Files
atom/packages/git-diff/spec/git-diff-spec.js
Ruby Allison Rose b079194478 Fix git diff subscriptions (#21968)
The startup script now uses a `Set` to manage `GitDiffView`s held in memory and destroy them when `deactivate` is called.
There are now four major subscription blocks. 
1. The outer subscriptions held by `activate`.
2. The per-editor subscriptions held within `activate`.
3. The per-editor repository event subscriptions held within each `GitDIffView` instance.
4. The per-editor modification event subscriptions held within each `GitDiffView` are only active when the editor content is bound to a valid git repository.

Teardowns of any editor or the module now result in `disposal` of the respective editor's subscriptions or all subscriptions authored within the module.

I removed some of `GitDiffView`'s unnecessary methods such as the `start`, `cancleUpdate`, `addDecoration` and `removeDecorations`;
The last two methods were combined into the body of `updateDiffs`.
`scheduleUpdate` now calls `requestAnimationFrame` instead of `setImmediate` because it's native, standard, and yields
to other more important browser processes. I know Atom Core implements setImmediate, but rAF seems to work just as fast if not faster.
The memory management of the editor markers and diffs have been joined using a WeakMap. When the diffs are destroyed,
so too are the editor markers.
Finally, I added the `destroy` method to handle the teardown of subscriptions and other destroyable objects contained within the `GitDiffViews` before object release.
2021-03-08 21:12:07 +03:00

314 lines
10 KiB
JavaScript

const path = require('path');
const fs = require('fs-plus');
const temp = require('temp').track();
describe('GitDiff package', () => {
let editor, editorElement, projectPath, screenUpdates;
beforeEach(() => {
screenUpdates = 0;
spyOn(window, 'requestAnimationFrame').andCallFake(fn => {
fn();
screenUpdates++;
});
spyOn(window, 'cancelAnimationFrame').andCallFake(i => null);
projectPath = temp.mkdirSync('git-diff-spec-');
const otherPath = temp.mkdirSync('some-other-path-');
fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath);
fs.moveSync(
path.join(projectPath, 'git.git'),
path.join(projectPath, '.git')
);
atom.project.setPaths([otherPath, projectPath]);
jasmine.attachToDOM(atom.workspace.getElement());
waitsForPromise(async () => {
await atom.workspace.open(path.join(projectPath, 'sample.js'));
await atom.packages.activatePackage('git-diff');
});
runs(() => {
editor = atom.workspace.getActiveTextEditor();
editorElement = atom.views.getView(editor);
});
});
afterEach(() => {
temp.cleanup();
});
describe('when the editor has no changes', () => {
it("doesn't mark the editor", () => {
waitsFor(() => screenUpdates > 0);
runs(() => expect(editor.getMarkers().length).toBe(0));
});
});
describe('when the editor has modified lines', () => {
it('highlights the modified lines', () => {
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
0
);
editor.insertText('a');
advanceClock(editor.getBuffer().stoppedChangingDelay);
waitsFor(() => editor.getMarkers().length > 0);
runs(() => {
expect(
editorElement.querySelectorAll('.git-line-modified').length
).toBe(1);
expect(editorElement.querySelector('.git-line-modified')).toHaveData(
'buffer-row',
0
);
});
});
});
describe('when the editor has added lines', () => {
it('highlights the added lines', () => {
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0);
editor.moveToEndOfLine();
editor.insertNewline();
editor.insertText('a');
advanceClock(editor.getBuffer().stoppedChangingDelay);
waitsFor(() => editor.getMarkers().length > 0);
runs(() => {
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(
1
);
expect(editorElement.querySelector('.git-line-added')).toHaveData(
'buffer-row',
1
);
});
});
});
describe('when the editor has removed lines', () => {
it('highlights the line preceeding the deleted lines', () => {
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0);
editor.setCursorBufferPosition([5]);
editor.deleteLine();
advanceClock(editor.getBuffer().stoppedChangingDelay);
waitsFor(() => editor.getMarkers().length > 0);
runs(() => {
expect(editorElement.querySelectorAll('.git-line-removed').length).toBe(
1
);
expect(editorElement.querySelector('.git-line-removed')).toHaveData(
'buffer-row',
4
);
});
});
});
describe('when the editor has removed the first line', () => {
it('highlights the line preceeding the deleted lines', () => {
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0);
editor.setCursorBufferPosition([0, 0]);
editor.deleteLine();
advanceClock(editor.getBuffer().stoppedChangingDelay);
waitsFor(() => editor.getMarkers().length > 0);
runs(() => {
expect(
editorElement.querySelectorAll('.git-previous-line-removed').length
).toBe(1);
expect(
editorElement.querySelector('.git-previous-line-removed')
).toHaveData('buffer-row', 0);
});
});
});
describe('when a modified line is restored to the HEAD version contents', () => {
it('removes the diff highlight', () => {
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
0
);
editor.insertText('a');
advanceClock(editor.getBuffer().stoppedChangingDelay);
waitsFor(
() => editorElement.querySelectorAll('.git-line-modified').length > 0
);
runs(() => {
expect(
editorElement.querySelectorAll('.git-line-modified').length
).toBe(1);
editor.backspace();
advanceClock(editor.getBuffer().stoppedChangingDelay);
});
waitsFor(
() => editorElement.querySelectorAll('.git-line-modified').length < 1
);
runs(() => {
expect(
editorElement.querySelectorAll('.git-line-modified').length
).toBe(0);
});
});
});
describe('when a modified file is opened', () => {
it('highlights the changed lines', () => {
fs.writeFileSync(
path.join(projectPath, 'sample.txt'),
'Some different text.'
);
waitsForPromise(() =>
atom.workspace.open(path.join(projectPath, 'sample.txt'))
);
runs(() => {
editor = atom.workspace.getActiveTextEditor();
editorElement = editor.getElement();
});
waitsFor(() => editor.getMarkers().length > 0);
runs(() => {
expect(
editorElement.querySelectorAll('.git-line-modified').length
).toBe(1);
expect(editorElement.querySelector('.git-line-modified')).toHaveData(
'buffer-row',
0
);
});
});
});
describe('when the project paths change', () => {
it("doesn't try to use the destroyed git repository", () => {
editor.deleteLine();
atom.project.setPaths([temp.mkdirSync('no-repository')]);
advanceClock(editor.getBuffer().stoppedChangingDelay);
waitsFor(() => editor.getMarkers().length === 0);
runs(() => {
expect(editor.getMarkers().length).toBe(0);
});
});
});
describe('move-to-next-diff/move-to-previous-diff events', () => {
it('moves the cursor to first character of the next/previous diff line', () => {
editor.insertText('a');
waitsFor(() => editor.getMarkers().length > 0);
runs(() => {
editor.setCursorBufferPosition([5]);
editor.deleteLine();
advanceClock(editor.getBuffer().stoppedChangingDelay);
editor.setCursorBufferPosition([0]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff');
expect(editor.getCursorBufferPosition()).toEqual([4, 4]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff');
expect(editor.getCursorBufferPosition()).toEqual([0, 0]);
});
});
it('wraps around to the first/last diff in the file', () => {
editor.insertText('a');
waitsFor(() => editor.getMarkers().length > 0);
runs(() => {
editor.setCursorBufferPosition([5]);
editor.deleteLine();
advanceClock(editor.getBuffer().stoppedChangingDelay);
editor.setCursorBufferPosition([0]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff');
expect(editor.getCursorBufferPosition().toArray()).toEqual([4, 4]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff');
expect(editor.getCursorBufferPosition().toArray()).toEqual([0, 0]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff');
expect(editor.getCursorBufferPosition().toArray()).toEqual([4, 4]);
});
});
describe('when the wrapAroundOnMoveToDiff config option is false', () => {
beforeEach(() =>
atom.config.set('git-diff.wrapAroundOnMoveToDiff', false)
);
it('does not wraps around to the first/last diff in the file', () => {
editor.insertText('a');
editor.setCursorBufferPosition([5]);
editor.deleteLine();
advanceClock(editor.getBuffer().stoppedChangingDelay);
waitsFor(() => editor.getMarkers().length > 0);
runs(() => {
editor.setCursorBufferPosition([0]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff');
expect(editor.getCursorBufferPosition()).toEqual([4, 4]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff');
expect(editor.getCursorBufferPosition()).toEqual([4, 4]);
atom.commands.dispatch(
editorElement,
'git-diff:move-to-previous-diff'
);
expect(editor.getCursorBufferPosition()).toEqual([0, 0]);
atom.commands.dispatch(
editorElement,
'git-diff:move-to-previous-diff'
);
expect(editor.getCursorBufferPosition()).toEqual([0, 0]);
});
});
});
});
describe('when the showIconsInEditorGutter config option is true', () => {
beforeEach(() => {
atom.config.set('git-diff.showIconsInEditorGutter', true);
});
it('the gutter has a git-diff-icon class', () => {
waitsFor(() => screenUpdates > 0);
runs(() => {
expect(editorElement.querySelector('.gutter')).toHaveClass(
'git-diff-icon'
);
});
});
it('keeps the git-diff-icon class when editor.showLineNumbers is toggled', () => {
waitsFor(() => screenUpdates > 0);
runs(() => {
atom.config.set('editor.showLineNumbers', false);
expect(editorElement.querySelector('.gutter')).not.toHaveClass(
'git-diff-icon'
);
atom.config.set('editor.showLineNumbers', true);
expect(editorElement.querySelector('.gutter')).toHaveClass(
'git-diff-icon'
);
});
});
it('removes the git-diff-icon class when the showIconsInEditorGutter config option set to false', () => {
waitsFor(() => screenUpdates > 0);
runs(() => {
atom.config.set('git-diff.showIconsInEditorGutter', false);
expect(editorElement.querySelector('.gutter')).not.toHaveClass(
'git-diff-icon'
);
});
});
});
});