widget.spec.ts 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { MessageLoop, Message } from '@lumino/messaging';
  4. import { Panel } from '@lumino/widgets';
  5. import { Widget } from '@lumino/widgets';
  6. import { simulate } from 'simulate-event';
  7. import { CodeEditor, CodeEditorWrapper } from '@jupyterlab/codeeditor';
  8. import { CodeMirrorEditor } from '@jupyterlab/codemirror';
  9. import {
  10. Completer,
  11. CompletionHandler,
  12. CompleterModel
  13. } from '@jupyterlab/completer';
  14. import { framePromise, sleep } from '@jupyterlab/testutils';
  15. const TEST_ITEM_CLASS = 'jp-TestItem';
  16. const ITEM_CLASS = 'jp-Completer-item';
  17. const ACTIVE_CLASS = 'jp-mod-active';
  18. function createEditorWidget(): CodeEditorWrapper {
  19. const model = new CodeEditor.Model();
  20. const factory = (options: CodeEditor.IOptions) => {
  21. return new CodeMirrorEditor(options);
  22. };
  23. return new CodeEditorWrapper({ factory, model });
  24. }
  25. class CustomRenderer extends Completer.Renderer {
  26. createCompletionItemNode(
  27. item: CompletionHandler.ICompletionItem,
  28. orderedTypes: string[]
  29. ): HTMLLIElement {
  30. let li = super.createCompletionItemNode!(item, orderedTypes);
  31. li.classList.add(TEST_ITEM_CLASS);
  32. return li;
  33. }
  34. createItemNode(
  35. item: Completer.IItem,
  36. typeMap: Completer.TypeMap,
  37. orderedTypes: string[]
  38. ): HTMLLIElement {
  39. const li = super.createItemNode(item, typeMap, orderedTypes);
  40. li.classList.add(TEST_ITEM_CLASS);
  41. return li;
  42. }
  43. }
  44. class LogWidget extends Completer {
  45. events: string[] = [];
  46. methods: string[] = [];
  47. dispose(): void {
  48. super.dispose();
  49. this.events.length = 0;
  50. }
  51. handleEvent(event: Event): void {
  52. this.events.push(event.type);
  53. super.handleEvent(event);
  54. }
  55. protected onUpdateRequest(msg: Message): void {
  56. super.onUpdateRequest(msg);
  57. this.methods.push('onUpdateRequest');
  58. }
  59. }
  60. describe('completer/widget', () => {
  61. describe('Completer', () => {
  62. describe('#constructor()', () => {
  63. it('should create a completer widget', () => {
  64. const widget = new Completer({ editor: null });
  65. expect(widget).toBeInstanceOf(Completer);
  66. expect(Array.from(widget.node.classList)).toEqual(
  67. expect.arrayContaining(['jp-Completer'])
  68. );
  69. });
  70. it('should accept options with a model', () => {
  71. const options: Completer.IOptions = {
  72. editor: null,
  73. model: new CompleterModel()
  74. };
  75. const widget = new Completer(options);
  76. expect(widget).toBeInstanceOf(Completer);
  77. expect(widget.model).toBe(options.model);
  78. });
  79. it('should accept options with a renderer', () => {
  80. const options: Completer.IOptions = {
  81. editor: null,
  82. model: new CompleterModel(),
  83. renderer: new CustomRenderer()
  84. };
  85. options.model!.setOptions(['foo', 'bar']);
  86. const widget = new Completer(options);
  87. expect(widget).toBeInstanceOf(Completer);
  88. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  89. const items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
  90. expect(items).toHaveLength(2);
  91. expect(Array.from(items[0].classList)).toEqual(
  92. expect.arrayContaining([TEST_ITEM_CLASS])
  93. );
  94. });
  95. it('should accept completion items with a renderer', () => {
  96. let options: Completer.IOptions = {
  97. editor: null,
  98. model: new CompleterModel(),
  99. renderer: new CustomRenderer()
  100. };
  101. options.model!.setCompletionItems!([
  102. { label: 'foo' },
  103. { label: 'bar' }
  104. ]);
  105. let widget = new Completer(options);
  106. expect(widget).toBeInstanceOf(Completer);
  107. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  108. let items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
  109. expect(items).toHaveLength(2);
  110. expect(Array.from(items[0].classList)).toEqual(
  111. expect.arrayContaining([TEST_ITEM_CLASS])
  112. );
  113. });
  114. });
  115. describe('#selected', () => {
  116. it('should emit a signal when an item is selected', () => {
  117. const anchor = createEditorWidget();
  118. const options: Completer.IOptions = {
  119. editor: anchor.editor,
  120. model: new CompleterModel()
  121. };
  122. let value = '';
  123. const listener = (sender: any, selected: string) => {
  124. value = selected;
  125. };
  126. options.model!.setOptions(['foo', 'bar']);
  127. Widget.attach(anchor, document.body);
  128. const widget = new Completer(options);
  129. widget.selected.connect(listener);
  130. Widget.attach(widget, document.body);
  131. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  132. expect(value).toBe('');
  133. widget.selectActive();
  134. expect(value).toBe('foo');
  135. widget.dispose();
  136. anchor.dispose();
  137. });
  138. describe('#selected with completion items', () => {
  139. it('should emit the insert text if it is present', () => {
  140. let anchor = createEditorWidget();
  141. let options: Completer.IOptions = {
  142. editor: anchor.editor,
  143. model: new CompleterModel()
  144. };
  145. let value = '';
  146. let listener = (sender: any, selected: string) => {
  147. value = selected;
  148. };
  149. options.model!.setCompletionItems!([
  150. { label: 'foo', insertText: 'bar' },
  151. { label: 'baz' }
  152. ]);
  153. Widget.attach(anchor, document.body);
  154. let widget = new Completer(options);
  155. widget.selected.connect(listener);
  156. Widget.attach(widget, document.body);
  157. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  158. expect(value).toBe('');
  159. widget.selectActive();
  160. expect(value).toBe('bar');
  161. widget.dispose();
  162. anchor.dispose();
  163. });
  164. it('should emit the label if insert text is not present', () => {
  165. let anchor = createEditorWidget();
  166. let options: Completer.IOptions = {
  167. editor: anchor.editor,
  168. model: new CompleterModel()
  169. };
  170. let value = '';
  171. let listener = (sender: any, selected: string) => {
  172. value = selected;
  173. };
  174. options.model!.setCompletionItems!([
  175. { label: 'foo' },
  176. { label: 'baz' }
  177. ]);
  178. Widget.attach(anchor, document.body);
  179. let widget = new Completer(options);
  180. widget.selected.connect(listener);
  181. Widget.attach(widget, document.body);
  182. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  183. expect(value).toBe('');
  184. widget.selectActive();
  185. expect(value).toBe('foo');
  186. widget.dispose();
  187. anchor.dispose();
  188. });
  189. });
  190. });
  191. describe('#visibilityChanged', () => {
  192. it('should emit a signal when completer visibility changes', async () => {
  193. const panel = new Panel();
  194. const code = createEditorWidget();
  195. const editor = code.editor;
  196. const model = new CompleterModel();
  197. let called = false;
  198. editor.model.value.text = 'a';
  199. panel.node.style.position = 'absolute';
  200. panel.node.style.top = '0px';
  201. panel.node.style.left = '0px';
  202. panel.node.style.height = '1000px';
  203. code.node.style.height = '900px';
  204. panel.addWidget(code);
  205. Widget.attach(panel, document.body);
  206. panel.node.scrollTop = 0;
  207. document.body.scrollTop = 0;
  208. const position = code.editor.getPositionAt(1)!;
  209. editor.setCursorPosition(position);
  210. const request: Completer.ITextState = {
  211. column: position.column,
  212. lineHeight: editor.lineHeight,
  213. charWidth: editor.charWidth,
  214. line: position.line,
  215. text: 'a'
  216. };
  217. model.original = request;
  218. model.cursor = { start: 0, end: 1 };
  219. model.setOptions(['abc', 'abd', 'abe', 'abi']);
  220. const widget = new Completer({ model, editor: code.editor });
  221. widget.hide();
  222. expect(called).toBe(false);
  223. widget.visibilityChanged.connect(() => {
  224. called = true;
  225. });
  226. Widget.attach(widget, document.body);
  227. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  228. await framePromise();
  229. expect(called).toBe(true);
  230. widget.dispose();
  231. code.dispose();
  232. panel.dispose();
  233. });
  234. it('should emit a signal when completion items completer visibility changes', async () => {
  235. let panel = new Panel();
  236. let code = createEditorWidget();
  237. let editor = code.editor;
  238. let model = new CompleterModel();
  239. let called = false;
  240. editor.model.value.text = 'a';
  241. panel.node.style.position = 'absolute';
  242. panel.node.style.top = '0px';
  243. panel.node.style.left = '0px';
  244. panel.node.style.height = '1000px';
  245. code.node.style.height = '900px';
  246. panel.addWidget(code);
  247. Widget.attach(panel, document.body);
  248. panel.node.scrollTop = 0;
  249. document.body.scrollTop = 0;
  250. let position = code.editor.getPositionAt(1)!;
  251. editor.setCursorPosition(position);
  252. let request: Completer.ITextState = {
  253. column: position.column,
  254. lineHeight: editor.lineHeight,
  255. charWidth: editor.charWidth,
  256. line: position.line,
  257. text: 'a'
  258. };
  259. model.original = request;
  260. model.cursor = { start: 0, end: 1 };
  261. model.setCompletionItems!([
  262. { label: 'abc' },
  263. { label: 'abd' },
  264. { label: 'abe' },
  265. { label: 'abi' }
  266. ]);
  267. let widget = new Completer({ model, editor: code.editor });
  268. widget.hide();
  269. expect(called).toBe(false);
  270. widget.visibilityChanged.connect(() => {
  271. called = true;
  272. });
  273. Widget.attach(widget, document.body);
  274. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  275. await framePromise();
  276. expect(called).toBe(true);
  277. widget.dispose();
  278. code.dispose();
  279. panel.dispose();
  280. });
  281. });
  282. describe('#model', () => {
  283. it('should default to null', () => {
  284. const widget = new Completer({ editor: null });
  285. expect(widget.model).toBeNull();
  286. });
  287. it('should be settable', () => {
  288. const widget = new Completer({ editor: null });
  289. expect(widget.model).toBeNull();
  290. widget.model = new CompleterModel();
  291. expect(widget.model).toBeInstanceOf(CompleterModel);
  292. });
  293. it('should be safe to set multiple times', () => {
  294. const model = new CompleterModel();
  295. const widget = new Completer({ editor: null });
  296. widget.model = model;
  297. widget.model = model;
  298. expect(widget.model).toBe(model);
  299. });
  300. it('should be safe to reset', () => {
  301. const model = new CompleterModel();
  302. const widget = new Completer({
  303. editor: null,
  304. model: new CompleterModel()
  305. });
  306. expect(widget.model).not.toBe(model);
  307. widget.model = model;
  308. expect(widget.model).toBe(model);
  309. });
  310. });
  311. describe('#editor', () => {
  312. it('should default to null', () => {
  313. const widget = new Completer({ editor: null });
  314. expect(widget.editor).toBeNull();
  315. });
  316. it('should be settable', () => {
  317. const anchor = createEditorWidget();
  318. const widget = new Completer({ editor: null });
  319. expect(widget.editor).toBeNull();
  320. widget.editor = anchor.editor;
  321. expect(widget.editor).toBeTruthy();
  322. });
  323. });
  324. describe('#dispose()', () => {
  325. it('should dispose of the resources held by the widget', () => {
  326. const widget = new Completer({ editor: null });
  327. widget.dispose();
  328. expect(widget.isDisposed).toBe(true);
  329. });
  330. it('should be safe to call multiple times', () => {
  331. const widget = new Completer({ editor: null });
  332. widget.dispose();
  333. widget.dispose();
  334. expect(widget.isDisposed).toBe(true);
  335. });
  336. });
  337. describe('#reset()', () => {
  338. it('should reset the completer widget', () => {
  339. const anchor = createEditorWidget();
  340. const model = new CompleterModel();
  341. const options: Completer.IOptions = {
  342. editor: anchor.editor,
  343. model
  344. };
  345. model.setOptions(['foo', 'bar'], { foo: 'instance', bar: 'function' });
  346. Widget.attach(anchor, document.body);
  347. const widget = new Completer(options);
  348. Widget.attach(widget, document.body);
  349. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  350. expect(widget.isHidden).toBe(false);
  351. expect(model.options).toBeTruthy();
  352. widget.reset();
  353. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  354. expect(widget.isHidden).toBe(true);
  355. expect(model.options().next()).toBeUndefined();
  356. widget.dispose();
  357. anchor.dispose();
  358. });
  359. it('should reset the completer widget and its completion items', () => {
  360. let anchor = createEditorWidget();
  361. let model = new CompleterModel();
  362. let options: Completer.IOptions = {
  363. editor: anchor.editor,
  364. model
  365. };
  366. model.setCompletionItems!([{ label: 'foo' }, { label: 'bar' }]);
  367. Widget.attach(anchor, document.body);
  368. let widget = new Completer(options);
  369. Widget.attach(widget, document.body);
  370. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  371. expect(widget.isHidden).toBe(false);
  372. expect(model.completionItems!()).toEqual([
  373. { label: 'foo' },
  374. { label: 'bar' }
  375. ]);
  376. widget.reset();
  377. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  378. expect(widget.isHidden).toBe(true);
  379. expect(model.completionItems!()).toEqual([]);
  380. widget.dispose();
  381. anchor.dispose();
  382. });
  383. });
  384. describe('#handleEvent()', () => {
  385. it('should handle document keydown, mousedown, and scroll events', () => {
  386. const anchor = createEditorWidget();
  387. const widget = new LogWidget({ editor: anchor.editor });
  388. Widget.attach(anchor, document.body);
  389. Widget.attach(widget, document.body);
  390. ['keydown', 'mousedown', 'scroll'].forEach(type => {
  391. simulate(document.body, type);
  392. expect(widget.events).toEqual(expect.arrayContaining([type]));
  393. });
  394. widget.dispose();
  395. anchor.dispose();
  396. });
  397. describe('keydown', () => {
  398. it('should reset if keydown is outside anchor', () => {
  399. const model = new CompleterModel();
  400. const anchor = createEditorWidget();
  401. const options: Completer.IOptions = {
  402. editor: anchor.editor,
  403. model
  404. };
  405. model.setOptions(['foo', 'bar'], {
  406. foo: 'instance',
  407. bar: 'function'
  408. });
  409. Widget.attach(anchor, document.body);
  410. const widget = new Completer(options);
  411. Widget.attach(widget, document.body);
  412. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  413. expect(widget.isHidden).toBe(false);
  414. expect(model.options).toBeTruthy();
  415. simulate(document.body, 'keydown', { keyCode: 70 }); // F
  416. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  417. expect(widget.isHidden).toBe(true);
  418. expect(model.options().next()).toBeUndefined();
  419. widget.dispose();
  420. anchor.dispose();
  421. });
  422. it('should reset completion items if keydown is outside anchor', () => {
  423. let model = new CompleterModel();
  424. let anchor = createEditorWidget();
  425. let options: Completer.IOptions = {
  426. editor: anchor.editor,
  427. model
  428. };
  429. model.setCompletionItems!([{ label: 'foo' }, { label: 'bar' }]);
  430. Widget.attach(anchor, document.body);
  431. let widget = new Completer(options);
  432. Widget.attach(widget, document.body);
  433. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  434. expect(widget.isHidden).toBe(false);
  435. expect(model.completionItems!()).toEqual([
  436. { label: 'foo' },
  437. { label: 'bar' }
  438. ]);
  439. simulate(document.body, 'keydown', { keyCode: 70 }); // F
  440. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  441. expect(widget.isHidden).toBe(true);
  442. expect(model.completionItems!()).toEqual([]);
  443. widget.dispose();
  444. anchor.dispose();
  445. });
  446. it('should select the item below and not progress past last', () => {
  447. const anchor = createEditorWidget();
  448. const model = new CompleterModel();
  449. const options: Completer.IOptions = {
  450. editor: anchor.editor,
  451. model
  452. };
  453. model.setOptions(['foo', 'bar', 'baz'], {
  454. foo: 'instance',
  455. bar: 'function'
  456. });
  457. Widget.attach(anchor, document.body);
  458. const widget = new Completer(options);
  459. const target = document.createElement('div');
  460. anchor.node.appendChild(target);
  461. Widget.attach(widget, document.body);
  462. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  463. const items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
  464. expect(Array.from(items[0].classList)).toEqual(
  465. expect.arrayContaining([ACTIVE_CLASS])
  466. );
  467. expect(Array.from(items[1].classList)).toEqual(
  468. expect.not.arrayContaining([ACTIVE_CLASS])
  469. );
  470. expect(Array.from(items[2].classList)).toEqual(
  471. expect.not.arrayContaining([ACTIVE_CLASS])
  472. );
  473. simulate(target, 'keydown', { keyCode: 40 }); // Down
  474. expect(Array.from(items[0].classList)).toEqual(
  475. expect.not.arrayContaining([ACTIVE_CLASS])
  476. );
  477. expect(Array.from(items[1].classList)).toEqual(
  478. expect.arrayContaining([ACTIVE_CLASS])
  479. );
  480. expect(Array.from(items[2].classList)).toEqual(
  481. expect.not.arrayContaining([ACTIVE_CLASS])
  482. );
  483. simulate(target, 'keydown', { keyCode: 40 }); // Down
  484. expect(Array.from(items[0].classList)).toEqual(
  485. expect.not.arrayContaining([ACTIVE_CLASS])
  486. );
  487. expect(Array.from(items[1].classList)).toEqual(
  488. expect.not.arrayContaining([ACTIVE_CLASS])
  489. );
  490. expect(Array.from(items[2].classList)).toEqual(
  491. expect.arrayContaining([ACTIVE_CLASS])
  492. );
  493. simulate(target, 'keydown', { keyCode: 40 }); // Down
  494. expect(Array.from(items[0].classList)).toEqual(
  495. expect.not.arrayContaining([ACTIVE_CLASS])
  496. );
  497. expect(Array.from(items[1].classList)).toEqual(
  498. expect.not.arrayContaining([ACTIVE_CLASS])
  499. );
  500. expect(Array.from(items[2].classList)).toEqual(
  501. expect.arrayContaining([ACTIVE_CLASS])
  502. );
  503. widget.dispose();
  504. anchor.dispose();
  505. });
  506. it('should select the completion item below and not progress past last', () => {
  507. let anchor = createEditorWidget();
  508. let model = new CompleterModel();
  509. let options: Completer.IOptions = {
  510. editor: anchor.editor,
  511. model
  512. };
  513. model.setCompletionItems!([
  514. { label: 'foo' },
  515. { label: 'bar' },
  516. { label: 'baz' }
  517. ]);
  518. Widget.attach(anchor, document.body);
  519. let widget = new Completer(options);
  520. let target = document.createElement('div');
  521. anchor.node.appendChild(target);
  522. Widget.attach(widget, document.body);
  523. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  524. let items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
  525. expect(Array.from(items[0].classList)).toEqual(
  526. expect.arrayContaining([ACTIVE_CLASS])
  527. );
  528. expect(Array.from(items[1].classList)).toEqual(
  529. expect.not.arrayContaining([ACTIVE_CLASS])
  530. );
  531. expect(Array.from(items[2].classList)).toEqual(
  532. expect.not.arrayContaining([ACTIVE_CLASS])
  533. );
  534. simulate(target, 'keydown', { keyCode: 40 }); // Down
  535. expect(Array.from(items[0].classList)).toEqual(
  536. expect.not.arrayContaining([ACTIVE_CLASS])
  537. );
  538. expect(Array.from(items[1].classList)).toEqual(
  539. expect.arrayContaining([ACTIVE_CLASS])
  540. );
  541. expect(Array.from(items[2].classList)).toEqual(
  542. expect.not.arrayContaining([ACTIVE_CLASS])
  543. );
  544. simulate(target, 'keydown', { keyCode: 40 }); // Down
  545. expect(Array.from(items[0].classList)).toEqual(
  546. expect.not.arrayContaining([ACTIVE_CLASS])
  547. );
  548. expect(Array.from(items[1].classList)).toEqual(
  549. expect.not.arrayContaining([ACTIVE_CLASS])
  550. );
  551. expect(Array.from(items[2].classList)).toEqual(
  552. expect.arrayContaining([ACTIVE_CLASS])
  553. );
  554. simulate(target, 'keydown', { keyCode: 40 }); // Down
  555. expect(Array.from(items[0].classList)).toEqual(
  556. expect.not.arrayContaining([ACTIVE_CLASS])
  557. );
  558. expect(Array.from(items[1].classList)).toEqual(
  559. expect.not.arrayContaining([ACTIVE_CLASS])
  560. );
  561. expect(Array.from(items[2].classList)).toEqual(
  562. expect.arrayContaining([ACTIVE_CLASS])
  563. );
  564. widget.dispose();
  565. anchor.dispose();
  566. });
  567. it('should select the item above and not progress beyond first', () => {
  568. const anchor = createEditorWidget();
  569. const model = new CompleterModel();
  570. const options: Completer.IOptions = {
  571. editor: anchor.editor,
  572. model
  573. };
  574. model.setOptions(['foo', 'bar', 'baz'], {
  575. foo: 'instance',
  576. bar: 'function'
  577. });
  578. Widget.attach(anchor, document.body);
  579. const widget = new Completer(options);
  580. Widget.attach(widget, document.body);
  581. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  582. const items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
  583. expect(Array.from(items[0].classList)).toEqual(
  584. expect.arrayContaining([ACTIVE_CLASS])
  585. );
  586. expect(Array.from(items[1].classList)).toEqual(
  587. expect.not.arrayContaining([ACTIVE_CLASS])
  588. );
  589. expect(Array.from(items[2].classList)).toEqual(
  590. expect.not.arrayContaining([ACTIVE_CLASS])
  591. );
  592. simulate(anchor.node, 'keydown', { keyCode: 40 }); // Down
  593. expect(Array.from(items[0].classList)).toEqual(
  594. expect.not.arrayContaining([ACTIVE_CLASS])
  595. );
  596. expect(Array.from(items[1].classList)).toEqual(
  597. expect.arrayContaining([ACTIVE_CLASS])
  598. );
  599. expect(Array.from(items[2].classList)).toEqual(
  600. expect.not.arrayContaining([ACTIVE_CLASS])
  601. );
  602. simulate(anchor.node, 'keydown', { keyCode: 40 }); // Down
  603. expect(Array.from(items[0].classList)).toEqual(
  604. expect.not.arrayContaining([ACTIVE_CLASS])
  605. );
  606. expect(Array.from(items[1].classList)).toEqual(
  607. expect.not.arrayContaining([ACTIVE_CLASS])
  608. );
  609. expect(Array.from(items[2].classList)).toEqual(
  610. expect.arrayContaining([ACTIVE_CLASS])
  611. );
  612. simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
  613. expect(Array.from(items[0].classList)).toEqual(
  614. expect.not.arrayContaining([ACTIVE_CLASS])
  615. );
  616. expect(Array.from(items[1].classList)).toEqual(
  617. expect.arrayContaining([ACTIVE_CLASS])
  618. );
  619. expect(Array.from(items[2].classList)).toEqual(
  620. expect.not.arrayContaining([ACTIVE_CLASS])
  621. );
  622. simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
  623. expect(Array.from(items[0].classList)).toEqual(
  624. expect.arrayContaining([ACTIVE_CLASS])
  625. );
  626. expect(Array.from(items[1].classList)).toEqual(
  627. expect.not.arrayContaining([ACTIVE_CLASS])
  628. );
  629. expect(Array.from(items[2].classList)).toEqual(
  630. expect.not.arrayContaining([ACTIVE_CLASS])
  631. );
  632. simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
  633. expect(Array.from(items[0].classList)).toEqual(
  634. expect.arrayContaining([ACTIVE_CLASS])
  635. );
  636. expect(Array.from(items[1].classList)).toEqual(
  637. expect.not.arrayContaining([ACTIVE_CLASS])
  638. );
  639. expect(Array.from(items[2].classList)).toEqual(
  640. expect.not.arrayContaining([ACTIVE_CLASS])
  641. );
  642. widget.dispose();
  643. anchor.dispose();
  644. });
  645. it('should select the completion item above and not progress beyond first', () => {
  646. let anchor = createEditorWidget();
  647. let model = new CompleterModel();
  648. let options: Completer.IOptions = {
  649. editor: anchor.editor,
  650. model
  651. };
  652. model.setCompletionItems!([
  653. { label: 'foo' },
  654. { label: 'bar' },
  655. { label: 'baz' }
  656. ]);
  657. Widget.attach(anchor, document.body);
  658. let widget = new Completer(options);
  659. Widget.attach(widget, document.body);
  660. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  661. let items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
  662. expect(Array.from(items[0].classList)).toEqual(
  663. expect.arrayContaining([ACTIVE_CLASS])
  664. );
  665. expect(Array.from(items[1].classList)).toEqual(
  666. expect.not.arrayContaining([ACTIVE_CLASS])
  667. );
  668. expect(Array.from(items[2].classList)).toEqual(
  669. expect.not.arrayContaining([ACTIVE_CLASS])
  670. );
  671. simulate(anchor.node, 'keydown', { keyCode: 40 }); // Down
  672. expect(Array.from(items[0].classList)).toEqual(
  673. expect.not.arrayContaining([ACTIVE_CLASS])
  674. );
  675. expect(Array.from(items[1].classList)).toEqual(
  676. expect.arrayContaining([ACTIVE_CLASS])
  677. );
  678. expect(Array.from(items[2].classList)).toEqual(
  679. expect.not.arrayContaining([ACTIVE_CLASS])
  680. );
  681. simulate(anchor.node, 'keydown', { keyCode: 40 }); // Down
  682. expect(Array.from(items[0].classList)).toEqual(
  683. expect.not.arrayContaining([ACTIVE_CLASS])
  684. );
  685. expect(Array.from(items[1].classList)).toEqual(
  686. expect.not.arrayContaining([ACTIVE_CLASS])
  687. );
  688. expect(Array.from(items[2].classList)).toEqual(
  689. expect.arrayContaining([ACTIVE_CLASS])
  690. );
  691. simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
  692. expect(Array.from(items[0].classList)).toEqual(
  693. expect.not.arrayContaining([ACTIVE_CLASS])
  694. );
  695. expect(Array.from(items[1].classList)).toEqual(
  696. expect.arrayContaining([ACTIVE_CLASS])
  697. );
  698. expect(Array.from(items[2].classList)).toEqual(
  699. expect.not.arrayContaining([ACTIVE_CLASS])
  700. );
  701. simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
  702. expect(Array.from(items[0].classList)).toEqual(
  703. expect.arrayContaining([ACTIVE_CLASS])
  704. );
  705. expect(Array.from(items[1].classList)).toEqual(
  706. expect.not.arrayContaining([ACTIVE_CLASS])
  707. );
  708. expect(Array.from(items[2].classList)).toEqual(
  709. expect.not.arrayContaining([ACTIVE_CLASS])
  710. );
  711. simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
  712. expect(Array.from(items[0].classList)).toEqual(
  713. expect.arrayContaining([ACTIVE_CLASS])
  714. );
  715. expect(Array.from(items[1].classList)).toEqual(
  716. expect.not.arrayContaining([ACTIVE_CLASS])
  717. );
  718. expect(Array.from(items[2].classList)).toEqual(
  719. expect.not.arrayContaining([ACTIVE_CLASS])
  720. );
  721. widget.dispose();
  722. anchor.dispose();
  723. });
  724. it('should mark common subset on start and complete that subset on tab', async () => {
  725. const anchor = createEditorWidget();
  726. const model = new CompleterModel();
  727. const options: Completer.IOptions = {
  728. editor: anchor.editor,
  729. model
  730. };
  731. let value = '';
  732. const listener = (sender: any, selected: string) => {
  733. value = selected;
  734. };
  735. model.setOptions(['fo', 'foo', 'foo', 'fooo'], {
  736. foo: 'instance',
  737. bar: 'function'
  738. });
  739. Widget.attach(anchor, document.body);
  740. const widget = new Completer(options);
  741. widget.selected.connect(listener);
  742. Widget.attach(widget, document.body);
  743. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  744. await framePromise();
  745. const marked = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`);
  746. expect(Object.keys(value)).toHaveLength(0);
  747. expect(marked).toHaveLength(4);
  748. expect(marked[0].textContent).toBe('fo');
  749. expect(marked[1].textContent).toBe('fo');
  750. expect(marked[2].textContent).toBe('fo');
  751. expect(marked[3].textContent).toBe('fo');
  752. simulate(anchor.node, 'keydown', { keyCode: 9 }); // Tab key
  753. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  754. expect(value).toBe('fo');
  755. widget.dispose();
  756. anchor.dispose();
  757. });
  758. it('should mark common subset of completion items on start and complete that subset on tab', async () => {
  759. let anchor = createEditorWidget();
  760. let model = new CompleterModel();
  761. let options: Completer.IOptions = {
  762. editor: anchor.editor,
  763. model
  764. };
  765. let value = '';
  766. let listener = (sender: any, selected: string) => {
  767. value = selected;
  768. };
  769. model.setCompletionItems!([
  770. { label: 'fo' },
  771. { label: 'foo' },
  772. { label: 'foo' },
  773. { label: 'fooo' }
  774. ]);
  775. Widget.attach(anchor, document.body);
  776. let widget = new Completer(options);
  777. widget.selected.connect(listener);
  778. Widget.attach(widget, document.body);
  779. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  780. await framePromise();
  781. let marked = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`);
  782. expect(value).toHaveLength(0);
  783. expect(marked).toHaveLength(4);
  784. expect(marked[0].textContent).toBe('fo');
  785. expect(marked[1].textContent).toBe('fo');
  786. expect(marked[2].textContent).toBe('fo');
  787. expect(marked[3].textContent).toBe('fo');
  788. simulate(anchor.node, 'keydown', { keyCode: 9 }); // Tab key
  789. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  790. expect(value).toBe('fo');
  791. widget.dispose();
  792. anchor.dispose();
  793. });
  794. });
  795. it('should insert single completion item on tab', async () => {
  796. const anchor = createEditorWidget();
  797. const model = new CompleterModel();
  798. const options: Completer.IOptions = {
  799. editor: anchor.editor,
  800. model
  801. };
  802. let value = '';
  803. const listener = (sender: any, selected: string) => {
  804. value = selected;
  805. };
  806. model.setCompletionItems!([{ label: 'foo' }]);
  807. Widget.attach(anchor, document.body);
  808. const widget = new Completer(options);
  809. widget.selected.connect(listener);
  810. Widget.attach(widget, document.body);
  811. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  812. simulate(anchor.node, 'keydown', { keyCode: 9 }); // Tab key
  813. expect(value).toBe('foo');
  814. widget.dispose();
  815. anchor.dispose();
  816. });
  817. it('should insert single completion item insertText on tab', async () => {
  818. const anchor = createEditorWidget();
  819. const model = new CompleterModel();
  820. const options: Completer.IOptions = {
  821. editor: anchor.editor,
  822. model
  823. };
  824. let value = '';
  825. const listener = (sender: any, selected: string) => {
  826. value = selected;
  827. };
  828. model.setCompletionItems!([{ label: 'foo', insertText: 'bar' }]);
  829. Widget.attach(anchor, document.body);
  830. const widget = new Completer(options);
  831. widget.selected.connect(listener);
  832. Widget.attach(widget, document.body);
  833. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  834. simulate(anchor.node, 'keydown', { keyCode: 9 }); // Tab key
  835. expect(value).toBe('bar');
  836. widget.dispose();
  837. anchor.dispose();
  838. });
  839. describe('mousedown', () => {
  840. it('should trigger a selected signal on mouse down', () => {
  841. const anchor = createEditorWidget();
  842. const model = new CompleterModel();
  843. const options: Completer.IOptions = {
  844. editor: anchor.editor,
  845. model
  846. };
  847. let value = '';
  848. const listener = (sender: any, selected: string) => {
  849. value = selected;
  850. };
  851. model.setOptions(['foo', 'bar', 'baz'], {
  852. foo: 'instance',
  853. bar: 'function'
  854. });
  855. model.query = 'b';
  856. Widget.attach(anchor, document.body);
  857. const widget = new Completer(options);
  858. widget.selected.connect(listener);
  859. Widget.attach(widget, document.body);
  860. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  861. const item = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`)[1];
  862. simulate(anchor.node, 'keydown', { keyCode: 9 }); // Tab key
  863. expect(model.query).toBe('ba');
  864. simulate(item, 'mousedown');
  865. expect(value).toBe('baz');
  866. widget.dispose();
  867. anchor.dispose();
  868. });
  869. it('should trigger a selected signal on mouse down of completion item', () => {
  870. let anchor = createEditorWidget();
  871. let model = new CompleterModel();
  872. let options: Completer.IOptions = {
  873. editor: anchor.editor,
  874. model
  875. };
  876. let value = '';
  877. let listener = (sender: any, selected: string) => {
  878. value = selected;
  879. };
  880. model.setCompletionItems!([
  881. { label: 'foo' },
  882. { label: 'bar' },
  883. { label: 'baz' }
  884. ]);
  885. model.query = 'b';
  886. Widget.attach(anchor, document.body);
  887. let widget = new Completer(options);
  888. widget.selected.connect(listener);
  889. Widget.attach(widget, document.body);
  890. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  891. let item = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`)[1];
  892. simulate(anchor.node, 'keydown', { keyCode: 9 }); // Tab key
  893. expect(model.query).toBe('ba');
  894. simulate(item, 'mousedown');
  895. expect(value).toBe('baz');
  896. widget.dispose();
  897. anchor.dispose();
  898. });
  899. it('should ignore nonstandard mouse clicks (e.g., right click)', () => {
  900. const anchor = createEditorWidget();
  901. const model = new CompleterModel();
  902. const options: Completer.IOptions = {
  903. editor: anchor.editor,
  904. model
  905. };
  906. let value = '';
  907. const listener = (sender: any, selected: string) => {
  908. value = selected;
  909. };
  910. model.setOptions(['foo', 'bar']);
  911. Widget.attach(anchor, document.body);
  912. const widget = new Completer(options);
  913. widget.selected.connect(listener);
  914. Widget.attach(widget, document.body);
  915. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  916. expect(value).toBe('');
  917. simulate(widget.node, 'mousedown', { button: 1 });
  918. expect(value).toBe('');
  919. widget.dispose();
  920. anchor.dispose();
  921. });
  922. it('should ignore nonstandard mouse clicks (e.g., right click) on completion item', () => {
  923. let anchor = createEditorWidget();
  924. let model = new CompleterModel();
  925. let options: Completer.IOptions = {
  926. editor: anchor.editor,
  927. model
  928. };
  929. let value = '';
  930. let listener = (sender: any, selected: string) => {
  931. value = selected;
  932. };
  933. model.setCompletionItems!([{ label: 'foo' }, { label: 'bar' }]);
  934. Widget.attach(anchor, document.body);
  935. let widget = new Completer(options);
  936. widget.selected.connect(listener);
  937. Widget.attach(widget, document.body);
  938. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  939. expect(value).toBe('');
  940. simulate(widget.node, 'mousedown', { button: 1 });
  941. expect(value).toBe('');
  942. widget.dispose();
  943. anchor.dispose();
  944. });
  945. it('should ignore a mouse down that misses an item', () => {
  946. const anchor = createEditorWidget();
  947. const model = new CompleterModel();
  948. const options: Completer.IOptions = {
  949. editor: anchor.editor,
  950. model
  951. };
  952. let value = '';
  953. const listener = (sender: any, selected: string) => {
  954. value = selected;
  955. };
  956. model.setOptions(['foo', 'bar']);
  957. Widget.attach(anchor, document.body);
  958. const widget = new Completer(options);
  959. widget.selected.connect(listener);
  960. Widget.attach(widget, document.body);
  961. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  962. expect(value).toBe('');
  963. simulate(widget.node, 'mousedown');
  964. expect(value).toBe('');
  965. widget.dispose();
  966. anchor.dispose();
  967. });
  968. it('should ignore a mouse down that misses a completion item', () => {
  969. let anchor = createEditorWidget();
  970. let model = new CompleterModel();
  971. let options: Completer.IOptions = {
  972. editor: anchor.editor,
  973. model
  974. };
  975. let value = '';
  976. let listener = (sender: any, selected: string) => {
  977. value = selected;
  978. };
  979. model.setCompletionItems!([{ label: 'foo' }, { label: 'bar' }]);
  980. Widget.attach(anchor, document.body);
  981. let widget = new Completer(options);
  982. widget.selected.connect(listener);
  983. Widget.attach(widget, document.body);
  984. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  985. expect(value).toBe('');
  986. simulate(widget.node, 'mousedown');
  987. expect(value).toBe('');
  988. widget.dispose();
  989. anchor.dispose();
  990. });
  991. it('should hide widget if mouse down misses it', () => {
  992. const anchor = createEditorWidget();
  993. const model = new CompleterModel();
  994. const options: Completer.IOptions = {
  995. editor: anchor.editor,
  996. model
  997. };
  998. const listener = (sender: any, selected: string) => {
  999. // no op
  1000. };
  1001. model.setOptions(['foo', 'bar']);
  1002. Widget.attach(anchor, document.body);
  1003. const widget = new Completer(options);
  1004. widget.selected.connect(listener);
  1005. Widget.attach(widget, document.body);
  1006. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  1007. expect(widget.isHidden).toBe(false);
  1008. simulate(anchor.node, 'mousedown');
  1009. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  1010. expect(widget.isHidden).toBe(true);
  1011. widget.dispose();
  1012. anchor.dispose();
  1013. });
  1014. it('should hide completion items widget if mouse down misses it', () => {
  1015. let anchor = createEditorWidget();
  1016. let model = new CompleterModel();
  1017. let options: Completer.IOptions = {
  1018. editor: anchor.editor,
  1019. model
  1020. };
  1021. let listener = (sender: any, selected: string) => {
  1022. // no op
  1023. };
  1024. model.setCompletionItems!([{ label: 'foo' }, { label: 'bar' }]);
  1025. Widget.attach(anchor, document.body);
  1026. let widget = new Completer(options);
  1027. widget.selected.connect(listener);
  1028. Widget.attach(widget, document.body);
  1029. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  1030. expect(widget.isHidden).toBe(false);
  1031. simulate(anchor.node, 'mousedown');
  1032. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  1033. expect(widget.isHidden).toBe(true);
  1034. widget.dispose();
  1035. anchor.dispose();
  1036. });
  1037. });
  1038. describe('scroll', () => {
  1039. it.skip('should position itself according to the anchor', async () => {
  1040. const panel = new Panel();
  1041. const code = createEditorWidget();
  1042. const editor = code.editor;
  1043. const model = new CompleterModel();
  1044. const text = '\n\n\n\n\n\na';
  1045. code.node.style.height = '5000px';
  1046. code.node.style.width = '400px';
  1047. code.node.style.background = 'yellow';
  1048. editor.model.value.text = text;
  1049. panel.node.style.background = 'red';
  1050. panel.node.style.height = '2000px';
  1051. panel.node.style.width = '500px';
  1052. panel.node.style.maxHeight = '500px';
  1053. panel.node.style.overflow = 'auto';
  1054. panel.node.style.position = 'absolute';
  1055. panel.node.style.top = '0px';
  1056. panel.node.style.left = '0px';
  1057. panel.node.scrollTop = 10;
  1058. panel.addWidget(code);
  1059. Widget.attach(panel, document.body);
  1060. editor.refresh();
  1061. const position = code.editor.getPositionAt(text.length)!;
  1062. const coords = code.editor.getCoordinateForPosition(position);
  1063. editor.setCursorPosition(position);
  1064. const request: Completer.ITextState = {
  1065. column: position.column,
  1066. lineHeight: editor.lineHeight,
  1067. charWidth: editor.charWidth,
  1068. line: position.line,
  1069. text: 'a'
  1070. };
  1071. model.original = request;
  1072. model.cursor = { start: text.length - 1, end: text.length };
  1073. model.setOptions(['abc', 'abd', 'abe', 'abi']);
  1074. const widget = new Completer({ model, editor: code.editor });
  1075. Widget.attach(widget, document.body);
  1076. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  1077. simulate(document.body, 'scroll');
  1078. // Because the scroll handling is asynchronous, this test uses a large
  1079. // timeout (500ms) to guarantee the scroll handling has finished.
  1080. await sleep(500);
  1081. const top = parseInt(window.getComputedStyle(widget.node).top, 10);
  1082. const bottom = Math.floor(coords.bottom);
  1083. expect(top + panel.node.scrollTop).toBe(bottom);
  1084. widget.dispose();
  1085. code.dispose();
  1086. panel.dispose();
  1087. });
  1088. });
  1089. });
  1090. describe('#onUpdateRequest()', () => {
  1091. it('should emit a selection if there is only one match', () => {
  1092. const anchor = createEditorWidget();
  1093. const model = new CompleterModel();
  1094. const coords = { left: 0, right: 0, top: 100, bottom: 120 };
  1095. const request: Completer.ITextState = {
  1096. column: 0,
  1097. lineHeight: 0,
  1098. charWidth: 0,
  1099. line: 0,
  1100. coords: coords as CodeEditor.ICoordinate,
  1101. text: 'f'
  1102. };
  1103. let value = '';
  1104. const options: Completer.IOptions = {
  1105. editor: anchor.editor,
  1106. model
  1107. };
  1108. const listener = (sender: any, selected: string) => {
  1109. value = selected;
  1110. };
  1111. Widget.attach(anchor, document.body);
  1112. model.original = request;
  1113. model.setOptions(['foo']);
  1114. const widget = new Completer(options);
  1115. widget.selected.connect(listener);
  1116. Widget.attach(widget, document.body);
  1117. expect(value).toBe('');
  1118. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  1119. expect(value).toBe('foo');
  1120. widget.dispose();
  1121. anchor.dispose();
  1122. });
  1123. it('should do nothing if a model does not exist', () => {
  1124. const widget = new LogWidget({ editor: null });
  1125. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  1126. expect(widget.methods).toEqual(
  1127. expect.arrayContaining(['onUpdateRequest'])
  1128. );
  1129. });
  1130. it('should un-hide widget if multiple options are available', () => {
  1131. const anchor = createEditorWidget();
  1132. const model = new CompleterModel();
  1133. const coords = { left: 0, right: 0, top: 100, bottom: 120 };
  1134. const request: Completer.ITextState = {
  1135. column: 0,
  1136. lineHeight: 0,
  1137. charWidth: 0,
  1138. line: 0,
  1139. coords: coords as CodeEditor.ICoordinate,
  1140. text: 'f'
  1141. };
  1142. const options: Completer.IOptions = {
  1143. editor: anchor.editor,
  1144. model
  1145. };
  1146. Widget.attach(anchor, document.body);
  1147. model.original = request;
  1148. model.setOptions(['foo', 'bar', 'baz']);
  1149. const widget = new Completer(options);
  1150. widget.hide();
  1151. expect(widget.isHidden).toBe(true);
  1152. Widget.attach(widget, document.body);
  1153. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  1154. expect(widget.isVisible).toBe(true);
  1155. widget.dispose();
  1156. anchor.dispose();
  1157. });
  1158. it('should un-hide widget if multiple completion items are available', () => {
  1159. let anchor = createEditorWidget();
  1160. let model = new CompleterModel();
  1161. let coords = { left: 0, right: 0, top: 100, bottom: 120 };
  1162. let request: Completer.ITextState = {
  1163. column: 0,
  1164. lineHeight: 0,
  1165. charWidth: 0,
  1166. line: 0,
  1167. coords: coords as CodeEditor.ICoordinate,
  1168. text: 'f'
  1169. };
  1170. let options: Completer.IOptions = {
  1171. editor: anchor.editor,
  1172. model
  1173. };
  1174. Widget.attach(anchor, document.body);
  1175. model.original = request;
  1176. model.setCompletionItems!([
  1177. { label: 'foo' },
  1178. { label: 'bar' },
  1179. { label: 'baz' }
  1180. ]);
  1181. let widget = new Completer(options);
  1182. widget.hide();
  1183. expect(widget.isHidden).toBe(true);
  1184. Widget.attach(widget, document.body);
  1185. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  1186. expect(widget.isVisible).toBe(true);
  1187. widget.dispose();
  1188. anchor.dispose();
  1189. });
  1190. });
  1191. });
  1192. });