editor.spec.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import 'jest';
  4. import { generate, simulate } from 'simulate-event';
  5. import { CodeEditor } from '@jupyterlab/codeeditor';
  6. import { CodeMirrorEditor } from '@jupyterlab/codemirror';
  7. const UP_ARROW = 38;
  8. const DOWN_ARROW = 40;
  9. const ENTER = 13;
  10. class LogFileEditor extends CodeMirrorEditor {
  11. methods: string[] = [];
  12. protected onKeydown(event: KeyboardEvent): boolean {
  13. const value = super.onKeydown(event);
  14. this.methods.push('onKeydown');
  15. return value;
  16. }
  17. }
  18. describe('CodeMirrorEditor', () => {
  19. let editor: LogFileEditor;
  20. let host: HTMLElement;
  21. let model: CodeEditor.IModel;
  22. const TEXT = new Array(100).join('foo bar baz\n');
  23. beforeEach(() => {
  24. host = document.createElement('div');
  25. document.body.appendChild(host);
  26. model = new CodeEditor.Model();
  27. editor = new LogFileEditor({ host, model });
  28. });
  29. afterEach(() => {
  30. editor.dispose();
  31. document.body.removeChild(host);
  32. });
  33. describe('#constructor()', () => {
  34. it('should create a CodeMirrorEditor', () => {
  35. expect(editor).toBeInstanceOf(CodeMirrorEditor);
  36. });
  37. });
  38. describe('#edgeRequested', () => {
  39. it('should emit a signal when the top edge is requested', () => {
  40. let edge: CodeEditor.EdgeLocation | null = null;
  41. const event = generate('keydown', { keyCode: UP_ARROW });
  42. const listener = (sender: any, args: CodeEditor.EdgeLocation) => {
  43. edge = args;
  44. };
  45. editor.edgeRequested.connect(listener);
  46. expect(edge).toBeNull();
  47. editor.editor.triggerOnKeyDown(event);
  48. expect(edge).toBe('top');
  49. });
  50. it('should emit a signal when the bottom edge is requested', () => {
  51. let edge: CodeEditor.EdgeLocation | null = null;
  52. const event = generate('keydown', { keyCode: DOWN_ARROW });
  53. const listener = (sender: any, args: CodeEditor.EdgeLocation) => {
  54. edge = args;
  55. };
  56. editor.edgeRequested.connect(listener);
  57. expect(edge).toBeNull();
  58. editor.editor.triggerOnKeyDown(event);
  59. expect(edge).toBe('bottom');
  60. });
  61. });
  62. describe('#uuid', () => {
  63. it('should be the unique id of the editor', () => {
  64. expect(editor.uuid).toBeTruthy();
  65. const uuid = 'foo';
  66. editor = new LogFileEditor({ model, host, uuid });
  67. expect(editor.uuid).toBe('foo');
  68. });
  69. });
  70. describe('#selectionStyle', () => {
  71. it('should be the selection style of the editor', () => {
  72. expect(editor.selectionStyle).toEqual(CodeEditor.defaultSelectionStyle);
  73. });
  74. it('should be settable', () => {
  75. const style = {
  76. className: 'foo',
  77. displayName: 'bar',
  78. color: 'black'
  79. };
  80. editor.selectionStyle = style;
  81. expect(editor.selectionStyle).toEqual(style);
  82. });
  83. });
  84. describe('#editor', () => {
  85. it('should be the codemirror editor wrapped by the editor', () => {
  86. const cm = editor.editor;
  87. expect(cm.getDoc()).toBe(editor.doc);
  88. });
  89. });
  90. describe('#doc', () => {
  91. it('should be the codemirror doc wrapped by the editor', () => {
  92. const doc = editor.doc;
  93. expect(doc.getEditor()).toBe(editor.editor);
  94. });
  95. });
  96. describe('#lineCount', () => {
  97. it('should get the number of lines in the editor', () => {
  98. expect(editor.lineCount).toBe(1);
  99. editor.model.value.text = 'foo\nbar\nbaz';
  100. expect(editor.lineCount).toBe(3);
  101. });
  102. });
  103. describe('#getOption()', () => {
  104. it('should get whether line numbers should be shown', () => {
  105. expect(editor.getOption('lineNumbers')).toBe(false);
  106. });
  107. it('should get whether horizontally scrolling should be used', () => {
  108. expect(editor.getOption('lineWrap')).toBe('on');
  109. });
  110. it('should get whether the editor is readonly', () => {
  111. expect(editor.getOption('readOnly')).toBe(false);
  112. });
  113. });
  114. describe('#setOption()', () => {
  115. it('should set whether line numbers should be shown', () => {
  116. editor.setOption('lineNumbers', true);
  117. expect(editor.getOption('lineNumbers')).toBe(true);
  118. });
  119. it('should set whether horizontally scrolling should be used', () => {
  120. editor.setOption('lineWrap', 'off');
  121. expect(editor.getOption('lineWrap')).toBe('off');
  122. });
  123. it('should set whether the editor is readonly', () => {
  124. editor.setOption('readOnly', true);
  125. expect(editor.getOption('readOnly')).toBe(true);
  126. });
  127. });
  128. describe('#model', () => {
  129. it('should get the model used by the editor', () => {
  130. expect(editor.model).toBe(model);
  131. });
  132. });
  133. describe('#lineHeight', () => {
  134. it('should get the text height of a line in the editor', () => {
  135. expect(editor.lineHeight).toBeGreaterThan(0);
  136. });
  137. });
  138. describe('#charWidth', () => {
  139. it('should get the character width in the editor', () => {
  140. expect(editor.charWidth).toBeGreaterThan(0);
  141. });
  142. });
  143. describe('#isDisposed', () => {
  144. it('should test whether the editor is disposed', () => {
  145. expect(editor.isDisposed).toBe(false);
  146. editor.dispose();
  147. expect(editor.isDisposed).toBe(true);
  148. });
  149. });
  150. describe('#dispose()', () => {
  151. it('should dispose of the resources used by the editor', () => {
  152. expect(editor.isDisposed).toBe(false);
  153. editor.dispose();
  154. expect(editor.isDisposed).toBe(true);
  155. editor.dispose();
  156. expect(editor.isDisposed).toBe(true);
  157. });
  158. });
  159. describe('#getLine()', () => {
  160. it('should get a line of text', () => {
  161. model.value.text = 'foo\nbar';
  162. expect(editor.getLine(0)).toBe('foo');
  163. expect(editor.getLine(1)).toBe('bar');
  164. expect(editor.getLine(2)).toBeUndefined();
  165. });
  166. });
  167. describe('#getOffsetAt()', () => {
  168. it('should get the offset for a given position', () => {
  169. model.value.text = 'foo\nbar';
  170. let pos = {
  171. column: 2,
  172. line: 1
  173. };
  174. expect(editor.getOffsetAt(pos)).toBe(6);
  175. pos = {
  176. column: 2,
  177. line: 5
  178. };
  179. expect(editor.getOffsetAt(pos)).toBe(7);
  180. });
  181. });
  182. describe('#getPositionAt()', () => {
  183. it('should get the position for a given offset', () => {
  184. model.value.text = 'foo\nbar';
  185. let pos = editor.getPositionAt(6);
  186. expect(pos.column).toBe(2);
  187. expect(pos.line).toBe(1);
  188. pos = editor.getPositionAt(101);
  189. expect(pos.column).toBe(3);
  190. expect(pos.line).toBe(1);
  191. });
  192. });
  193. describe('#undo()', () => {
  194. it('should undo one edit', () => {
  195. model.value.text = 'foo';
  196. editor.undo();
  197. expect(model.value.text).toBe('');
  198. });
  199. });
  200. describe('#redo()', () => {
  201. it('should redo one undone edit', () => {
  202. model.value.text = 'foo';
  203. editor.undo();
  204. editor.redo();
  205. expect(model.value.text).toBe('foo');
  206. });
  207. });
  208. describe('#clearHistory()', () => {
  209. it('should clear the undo history', () => {
  210. model.value.text = 'foo';
  211. editor.clearHistory();
  212. editor.undo();
  213. expect(model.value.text).toBe('foo');
  214. });
  215. });
  216. describe('#focus()', () => {
  217. it('should give focus to the editor', () => {
  218. expect(host.contains(document.activeElement)).toBe(false);
  219. editor.focus();
  220. expect(host.contains(document.activeElement)).toBe(true);
  221. });
  222. });
  223. describe('#hasFocus()', () => {
  224. it('should test whether the editor has focus', () => {
  225. expect(editor.hasFocus()).toBe(false);
  226. editor.focus();
  227. expect(editor.hasFocus()).toBe(true);
  228. });
  229. });
  230. describe('#blur()', () => {
  231. it('should blur the editor', () => {
  232. editor.focus();
  233. expect(host.contains(document.activeElement)).toBe(true);
  234. editor.blur();
  235. expect(host.contains(document.activeElement)).toBe(false);
  236. });
  237. });
  238. describe('#handleEvent', () => {
  239. describe('focus', () => {
  240. it('should add the focus class to the host', () => {
  241. simulate(editor.editor.getInputField(), 'focus');
  242. expect(host.classList.contains('jp-mod-focused')).toBe(true);
  243. });
  244. });
  245. describe('blur', () => {
  246. it('should remove the focus class from the host', () => {
  247. simulate(editor.editor.getInputField(), 'focus');
  248. expect(host.classList.contains('jp-mod-focused')).toBe(true);
  249. simulate(editor.editor.getInputField(), 'blur');
  250. expect(host.classList.contains('jp-mod-focused')).toBe(false);
  251. });
  252. });
  253. });
  254. describe('#refresh()', () => {
  255. it('should repaint the editor', () => {
  256. editor.refresh();
  257. expect(editor).toBeTruthy();
  258. });
  259. });
  260. describe('#addKeydownHandler()', () => {
  261. it('should add a keydown handler to the editor', () => {
  262. let called = 0;
  263. const handler = () => {
  264. called++;
  265. return true;
  266. };
  267. const disposable = editor.addKeydownHandler(handler);
  268. let evt = generate('keydown', { keyCode: ENTER });
  269. editor.editor.triggerOnKeyDown(evt);
  270. expect(called).toBe(1);
  271. disposable.dispose();
  272. expect(disposable.isDisposed).toBe(true);
  273. evt = generate('keydown', { keyCode: ENTER });
  274. editor.editor.triggerOnKeyDown(evt);
  275. expect(called).toBe(1);
  276. });
  277. });
  278. describe('#setSize()', () => {
  279. it('should set the size of the editor in pixels', () => {
  280. editor.setSize({ width: 100, height: 100 });
  281. editor.setSize(null);
  282. expect(editor).toBeTruthy();
  283. });
  284. });
  285. describe('#revealPosition()', () => {
  286. it('should reveal the given position in the editor', () => {
  287. model.value.text = TEXT;
  288. editor.revealPosition({ line: 50, column: 0 });
  289. expect(editor).toBeTruthy();
  290. });
  291. });
  292. describe('#revealSelection()', () => {
  293. it('should reveal the given selection in the editor', () => {
  294. model.value.text = TEXT;
  295. const start = { line: 50, column: 0 };
  296. const end = { line: 52, column: 0 };
  297. editor.setSelection({ start, end });
  298. editor.revealSelection(editor.getSelection());
  299. expect(editor).toBeTruthy();
  300. });
  301. });
  302. describe('#getCoordinateForPosition()', () => {
  303. it('should get the window coordinates given a cursor position', () => {
  304. model.value.text = TEXT;
  305. const coord = editor.getCoordinateForPosition({ line: 10, column: 1 });
  306. if (typeof process !== 'undefined') {
  307. expect(coord.left).toBe(0);
  308. } else {
  309. expect(coord.left).toBeGreaterThan(0);
  310. }
  311. });
  312. });
  313. describe('#getPositionForCoordinate()', () => {
  314. it('should get the window coordinates given a cursor position', () => {
  315. model.value.text = TEXT;
  316. const coord = editor.getCoordinateForPosition({ line: 10, column: 1 });
  317. const newPos = editor.getPositionForCoordinate(coord)!;
  318. expect(newPos.line).toBeTruthy();
  319. expect(newPos.column).toBeTruthy();
  320. });
  321. });
  322. describe('#getCursorPosition()', () => {
  323. it('should get the primary position of the cursor', () => {
  324. model.value.text = TEXT;
  325. let pos = editor.getCursorPosition();
  326. expect(pos.line).toBe(0);
  327. expect(pos.column).toBe(0);
  328. editor.setCursorPosition({ line: 12, column: 3 });
  329. pos = editor.getCursorPosition();
  330. expect(pos.line).toBe(12);
  331. expect(pos.column).toBe(3);
  332. });
  333. });
  334. describe('#setCursorPosition()', () => {
  335. it('should set the primary position of the cursor', () => {
  336. model.value.text = TEXT;
  337. editor.setCursorPosition({ line: 12, column: 3 });
  338. const pos = editor.getCursorPosition();
  339. expect(pos.line).toBe(12);
  340. expect(pos.column).toBe(3);
  341. });
  342. });
  343. describe('#getSelection()', () => {
  344. it('should get the primary selection of the editor', () => {
  345. const selection = editor.getSelection();
  346. expect(selection.start.line).toBe(0);
  347. expect(selection.end.line).toBe(0);
  348. });
  349. });
  350. describe('#setSelection()', () => {
  351. it('should set the primary selection of the editor', () => {
  352. model.value.text = TEXT;
  353. const start = { line: 50, column: 0 };
  354. const end = { line: 52, column: 0 };
  355. editor.setSelection({ start, end });
  356. expect(editor.getSelection().start).toEqual(start);
  357. expect(editor.getSelection().end).toEqual(end);
  358. });
  359. it('should remove any secondary cursors', () => {
  360. model.value.text = TEXT;
  361. const range0 = {
  362. start: { line: 50, column: 0 },
  363. end: { line: 52, column: 0 }
  364. };
  365. const range1 = {
  366. start: { line: 53, column: 0 },
  367. end: { line: 54, column: 0 }
  368. };
  369. editor.setSelections([range0, range1]);
  370. editor.setSelection(range1);
  371. expect(editor.getSelections().length).toBe(1);
  372. });
  373. });
  374. describe('#getSelections()', () => {
  375. it('should get the selections for all the cursors', () => {
  376. model.value.text = TEXT;
  377. const range0 = {
  378. start: { line: 50, column: 0 },
  379. end: { line: 52, column: 0 }
  380. };
  381. const range1 = {
  382. start: { line: 53, column: 0 },
  383. end: { line: 54, column: 0 }
  384. };
  385. editor.setSelections([range0, range1]);
  386. const selections = editor.getSelections();
  387. expect(selections[0].start.line).toBe(50);
  388. expect(selections[1].end.line).toBe(54);
  389. });
  390. });
  391. describe('#setSelections()', () => {
  392. it('should set the selections for all the cursors', () => {
  393. model.value.text = TEXT;
  394. const range0 = {
  395. start: { line: 50, column: 0 },
  396. end: { line: 52, column: 0 }
  397. };
  398. const range1 = {
  399. start: { line: 53, column: 0 },
  400. end: { line: 54, column: 0 }
  401. };
  402. editor.setSelections([range0, range1]);
  403. const selections = editor.getSelections();
  404. expect(selections[0].start.line).toBe(50);
  405. expect(selections[1].end.line).toBe(54);
  406. });
  407. it('should set a default selection for an empty array', () => {
  408. model.value.text = TEXT;
  409. editor.setSelections([]);
  410. const selection = editor.getSelection();
  411. expect(selection.start.line).toBe(0);
  412. expect(selection.end.line).toBe(0);
  413. });
  414. });
  415. describe('#onKeydown()', () => {
  416. it('should run when there is a keydown event on the editor', () => {
  417. const event = generate('keydown', { keyCode: UP_ARROW });
  418. expect(editor.methods).toEqual(expect.not.arrayContaining(['onKeydown']));
  419. editor.editor.triggerOnKeyDown(event);
  420. expect(editor.methods).toEqual(expect.arrayContaining(['onKeydown']));
  421. });
  422. });
  423. });