editor.spec.ts 14 KB

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