-
-
Create Repository
-
{message}
-
- {inProgress ? 'Creating repository...' : 'Create repository'}
-
+
+
+
Create Repository
+
+ {
+ this.props.repository.hasDirectory()
+ ?
+ (
+ Initialize {this.props.workingDirectoryPath} with a
+ Git repository
+ )
+ : Initialize a new project directory with a Git repository
+ }
+
+ {this.props.repository.showGitTabInitInProgress()
+ ? 'Creating repository...' : 'Create repository'}
+
);
- } else {
- const isLoading = this.props.isLoading || this.props.repository.showGitTabLoading();
-
+ default:
return (
-
+
-
+
);
}
}
diff --git a/lib/views/github-header-view.js b/lib/views/github-header-view.js
new file mode 100644
index 0000000000..4838cd5d2a
--- /dev/null
+++ b/lib/views/github-header-view.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import {RemoteSetPropType} from '../prop-types';
+import RemoteSelector from './remote-selector';
+
+export default class GithubHeaderView extends React.Component {
+ static propTypes = {
+ avatarUrl: PropTypes.string,
+ remotes: RemoteSetPropType.isRequired,
+ currentRemoteName: PropTypes.string.isRequired,
+
+ handleRemoteSelect: PropTypes.func.isRequired,
+ }
+
+ render() {
+ const {avatarUrl} = this.props;
+ return (
+
+
+
+ null} />
+
+ );
+ }
+}
diff --git a/lib/views/github-tab-view.js b/lib/views/github-tab-view.js
index 6ce0dbddd2..c1cc3fb9e1 100644
--- a/lib/views/github-tab-view.js
+++ b/lib/views/github-tab-view.js
@@ -7,10 +7,14 @@ import {
} from '../prop-types';
import LoadingView from './loading-view';
import RemoteSelectorView from './remote-selector-view';
+import HeaderView from './header-view';
import RemoteContainer from '../containers/remote-container';
+import GithubHeaderContainer from '../containers/github-header-container';
+
export default class GitHubTabView extends React.Component {
static propTypes = {
+ project: PropTypes.object.isRequired,
workspace: PropTypes.object.isRequired,
remoteOperationObserver: OperationStateObserverPropType.isRequired,
loginModel: GithubLoginModelPropType.isRequired,
@@ -21,18 +25,20 @@ export default class GitHubTabView extends React.Component {
currentBranch: BranchPropType.isRequired,
remotes: RemoteSetPropType.isRequired,
currentRemote: RemotePropType.isRequired,
- manyRemotesAvailable: PropTypes.bool.isRequired,
+ isSelectingRemote: PropTypes.bool.isRequired,
aheadCount: PropTypes.number,
pushInProgress: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired,
handlePushBranch: PropTypes.func.isRequired,
handleRemoteSelect: PropTypes.func.isRequired,
+ changeProjectWorkingDirectory: PropTypes.func.isRequired,
}
render() {
return (
+ {this.renderHeader()}
{this.renderRemote()}
@@ -40,6 +46,23 @@ export default class GitHubTabView extends React.Component {
);
}
+ renderHeader() {
+ if (this.props.isLoading || !this.props.currentRemote.isPresent()) { return null; }
+
+ const {currentRemote} = this.props;
+ const currentRemoteName = currentRemote && currentRemote.name ? currentRemote.name : ' ';
+ return (
+
+ );
+ }
+
renderRemote() {
if (this.props.isLoading) {
return
;
@@ -66,7 +89,7 @@ export default class GitHubTabView extends React.Component {
);
}
- if (this.props.manyRemotesAvailable) {
+ if (this.props.isSelectingRemote) {
// No chosen remote, multiple remotes hosted on GitHub instances
return (
);
}
+
+ renderHeader() {
+ return (
+ this.props.changeProjectWorkingDirectory(e.target.value)}
+ currentProject={this.props.workingDirectory}
+ projectPaths={this.props.project.getPaths()}
+ />
+ );
+ }
}
diff --git a/lib/views/header-view.js b/lib/views/header-view.js
new file mode 100644
index 0000000000..b6347c8995
--- /dev/null
+++ b/lib/views/header-view.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import path from 'path';
+
+export default class HeaderView extends React.Component {
+ static propTypes = {
+ currentProject: PropTypes.string,
+ projectPaths: PropTypes.arrayOf(PropTypes.string),
+
+ handleProjectSelect: PropTypes.func.isRequired,
+ }
+
+ render() {
+ return (
+
+
+ {this.renderProjects()}
+
+
+ );
+ }
+
+ renderProjects = () => {
+ const projects = [];
+ for (const projectPath of this.props.projectPaths) {
+ projects.push({path.basename(projectPath)} );
+ }
+ return projects;
+ };
+}
diff --git a/lib/views/remote-selector.js b/lib/views/remote-selector.js
new file mode 100644
index 0000000000..809651010e
--- /dev/null
+++ b/lib/views/remote-selector.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import {RemoteSetPropType} from '../prop-types';
+
+export default class RemoteSelector extends React.Component {
+ static propTypes = {
+ remotes: RemoteSetPropType.isRequired,
+ currentRemoteName: PropTypes.string.isRequired,
+
+ handleRemoteSelect: PropTypes.func.isRequired,
+ }
+
+ render() {
+ return (
+
+ {this.renderRemotes()}
+
+ );
+ }
+
+ renderRemotes = () => {
+ const remoteOptions = [];
+ for (const remote of this.props.remotes) {
+ remoteOptions.push({remote.name} );
+ }
+ return remoteOptions;
+ };
+}
diff --git a/styles/project.less b/styles/project.less
new file mode 100644
index 0000000000..6dde98f812
--- /dev/null
+++ b/styles/project.less
@@ -0,0 +1,13 @@
+@import "variables";
+
+.github-Project {
+ display: flex;
+ align-items: center;
+ padding: @component-padding/2 @component-padding;
+ border-bottom: 1px solid @base-border-color;
+
+ &-path {
+ flex: 1;
+ }
+
+}
diff --git a/test/atom/decoration.test.js b/test/atom/decoration.test.js
index b31b040b5e..93d9ca244f 100644
--- a/test/atom/decoration.test.js
+++ b/test/atom/decoration.test.js
@@ -7,6 +7,7 @@ import Decoration from '../../lib/atom/decoration';
import AtomTextEditor from '../../lib/atom/atom-text-editor';
import Marker from '../../lib/atom/marker';
import MarkerLayer from '../../lib/atom/marker-layer';
+import ErrorBoundary from '../../lib/error-boundary';
describe('Decoration', function() {
let atomEnv, workspace, editor, marker;
@@ -128,15 +129,40 @@ describe('Decoration', function() {
assert.isNull(editor.gutterWithName(gutterName));
});
- it('throws an error if `gutterName` prop is not supplied for gutter decorations', function() {
- const app = (
-
-
- This is a subtree
-
-
- );
- assert.throws(() => mount(app), 'You are trying to decorate a gutter but did not supply gutterName prop.');
+ describe('throws an error', function() {
+ let errors;
+
+ // This consumes the error rather than printing it to console.
+ const onError = function(e) {
+ if (e.message === 'Uncaught Error: You are trying to decorate a gutter but did not supply gutterName prop.') {
+ errors.push(e.error);
+ e.preventDefault();
+ }
+ };
+
+ beforeEach(function() {
+ errors = [];
+ window.addEventListener('error', onError);
+ });
+
+ afterEach(function() {
+ errors = [];
+ window.removeEventListener('error', onError);
+ });
+
+ it('if `gutterName` prop is not supplied for gutter decorations', function() {
+ const app = (
+
+
+
+ This is a subtree
+
+
+
+ );
+ mount(app);
+ assert(errors[0], 'You are trying to decorate a gutter but did not supply gutterName prop.');
+ });
});
});
@@ -254,7 +280,7 @@ describe('Decoration', function() {
it('decorates a parent Marker on a prop-provided TextEditor', function() {
mount(
-
+
,
);
@@ -266,7 +292,7 @@ describe('Decoration', function() {
let layerID = null;
mount(
{ layerID = id; }}>
-
+
,
);
diff --git a/test/atom/marker.test.js b/test/atom/marker.test.js
index 297ce3333a..0fb62032f3 100644
--- a/test/atom/marker.test.js
+++ b/test/atom/marker.test.js
@@ -6,6 +6,7 @@ import Marker from '../../lib/atom/marker';
import AtomTextEditor from '../../lib/atom/atom-text-editor';
import RefHolder from '../../lib/models/ref-holder';
import MarkerLayer from '../../lib/atom/marker-layer';
+import ErrorBoundary from '../../lib/error-boundary';
describe('Marker', function() {
let atomEnv, workspace, editor, marker, markerID;
@@ -135,8 +136,31 @@ describe('Marker', function() {
assert.strictEqual(instance.markerHolder.get(), external);
});
- it('fails on construction if its ID is invalid', function() {
- assert.throws(() => mount( ), /Invalid marker ID: 67/);
+ describe('fails on construction', function() {
+ let errors;
+
+ // This consumes the error rather than printing it to console.
+ const onError = function(e) {
+ if (e.message === 'Uncaught Error: Invalid marker ID: 67') {
+ errors.push(e.error);
+ e.preventDefault();
+ }
+ };
+
+ beforeEach(function() {
+ errors = [];
+ window.addEventListener('error', onError);
+ });
+
+ afterEach(function() {
+ errors = [];
+ window.removeEventListener('error', onError);
+ });
+
+ it('if its ID is invalid', function() {
+ mount( );
+ assert(errors[0], 'Error: Invalid marker ID: 67');
+ });
});
it('does not destroy its marker on unmount', function() {
diff --git a/test/containers/comment-decorations-container.test.js b/test/containers/comment-decorations-container.test.js
index 6ee4eb3213..a00f9bb1e3 100644
--- a/test/containers/comment-decorations-container.test.js
+++ b/test/containers/comment-decorations-container.test.js
@@ -184,8 +184,10 @@ describe('CommentDecorationsContainer', function() {
it('renders nothing if query errors', function() {
const tokenWrapper = localRepoWrapper.find(ObserveModel).renderProp('children')('1234');
+ const stub = sinon.stub(console, 'warn');
const resultWrapper = tokenWrapper.find(QueryRenderer).renderProp('render')({
error: 'oh noes', props: null, retry: () => {}});
+ stub.restore();
assert.isTrue(resultWrapper.isEmptyRender());
});
@@ -254,12 +256,15 @@ describe('CommentDecorationsContainer', function() {
const resultWrapper = tokenWrapper.find(QueryRenderer).renderProp('render')({
error: null, props, retry: () => {},
});
+ const stub = sinon.stub(console, 'warn');
const reviewsWrapper = resultWrapper.find(AggregatedReviewsContainer).renderProp('children')({
errors: [new Error('ahhhh')],
summaries: [],
commentThreads: [],
});
+ stub.restore();
+
assert.isTrue(reviewsWrapper.isEmptyRender());
});
@@ -341,9 +346,11 @@ describe('CommentDecorationsContainer', function() {
{thread: {id: 'thread0'}, comments: [{id: 'comment0', path: 'a.txt'}, {id: 'comment1', path: 'a.txt'}]},
],
});
+ const stub = sinon.stub(console, 'warn');
const patchWrapper = reviewsWrapper.find(PullRequestPatchContainer).renderProp('children')(
new Error('oops'), null,
);
+ stub.restore();
assert.isTrue(patchWrapper.isEmptyRender());
});
diff --git a/test/containers/current-pull-request-container.test.js b/test/containers/current-pull-request-container.test.js
index 7d37e017c2..8370c82e44 100644
--- a/test/containers/current-pull-request-container.test.js
+++ b/test/containers/current-pull-request-container.test.js
@@ -45,6 +45,7 @@ describe('CurrentPullRequestContainer', function() {
pushInProgress={false}
onOpenIssueish={() => {}}
+ onOpenReviews={() => {}}
onCreatePr={() => {}}
{...overrideProps}
diff --git a/test/containers/issueish-detail-container.test.js b/test/containers/issueish-detail-container.test.js
index 417f65fc70..cc726c58d1 100644
--- a/test/containers/issueish-detail-container.test.js
+++ b/test/containers/issueish-detail-container.test.js
@@ -54,6 +54,7 @@ describe('IssueishDetailContainer', function() {
switchToIssueish: () => {},
onTitleChange: () => {},
destroy: () => {},
+ reportRelayError: () => {},
itemType: IssueishDetailItem,
refEditor: new RefHolder(),
diff --git a/test/containers/issueish-search-container.test.js b/test/containers/issueish-search-container.test.js
index e9119f1e5d..f3763b17d5 100644
--- a/test/containers/issueish-search-container.test.js
+++ b/test/containers/issueish-search-container.test.js
@@ -26,6 +26,7 @@ describe('IssueishSearchContainer', function() {
onOpenIssueish={() => {}}
onOpenSearch={() => {}}
+ onOpenReviews={() => {}}
{...overrideProps}
/>
@@ -86,31 +87,49 @@ describe('IssueishSearchContainer', function() {
await promise;
});
- it('passes an empty result list and an error prop to the controller when errored', async function() {
- expectRelayQuery({
- name: 'issueishSearchContainerQuery',
- variables: {
- query: 'type:pr',
- first: 20,
- checkSuiteCount: CHECK_SUITE_PAGE_SIZE,
- checkSuiteCursor: null,
- checkRunCount: CHECK_RUN_PAGE_SIZE,
- checkRunCursor: null,
- },
- }, op => {
- return relayResponseBuilder(op)
- .addError('uh oh')
- .build();
- }).resolve();
+ describe('when the query errors', function() {
+ let stub;
+ // Consumes the failing Relay Query console error
+ before(function() {
+ stub = sinon.stub(console, 'error');
+ // eslint-disable-next-line no-console
+ console.error.withArgs(
+ 'Error encountered in subquery',
+ sinon.match.defined.and(sinon.match.hasNested('errors[0].message', sinon.match('uh oh'))),
+ ).callsFake(() => {});
+ // eslint-disable-next-line no-console
+ console.error.callThrough();
+ });
- const wrapper = mount(buildApp({}));
+ it('passes an empty result list and an error prop to the controller', async function() {
+ expectRelayQuery({
+ name: 'issueishSearchContainerQuery',
+ variables: {
+ query: 'type:pr',
+ first: 20,
+ checkSuiteCount: CHECK_SUITE_PAGE_SIZE,
+ checkSuiteCursor: null,
+ checkRunCount: CHECK_RUN_PAGE_SIZE,
+ checkRunCursor: null,
+ },
+ }, op => {
+ return relayResponseBuilder(op)
+ .addError('uh oh')
+ .build();
+ }).resolve();
+
+ const wrapper = mount(buildApp({}));
+ await assert.async.isTrue(
+ wrapper.update().find('BareIssueishListController').filterWhere(n => !n.prop('isLoading')).exists(),
+ );
+ const controller = wrapper.find('BareIssueishListController');
+ assert.deepEqual(controller.prop('error').errors, [{message: 'uh oh'}]);
+ assert.lengthOf(controller.prop('results'), 0);
+ });
- await assert.async.isTrue(
- wrapper.update().find('BareIssueishListController').filterWhere(n => !n.prop('isLoading')).exists(),
- );
- const controller = wrapper.find('BareIssueishListController');
- assert.deepEqual(controller.prop('error').errors, [{message: 'uh oh'}]);
- assert.lengthOf(controller.prop('results'), 0);
+ after(function() {
+ stub.restore();
+ });
});
it('passes results to the controller', async function() {
diff --git a/test/containers/reviews-container.test.js b/test/containers/reviews-container.test.js
index 903922ec00..49e43678a6 100644
--- a/test/containers/reviews-container.test.js
+++ b/test/containers/reviews-container.test.js
@@ -39,7 +39,9 @@ describe('ReviewsContainer', function() {
queryData = {
repository: {
- pullRequest: {},
+ pullRequest: {
+ headRefOid: '0000000000000000000000000000000000000000',
+ },
},
};
});
diff --git a/test/controllers/changed-file-controller.test.js b/test/controllers/changed-file-controller.test.js
index 6247764cdf..ab5b808173 100644
--- a/test/controllers/changed-file-controller.test.js
+++ b/test/controllers/changed-file-controller.test.js
@@ -27,6 +27,9 @@ describe('ChangedFileController', function() {
keymaps: atomEnv.keymaps,
tooltips: atomEnv.tooltips,
config: atomEnv.config,
+ multiFilePatch: {
+ getFilePatches: () => {},
+ },
destroy: () => {},
undoLastDiscard: () => {},
diff --git a/test/controllers/github-tab-controller.test.js b/test/controllers/github-tab-controller.test.js
index 5843176be0..151781d564 100644
--- a/test/controllers/github-tab-controller.test.js
+++ b/test/controllers/github-tab-controller.test.js
@@ -57,21 +57,21 @@ describe('GitHubTabController', function() {
const allRemotes = new RemoteSet([dotcom0, dotcom1, nonDotcom]);
const wrapper = shallow(buildApp({allRemotes, selectedRemoteName: 'yes1'}));
assert.strictEqual(wrapper.find('GitHubTabView').prop('currentRemote'), dotcom1);
- assert.isFalse(wrapper.find('GitHubTabView').prop('manyRemotesAvailable'));
+ assert.isFalse(wrapper.find('GitHubTabView').prop('isSelectingRemote'));
});
it('uses a single GitHub-hosted remote', function() {
const allRemotes = new RemoteSet([dotcom0, nonDotcom]);
const wrapper = shallow(buildApp({allRemotes}));
assert.strictEqual(wrapper.find('GitHubTabView').prop('currentRemote'), dotcom0);
- assert.isFalse(wrapper.find('GitHubTabView').prop('manyRemotesAvailable'));
+ assert.isFalse(wrapper.find('GitHubTabView').prop('isSelectingRemote'));
});
it('indicates when multiple remotes are available', function() {
const allRemotes = new RemoteSet([dotcom0, dotcom1]);
const wrapper = shallow(buildApp({allRemotes}));
assert.isFalse(wrapper.find('GitHubTabView').prop('currentRemote').isPresent());
- assert.isTrue(wrapper.find('GitHubTabView').prop('manyRemotesAvailable'));
+ assert.isTrue(wrapper.find('GitHubTabView').prop('isSelectingRemote'));
});
});
diff --git a/test/fixtures/props/git-tab-props.js b/test/fixtures/props/git-tab-props.js
index fd3dfa2e65..dc26fe4732 100644
--- a/test/fixtures/props/git-tab-props.js
+++ b/test/fixtures/props/git-tab-props.js
@@ -25,6 +25,7 @@ export function gitTabItemProps(atomEnv, repository, overrides = {}) {
discardWorkDirChangesForPaths: noop,
openFiles: noop,
openInitializeDialog: noop,
+ changeProjectWorkingDirectory: noop,
...overrides
};
}
diff --git a/test/fixtures/props/github-tab-props.js b/test/fixtures/props/github-tab-props.js
index d5ae646f10..de4ccf1695 100644
--- a/test/fixtures/props/github-tab-props.js
+++ b/test/fixtures/props/github-tab-props.js
@@ -12,6 +12,10 @@ export function gitHubTabItemProps(atomEnv, repository, overrides = {}) {
workspace: atomEnv.workspace,
repository,
loginModel: new GithubLoginModel(InMemoryStrategy),
+ project: {
+ getPaths: () => [],
+ },
+ changeProjectWorkingDirectory: () => {},
...overrides,
};
}
@@ -39,6 +43,7 @@ export function gitHubTabControllerProps(atomEnv, repository, overrides = {}) {
export function gitHubTabViewProps(atomEnv, repository, overrides = {}) {
return {
+ project: atomEnv.project,
workspace: atomEnv.workspace,
remoteOperationObserver: new OperationStateObserver(repository, PUSH, PULL, FETCH),
loginModel: new GithubLoginModel(InMemoryStrategy),
@@ -49,12 +54,13 @@ export function gitHubTabViewProps(atomEnv, repository, overrides = {}) {
currentBranch: nullBranch,
remotes: new RemoteSet(),
currentRemote: nullRemote,
- manyRemotesAvailable: false,
+ isSelectingRemote: false,
aheadCount: 0,
pushInProgress: false,
handlePushBranch: () => {},
handleRemoteSelect: () => {},
+ changeProjectWorkingDirectory: () => {},
...overrides,
};
diff --git a/test/github-package.test.js b/test/github-package.test.js
index 496c52550b..dfb0308011 100644
--- a/test/github-package.test.js
+++ b/test/github-package.test.js
@@ -141,14 +141,12 @@ describe('GithubPackage', function() {
describe('activate()', function() {
let atomEnv, githubPackage;
- let workspace, project, config;
- let configDirPath, contextPool;
+ let project, config, configDirPath, contextPool;
beforeEach(async function() {
({
atomEnv, githubPackage,
- workspace, project,
- config, configDirPath, contextPool,
+ project, config, configDirPath, contextPool,
} = await buildAtomEnvironmentAndGithubPackage(global.buildAtomEnvironmentAndGithubPackage));
});
@@ -158,7 +156,7 @@ describe('GithubPackage', function() {
atomEnv.destroy();
});
- describe('with no project, state, or active pane', function() {
+ describe('with no projects or state', function() {
beforeEach(async function() {
await contextUpdateAfter(githubPackage, () => githubPackage.activate());
});
@@ -187,7 +185,7 @@ describe('GithubPackage', function() {
});
describe('with only projects', function() {
- let workdirPath1, workdirPath2, nonRepositoryPath;
+ let workdirPath1, workdirPath2, nonRepositoryPath, context1;
beforeEach(async function() {
([workdirPath1, workdirPath2, nonRepositoryPath] = await Promise.all([
cloneRepository('three-files'),
@@ -197,10 +195,15 @@ describe('GithubPackage', function() {
project.setPaths([workdirPath1, workdirPath2, nonRepositoryPath]);
await contextUpdateAfter(githubPackage, () => githubPackage.activate());
+
+ context1 = contextPool.getContext(workdirPath1);
});
- it('uses an undetermined repository context', function() {
- assert.isTrue(githubPackage.getActiveRepository().isUndetermined());
+ it('uses the first project\'s context', function() {
+ assert.isTrue(context1.isPresent());
+ assert.strictEqual(githubPackage.getActiveWorkdir(), workdirPath1);
+ assert.strictEqual(context1.getRepository(), githubPackage.getActiveRepository());
+ assert.strictEqual(context1.getResolutionProgress(), githubPackage.getActiveResolutionProgress());
});
it('creates contexts from preexisting projects', function() {
@@ -210,28 +213,6 @@ describe('GithubPackage', function() {
});
});
- describe('with projects and an active pane', function() {
- let workdirPath1, workdirPath2, context2;
- beforeEach(async function() {
- ([workdirPath1, workdirPath2] = await Promise.all([
- cloneRepository('three-files'),
- cloneRepository('three-files'),
- ]));
- project.setPaths([workdirPath1, workdirPath2]);
- await workspace.open(path.join(workdirPath2, 'a.txt'));
-
- await contextUpdateAfter(githubPackage, () => githubPackage.activate());
- context2 = contextPool.getContext(workdirPath2);
- });
-
- it('uses the active pane\'s context', function() {
- assert.isTrue(context2.isPresent());
- assert.strictEqual(context2.getRepository(), githubPackage.getActiveRepository());
- assert.strictEqual(context2.getResolutionProgress(), githubPackage.getActiveResolutionProgress());
- assert.strictEqual(githubPackage.getActiveWorkdir(), workdirPath2);
- });
- });
-
describe('with projects and state', function() {
let workdirPath1, workdirPath2, workdirPath3;
beforeEach(async function() {
@@ -256,31 +237,7 @@ describe('GithubPackage', function() {
});
});
- describe('with projects, state, and an active pane', function() {
- let workdirPath1, workdirPath2, context2;
- beforeEach(async function() {
- ([workdirPath1, workdirPath2] = await Promise.all([
- cloneRepository('three-files'),
- cloneRepository('three-files'),
- ]));
- project.setPaths([workdirPath1, workdirPath2]);
- await workspace.open(path.join(workdirPath2, 'b.txt'));
-
- await contextUpdateAfter(githubPackage, () => githubPackage.activate({
- activeRepositoryPath: workdirPath1,
- }));
- context2 = contextPool.getContext(workdirPath2);
- });
-
- it('uses the active pane\'s context', function() {
- assert.isTrue(context2.isPresent());
- assert.strictEqual(context2.getRepository(), githubPackage.getActiveRepository());
- assert.strictEqual(context2.getResolutionProgress(), githubPackage.getActiveResolutionProgress());
- assert.strictEqual(githubPackage.getActiveWorkdir(), workdirPath2);
- });
- });
-
- describe('with 1 project and state', function() {
+ describe('with 1 project and absent state', function() {
let workdirPath1, workdirPath2, context1;
beforeEach(async function() {
([workdirPath1, workdirPath2] = await Promise.all([
@@ -394,14 +351,12 @@ describe('GithubPackage', function() {
describe('scheduleActiveContextUpdate()', function() {
let atomEnv, githubPackage;
- let workspace, project, commands;
- let contextPool;
+ let project, contextPool;
beforeEach(async function() {
({
atomEnv, githubPackage,
- workspace, project, commands,
- contextPool,
+ project, contextPool,
} = await buildAtomEnvironmentAndGithubPackage(global.buildAtomEnvironmentAndGithubPackage));
});
@@ -434,8 +389,12 @@ describe('GithubPackage', function() {
await contextUpdateAfter(githubPackage, () => githubPackage.activate());
});
- it('uses an absent context', function() {
- assert.isTrue(githubPackage.getActiveRepository().isUndetermined());
+ it('uses the first project\'s context', function() {
+ const context1 = contextPool.getContext(workdirPath1);
+ assert.isTrue(context1.isPresent());
+ assert.strictEqual(githubPackage.getActiveWorkdir(), workdirPath1);
+ assert.strictEqual(context1.getRepository(), githubPackage.getActiveRepository());
+ assert.strictEqual(context1.getResolutionProgress(), githubPackage.getActiveResolutionProgress());
});
it('has no contexts for projects that are not open', function() {
@@ -471,19 +430,8 @@ describe('GithubPackage', function() {
assert.isFalse(contextPool.getContext(workdirPath1).isPresent());
});
- it('use an absent guess repo', function() {
- assert.isTrue(githubPackage.getActiveRepository().isAbsentGuess());
- });
- });
-
- describe('when an active pane is opened', function() {
- beforeEach(async function() {
- await contextUpdateAfter(githubPackage, () => workspace.open(path.join(workdirPath2, 'b.txt')));
- });
-
- it('uses the new active pane\'s context', function() {
- const repository2 = contextPool.getContext(workdirPath2).getRepository();
- assert.strictEqual(githubPackage.getActiveRepository(), repository2);
+ it('uses an absent repo', function() {
+ assert.isTrue(githubPackage.getActiveRepository().isAbsent());
});
});
});
@@ -503,11 +451,12 @@ describe('GithubPackage', function() {
resolutionMergeConflict.reportMarkerCount('modified-on-both-ours.txt', remainingMarkerCount);
});
- describe('when opening an in-progress merge-conflict project', function() {
+ describe('when selecting an in-progress merge-conflict project', function() {
let resolutionMergeConflict;
beforeEach(async function() {
- await workspace.open(path.join(workdirMergeConflict, 'modified-on-both-ours.txt'));
- await githubPackage.scheduleActiveContextUpdate();
+ await githubPackage.scheduleActiveContextUpdate({
+ activeRepositoryPath: workdirMergeConflict,
+ });
resolutionMergeConflict = contextPool.getContext(workdirMergeConflict).getResolutionProgress();
});
@@ -527,8 +476,9 @@ describe('GithubPackage', function() {
describe('when opening a no-conflict repository project', function() {
let resolutionNoConflict;
beforeEach(async function() {
- await workspace.open(path.join(workdirNoConflict, 'b.txt'));
- await githubPackage.scheduleActiveContextUpdate();
+ await githubPackage.scheduleActiveContextUpdate({
+ activeRepositoryPath: workdirNoConflict,
+ });
resolutionNoConflict = contextPool.getContext(workdirNoConflict).getResolutionProgress();
});
@@ -543,8 +493,9 @@ describe('GithubPackage', function() {
describe('when opening a non-repository project', function() {
beforeEach(async function() {
- await workspace.open(path.join(nonRepositoryPath, 'c.txt'));
- await githubPackage.scheduleActiveContextUpdate();
+ await githubPackage.scheduleActiveContextUpdate({
+ activeRepositoryPath: nonRepositoryPath,
+ });
});
it('has no active resolution progress', function() {
@@ -553,28 +504,27 @@ describe('GithubPackage', function() {
});
});
- describe('with projects, state, and an active pane', function() {
- let workdirPath1, workdirPath2, workdirPath3, context2;
+ describe('with projects and absent state', function() {
+ let workdirPath1, workdirPath2, workdirPath3, context1;
beforeEach(async function() {
([workdirPath1, workdirPath2, workdirPath3] = await Promise.all([
cloneRepository('three-files'),
cloneRepository('three-files'),
cloneRepository('three-files'),
]));
- project.setPaths([workdirPath1]);
- await workspace.open(path.join(workdirPath2, 'a.txt'));
+ project.setPaths([workdirPath1, workdirPath2]);
await githubPackage.scheduleActiveContextUpdate({
activeRepositoryPath: workdirPath3,
});
- context2 = contextPool.getContext(workdirPath2);
+ context1 = contextPool.getContext(workdirPath1);
});
- it('uses the active pane\'s context', function() {
- assert.isTrue(context2.isPresent());
- assert.strictEqual(context2.getRepository(), githubPackage.getActiveRepository());
- assert.strictEqual(context2.getResolutionProgress(), githubPackage.getActiveResolutionProgress());
- assert.strictEqual(githubPackage.getActiveWorkdir(), workdirPath2);
+ it('uses the first project\'s context', function() {
+ assert.isTrue(context1.isPresent());
+ assert.strictEqual(context1.getRepository(), githubPackage.getActiveRepository());
+ assert.strictEqual(context1.getResolutionProgress(), githubPackage.getActiveResolutionProgress());
+ assert.strictEqual(githubPackage.getActiveWorkdir(), workdirPath1);
});
});
@@ -647,69 +597,6 @@ describe('GithubPackage', function() {
});
});
- describe('with an active pane in a non-repository project', function() {
- beforeEach(async function() {
- const nonRepositoryPath = await fs.realpath(temp.mkdirSync());
- const workdir = await cloneRepository('three-files');
- project.setPaths([nonRepositoryPath, workdir]);
- await fs.writeFile(path.join(nonRepositoryPath, 'a.txt'), 'stuff', {encoding: 'utf8'});
-
- await workspace.open(path.join(nonRepositoryPath, 'a.txt'));
-
- await githubPackage.scheduleActiveContextUpdate();
- });
-
- it('uses and absent context', function() {
- assert.isTrue(githubPackage.getActiveRepository().isAbsent());
- });
- });
-
- describe('with multiple pane items', function() {
- let workdirPath1, workdirPath2, context1;
-
- beforeEach(async function() {
- ([workdirPath1, workdirPath2] = await Promise.all([
- cloneRepository('three-files'),
- cloneRepository('three-files'),
- ]));
- project.setPaths([workdirPath2]);
-
- await workspace.open(path.join(workdirPath1, 'a.txt'));
- commands.dispatch(atomEnv.views.getView(workspace), 'tree-view:toggle-focus');
- workspace.getLeftDock().activate();
-
- await githubPackage.scheduleActiveContextUpdate();
- context1 = contextPool.getContext(workdirPath1);
- });
-
- it('uses the active pane\'s context', function() {
- assert.isTrue(context1.isPresent());
- assert.strictEqual(context1.getRepository(), githubPackage.getActiveRepository());
- assert.strictEqual(context1.getResolutionProgress(), githubPackage.getActiveResolutionProgress());
- assert.strictEqual(githubPackage.getActiveWorkdir(), workdirPath1);
- });
- });
-
- describe('with an active context', function() {
- let workdirPath1, workdirPath2;
- beforeEach(async function() {
- ([workdirPath1, workdirPath2] = await Promise.all([
- cloneRepository('three-files'),
- cloneRepository('three-files'),
- ]));
- project.setPaths([workdirPath1, workdirPath2]);
-
- contextPool.set([workdirPath1, workdirPath2]);
- githubPackage.setActiveContext(contextPool.getContext(workdirPath1));
-
- await githubPackage.scheduleActiveContextUpdate();
- });
-
- it('uses the active context', function() {
- assert.strictEqual(githubPackage.getActiveWorkdir(), workdirPath1);
- });
- });
-
describe('with a repository project\'s subdirectory', function() {
let workdirPath;
beforeEach(async function() {
@@ -759,7 +646,6 @@ describe('GithubPackage', function() {
const symlinkPath = (await fs.realpath(temp.mkdirSync())) + '-symlink';
fs.symlinkSync(workdirPath, symlinkPath);
project.setPaths([symlinkPath]);
- await workspace.open(path.join(symlinkPath, 'a.txt'));
await githubPackage.scheduleActiveContextUpdate();
});
diff --git a/test/views/git-tab-view.test.js b/test/views/git-tab-view.test.js
index 93710af685..4ad44b2f71 100644
--- a/test/views/git-tab-view.test.js
+++ b/test/views/git-tab-view.test.js
@@ -282,4 +282,13 @@ describe('GitTabView', function() {
wrapper.instance().focusAndSelectRecentCommit();
assert.isTrue(setFocus.calledWith(GitTabView.focus.RECENT_COMMIT));
});
+
+ it('calls changeProjectWorkingDirectory when a project is selected', async function() {
+ const select = sinon.spy();
+ const path = 'test/path';
+ const wrapper = mount(await buildApp({changeProjectWorkingDirectory: select}));
+ wrapper.find('.github-Project-path.input-select').simulate('change', {target: {value: path}});
+ assert.isTrue(select.calledWith(path));
+ wrapper.unmount();
+ });
});
diff --git a/test/views/github-tab-view.test.js b/test/views/github-tab-view.test.js
index 37b494df63..2bfe3a0a41 100644
--- a/test/views/github-tab-view.test.js
+++ b/test/views/github-tab-view.test.js
@@ -1,5 +1,5 @@
import React from 'react';
-import {shallow} from 'enzyme';
+import {shallow, mount} from 'enzyme';
import {gitHubTabViewProps} from '../fixtures/props/github-tab-props';
import Repository from '../../lib/models/repository';
@@ -48,7 +48,7 @@ describe('GitHubTabView', function() {
const wrapper = shallow(buildApp({
remotes,
currentRemote: nullRemote,
- manyRemotesAvailable: true,
+ isSelectingRemote: true,
handleRemoteSelect,
}));
@@ -60,7 +60,16 @@ describe('GitHubTabView', function() {
});
it('renders a static message when no remotes are available', function() {
- const wrapper = shallow(buildApp({currentRemote: nullRemote, manyRemotesAvailable: false}));
+ const wrapper = shallow(buildApp({currentRemote: nullRemote, isSelectingRemote: false}));
assert.isTrue(wrapper.find('.github-GitHub-noRemotes').exists());
});
+
+ it('calls changeProjectWorkingDirectory when a project is selected', function() {
+ const select = sinon.spy();
+ const path = 'test/path';
+ const wrapper = mount(buildApp({changeProjectWorkingDirectory: select}));
+ wrapper.find('.github-Project-path.input-select').simulate('change', {target: {value: path}});
+ assert.isTrue(select.calledWith(path));
+ wrapper.unmount();
+ });
});
diff --git a/test/views/header-view.test.js b/test/views/header-view.test.js
new file mode 100644
index 0000000000..77d9868202
--- /dev/null
+++ b/test/views/header-view.test.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import {shallow} from 'enzyme';
+import path from 'path';
+
+import HeaderView from '../../lib/views/header-view';
+
+describe('HeaderView', function() {
+ let wrapper, select;
+ const path1 = 'test/path/project1';
+ const path2 = '2nd-test/path/project2';
+ const paths = [path1, path2];
+
+ beforeEach(function() {
+ select = sinon.spy();
+ wrapper = shallow( );
+ });
+
+ it('renders an option for all given project paths', function() {
+ wrapper.find('option').forEach(function(node, index) {
+ assert.strictEqual(node.props().value, paths[index]);
+ assert.strictEqual(node.children().text(), path.basename(paths[index]));
+ });
+ });
+
+ it('selects the current project\'s path', function() {
+ assert.strictEqual(wrapper.find('select').props().value, path2);
+ });
+
+ it('calls handleProjectSelect on select', function() {
+ wrapper.find('select').simulate('change', {target: {value: path1}});
+ assert.isTrue(select.calledWith({target: {value: path1}}));
+ });
+});