const PaneContainer = require('../src/pane-container') const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers') describe('PaneContainer', () => { let confirm, params beforeEach(() => { confirm = spyOn(atom.applicationDelegate, 'confirm').andCallFake((options, callback) => callback(0)) params = { location: 'center', config: atom.config, deserializerManager: atom.deserializers, applicationDelegate: atom.applicationDelegate, viewRegistry: atom.views } }) describe('serialization', () => { let containerA, pane1A, pane2A, pane3A beforeEach(() => { // This is a dummy item to prevent panes from being empty on deserialization class Item { static deserialize () { return new (this)() } serialize () { return {deserializer: 'Item'} } } atom.deserializers.add(Item) containerA = new PaneContainer(params) pane1A = containerA.getActivePane() pane1A.addItem(new Item()) pane2A = pane1A.splitRight({items: [new Item()]}) pane3A = pane2A.splitDown({items: [new Item()]}) pane3A.focus() }) it('preserves the focused pane across serialization', () => { expect(pane3A.focused).toBe(true) const containerB = new PaneContainer(params) containerB.deserialize(containerA.serialize(), atom.deserializers) const pane3B = containerB.getPanes()[2] expect(pane3B.focused).toBe(true) }) it('preserves the active pane across serialization, independent of focus', () => { pane3A.activate() expect(containerA.getActivePane()).toBe(pane3A) const containerB = new PaneContainer(params) containerB.deserialize(containerA.serialize(), atom.deserializers) const pane3B = containerB.getPanes()[2] expect(containerB.getActivePane()).toBe(pane3B) }) it('makes the first pane active if no pane exists for the activePaneId', () => { pane3A.activate() const state = containerA.serialize() state.activePaneId = -22 const containerB = new PaneContainer(params) containerB.deserialize(state, atom.deserializers) expect(containerB.getActivePane()).toBe(containerB.getPanes()[0]) }) describe('if there are empty panes after deserialization', () => { beforeEach(() => { pane3A.getItems()[0].serialize = () => ({deserializer: 'Bogus'}) }) describe("if the 'core.destroyEmptyPanes' config option is false (the default)", () => it('leaves the empty panes intact', () => { const state = containerA.serialize() const containerB = new PaneContainer(params) containerB.deserialize(state, atom.deserializers) const [leftPane, column] = containerB.getRoot().getChildren() const [topPane, bottomPane] = column.getChildren() expect(leftPane.getItems().length).toBe(1) expect(topPane.getItems().length).toBe(1) expect(bottomPane.getItems().length).toBe(0) }) ) describe("if the 'core.destroyEmptyPanes' config option is true", () => it('removes empty panes on deserialization', () => { atom.config.set('core.destroyEmptyPanes', true) const state = containerA.serialize() const containerB = new PaneContainer(params) containerB.deserialize(state, atom.deserializers) const [leftPane, rightPane] = containerB.getRoot().getChildren() expect(leftPane.getItems().length).toBe(1) expect(rightPane.getItems().length).toBe(1) }) ) }) }) it('does not allow the root pane to be destroyed', () => { const container = new PaneContainer(params) container.getRoot().destroy() expect(container.getRoot()).toBeDefined() expect(container.getRoot().isDestroyed()).toBe(false) }) describe('::getActivePane()', () => { let container, pane1, pane2 beforeEach(() => { container = new PaneContainer(params) pane1 = container.getRoot() }) it('returns the first pane if no pane has been made active', () => { expect(container.getActivePane()).toBe(pane1) expect(pane1.isActive()).toBe(true) }) it('returns the most pane on which ::activate() was most recently called', () => { pane2 = pane1.splitRight() pane2.activate() expect(container.getActivePane()).toBe(pane2) expect(pane1.isActive()).toBe(false) expect(pane2.isActive()).toBe(true) pane1.activate() expect(container.getActivePane()).toBe(pane1) expect(pane1.isActive()).toBe(true) expect(pane2.isActive()).toBe(false) }) it('returns the next pane if the current active pane is destroyed', () => { pane2 = pane1.splitRight() pane2.activate() pane2.destroy() expect(container.getActivePane()).toBe(pane1) expect(pane1.isActive()).toBe(true) }) }) describe('::onDidChangeActivePane()', () => { let container, pane1, pane2, observed beforeEach(() => { container = new PaneContainer(params) container.getRoot().addItems([{}, {}]) container.getRoot().splitRight({items: [{}, {}]}); [pane1, pane2] = container.getPanes() observed = [] container.onDidChangeActivePane(pane => observed.push(pane)) }) it('invokes observers when the active pane changes', () => { pane1.activate() pane2.activate() expect(observed).toEqual([pane1, pane2]) }) }) describe('::onDidChangeActivePaneItem()', () => { let container, pane1, pane2, observed beforeEach(() => { container = new PaneContainer(params) container.getRoot().addItems([{}, {}]) container.getRoot().splitRight({items: [{}, {}]}); [pane1, pane2] = container.getPanes() observed = [] container.onDidChangeActivePaneItem(item => observed.push(item)) }) it('invokes observers when the active item of the active pane changes', () => { pane2.activateNextItem() pane2.activateNextItem() expect(observed).toEqual([pane2.itemAtIndex(1), pane2.itemAtIndex(0)]) }) it('invokes observers when the active pane changes', () => { pane1.activate() pane2.activate() expect(observed).toEqual([pane1.itemAtIndex(0), pane2.itemAtIndex(0)]) }) }) describe('::onDidStopChangingActivePaneItem()', () => { let container, pane1, pane2, observed beforeEach(() => { container = new PaneContainer(params) container.getRoot().addItems([{}, {}]) container.getRoot().splitRight({items: [{}, {}]}); [pane1, pane2] = container.getPanes() observed = [] container.onDidStopChangingActivePaneItem(item => observed.push(item)) }) it('invokes observers once when the active item of the active pane changes', () => { pane2.activateNextItem() pane2.activateNextItem() expect(observed).toEqual([]) advanceClock(100) expect(observed).toEqual([pane2.itemAtIndex(0)]) }) it('invokes observers once when the active pane changes', () => { pane1.activate() pane2.activate() expect(observed).toEqual([]) advanceClock(100) expect(observed).toEqual([pane2.itemAtIndex(0)]) }) }) describe('::onDidActivatePane', () => { it('invokes observers when a pane is activated (even if it was already active)', () => { const container = new PaneContainer(params) container.getRoot().splitRight() const [pane1, pane2] = container.getPanes() const activatedPanes = [] container.onDidActivatePane(pane => activatedPanes.push(pane)) pane1.activate() pane1.activate() pane2.activate() pane2.activate() expect(activatedPanes).toEqual([pane1, pane1, pane2, pane2]) }) }) describe('::observePanes()', () => { it('invokes observers with all current and future panes', () => { const container = new PaneContainer(params) container.getRoot().splitRight() const [pane1, pane2] = container.getPanes() const observed = [] container.observePanes(pane => observed.push(pane)) const pane3 = pane2.splitDown() const pane4 = pane2.splitRight() expect(observed).toEqual([pane1, pane2, pane3, pane4]) }) }) describe('::observePaneItems()', () => it('invokes observers with all current and future pane items', () => { const container = new PaneContainer(params) container.getRoot().addItems([{}, {}]) container.getRoot().splitRight({items: [{}]}) const pane2 = container.getPanes()[1] const observed = [] container.observePaneItems(pane => observed.push(pane)) const pane3 = pane2.splitDown({items: [{}]}) pane3.addItems([{}, {}]) expect(observed).toEqual(container.getPaneItems()) }) ) describe('::confirmClose()', () => { let container, pane1, pane2 beforeEach(() => { class TestItem { shouldPromptToSave () { return true } getURI () { return 'test' } } container = new PaneContainer(params) container.getRoot().splitRight(); [pane1, pane2] = container.getPanes() pane1.addItem(new TestItem()) pane2.addItem(new TestItem()) }) it('returns true if the user saves all modified files when prompted', async () => { confirm.andCallFake((options, callback) => callback(0)) const saved = await container.confirmClose() expect(confirm).toHaveBeenCalled() expect(saved).toBeTruthy() }) it('returns false if the user cancels saving any modified file', async () => { confirm.andCallFake((options, callback) => callback(1)) const saved = await container.confirmClose() expect(confirm).toHaveBeenCalled() expect(saved).toBeFalsy() }) }) describe('::onDidAddPane(callback)', () => { it('invokes the given callback when panes are added', () => { const container = new PaneContainer(params) const events = [] container.onDidAddPane((event) => { expect(container.getPanes().includes(event.pane)).toBe(true) events.push(event) }) const pane1 = container.getActivePane() const pane2 = pane1.splitRight() const pane3 = pane2.splitDown() expect(events).toEqual([{pane: pane2}, {pane: pane3}]) }) }) describe('::onWillDestroyPane(callback)', () => { it('invokes the given callback before panes or their items are destroyed', () => { class TestItem { constructor () { this._isDestroyed = false } destroy () { this._isDestroyed = true } isDestroyed () { return this._isDestroyed } } const container = new PaneContainer(params) const events = [] container.onWillDestroyPane((event) => { const itemsDestroyed = event.pane.getItems().map((item) => item.isDestroyed()) events.push([event, {itemsDestroyed}]) }) const pane1 = container.getActivePane() const pane2 = pane1.splitRight() pane2.addItem(new TestItem()) pane2.destroy() expect(events).toEqual([[{pane: pane2}, {itemsDestroyed: [false]}]]) }) }) describe('::onDidDestroyPane(callback)', () => { it('invokes the given callback when panes are destroyed', () => { const container = new PaneContainer(params) const events = [] container.onDidDestroyPane((event) => { expect(container.getPanes().includes(event.pane)).toBe(false) events.push(event) }) const pane1 = container.getActivePane() const pane2 = pane1.splitRight() const pane3 = pane2.splitDown() pane2.destroy() pane3.destroy() expect(events).toEqual([{pane: pane2}, {pane: pane3}]) }) it('invokes the given callback when the container is destroyed', () => { const container = new PaneContainer(params) const events = [] container.onDidDestroyPane((event) => { expect(container.getPanes().includes(event.pane)).toBe(false) events.push(event) }) const pane1 = container.getActivePane() const pane2 = pane1.splitRight() const pane3 = pane2.splitDown() container.destroy() expect(events).toEqual([{pane: pane1}, {pane: pane2}, {pane: pane3}]) }) }) describe('::onWillDestroyPaneItem() and ::onDidDestroyPaneItem', () => { it('invokes the given callbacks when an item will be destroyed on any pane', async () => { const container = new PaneContainer(params) const pane1 = container.getRoot() const item1 = {} const item2 = {} const item3 = {} pane1.addItem(item1) const events = [] container.onWillDestroyPaneItem(event => events.push(['will', event])) container.onDidDestroyPaneItem(event => events.push(['did', event])) const pane2 = pane1.splitRight({items: [item2, item3]}) await pane1.destroyItem(item1) await pane2.destroyItem(item3) await pane2.destroyItem(item2) expect(events).toEqual([ ['will', {item: item1, pane: pane1, index: 0}], ['did', {item: item1, pane: pane1, index: 0}], ['will', {item: item3, pane: pane2, index: 1}], ['did', {item: item3, pane: pane2, index: 1}], ['will', {item: item2, pane: pane2, index: 0}], ['did', {item: item2, pane: pane2, index: 0}] ]) }) }) describe('::saveAll()', () => it('saves all modified pane items', async () => { const container = new PaneContainer(params) const pane1 = container.getRoot() pane1.splitRight() const item1 = { saved: false, getURI () { return '' }, isModified () { return true }, save () { this.saved = true } } const item2 = { saved: false, getURI () { return '' }, isModified () { return false }, save () { this.saved = true } } const item3 = { saved: false, getURI () { return '' }, isModified () { return true }, save () { this.saved = true } } pane1.addItem(item1) pane1.addItem(item2) pane1.addItem(item3) container.saveAll() expect(item1.saved).toBe(true) expect(item2.saved).toBe(false) expect(item3.saved).toBe(true) }) ) describe('::moveActiveItemToPane(destPane) and ::copyActiveItemToPane(destPane)', () => { let container, pane1, pane2, item1 beforeEach(() => { class TestItem { constructor (id) { this.id = id } copy () { return new TestItem(this.id) } } container = new PaneContainer(params) pane1 = container.getRoot() item1 = new TestItem('1') pane2 = pane1.splitRight({items: [item1]}) }) describe('::::moveActiveItemToPane(destPane)', () => it('moves active item to given pane and focuses it', () => { container.moveActiveItemToPane(pane1) expect(pane1.getActiveItem()).toBe(item1) }) ) describe('::::copyActiveItemToPane(destPane)', () => it('copies active item to given pane and focuses it', () => { container.copyActiveItemToPane(pane1) expect(container.paneForItem(item1)).toBe(pane2) expect(pane1.getActiveItem().id).toBe(item1.id) }) ) }) })