pulsar/spec/pane-container-spec.js
2021-09-06 21:12:47 +02:00

533 lines
16 KiB
JavaScript

const PaneContainer = require('../src/pane-container');
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.length).toBe(6);
expect(events[1]).toEqual([
'did',
{ item: item1, pane: pane1, index: 0 }
]);
expect(events[3]).toEqual([
'did',
{ item: item3, pane: pane2, index: 1 }
]);
expect(events[5]).toEqual([
'did',
{ item: item2, pane: pane2, index: 0 }
]);
expect(events[0][0]).toEqual('will');
expect(events[0][1].item).toEqual(item1);
expect(events[0][1].pane).toEqual(pane1);
expect(events[0][1].index).toEqual(0);
expect(typeof events[0][1].prevent).toEqual('function');
expect(events[2][0]).toEqual('will');
expect(events[2][1].item).toEqual(item3);
expect(events[2][1].pane).toEqual(pane2);
expect(events[2][1].index).toEqual(1);
expect(typeof events[2][1].prevent).toEqual('function');
expect(events[4][0]).toEqual('will');
expect(events[4][1].item).toEqual(item2);
expect(events[4][1].pane).toEqual(pane2);
expect(events[4][1].index).toEqual(0);
expect(typeof events[4][1].prevent).toEqual('function');
});
});
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);
}));
});
});