widget.spec.ts 46 KB

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