window-event-handler-spec.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. const KeymapManager = require('atom-keymap');
  2. const WindowEventHandler = require('../src/window-event-handler');
  3. const { conditionPromise } = require('./async-spec-helpers');
  4. describe('WindowEventHandler', () => {
  5. let windowEventHandler;
  6. beforeEach(() => {
  7. atom.uninstallWindowEventHandler();
  8. spyOn(atom, 'hide');
  9. const initialPath = atom.project.getPaths()[0];
  10. spyOn(atom, 'getLoadSettings').andCallFake(() => {
  11. const loadSettings = atom.getLoadSettings.originalValue.call(atom);
  12. loadSettings.initialPath = initialPath;
  13. return loadSettings;
  14. });
  15. atom.project.destroy();
  16. windowEventHandler = new WindowEventHandler({
  17. atomEnvironment: atom,
  18. applicationDelegate: atom.applicationDelegate
  19. });
  20. windowEventHandler.initialize(window, document);
  21. });
  22. afterEach(() => {
  23. windowEventHandler.unsubscribe();
  24. atom.installWindowEventHandler();
  25. });
  26. describe('when the window is loaded', () =>
  27. it("doesn't have .is-blurred on the body tag", () => {
  28. if (process.platform === 'win32') {
  29. return;
  30. } // Win32TestFailures - can not steal focus
  31. expect(document.body.className).not.toMatch('is-blurred');
  32. }));
  33. describe('when the window is blurred', () => {
  34. beforeEach(() => window.dispatchEvent(new CustomEvent('blur')));
  35. afterEach(() => document.body.classList.remove('is-blurred'));
  36. it('adds the .is-blurred class on the body', () =>
  37. expect(document.body.className).toMatch('is-blurred'));
  38. describe('when the window is focused again', () =>
  39. it('removes the .is-blurred class from the body', () => {
  40. window.dispatchEvent(new CustomEvent('focus'));
  41. expect(document.body.className).not.toMatch('is-blurred');
  42. }));
  43. });
  44. describe('resize event', () =>
  45. it('calls storeWindowDimensions', async () => {
  46. jasmine.useRealClock();
  47. spyOn(atom, 'storeWindowDimensions');
  48. window.dispatchEvent(new CustomEvent('resize'));
  49. await conditionPromise(() => atom.storeWindowDimensions.callCount > 0);
  50. }));
  51. describe('window:close event', () =>
  52. it('closes the window', () => {
  53. spyOn(atom, 'close');
  54. window.dispatchEvent(new CustomEvent('window:close'));
  55. expect(atom.close).toHaveBeenCalled();
  56. }));
  57. describe('when a link is clicked', () => {
  58. it('opens the http/https links in an external application', () => {
  59. const { shell } = require('electron');
  60. spyOn(shell, 'openExternal');
  61. const link = document.createElement('a');
  62. const linkChild = document.createElement('span');
  63. link.appendChild(linkChild);
  64. link.href = 'http://github.com';
  65. jasmine.attachToDOM(link);
  66. const fakeEvent = {
  67. target: linkChild,
  68. currentTarget: link,
  69. preventDefault: () => {}
  70. };
  71. windowEventHandler.handleLinkClick(fakeEvent);
  72. expect(shell.openExternal).toHaveBeenCalled();
  73. expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com');
  74. shell.openExternal.reset();
  75. link.href = 'https://github.com';
  76. windowEventHandler.handleLinkClick(fakeEvent);
  77. expect(shell.openExternal).toHaveBeenCalled();
  78. expect(shell.openExternal.argsForCall[0][0]).toBe('https://github.com');
  79. shell.openExternal.reset();
  80. link.href = '';
  81. windowEventHandler.handleLinkClick(fakeEvent);
  82. expect(shell.openExternal).not.toHaveBeenCalled();
  83. shell.openExternal.reset();
  84. link.href = '#scroll-me';
  85. windowEventHandler.handleLinkClick(fakeEvent);
  86. expect(shell.openExternal).not.toHaveBeenCalled();
  87. });
  88. it('opens the "atom://" links with URL handler', () => {
  89. const uriHandler = windowEventHandler.atomEnvironment.uriHandlerRegistry;
  90. expect(uriHandler).toBeDefined();
  91. spyOn(uriHandler, 'handleURI');
  92. const link = document.createElement('a');
  93. const linkChild = document.createElement('span');
  94. link.appendChild(linkChild);
  95. link.href = 'atom://github.com';
  96. jasmine.attachToDOM(link);
  97. const fakeEvent = {
  98. target: linkChild,
  99. currentTarget: link,
  100. preventDefault: () => {}
  101. };
  102. windowEventHandler.handleLinkClick(fakeEvent);
  103. expect(uriHandler.handleURI).toHaveBeenCalled();
  104. expect(uriHandler.handleURI.argsForCall[0][0]).toBe('atom://github.com');
  105. });
  106. });
  107. describe('when a form is submitted', () =>
  108. it("prevents the default so that the window's URL isn't changed", () => {
  109. const form = document.createElement('form');
  110. jasmine.attachToDOM(form);
  111. let defaultPrevented = false;
  112. const event = new CustomEvent('submit', { bubbles: true });
  113. event.preventDefault = () => {
  114. defaultPrevented = true;
  115. };
  116. form.dispatchEvent(event);
  117. expect(defaultPrevented).toBe(true);
  118. }));
  119. describe('core:focus-next and core:focus-previous', () => {
  120. describe('when there is no currently focused element', () =>
  121. it('focuses the element with the lowest/highest tabindex', () => {
  122. const wrapperDiv = document.createElement('div');
  123. wrapperDiv.innerHTML = `
  124. <div>
  125. <button tabindex="2"></button>
  126. <input tabindex="1">
  127. </div>
  128. `.trim();
  129. const elements = wrapperDiv.firstChild;
  130. jasmine.attachToDOM(elements);
  131. elements.dispatchEvent(
  132. new CustomEvent('core:focus-next', { bubbles: true })
  133. );
  134. expect(document.activeElement.tabIndex).toBe(1);
  135. document.body.focus();
  136. elements.dispatchEvent(
  137. new CustomEvent('core:focus-previous', { bubbles: true })
  138. );
  139. expect(document.activeElement.tabIndex).toBe(2);
  140. }));
  141. describe('when a tabindex is set on the currently focused element', () =>
  142. it('focuses the element with the next highest/lowest tabindex, skipping disabled elements', () => {
  143. const wrapperDiv = document.createElement('div');
  144. wrapperDiv.innerHTML = `
  145. <div>
  146. <input tabindex="1">
  147. <button tabindex="2"></button>
  148. <button tabindex="5"></button>
  149. <input tabindex="-1">
  150. <input tabindex="3">
  151. <button tabindex="7"></button>
  152. <input tabindex="9" disabled>
  153. </div>
  154. `.trim();
  155. const elements = wrapperDiv.firstChild;
  156. jasmine.attachToDOM(elements);
  157. elements.querySelector('[tabindex="1"]').focus();
  158. elements.dispatchEvent(
  159. new CustomEvent('core:focus-next', { bubbles: true })
  160. );
  161. expect(document.activeElement.tabIndex).toBe(2);
  162. elements.dispatchEvent(
  163. new CustomEvent('core:focus-next', { bubbles: true })
  164. );
  165. expect(document.activeElement.tabIndex).toBe(3);
  166. elements.dispatchEvent(
  167. new CustomEvent('core:focus-next', { bubbles: true })
  168. );
  169. expect(document.activeElement.tabIndex).toBe(5);
  170. elements.dispatchEvent(
  171. new CustomEvent('core:focus-next', { bubbles: true })
  172. );
  173. expect(document.activeElement.tabIndex).toBe(7);
  174. elements.dispatchEvent(
  175. new CustomEvent('core:focus-next', { bubbles: true })
  176. );
  177. expect(document.activeElement.tabIndex).toBe(1);
  178. elements.dispatchEvent(
  179. new CustomEvent('core:focus-previous', { bubbles: true })
  180. );
  181. expect(document.activeElement.tabIndex).toBe(7);
  182. elements.dispatchEvent(
  183. new CustomEvent('core:focus-previous', { bubbles: true })
  184. );
  185. expect(document.activeElement.tabIndex).toBe(5);
  186. elements.dispatchEvent(
  187. new CustomEvent('core:focus-previous', { bubbles: true })
  188. );
  189. expect(document.activeElement.tabIndex).toBe(3);
  190. elements.dispatchEvent(
  191. new CustomEvent('core:focus-previous', { bubbles: true })
  192. );
  193. expect(document.activeElement.tabIndex).toBe(2);
  194. elements.dispatchEvent(
  195. new CustomEvent('core:focus-previous', { bubbles: true })
  196. );
  197. expect(document.activeElement.tabIndex).toBe(1);
  198. elements.dispatchEvent(
  199. new CustomEvent('core:focus-previous', { bubbles: true })
  200. );
  201. expect(document.activeElement.tabIndex).toBe(7);
  202. }));
  203. });
  204. describe('when keydown events occur on the document', () =>
  205. it('dispatches the event via the KeymapManager and CommandRegistry', () => {
  206. const dispatchedCommands = [];
  207. atom.commands.onWillDispatch(command => dispatchedCommands.push(command));
  208. atom.commands.add('*', { 'foo-command': () => {} });
  209. atom.keymaps.add('source-name', { '*': { x: 'foo-command' } });
  210. const event = KeymapManager.buildKeydownEvent('x', {
  211. target: document.createElement('div')
  212. });
  213. document.dispatchEvent(event);
  214. expect(dispatchedCommands.length).toBe(1);
  215. expect(dispatchedCommands[0].type).toBe('foo-command');
  216. }));
  217. describe('native key bindings', () =>
  218. it("correctly dispatches them to active elements with the '.native-key-bindings' class", () => {
  219. const webContentsSpy = jasmine.createSpyObj('webContents', [
  220. 'copy',
  221. 'paste'
  222. ]);
  223. spyOn(atom.applicationDelegate, 'getCurrentWindow').andReturn({
  224. webContents: webContentsSpy,
  225. on: () => {}
  226. });
  227. const nativeKeyBindingsInput = document.createElement('input');
  228. nativeKeyBindingsInput.classList.add('native-key-bindings');
  229. jasmine.attachToDOM(nativeKeyBindingsInput);
  230. nativeKeyBindingsInput.focus();
  231. atom.dispatchApplicationMenuCommand('core:copy');
  232. atom.dispatchApplicationMenuCommand('core:paste');
  233. expect(webContentsSpy.copy).toHaveBeenCalled();
  234. expect(webContentsSpy.paste).toHaveBeenCalled();
  235. webContentsSpy.copy.reset();
  236. webContentsSpy.paste.reset();
  237. const normalInput = document.createElement('input');
  238. jasmine.attachToDOM(normalInput);
  239. normalInput.focus();
  240. atom.dispatchApplicationMenuCommand('core:copy');
  241. atom.dispatchApplicationMenuCommand('core:paste');
  242. expect(webContentsSpy.copy).not.toHaveBeenCalled();
  243. expect(webContentsSpy.paste).not.toHaveBeenCalled();
  244. }));
  245. });