diff --git a/lib/atom/decoration.js b/lib/atom/decoration.js
index cd3cbead27..9da22e42ac 100644
--- a/lib/atom/decoration.js
+++ b/lib/atom/decoration.js
@@ -64,12 +64,12 @@ class BareDecoration extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.editorHolder !== prevProps.editorHolder) {
this.editorSub.dispose();
- this.editorSub = this.state.editorHolder.observe(this.observeParents);
+ this.editorSub = this.props.editorHolder.observe(this.observeParents);
}
if (this.props.decorableHolder !== prevProps.decorableHolder) {
this.decorableSub.dispose();
- this.decorableSub = this.state.decorableHolder.observe(this.observeParents);
+ this.decorableSub = this.props.decorableHolder.observe(this.observeParents);
}
if (
@@ -95,10 +95,10 @@ class BareDecoration extends React.Component {
this.decorationHolder.map(decoration => decoration.destroy());
const editorValid = this.props.editorHolder.map(editor => !editor.isDestroyed()).getOr(false);
- const markableValid = this.props.decorableHolder.map(decorable => !decorable.isDestroyed()).getOr(false);
+ const decorableValid = this.props.decorableHolder.map(decorable => !decorable.isDestroyed()).getOr(false);
// Ensure the Marker or MarkerLayer corresponds to the context's TextEditor
- const markableMatches = this.props.decorableHolder.map(decorable => this.props.editorHolder.map(editor => {
+ const decorableMatches = this.props.decorableHolder.map(decorable => this.props.editorHolder.map(editor => {
const layer = decorable.layer || decorable;
const displayLayer = editor.getMarkerLayer(layer.id);
if (!displayLayer) {
@@ -110,7 +110,7 @@ class BareDecoration extends React.Component {
return true;
}).getOr(false)).getOr(false);
- if (!editorValid || !markableValid || !markableMatches) {
+ if (!editorValid || !decorableValid || !decorableMatches) {
return;
}
diff --git a/lib/controllers/editor-conflict-controller.js b/lib/controllers/editor-conflict-controller.js
index 728cfc5021..79f7309512 100644
--- a/lib/controllers/editor-conflict-controller.js
+++ b/lib/controllers/editor-conflict-controller.js
@@ -18,11 +18,7 @@ export default class EditorConflictController extends React.Component {
commandRegistry: PropTypes.object.isRequired,
resolutionProgress: PropTypes.object.isRequired,
isRebase: PropTypes.bool.isRequired,
- refreshResolutionProgress: PropTypes.func,
- }
-
- static defaultProps = {
- refreshResolutionProgress: () => {},
+ refreshResolutionProgress: PropTypes.func.isRequired,
}
constructor(props, context) {
@@ -49,7 +45,6 @@ export default class EditorConflictController extends React.Component {
const buffer = this.props.editor.getBuffer();
this.subscriptions.add(
- this.props.editor.onDidStopChanging(() => this.forceUpdate()),
this.props.editor.onDidDestroy(() => this.props.refreshResolutionProgress(this.props.editor.getPath())),
buffer.onDidReload(() => this.reparseConflicts()),
);
@@ -171,7 +166,7 @@ export default class EditorConflictController extends React.Component {
}
dismissConflicts(conflicts) {
- this.setState((prevState, props) => {
+ this.setState(prevState => {
const {added} = compareSets(new Set(conflicts), prevState.conflicts);
return {conflicts: added};
});
diff --git a/lib/models/conflicts/banner.js b/lib/models/conflicts/banner.js
index 47080ca2ce..25630fe329 100644
--- a/lib/models/conflicts/banner.js
+++ b/lib/models/conflicts/banner.js
@@ -26,6 +26,7 @@ export default class Banner {
revert() {
const range = this.getMarker().getBufferRange();
this.editor.setTextInBufferRange(range, this.originalText);
+ this.getMarker().setBufferRange(range);
}
delete() {
diff --git a/lib/models/conflicts/side.js b/lib/models/conflicts/side.js
index e99174c89a..17585511a0 100644
--- a/lib/models/conflicts/side.js
+++ b/lib/models/conflicts/side.js
@@ -98,6 +98,7 @@ export default class Side {
revert() {
const range = this.getMarker().getBufferRange();
this.editor.setTextInBufferRange(range, this.originalText);
+ this.getMarker().setBufferRange(range);
}
deleteBanner() {
diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js
index 830c18280e..8d93ffb7b8 100644
--- a/lib/models/repository-states/present.js
+++ b/lib/models/repository-states/present.js
@@ -610,9 +610,6 @@ export default class Present extends State {
const bundle = await this.git().getStatusBundle();
const results = await this.formatChangedFiles(bundle);
results.branch = bundle.branch;
- if (!results.branch.aheadBehind) {
- results.branch.aheadBehind = {ahead: null, behind: null};
- }
return results;
} catch (err) {
if (err instanceof LargeRepoError) {
@@ -855,7 +852,7 @@ export default class Present extends State {
// Direct blob access
getBlobContents(sha) {
- return this.cache.getOrSet(Keys.blob(sha), () => {
+ return this.cache.getOrSet(Keys.blob.oneWith(sha), () => {
return this.git().getBlobContents(sha);
});
}
@@ -876,6 +873,7 @@ export default class Present extends State {
// Cache
+ /* istanbul ignore next */
getCache() {
return this.cache;
}
@@ -974,6 +972,7 @@ class Cache {
this.didUpdate();
}
+ /* istanbul ignore next */
[Symbol.iterator]() {
return this.storage[Symbol.iterator]();
}
@@ -988,6 +987,7 @@ class Cache {
this.emitter.emit('did-update');
}
+ /* istanbul ignore next */
onDidUpdate(callback) {
return this.emitter.on('did-update', callback);
}
@@ -1025,6 +1025,7 @@ class CacheKey {
}
}
+ /* istanbul ignore next */
toString() {
return `CacheKey(${this.primary})`;
}
@@ -1041,6 +1042,7 @@ class GroupKey {
}
}
+ /* istanbul ignore next */
toString() {
return `GroupKey(${this.group})`;
}
diff --git a/test/atom/decoration.test.js b/test/atom/decoration.test.js
index 2e9fd4fdcd..db187dfdcb 100644
--- a/test/atom/decoration.test.js
+++ b/test/atom/decoration.test.js
@@ -99,6 +99,69 @@ describe('Decoration', function() {
});
});
+ describe('when called with changed props', function() {
+ let wrapper, originalDecoration;
+
+ beforeEach(function() {
+ const app = (
+
+ );
+ wrapper = mount(app);
+
+ originalDecoration = editor.getLineDecorations({position: 'head', class: 'something'})[0];
+ });
+
+ it('redecorates when a new Editor and Marker are provided', async function() {
+ const newEditor = await workspace.open(require.resolve('../../package.json'));
+ const newMarker = newEditor.markBufferRange([[0, 0], [2, 0]]);
+
+ wrapper.setProps({editor: newEditor, decorable: newMarker});
+
+ assert.isTrue(originalDecoration.isDestroyed());
+ assert.lengthOf(editor.getLineDecorations({position: 'head', class: 'something'}), 0);
+ assert.lengthOf(newEditor.getLineDecorations({position: 'head', class: 'something'}), 1);
+ });
+
+ it('redecorates when a new MarkerLayer is provided', function() {
+ const newLayer = editor.addMarkerLayer();
+
+ wrapper.setProps({decorable: newLayer, decorateMethod: 'decorateMarkerLayer'});
+
+ assert.isTrue(originalDecoration.isDestroyed());
+ assert.lengthOf(editor.getLineDecorations({position: 'head', class: 'something'}), 0);
+
+ // Turns out Atom doesn't provide any public way to query the markers on a layer.
+ assert.lengthOf(
+ Array.from(editor.decorationManager.layerDecorationsByMarkerLayer.get(newLayer)),
+ 1,
+ );
+ });
+
+ it('updates decoration properties', function() {
+ wrapper.setProps({className: 'different'});
+
+ assert.lengthOf(editor.getLineDecorations({position: 'head', class: 'something'}), 0);
+ assert.lengthOf(editor.getLineDecorations({position: 'head', class: 'different'}), 1);
+ assert.isFalse(originalDecoration.isDestroyed());
+ assert.strictEqual(originalDecoration.getProperties().class, 'different');
+ });
+
+ it('does not redecorate when the decorable is on the wrong TextEditor', async function() {
+ const newEditor = await workspace.open(require.resolve('../../package.json'));
+
+ wrapper.setProps({editor: newEditor});
+
+ assert.isTrue(originalDecoration.isDestroyed());
+ assert.lengthOf(editor.getLineDecorations({}), 0);
+ });
+ });
+
it('destroys its decoration on unmount', function() {
const app = (
cc.prop('conflict') === conflict));
+ });
});
describe('on a file with 3-way diff markers', function() {
@@ -442,4 +505,12 @@ describe('EditorConflictController', function() {
assert.equal(editor.getText(), 'These are my changes\nThese are your changes\n\nPast the end\n');
});
});
+
+ it('cleans up its subscriptions when unmounting', async function() {
+ await useFixture('triple-2way-diff.txt');
+ wrapper.unmount();
+
+ editor.destroy();
+ assert.isFalse(refreshResolutionProgress.called);
+ });
});
diff --git a/test/fixtures/conflict-marker-examples/single-2way-diff-empty.txt b/test/fixtures/conflict-marker-examples/single-2way-diff-empty.txt
new file mode 100644
index 0000000000..802eb878f7
--- /dev/null
+++ b/test/fixtures/conflict-marker-examples/single-2way-diff-empty.txt
@@ -0,0 +1,8 @@
+Before the start
+
+<<<<<<< HEAD
+These are my changes
+=======
+>>>>>>> master
+
+Past the end
diff --git a/test/models/conflicts/conflict.test.js b/test/models/conflicts/conflict.test.js
index f8ac15018d..8a1c877a8a 100644
--- a/test/models/conflicts/conflict.test.js
+++ b/test/models/conflicts/conflict.test.js
@@ -21,10 +21,10 @@ describe('Conflict', function() {
return atomEnv.workspace.open(fullPath);
};
- const assertConflictOnRows = function(conflict, description, message) {
+ const assertConflictOnRows = function(conflict, description) {
const isRangeOnRows = function(range, startRow, endRow, rangeName) {
- assert(
- range.start.row === startRow && range.start.column === 0 && range.end.row === endRow && range.end.column === 0,
+ assert.isTrue(
+ range.start.row === startRow && range.end.row === endRow,
`expected conflict's ${rangeName} range to cover rows ${startRow} to ${endRow}, but it was ${range}`,
);
};
@@ -33,27 +33,37 @@ describe('Conflict', function() {
return isRangeOnRows(range, row, row + 1, rangeName);
};
- const ourBannerRange = conflict.getSide(OURS).banner.marker.getBufferRange();
+ const isPointOnRow = function(range, row, rangeName) {
+ return isRangeOnRows(range, row, row, rangeName);
+ };
+
+ const ourBannerRange = conflict.getSide(OURS).getBannerMarker().getBufferRange();
isRangeOnRow(ourBannerRange, description.ourBannerRow, '"ours" banner');
- const ourSideRange = conflict.getSide(OURS).marker.getBufferRange();
+ const ourSideRange = conflict.getSide(OURS).getMarker().getBufferRange();
isRangeOnRows(ourSideRange, description.ourSideRows[0], description.ourSideRows[1], '"ours"');
assert.strictEqual(conflict.getSide(OURS).position, description.ourPosition || TOP, '"ours" in expected position');
- const theirBannerRange = conflict.getSide(THEIRS).banner.marker.getBufferRange();
+ const ourBlockRange = conflict.getSide(OURS).getBlockMarker().getBufferRange();
+ isPointOnRow(ourBlockRange, description.ourBannerRow, '"ours" block range');
+
+ const theirBannerRange = conflict.getSide(THEIRS).getBannerMarker().getBufferRange();
isRangeOnRow(theirBannerRange, description.theirBannerRow, '"theirs" banner');
- const theirSideRange = conflict.getSide(THEIRS).marker.getBufferRange();
+ const theirSideRange = conflict.getSide(THEIRS).getMarker().getBufferRange();
isRangeOnRows(theirSideRange, description.theirSideRows[0], description.theirSideRows[1], '"theirs"');
assert.strictEqual(conflict.getSide(THEIRS).position, description.theirPosition || BOTTOM, '"theirs" in expected position');
+ const theirBlockRange = conflict.getSide(THEIRS).getBlockMarker().getBufferRange();
+ isPointOnRow(theirBlockRange, description.theirBannerRow, '"theirs" block range');
+
if (description.baseBannerRow || description.baseSideRows) {
assert.isNotNull(conflict.getSide(BASE), "expected conflict's base side to be non-null");
- const baseBannerRange = conflict.getSide(BASE).banner.marker.getBufferRange();
+ const baseBannerRange = conflict.getSide(BASE).getBannerMarker().getBufferRange();
isRangeOnRow(baseBannerRange, description.baseBannerRow, '"base" banner');
- const baseSideRange = conflict.getSide(BASE).marker.getBufferRange();
+ const baseSideRange = conflict.getSide(BASE).getMarker().getBufferRange();
isRangeOnRows(baseSideRange, description.baseSideRows[0], description.baseSideRows[1], '"base"');
assert.strictEqual(conflict.getSide(BASE).position, MIDDLE, '"base" in MIDDLE position');
} else {
@@ -277,4 +287,345 @@ end
assert.isTrue(conflict.getSeparator().isModified());
});
});
+
+ describe('contextual block position and CSS class generation', function() {
+ let editor, conflict;
+
+ describe('from a merge', function() {
+ beforeEach(async function() {
+ editor = await editorOnFixture('single-3way-diff.txt');
+ conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0];
+ });
+
+ it('accesses the block decoration position', function() {
+ assert.strictEqual(conflict.getSide(OURS).getBlockPosition(), 'before');
+ assert.strictEqual(conflict.getSide(BASE).getBlockPosition(), 'before');
+ assert.strictEqual(conflict.getSide(THEIRS).getBlockPosition(), 'after');
+ });
+
+ it('accesses the line decoration CSS class', function() {
+ assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictOurs');
+ assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictBase');
+ assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictTheirs');
+ });
+
+ it('accesses the line decoration CSS class when modified', function() {
+ for (const position of [[5, 1], [3, 1], [1, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictModified');
+ assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictModified');
+ assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictModified');
+ });
+
+ it('accesses the line decoration CSS class when the banner is modified', function() {
+ for (const position of [[6, 1], [2, 1], [0, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictModified');
+ assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictModified');
+ assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictModified');
+ });
+
+ it('accesses the banner CSS class', function() {
+ assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictOursBanner');
+ assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictBaseBanner');
+ assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictTheirsBanner');
+ });
+
+ it('accesses the banner CSS class when modified', function() {
+ for (const position of [[5, 1], [3, 1], [1, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ });
+
+ it('accesses the banner CSS class when the banner is modified', function() {
+ for (const position of [[6, 1], [2, 1], [0, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ });
+
+ it('accesses the block CSS classes', function() {
+ assert.strictEqual(
+ conflict.getSide(OURS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictOursBlock github-ConflictTopBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(BASE).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(THEIRS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictBottomBlock',
+ );
+ });
+
+ it('accesses the block CSS classes when modified', function() {
+ for (const position of [[5, 1], [3, 1], [1, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(
+ conflict.getSide(OURS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictOursBlock github-ConflictTopBlock github-ConflictModifiedBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(BASE).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock github-ConflictModifiedBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(THEIRS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictBottomBlock github-ConflictModifiedBlock',
+ );
+ });
+
+ it('accesses the block CSS classes when the banner is modified', function() {
+ for (const position of [[6, 1], [2, 1], [0, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(
+ conflict.getSide(OURS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictOursBlock github-ConflictTopBlock github-ConflictModifiedBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(BASE).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock github-ConflictModifiedBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(THEIRS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictBottomBlock github-ConflictModifiedBlock',
+ );
+ });
+ });
+
+ describe('from a rebase', function() {
+ beforeEach(async function() {
+ editor = await editorOnFixture('single-3way-diff.txt');
+ conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), true)[0];
+ });
+
+ it('accesses the block decoration position', function() {
+ assert.strictEqual(conflict.getSide(THEIRS).getBlockPosition(), 'before');
+ assert.strictEqual(conflict.getSide(BASE).getBlockPosition(), 'before');
+ assert.strictEqual(conflict.getSide(OURS).getBlockPosition(), 'after');
+ });
+
+ it('accesses the line decoration CSS class', function() {
+ assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictTheirs');
+ assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictBase');
+ assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictOurs');
+ });
+
+ it('accesses the line decoration CSS class when modified', function() {
+ for (const position of [[5, 1], [3, 1], [1, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictModified');
+ assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictModified');
+ assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictModified');
+ });
+
+ it('accesses the line decoration CSS class when the banner is modified', function() {
+ for (const position of [[6, 1], [2, 1], [0, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictModified');
+ assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictModified');
+ assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictModified');
+ });
+
+ it('accesses the banner CSS class', function() {
+ assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictTheirsBanner');
+ assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictBaseBanner');
+ assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictOursBanner');
+ });
+
+ it('accesses the banner CSS class when modified', function() {
+ for (const position of [[5, 1], [3, 1], [1, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ });
+
+ it('accesses the banner CSS class when the banner is modified', function() {
+ for (const position of [[6, 1], [2, 1], [0, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictModifiedBanner');
+ });
+
+ it('accesses the block CSS classes', function() {
+ assert.strictEqual(
+ conflict.getSide(THEIRS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictTopBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(BASE).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(OURS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictOursBlock github-ConflictBottomBlock',
+ );
+ });
+
+ it('accesses the block CSS classes when modified', function() {
+ for (const position of [[5, 1], [3, 1], [1, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(
+ conflict.getSide(THEIRS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictTopBlock github-ConflictModifiedBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(BASE).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock github-ConflictModifiedBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(OURS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictOursBlock github-ConflictBottomBlock github-ConflictModifiedBlock',
+ );
+ });
+
+ it('accesses the block CSS classes when the banner is modified', function() {
+ for (const position of [[6, 1], [2, 1], [0, 1]]) {
+ editor.setCursorBufferPosition(position);
+ editor.insertText('change');
+ }
+
+ assert.strictEqual(
+ conflict.getSide(THEIRS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictTopBlock github-ConflictModifiedBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(BASE).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock github-ConflictModifiedBlock',
+ );
+ assert.strictEqual(
+ conflict.getSide(OURS).getBlockCSSClasses(),
+ 'github-ConflictBlock github-ConflictOursBlock github-ConflictBottomBlock github-ConflictModifiedBlock',
+ );
+ });
+ });
+ });
+
+ it('accesses a side range that encompasses the banner and content', async function() {
+ const editor = await editorOnFixture('single-3way-diff.txt');
+ const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0];
+
+ assert.deepEqual(conflict.getSide(OURS).getRange().serialize(), [[0, 0], [2, 0]]);
+ assert.deepEqual(conflict.getSide(BASE).getRange().serialize(), [[2, 0], [4, 0]]);
+ assert.deepEqual(conflict.getSide(THEIRS).getRange().serialize(), [[5, 0], [7, 0]]);
+ });
+
+ it('determines the inclusion of points', async function() {
+ const editor = await editorOnFixture('single-3way-diff.txt');
+ const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0];
+
+ assert.isTrue(conflict.getSide(OURS).includesPoint([0, 1]));
+ assert.isTrue(conflict.getSide(OURS).includesPoint([1, 3]));
+ assert.isFalse(conflict.getSide(OURS).includesPoint([2, 1]));
+ });
+
+ it('detects when a side is empty', async function() {
+ const editor = await editorOnFixture('single-2way-diff-empty.txt');
+ const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0];
+
+ assert.isFalse(conflict.getSide(OURS).isEmpty());
+ assert.isTrue(conflict.getSide(THEIRS).isEmpty());
+ });
+
+ it('reverts a modified Side', async function() {
+ const editor = await editorOnFixture('single-3way-diff.txt');
+ const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0];
+
+ editor.setCursorBufferPosition([5, 10]);
+ editor.insertText('MY-CHANGE');
+
+ assert.isTrue(conflict.getSide(THEIRS).isModified());
+ assert.match(editor.getText(), /MY-CHANGE/);
+
+ conflict.getSide(THEIRS).revert();
+
+ assert.isFalse(conflict.getSide(THEIRS).isModified());
+ assert.notMatch(editor.getText(), /MY-CHANGE/);
+ });
+
+ it('reverts a modified Side banner', async function() {
+ const editor = await editorOnFixture('single-3way-diff.txt');
+ const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0];
+
+ editor.setCursorBufferPosition([6, 4]);
+ editor.insertText('MY-CHANGE');
+
+ assert.isTrue(conflict.getSide(THEIRS).isBannerModified());
+ assert.match(editor.getText(), /MY-CHANGE/);
+
+ conflict.getSide(THEIRS).revertBanner();
+
+ assert.isFalse(conflict.getSide(THEIRS).isBannerModified());
+ assert.notMatch(editor.getText(), /MY-CHANGE/);
+ });
+
+ it('deletes a banner', async function() {
+ const editor = await editorOnFixture('single-3way-diff.txt');
+ const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0];
+
+ assert.match(editor.getText(), /<<<<<<< HEAD/);
+ conflict.getSide(OURS).deleteBanner();
+ assert.notMatch(editor.getText(), /<<<<<<< HEAD/);
+ });
+
+ it('deletes a side', async function() {
+ const editor = await editorOnFixture('single-3way-diff.txt');
+ const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0];
+
+ assert.match(editor.getText(), /your/);
+ conflict.getSide(THEIRS).delete();
+ assert.notMatch(editor.getText(), /your/);
+ });
+
+ it('appends text to a side', async function() {
+ const editor = await editorOnFixture('single-3way-diff.txt');
+ const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0];
+
+ assert.notMatch(editor.getText(), /APPENDED/);
+ conflict.getSide(THEIRS).appendText('APPENDED\n');
+ assert.match(editor.getText(), /APPENDED/);
+
+ assert.isTrue(conflict.getSide(THEIRS).isModified());
+ assert.strictEqual(conflict.getSide(THEIRS).getText(), 'These are your changes\nAPPENDED\n');
+ assert.isFalse(conflict.getSide(THEIRS).isBannerModified());
+ });
});
diff --git a/test/models/repository.test.js b/test/models/repository.test.js
index cfbf9df9b0..e802d8cdd8 100644
--- a/test/models/repository.test.js
+++ b/test/models/repository.test.js
@@ -7,6 +7,7 @@ import isEqualWith from 'lodash.isequalwith';
import Repository from '../../lib/models/repository';
import CompositeGitStrategy from '../../lib/composite-git-strategy';
+import {LargeRepoError} from '../../lib/git-shell-out-strategy';
import {nullCommit} from '../../lib/models/commit';
import {nullOperationStates} from '../../lib/models/operation-states';
import Author from '../../lib/models/author';
@@ -444,6 +445,60 @@ describe('Repository', function() {
});
});
+ describe('getStatusBundle', function() {
+ it('transitions to the TooLarge state and returns empty status when too large', async function() {
+ const workdir = await cloneRepository();
+ const repository = new Repository(workdir);
+ await repository.getLoadPromise();
+
+ sinon.stub(repository.git, 'getStatusBundle').rejects(new LargeRepoError());
+
+ const result = await repository.getStatusBundle();
+
+ assert.isTrue(repository.isInState('TooLarge'));
+ assert.deepEqual(result.branch, {});
+ assert.deepEqual(result.stagedFiles, {});
+ assert.deepEqual(result.unstagedFiles, {});
+ assert.deepEqual(result.mergeConflictFiles, {});
+ });
+
+ it('propagates unrecognized git errors', async function() {
+ const workdir = await cloneRepository();
+ const repository = new Repository(workdir);
+ await repository.getLoadPromise();
+
+ sinon.stub(repository.git, 'getStatusBundle').rejects(new Error('oh no'));
+
+ await assert.isRejected(repository.getStatusBundle(), /oh no/);
+ });
+
+ it('post-processes renamed files to an addition and a deletion', async function() {
+ const workdir = await cloneRepository();
+ const repository = new Repository(workdir);
+ await repository.getLoadPromise();
+
+ sinon.stub(repository.git, 'getStatusBundle').resolves({
+ changedEntries: [],
+ untrackedEntries: [],
+ renamedEntries: [
+ {stagedStatus: 'R', origFilePath: 'from0.txt', filePath: 'to0.txt'},
+ {unstagedStatus: 'R', origFilePath: 'from1.txt', filePath: 'to1.txt'},
+ {stagedStatus: 'C', filePath: 'c2.txt'},
+ {unstagedStatus: 'C', filePath: 'c3.txt'},
+ ],
+ unmergedEntries: [],
+ });
+
+ const result = await repository.getStatusBundle();
+ assert.strictEqual(result.stagedFiles['from0.txt'], 'deleted');
+ assert.strictEqual(result.stagedFiles['to0.txt'], 'added');
+ assert.strictEqual(result.unstagedFiles['from1.txt'], 'deleted');
+ assert.strictEqual(result.unstagedFiles['to1.txt'], 'added');
+ assert.strictEqual(result.stagedFiles['c2.txt'], 'added');
+ assert.strictEqual(result.unstagedFiles['c3.txt'], 'added');
+ });
+ });
+
describe('getFilePatchForPath', function() {
it('returns cached MultiFilePatch objects if they exist', async function() {
const workingDirPath = await cloneRepository('multiple-commits');
@@ -931,6 +986,20 @@ describe('Repository', function() {
});
});
+ describe('unsetConfig', function() {
+ it('unsets a git config option', async function() {
+ const workingDirPath = await cloneRepository('three-files');
+ const repository = new Repository(workingDirPath);
+ await repository.getLoadPromise();
+
+ await repository.setConfig('some.key', 'value');
+ assert.strictEqual(await repository.getConfig('some.key'), 'value');
+
+ await repository.unsetConfig('some.key');
+ assert.isNull(await repository.getConfig('some.key'));
+ });
+ });
+
describe('getCommitter', function() {
it('returns user name and email if they exist', async function() {
const workingDirPath = await cloneRepository('three-files');
@@ -1435,6 +1504,18 @@ describe('Repository', function() {
});
});
+ describe('getBlobContents(sha)', function() {
+ it('returns blob contents for sha', async function() {
+ const workingDirPath = await cloneRepository('three-files');
+ const repository = new Repository(workingDirPath);
+ await repository.getLoadPromise();
+
+ const sha = await repository.createBlob({stdin: 'aa\nbb\ncc\n'});
+ const contents = await repository.getBlobContents(sha);
+ assert.strictEqual(contents, 'aa\nbb\ncc\n');
+ });
+ });
+
describe('discardWorkDirChangesForPaths()', function() {
it('can discard working directory changes in modified files', async function() {
const workingDirPath = await cloneRepository('three-files');