atom-environment-spec.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  1. const { conditionPromise } = require('./async-spec-helpers');
  2. const fs = require('fs');
  3. const path = require('path');
  4. const temp = require('temp').track();
  5. const AtomEnvironment = require('../src/atom-environment');
  6. describe('AtomEnvironment', () => {
  7. afterEach(() => {
  8. try {
  9. temp.cleanupSync();
  10. } catch (error) {}
  11. });
  12. describe('window sizing methods', () => {
  13. describe('::getPosition and ::setPosition', () => {
  14. let originalPosition = null;
  15. beforeEach(() => (originalPosition = atom.getPosition()));
  16. afterEach(() => atom.setPosition(originalPosition.x, originalPosition.y));
  17. it('sets the position of the window, and can retrieve the position just set', () => {
  18. atom.setPosition(22, 45);
  19. expect(atom.getPosition()).toEqual({ x: 22, y: 45 });
  20. });
  21. });
  22. describe('::getSize and ::setSize', () => {
  23. let originalSize = null;
  24. beforeEach(() => (originalSize = atom.getSize()));
  25. afterEach(() => atom.setSize(originalSize.width, originalSize.height));
  26. it('sets the size of the window, and can retrieve the size just set', async () => {
  27. const newWidth = originalSize.width - 12;
  28. const newHeight = originalSize.height - 23;
  29. await atom.setSize(newWidth, newHeight);
  30. expect(atom.getSize()).toEqual({ width: newWidth, height: newHeight });
  31. });
  32. });
  33. });
  34. describe('.isReleasedVersion()', () => {
  35. it('returns false if the version is a SHA and true otherwise', () => {
  36. let version = '0.1.0';
  37. spyOn(atom, 'getVersion').andCallFake(() => version);
  38. expect(atom.isReleasedVersion()).toBe(true);
  39. version = '36b5518';
  40. expect(atom.isReleasedVersion()).toBe(false);
  41. });
  42. });
  43. describe('.versionSatisfies()', () => {
  44. it('returns appropriately for provided range', () => {
  45. let testPulsarVersion = '0.1.0';
  46. spyOn(atom, 'getVersion').andCallFake(() => testPulsarVersion);
  47. expect(atom.versionSatisfies('>0.2.0')).toBe(false);
  48. expect(atom.versionSatisfies('>=0.x.x <=2.x.x')).toBe(true);
  49. expect(atom.versionSatisfies('^0.1.x')).toBe(true);
  50. });
  51. });
  52. describe('loading default config', () => {
  53. it('loads the default core config schema', () => {
  54. expect(atom.config.get('core.excludeVcsIgnoredPaths')).toBe(true);
  55. expect(atom.config.get('core.followSymlinks')).toBe(true);
  56. expect(atom.config.get('editor.showInvisibles')).toBe(false);
  57. });
  58. });
  59. describe('window onerror handler', () => {
  60. let devToolsPromise = null;
  61. beforeEach(() => {
  62. devToolsPromise = Promise.resolve();
  63. spyOn(atom, 'openDevTools').andReturn(devToolsPromise);
  64. spyOn(atom, 'executeJavaScriptInDevTools');
  65. });
  66. it('will open the dev tools when an error is triggered', async () => {
  67. try {
  68. a + 1; // eslint-disable-line no-undef, no-unused-expressions
  69. } catch (e) {
  70. window.onerror(e.toString(), 'abc', 2, 3, e);
  71. }
  72. await devToolsPromise;
  73. expect(atom.openDevTools).toHaveBeenCalled();
  74. expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled();
  75. });
  76. describe('::onWillThrowError', () => {
  77. let willThrowSpy = null;
  78. beforeEach(() => {
  79. willThrowSpy = jasmine.createSpy();
  80. });
  81. it('is called when there is an error', () => {
  82. let error = null;
  83. atom.onWillThrowError(willThrowSpy);
  84. try {
  85. a + 1; // eslint-disable-line no-undef, no-unused-expressions
  86. } catch (e) {
  87. error = e;
  88. window.onerror(e.toString(), 'abc', 2, 3, e);
  89. }
  90. delete willThrowSpy.mostRecentCall.args[0].preventDefault;
  91. expect(willThrowSpy).toHaveBeenCalledWith({
  92. message: error.toString(),
  93. url: 'abc',
  94. line: 2,
  95. column: 3,
  96. originalError: error
  97. });
  98. });
  99. it('will not show the devtools when preventDefault() is called', () => {
  100. willThrowSpy.andCallFake(errorObject => errorObject.preventDefault());
  101. atom.onWillThrowError(willThrowSpy);
  102. try {
  103. a + 1; // eslint-disable-line no-undef, no-unused-expressions
  104. } catch (e) {
  105. window.onerror(e.toString(), 'abc', 2, 3, e);
  106. }
  107. expect(willThrowSpy).toHaveBeenCalled();
  108. expect(atom.openDevTools).not.toHaveBeenCalled();
  109. expect(atom.executeJavaScriptInDevTools).not.toHaveBeenCalled();
  110. });
  111. });
  112. describe('::onDidThrowError', () => {
  113. let didThrowSpy = null;
  114. beforeEach(() => (didThrowSpy = jasmine.createSpy()));
  115. it('is called when there is an error', () => {
  116. let error = null;
  117. atom.onDidThrowError(didThrowSpy);
  118. try {
  119. a + 1; // eslint-disable-line no-undef, no-unused-expressions
  120. } catch (e) {
  121. error = e;
  122. window.onerror(e.toString(), 'abc', 2, 3, e);
  123. }
  124. expect(didThrowSpy).toHaveBeenCalledWith({
  125. message: error.toString(),
  126. url: 'abc',
  127. line: 2,
  128. column: 3,
  129. originalError: error
  130. });
  131. });
  132. });
  133. });
  134. describe('.assert(condition, message, callback)', () => {
  135. let errors = null;
  136. beforeEach(() => {
  137. errors = [];
  138. spyOn(atom, 'isReleasedVersion').andReturn(true);
  139. atom.onDidFailAssertion(error => errors.push(error));
  140. });
  141. describe('if the condition is false', () => {
  142. it('notifies onDidFailAssertion handlers with an error object based on the call site of the assertion', () => {
  143. const result = atom.assert(false, 'a == b');
  144. expect(result).toBe(false);
  145. expect(errors.length).toBe(1);
  146. expect(errors[0].message).toBe('Assertion failed: a == b');
  147. expect(errors[0].stack).toContain('atom-environment-spec');
  148. });
  149. describe('if passed a callback function', () => {
  150. it("calls the callback with the assertion failure's error object", () => {
  151. let error = null;
  152. atom.assert(false, 'a == b', e => (error = e));
  153. expect(error).toBe(errors[0]);
  154. });
  155. });
  156. describe('if passed metadata', () => {
  157. it("assigns the metadata on the assertion failure's error object", () => {
  158. atom.assert(false, 'a == b', { foo: 'bar' });
  159. expect(errors[0].metadata).toEqual({ foo: 'bar' });
  160. });
  161. });
  162. describe('when Atom has been built from source', () => {
  163. it('throws an error', () => {
  164. atom.isReleasedVersion.andReturn(false);
  165. expect(() => atom.assert(false, 'testing')).toThrow(
  166. 'Assertion failed: testing'
  167. );
  168. });
  169. });
  170. });
  171. describe('if the condition is true', () => {
  172. it('does nothing', () => {
  173. const result = atom.assert(true, 'a == b');
  174. expect(result).toBe(true);
  175. expect(errors).toEqual([]);
  176. });
  177. });
  178. });
  179. describe('saving and loading', () => {
  180. beforeEach(() => (atom.enablePersistence = true));
  181. afterEach(() => (atom.enablePersistence = false));
  182. it('selects the state based on the current project paths', async () => {
  183. jasmine.useRealClock();
  184. const [dir1, dir2] = [temp.mkdirSync('dir1-'), temp.mkdirSync('dir2-')];
  185. const loadSettings = Object.assign(atom.getLoadSettings(), {
  186. initialProjectRoots: [dir1],
  187. windowState: null
  188. });
  189. spyOn(atom, 'getLoadSettings').andCallFake(() => loadSettings);
  190. spyOn(atom, 'serialize').andReturn({ stuff: 'cool' });
  191. atom.project.setPaths([dir1, dir2]);
  192. // State persistence will fail if other Atom instances are running
  193. expect(await atom.stateStore.connect()).toBe(true);
  194. await atom.saveState();
  195. expect(await atom.loadState()).toBeFalsy();
  196. loadSettings.initialProjectRoots = [dir2, dir1];
  197. expect(await atom.loadState()).toEqual({ stuff: 'cool' });
  198. });
  199. it('saves state when the CPU is idle after a keydown or mousedown event', () => {
  200. const atomEnv = new AtomEnvironment({
  201. applicationDelegate: global.atom.applicationDelegate
  202. });
  203. const idleCallbacks = [];
  204. atomEnv.initialize({
  205. window: {
  206. requestIdleCallback(callback) {
  207. idleCallbacks.push(callback);
  208. },
  209. addEventListener() {},
  210. removeEventListener() {}
  211. },
  212. document: document.implementation.createHTMLDocument()
  213. });
  214. spyOn(atomEnv, 'saveState');
  215. const keydown = new KeyboardEvent('keydown');
  216. atomEnv.document.dispatchEvent(keydown);
  217. advanceClock(atomEnv.saveStateDebounceInterval);
  218. idleCallbacks.shift()();
  219. expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: false });
  220. expect(atomEnv.saveState).not.toHaveBeenCalledWith({ isUnloading: true });
  221. atomEnv.saveState.reset();
  222. const mousedown = new MouseEvent('mousedown');
  223. atomEnv.document.dispatchEvent(mousedown);
  224. advanceClock(atomEnv.saveStateDebounceInterval);
  225. idleCallbacks.shift()();
  226. expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: false });
  227. expect(atomEnv.saveState).not.toHaveBeenCalledWith({ isUnloading: true });
  228. atomEnv.destroy();
  229. });
  230. it('ignores mousedown/keydown events happening after calling prepareToUnloadEditorWindow', async () => {
  231. const atomEnv = new AtomEnvironment({
  232. applicationDelegate: global.atom.applicationDelegate
  233. });
  234. const idleCallbacks = [];
  235. atomEnv.initialize({
  236. window: {
  237. requestIdleCallback(callback) {
  238. idleCallbacks.push(callback);
  239. },
  240. addEventListener() {},
  241. removeEventListener() {}
  242. },
  243. document: document.implementation.createHTMLDocument()
  244. });
  245. spyOn(atomEnv, 'saveState');
  246. let mousedown = new MouseEvent('mousedown');
  247. atomEnv.document.dispatchEvent(mousedown);
  248. expect(atomEnv.saveState).not.toHaveBeenCalled();
  249. await atomEnv.prepareToUnloadEditorWindow();
  250. expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: true });
  251. advanceClock(atomEnv.saveStateDebounceInterval);
  252. idleCallbacks.shift()();
  253. expect(atomEnv.saveState.calls.length).toBe(1);
  254. mousedown = new MouseEvent('mousedown');
  255. atomEnv.document.dispatchEvent(mousedown);
  256. advanceClock(atomEnv.saveStateDebounceInterval);
  257. idleCallbacks.shift()();
  258. expect(atomEnv.saveState.calls.length).toBe(1);
  259. atomEnv.destroy();
  260. });
  261. it('serializes the project state with all the options supplied in saveState', async () => {
  262. spyOn(atom.project, 'serialize').andReturn({ foo: 42 });
  263. await atom.saveState({ anyOption: 'any option' });
  264. expect(atom.project.serialize.calls.length).toBe(1);
  265. expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({
  266. anyOption: 'any option'
  267. });
  268. });
  269. it('serializes the text editor registry', async () => {
  270. await atom.packages.activatePackage('language-text');
  271. const editor = await atom.workspace.open('sample.js');
  272. expect(atom.grammars.assignLanguageMode(editor, 'text.plain')).toBe(true);
  273. const atom2 = new AtomEnvironment({
  274. applicationDelegate: atom.applicationDelegate,
  275. window: document.createElement('div'),
  276. document: Object.assign(document.createElement('div'), {
  277. body: document.createElement('div'),
  278. head: document.createElement('div')
  279. })
  280. });
  281. atom2.initialize({ document, window });
  282. await atom2.deserialize(atom.serialize());
  283. await atom2.packages.activatePackage('language-text');
  284. const editor2 = atom2.workspace.getActiveTextEditor();
  285. expect(
  286. editor2
  287. .getBuffer()
  288. .getLanguageMode()
  289. .getLanguageId()
  290. ).toBe('text.plain');
  291. atom2.destroy();
  292. });
  293. describe('deserialization failures', () => {
  294. it('propagates unrecognized project state restoration failures', async () => {
  295. let err;
  296. spyOn(atom.project, 'deserialize').andCallFake(() => {
  297. err = new Error('deserialization failure');
  298. return Promise.reject(err);
  299. });
  300. spyOn(atom.notifications, 'addError');
  301. await atom.deserialize({ project: 'should work' });
  302. expect(atom.notifications.addError).toHaveBeenCalledWith(
  303. 'Unable to deserialize project',
  304. {
  305. description: 'deserialization failure',
  306. stack: err.stack
  307. }
  308. );
  309. });
  310. it('disregards missing project folder errors', async () => {
  311. spyOn(atom.project, 'deserialize').andCallFake(() => {
  312. const err = new Error('deserialization failure');
  313. err.missingProjectPaths = ['nah'];
  314. return Promise.reject(err);
  315. });
  316. spyOn(atom.notifications, 'addError');
  317. await atom.deserialize({ project: 'should work' });
  318. expect(atom.notifications.addError).not.toHaveBeenCalled();
  319. });
  320. });
  321. });
  322. describe('openInitialEmptyEditorIfNecessary', () => {
  323. describe('when there are no paths set', () => {
  324. beforeEach(() =>
  325. spyOn(atom, 'getLoadSettings').andReturn({ hasOpenFiles: false })
  326. );
  327. it('opens an empty buffer', () => {
  328. spyOn(atom.workspace, 'open');
  329. atom.openInitialEmptyEditorIfNecessary();
  330. expect(atom.workspace.open).toHaveBeenCalledWith(null, {
  331. pending: true
  332. });
  333. });
  334. it('does not open an empty buffer when a buffer is already open', async () => {
  335. await atom.workspace.open();
  336. spyOn(atom.workspace, 'open');
  337. atom.openInitialEmptyEditorIfNecessary();
  338. expect(atom.workspace.open).not.toHaveBeenCalled();
  339. });
  340. it('does not open an empty buffer when core.openEmptyEditorOnStart is false', async () => {
  341. atom.config.set('core.openEmptyEditorOnStart', false);
  342. spyOn(atom.workspace, 'open');
  343. atom.openInitialEmptyEditorIfNecessary();
  344. expect(atom.workspace.open).not.toHaveBeenCalled();
  345. });
  346. });
  347. describe('when the project has a path', () => {
  348. beforeEach(() => {
  349. spyOn(atom, 'getLoadSettings').andReturn({ hasOpenFiles: true });
  350. spyOn(atom.workspace, 'open');
  351. });
  352. it('does not open an empty buffer', () => {
  353. atom.openInitialEmptyEditorIfNecessary();
  354. expect(atom.workspace.open).not.toHaveBeenCalled();
  355. });
  356. });
  357. });
  358. describe('adding a project folder', () => {
  359. it('does nothing if the user dismisses the file picker', () => {
  360. const projectRoots = atom.project.getPaths();
  361. spyOn(atom, 'pickFolder').andCallFake(callback => callback(null));
  362. atom.addProjectFolder();
  363. expect(atom.project.getPaths()).toEqual(projectRoots);
  364. });
  365. describe('when there is no saved state for the added folders', () => {
  366. beforeEach(() => {
  367. spyOn(atom, 'loadState').andReturn(Promise.resolve(null));
  368. spyOn(atom, 'attemptRestoreProjectStateForPaths');
  369. });
  370. it('adds the selected folder to the project', async () => {
  371. atom.project.setPaths([]);
  372. const tempDirectory = temp.mkdirSync('a-new-directory');
  373. spyOn(atom, 'pickFolder').andCallFake(callback =>
  374. callback([tempDirectory])
  375. );
  376. await atom.addProjectFolder();
  377. expect(atom.project.getPaths()).toEqual([tempDirectory]);
  378. expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled();
  379. });
  380. });
  381. describe('when there is saved state for the relevant directories', () => {
  382. const state = Symbol('savedState');
  383. beforeEach(() => {
  384. spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':'));
  385. spyOn(atom, 'loadState').andCallFake(async key =>
  386. key === __dirname ? state : null
  387. );
  388. spyOn(atom, 'attemptRestoreProjectStateForPaths');
  389. spyOn(atom, 'pickFolder').andCallFake(callback =>
  390. callback([__dirname])
  391. );
  392. atom.project.setPaths([]);
  393. });
  394. describe('when there are no project folders', () => {
  395. it('attempts to restore the project state', async () => {
  396. await atom.addProjectFolder();
  397. expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(
  398. state,
  399. [__dirname]
  400. );
  401. expect(atom.project.getPaths()).toEqual([]);
  402. });
  403. });
  404. describe('when there are already project folders', () => {
  405. const openedPath = path.join(__dirname, 'fixtures');
  406. beforeEach(() => atom.project.setPaths([openedPath]));
  407. it('does not attempt to restore the project state, instead adding the project paths', async () => {
  408. await atom.addProjectFolder();
  409. expect(
  410. atom.attemptRestoreProjectStateForPaths
  411. ).not.toHaveBeenCalled();
  412. expect(atom.project.getPaths()).toEqual([openedPath, __dirname]);
  413. });
  414. });
  415. });
  416. });
  417. describe('attemptRestoreProjectStateForPaths(state, projectPaths, filesToOpen)', () => {
  418. describe('when the window is clean (empty or has only unnamed, unmodified buffers)', () => {
  419. beforeEach(async () => {
  420. // Unnamed, unmodified buffer doesn't count toward "clean"-ness
  421. await atom.workspace.open();
  422. });
  423. it('automatically restores the saved state into the current environment', async () => {
  424. const projectPath = temp.mkdirSync();
  425. const filePath1 = path.join(projectPath, 'file-1');
  426. const filePath2 = path.join(projectPath, 'file-2');
  427. const filePath3 = path.join(projectPath, 'file-3');
  428. fs.writeFileSync(filePath1, 'abc');
  429. fs.writeFileSync(filePath2, 'def');
  430. fs.writeFileSync(filePath3, 'ghi');
  431. const env1 = new AtomEnvironment({
  432. applicationDelegate: atom.applicationDelegate
  433. });
  434. env1.project.setPaths([projectPath]);
  435. await env1.workspace.open(filePath1);
  436. await env1.workspace.open(filePath2);
  437. await env1.workspace.open(filePath3);
  438. const env1State = env1.serialize();
  439. env1.destroy();
  440. const env2 = new AtomEnvironment({
  441. applicationDelegate: atom.applicationDelegate
  442. });
  443. await env2.attemptRestoreProjectStateForPaths(
  444. env1State,
  445. [projectPath],
  446. [filePath2]
  447. );
  448. const restoredURIs = env2.workspace.getPaneItems().map(p => p.getURI());
  449. expect(restoredURIs).toEqual([filePath1, filePath2, filePath3]);
  450. env2.destroy();
  451. });
  452. describe('when a dock has a non-text editor', () => {
  453. it("doesn't prompt the user to restore state", () => {
  454. const dock = atom.workspace.getLeftDock();
  455. dock.getActivePane().addItem({
  456. getTitle() {
  457. return 'title';
  458. },
  459. element: document.createElement('div')
  460. });
  461. const state = {};
  462. spyOn(atom, 'confirm');
  463. atom.attemptRestoreProjectStateForPaths(
  464. state,
  465. [__dirname],
  466. [__filename]
  467. );
  468. expect(atom.confirm).not.toHaveBeenCalled();
  469. });
  470. });
  471. });
  472. describe('when the window is dirty', () => {
  473. let editor;
  474. beforeEach(async () => {
  475. editor = await atom.workspace.open();
  476. editor.setText('new editor');
  477. });
  478. describe('when a dock has a modified editor', () => {
  479. it('prompts the user to restore the state', () => {
  480. const dock = atom.workspace.getLeftDock();
  481. dock.getActivePane().addItem(editor);
  482. spyOn(atom, 'confirm').andReturn(1);
  483. spyOn(atom.project, 'addPath');
  484. spyOn(atom.workspace, 'open');
  485. const state = Symbol('state');
  486. atom.attemptRestoreProjectStateForPaths(
  487. state,
  488. [__dirname],
  489. [__filename]
  490. );
  491. expect(atom.confirm).toHaveBeenCalled();
  492. });
  493. });
  494. it('prompts the user to restore the state in a new window, discarding it and adding folder to current window', async () => {
  495. jasmine.useRealClock();
  496. spyOn(atom, 'confirm').andCallFake((options, callback) => callback(1));
  497. spyOn(atom.project, 'addPath');
  498. spyOn(atom.workspace, 'open');
  499. const state = Symbol('state');
  500. atom.attemptRestoreProjectStateForPaths(
  501. state,
  502. [__dirname],
  503. [__filename]
  504. );
  505. expect(atom.confirm).toHaveBeenCalled();
  506. await conditionPromise(() => atom.project.addPath.callCount === 1);
  507. expect(atom.project.addPath).toHaveBeenCalledWith(__dirname);
  508. expect(atom.workspace.open.callCount).toBe(1);
  509. expect(atom.workspace.open).toHaveBeenCalledWith(__filename);
  510. });
  511. it('prompts the user to restore the state in a new window, opening a new window', async () => {
  512. jasmine.useRealClock();
  513. spyOn(atom, 'confirm').andCallFake((options, callback) => callback(0));
  514. spyOn(atom, 'open');
  515. const state = Symbol('state');
  516. atom.attemptRestoreProjectStateForPaths(
  517. state,
  518. [__dirname],
  519. [__filename]
  520. );
  521. expect(atom.confirm).toHaveBeenCalled();
  522. await conditionPromise(() => atom.open.callCount === 1);
  523. expect(atom.open).toHaveBeenCalledWith({
  524. pathsToOpen: [__dirname, __filename],
  525. newWindow: true,
  526. devMode: atom.inDevMode(),
  527. safeMode: atom.inSafeMode()
  528. });
  529. });
  530. });
  531. });
  532. describe('::unloadEditorWindow()', () => {
  533. it('saves the BlobStore so it can be loaded after reload', () => {
  534. const configDirPath = temp.mkdirSync('atom-spec-environment');
  535. const fakeBlobStore = jasmine.createSpyObj('blob store', ['save']);
  536. const atomEnvironment = new AtomEnvironment({
  537. applicationDelegate: atom.applicationDelegate,
  538. enablePersistence: true
  539. });
  540. atomEnvironment.initialize({
  541. configDirPath,
  542. blobStore: fakeBlobStore,
  543. window,
  544. document
  545. });
  546. atomEnvironment.unloadEditorWindow();
  547. expect(fakeBlobStore.save).toHaveBeenCalled();
  548. atomEnvironment.destroy();
  549. });
  550. });
  551. describe('::destroy()', () => {
  552. it('does not throw exceptions when unsubscribing from ipc events (regression)', async () => {
  553. const fakeDocument = {
  554. addEventListener() {},
  555. removeEventListener() {},
  556. head: document.createElement('head'),
  557. body: document.createElement('body')
  558. };
  559. const atomEnvironment = new AtomEnvironment({
  560. applicationDelegate: atom.applicationDelegate
  561. });
  562. atomEnvironment.initialize({ window, document: fakeDocument });
  563. spyOn(atomEnvironment.packages, 'loadPackages').andReturn(
  564. Promise.resolve()
  565. );
  566. spyOn(atomEnvironment.packages, 'activate').andReturn(Promise.resolve());
  567. spyOn(atomEnvironment, 'displayWindow').andReturn(Promise.resolve());
  568. await atomEnvironment.startEditorWindow();
  569. atomEnvironment.unloadEditorWindow();
  570. atomEnvironment.destroy();
  571. });
  572. });
  573. describe('::whenShellEnvironmentLoaded()', () => {
  574. let atomEnvironment, envLoaded, spy;
  575. beforeEach(() => {
  576. let resolvePromise = null;
  577. const promise = new Promise(resolve => {
  578. resolvePromise = resolve;
  579. });
  580. envLoaded = () => {
  581. resolvePromise();
  582. return promise;
  583. };
  584. atomEnvironment = new AtomEnvironment({
  585. applicationDelegate: atom.applicationDelegate,
  586. updateProcessEnv() {
  587. return promise;
  588. }
  589. });
  590. atomEnvironment.initialize({ window, document });
  591. spy = jasmine.createSpy();
  592. });
  593. afterEach(() => atomEnvironment.destroy());
  594. it('is triggered once the shell environment is loaded', async () => {
  595. atomEnvironment.whenShellEnvironmentLoaded(spy);
  596. atomEnvironment.updateProcessEnvAndTriggerHooks();
  597. await envLoaded();
  598. expect(spy).toHaveBeenCalled();
  599. });
  600. it('triggers the callback immediately if the shell environment is already loaded', async () => {
  601. atomEnvironment.updateProcessEnvAndTriggerHooks();
  602. await envLoaded();
  603. atomEnvironment.whenShellEnvironmentLoaded(spy);
  604. expect(spy).toHaveBeenCalled();
  605. });
  606. });
  607. describe('::openLocations(locations)', () => {
  608. beforeEach(() => {
  609. atom.project.setPaths([]);
  610. });
  611. describe('when there is no saved state', () => {
  612. beforeEach(() => {
  613. spyOn(atom, 'loadState').andReturn(Promise.resolve(null));
  614. });
  615. describe('when the opened path exists', () => {
  616. it('opens a file', async () => {
  617. const pathToOpen = __filename;
  618. await atom.openLocations([
  619. { pathToOpen, exists: true, isFile: true }
  620. ]);
  621. expect(atom.project.getPaths()).toEqual([]);
  622. });
  623. it('opens a directory as a project folder', async () => {
  624. const pathToOpen = __dirname;
  625. await atom.openLocations([
  626. { pathToOpen, exists: true, isDirectory: true }
  627. ]);
  628. expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual(
  629. []
  630. );
  631. expect(atom.project.getPaths()).toEqual([pathToOpen]);
  632. });
  633. });
  634. describe('when the opened path does not exist', () => {
  635. it('opens it as a new file', async () => {
  636. const pathToOpen = path.join(
  637. __dirname,
  638. 'this-path-does-not-exist.txt'
  639. );
  640. await atom.openLocations([{ pathToOpen, exists: false }]);
  641. expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual(
  642. [pathToOpen]
  643. );
  644. expect(atom.project.getPaths()).toEqual([]);
  645. });
  646. it('may be required to be an existing directory', async () => {
  647. spyOn(atom.notifications, 'addWarning');
  648. const nonExistent = path.join(__dirname, 'no');
  649. const existingFile = __filename;
  650. const existingDir = path.join(__dirname, 'fixtures');
  651. await atom.openLocations([
  652. { pathToOpen: nonExistent, isDirectory: true },
  653. { pathToOpen: existingFile, isDirectory: true },
  654. { pathToOpen: existingDir, isDirectory: true }
  655. ]);
  656. expect(atom.workspace.getTextEditors()).toEqual([]);
  657. expect(atom.project.getPaths()).toEqual([existingDir]);
  658. expect(atom.notifications.addWarning).toHaveBeenCalledWith(
  659. 'Unable to open project folders',
  660. {
  661. description: `The directories \`${nonExistent}\` and \`${existingFile}\` do not exist.`
  662. }
  663. );
  664. });
  665. });
  666. describe('when the opened path is handled by a registered directory provider', () => {
  667. let serviceDisposable;
  668. beforeEach(() => {
  669. serviceDisposable = atom.packages.serviceHub.provide(
  670. 'atom.directory-provider',
  671. '0.1.0',
  672. {
  673. directoryForURISync(uri) {
  674. if (uri.startsWith('remote://')) {
  675. return {
  676. getPath() {
  677. return uri;
  678. }
  679. };
  680. } else {
  681. return null;
  682. }
  683. }
  684. }
  685. );
  686. waitsFor(() => atom.project.directoryProviders.length > 0);
  687. });
  688. afterEach(() => {
  689. serviceDisposable.dispose();
  690. });
  691. it("adds it to the project's paths as is", async () => {
  692. const pathToOpen = 'remote://server:7644/some/dir/path';
  693. spyOn(atom.project, 'addPath');
  694. await atom.openLocations([{ pathToOpen }]);
  695. expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen);
  696. });
  697. });
  698. });
  699. describe('when there is saved state for the relevant directories', () => {
  700. const state = Symbol('savedState');
  701. beforeEach(() => {
  702. spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':'));
  703. spyOn(atom, 'loadState').andCallFake(function(key) {
  704. if (key === __dirname) {
  705. return Promise.resolve(state);
  706. } else {
  707. return Promise.resolve(null);
  708. }
  709. });
  710. spyOn(atom, 'attemptRestoreProjectStateForPaths');
  711. });
  712. describe('when there are no project folders', () => {
  713. it('attempts to restore the project state', async () => {
  714. const pathToOpen = __dirname;
  715. await atom.openLocations([{ pathToOpen, isDirectory: true }]);
  716. expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(
  717. state,
  718. [pathToOpen],
  719. []
  720. );
  721. expect(atom.project.getPaths()).toEqual([]);
  722. });
  723. it('includes missing mandatory project folders in computation of initial state key', async () => {
  724. const existingDir = path.join(__dirname, 'fixtures');
  725. const missingDir = path.join(__dirname, 'no');
  726. atom.loadState.andCallFake(function(key) {
  727. if (key === `${existingDir}:${missingDir}`) {
  728. return Promise.resolve(state);
  729. } else {
  730. return Promise.resolve(null);
  731. }
  732. });
  733. await atom.openLocations([
  734. { pathToOpen: existingDir },
  735. { pathToOpen: missingDir, isDirectory: true }
  736. ]);
  737. expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(
  738. state,
  739. [existingDir],
  740. []
  741. );
  742. expect(atom.project.getPaths(), [existingDir]);
  743. });
  744. it('opens the specified files', async () => {
  745. await atom.openLocations([
  746. { pathToOpen: __dirname, isDirectory: true },
  747. { pathToOpen: __filename }
  748. ]);
  749. expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(
  750. state,
  751. [__dirname],
  752. [__filename]
  753. );
  754. expect(atom.project.getPaths()).toEqual([]);
  755. });
  756. });
  757. describe('when there are already project folders', () => {
  758. beforeEach(() => atom.project.setPaths([__dirname]));
  759. it('does not attempt to restore the project state, instead adding the project paths', async () => {
  760. const pathToOpen = path.join(__dirname, 'fixtures');
  761. await atom.openLocations([
  762. { pathToOpen, exists: true, isDirectory: true }
  763. ]);
  764. expect(
  765. atom.attemptRestoreProjectStateForPaths
  766. ).not.toHaveBeenCalled();
  767. expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]);
  768. });
  769. it('opens the specified files', async () => {
  770. const pathToOpen = path.join(__dirname, 'fixtures');
  771. const fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt');
  772. await atom.openLocations([
  773. { pathToOpen, exists: true, isDirectory: true },
  774. { pathToOpen: fileToOpen, exists: true, isFile: true }
  775. ]);
  776. expect(
  777. atom.attemptRestoreProjectStateForPaths
  778. ).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen]);
  779. expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]);
  780. });
  781. });
  782. });
  783. });
  784. describe('::getReleaseChannel()', () => {
  785. let version;
  786. beforeEach(() => {
  787. spyOn(atom, 'getVersion').andCallFake(() => version);
  788. });
  789. it('returns the correct channel based on the version number', () => {
  790. version = '1.5.6';
  791. expect(atom.getReleaseChannel()).toBe('stable');
  792. version = '1.5.0-beta10';
  793. expect(atom.getReleaseChannel()).toBe('beta');
  794. version = '1.7.0-dev-5340c91';
  795. expect(atom.getReleaseChannel()).toBe('dev');
  796. });
  797. });
  798. });