diff --git a/lib/items/changed-file-item.js b/lib/items/changed-file-item.js index da54340e9d..25a3f1a6e9 100644 --- a/lib/items/changed-file-item.js +++ b/lib/items/changed-file-item.js @@ -45,7 +45,9 @@ export default class ChangedFileItem extends React.Component { this.refEditor = new RefHolder(); this.refEditor.observe(editor => { - this.emitter.emit('did-change-embedded-text-editor', editor); + if (editor.isAlive()) { + this.emitter.emit('did-change-embedded-text-editor', editor); + } }); } @@ -94,7 +96,7 @@ export default class ChangedFileItem extends React.Component { } observeEmbeddedTextEditor(cb) { - this.refEditor.map(editor => cb(editor)); + this.refEditor.map(editor => editor.isAlive() && cb(editor)); return this.emitter.on('did-change-embedded-text-editor', cb); } diff --git a/lib/items/commit-detail-item.js b/lib/items/commit-detail-item.js index 4d019a2a16..7fe4f2cc6e 100644 --- a/lib/items/commit-detail-item.js +++ b/lib/items/commit-detail-item.js @@ -30,7 +30,9 @@ export default class CommitDetailItem extends React.Component { this.refEditor = new RefHolder(); this.refEditor.observe(editor => { - this.emitter.emit('did-change-embedded-text-editor', editor); + if (editor.isAlive()) { + this.emitter.emit('did-change-embedded-text-editor', editor); + } }); } @@ -81,7 +83,7 @@ export default class CommitDetailItem extends React.Component { } observeEmbeddedTextEditor(cb) { - this.refEditor.map(editor => cb(editor)); + this.refEditor.map(editor => editor.isAlive() && cb(editor)); return this.emitter.on('did-change-embedded-text-editor', cb); } diff --git a/lib/items/commit-preview-item.js b/lib/items/commit-preview-item.js index 9b44b3f4ad..e7d675b977 100644 --- a/lib/items/commit-preview-item.js +++ b/lib/items/commit-preview-item.js @@ -32,7 +32,9 @@ export default class CommitPreviewItem extends React.Component { this.refEditor = new RefHolder(); this.refEditor.observe(editor => { - this.emitter.emit('did-change-embedded-text-editor', editor); + if (editor.isAlive()) { + this.emitter.emit('did-change-embedded-text-editor', editor); + } }); } @@ -83,7 +85,7 @@ export default class CommitPreviewItem extends React.Component { } observeEmbeddedTextEditor(cb) { - this.refEditor.map(editor => cb(editor)); + this.refEditor.map(editor => editor.isAlive() && cb(editor)); return this.emitter.on('did-change-embedded-text-editor', cb); } diff --git a/lib/items/issueish-detail-item.js b/lib/items/issueish-detail-item.js index 2d83f30963..afb370b763 100644 --- a/lib/items/issueish-detail-item.js +++ b/lib/items/issueish-detail-item.js @@ -91,7 +91,9 @@ export default class IssueishDetailItem extends Component { this.refEditor = new RefHolder(); this.refEditor.observe(editor => { - this.emitter.emit('did-change-embedded-text-editor', editor); + if (editor.isAlive()) { + this.emitter.emit('did-change-embedded-text-editor', editor); + } }); } @@ -214,7 +216,7 @@ export default class IssueishDetailItem extends Component { } observeEmbeddedTextEditor(cb) { - this.refEditor.map(editor => cb(editor)); + this.refEditor.map(editor => editor.isAlive() && cb(editor)); return this.emitter.on('did-change-embedded-text-editor', cb); } diff --git a/test/items/changed-file-item.test.js b/test/items/changed-file-item.test.js index 0194f9250b..97ac8bda4f 100644 --- a/test/items/changed-file-item.test.js +++ b/test/items/changed-file-item.test.js @@ -57,7 +57,7 @@ describe('ChangedFileItem', function() { ); } - function open(wrapper, options = {}) { + function open(options = {}) { const opts = { relPath: 'a.txt', workingDirectory: repository.getWorkingDirectoryPath(), @@ -70,14 +70,14 @@ describe('ChangedFileItem', function() { it('locates the repository from the context pool', async function() { const wrapper = mount(buildPaneApp()); - await open(wrapper); + await open(); assert.strictEqual(wrapper.update().find('ChangedFileContainer').prop('repository'), repository); }); it('passes an absent repository if the working directory is unrecognized', async function() { const wrapper = mount(buildPaneApp()); - await open(wrapper, {workingDirectory: '/nope'}); + await open({workingDirectory: '/nope'}); assert.isTrue(wrapper.update().find('ChangedFileContainer').prop('repository').isAbsent()); }); @@ -85,22 +85,22 @@ describe('ChangedFileItem', function() { it('passes other props to the container', async function() { const other = Symbol('other'); const wrapper = mount(buildPaneApp({other})); - await open(wrapper); + await open(); assert.strictEqual(wrapper.update().find('ChangedFileContainer').prop('other'), other); }); describe('getTitle()', function() { it('renders an unstaged title', async function() { - const wrapper = mount(buildPaneApp()); - const item = await open(wrapper, {stagingStatus: 'unstaged'}); + mount(buildPaneApp()); + const item = await open({stagingStatus: 'unstaged'}); assert.strictEqual(item.getTitle(), 'Unstaged Changes: a.txt'); }); it('renders a staged title', async function() { - const wrapper = mount(buildPaneApp()); - const item = await open(wrapper, {stagingStatus: 'staged'}); + mount(buildPaneApp()); + const item = await open({stagingStatus: 'staged'}); assert.strictEqual(item.getTitle(), 'Staged Changes: a.txt'); }); @@ -150,14 +150,14 @@ describe('ChangedFileItem', function() { }); it('serializes itself as a FilePatchControllerStub', async function() { - const wrapper = mount(buildPaneApp()); - const item0 = await open(wrapper, {relPath: 'a.txt', workingDirectory: '/dir0', stagingStatus: 'unstaged'}); + mount(buildPaneApp()); + const item0 = await open({relPath: 'a.txt', workingDirectory: '/dir0', stagingStatus: 'unstaged'}); assert.deepEqual(item0.serialize(), { deserializer: 'FilePatchControllerStub', uri: 'atom-github://file-patch/a.txt?workdir=%2Fdir0&stagingStatus=unstaged', }); - const item1 = await open(wrapper, {relPath: 'b.txt', workingDirectory: '/dir1', stagingStatus: 'staged'}); + const item1 = await open({relPath: 'b.txt', workingDirectory: '/dir1', stagingStatus: 'staged'}); assert.deepEqual(item1.serialize(), { deserializer: 'FilePatchControllerStub', uri: 'atom-github://file-patch/b.txt?workdir=%2Fdir1&stagingStatus=staged', @@ -165,8 +165,8 @@ describe('ChangedFileItem', function() { }); it('has some item-level accessors', async function() { - const wrapper = mount(buildPaneApp()); - const item = await open(wrapper, {relPath: 'a.txt', workingDirectory: '/dir', stagingStatus: 'unstaged'}); + mount(buildPaneApp()); + const item = await open({relPath: 'a.txt', workingDirectory: '/dir', stagingStatus: 'unstaged'}); assert.strictEqual(item.getStagingStatus(), 'unstaged'); assert.strictEqual(item.getFilePath(), 'a.txt'); @@ -178,16 +178,18 @@ describe('ChangedFileItem', function() { let sub, editor; beforeEach(function() { - editor = Symbol('editor'); + editor = { + isAlive() { return true; }, + }; }); afterEach(function() { sub && sub.dispose(); }); - it('calls its callback immediately if an editor is present', async function() { + it('calls its callback immediately if an editor is present and alive', async function() { const wrapper = mount(buildPaneApp()); - const item = await open(wrapper); + const item = await open(); wrapper.update().find('ChangedFileContainer').prop('refEditor').setter(editor); @@ -196,9 +198,20 @@ describe('ChangedFileItem', function() { assert.isTrue(cb.calledWith(editor)); }); + it('does not call its callback if an editor is present but destroyed', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(); + + wrapper.update().find('ChangedFileContainer').prop('refEditor').setter({isAlive() { return false; }}); + + const cb = sinon.spy(); + sub = item.observeEmbeddedTextEditor(cb); + assert.isFalse(cb.called); + }); + it('calls its callback later if the editor changes', async function() { const wrapper = mount(buildPaneApp()); - const item = await open(wrapper); + const item = await open(); const cb = sinon.spy(); sub = item.observeEmbeddedTextEditor(cb); @@ -206,5 +219,16 @@ describe('ChangedFileItem', function() { wrapper.update().find('ChangedFileContainer').prop('refEditor').setter(editor); assert.isTrue(cb.calledWith(editor)); }); + + it('does not call its callback after its editor is destroyed', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(); + + const cb = sinon.spy(); + sub = item.observeEmbeddedTextEditor(cb); + + wrapper.update().find('ChangedFileContainer').prop('refEditor').setter({isAlive() { return false; }}); + assert.isFalse(cb.called); + }); }); }); diff --git a/test/items/commit-detail-item.test.js b/test/items/commit-detail-item.test.js index 3760c9ad89..cd0bb820c4 100644 --- a/test/items/commit-detail-item.test.js +++ b/test/items/commit-detail-item.test.js @@ -192,16 +192,18 @@ describe('CommitDetailItem', function() { let sub, editor; beforeEach(function() { - editor = Symbol('editor'); + editor = { + isAlive() { return true; }, + }; }); afterEach(function() { sub && sub.dispose(); }); - it('calls its callback immediately if an editor is present', async function() { + it('calls its callback immediately if an editor is present and alive', async function() { const wrapper = mount(buildPaneApp()); - const item = await open(wrapper); + const item = await open(); wrapper.update().find('CommitDetailContainer').prop('refEditor').setter(editor); @@ -210,9 +212,20 @@ describe('CommitDetailItem', function() { assert.isTrue(cb.calledWith(editor)); }); + it('does not call its callback if an editor is present but destroyed', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(); + + wrapper.update().find('CommitDetailContainer').prop('refEditor').setter({isAlive() { return false; }}); + + const cb = sinon.spy(); + sub = item.observeEmbeddedTextEditor(cb); + assert.isFalse(cb.called); + }); + it('calls its callback later if the editor changes', async function() { const wrapper = mount(buildPaneApp()); - const item = await open(wrapper); + const item = await open(); const cb = sinon.spy(); sub = item.observeEmbeddedTextEditor(cb); @@ -220,5 +233,16 @@ describe('CommitDetailItem', function() { wrapper.update().find('CommitDetailContainer').prop('refEditor').setter(editor); assert.isTrue(cb.calledWith(editor)); }); + + it('does not call its callback after its editor is destroyed', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(); + + const cb = sinon.spy(); + sub = item.observeEmbeddedTextEditor(cb); + + wrapper.update().find('CommitDetailContainer').prop('refEditor').setter({isAlive() { return false; }}); + assert.isFalse(cb.called); + }); }); }); diff --git a/test/items/commit-preview-item.test.js b/test/items/commit-preview-item.test.js index c1e235bf9d..389832be43 100644 --- a/test/items/commit-preview-item.test.js +++ b/test/items/commit-preview-item.test.js @@ -54,7 +54,7 @@ describe('CommitPreviewItem', function() { ); } - function open(wrapper, options = {}) { + function open(options = {}) { const opts = { workingDirectory: repository.getWorkingDirectoryPath(), ...options, @@ -65,7 +65,7 @@ describe('CommitPreviewItem', function() { it('constructs and opens the correct URI', async function() { const wrapper = mount(buildPaneApp()); - await open(wrapper); + await open(); assert.isTrue(wrapper.update().find('CommitPreviewItem').exists()); }); @@ -73,37 +73,37 @@ describe('CommitPreviewItem', function() { it('passes extra props to its container', async function() { const extra = Symbol('extra'); const wrapper = mount(buildPaneApp({extra})); - await open(wrapper); + await open(); assert.strictEqual(wrapper.update().find('CommitPreviewContainer').prop('extra'), extra); }); it('locates the repository from the context pool', async function() { const wrapper = mount(buildPaneApp()); - await open(wrapper); + await open(); assert.strictEqual(wrapper.update().find('CommitPreviewContainer').prop('repository'), repository); }); it('passes an absent repository if the working directory is unrecognized', async function() { const wrapper = mount(buildPaneApp()); - await open(wrapper, {workingDirectory: '/nah'}); + await open({workingDirectory: '/nah'}); assert.isTrue(wrapper.update().find('CommitPreviewContainer').prop('repository').isAbsent()); }); it('returns a fixed title and icon', async function() { - const wrapper = mount(buildPaneApp()); - const item = await open(wrapper); + mount(buildPaneApp()); + const item = await open(); assert.strictEqual(item.getTitle(), 'Staged Changes'); assert.strictEqual(item.getIconName(), 'tasklist'); }); it('terminates pending state', async function() { - const wrapper = mount(buildPaneApp()); + mount(buildPaneApp()); - const item = await open(wrapper); + const item = await open(); const callback = sinon.spy(); const sub = item.onDidTerminatePendingState(callback); @@ -117,9 +117,9 @@ describe('CommitPreviewItem', function() { }); it('may be destroyed once', async function() { - const wrapper = mount(buildPaneApp()); + mount(buildPaneApp()); - const item = await open(wrapper); + const item = await open(); const callback = sinon.spy(); const sub = item.onDidDestroy(callback); @@ -131,14 +131,14 @@ describe('CommitPreviewItem', function() { }); it('serializes itself as a CommitPreviewStub', async function() { - const wrapper = mount(buildPaneApp()); - const item0 = await open(wrapper, {workingDirectory: '/dir0'}); + mount(buildPaneApp()); + const item0 = await open({workingDirectory: '/dir0'}); assert.deepEqual(item0.serialize(), { deserializer: 'CommitPreviewStub', uri: 'atom-github://commit-preview?workdir=%2Fdir0', }); - const item1 = await open(wrapper, {workingDirectory: '/dir1'}); + const item1 = await open({workingDirectory: '/dir1'}); assert.deepEqual(item1.serialize(), { deserializer: 'CommitPreviewStub', uri: 'atom-github://commit-preview?workdir=%2Fdir1', @@ -146,15 +146,15 @@ describe('CommitPreviewItem', function() { }); it('has an item-level accessor for the current working directory', async function() { - const wrapper = mount(buildPaneApp()); - const item = await open(wrapper, {workingDirectory: '/dir7'}); + mount(buildPaneApp()); + const item = await open({workingDirectory: '/dir7'}); assert.strictEqual(item.getWorkingDirectory(), '/dir7'); }); describe('focus()', function() { it('imperatively focuses the value of the initial focus ref', async function() { - const wrapper = mount(buildPaneApp()); - const item = await open(wrapper); + mount(buildPaneApp()); + const item = await open(); const focusSpy = {focus: sinon.spy()}; item.refInitialFocus.setter(focusSpy); @@ -165,8 +165,8 @@ describe('CommitPreviewItem', function() { }); it('is a no-op if there is no initial focus ref', async function() { - const wrapper = mount(buildPaneApp()); - const item = await open(wrapper); + mount(buildPaneApp()); + const item = await open(); item.refInitialFocus.setter(null); @@ -178,16 +178,18 @@ describe('CommitPreviewItem', function() { let sub, editor; beforeEach(function() { - editor = Symbol('editor'); + editor = { + isAlive() { return true; }, + }; }); afterEach(function() { sub && sub.dispose(); }); - it('calls its callback immediately if an editor is present', async function() { + it('calls its callback immediately if an editor is present and alive', async function() { const wrapper = mount(buildPaneApp()); - const item = await open(wrapper); + const item = await open(); wrapper.update().find('CommitPreviewContainer').prop('refEditor').setter(editor); @@ -196,9 +198,20 @@ describe('CommitPreviewItem', function() { assert.isTrue(cb.calledWith(editor)); }); + it('does not call its callback if an editor is present but destroyed', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(); + + wrapper.update().find('CommitPreviewContainer').prop('refEditor').setter({isAlive() { return false; }}); + + const cb = sinon.spy(); + sub = item.observeEmbeddedTextEditor(cb); + assert.isFalse(cb.called); + }); + it('calls its callback later if the editor changes', async function() { const wrapper = mount(buildPaneApp()); - const item = await open(wrapper); + const item = await open(); const cb = sinon.spy(); sub = item.observeEmbeddedTextEditor(cb); @@ -206,5 +219,16 @@ describe('CommitPreviewItem', function() { wrapper.update().find('CommitPreviewContainer').prop('refEditor').setter(editor); assert.isTrue(cb.calledWith(editor)); }); + + it('does not call its callback after its editor is destroyed', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(); + + const cb = sinon.spy(); + sub = item.observeEmbeddedTextEditor(cb); + + wrapper.update().find('CommitPreviewContainer').prop('refEditor').setter({isAlive() { return false; }}); + assert.isFalse(cb.called); + }); }); }); diff --git a/test/items/issueish-detail-item.test.js b/test/items/issueish-detail-item.test.js index 675068e028..a9857a8f11 100644 --- a/test/items/issueish-detail-item.test.js +++ b/test/items/issueish-detail-item.test.js @@ -306,7 +306,9 @@ describe('IssueishDetailItem', function() { let sub, uri, editor; beforeEach(function() { - editor = Symbol('editor'); + editor = { + isAlive() { return true; }, + }; uri = IssueishDetailItem.buildURI({ host: 'one.com', owner: 'me', @@ -320,7 +322,7 @@ describe('IssueishDetailItem', function() { sub && sub.dispose(); }); - it('calls its callback immediately if an editor is present', async function() { + it('calls its callback immediately if an editor is present and alive', async function() { const wrapper = mount(buildApp()); const item = await atomEnv.workspace.open(uri); @@ -331,6 +333,17 @@ describe('IssueishDetailItem', function() { assert.isTrue(cb.calledWith(editor)); }); + it('does not call its callback if an editor is present but destroyed', async function() { + const wrapper = mount(buildApp()); + const item = await atomEnv.workspace.open(uri); + + wrapper.update().find('IssueishDetailContainer').prop('refEditor').setter({isAlive() { return false; }}); + + const cb = sinon.spy(); + sub = item.observeEmbeddedTextEditor(cb); + assert.isFalse(cb.called); + }); + it('calls its callback later if the editor changes', async function() { const wrapper = mount(buildApp()); const item = await atomEnv.workspace.open(uri); @@ -341,6 +354,17 @@ describe('IssueishDetailItem', function() { wrapper.update().find('IssueishDetailContainer').prop('refEditor').setter(editor); assert.isTrue(cb.calledWith(editor)); }); + + it('does not call its callback after its editor is destroyed', async function() { + const wrapper = mount(buildApp()); + const item = await atomEnv.workspace.open(uri); + + const cb = sinon.spy(); + sub = item.observeEmbeddedTextEditor(cb); + + wrapper.update().find('IssueishDetailContainer').prop('refEditor').setter({isAlive() { return false; }}); + assert.isFalse(cb.called); + }); }); describe('tab navigation', function() {