this.props.onChangeMessage && this.props.onChangeMessage(this.editor.getText())),
this.editor.onDidChangeCursorPosition(() => { etch.update(this); }),
- this.editor.getBuffer().onDidChangeText(() => { etch.update(this); }),
props.commandRegistry.add(this.element, {'github:commit': () => this.commit()}),
);
- this.setMessageAndAmendStatus();
- this.updateStateForRepository();
const grammar = atom.grammars.grammarForScopeName(COMMIT_GRAMMAR_SCOPE);
if (grammar) {
@@ -38,8 +39,12 @@ export default class CommitView {
}
update(props) {
+ const previousMessage = this.props.message;
this.props = {...this.props, ...props};
- this.setMessageAndAmendStatus();
+ const newMessage = this.props.message;
+ if (this.editor && previousMessage !== newMessage && this.editor.getText() !== newMessage) {
+ this.editor.setText(newMessage);
+ }
return etch.update(this);
}
@@ -52,33 +57,6 @@ export default class CommitView {
this.grammarSubscription.dispose();
}
- setMessageAndAmendStatus() {
- const viewState = this.props.viewState || {};
- const message = viewState.message || '';
- if (this.props.message && message === '') {
- this.editor.setText(this.props.message);
- this.editor.setCursorBufferPosition([0, 0]);
- } else {
- this.editor.setText(message || '');
- if (viewState.cursorPosition) { this.editor.setCursorBufferPosition(viewState.cursorPosition); }
- }
- this.refs.amend.checked = Boolean(viewState.amendInProgress);
- }
-
- updateStateForRepository() {
- if (this.props.viewState) {
- Object.assign(this.props.viewState, {
- message: this.editor.getText(),
- cursorPosition: this.editor.getCursorBufferPosition(),
- amendInProgress: this.refs.amend.checked,
- });
- }
- }
-
- readAfterUpdate() {
- this.updateStateForRepository();
- }
-
render() {
let remainingCharsClassName = '';
if (this.getRemainingCharacters() < 0) {
@@ -104,10 +82,16 @@ export default class CommitView {
onclick={this.abortMerge}
style={{display: this.props.isMerging ? '' : 'none'}}>Abort Merge
@@ -118,55 +102,18 @@ export default class CommitView {
);
}
- async abortMerge() {
- const choice = atom.confirm({
- message: 'Abort merge',
- detailedMessage: 'Are you sure?',
- buttons: ['Abort', 'Cancel'],
- });
- if (choice !== 0) { return null; }
-
- try {
- await this.props.abortMerge();
- this.editor.setText('');
- } catch (e) {
- if (e.code === 'EDIRTYSTAGED') {
- this.props.notificationManager.addError(`Cannot abort because ${e.path} is both dirty and staged.`);
- }
- }
- return etch.update(this);
+ abortMerge() {
+ this.props.abortMerge();
}
- async handleAmendBoxClick() {
- const checked = this.refs.amend.checked;
- const viewState = this.props.viewState || {};
- viewState.amendInProgress = checked;
- if (checked) {
- viewState.messagePriorToAmending = this.editor.getText();
- const lastCommitMessage = this.props.lastCommit ? this.props.lastCommit.message : '';
- this.editor.setText(lastCommitMessage);
- } else {
- this.editor.setText(viewState.messagePriorToAmending || '');
- }
- this.editor.setCursorBufferPosition([0, 0]);
- if (this.props.setAmending) { await this.props.setAmending(checked); }
- return etch.update(this);
+ handleAmendBoxClick() {
+ this.props.setAmending(this.refs.amend.checked);
}
- async commit() {
+ commit() {
if (this.isCommitButtonEnabled()) {
- try {
- await this.props.commit(this.editor.getText(), {amend: this.refs.amend.checked});
- this.editor.setText('');
- this.editor.getBuffer().clearUndoStack();
- } catch (e) {
- if (e.code === 'ECONFLICT') {
- this.props.notificationManager.addError('Cannot commit without resolving all the merge conflicts first.');
- }
- }
+ this.props.commit(this.editor.getText());
}
- this.refs.amend.checked = false;
- return etch.update(this);
}
getRemainingCharacters() {
diff --git a/lib/views/git-panel-view.js b/lib/views/git-panel-view.js
index e5d175894a..755aaa2b09 100644
--- a/lib/views/git-panel-view.js
+++ b/lib/views/git-panel-view.js
@@ -5,7 +5,7 @@
import etch from 'etch';
import StagingView from './staging-view';
-import CommitView from './commit-view';
+import CommitViewController from '../controllers/commit-view-controller';
export default class GitPanelView {
constructor(props) {
@@ -46,25 +46,21 @@ export default class GitPanelView {
lastCommit={this.props.lastCommit}
isAmending={this.props.isAmending}
/>
- 0}
mergeConflictsExist={this.props.mergeConflicts.length > 0}
commit={this.props.commit}
+ amending={this.props.amending}
setAmending={this.props.setAmending}
- disableCommitButton={this}
abortMerge={this.props.abortMerge}
branchName={this.props.branchName}
commandRegistry={this.props.commandRegistry}
- notificationManager={this.props.notificationManager}
- maximumCharacterLimit={72}
- message={this.props.mergeMessage}
+ mergeMessage={this.props.mergeMessage}
isMerging={this.props.isMerging}
isAmending={this.props.isAmending}
lastCommit={this.props.lastCommit}
repository={this.props.repository}
- viewState={this.props.viewState}
/>
);
diff --git a/test/controllers/commit-view-controller.test.js b/test/controllers/commit-view-controller.test.js
new file mode 100644
index 0000000000..9ed68c2ded
--- /dev/null
+++ b/test/controllers/commit-view-controller.test.js
@@ -0,0 +1,84 @@
+/** @babel */
+
+import CommitViewController from '../../lib/controllers/commit-view-controller';
+import {cloneRepository, buildRepository} from '../helpers';
+
+describe('CommitViewController', () => {
+ let atomEnvironment, commandRegistry;
+
+ beforeEach(() => {
+ atomEnvironment = global.buildAtomEnvironment();
+ commandRegistry = atomEnvironment.commands;
+ });
+
+ afterEach(() => {
+ atomEnvironment.destroy();
+ });
+
+ it('correctly updates state when switching repos', async () => {
+ const workdirPath1 = await cloneRepository('three-files');
+ const repository1 = await buildRepository(workdirPath1);
+ const workdirPath2 = await cloneRepository('three-files');
+ const repository2 = await buildRepository(workdirPath2);
+ const controller = new CommitViewController({commandRegistry, repository: repository1});
+
+ assert.equal(controller.regularCommitMessage, '');
+ assert.equal(controller.amendingCommitMessage, '');
+
+ controller.regularCommitMessage = 'regular message 1';
+ controller.amendingCommitMessage = 'amending message 1';
+
+ await controller.update({repository: repository2});
+ assert.equal(controller.regularCommitMessage, '');
+ assert.equal(controller.amendingCommitMessage, '');
+
+ await controller.update({repository: repository1});
+ assert.equal(controller.regularCommitMessage, 'regular message 1');
+ assert.equal(controller.amendingCommitMessage, 'amending message 1');
+ });
+
+ describe('the passed commit message', () => {
+ let controller, commitView, lastCommit;
+ beforeEach(async () => {
+ const workdirPath = await cloneRepository('three-files');
+ const repository = await buildRepository(workdirPath);
+ controller = new CommitViewController({commandRegistry, repository});
+ commitView = controller.refs.commitView;
+ lastCommit = {sha: 'a1e23fd45', message: 'last commit message'};
+ });
+
+ it('is set to the regularCommitMessage in the default case', async () => {
+ controller.regularCommitMessage = 'regular message';
+ await controller.update();
+ assert.equal(commitView.props.message, 'regular message');
+ });
+
+ describe('when isAmending is true', () => {
+ it('is set to the last commits message if amendingCommitMessage is blank', async () => {
+ controller.amendingCommitMessage = 'amending commit message';
+ await controller.update({isAmending: true, lastCommit});
+ assert.equal(commitView.props.message, 'amending commit message');
+ });
+
+ it('is set to amendingCommitMessage if it is set', async () => {
+ controller.amendingCommitMessage = 'amending commit message';
+ await controller.update({isAmending: true, lastCommit});
+ assert.equal(commitView.props.message, 'amending commit message');
+ });
+ });
+
+ describe('when a merge message is defined', () => {
+ it('is set to the merge message if regularCommitMessage is blank', async () => {
+ controller.regularCommitMessage = '';
+ await controller.update({mergeMessage: 'merge conflict!'});
+ assert.equal(commitView.props.message, 'merge conflict!');
+ });
+
+ it('is set to regularCommitMessage if it is set', async () => {
+ controller.regularCommitMessage = 'regular commit message';
+ await controller.update({mergeMessage: 'merge conflict!'});
+ assert.equal(commitView.props.message, 'regular commit message');
+ });
+ });
+ });
+});
diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js
index c9ae689a1d..f062145159 100644
--- a/test/controllers/file-patch-controller.test.js
+++ b/test/controllers/file-patch-controller.test.js
@@ -2,7 +2,6 @@
import fs from 'fs';
import path from 'path';
-import sinon from 'sinon';
import {cloneRepository, buildRepository} from '../helpers';
import FilePatch from '../../lib/models/file-patch';
diff --git a/test/controllers/git-controller.test.js b/test/controllers/git-controller.test.js
index 52e771dae9..3da688d5e5 100644
--- a/test/controllers/git-controller.test.js
+++ b/test/controllers/git-controller.test.js
@@ -4,7 +4,6 @@ import path from 'path';
import fs from 'fs';
import React from 'react';
-import sinon from 'sinon';
import {shallow} from 'enzyme';
import {cloneRepository, buildRepository} from '../helpers';
@@ -245,4 +244,23 @@ describe('GitController', () => {
assert.equal(wrapper.instance().focusGitPanel.callCount, 1);
});
});
+
+ it('correctly updates state when switching repos', async () => {
+ const workdirPath1 = await cloneRepository('three-files');
+ const repository1 = await buildRepository(workdirPath1);
+ const workdirPath2 = await cloneRepository('three-files');
+ const repository2 = await buildRepository(workdirPath2);
+
+ app = React.cloneElement(app, {repository: repository1});
+ const wrapper = shallow(app);
+
+ assert.equal(wrapper.state('amending'), false);
+
+ wrapper.setState({amending: true});
+ wrapper.setProps({repository: repository2});
+ assert.equal(wrapper.state('amending'), false);
+
+ wrapper.setProps({repository: repository1});
+ assert.equal(wrapper.state('amending'), true);
+ });
});
diff --git a/test/controllers/git-panel-controller.test.js b/test/controllers/git-panel-controller.test.js
index c9ea8203bb..81c2eb063b 100644
--- a/test/controllers/git-panel-controller.test.js
+++ b/test/controllers/git-panel-controller.test.js
@@ -3,19 +3,21 @@
import fs from 'fs';
import path from 'path';
-import sinon from 'sinon';
import dedent from 'dedent-js';
-import {cloneRepository, buildRepository} from '../helpers';
import GitPanelController from '../../lib/controllers/git-panel-controller';
+import {cloneRepository, buildRepository} from '../helpers';
+import {AbortMergeError, CommitError} from '../../lib/models/repository';
+
describe('GitPanelController', () => {
- let atomEnvironment, workspace, commandRegistry;
+ let atomEnvironment, workspace, commandRegistry, notificationManager;
beforeEach(() => {
atomEnvironment = global.buildAtomEnvironment();
workspace = atomEnvironment.workspace;
commandRegistry = atomEnvironment.commands;
+ notificationManager = atomEnvironment.notifications;
});
afterEach(() => {
@@ -40,7 +42,7 @@ describe('GitPanelController', () => {
assert.isDefined(controller.refs.gitPanel.refs.repoInfo);
});
- it('keeps the state of the GitPanelView in sync with the assigned repository', async done => {
+ it('keeps the state of the GitPanelView in sync with the assigned repository', async () => {
const workdirPath1 = await cloneRepository('three-files');
const repository1 = await buildRepository(workdirPath1);
const workdirPath2 = await cloneRepository('three-files');
@@ -76,33 +78,104 @@ describe('GitPanelController', () => {
const didChangeAmending = sinon.spy();
const workdirPath = await cloneRepository('multiple-commits');
const repository = await buildRepository(workdirPath);
- const controller = new GitPanelController({workspace, commandRegistry, repository, didChangeAmending});
+ const controller = new GitPanelController({workspace, commandRegistry, repository, didChangeAmending, isAmending: false});
await controller.getLastModelDataRefreshPromise();
assert.deepEqual(controller.refs.gitPanel.props.stagedChanges, []);
assert.equal(didChangeAmending.callCount, 0);
await controller.setAmending(true);
assert.equal(didChangeAmending.callCount, 1);
+ await controller.update({isAmending: true});
assert.deepEqual(
controller.refs.gitPanel.props.stagedChanges,
await controller.getActiveRepository().getStagedChangesSinceParentCommit(),
);
await controller.commit('Delete most of the code', {amend: true});
- await controller.getLastModelDataRefreshPromise();
- assert(!controller.refs.gitPanel.props.isAmending);
+ assert.equal(didChangeAmending.callCount, 2);
});
+ describe('abortMerge()', () => {
+ it('shows an error notification when abortMerge() throws an EDIRTYSTAGED exception', async () => {
+ const workdirPath = await cloneRepository('three-files');
+ const repository = await buildRepository(workdirPath);
+ sinon.stub(repository, 'abortMerge', async () => {
+ await Promise.resolve();
+ throw new AbortMergeError('EDIRTYSTAGED', 'a.txt');
+ });
+
+ const controller = new GitPanelController({workspace, commandRegistry, notificationManager, repository});
+ assert.equal(notificationManager.getNotifications().length, 0);
+ sinon.stub(atom, 'confirm').returns(0);
+ await controller.abortMerge();
+ assert.equal(notificationManager.getNotifications().length, 1);
+ });
+
+ it('resets merge related state', async () => {
+ const workdirPath = await cloneRepository('merge-conflict');
+ const repository = await buildRepository(workdirPath);
+
+ await repository.git.merge('origin/branch')
+ .then(() => { throw new Error('Expected merge to throw an error'); })
+ .catch(() => true);
+
+ const controller = new GitPanelController({workspace, commandRegistry, repository});
+ await controller.getLastModelDataRefreshPromise();
+ let modelData = controller.repositoryObserver.getActiveModelData();
+
+ assert.notEqual(modelData.mergeConflicts.length, 0);
+ assert.isTrue(modelData.isMerging);
+ assert.isOk(modelData.mergeMessage);
+
+ sinon.stub(atom, 'confirm').returns(0);
+ await controller.abortMerge();
+ await controller.getLastModelDataRefreshPromise();
+ modelData = controller.repositoryObserver.getActiveModelData();
+
+ assert.equal(modelData.mergeConflicts.length, 0);
+ assert.isFalse(modelData.isMerging);
+ assert.isNull(modelData.mergeMessage);
+ });
+ });
+
+ describe('commit(message)', () => {
+ it('shows an error notification when committing throws an ECONFLICT exception', async () => {
+ const workdirPath = await cloneRepository('three-files');
+ const repository = await buildRepository(workdirPath);
+ sinon.stub(repository, 'commit', async () => {
+ await Promise.resolve();
+ throw new CommitError('ECONFLICT');
+ });
+
+ const controller = new GitPanelController({workspace, commandRegistry, notificationManager, repository});
+ assert.equal(notificationManager.getNotifications().length, 0);
+ await controller.commit();
+ assert.equal(notificationManager.getNotifications().length, 1);
+ });
+
+ it('sets amending to false', async () => {
+ const workdirPath = await cloneRepository('three-files');
+ const repository = await buildRepository(workdirPath);
+ sinon.stub(repository, 'commit', () => Promise.resolve());
+ const didChangeAmending = sinon.stub();
+ const controller = new GitPanelController({workspace, commandRegistry, repository, didChangeAmending});
+
+ await controller.commit('message');
+ assert.equal(didChangeAmending.callCount, 1);
+ });
+ });
+
+
describe('integration tests', () => {
it('can stage and unstage files and commit', async () => {
const workdirPath = await cloneRepository('three-files');
const repository = await buildRepository(workdirPath);
fs.writeFileSync(path.join(workdirPath, 'a.txt'), 'a change\n');
fs.unlinkSync(path.join(workdirPath, 'b.txt'));
- const controller = new GitPanelController({workspace, commandRegistry, repository});
+ const controller = new GitPanelController({workspace, commandRegistry, repository, didChangeAmending: sinon.stub()});
await controller.getLastModelDataRefreshPromise();
const stagingView = controller.refs.gitPanel.refs.stagingView;
- const commitView = controller.refs.gitPanel.refs.commitView;
+ const commitView = controller.refs.gitPanel.refs.commitViewController.refs.commitView;
assert.equal(stagingView.props.unstagedChanges.length, 2);
assert.equal(stagingView.props.stagedChanges.length, 0);
diff --git a/test/controllers/status-bar-tile-controller.test.js b/test/controllers/status-bar-tile-controller.test.js
index db1efce90b..b99b1da405 100644
--- a/test/controllers/status-bar-tile-controller.test.js
+++ b/test/controllers/status-bar-tile-controller.test.js
@@ -4,7 +4,6 @@ import fs from 'fs';
import path from 'path';
import etch from 'etch';
-import sinon from 'sinon';
import {cloneRepository, buildRepository, setUpLocalAndRemoteRepositories} from '../helpers';
import StatusBarTileController from '../../lib/controllers/status-bar-tile-controller';
diff --git a/test/github-package.test.js b/test/github-package.test.js
index 034ff089e3..0b430fd622 100644
--- a/test/github-package.test.js
+++ b/test/github-package.test.js
@@ -5,7 +5,6 @@ import {Directory} from 'atom';
import fs from 'fs';
import path from 'path';
import temp from 'temp';
-import sinon from 'sinon';
import {cloneRepository} from './helpers';
import GithubPackage from '../lib/github-package';
diff --git a/test/helpers.js b/test/helpers.js
index b4e0f91bb7..f845dd7e8e 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -7,6 +7,7 @@ import temp from 'temp';
import {Directory} from 'atom';
import React from 'react';
import ReactDom from 'react-dom';
+import sinon from 'sinon';
import Repository from '../lib/models/repository';
import GitShellOutStrategy from '../lib/git-shell-out-strategy';
@@ -195,8 +196,14 @@ export function createRenderer() {
return renderer;
}
+// eslint-disable-next-line jasmine/no-global-setup
+beforeEach(() => {
+ global.sinon = sinon.sandbox.create();
+});
+
// eslint-disable-next-line jasmine/no-global-setup
afterEach(() => {
activeRenderers.forEach(r => r.unmount());
activeRenderers = [];
+ global.sinon.restore();
});
diff --git a/test/models/file-system-change-observer.test.js b/test/models/file-system-change-observer.test.js
index 11fc83f004..33b3ac5223 100644
--- a/test/models/file-system-change-observer.test.js
+++ b/test/models/file-system-change-observer.test.js
@@ -2,7 +2,6 @@
import fs from 'fs';
import path from 'path';
-import sinon from 'sinon';
import {cloneRepository, buildRepository, setUpLocalAndRemoteRepositories} from '../helpers';
diff --git a/test/models/model-state-registry.test.js b/test/models/model-state-registry.test.js
new file mode 100644
index 0000000000..0d83ca3f8d
--- /dev/null
+++ b/test/models/model-state-registry.test.js
@@ -0,0 +1,138 @@
+/** @babel */
+
+import ModelStateRegistry from '../../lib/models/model-state-registry';
+
+const Type1 = {type: 1};
+const Type2 = {type: 2};
+
+const model1 = {model: 1};
+const model2 = {model: 2};
+
+describe('ModelStateRegistry', () => {
+ beforeEach(() => {
+ ModelStateRegistry.clearSavedState();
+ });
+
+ describe('#setModel', () => {
+ it('saves the previous data to be restored later', () => {
+ let data;
+ const registry = new ModelStateRegistry(Type1, {
+ initialModel: model1,
+ save: () => data,
+ restore: (saved = {}) => { data = saved; },
+ });
+ assert.deepEqual(data, {});
+ data = {some: 'data'};
+ registry.setModel(model2);
+ assert.deepEqual(data, {});
+ registry.setModel(model1);
+ assert.deepEqual(data, {some: 'data'});
+ });
+
+ it('does not call save or restore if the model has not changed', () => {
+ let data;
+ const save = sinon.spy(() => data);
+ const restore = sinon.spy((saved = {}) => { data = saved; });
+ const registry = new ModelStateRegistry(Type1, {
+ initialModel: model1,
+ save,
+ restore,
+ });
+ save.reset();
+ restore.reset();
+ registry.setModel(model1);
+ assert.equal(save.callCount, 0);
+ assert.equal(restore.callCount, 0);
+ });
+
+ it('does not call save or restore for a model that does not exist', () => {
+ const save = sinon.stub();
+ const restore = sinon.stub();
+ const registry = new ModelStateRegistry(Type1, {
+ initialModel: model1,
+ save, restore,
+ });
+
+ save.reset();
+ restore.reset();
+ registry.setModel(null);
+ assert.equal(save.callCount, 1);
+ assert.equal(restore.callCount, 0);
+
+ save.reset();
+ restore.reset();
+ registry.setModel(model1);
+ assert.equal(save.callCount, 0);
+ assert.equal(restore.callCount, 1);
+ });
+ });
+
+ it('shares data across multiple instances given the same type and model', () => {
+ let data;
+ const registry1 = new ModelStateRegistry(Type1, {
+ initialModel: model1,
+ save: () => data,
+ restore: (saved = {}) => { data = saved; },
+ });
+ data = {some: 'data'};
+ registry1.setModel(model2);
+ assert.deepEqual(data, {});
+ data = {more: 'datas'};
+ registry1.setModel(model1);
+
+ let data2;
+ const registry2 = new ModelStateRegistry(Type1, {
+ initialModel: model1,
+ save: () => data2,
+ restore: (saved = {}) => { data2 = saved; },
+ });
+ assert.deepEqual(data2, {some: 'data'});
+ registry2.setModel(model2);
+ assert.deepEqual(data2, {more: 'datas'});
+ });
+
+ it('does not share data across multiple instances given the same model but a different type', () => {
+ let data;
+ const registry1 = new ModelStateRegistry(Type1, {
+ initialModel: model1,
+ save: () => data,
+ restore: (saved = {}) => { data = saved; },
+ });
+ data = {some: 'data'};
+ registry1.setModel(model2);
+ assert.deepEqual(data, {});
+ data = {more: 'datas'};
+ registry1.setModel(model1);
+
+ let data2;
+ const registry2 = new ModelStateRegistry(Type2, {
+ initialModel: model1,
+ save: () => data2,
+ restore: (saved = {}) => { data2 = saved; },
+ });
+ assert.deepEqual(data2, {});
+ data2 = {evenMore: 'data'};
+ registry2.setModel(model2);
+ assert.deepEqual(data2, {});
+ registry2.setModel(model1);
+ assert.deepEqual(data2, {evenMore: 'data'});
+ });
+
+ describe('#save and #restore', () => {
+ it('manually saves and restores data', () => {
+ let data;
+ const registry = new ModelStateRegistry(Type1, {
+ initialModel: model1,
+ save: () => data,
+ restore: (saved = {}) => { data = saved; },
+ });
+ data = {some: 'data'};
+ registry.save();
+ data = {};
+ registry.restore(model2);
+ assert.deepEqual(data, {});
+ registry.restore(model1);
+ assert.deepEqual(data, {some: 'data'});
+ });
+ });
+});
diff --git a/test/models/workspace-change-observer.test.js b/test/models/workspace-change-observer.test.js
index 8e54dbf062..6b0edccc37 100644
--- a/test/models/workspace-change-observer.test.js
+++ b/test/models/workspace-change-observer.test.js
@@ -1,7 +1,6 @@
/** @babel */
import path from 'path';
-import sinon from 'sinon';
import {cloneRepository, buildRepository} from '../helpers';
diff --git a/test/multi-list.test.js b/test/multi-list.test.js
index 2cd7de8494..fd70ce4d06 100644
--- a/test/multi-list.test.js
+++ b/test/multi-list.test.js
@@ -1,6 +1,5 @@
/** @babel */
-import sinon from 'sinon';
import MultiList from '../lib/multi-list';
describe('MultiList', () => {
diff --git a/test/views/commit-view.test.js b/test/views/commit-view.test.js
index 8da5aed677..b4c9aa978b 100644
--- a/test/views/commit-view.test.js
+++ b/test/views/commit-view.test.js
@@ -2,73 +2,57 @@
import {cloneRepository, buildRepository} from '../helpers';
import etch from 'etch';
-import sinon from 'sinon';
import CommitView from '../../lib/views/commit-view';
-import {AbortMergeError, CommitError} from '../../lib/models/repository';
describe('CommitView', () => {
- let atomEnv, workspace, commandRegistry, notificationManager, confirmChoice;
+ let atomEnv, commandRegistry;
beforeEach(() => {
atomEnv = global.buildAtomEnvironment();
- workspace = atomEnv.workspace;
commandRegistry = atomEnv.commands;
- notificationManager = atomEnv.notifications;
- confirmChoice = 0;
- sinon.stub(atom, 'confirm', () => confirmChoice);
});
afterEach(() => {
atomEnv.destroy();
- atom.confirm.restore();
});
it('displays the remaining characters limit based on which line is being edited', async () => {
- const workdirPath = await cloneRepository('three-files');
- const repository = await buildRepository(workdirPath);
- const viewState = {};
- const view = new CommitView({workspace, repository, commandRegistry, stagedChangesExist: true, maximumCharacterLimit: 72, viewState});
- const {editor} = view.refs;
+ const view = new CommitView({commandRegistry, stagedChangesExist: true, maximumCharacterLimit: 72, message: ''});
assert.equal(view.refs.remainingCharacters.textContent, '72');
- editor.insertText('abcde fghij');
- await etch.getScheduler().getNextUpdatePromise();
+ await view.update({message: 'abcde fghij'});
assert.equal(view.refs.remainingCharacters.textContent, '61');
assert(!view.refs.remainingCharacters.classList.contains('is-error'));
assert(!view.refs.remainingCharacters.classList.contains('is-warning'));
- editor.insertText('\nklmno');
- await etch.getScheduler().getNextUpdatePromise();
+ await view.update({message: '\nklmno'});
assert.equal(view.refs.remainingCharacters.textContent, '∞');
assert(!view.refs.remainingCharacters.classList.contains('is-error'));
assert(!view.refs.remainingCharacters.classList.contains('is-warning'));
- editor.insertText('\npqrst');
- await etch.getScheduler().getNextUpdatePromise();
+ await view.update({message: 'abcde\npqrst'});
assert.equal(view.refs.remainingCharacters.textContent, '∞');
assert(!view.refs.remainingCharacters.classList.contains('is-error'));
assert(!view.refs.remainingCharacters.classList.contains('is-warning'));
- editor.setCursorBufferPosition([0, 3]);
+ view.editor.setCursorBufferPosition([0, 3]);
await etch.getScheduler().getNextUpdatePromise();
- assert.equal(view.refs.remainingCharacters.textContent, '61');
+ assert.equal(view.refs.remainingCharacters.textContent, '67');
assert(!view.refs.remainingCharacters.classList.contains('is-error'));
assert(!view.refs.remainingCharacters.classList.contains('is-warning'));
await view.update({stagedChangesExist: true, maximumCharacterLimit: 50});
- assert.equal(view.refs.remainingCharacters.textContent, '39');
+ assert.equal(view.refs.remainingCharacters.textContent, '45');
assert(!view.refs.remainingCharacters.classList.contains('is-error'));
assert(!view.refs.remainingCharacters.classList.contains('is-warning'));
- editor.insertText('abcde fghij klmno pqrst uvwxyz');
- await etch.getScheduler().getNextUpdatePromise();
+ await view.update({message: 'a'.repeat(41)});
assert.equal(view.refs.remainingCharacters.textContent, '9');
assert(!view.refs.remainingCharacters.classList.contains('is-error'));
assert(view.refs.remainingCharacters.classList.contains('is-warning'));
- editor.insertText('ABCDE FGHIJ KLMNO');
- await etch.getScheduler().getNextUpdatePromise();
+ await view.update({message: 'a'.repeat(58)});
assert.equal(view.refs.remainingCharacters.textContent, '-8');
assert(view.refs.remainingCharacters.classList.contains('is-error'));
assert(!view.refs.remainingCharacters.classList.contains('is-warning'));
@@ -77,14 +61,14 @@ describe('CommitView', () => {
it('uses the git commit message grammar when the grammar is loaded', async () => {
await atom.packages.activatePackage('language-git');
- const view = new CommitView({workspace, commandRegistry});
+ const view = new CommitView({commandRegistry});
assert.equal(view.editor.getGrammar().scopeName, 'text.git-commit');
});
it('uses the git commit message grammar when the grammar has not been loaded', async () => {
atom.packages.deactivatePackage('language-git');
- const view = new CommitView({workspace, commandRegistry});
+ const view = new CommitView({commandRegistry});
assert(view.editor.getGrammar().scopeName.startsWith('text.plain'));
await atom.packages.activatePackage('language-git');
@@ -96,7 +80,7 @@ describe('CommitView', () => {
const workdirPath = await cloneRepository('three-files');
const repository = await buildRepository(workdirPath);
const viewState = {};
- const view = new CommitView({workspace, repository, commandRegistry, stagedChangesExist: false, viewState});
+ const view = new CommitView({repository, commandRegistry, stagedChangesExist: false, viewState});
const {editor, commitButton} = view.refs;
assert.isTrue(commitButton.disabled);
@@ -119,20 +103,14 @@ describe('CommitView', () => {
});
it('calls props.commit(message) when the commit button is clicked or github:commit is dispatched', async () => {
- const workdirPath = await cloneRepository('three-files');
- const repository = await buildRepository(workdirPath);
const commit = sinon.spy();
- const view = new CommitView({workspace, commandRegistry, stagedChangesExist: false, commit});
+ const view = new CommitView({commandRegistry, stagedChangesExist: false, commit, message: ''});
const {editor, commitButton} = view.refs;
// commit by clicking the commit button
- await view.update({repository, stagedChangesExist: true});
- editor.setText('Commit 1');
- await etch.getScheduler().getNextUpdatePromise();
+ await view.update({stagedChangesExist: true, message: 'Commit 1'});
commitButton.dispatchEvent(new MouseEvent('click'));
- await etch.getScheduler().getNextUpdatePromise();
assert.equal(commit.args[0][0], 'Commit 1');
- assert.equal(editor.getText(), '');
// undo history is cleared
commandRegistry.dispatch(editor.element, 'core:undo');
@@ -140,72 +118,25 @@ describe('CommitView', () => {
// commit via the github:commit command
commit.reset();
- await view.update({repository, stagedChangesExist: true});
- editor.setText('Commit 2');
- await etch.getScheduler().getNextUpdatePromise();
+ await view.update({stagedChangesExist: true, message: 'Commit 2'});
commandRegistry.dispatch(editor.element, 'github:commit');
- await etch.getScheduler().getNextUpdatePromise();
assert.equal(commit.args[0][0], 'Commit 2');
- assert.equal(editor.getText(), '');
// disable github:commit when there are no staged changes...
commit.reset();
- await view.update({repository, stagedChangesExist: false});
- editor.setText('Commit 4');
- await etch.getScheduler().getNextUpdatePromise();
+ await view.update({stagedChangesExist: false, message: 'Commit 4'});
commandRegistry.dispatch(editor.element, 'github:commit');
- await etch.getScheduler().getNextUpdatePromise();
assert.equal(commit.callCount, 0);
- assert.equal(editor.getText(), 'Commit 4');
// ...or the commit message is empty
commit.reset();
- editor.setText('');
- await etch.getScheduler().getNextUpdatePromise();
- await view.update({repository, stagedChangesExist: true});
+ await view.update({stagedChangesExist: true, message: ''});
commandRegistry.dispatch(editor.element, 'github:commit');
- await etch.getScheduler().getNextUpdatePromise();
assert.equal(commit.callCount, 0);
});
- it('shows an error notification when props.commit() throws an ECONFLICT exception', async () => {
- const commit = sinon.spy(async () => {
- await Promise.resolve();
- throw new CommitError('ECONFLICT');
- });
- const view = new CommitView({workspace, commandRegistry, notificationManager, stagedChangesExist: true, commit});
- const {editor, commitButton} = view.refs;
- editor.setText('A message.');
- await etch.getScheduler().getNextUpdatePromise();
- assert.equal(notificationManager.getNotifications().length, 0);
- commitButton.dispatchEvent(new MouseEvent('click'));
- await etch.getScheduler().getNextUpdatePromise();
- assert(commit.calledOnce);
- assert.equal(editor.getText(), 'A message.');
- assert.equal(notificationManager.getNotifications().length, 1);
- });
-
- it('replaces the contents of the commit message when it is empty and a message is supplied from the outside', async () => {
- const workdirPath = await cloneRepository('three-files');
- const repository = await buildRepository(workdirPath);
- const viewState = {};
- const view = new CommitView({workspace, repository, commandRegistry, stagedChangesExist: true, maximumCharacterLimit: 72, viewState});
- const {editor} = view.refs;
- editor.setText('message 1');
- await etch.getScheduler().getNextUpdatePromise();
- assert.equal(editor.getText(), 'message 1');
-
- await view.update({message: 'Merge conflict!'});
- assert.equal(editor.getText(), 'message 1');
-
- editor.setText('');
- await etch.getScheduler().getNextUpdatePromise();
- await view.update({message: 'Merge conflict!'});
- assert.equal(editor.getText(), 'Merge conflict!');
- });
-
it('shows the "Abort Merge" button when props.isMerging is true', async () => {
- const view = new CommitView({workspace, commandRegistry, stagedChangesExist: true, isMerging: false});
+ const view = new CommitView({commandRegistry, stagedChangesExist: true, isMerging: false});
const {abortMergeButton} = view.refs;
assert.equal(abortMergeButton.style.display, 'none');
@@ -216,74 +147,18 @@ describe('CommitView', () => {
assert.equal(abortMergeButton.style.display, 'none');
});
- it('calls props.abortMerge() when the "Abort Merge" button is clicked and then clears the commit message', async () => {
+ it('calls props.abortMerge() when the "Abort Merge" button is clicked', () => {
const abortMerge = sinon.spy(() => Promise.resolve());
- const view = new CommitView({workspace, commandRegistry, stagedChangesExist: true, isMerging: true, abortMerge});
- const {editor, abortMergeButton} = view.refs;
- editor.setText('A message.');
+ const view = new CommitView({commandRegistry, stagedChangesExist: true, isMerging: true, abortMerge});
+ const {abortMergeButton} = view.refs;
abortMergeButton.dispatchEvent(new MouseEvent('click'));
- await etch.getScheduler().getNextUpdatePromise();
assert(abortMerge.calledOnce);
- assert.equal(editor.getText(), '');
- });
-
- it('shows an error notification when props.abortMerge() throws an EDIRTYSTAGED exception', async () => {
- const abortMerge = sinon.spy(async () => {
- await Promise.resolve();
- throw new AbortMergeError('EDIRTYSTAGED', 'a.txt');
- });
- const view = new CommitView({workspace, commandRegistry, notificationManager, stagedChangesExist: true, isMerging: true, abortMerge});
- const {editor, abortMergeButton} = view.refs;
- editor.setText('A message.');
- assert.equal(notificationManager.getNotifications().length, 0);
- abortMergeButton.dispatchEvent(new MouseEvent('click'));
- await etch.getScheduler().getNextUpdatePromise();
- assert(abortMerge.calledOnce);
- assert.equal(editor.getText(), 'A message.');
- assert.equal(notificationManager.getNotifications().length, 1);
});
describe('amending', () => {
- it('displays the appropriate commit message and sets the cursor to the beginning of the text', async () => {
- const workdirPath = await cloneRepository('three-files');
- const repository = await buildRepository(workdirPath);
- const viewState = {};
- const view = new CommitView({workspace, repository, commandRegistry, stagedChangesExist: false, lastCommit: {message: 'previous commit\'s message'}, viewState});
- const {editor, amend} = view.refs;
-
- editor.setText('some commit message');
- assert.isFalse(amend.checked);
- assert.equal(editor.getText(), 'some commit message');
-
- // displays message for last commit
- amend.click();
- assert.isTrue(amend.checked);
- assert.equal(editor.getText(), 'previous commit\'s message');
- assert.deepEqual(editor.getCursorBufferPosition().serialize(), [0, 0]);
-
- // restores original message
- amend.click();
- assert.isFalse(amend.checked);
- assert.equal(editor.getText(), 'some commit message');
- assert.deepEqual(editor.getCursorBufferPosition().serialize(), [0, 0]);
- });
-
- it('clears the amend checkbox after committing', async () => {
- const workdirPath = await cloneRepository('three-files');
- const repository = await buildRepository(workdirPath);
- const view = new CommitView({workspace, commandRegistry, stagedChangesExist: false});
- const {amend} = view.refs;
- await view.update({repository, stagedChangesExist: true});
- assert.isFalse(amend.checked);
- amend.click();
- assert.isTrue(amend.checked);
- await view.commit();
- assert.isFalse(amend.checked);
- });
-
it('calls props.setAmending() when the box is checked or unchecked', () => {
const setAmending = sinon.spy();
- const view = new CommitView({workspace, commandRegistry, stagedChangesExist: false, lastCommit: {message: 'previous commit\'s message'}, setAmending});
+ const view = new CommitView({commandRegistry, stagedChangesExist: false, lastCommit: {message: 'previous commit\'s message'}, setAmending});
const {amend} = view.refs;
amend.click();
@@ -293,115 +168,4 @@ describe('CommitView', () => {
assert.deepEqual(setAmending.args, [[true], [false]]);
});
});
-
- describe('when switching between repositories', () => {
- it('retains the commit message and cursor location', async () => {
- const workdirPath1 = await cloneRepository('multiple-commits');
- const repository1 = await buildRepository(workdirPath1);
- const workdirPath2 = await cloneRepository('three-files');
- const repository2 = await buildRepository(workdirPath2);
-
- const viewStateForRepo1 = {};
- const viewStateForRepo2 = {};
-
- let viewForRepo1 = new CommitView({workspace, repository: repository1, commandRegistry, stagedChangesExist: true, viewState: viewStateForRepo1});
- let editor = viewForRepo1.refs.editor;
-
- const repository1Message = 'commit message for first repo\nsome details about the commit\nmore details';
- editor.setText(repository1Message);
- const repository1CursorPosition = [1, 3];
- editor.setCursorBufferPosition(repository1CursorPosition);
- await etch.getScheduler().getNextUpdatePromise();
- assert.equal(editor.getText(), repository1Message);
- assert.deepEqual(editor.getCursorBufferPosition().serialize(), repository1CursorPosition);
-
- let viewForRepo2 = new CommitView({workspace, repository: repository2, commandRegistry, stagedChangesExist: true, viewState: viewStateForRepo2});
- editor = viewForRepo2.refs.editor;
- assert.equal(editor.getText(), '');
-
- const repository2Message = 'commit message for second repo';
- editor.setText(repository2Message);
- const repository2CursorPosition = [0, 10];
- editor.setCursorBufferPosition(repository2CursorPosition);
- await etch.getScheduler().getNextUpdatePromise();
- assert.equal(editor.getText(), repository2Message);
- assert.deepEqual(editor.getCursorBufferPosition().serialize(), repository2CursorPosition);
-
- // when repository1 is selected, restore its state
- viewForRepo1 = new CommitView({workspace, repository: repository1, commandRegistry, stagedChangesExist: true, viewState: viewStateForRepo1});
- editor = viewForRepo1.refs.editor;
- assert.equal(editor.getText(), repository1Message);
- assert.deepEqual(editor.getCursorBufferPosition().serialize(), repository1CursorPosition);
-
- // when repository2 is selected, restore its state
- viewForRepo2 = new CommitView({workspace, repository: repository2, commandRegistry, stagedChangesExist: true, viewState: viewStateForRepo2});
- editor = viewForRepo2.refs.editor;
- assert.equal(editor.getText(), repository2Message);
- assert.deepEqual(editor.getCursorBufferPosition().serialize(), repository2CursorPosition);
- });
-
- it('retains the amend status and restores the correct commit message when amend state is exited', async () => {
- const workdirPath1 = await cloneRepository('multiple-commits');
- const repository1 = await buildRepository(workdirPath1);
- const workdirPath2 = await cloneRepository('three-files');
- const repository2 = await buildRepository(workdirPath2);
-
- const repository1LastCommit = {message: 'first repository\'s previous commit\'s message'};
- const repository2LastCommit = {message: 'second repository\'s previous commit\'s message'};
- const viewStateForRepo1 = {};
- const viewStateForRepo2 = {};
-
- const view = new CommitView({workspace, repository: repository1, lastCommit: repository1LastCommit, commandRegistry, stagedChangesExist: true, viewState: viewStateForRepo1});
- const {editor, amend} = view.refs;
-
- // create message for repository1
- const repository1Message = 'commit message for first repo\nsome details about the commit\nmore details';
- editor.setText(repository1Message);
- await etch.getScheduler().getNextUpdatePromise();
-
- // put repository1 in amend state, commit message changes to that of the last commit
- amend.click();
- await etch.getScheduler().getNextUpdatePromise();
- assert.isTrue(amend.checked);
- assert.equal(editor.getText(), repository1LastCommit.message);
-
- // when repository2 is selected, restore to initial state of unchecked amend box and empty commit message
- await view.update({repository: repository2, lastCommit: repository2LastCommit, viewState: viewStateForRepo2});
- assert.isFalse(amend.checked);
- assert.equal(editor.getText(), '');
-
- // create commit message for repository2
- const repository2Message = 'commit message for second repo';
- editor.setText(repository2Message);
- await etch.getScheduler().getNextUpdatePromise();
- assert.isFalse(amend.checked);
- assert.equal(editor.getText(), repository2Message);
-
- // put repository2 in amend state, commit message changes to that of the last commit
- amend.click();
- await etch.getScheduler().getNextUpdatePromise();
- assert.isTrue(amend.checked);
- assert.equal(editor.getText(), repository2LastCommit.message);
-
- // when repository1 is selected, restore its state
- await view.update({repository: repository1, viewState: viewStateForRepo1});
- assert.isTrue(amend.checked);
- assert.equal(editor.getText(), repository1LastCommit.message);
-
- // exit amend state and restore original message for repository1
- amend.click();
- assert.isFalse(amend.checked);
- assert.equal(editor.getText(), repository1Message);
-
- // when repository2 is selected, restore its state
- await view.update({repository: repository2, viewState: viewStateForRepo2});
- assert.isTrue(amend.checked);
- assert.equal(editor.getText(), repository2LastCommit.message);
-
- // exit amend state and restore original message for repository2
- amend.click();
- assert.isFalse(amend.checked);
- assert.equal(editor.getText(), repository2Message);
- });
- });
});
diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js
index 0d402a8e46..bc986b1c0d 100644
--- a/test/views/file-patch-view.test.js
+++ b/test/views/file-patch-view.test.js
@@ -1,6 +1,5 @@
/** @babel */
-import sinon from 'sinon';
import FilePatchView from '../../lib/views/file-patch-view';
import Hunk from '../../lib/models/hunk';
diff --git a/test/views/hunk-view.test.js b/test/views/hunk-view.test.js
index e6f0c1a2b6..e7c70439ae 100644
--- a/test/views/hunk-view.test.js
+++ b/test/views/hunk-view.test.js
@@ -1,6 +1,5 @@
/** @babel */
-import sinon from 'sinon';
import Hunk from '../../lib/models/hunk';
import HunkLine from '../../lib/models/hunk-line';
diff --git a/test/views/list-view.test.js b/test/views/list-view.test.js
index 7a1cceaeeb..cb71c4ea81 100644
--- a/test/views/list-view.test.js
+++ b/test/views/list-view.test.js
@@ -3,7 +3,6 @@
/* eslint react/no-unknown-property: "off" */
import etch from 'etch';
-import sinon from 'sinon';
import simulant from 'simulant';
import ListView from '../../lib/views/list-view';
diff --git a/test/views/pane-item.test.js b/test/views/pane-item.test.js
index 54e136eb66..05ecbb0dc0 100644
--- a/test/views/pane-item.test.js
+++ b/test/views/pane-item.test.js
@@ -1,7 +1,6 @@
/** @babel */
import React from 'react';
-import sinon from 'sinon';
import PaneItem from '../../lib/views/pane-item';
diff --git a/test/views/panel.test.js b/test/views/panel.test.js
index 338ce898c7..5b5bc7def7 100644
--- a/test/views/panel.test.js
+++ b/test/views/panel.test.js
@@ -1,7 +1,6 @@
/** @babel */
import React from 'react';
-import sinon from 'sinon';
import Panel from '../../lib/views/panel';
diff --git a/test/views/staging-view.test.js b/test/views/staging-view.test.js
index f69c9c9da6..9fbc6734e8 100644
--- a/test/views/staging-view.test.js
+++ b/test/views/staging-view.test.js
@@ -1,6 +1,5 @@
/** @babel */
-import sinon from 'sinon';
import StagingView from '../../lib/views/staging-view';
import {assertEqualSets} from '../helpers';