settings-view-spec.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. const path = require('path');
  2. const main = require('../lib/main');
  3. const PackageManager = require('../lib/package-manager');
  4. const SettingsView = require('../lib/settings-view');
  5. const SnippetsProvider =
  6. {getSnippets() { return {}; }};
  7. describe("SettingsView", function() {
  8. let settingsView = null;
  9. const packageManager = new PackageManager();
  10. beforeEach(function() {
  11. settingsView = main.createSettingsView({packageManager, snippetsProvider: SnippetsProvider});
  12. spyOn(settingsView, "initializePanels").andCallThrough();
  13. window.advanceClock(10000);
  14. waitsFor(() => settingsView.initializePanels.callCount > 0);
  15. });
  16. describe("serialization", function() {
  17. it("remembers which panel was visible", function() {
  18. settingsView.showPanel('Themes');
  19. const newSettingsView = main.createSettingsView(settingsView.serialize());
  20. settingsView.destroy();
  21. jasmine.attachToDOM(newSettingsView.element);
  22. newSettingsView.initializePanels();
  23. expect(newSettingsView.activePanel).toEqual({name: 'Themes', options: {}});
  24. });
  25. it("shows the previously active panel if it is added after deserialization", function() {
  26. settingsView.addCorePanel('Panel 1', 'panel-1', function() {
  27. const div = document.createElement('div');
  28. div.id = 'panel-1';
  29. return {
  30. element: div,
  31. show() { return div.style.display = ''; },
  32. focus() { return div.focus(); },
  33. destroy() { return div.remove(); }
  34. };
  35. });
  36. settingsView.showPanel('Panel 1');
  37. const newSettingsView = main.createSettingsView(settingsView.serialize());
  38. newSettingsView.addPanel('Panel 1', function() {
  39. const div = document.createElement('div');
  40. div.id = 'panel-1';
  41. return {
  42. element: div,
  43. show() { return div.style.display = ''; },
  44. focus() { return div.focus(); },
  45. destroy() { return div.remove(); }
  46. };
  47. });
  48. newSettingsView.initializePanels();
  49. jasmine.attachToDOM(newSettingsView.element);
  50. expect(newSettingsView.activePanel).toEqual({name: 'Panel 1', options: {}});
  51. });
  52. it("shows the Settings panel if the last saved active panel name no longer exists", function() {
  53. settingsView.addCorePanel('Panel 1', 'panel1', function() {
  54. const div = document.createElement('div');
  55. div.id = 'panel-1';
  56. return {
  57. element: div,
  58. show() { return div.style.display = ''; },
  59. focus() { return div.focus(); },
  60. destroy() { return div.remove(); }
  61. };
  62. });
  63. settingsView.showPanel('Panel 1');
  64. const newSettingsView = main.createSettingsView(settingsView.serialize());
  65. settingsView.destroy();
  66. jasmine.attachToDOM(newSettingsView.element);
  67. newSettingsView.initializePanels();
  68. expect(newSettingsView.activePanel).toEqual({name: 'Core', options: {}});
  69. });
  70. it("serializes the active panel name even when the panels were never initialized", function() {
  71. settingsView.showPanel('Themes');
  72. const settingsView2 = main.createSettingsView(settingsView.serialize());
  73. const settingsView3 = main.createSettingsView(settingsView2.serialize());
  74. jasmine.attachToDOM(settingsView3.element);
  75. settingsView3.initializePanels();
  76. expect(settingsView3.activePanel).toEqual({name: 'Themes', options: {}});
  77. });
  78. });
  79. describe(".addCorePanel(name, iconName, view)", () => it("adds a menu entry to the left and a panel that can be activated by clicking it", function() {
  80. settingsView.addCorePanel('Panel 1', 'panel1', function() {
  81. const div = document.createElement('div');
  82. div.id = 'panel-1';
  83. return {
  84. element: div,
  85. show() { return div.style.display = ''; },
  86. focus() { return div.focus(); },
  87. destroy() { return div.remove(); }
  88. };
  89. });
  90. settingsView.addCorePanel('Panel 2', 'panel2', function() {
  91. const div = document.createElement('div');
  92. div.id = 'panel-2';
  93. return {
  94. element: div,
  95. show() { return div.style.display = ''; },
  96. focus() { return div.focus(); },
  97. destroy() { return div.remove(); }
  98. };
  99. });
  100. expect(settingsView.refs.panelMenu.querySelector('li[name="Panel 1"]')).toExist();
  101. expect(settingsView.refs.panelMenu.querySelector('li[name="Panel 2"]')).toExist();
  102. //expect(settingsView.refs.panelMenu.children[1]).toHaveClass 'active' # TODO FIX
  103. jasmine.attachToDOM(settingsView.element);
  104. settingsView.refs.panelMenu.querySelector('li[name="Panel 1"] a').click();
  105. expect(settingsView.refs.panelMenu.querySelectorAll('.active').length).toBe(1);
  106. expect(settingsView.refs.panelMenu.querySelector('li[name="Panel 1"]')).toHaveClass('active');
  107. expect(settingsView.refs.panels.querySelector('#panel-1')).toBeVisible();
  108. expect(settingsView.refs.panels.querySelector('#panel-2')).not.toExist();
  109. settingsView.refs.panelMenu.querySelector('li[name="Panel 2"] a').click();
  110. expect(settingsView.refs.panelMenu.querySelectorAll('.active').length).toBe(1);
  111. expect(settingsView.refs.panelMenu.querySelector('li[name="Panel 2"]')).toHaveClass('active');
  112. expect(settingsView.refs.panels.querySelector('#panel-1')).toBeHidden();
  113. expect(settingsView.refs.panels.querySelector('#panel-2')).toBeVisible();
  114. }));
  115. describe("when the package is activated", function() {
  116. const openWithCommand = command => waitsFor(function(done) {
  117. var openSubscription = atom.workspace.onDidOpen(function() {
  118. openSubscription.dispose();
  119. return done();
  120. });
  121. return atom.commands.dispatch(atom.views.getView(atom.workspace), command);
  122. });
  123. beforeEach(function() {
  124. jasmine.attachToDOM(atom.views.getView(atom.workspace));
  125. waitsForPromise(() => atom.packages.activatePackage('settings-view'));
  126. });
  127. describe("when the settings view is opened with a settings-view:* command", function() {
  128. beforeEach(() => settingsView = null);
  129. describe("settings-view:open", function() {
  130. it("opens the settings view", function() {
  131. openWithCommand('settings-view:open');
  132. runs(() => expect(atom.workspace.getActivePaneItem().activePanel)
  133. .toEqual({name: 'Core', options: {}}));
  134. });
  135. it("always open existing item in workspace", function() {
  136. const center = atom.workspace.getCenter();
  137. let [pane1, pane2] = [];
  138. waitsForPromise(() => atom.workspace.open(null, {split: 'right'}));
  139. runs(function() {
  140. expect(center.getPanes()).toHaveLength(2);
  141. [pane1, pane2] = center.getPanes();
  142. expect(atom.workspace.getActivePane()).toBe(pane2);
  143. });
  144. openWithCommand('settings-view:open');
  145. runs(function() {
  146. expect(atom.workspace.getActivePaneItem().activePanel).toEqual({name: 'Core', options: {}});
  147. expect(atom.workspace.getActivePane()).toBe(pane2);
  148. });
  149. runs(() => pane1.activate());
  150. openWithCommand('settings-view:open');
  151. runs(function() {
  152. expect(atom.workspace.getActivePaneItem().activePanel).toEqual({name: 'Core', options: {}});
  153. expect(atom.workspace.getActivePane()).toBe(pane2);
  154. });
  155. });
  156. });
  157. describe("settings-view:core", () => it("opens the core settings view", function() {
  158. openWithCommand('settings-view:editor');
  159. runs(() => openWithCommand('settings-view:core'));
  160. runs(() => expect(atom.workspace.getActivePaneItem().activePanel)
  161. .toEqual({name: 'Core', options: {uri: 'atom://config/core'}}));
  162. }));
  163. describe("settings-view:editor", () => it("opens the editor settings view", function() {
  164. openWithCommand('settings-view:editor');
  165. runs(() => expect(atom.workspace.getActivePaneItem().activePanel)
  166. .toEqual({name: 'Editor', options: {uri: 'atom://config/editor'}}));
  167. }));
  168. describe("settings-view:show-keybindings", () => it("opens the settings view to the keybindings page", function() {
  169. openWithCommand('settings-view:show-keybindings');
  170. runs(() => expect(atom.workspace.getActivePaneItem().activePanel)
  171. .toEqual({name: 'Keybindings', options: {uri: 'atom://config/keybindings'}}));
  172. }));
  173. describe("settings-view:change-themes", () => it("opens the settings view to the themes page", function() {
  174. openWithCommand('settings-view:change-themes');
  175. runs(() => expect(atom.workspace.getActivePaneItem().activePanel)
  176. .toEqual({name: 'Themes', options: {uri: 'atom://config/themes'}}));
  177. }));
  178. describe("settings-view:uninstall-themes", () => it("opens the settings view to the themes page", function() {
  179. openWithCommand('settings-view:uninstall-themes');
  180. runs(() => expect(atom.workspace.getActivePaneItem().activePanel)
  181. .toEqual({name: 'Themes', options: {uri: 'atom://config/themes'}}));
  182. }));
  183. describe("settings-view:uninstall-packages", () => it("opens the settings view to the install page", function() {
  184. openWithCommand('settings-view:uninstall-packages');
  185. runs(() => expect(atom.workspace.getActivePaneItem().activePanel)
  186. .toEqual({name: 'Packages', options: {uri: 'atom://config/packages'}}));
  187. }));
  188. describe("settings-view:install-packages-and-themes", () => it("opens the settings view to the install page", function() {
  189. openWithCommand('settings-view:install-packages-and-themes');
  190. runs(() => expect(atom.workspace.getActivePaneItem().activePanel)
  191. .toEqual({name: 'Install', options: {uri: 'atom://config/install'}}));
  192. }));
  193. describe("settings-view:check-for-package-updates", () => it("opens the settings view to the install page", function() {
  194. openWithCommand('settings-view:check-for-package-updates');
  195. runs(() => expect(atom.workspace.getActivePaneItem().activePanel)
  196. .toEqual({name: 'Updates', options: {uri: 'atom://config/updates'}}));
  197. }));
  198. });
  199. describe("when atom.workspace.open() is used with a config URI", function() {
  200. const focusIsWithinActivePanel = function() {
  201. const activePanel = settingsView.panelsByName[settingsView.activePanel.name];
  202. return (activePanel.element === document.activeElement) || activePanel.element.contains(document.activeElement);
  203. };
  204. const expectActivePanelToBeKeyboardScrollable = function() {
  205. const activePanel = settingsView.panelsByName[settingsView.activePanel.name];
  206. spyOn(activePanel, 'pageDown');
  207. atom.commands.dispatch(activePanel.element, 'core:page-down');
  208. expect(activePanel.pageDown).toHaveBeenCalled();
  209. spyOn(activePanel, 'pageUp');
  210. atom.commands.dispatch(activePanel.element, 'core:page-up');
  211. expect(activePanel.pageUp).toHaveBeenCalled();
  212. };
  213. beforeEach(() => settingsView = null);
  214. it("opens the settings to the correct panel with atom://config/<panel-name> and that panel is keyboard-scrollable", function() {
  215. waitsForPromise(() => atom.workspace.open('atom://config').then(s => settingsView = s));
  216. waitsFor(done => process.nextTick(done));
  217. runs(function() {
  218. expect(settingsView.activePanel)
  219. .toEqual({name: 'Core', options: {}});
  220. expect(focusIsWithinActivePanel()).toBe(true);
  221. expectActivePanelToBeKeyboardScrollable();
  222. });
  223. waitsForPromise(() => atom.workspace.open('atom://config/editor').then(s => settingsView = s));
  224. waits(1);
  225. runs(function() {
  226. expect(settingsView.activePanel)
  227. .toEqual({name: 'Editor', options: {uri: 'atom://config/editor'}});
  228. expect(focusIsWithinActivePanel()).toBe(true);
  229. expectActivePanelToBeKeyboardScrollable();
  230. });
  231. waitsForPromise(() => atom.workspace.open('atom://config/keybindings').then(s => settingsView = s));
  232. waits(1);
  233. runs(function() {
  234. expect(settingsView.activePanel)
  235. .toEqual({name: 'Keybindings', options: {uri: 'atom://config/keybindings'}});
  236. expect(focusIsWithinActivePanel()).toBe(true);
  237. expectActivePanelToBeKeyboardScrollable();
  238. });
  239. waitsForPromise(() => atom.workspace.open('atom://config/packages').then(s => settingsView = s));
  240. waits(1);
  241. runs(function() {
  242. expect(settingsView.activePanel)
  243. .toEqual({name: 'Packages', options: {uri: 'atom://config/packages'}});
  244. expect(focusIsWithinActivePanel()).toBe(true);
  245. expectActivePanelToBeKeyboardScrollable();
  246. });
  247. waitsForPromise(() => atom.workspace.open('atom://config/themes').then(s => settingsView = s));
  248. waits(1);
  249. runs(function() {
  250. expect(settingsView.activePanel)
  251. .toEqual({name: 'Themes', options: {uri: 'atom://config/themes'}});
  252. expect(focusIsWithinActivePanel()).toBe(true);
  253. expectActivePanelToBeKeyboardScrollable();
  254. });
  255. waitsForPromise(() => atom.workspace.open('atom://config/updates').then(s => settingsView = s));
  256. waits(1);
  257. runs(function() {
  258. expect(settingsView.activePanel)
  259. .toEqual({name: 'Updates', options: {uri: 'atom://config/updates'}});
  260. expect(focusIsWithinActivePanel()).toBe(true);
  261. expectActivePanelToBeKeyboardScrollable();
  262. });
  263. waitsForPromise(() => atom.workspace.open('atom://config/install').then(s => settingsView = s));
  264. let hasSystemPanel = false;
  265. waits(1);
  266. runs(function() {
  267. expect(settingsView.activePanel)
  268. .toEqual({name: 'Install', options: {uri: 'atom://config/install'}});
  269. expect(focusIsWithinActivePanel()).toBe(true);
  270. expectActivePanelToBeKeyboardScrollable();
  271. return hasSystemPanel = (settingsView.panelsByName['System'] != null);
  272. });
  273. if (hasSystemPanel) {
  274. waitsForPromise(() => atom.workspace.open('atom://config/system').then(s => settingsView = s));
  275. waits(1);
  276. runs(function() {
  277. expect(settingsView.activePanel)
  278. .toEqual({name: 'System', options: {uri: 'atom://config/system'}});
  279. expect(focusIsWithinActivePanel()).toBe(true);
  280. expectActivePanelToBeKeyboardScrollable();
  281. });
  282. }
  283. });
  284. it("opens the package settings view with atom://config/packages/<package-name>", function() {
  285. waitsForPromise(() => atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-readme')));
  286. waitsForPromise(() => atom.workspace.open('atom://config/packages/package-with-readme').then(s => settingsView = s));
  287. waitsFor(done => process.nextTick(done));
  288. runs(() => expect(settingsView.activePanel)
  289. .toEqual({name: 'package-with-readme', options: {
  290. uri: 'atom://config/packages/package-with-readme',
  291. pack: {
  292. name: 'package-with-readme',
  293. metadata: {
  294. name: 'package-with-readme'
  295. }
  296. },
  297. back: 'Packages'
  298. }}));
  299. });
  300. it("doesn't use cached package detail when package re-activated and opnes the package view with atom://config/packages/<package-name>", function() {
  301. let [detailInitial, detailAfterReactivate] = [];
  302. waitsForPromise(function() {
  303. atom.packages.activate();
  304. return new Promise(resolve => atom.packages.onDidActivateInitialPackages(resolve));
  305. });
  306. waitsForPromise(() => atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-readme')));
  307. waitsForPromise(() => atom.workspace.open('atom://config/packages/package-with-readme').then(s => settingsView = s));
  308. waitsFor(done => process.nextTick(done));
  309. runs(function() {
  310. detailInitial = settingsView.getOrCreatePanel('package-with-readme');
  311. expect(settingsView.getOrCreatePanel('package-with-readme')).toBe(detailInitial);
  312. });
  313. waitsForPromise(() => atom.packages.deactivatePackage('package-with-readme'));
  314. waitsForPromise(() => atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-readme')));
  315. waitsForPromise(() => atom.workspace.open('atom://config/packages/package-with-readme'));
  316. runs(function() {
  317. detailAfterReactivate = settingsView.getOrCreatePanel('package-with-readme');
  318. expect(settingsView.getOrCreatePanel('package-with-readme')).toBe(detailAfterReactivate);
  319. expect(detailInitial).toBeTruthy();
  320. expect(detailAfterReactivate).toBeTruthy();
  321. expect(detailInitial).not.toBe(detailAfterReactivate);
  322. });
  323. });
  324. it("passes the URI to a pane's beforeShow() method on settings view initialization", function() {
  325. const InstallPanel = require('../lib/install-panel');
  326. spyOn(InstallPanel.prototype, 'beforeShow');
  327. waitsForPromise(() => atom.workspace.open('atom://config/install/package:something').then(s => settingsView = s));
  328. waitsFor(() => settingsView.activePanel != null
  329. , 'The activePanel should be set', 5000);
  330. runs(function() {
  331. expect(settingsView.activePanel)
  332. .toEqual({name: 'Install', options: {uri: 'atom://config/install/package:something'}});
  333. expect(InstallPanel.prototype.beforeShow).toHaveBeenCalledWith({uri: 'atom://config/install/package:something'});});
  334. });
  335. it("passes the URI to a pane's beforeShow() method after initialization", function() {
  336. const InstallPanel = require('../lib/install-panel');
  337. spyOn(InstallPanel.prototype, 'beforeShow');
  338. waitsForPromise(() => atom.workspace.open('atom://config').then(s => settingsView = s));
  339. waitsFor(done => process.nextTick(done));
  340. runs(() => expect(settingsView.activePanel).toEqual({name: 'Core', options: {}}));
  341. waitsForPromise(() => atom.workspace.open('atom://config/install/package:something').then(s => settingsView = s));
  342. waits(1);
  343. runs(function() {
  344. expect(settingsView.activePanel)
  345. .toEqual({name: 'Install', options: {uri: 'atom://config/install/package:something'}});
  346. expect(InstallPanel.prototype.beforeShow).toHaveBeenCalledWith({uri: 'atom://config/install/package:something'});});
  347. });
  348. });
  349. describe("when the package is then deactivated", function() {
  350. beforeEach(() => settingsView = null);
  351. it("calls the dispose method on all panels", function() {
  352. openWithCommand('settings-view:open');
  353. waitsFor(done => process.nextTick(done));
  354. runs(function() {
  355. let panel;
  356. settingsView = atom.workspace.getActivePaneItem();
  357. const panels = [
  358. settingsView.getOrCreatePanel('Core'),
  359. settingsView.getOrCreatePanel('Editor'),
  360. settingsView.getOrCreatePanel('Keybindings'),
  361. settingsView.getOrCreatePanel('Packages'),
  362. settingsView.getOrCreatePanel('Themes'),
  363. settingsView.getOrCreatePanel('Updates'),
  364. settingsView.getOrCreatePanel('Install')
  365. ];
  366. const systemPanel = settingsView.getOrCreatePanel('System');
  367. if (systemPanel != null) {
  368. panels.push(systemPanel);
  369. }
  370. for (panel of Array.from(panels)) {
  371. if (panel.dispose) {
  372. spyOn(panel, 'dispose');
  373. } else {
  374. spyOn(panel, 'destroy');
  375. }
  376. }
  377. waitsForPromise(() => Promise.resolve(atom.packages.deactivatePackage('settings-view'))); // Ensure works on promise and non-promise versions
  378. runs(function() {
  379. for (panel of Array.from(panels)) {
  380. if (panel.dispose) {
  381. expect(panel.dispose).toHaveBeenCalled();
  382. } else {
  383. expect(panel.destroy).toHaveBeenCalled();
  384. }
  385. }
  386. });
  387. });
  388. });
  389. });
  390. });
  391. describe("when an installed package is clicked from the Install panel", () => it("displays the package details", function() {
  392. waitsFor(() => atom.packages.activatePackage('settings-view'));
  393. runs(function() {
  394. settingsView.packageManager.getClient();
  395. spyOn(settingsView.packageManager.client, 'featuredPackages').andCallFake(callback => callback(null, [{name: 'settings-view'}]));
  396. return settingsView.showPanel('Install');
  397. });
  398. waitsFor(() => settingsView.element.querySelectorAll('.package-card:not(.hidden)').length > 0);
  399. runs(function() {
  400. settingsView.element.querySelectorAll('.package-card:not(.hidden)')[0].click();
  401. const packageDetail = settingsView.element.querySelector('.package-detail .active');
  402. expect(packageDetail.textContent).toBe('Settings View');
  403. });
  404. }));
  405. describe("when the active theme has settings", function() {
  406. let panel = null;
  407. beforeEach(function() {
  408. atom.packages.packageDirPaths.push(path.join(__dirname, 'fixtures'));
  409. atom.packages.loadPackage('ui-theme-with-config');
  410. atom.packages.loadPackage('syntax-theme-with-config');
  411. atom.config.set('core.themes', ['ui-theme-with-config', 'syntax-theme-with-config']);
  412. const reloadedHandler = jasmine.createSpy('reloadedHandler');
  413. atom.themes.onDidChangeActiveThemes(reloadedHandler);
  414. atom.themes.activatePackages();
  415. waitsFor("themes to be reloaded", () => reloadedHandler.callCount === 1);
  416. runs(function() {
  417. settingsView.showPanel('Themes');
  418. return panel = settingsView.element.querySelector('.themes-panel');
  419. });
  420. });
  421. afterEach(() => atom.themes.unwatchUserStylesheet());
  422. describe("when the UI theme's settings button is clicked", () => it("navigates to that theme's detail view", function() {
  423. jasmine.attachToDOM(settingsView.element);
  424. expect(panel.querySelector('.active-theme-settings')).toBeVisible();
  425. panel.querySelector('.active-theme-settings').click();
  426. const packageDetail = settingsView.element.querySelector('.package-detail li.active');
  427. expect(packageDetail.textContent).toBe('Ui Theme With Config');
  428. }));
  429. describe("when the syntax theme's settings button is clicked", () => it("navigates to that theme's detail view", function() {
  430. jasmine.attachToDOM(settingsView.element);
  431. expect(panel.querySelector('.active-syntax-settings')).toBeVisible();
  432. panel.querySelector('.active-syntax-settings').click();
  433. const packageDetail = settingsView.element.querySelector('.package-detail li.active');
  434. expect(packageDetail.textContent).toBe('Syntax Theme With Config');
  435. }));
  436. });
  437. });