123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731 |
- const { extend } = require('underscore-plus');
- const { Emitter } = require('event-kit');
- const Grim = require('grim');
- const Pane = require('../src/pane');
- const PaneContainer = require('../src/pane-container');
- const { conditionPromise, timeoutPromise } = require('./async-spec-helpers');
- describe('Pane', () => {
- let confirm, showSaveDialog, deserializerDisposable;
- class Item {
- static deserialize({ name, uri }) {
- return new Item(name, uri);
- }
- constructor(name, uri) {
- this.name = name;
- this.uri = uri;
- this.emitter = new Emitter();
- this.destroyed = false;
- }
- getURI() {
- return this.uri;
- }
- getPath() {
- return this.path;
- }
- isEqual(other) {
- return this.name === (other && other.name);
- }
- isPermanentDockItem() {
- return false;
- }
- isDestroyed() {
- return this.destroyed;
- }
- serialize() {
- return { deserializer: 'Item', name: this.name, uri: this.uri };
- }
- copy() {
- return new Item(this.name, this.uri);
- }
- destroy() {
- this.destroyed = true;
- return this.emitter.emit('did-destroy');
- }
- onDidDestroy(fn) {
- return this.emitter.on('did-destroy', fn);
- }
- onDidTerminatePendingState(callback) {
- return this.emitter.on('terminate-pending-state', callback);
- }
- terminatePendingState() {
- return this.emitter.emit('terminate-pending-state');
- }
- }
- beforeEach(() => {
- confirm = spyOn(atom.applicationDelegate, 'confirm');
- showSaveDialog = spyOn(atom.applicationDelegate, 'showSaveDialog');
- deserializerDisposable = atom.deserializers.add(Item);
- });
- afterEach(() => {
- deserializerDisposable.dispose();
- });
- function paneParams(params) {
- return extend(
- {
- applicationDelegate: atom.applicationDelegate,
- config: atom.config,
- deserializerManager: atom.deserializers,
- notificationManager: atom.notifications
- },
- params
- );
- }
- describe('construction', () => {
- it('sets the active item to the first item', () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B')] })
- );
- expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0));
- });
- it('compacts the items array', () => {
- const pane = new Pane(
- paneParams({ items: [undefined, new Item('A'), null, new Item('B')] })
- );
- expect(pane.getItems().length).toBe(2);
- expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0));
- });
- });
- describe('::activate()', () => {
- let container, pane1, pane2;
- beforeEach(() => {
- container = new PaneContainer({
- location: 'center',
- config: atom.config,
- applicationDelegate: atom.applicationDelegate
- });
- container.getActivePane().splitRight();
- [pane1, pane2] = container.getPanes();
- });
- it('changes the active pane on the container', () => {
- expect(container.getActivePane()).toBe(pane2);
- pane1.activate();
- expect(container.getActivePane()).toBe(pane1);
- pane2.activate();
- expect(container.getActivePane()).toBe(pane2);
- });
- it('invokes ::onDidChangeActivePane observers on the container', () => {
- const observed = [];
- container.onDidChangeActivePane(activePane => observed.push(activePane));
- pane1.activate();
- pane1.activate();
- pane2.activate();
- pane1.activate();
- expect(observed).toEqual([pane1, pane2, pane1]);
- });
- it('invokes ::onDidChangeActive observers on the relevant panes', () => {
- const observed = [];
- pane1.onDidChangeActive(active => observed.push(active));
- pane1.activate();
- pane2.activate();
- expect(observed).toEqual([true, false]);
- });
- it('invokes ::onDidActivate() observers', () => {
- let eventCount = 0;
- pane1.onDidActivate(() => eventCount++);
- pane1.activate();
- pane1.activate();
- pane2.activate();
- expect(eventCount).toBe(2);
- });
- });
- describe('::addItem(item, index)', () => {
- it('adds the item at the given index', () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B')] })
- );
- const [item1, item2] = pane.getItems();
- const item3 = new Item('C');
- pane.addItem(item3, { index: 1 });
- expect(pane.getItems()).toEqual([item1, item3, item2]);
- });
- it('adds the item after the active item if no index is provided', () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
- );
- const [item1, item2, item3] = pane.getItems();
- pane.activateItem(item2);
- const item4 = new Item('D');
- pane.addItem(item4);
- expect(pane.getItems()).toEqual([item1, item2, item4, item3]);
- });
- it('sets the active item after adding the first item', () => {
- const pane = new Pane(paneParams());
- const item = new Item('A');
- pane.addItem(item);
- expect(pane.getActiveItem()).toBe(item);
- });
- it('invokes ::onDidAddItem() observers', () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B')] })
- );
- const events = [];
- pane.onDidAddItem(event => events.push(event));
- const item = new Item('C');
- pane.addItem(item, { index: 1 });
- expect(events).toEqual([{ item, index: 1, moved: false }]);
- });
- it('throws an exception if the item is already present on a pane', () => {
- const item = new Item('A');
- const container = new PaneContainer({
- config: atom.config,
- applicationDelegate: atom.applicationDelegate
- });
- const pane1 = container.getActivePane();
- pane1.addItem(item);
- const pane2 = pane1.splitRight();
- expect(() => pane2.addItem(item)).toThrow();
- });
- it("throws an exception if the item isn't an object", () => {
- const pane = new Pane(paneParams({ items: [] }));
- expect(() => pane.addItem(null)).toThrow();
- expect(() => pane.addItem('foo')).toThrow();
- expect(() => pane.addItem(1)).toThrow();
- });
- it('destroys any existing pending item', () => {
- const pane = new Pane(paneParams({ items: [] }));
- const itemA = new Item('A');
- const itemB = new Item('B');
- const itemC = new Item('C');
- pane.addItem(itemA, { pending: false });
- pane.addItem(itemB, { pending: true });
- pane.addItem(itemC, { pending: false });
- expect(itemB.isDestroyed()).toBe(true);
- });
- it('adds the new item before destroying any existing pending item', () => {
- const eventOrder = [];
- const pane = new Pane(paneParams({ items: [] }));
- const itemA = new Item('A');
- const itemB = new Item('B');
- pane.addItem(itemA, { pending: true });
- pane.onDidAddItem(function({ item }) {
- if (item === itemB) eventOrder.push('add');
- });
- pane.onDidRemoveItem(function({ item }) {
- if (item === itemA) eventOrder.push('remove');
- });
- pane.addItem(itemB);
- waitsFor(() => eventOrder.length === 2);
- runs(() => expect(eventOrder).toEqual(['add', 'remove']));
- });
- it('subscribes to be notified when item terminates its pending state', () => {
- const fakeDisposable = { dispose: () => {} };
- const spy = jasmine
- .createSpy('onDidTerminatePendingState')
- .andReturn(fakeDisposable);
- const pane = new Pane(paneParams({ items: [] }));
- const item = {
- getTitle: () => '',
- onDidTerminatePendingState: spy
- };
- pane.addItem(item);
- expect(spy).toHaveBeenCalled();
- });
- it('subscribes to be notified when item is destroyed', () => {
- const fakeDisposable = { dispose: () => {} };
- const spy = jasmine.createSpy('onDidDestroy').andReturn(fakeDisposable);
- const pane = new Pane(paneParams({ items: [] }));
- const item = {
- getTitle: () => '',
- onDidDestroy: spy
- };
- pane.addItem(item);
- expect(spy).toHaveBeenCalled();
- });
- describe('when using the old API of ::addItem(item, index)', () => {
- beforeEach(() => spyOn(Grim, 'deprecate'));
- it('supports the older public API', () => {
- const pane = new Pane(paneParams({ items: [] }));
- const itemA = new Item('A');
- const itemB = new Item('B');
- const itemC = new Item('C');
- pane.addItem(itemA, 0);
- pane.addItem(itemB, 0);
- pane.addItem(itemC, 0);
- expect(pane.getItems()).toEqual([itemC, itemB, itemA]);
- });
- it('shows a deprecation warning', () => {
- const pane = new Pane(paneParams({ items: [] }));
- pane.addItem(new Item(), 2);
- expect(Grim.deprecate).toHaveBeenCalledWith(
- 'Pane::addItem(item, 2) is deprecated in favor of Pane::addItem(item, {index: 2})'
- );
- });
- });
- });
- describe('::activateItem(item)', () => {
- let pane = null;
- beforeEach(() => {
- pane = new Pane(paneParams({ items: [new Item('A'), new Item('B')] }));
- });
- it('changes the active item to the current item', () => {
- expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0));
- pane.activateItem(pane.itemAtIndex(1));
- expect(pane.getActiveItem()).toBe(pane.itemAtIndex(1));
- });
- it("adds the given item if it isn't present in ::items", () => {
- const item = new Item('C');
- pane.activateItem(item);
- expect(pane.getItems().includes(item)).toBe(true);
- expect(pane.getActiveItem()).toBe(item);
- });
- it('invokes ::onDidChangeActiveItem() observers', () => {
- const observed = [];
- pane.onDidChangeActiveItem(item => observed.push(item));
- pane.activateItem(pane.itemAtIndex(1));
- expect(observed).toEqual([pane.itemAtIndex(1)]);
- });
- describe('when the item being activated is pending', () => {
- let itemC = null;
- let itemD = null;
- beforeEach(() => {
- itemC = new Item('C');
- itemD = new Item('D');
- });
- it('replaces the active item if it is pending', () => {
- pane.activateItem(itemC, { pending: true });
- expect(pane.getItems().map(item => item.name)).toEqual(['A', 'C', 'B']);
- pane.activateItem(itemD, { pending: true });
- expect(pane.getItems().map(item => item.name)).toEqual(['A', 'D', 'B']);
- });
- it('adds the item after the active item if it is not pending', () => {
- pane.activateItem(itemC, { pending: true });
- pane.activateItemAtIndex(2);
- pane.activateItem(itemD, { pending: true });
- expect(pane.getItems().map(item => item.name)).toEqual(['A', 'B', 'D']);
- });
- });
- });
- describe('::setPendingItem', () => {
- let pane = null;
- beforeEach(() => {
- pane = atom.workspace.getActivePane();
- });
- it('changes the pending item', () => {
- expect(pane.getPendingItem()).toBeNull();
- pane.setPendingItem('fake item');
- expect(pane.getPendingItem()).toEqual('fake item');
- });
- });
- describe('::onItemDidTerminatePendingState callback', () => {
- let pane = null;
- let callbackCalled = false;
- beforeEach(() => {
- pane = atom.workspace.getActivePane();
- callbackCalled = false;
- });
- it('is called when the pending item changes', () => {
- pane.setPendingItem('fake item one');
- pane.onItemDidTerminatePendingState(function(item) {
- callbackCalled = true;
- expect(item).toEqual('fake item one');
- });
- pane.setPendingItem('fake item two');
- expect(callbackCalled).toBeTruthy();
- });
- it('has access to the new pending item via ::getPendingItem', () => {
- pane.setPendingItem('fake item one');
- pane.onItemDidTerminatePendingState(function(item) {
- callbackCalled = true;
- expect(pane.getPendingItem()).toEqual('fake item two');
- });
- pane.setPendingItem('fake item two');
- expect(callbackCalled).toBeTruthy();
- });
- it("isn't called when a pending item is replaced with a new one", async () => {
- pane = null;
- const pendingSpy = jasmine.createSpy('onItemDidTerminatePendingState');
- const destroySpy = jasmine.createSpy('onWillDestroyItem');
- await atom.workspace.open('sample.txt', { pending: true });
- pane = atom.workspace.getActivePane();
- pane.onItemDidTerminatePendingState(pendingSpy);
- pane.onWillDestroyItem(destroySpy);
- await atom.workspace.open('sample.js', { pending: true });
- expect(destroySpy).toHaveBeenCalled();
- expect(pendingSpy).not.toHaveBeenCalled();
- });
- });
- describe('::activateNextRecentlyUsedItem() and ::activatePreviousRecentlyUsedItem()', () => {
- it('sets the active item to the next/previous item in the itemStack, looping around at either end', () => {
- const pane = new Pane(
- paneParams({
- items: [
- new Item('A'),
- new Item('B'),
- new Item('C'),
- new Item('D'),
- new Item('E')
- ]
- })
- );
- const [item1, item2, item3, item4, item5] = pane.getItems();
- pane.itemStack = [item3, item1, item2, item5, item4];
- pane.activateItem(item4);
- expect(pane.getActiveItem()).toBe(item4);
- pane.activateNextRecentlyUsedItem();
- expect(pane.getActiveItem()).toBe(item5);
- pane.activateNextRecentlyUsedItem();
- expect(pane.getActiveItem()).toBe(item2);
- pane.activatePreviousRecentlyUsedItem();
- expect(pane.getActiveItem()).toBe(item5);
- pane.activatePreviousRecentlyUsedItem();
- expect(pane.getActiveItem()).toBe(item4);
- pane.activatePreviousRecentlyUsedItem();
- expect(pane.getActiveItem()).toBe(item3);
- pane.activatePreviousRecentlyUsedItem();
- expect(pane.getActiveItem()).toBe(item1);
- pane.activateNextRecentlyUsedItem();
- expect(pane.getActiveItem()).toBe(item3);
- pane.activateNextRecentlyUsedItem();
- expect(pane.getActiveItem()).toBe(item4);
- pane.activateNextRecentlyUsedItem();
- pane.moveActiveItemToTopOfStack();
- expect(pane.getActiveItem()).toBe(item5);
- expect(pane.itemStack[4]).toBe(item5);
- });
- });
- describe('::activateNextItem() and ::activatePreviousItem()', () => {
- it('sets the active item to the next/previous item, looping around at either end', () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
- );
- const [item1, item2, item3] = pane.getItems();
- expect(pane.getActiveItem()).toBe(item1);
- pane.activatePreviousItem();
- expect(pane.getActiveItem()).toBe(item3);
- pane.activatePreviousItem();
- expect(pane.getActiveItem()).toBe(item2);
- pane.activateNextItem();
- expect(pane.getActiveItem()).toBe(item3);
- pane.activateNextItem();
- expect(pane.getActiveItem()).toBe(item1);
- });
- });
- describe('::activateLastItem()', () => {
- it('sets the active item to the last item', () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
- );
- const [item1, , item3] = pane.getItems();
- expect(pane.getActiveItem()).toBe(item1);
- pane.activateLastItem();
- expect(pane.getActiveItem()).toBe(item3);
- });
- });
- describe('::moveItemRight() and ::moveItemLeft()', () => {
- it('moves the active item to the right and left, without looping around at either end', () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
- );
- const [item1, item2, item3] = pane.getItems();
- pane.activateItemAtIndex(0);
- expect(pane.getActiveItem()).toBe(item1);
- pane.moveItemLeft();
- expect(pane.getItems()).toEqual([item1, item2, item3]);
- pane.moveItemRight();
- expect(pane.getItems()).toEqual([item2, item1, item3]);
- pane.moveItemLeft();
- expect(pane.getItems()).toEqual([item1, item2, item3]);
- pane.activateItemAtIndex(2);
- expect(pane.getActiveItem()).toBe(item3);
- pane.moveItemRight();
- expect(pane.getItems()).toEqual([item1, item2, item3]);
- });
- });
- describe('::activateItemAtIndex(index)', () => {
- it('activates the item at the given index', () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
- );
- const [item1, item2, item3] = pane.getItems();
- pane.activateItemAtIndex(2);
- expect(pane.getActiveItem()).toBe(item3);
- pane.activateItemAtIndex(1);
- expect(pane.getActiveItem()).toBe(item2);
- pane.activateItemAtIndex(0);
- expect(pane.getActiveItem()).toBe(item1);
- // Doesn't fail with out-of-bounds indices
- pane.activateItemAtIndex(100);
- expect(pane.getActiveItem()).toBe(item1);
- pane.activateItemAtIndex(-1);
- expect(pane.getActiveItem()).toBe(item1);
- });
- });
- describe('::destroyItem(item)', () => {
- let pane, item1, item2, item3;
- beforeEach(() => {
- pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
- );
- [item1, item2, item3] = pane.getItems();
- });
- it('removes the item from the items list and destroys it', () => {
- expect(pane.getActiveItem()).toBe(item1);
- pane.destroyItem(item2);
- expect(pane.getItems().includes(item2)).toBe(false);
- expect(item2.isDestroyed()).toBe(true);
- expect(pane.getActiveItem()).toBe(item1);
- pane.destroyItem(item1);
- expect(pane.getItems().includes(item1)).toBe(false);
- expect(item1.isDestroyed()).toBe(true);
- });
- it('removes the item from the itemStack', () => {
- pane.itemStack = [item2, item3, item1];
- pane.activateItem(item1);
- expect(pane.getActiveItem()).toBe(item1);
- pane.destroyItem(item3);
- expect(pane.itemStack).toEqual([item2, item1]);
- expect(pane.getActiveItem()).toBe(item1);
- pane.destroyItem(item1);
- expect(pane.itemStack).toEqual([item2]);
- expect(pane.getActiveItem()).toBe(item2);
- pane.destroyItem(item2);
- expect(pane.itemStack).toEqual([]);
- expect(pane.getActiveItem()).toBeUndefined();
- });
- it('does nothing if prevented', () => {
- const container = new PaneContainer({
- config: atom.config,
- deserializerManager: atom.deserializers,
- applicationDelegate: atom.applicationDelegate
- });
- pane.setContainer(container);
- container.onWillDestroyPaneItem(e => e.prevent());
- pane.itemStack = [item2, item3, item1];
- pane.activateItem(item1);
- expect(pane.getActiveItem()).toBe(item1);
- pane.destroyItem(item3);
- expect(pane.itemStack).toEqual([item2, item3, item1]);
- expect(pane.getActiveItem()).toBe(item1);
- pane.destroyItem(item1);
- expect(pane.itemStack).toEqual([item2, item3, item1]);
- expect(pane.getActiveItem()).toBe(item1);
- pane.destroyItem(item2);
- expect(pane.itemStack).toEqual([item2, item3, item1]);
- expect(pane.getActiveItem()).toBe(item1);
- });
- it('invokes ::onWillDestroyItem() and PaneContainer::onWillDestroyPaneItem observers before destroying the item', async () => {
- jasmine.useRealClock();
- pane.container = new PaneContainer({ config: atom.config, confirm });
- const events = [];
- pane.onWillDestroyItem(async event => {
- expect(item2.isDestroyed()).toBe(false);
- await timeoutPromise(50);
- expect(item2.isDestroyed()).toBe(false);
- events.push(['will-destroy-item', event]);
- });
- pane.container.onWillDestroyPaneItem(async event => {
- expect(item2.isDestroyed()).toBe(false);
- await timeoutPromise(50);
- expect(item2.isDestroyed()).toBe(false);
- events.push(['will-destroy-pane-item', event]);
- });
- await pane.destroyItem(item2);
- expect(item2.isDestroyed()).toBe(true);
- expect(events[0][0]).toEqual('will-destroy-item');
- expect(events[0][1].item).toEqual(item2);
- expect(events[0][1].index).toEqual(1);
- expect(events[1][0]).toEqual('will-destroy-pane-item');
- expect(events[1][1].item).toEqual(item2);
- expect(events[1][1].index).toEqual(1);
- expect(typeof events[1][1].prevent).toEqual('function');
- expect(events[1][1].pane).toEqual(pane);
- });
- it('invokes ::onWillRemoveItem() observers', () => {
- const events = [];
- pane.onWillRemoveItem(event => events.push(event));
- pane.destroyItem(item2);
- expect(events).toEqual([
- { item: item2, index: 1, moved: false, destroyed: true }
- ]);
- });
- it('invokes ::onDidRemoveItem() observers', () => {
- const events = [];
- pane.onDidRemoveItem(event => events.push(event));
- pane.destroyItem(item2);
- expect(events).toEqual([
- { item: item2, index: 1, moved: false, destroyed: true }
- ]);
- });
- describe('when the destroyed item is the active item and is the first item', () => {
- it('activates the next item', () => {
- expect(pane.getActiveItem()).toBe(item1);
- pane.destroyItem(item1);
- expect(pane.getActiveItem()).toBe(item2);
- });
- });
- describe('when the destroyed item is the active item and is not the first item', () => {
- beforeEach(() => pane.activateItem(item2));
- it('activates the previous item', () => {
- expect(pane.getActiveItem()).toBe(item2);
- pane.destroyItem(item2);
- expect(pane.getActiveItem()).toBe(item1);
- });
- });
- describe('if the item is modified', () => {
- let itemURI = null;
- beforeEach(() => {
- item1.shouldPromptToSave = () => true;
- item1.save = jasmine.createSpy('save');
- item1.saveAs = jasmine.createSpy('saveAs');
- item1.getURI = () => itemURI;
- });
- describe('if the [Save] option is selected', () => {
- describe('when the item has a uri', () => {
- it('saves the item before destroying it', async () => {
- itemURI = 'test';
- confirm.andCallFake((options, callback) => callback(0));
- const success = await pane.destroyItem(item1);
- expect(item1.save).toHaveBeenCalled();
- expect(pane.getItems().includes(item1)).toBe(false);
- expect(item1.isDestroyed()).toBe(true);
- expect(success).toBe(true);
- });
- });
- describe('when the item has no uri', () => {
- it('presents a save-as dialog, then saves the item with the given uri before removing and destroying it', async () => {
- jasmine.useRealClock();
- itemURI = null;
- showSaveDialog.andCallFake((options, callback) =>
- callback('/selected/path')
- );
- confirm.andCallFake((options, callback) => callback(0));
- const success = await pane.destroyItem(item1);
- expect(showSaveDialog.mostRecentCall.args[0]).toEqual({});
- await conditionPromise(() => item1.saveAs.callCount === 1);
- expect(item1.saveAs).toHaveBeenCalledWith('/selected/path');
- expect(pane.getItems().includes(item1)).toBe(false);
- expect(item1.isDestroyed()).toBe(true);
- expect(success).toBe(true);
- });
- });
- });
- describe("if the [Don't Save] option is selected", () => {
- it('removes and destroys the item without saving it', async () => {
- confirm.andCallFake((options, callback) => callback(2));
- const success = await pane.destroyItem(item1);
- expect(item1.save).not.toHaveBeenCalled();
- expect(pane.getItems().includes(item1)).toBe(false);
- expect(item1.isDestroyed()).toBe(true);
- expect(success).toBe(true);
- });
- });
- describe('if the [Cancel] option is selected', () => {
- it('does not save, remove, or destroy the item', async () => {
- confirm.andCallFake((options, callback) => callback(1));
- const success = await pane.destroyItem(item1);
- expect(item1.save).not.toHaveBeenCalled();
- expect(pane.getItems().includes(item1)).toBe(true);
- expect(item1.isDestroyed()).toBe(false);
- expect(success).toBe(false);
- });
- });
- describe('when force=true', () => {
- it('destroys the item immediately', async () => {
- const success = await pane.destroyItem(item1, true);
- expect(item1.save).not.toHaveBeenCalled();
- expect(pane.getItems().includes(item1)).toBe(false);
- expect(item1.isDestroyed()).toBe(true);
- expect(success).toBe(true);
- });
- });
- });
- describe('when the last item is destroyed', () => {
- describe("when the 'core.destroyEmptyPanes' config option is false (the default)", () => {
- it('does not destroy the pane, but leaves it in place with empty items', () => {
- expect(atom.config.get('core.destroyEmptyPanes')).toBe(false);
- for (let item of pane.getItems()) {
- pane.destroyItem(item);
- }
- expect(pane.isDestroyed()).toBe(false);
- expect(pane.getActiveItem()).toBeUndefined();
- expect(() => pane.saveActiveItem()).not.toThrow();
- expect(() => pane.saveActiveItemAs()).not.toThrow();
- });
- });
- describe("when the 'core.destroyEmptyPanes' config option is true", () => {
- it('destroys the pane', () => {
- atom.config.set('core.destroyEmptyPanes', true);
- for (let item of pane.getItems()) {
- pane.destroyItem(item);
- }
- expect(pane.isDestroyed()).toBe(true);
- });
- });
- });
- describe('when passed a permanent dock item', () => {
- it("doesn't destroy the item", async () => {
- spyOn(item1, 'isPermanentDockItem').andReturn(true);
- const success = await pane.destroyItem(item1);
- expect(pane.getItems().includes(item1)).toBe(true);
- expect(item1.isDestroyed()).toBe(false);
- expect(success).toBe(false);
- });
- it('destroy the item if force=true', async () => {
- spyOn(item1, 'isPermanentDockItem').andReturn(true);
- const success = await pane.destroyItem(item1, true);
- expect(pane.getItems().includes(item1)).toBe(false);
- expect(item1.isDestroyed()).toBe(true);
- expect(success).toBe(true);
- });
- });
- });
- describe('::destroyActiveItem()', () => {
- it('destroys the active item', () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B')] })
- );
- const activeItem = pane.getActiveItem();
- pane.destroyActiveItem();
- expect(activeItem.isDestroyed()).toBe(true);
- expect(pane.getItems().includes(activeItem)).toBe(false);
- });
- it('does not throw an exception if there are no more items', () => {
- const pane = new Pane(paneParams());
- pane.destroyActiveItem();
- });
- });
- describe('::destroyItems()', () => {
- it('destroys all items', async () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
- );
- const [item1, item2, item3] = pane.getItems();
- await pane.destroyItems();
- expect(item1.isDestroyed()).toBe(true);
- expect(item2.isDestroyed()).toBe(true);
- expect(item3.isDestroyed()).toBe(true);
- expect(pane.getItems()).toEqual([]);
- });
- });
- describe('::observeItems()', () => {
- it('invokes the observer with all current and future items', () => {
- const pane = new Pane(paneParams({ items: [new Item(), new Item()] }));
- const [item1, item2] = pane.getItems();
- const observed = [];
- pane.observeItems(item => observed.push(item));
- const item3 = new Item();
- pane.addItem(item3);
- expect(observed).toEqual([item1, item2, item3]);
- });
- });
- describe('when an item emits a destroyed event', () => {
- it('removes it from the list of items', () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
- );
- const [item1, , item3] = pane.getItems();
- pane.itemAtIndex(1).destroy();
- expect(pane.getItems()).toEqual([item1, item3]);
- });
- });
- describe('::destroyInactiveItems()', () => {
- it('destroys all items but the active item', () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
- );
- const [, item2] = pane.getItems();
- pane.activateItem(item2);
- pane.destroyInactiveItems();
- expect(pane.getItems()).toEqual([item2]);
- });
- });
- describe('::saveActiveItem()', () => {
- let pane;
- beforeEach(() => {
- pane = new Pane(paneParams({ items: [new Item('A')] }));
- showSaveDialog.andCallFake((options, callback) =>
- callback('/selected/path')
- );
- });
- describe('when the active item has a uri', () => {
- beforeEach(() => {
- pane.getActiveItem().uri = 'test';
- });
- describe('when the active item has a save method', () => {
- it('saves the current item', () => {
- pane.getActiveItem().save = jasmine.createSpy('save');
- pane.saveActiveItem();
- expect(pane.getActiveItem().save).toHaveBeenCalled();
- });
- });
- describe('when the current item has no save method', () => {
- it('does nothing', () => {
- expect(pane.getActiveItem().save).toBeUndefined();
- pane.saveActiveItem();
- });
- });
- });
- describe('when the current item has no uri', () => {
- describe('when the current item has a saveAs method', () => {
- it('opens a save dialog and saves the current item as the selected path', async () => {
- pane.getActiveItem().saveAs = jasmine.createSpy('saveAs');
- await pane.saveActiveItem();
- expect(showSaveDialog.mostRecentCall.args[0]).toEqual({});
- expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith(
- '/selected/path'
- );
- });
- });
- describe('when the current item has no saveAs method', () => {
- it('does nothing', async () => {
- expect(pane.getActiveItem().saveAs).toBeUndefined();
- await pane.saveActiveItem();
- expect(showSaveDialog).not.toHaveBeenCalled();
- });
- });
- it('does nothing if the user cancels choosing a path', async () => {
- pane.getActiveItem().saveAs = jasmine.createSpy('saveAs');
- showSaveDialog.andCallFake((options, callback) => callback(undefined));
- await pane.saveActiveItem();
- expect(pane.getActiveItem().saveAs).not.toHaveBeenCalled();
- });
- });
- describe("when the item's saveAs rejects with a well-known IO error", () => {
- it('creates a notification', () => {
- pane.getActiveItem().saveAs = () => {
- const error = new Error("EACCES, permission denied '/foo'");
- error.path = '/foo';
- error.code = 'EACCES';
- return Promise.reject(error);
- };
- waitsFor(done => {
- const subscription = atom.notifications.onDidAddNotification(function(
- notification
- ) {
- expect(notification.getType()).toBe('warning');
- expect(notification.getMessage()).toContain('Permission denied');
- expect(notification.getMessage()).toContain('/foo');
- subscription.dispose();
- done();
- });
- pane.saveActiveItem();
- });
- });
- });
- describe("when the item's saveAs throws a well-known IO error", () => {
- it('creates a notification', () => {
- pane.getActiveItem().saveAs = () => {
- const error = new Error("EACCES, permission denied '/foo'");
- error.path = '/foo';
- error.code = 'EACCES';
- throw error;
- };
- waitsFor(done => {
- const subscription = atom.notifications.onDidAddNotification(function(
- notification
- ) {
- expect(notification.getType()).toBe('warning');
- expect(notification.getMessage()).toContain('Permission denied');
- expect(notification.getMessage()).toContain('/foo');
- subscription.dispose();
- done();
- });
- pane.saveActiveItem();
- });
- });
- });
- });
- describe('::saveActiveItemAs()', () => {
- let pane = null;
- beforeEach(() => {
- pane = new Pane(paneParams({ items: [new Item('A')] }));
- showSaveDialog.andCallFake((options, callback) =>
- callback('/selected/path')
- );
- });
- describe('when the current item has a saveAs method', () => {
- it('opens the save dialog and calls saveAs on the item with the selected path', async () => {
- jasmine.useRealClock();
- pane.getActiveItem().path = __filename;
- pane.getActiveItem().saveAs = jasmine.createSpy('saveAs');
- pane.saveActiveItemAs();
- expect(showSaveDialog.mostRecentCall.args[0]).toEqual({
- defaultPath: __filename
- });
- await conditionPromise(
- () => pane.getActiveItem().saveAs.callCount === 1
- );
- expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith(
- '/selected/path'
- );
- });
- });
- describe('when the current item does not have a saveAs method', () => {
- it('does nothing', () => {
- expect(pane.getActiveItem().saveAs).toBeUndefined();
- pane.saveActiveItemAs();
- expect(showSaveDialog).not.toHaveBeenCalled();
- });
- });
- describe("when the item's saveAs method throws a well-known IO error", () => {
- it('creates a notification', () => {
- pane.getActiveItem().saveAs = () => {
- const error = new Error("EACCES, permission denied '/foo'");
- error.path = '/foo';
- error.code = 'EACCES';
- return Promise.reject(error);
- };
- waitsFor(done => {
- const subscription = atom.notifications.onDidAddNotification(function(
- notification
- ) {
- expect(notification.getType()).toBe('warning');
- expect(notification.getMessage()).toContain('Permission denied');
- expect(notification.getMessage()).toContain('/foo');
- subscription.dispose();
- done();
- });
- pane.saveActiveItemAs();
- });
- });
- });
- });
- describe('::itemForURI(uri)', () => {
- it('returns the item for which a call to .getURI() returns the given uri', () => {
- const pane = new Pane(
- paneParams({
- items: [new Item('A'), new Item('B'), new Item('C'), new Item('D')]
- })
- );
- const [item1, item2] = pane.getItems();
- item1.uri = 'a';
- item2.uri = 'b';
- expect(pane.itemForURI('a')).toBe(item1);
- expect(pane.itemForURI('b')).toBe(item2);
- expect(pane.itemForURI('bogus')).toBeUndefined();
- });
- });
- describe('::moveItem(item, index)', () => {
- let pane, item1, item2, item3, item4;
- beforeEach(() => {
- pane = new Pane(
- paneParams({
- items: [new Item('A'), new Item('B'), new Item('C'), new Item('D')]
- })
- );
- [item1, item2, item3, item4] = pane.getItems();
- });
- it('moves the item to the given index and invokes ::onDidMoveItem observers', () => {
- pane.moveItem(item1, 2);
- expect(pane.getItems()).toEqual([item2, item3, item1, item4]);
- pane.moveItem(item2, 3);
- expect(pane.getItems()).toEqual([item3, item1, item4, item2]);
- pane.moveItem(item2, 1);
- expect(pane.getItems()).toEqual([item3, item2, item1, item4]);
- });
- it('invokes ::onDidMoveItem() observers', () => {
- const events = [];
- pane.onDidMoveItem(event => events.push(event));
- pane.moveItem(item1, 2);
- pane.moveItem(item2, 3);
- expect(events).toEqual([
- { item: item1, oldIndex: 0, newIndex: 2 },
- { item: item2, oldIndex: 0, newIndex: 3 }
- ]);
- });
- });
- describe('::moveItemToPane(item, pane, index)', () => {
- let container, pane1, pane2;
- let item1, item2, item3, item4, item5;
- beforeEach(() => {
- container = new PaneContainer({ config: atom.config, confirm });
- pane1 = container.getActivePane();
- pane1.addItems([new Item('A'), new Item('B'), new Item('C')]);
- pane2 = pane1.splitRight({ items: [new Item('D'), new Item('E')] });
- [item1, item2, item3] = pane1.getItems();
- [item4, item5] = pane2.getItems();
- });
- it('moves the item to the given pane at the given index', () => {
- pane1.moveItemToPane(item2, pane2, 1);
- expect(pane1.getItems()).toEqual([item1, item3]);
- expect(pane2.getItems()).toEqual([item4, item2, item5]);
- });
- it('invokes ::onWillRemoveItem() observers', () => {
- const events = [];
- pane1.onWillRemoveItem(event => events.push(event));
- pane1.moveItemToPane(item2, pane2, 1);
- expect(events).toEqual([
- { item: item2, index: 1, moved: true, destroyed: false }
- ]);
- });
- it('invokes ::onDidRemoveItem() observers', () => {
- const events = [];
- pane1.onDidRemoveItem(event => events.push(event));
- pane1.moveItemToPane(item2, pane2, 1);
- expect(events).toEqual([
- { item: item2, index: 1, moved: true, destroyed: false }
- ]);
- });
- it('does not invoke ::onDidAddPaneItem observers on the container', () => {
- const addedItems = [];
- container.onDidAddPaneItem(item => addedItems.push(item));
- pane1.moveItemToPane(item2, pane2, 1);
- expect(addedItems).toEqual([]);
- });
- describe('when the moved item the last item in the source pane', () => {
- beforeEach(() => item5.destroy());
- describe("when the 'core.destroyEmptyPanes' config option is false (the default)", () => {
- it('does not destroy the pane or the item', () => {
- pane2.moveItemToPane(item4, pane1, 0);
- expect(pane2.isDestroyed()).toBe(false);
- expect(item4.isDestroyed()).toBe(false);
- });
- });
- describe("when the 'core.destroyEmptyPanes' config option is true", () => {
- it('destroys the pane, but not the item', () => {
- atom.config.set('core.destroyEmptyPanes', true);
- pane2.moveItemToPane(item4, pane1, 0);
- expect(pane2.isDestroyed()).toBe(true);
- expect(item4.isDestroyed()).toBe(false);
- });
- });
- });
- describe('when the item being moved is pending', () => {
- it('is made permanent in the new pane', () => {
- const item6 = new Item('F');
- pane1.addItem(item6, { pending: true });
- expect(pane1.getPendingItem()).toEqual(item6);
- pane1.moveItemToPane(item6, pane2, 0);
- expect(pane2.getPendingItem()).not.toEqual(item6);
- });
- });
- describe('when the target pane has a pending item', () => {
- it('does not destroy the pending item', () => {
- const item6 = new Item('F');
- pane1.addItem(item6, { pending: true });
- expect(pane1.getPendingItem()).toEqual(item6);
- pane2.moveItemToPane(item5, pane1, 0);
- expect(pane1.getPendingItem()).toEqual(item6);
- });
- });
- });
- describe('split methods', () => {
- let pane1, item1, container;
- beforeEach(() => {
- container = new PaneContainer({
- config: atom.config,
- confirm,
- deserializerManager: atom.deserializers
- });
- pane1 = container.getActivePane();
- item1 = new Item('A');
- pane1.addItem(item1);
- });
- describe('::splitLeft(params)', () => {
- describe('when the parent is the container root', () => {
- it('replaces itself with a row and inserts a new pane to the left of itself', () => {
- const pane2 = pane1.splitLeft({ items: [new Item('B')] });
- const pane3 = pane1.splitLeft({ items: [new Item('C')] });
- expect(container.root.orientation).toBe('horizontal');
- expect(container.root.children).toEqual([pane2, pane3, pane1]);
- });
- });
- describe('when `moveActiveItem: true` is passed in the params', () => {
- it('moves the active item', () => {
- const pane2 = pane1.splitLeft({ moveActiveItem: true });
- expect(pane2.getActiveItem()).toBe(item1);
- });
- });
- describe('when `copyActiveItem: true` is passed in the params', () => {
- it('duplicates the active item', () => {
- const pane2 = pane1.splitLeft({ copyActiveItem: true });
- expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem());
- });
- it("does nothing if the active item doesn't implement .copy()", () => {
- item1.copy = null;
- const pane2 = pane1.splitLeft({ copyActiveItem: true });
- expect(pane2.getActiveItem()).toBeUndefined();
- });
- });
- describe('when the parent is a column', () => {
- it('replaces itself with a row and inserts a new pane to the left of itself', () => {
- pane1.splitDown();
- const pane2 = pane1.splitLeft({ items: [new Item('B')] });
- const pane3 = pane1.splitLeft({ items: [new Item('C')] });
- const row = container.root.children[0];
- expect(row.orientation).toBe('horizontal');
- expect(row.children).toEqual([pane2, pane3, pane1]);
- });
- });
- });
- describe('::splitRight(params)', () => {
- describe('when the parent is the container root', () => {
- it('replaces itself with a row and inserts a new pane to the right of itself', () => {
- const pane2 = pane1.splitRight({ items: [new Item('B')] });
- const pane3 = pane1.splitRight({ items: [new Item('C')] });
- expect(container.root.orientation).toBe('horizontal');
- expect(container.root.children).toEqual([pane1, pane3, pane2]);
- });
- });
- describe('when `moveActiveItem: true` is passed in the params', () => {
- it('moves the active item', () => {
- const pane2 = pane1.splitRight({ moveActiveItem: true });
- expect(pane2.getActiveItem()).toBe(item1);
- });
- });
- describe('when `copyActiveItem: true` is passed in the params', () => {
- it('duplicates the active item', () => {
- const pane2 = pane1.splitRight({ copyActiveItem: true });
- expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem());
- });
- });
- describe('when the parent is a column', () => {
- it('replaces itself with a row and inserts a new pane to the right of itself', () => {
- pane1.splitDown();
- const pane2 = pane1.splitRight({ items: [new Item('B')] });
- const pane3 = pane1.splitRight({ items: [new Item('C')] });
- const row = container.root.children[0];
- expect(row.orientation).toBe('horizontal');
- expect(row.children).toEqual([pane1, pane3, pane2]);
- });
- });
- });
- describe('::splitUp(params)', () => {
- describe('when the parent is the container root', () => {
- it('replaces itself with a column and inserts a new pane above itself', () => {
- const pane2 = pane1.splitUp({ items: [new Item('B')] });
- const pane3 = pane1.splitUp({ items: [new Item('C')] });
- expect(container.root.orientation).toBe('vertical');
- expect(container.root.children).toEqual([pane2, pane3, pane1]);
- });
- });
- describe('when `moveActiveItem: true` is passed in the params', () => {
- it('moves the active item', () => {
- const pane2 = pane1.splitUp({ moveActiveItem: true });
- expect(pane2.getActiveItem()).toBe(item1);
- });
- });
- describe('when `copyActiveItem: true` is passed in the params', () => {
- it('duplicates the active item', () => {
- const pane2 = pane1.splitUp({ copyActiveItem: true });
- expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem());
- });
- });
- describe('when the parent is a row', () => {
- it('replaces itself with a column and inserts a new pane above itself', () => {
- pane1.splitRight();
- const pane2 = pane1.splitUp({ items: [new Item('B')] });
- const pane3 = pane1.splitUp({ items: [new Item('C')] });
- const column = container.root.children[0];
- expect(column.orientation).toBe('vertical');
- expect(column.children).toEqual([pane2, pane3, pane1]);
- });
- });
- });
- describe('::splitDown(params)', () => {
- describe('when the parent is the container root', () => {
- it('replaces itself with a column and inserts a new pane below itself', () => {
- const pane2 = pane1.splitDown({ items: [new Item('B')] });
- const pane3 = pane1.splitDown({ items: [new Item('C')] });
- expect(container.root.orientation).toBe('vertical');
- expect(container.root.children).toEqual([pane1, pane3, pane2]);
- });
- });
- describe('when `moveActiveItem: true` is passed in the params', () => {
- it('moves the active item', () => {
- const pane2 = pane1.splitDown({ moveActiveItem: true });
- expect(pane2.getActiveItem()).toBe(item1);
- });
- });
- describe('when `copyActiveItem: true` is passed in the params', () => {
- it('duplicates the active item', () => {
- const pane2 = pane1.splitDown({ copyActiveItem: true });
- expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem());
- });
- });
- describe('when the parent is a row', () => {
- it('replaces itself with a column and inserts a new pane below itself', () => {
- pane1.splitRight();
- const pane2 = pane1.splitDown({ items: [new Item('B')] });
- const pane3 = pane1.splitDown({ items: [new Item('C')] });
- const column = container.root.children[0];
- expect(column.orientation).toBe('vertical');
- expect(column.children).toEqual([pane1, pane3, pane2]);
- });
- });
- });
- describe('when the pane is empty', () => {
- describe('when `moveActiveItem: true` is passed in the params', () => {
- it('gracefully ignores the moveActiveItem parameter', () => {
- pane1.destroyItem(item1);
- expect(pane1.getActiveItem()).toBe(undefined);
- const pane2 = pane1.split('horizontal', 'before', {
- moveActiveItem: true
- });
- expect(container.root.children).toEqual([pane2, pane1]);
- expect(pane2.getActiveItem()).toBe(undefined);
- });
- });
- describe('when `copyActiveItem: true` is passed in the params', () => {
- it('gracefully ignores the copyActiveItem parameter', () => {
- pane1.destroyItem(item1);
- expect(pane1.getActiveItem()).toBe(undefined);
- const pane2 = pane1.split('horizontal', 'before', {
- copyActiveItem: true
- });
- expect(container.root.children).toEqual([pane2, pane1]);
- expect(pane2.getActiveItem()).toBe(undefined);
- });
- });
- });
- it('activates the new pane', () => {
- expect(pane1.isActive()).toBe(true);
- const pane2 = pane1.splitRight();
- expect(pane1.isActive()).toBe(false);
- expect(pane2.isActive()).toBe(true);
- });
- });
- describe('::close()', () => {
- it('prompts to save unsaved items before destroying the pane', async () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B')] })
- );
- const [item1] = pane.getItems();
- item1.shouldPromptToSave = () => true;
- item1.getURI = () => '/test/path';
- item1.save = jasmine.createSpy('save');
- confirm.andCallFake((options, callback) => callback(0));
- await pane.close();
- expect(confirm).toHaveBeenCalled();
- expect(item1.save).toHaveBeenCalled();
- expect(pane.isDestroyed()).toBe(true);
- });
- it('does not destroy the pane if the user clicks cancel', async () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B')] })
- );
- const [item1] = pane.getItems();
- item1.shouldPromptToSave = () => true;
- item1.getURI = () => '/test/path';
- item1.save = jasmine.createSpy('save');
- confirm.andCallFake((options, callback) => callback(1));
- await pane.close();
- expect(confirm).toHaveBeenCalled();
- expect(item1.save).not.toHaveBeenCalled();
- expect(pane.isDestroyed()).toBe(false);
- });
- it('does not destroy the pane if the user starts to save but then does not choose a path', async () => {
- const pane = new Pane(
- paneParams({ items: [new Item('A'), new Item('B')] })
- );
- const [item1] = pane.getItems();
- item1.shouldPromptToSave = () => true;
- item1.saveAs = jasmine.createSpy('saveAs');
- confirm.andCallFake((options, callback) => callback(0));
- showSaveDialog.andCallFake((options, callback) => callback(undefined));
- await pane.close();
- expect(atom.applicationDelegate.confirm).toHaveBeenCalled();
- expect(confirm.callCount).toBe(1);
- expect(item1.saveAs).not.toHaveBeenCalled();
- expect(pane.isDestroyed()).toBe(false);
- });
- describe('when item fails to save', () => {
- let pane, item1;
- beforeEach(() => {
- pane = new Pane({
- items: [new Item('A'), new Item('B')],
- applicationDelegate: atom.applicationDelegate,
- config: atom.config
- });
- [item1] = pane.getItems();
- item1.shouldPromptToSave = () => true;
- item1.getURI = () => '/test/path';
- item1.save = jasmine.createSpy('save').andCallFake(() => {
- const error = new Error("EACCES, permission denied '/test/path'");
- error.path = '/test/path';
- error.code = 'EACCES';
- throw error;
- });
- });
- it('does not destroy the pane if save fails and user clicks cancel', async () => {
- let confirmations = 0;
- confirm.andCallFake((options, callback) => {
- confirmations++;
- if (confirmations === 1) {
- callback(0); // click save
- } else {
- callback(1);
- }
- }); // click cancel
- await pane.close();
- expect(atom.applicationDelegate.confirm).toHaveBeenCalled();
- expect(confirmations).toBe(2);
- expect(item1.save).toHaveBeenCalled();
- expect(pane.isDestroyed()).toBe(false);
- });
- it('does destroy the pane if the user saves the file under a new name', async () => {
- item1.saveAs = jasmine.createSpy('saveAs').andReturn(true);
- let confirmations = 0;
- confirm.andCallFake((options, callback) => {
- confirmations++;
- callback(0);
- }); // save and then save as
- showSaveDialog.andCallFake((options, callback) => callback('new/path'));
- await pane.close();
- expect(atom.applicationDelegate.confirm).toHaveBeenCalled();
- expect(confirmations).toBe(2);
- expect(
- atom.applicationDelegate.showSaveDialog.mostRecentCall.args[0]
- ).toEqual({});
- expect(item1.save).toHaveBeenCalled();
- expect(item1.saveAs).toHaveBeenCalled();
- expect(pane.isDestroyed()).toBe(true);
- });
- it('asks again if the saveAs also fails', async () => {
- item1.saveAs = jasmine.createSpy('saveAs').andCallFake(() => {
- const error = new Error("EACCES, permission denied '/test/path'");
- error.path = '/test/path';
- error.code = 'EACCES';
- throw error;
- });
- let confirmations = 0;
- confirm.andCallFake((options, callback) => {
- confirmations++;
- if (confirmations < 3) {
- callback(0); // save, save as, save as
- } else {
- callback(2); // don't save
- }
- });
- showSaveDialog.andCallFake((options, callback) => callback('new/path'));
- await pane.close();
- expect(atom.applicationDelegate.confirm).toHaveBeenCalled();
- expect(confirmations).toBe(3);
- expect(
- atom.applicationDelegate.showSaveDialog.mostRecentCall.args[0]
- ).toEqual({});
- expect(item1.save).toHaveBeenCalled();
- expect(item1.saveAs).toHaveBeenCalled();
- expect(pane.isDestroyed()).toBe(true);
- });
- });
- });
- describe('::destroy()', () => {
- let container, pane1, pane2;
- beforeEach(() => {
- container = new PaneContainer({ config: atom.config, confirm });
- pane1 = container.root;
- pane1.addItems([new Item('A'), new Item('B')]);
- pane2 = pane1.splitRight();
- });
- it('invokes ::onWillDestroy observers before destroying items', () => {
- let itemsDestroyed = null;
- pane1.onWillDestroy(() => {
- itemsDestroyed = pane1.getItems().map(item => item.isDestroyed());
- });
- pane1.destroy();
- expect(itemsDestroyed).toEqual([false, false]);
- });
- it("destroys the pane's destroyable items", () => {
- const [item1, item2] = pane1.getItems();
- pane1.destroy();
- expect(item1.isDestroyed()).toBe(true);
- expect(item2.isDestroyed()).toBe(true);
- });
- describe('if the pane is active', () => {
- it('makes the next pane active', () => {
- expect(pane2.isActive()).toBe(true);
- pane2.destroy();
- expect(pane1.isActive()).toBe(true);
- });
- });
- describe("if the pane's parent has more than two children", () => {
- it('removes the pane from its parent', () => {
- const pane3 = pane2.splitRight();
- expect(container.root.children).toEqual([pane1, pane2, pane3]);
- pane2.destroy();
- expect(container.root.children).toEqual([pane1, pane3]);
- });
- });
- describe("if the pane's parent has two children", () => {
- it('replaces the parent with its last remaining child', () => {
- const pane3 = pane2.splitDown();
- expect(container.root.children[0]).toBe(pane1);
- expect(container.root.children[1].children).toEqual([pane2, pane3]);
- pane3.destroy();
- expect(container.root.children).toEqual([pane1, pane2]);
- pane2.destroy();
- expect(container.root).toBe(pane1);
- });
- });
- });
- describe('pending state', () => {
- let editor1, pane, eventCount;
- beforeEach(async () => {
- editor1 = await atom.workspace.open('sample.txt', { pending: true });
- pane = atom.workspace.getActivePane();
- eventCount = 0;
- editor1.onDidTerminatePendingState(() => eventCount++);
- });
- it('does not open file in pending state by default', async () => {
- await atom.workspace.open('sample.js');
- expect(pane.getPendingItem()).toBeNull();
- });
- it("opens file in pending state if 'pending' option is true", () => {
- expect(pane.getPendingItem()).toEqual(editor1);
- });
- it('terminates pending state if ::terminatePendingState is invoked', () => {
- editor1.terminatePendingState();
- expect(pane.getPendingItem()).toBeNull();
- expect(eventCount).toBe(1);
- });
- it('terminates pending state when buffer is changed', () => {
- editor1.insertText("I'll be back!");
- advanceClock(editor1.getBuffer().stoppedChangingDelay);
- expect(pane.getPendingItem()).toBeNull();
- expect(eventCount).toBe(1);
- });
- it('only calls terminate handler once when text is modified twice', async () => {
- const originalText = editor1.getText();
- editor1.insertText('Some text');
- advanceClock(editor1.getBuffer().stoppedChangingDelay);
- await editor1.save();
- editor1.insertText('More text');
- advanceClock(editor1.getBuffer().stoppedChangingDelay);
- expect(pane.getPendingItem()).toBeNull();
- expect(eventCount).toBe(1);
- // Reset fixture back to original state
- editor1.setText(originalText);
- await editor1.save();
- });
- it('only calls clearPendingItem if there is a pending item to clear', () => {
- spyOn(pane, 'clearPendingItem').andCallThrough();
- editor1.terminatePendingState();
- editor1.terminatePendingState();
- expect(pane.getPendingItem()).toBeNull();
- expect(pane.clearPendingItem.callCount).toBe(1);
- });
- });
- describe('serialization', () => {
- let pane = null;
- beforeEach(() => {
- pane = new Pane(
- paneParams({
- items: [new Item('A', 'a'), new Item('B', 'b'), new Item('C', 'c')],
- flexScale: 2
- })
- );
- });
- it('can serialize and deserialize the pane and all its items', () => {
- const newPane = Pane.deserialize(pane.serialize(), atom);
- expect(newPane.getItems()).toEqual(pane.getItems());
- });
- it('restores the active item on deserialization', () => {
- pane.activateItemAtIndex(1);
- const newPane = Pane.deserialize(pane.serialize(), atom);
- expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1));
- });
- it("restores the active item when it doesn't implement getURI()", () => {
- pane.items[1].getURI = null;
- pane.activateItemAtIndex(1);
- const newPane = Pane.deserialize(pane.serialize(), atom);
- expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1));
- });
- it("restores the correct item when it doesn't implement getURI() and some items weren't deserialized", () => {
- const unserializable = {};
- pane.addItem(unserializable, { index: 0 });
- pane.items[2].getURI = null;
- pane.activateItemAtIndex(2);
- const newPane = Pane.deserialize(pane.serialize(), atom);
- expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1));
- });
- it('does not include items that cannot be deserialized', () => {
- spyOn(console, 'warn');
- const unserializable = {};
- pane.activateItem(unserializable);
- const newPane = Pane.deserialize(pane.serialize(), atom);
- expect(newPane.getActiveItem()).toEqual(pane.itemAtIndex(0));
- expect(newPane.getItems().length).toBe(pane.getItems().length - 1);
- });
- it("includes the pane's focus state in the serialized state", () => {
- pane.focus();
- const newPane = Pane.deserialize(pane.serialize(), atom);
- expect(newPane.focused).toBe(true);
- });
- it('can serialize and deserialize the order of the items in the itemStack', () => {
- const [item1, item2, item3] = pane.getItems();
- pane.itemStack = [item3, item1, item2];
- const newPane = Pane.deserialize(pane.serialize(), atom);
- expect(newPane.itemStack).toEqual(pane.itemStack);
- expect(newPane.itemStack[2]).toEqual(item2);
- });
- it('builds the itemStack if the itemStack is not serialized', () => {
- const newPane = Pane.deserialize(pane.serialize(), atom);
- expect(newPane.getItems()).toEqual(newPane.itemStack);
- });
- it('rebuilds the itemStack if items.length does not match itemStack.length', () => {
- const [, item2, item3] = pane.getItems();
- pane.itemStack = [item2, item3];
- const newPane = Pane.deserialize(pane.serialize(), atom);
- expect(newPane.getItems()).toEqual(newPane.itemStack);
- });
- it('does not serialize the reference to the items in the itemStack for pane items that will not be serialized', () => {
- const [item1, item2, item3] = pane.getItems();
- pane.itemStack = [item2, item1, item3];
- const unserializable = {};
- pane.activateItem(unserializable);
- const newPane = Pane.deserialize(pane.serialize(), atom);
- expect(newPane.itemStack).toEqual([item2, item1, item3]);
- });
- });
- });
|