pane-container-spec.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. const PaneContainer = require('../src/pane-container');
  2. describe('PaneContainer', () => {
  3. let confirm, params;
  4. beforeEach(() => {
  5. confirm = spyOn(atom.applicationDelegate, 'confirm').andCallFake(
  6. (options, callback) => callback(0)
  7. );
  8. params = {
  9. location: 'center',
  10. config: atom.config,
  11. deserializerManager: atom.deserializers,
  12. applicationDelegate: atom.applicationDelegate,
  13. viewRegistry: atom.views
  14. };
  15. });
  16. describe('serialization', () => {
  17. let containerA, pane1A, pane2A, pane3A;
  18. beforeEach(() => {
  19. // This is a dummy item to prevent panes from being empty on deserialization
  20. class Item {
  21. static deserialize() {
  22. return new this();
  23. }
  24. serialize() {
  25. return { deserializer: 'Item' };
  26. }
  27. }
  28. atom.deserializers.add(Item);
  29. containerA = new PaneContainer(params);
  30. pane1A = containerA.getActivePane();
  31. pane1A.addItem(new Item());
  32. pane2A = pane1A.splitRight({ items: [new Item()] });
  33. pane3A = pane2A.splitDown({ items: [new Item()] });
  34. pane3A.focus();
  35. });
  36. it('preserves the focused pane across serialization', () => {
  37. expect(pane3A.focused).toBe(true);
  38. const containerB = new PaneContainer(params);
  39. containerB.deserialize(containerA.serialize(), atom.deserializers);
  40. const pane3B = containerB.getPanes()[2];
  41. expect(pane3B.focused).toBe(true);
  42. });
  43. it('preserves the active pane across serialization, independent of focus', () => {
  44. pane3A.activate();
  45. expect(containerA.getActivePane()).toBe(pane3A);
  46. const containerB = new PaneContainer(params);
  47. containerB.deserialize(containerA.serialize(), atom.deserializers);
  48. const pane3B = containerB.getPanes()[2];
  49. expect(containerB.getActivePane()).toBe(pane3B);
  50. });
  51. it('makes the first pane active if no pane exists for the activePaneId', () => {
  52. pane3A.activate();
  53. const state = containerA.serialize();
  54. state.activePaneId = -22;
  55. const containerB = new PaneContainer(params);
  56. containerB.deserialize(state, atom.deserializers);
  57. expect(containerB.getActivePane()).toBe(containerB.getPanes()[0]);
  58. });
  59. describe('if there are empty panes after deserialization', () => {
  60. beforeEach(() => {
  61. pane3A.getItems()[0].serialize = () => ({ deserializer: 'Bogus' });
  62. });
  63. describe("if the 'core.destroyEmptyPanes' config option is false (the default)", () =>
  64. it('leaves the empty panes intact', () => {
  65. const state = containerA.serialize();
  66. const containerB = new PaneContainer(params);
  67. containerB.deserialize(state, atom.deserializers);
  68. const [leftPane, column] = containerB.getRoot().getChildren();
  69. const [topPane, bottomPane] = column.getChildren();
  70. expect(leftPane.getItems().length).toBe(1);
  71. expect(topPane.getItems().length).toBe(1);
  72. expect(bottomPane.getItems().length).toBe(0);
  73. }));
  74. describe("if the 'core.destroyEmptyPanes' config option is true", () =>
  75. it('removes empty panes on deserialization', () => {
  76. atom.config.set('core.destroyEmptyPanes', true);
  77. const state = containerA.serialize();
  78. const containerB = new PaneContainer(params);
  79. containerB.deserialize(state, atom.deserializers);
  80. const [leftPane, rightPane] = containerB.getRoot().getChildren();
  81. expect(leftPane.getItems().length).toBe(1);
  82. expect(rightPane.getItems().length).toBe(1);
  83. }));
  84. });
  85. });
  86. it('does not allow the root pane to be destroyed', () => {
  87. const container = new PaneContainer(params);
  88. container.getRoot().destroy();
  89. expect(container.getRoot()).toBeDefined();
  90. expect(container.getRoot().isDestroyed()).toBe(false);
  91. });
  92. describe('::getActivePane()', () => {
  93. let container, pane1, pane2;
  94. beforeEach(() => {
  95. container = new PaneContainer(params);
  96. pane1 = container.getRoot();
  97. });
  98. it('returns the first pane if no pane has been made active', () => {
  99. expect(container.getActivePane()).toBe(pane1);
  100. expect(pane1.isActive()).toBe(true);
  101. });
  102. it('returns the most pane on which ::activate() was most recently called', () => {
  103. pane2 = pane1.splitRight();
  104. pane2.activate();
  105. expect(container.getActivePane()).toBe(pane2);
  106. expect(pane1.isActive()).toBe(false);
  107. expect(pane2.isActive()).toBe(true);
  108. pane1.activate();
  109. expect(container.getActivePane()).toBe(pane1);
  110. expect(pane1.isActive()).toBe(true);
  111. expect(pane2.isActive()).toBe(false);
  112. });
  113. it('returns the next pane if the current active pane is destroyed', () => {
  114. pane2 = pane1.splitRight();
  115. pane2.activate();
  116. pane2.destroy();
  117. expect(container.getActivePane()).toBe(pane1);
  118. expect(pane1.isActive()).toBe(true);
  119. });
  120. });
  121. describe('::onDidChangeActivePane()', () => {
  122. let container, pane1, pane2, observed;
  123. beforeEach(() => {
  124. container = new PaneContainer(params);
  125. container.getRoot().addItems([{}, {}]);
  126. container.getRoot().splitRight({ items: [{}, {}] });
  127. [pane1, pane2] = container.getPanes();
  128. observed = [];
  129. container.onDidChangeActivePane(pane => observed.push(pane));
  130. });
  131. it('invokes observers when the active pane changes', () => {
  132. pane1.activate();
  133. pane2.activate();
  134. expect(observed).toEqual([pane1, pane2]);
  135. });
  136. });
  137. describe('::onDidChangeActivePaneItem()', () => {
  138. let container, pane1, pane2, observed;
  139. beforeEach(() => {
  140. container = new PaneContainer(params);
  141. container.getRoot().addItems([{}, {}]);
  142. container.getRoot().splitRight({ items: [{}, {}] });
  143. [pane1, pane2] = container.getPanes();
  144. observed = [];
  145. container.onDidChangeActivePaneItem(item => observed.push(item));
  146. });
  147. it('invokes observers when the active item of the active pane changes', () => {
  148. pane2.activateNextItem();
  149. pane2.activateNextItem();
  150. expect(observed).toEqual([pane2.itemAtIndex(1), pane2.itemAtIndex(0)]);
  151. });
  152. it('invokes observers when the active pane changes', () => {
  153. pane1.activate();
  154. pane2.activate();
  155. expect(observed).toEqual([pane1.itemAtIndex(0), pane2.itemAtIndex(0)]);
  156. });
  157. });
  158. describe('::onDidStopChangingActivePaneItem()', () => {
  159. let container, pane1, pane2, observed;
  160. beforeEach(() => {
  161. container = new PaneContainer(params);
  162. container.getRoot().addItems([{}, {}]);
  163. container.getRoot().splitRight({ items: [{}, {}] });
  164. [pane1, pane2] = container.getPanes();
  165. observed = [];
  166. container.onDidStopChangingActivePaneItem(item => observed.push(item));
  167. });
  168. it('invokes observers once when the active item of the active pane changes', () => {
  169. pane2.activateNextItem();
  170. pane2.activateNextItem();
  171. expect(observed).toEqual([]);
  172. advanceClock(100);
  173. expect(observed).toEqual([pane2.itemAtIndex(0)]);
  174. });
  175. it('invokes observers once when the active pane changes', () => {
  176. pane1.activate();
  177. pane2.activate();
  178. expect(observed).toEqual([]);
  179. advanceClock(100);
  180. expect(observed).toEqual([pane2.itemAtIndex(0)]);
  181. });
  182. });
  183. describe('::onDidActivatePane', () => {
  184. it('invokes observers when a pane is activated (even if it was already active)', () => {
  185. const container = new PaneContainer(params);
  186. container.getRoot().splitRight();
  187. const [pane1, pane2] = container.getPanes();
  188. const activatedPanes = [];
  189. container.onDidActivatePane(pane => activatedPanes.push(pane));
  190. pane1.activate();
  191. pane1.activate();
  192. pane2.activate();
  193. pane2.activate();
  194. expect(activatedPanes).toEqual([pane1, pane1, pane2, pane2]);
  195. });
  196. });
  197. describe('::observePanes()', () => {
  198. it('invokes observers with all current and future panes', () => {
  199. const container = new PaneContainer(params);
  200. container.getRoot().splitRight();
  201. const [pane1, pane2] = container.getPanes();
  202. const observed = [];
  203. container.observePanes(pane => observed.push(pane));
  204. const pane3 = pane2.splitDown();
  205. const pane4 = pane2.splitRight();
  206. expect(observed).toEqual([pane1, pane2, pane3, pane4]);
  207. });
  208. });
  209. describe('::observePaneItems()', () =>
  210. it('invokes observers with all current and future pane items', () => {
  211. const container = new PaneContainer(params);
  212. container.getRoot().addItems([{}, {}]);
  213. container.getRoot().splitRight({ items: [{}] });
  214. const pane2 = container.getPanes()[1];
  215. const observed = [];
  216. container.observePaneItems(pane => observed.push(pane));
  217. const pane3 = pane2.splitDown({ items: [{}] });
  218. pane3.addItems([{}, {}]);
  219. expect(observed).toEqual(container.getPaneItems());
  220. }));
  221. describe('::confirmClose()', () => {
  222. let container, pane1, pane2;
  223. beforeEach(() => {
  224. class TestItem {
  225. shouldPromptToSave() {
  226. return true;
  227. }
  228. getURI() {
  229. return 'test';
  230. }
  231. }
  232. container = new PaneContainer(params);
  233. container.getRoot().splitRight();
  234. [pane1, pane2] = container.getPanes();
  235. pane1.addItem(new TestItem());
  236. pane2.addItem(new TestItem());
  237. });
  238. it('returns true if the user saves all modified files when prompted', async () => {
  239. confirm.andCallFake((options, callback) => callback(0));
  240. const saved = await container.confirmClose();
  241. expect(confirm).toHaveBeenCalled();
  242. expect(saved).toBeTruthy();
  243. });
  244. it('returns false if the user cancels saving any modified file', async () => {
  245. confirm.andCallFake((options, callback) => callback(1));
  246. const saved = await container.confirmClose();
  247. expect(confirm).toHaveBeenCalled();
  248. expect(saved).toBeFalsy();
  249. });
  250. });
  251. describe('::onDidAddPane(callback)', () => {
  252. it('invokes the given callback when panes are added', () => {
  253. const container = new PaneContainer(params);
  254. const events = [];
  255. container.onDidAddPane(event => {
  256. expect(container.getPanes().includes(event.pane)).toBe(true);
  257. events.push(event);
  258. });
  259. const pane1 = container.getActivePane();
  260. const pane2 = pane1.splitRight();
  261. const pane3 = pane2.splitDown();
  262. expect(events).toEqual([{ pane: pane2 }, { pane: pane3 }]);
  263. });
  264. });
  265. describe('::onWillDestroyPane(callback)', () => {
  266. it('invokes the given callback before panes or their items are destroyed', () => {
  267. class TestItem {
  268. constructor() {
  269. this._isDestroyed = false;
  270. }
  271. destroy() {
  272. this._isDestroyed = true;
  273. }
  274. isDestroyed() {
  275. return this._isDestroyed;
  276. }
  277. }
  278. const container = new PaneContainer(params);
  279. const events = [];
  280. container.onWillDestroyPane(event => {
  281. const itemsDestroyed = event.pane
  282. .getItems()
  283. .map(item => item.isDestroyed());
  284. events.push([event, { itemsDestroyed }]);
  285. });
  286. const pane1 = container.getActivePane();
  287. const pane2 = pane1.splitRight();
  288. pane2.addItem(new TestItem());
  289. pane2.destroy();
  290. expect(events).toEqual([[{ pane: pane2 }, { itemsDestroyed: [false] }]]);
  291. });
  292. });
  293. describe('::onDidDestroyPane(callback)', () => {
  294. it('invokes the given callback when panes are destroyed', () => {
  295. const container = new PaneContainer(params);
  296. const events = [];
  297. container.onDidDestroyPane(event => {
  298. expect(container.getPanes().includes(event.pane)).toBe(false);
  299. events.push(event);
  300. });
  301. const pane1 = container.getActivePane();
  302. const pane2 = pane1.splitRight();
  303. const pane3 = pane2.splitDown();
  304. pane2.destroy();
  305. pane3.destroy();
  306. expect(events).toEqual([{ pane: pane2 }, { pane: pane3 }]);
  307. });
  308. it('invokes the given callback when the container is destroyed', () => {
  309. const container = new PaneContainer(params);
  310. const events = [];
  311. container.onDidDestroyPane(event => {
  312. expect(container.getPanes().includes(event.pane)).toBe(false);
  313. events.push(event);
  314. });
  315. const pane1 = container.getActivePane();
  316. const pane2 = pane1.splitRight();
  317. const pane3 = pane2.splitDown();
  318. container.destroy();
  319. expect(events).toEqual([
  320. { pane: pane1 },
  321. { pane: pane2 },
  322. { pane: pane3 }
  323. ]);
  324. });
  325. });
  326. describe('::onWillDestroyPaneItem() and ::onDidDestroyPaneItem()', () => {
  327. it('invokes the given callbacks when an item will be destroyed on any pane', async () => {
  328. const container = new PaneContainer(params);
  329. const pane1 = container.getRoot();
  330. const item1 = {};
  331. const item2 = {};
  332. const item3 = {};
  333. pane1.addItem(item1);
  334. const events = [];
  335. container.onWillDestroyPaneItem(event => events.push(['will', event]));
  336. container.onDidDestroyPaneItem(event => events.push(['did', event]));
  337. const pane2 = pane1.splitRight({ items: [item2, item3] });
  338. await pane1.destroyItem(item1);
  339. await pane2.destroyItem(item3);
  340. await pane2.destroyItem(item2);
  341. expect(events.length).toBe(6);
  342. expect(events[1]).toEqual([
  343. 'did',
  344. { item: item1, pane: pane1, index: 0 }
  345. ]);
  346. expect(events[3]).toEqual([
  347. 'did',
  348. { item: item3, pane: pane2, index: 1 }
  349. ]);
  350. expect(events[5]).toEqual([
  351. 'did',
  352. { item: item2, pane: pane2, index: 0 }
  353. ]);
  354. expect(events[0][0]).toEqual('will');
  355. expect(events[0][1].item).toEqual(item1);
  356. expect(events[0][1].pane).toEqual(pane1);
  357. expect(events[0][1].index).toEqual(0);
  358. expect(typeof events[0][1].prevent).toEqual('function');
  359. expect(events[2][0]).toEqual('will');
  360. expect(events[2][1].item).toEqual(item3);
  361. expect(events[2][1].pane).toEqual(pane2);
  362. expect(events[2][1].index).toEqual(1);
  363. expect(typeof events[2][1].prevent).toEqual('function');
  364. expect(events[4][0]).toEqual('will');
  365. expect(events[4][1].item).toEqual(item2);
  366. expect(events[4][1].pane).toEqual(pane2);
  367. expect(events[4][1].index).toEqual(0);
  368. expect(typeof events[4][1].prevent).toEqual('function');
  369. });
  370. });
  371. describe('::saveAll()', () =>
  372. it('saves all modified pane items', async () => {
  373. const container = new PaneContainer(params);
  374. const pane1 = container.getRoot();
  375. pane1.splitRight();
  376. const item1 = {
  377. saved: false,
  378. getURI() {
  379. return '';
  380. },
  381. isModified() {
  382. return true;
  383. },
  384. save() {
  385. this.saved = true;
  386. }
  387. };
  388. const item2 = {
  389. saved: false,
  390. getURI() {
  391. return '';
  392. },
  393. isModified() {
  394. return false;
  395. },
  396. save() {
  397. this.saved = true;
  398. }
  399. };
  400. const item3 = {
  401. saved: false,
  402. getURI() {
  403. return '';
  404. },
  405. isModified() {
  406. return true;
  407. },
  408. save() {
  409. this.saved = true;
  410. }
  411. };
  412. pane1.addItem(item1);
  413. pane1.addItem(item2);
  414. pane1.addItem(item3);
  415. container.saveAll();
  416. expect(item1.saved).toBe(true);
  417. expect(item2.saved).toBe(false);
  418. expect(item3.saved).toBe(true);
  419. }));
  420. describe('::moveActiveItemToPane(destPane) and ::copyActiveItemToPane(destPane)', () => {
  421. let container, pane1, pane2, item1;
  422. beforeEach(() => {
  423. class TestItem {
  424. constructor(id) {
  425. this.id = id;
  426. }
  427. copy() {
  428. return new TestItem(this.id);
  429. }
  430. }
  431. container = new PaneContainer(params);
  432. pane1 = container.getRoot();
  433. item1 = new TestItem('1');
  434. pane2 = pane1.splitRight({ items: [item1] });
  435. });
  436. describe('::::moveActiveItemToPane(destPane)', () =>
  437. it('moves active item to given pane and focuses it', () => {
  438. container.moveActiveItemToPane(pane1);
  439. expect(pane1.getActiveItem()).toBe(item1);
  440. }));
  441. describe('::::copyActiveItemToPane(destPane)', () =>
  442. it('copies active item to given pane and focuses it', () => {
  443. container.copyActiveItemToPane(pane1);
  444. expect(container.paneForItem(item1)).toBe(pane2);
  445. expect(pane1.getActiveItem().id).toBe(item1.id);
  446. }));
  447. });
  448. });