widget.spec.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { expect } from 'chai';
  4. import { MessageLoop, Message } from '@phosphor/messaging';
  5. import { Panel } from '@phosphor/widgets';
  6. import { Widget } from '@phosphor/widgets';
  7. import { simulate } from 'simulate-event';
  8. import { CodeEditor, CodeEditorWrapper } from '@jupyterlab/codeeditor';
  9. import { CodeMirrorEditor } from '@jupyterlab/codemirror';
  10. import { Completer, CompleterModel } from '@jupyterlab/completer/src';
  11. import { framePromise, sleep } from '@jupyterlab/testutils';
  12. const TEST_ITEM_CLASS = 'jp-TestItem';
  13. const ITEM_CLASS = 'jp-Completer-item';
  14. const ACTIVE_CLASS = 'jp-mod-active';
  15. function createEditorWidget(): CodeEditorWrapper {
  16. let model = new CodeEditor.Model();
  17. let factory = (options: CodeEditor.IOptions) => {
  18. return new CodeMirrorEditor(options);
  19. };
  20. return new CodeEditorWrapper({ factory, model });
  21. }
  22. class CustomRenderer extends Completer.Renderer {
  23. createItemNode(
  24. item: Completer.IItem,
  25. typeMap: Completer.TypeMap,
  26. orderedTypes: string[]
  27. ): HTMLLIElement {
  28. let li = super.createItemNode(item, typeMap, orderedTypes);
  29. li.classList.add(TEST_ITEM_CLASS);
  30. return li;
  31. }
  32. }
  33. class LogWidget extends Completer {
  34. events: string[] = [];
  35. methods: string[] = [];
  36. dispose(): void {
  37. super.dispose();
  38. this.events.length = 0;
  39. }
  40. handleEvent(event: Event): void {
  41. this.events.push(event.type);
  42. super.handleEvent(event);
  43. }
  44. protected onUpdateRequest(msg: Message): void {
  45. super.onUpdateRequest(msg);
  46. this.methods.push('onUpdateRequest');
  47. }
  48. }
  49. describe('completer/widget', () => {
  50. describe('Completer', () => {
  51. describe('#constructor()', () => {
  52. it('should create a completer widget', () => {
  53. let widget = new Completer({ editor: null });
  54. expect(widget).to.be.an.instanceof(Completer);
  55. expect(Array.from(widget.node.classList)).to.contain('jp-Completer');
  56. });
  57. it('should accept options with a model', () => {
  58. let options: Completer.IOptions = {
  59. editor: null,
  60. model: new CompleterModel()
  61. };
  62. let widget = new Completer(options);
  63. expect(widget).to.be.an.instanceof(Completer);
  64. expect(widget.model).to.equal(options.model);
  65. });
  66. it('should accept options with a renderer', () => {
  67. let options: Completer.IOptions = {
  68. editor: null,
  69. model: new CompleterModel(),
  70. renderer: new CustomRenderer()
  71. };
  72. options.model.setOptions(['foo', 'bar']);
  73. let widget = new Completer(options);
  74. expect(widget).to.be.an.instanceof(Completer);
  75. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  76. let items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
  77. expect(items).to.have.length(2);
  78. expect(Array.from(items[0].classList)).to.contain(TEST_ITEM_CLASS);
  79. });
  80. });
  81. describe('#selected', () => {
  82. it('should emit a signal when an item is selected', () => {
  83. let anchor = createEditorWidget();
  84. let options: Completer.IOptions = {
  85. editor: anchor.editor,
  86. model: new CompleterModel()
  87. };
  88. let value = '';
  89. let listener = (sender: any, selected: string) => {
  90. value = selected;
  91. };
  92. options.model.setOptions(['foo', 'bar']);
  93. Widget.attach(anchor, document.body);
  94. let widget = new Completer(options);
  95. widget.selected.connect(listener);
  96. Widget.attach(widget, document.body);
  97. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  98. expect(value).to.equal('');
  99. widget.selectActive();
  100. expect(value).to.equal('foo');
  101. widget.dispose();
  102. anchor.dispose();
  103. });
  104. });
  105. describe('#visibilityChanged', () => {
  106. it('should emit a signal when completer visibility changes', async () => {
  107. let panel = new Panel();
  108. let code = createEditorWidget();
  109. let editor = code.editor;
  110. let model = new CompleterModel();
  111. let called = false;
  112. editor.model.value.text = 'a';
  113. panel.node.style.position = 'absolute';
  114. panel.node.style.top = '0px';
  115. panel.node.style.left = '0px';
  116. panel.node.style.height = '1000px';
  117. code.node.style.height = '900px';
  118. panel.addWidget(code);
  119. Widget.attach(panel, document.body);
  120. panel.node.scrollTop = 0;
  121. document.body.scrollTop = 0;
  122. let position = code.editor.getPositionAt(1);
  123. editor.setCursorPosition(position);
  124. let request: Completer.ITextState = {
  125. column: position.column,
  126. lineHeight: editor.lineHeight,
  127. charWidth: editor.charWidth,
  128. line: position.line,
  129. text: 'a'
  130. };
  131. model.original = request;
  132. model.cursor = { start: 0, end: 1 };
  133. model.setOptions(['abc', 'abd', 'abe', 'abi']);
  134. let widget = new Completer({ model, editor: code.editor });
  135. widget.hide();
  136. expect(called).to.equal(false);
  137. widget.visibilityChanged.connect(() => {
  138. called = true;
  139. });
  140. Widget.attach(widget, document.body);
  141. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  142. await framePromise();
  143. expect(called).to.equal(true);
  144. widget.dispose();
  145. code.dispose();
  146. panel.dispose();
  147. });
  148. });
  149. describe('#model', () => {
  150. it('should default to null', () => {
  151. let widget = new Completer({ editor: null });
  152. expect(widget.model).to.be.null;
  153. });
  154. it('should be settable', () => {
  155. let widget = new Completer({ editor: null });
  156. expect(widget.model).to.be.null;
  157. widget.model = new CompleterModel();
  158. expect(widget.model).to.be.an.instanceof(CompleterModel);
  159. });
  160. it('should be safe to set multiple times', () => {
  161. let model = new CompleterModel();
  162. let widget = new Completer({ editor: null });
  163. widget.model = model;
  164. widget.model = model;
  165. expect(widget.model).to.equal(model);
  166. });
  167. it('should be safe to reset', () => {
  168. let model = new CompleterModel();
  169. let widget = new Completer({
  170. editor: null,
  171. model: new CompleterModel()
  172. });
  173. expect(widget.model).not.to.equal(model);
  174. widget.model = model;
  175. expect(widget.model).to.equal(model);
  176. });
  177. });
  178. describe('#editor', () => {
  179. it('should default to null', () => {
  180. let widget = new Completer({ editor: null });
  181. expect(widget.editor).to.be.null;
  182. });
  183. it('should be settable', () => {
  184. let anchor = createEditorWidget();
  185. let widget = new Completer({ editor: null });
  186. expect(widget.editor).to.be.null;
  187. widget.editor = anchor.editor;
  188. expect(widget.editor).to.be.ok;
  189. });
  190. });
  191. describe('#dispose()', () => {
  192. it('should dispose of the resources held by the widget', () => {
  193. let widget = new Completer({ editor: null });
  194. widget.dispose();
  195. expect(widget.isDisposed).to.equal(true);
  196. });
  197. it('should be safe to call multiple times', () => {
  198. let widget = new Completer({ editor: null });
  199. widget.dispose();
  200. widget.dispose();
  201. expect(widget.isDisposed).to.equal(true);
  202. });
  203. });
  204. describe('#reset()', () => {
  205. it('should reset the completer widget', () => {
  206. let anchor = createEditorWidget();
  207. let model = new CompleterModel();
  208. let options: Completer.IOptions = {
  209. editor: anchor.editor,
  210. model
  211. };
  212. model.setOptions(['foo', 'bar'], { foo: 'instance', bar: 'function' });
  213. Widget.attach(anchor, document.body);
  214. let widget = new Completer(options);
  215. Widget.attach(widget, document.body);
  216. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  217. expect(widget.isHidden).to.equal(false);
  218. expect(model.options).to.be.ok;
  219. widget.reset();
  220. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  221. expect(widget.isHidden).to.equal(true);
  222. expect(model.options().next()).to.be.undefined;
  223. widget.dispose();
  224. anchor.dispose();
  225. });
  226. });
  227. describe('#handleEvent()', () => {
  228. it('should handle document keydown, mousedown, and scroll events', () => {
  229. let anchor = createEditorWidget();
  230. let widget = new LogWidget({ editor: anchor.editor });
  231. Widget.attach(anchor, document.body);
  232. Widget.attach(widget, document.body);
  233. ['keydown', 'mousedown', 'scroll'].forEach(type => {
  234. simulate(document.body, type);
  235. expect(widget.events).to.contain(type);
  236. });
  237. widget.dispose();
  238. anchor.dispose();
  239. });
  240. describe('keydown', () => {
  241. it('should reset if keydown is outside anchor', () => {
  242. let model = new CompleterModel();
  243. let anchor = createEditorWidget();
  244. let options: Completer.IOptions = {
  245. editor: anchor.editor,
  246. model
  247. };
  248. model.setOptions(['foo', 'bar'], {
  249. foo: 'instance',
  250. bar: 'function'
  251. });
  252. Widget.attach(anchor, document.body);
  253. let widget = new Completer(options);
  254. Widget.attach(widget, document.body);
  255. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  256. expect(widget.isHidden).to.equal(false);
  257. expect(model.options).to.be.ok;
  258. simulate(document.body, 'keydown', { keyCode: 70 }); // F
  259. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  260. expect(widget.isHidden).to.equal(true);
  261. expect(model.options().next()).to.be.undefined;
  262. widget.dispose();
  263. anchor.dispose();
  264. });
  265. it('should select the item below and not progress past last', () => {
  266. let anchor = createEditorWidget();
  267. let model = new CompleterModel();
  268. let options: Completer.IOptions = {
  269. editor: anchor.editor,
  270. model
  271. };
  272. model.setOptions(['foo', 'bar', 'baz'], {
  273. foo: 'instance',
  274. bar: 'function'
  275. });
  276. Widget.attach(anchor, document.body);
  277. let widget = new Completer(options);
  278. let target = document.createElement('div');
  279. anchor.node.appendChild(target);
  280. Widget.attach(widget, document.body);
  281. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  282. let items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
  283. expect(Array.from(items[0].classList)).to.contain(ACTIVE_CLASS);
  284. expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
  285. expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
  286. simulate(target, 'keydown', { keyCode: 40 }); // Down
  287. expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
  288. expect(Array.from(items[1].classList)).to.contain(ACTIVE_CLASS);
  289. expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
  290. simulate(target, 'keydown', { keyCode: 40 }); // Down
  291. expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
  292. expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
  293. expect(Array.from(items[2].classList)).to.contain(ACTIVE_CLASS);
  294. simulate(target, 'keydown', { keyCode: 40 }); // Down
  295. expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
  296. expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
  297. expect(Array.from(items[2].classList)).to.contain(ACTIVE_CLASS);
  298. widget.dispose();
  299. anchor.dispose();
  300. });
  301. it('should select the item above and not progress beyond first', () => {
  302. let anchor = createEditorWidget();
  303. let model = new CompleterModel();
  304. let options: Completer.IOptions = {
  305. editor: anchor.editor,
  306. model
  307. };
  308. model.setOptions(['foo', 'bar', 'baz'], {
  309. foo: 'instance',
  310. bar: 'function'
  311. });
  312. Widget.attach(anchor, document.body);
  313. let widget = new Completer(options);
  314. Widget.attach(widget, document.body);
  315. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  316. let items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
  317. expect(Array.from(items[0].classList)).to.contain(ACTIVE_CLASS);
  318. expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
  319. expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
  320. simulate(anchor.node, 'keydown', { keyCode: 40 }); // Down
  321. expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
  322. expect(Array.from(items[1].classList)).to.contain(ACTIVE_CLASS);
  323. expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
  324. simulate(anchor.node, 'keydown', { keyCode: 40 }); // Down
  325. expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
  326. expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
  327. expect(Array.from(items[2].classList)).to.contain(ACTIVE_CLASS);
  328. simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
  329. expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
  330. expect(Array.from(items[1].classList)).to.contain(ACTIVE_CLASS);
  331. expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
  332. simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
  333. expect(Array.from(items[0].classList)).to.contain(ACTIVE_CLASS);
  334. expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
  335. expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
  336. simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
  337. expect(Array.from(items[0].classList)).to.contain(ACTIVE_CLASS);
  338. expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
  339. expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
  340. widget.dispose();
  341. anchor.dispose();
  342. });
  343. it('should mark common subset on start and complete that subset on tab', async () => {
  344. let anchor = createEditorWidget();
  345. let model = new CompleterModel();
  346. let options: Completer.IOptions = {
  347. editor: anchor.editor,
  348. model
  349. };
  350. let value = '';
  351. let listener = (sender: any, selected: string) => {
  352. value = selected;
  353. };
  354. model.setOptions(['fo', 'foo', 'foo', 'fooo'], {
  355. foo: 'instance',
  356. bar: 'function'
  357. });
  358. Widget.attach(anchor, document.body);
  359. let widget = new Completer(options);
  360. widget.selected.connect(listener);
  361. Widget.attach(widget, document.body);
  362. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  363. await framePromise();
  364. let marked = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`);
  365. expect(value).to.be.empty;
  366. expect(marked).to.have.length(4);
  367. expect(marked[0].textContent).to.equal('fo');
  368. expect(marked[1].textContent).to.equal('fo');
  369. expect(marked[2].textContent).to.equal('fo');
  370. expect(marked[3].textContent).to.equal('fo');
  371. simulate(anchor.node, 'keydown', { keyCode: 9 }); // Tab key
  372. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  373. expect(value).to.equal('fo');
  374. widget.dispose();
  375. anchor.dispose();
  376. });
  377. });
  378. describe('mousedown', () => {
  379. it('should trigger a selected signal on mouse down', () => {
  380. let anchor = createEditorWidget();
  381. let model = new CompleterModel();
  382. let options: Completer.IOptions = {
  383. editor: anchor.editor,
  384. model
  385. };
  386. let value = '';
  387. let listener = (sender: any, selected: string) => {
  388. value = selected;
  389. };
  390. model.setOptions(['foo', 'bar', 'baz'], {
  391. foo: 'instance',
  392. bar: 'function'
  393. });
  394. model.query = 'b';
  395. Widget.attach(anchor, document.body);
  396. let widget = new Completer(options);
  397. widget.selected.connect(listener);
  398. Widget.attach(widget, document.body);
  399. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  400. let item = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`)[1];
  401. simulate(anchor.node, 'keydown', { keyCode: 9 }); // Tab key
  402. expect(model.query).to.equal('ba');
  403. simulate(item, 'mousedown');
  404. expect(value).to.equal('baz');
  405. widget.dispose();
  406. anchor.dispose();
  407. });
  408. it('should ignore nonstandard mouse clicks (e.g., right click)', () => {
  409. let anchor = createEditorWidget();
  410. let model = new CompleterModel();
  411. let options: Completer.IOptions = {
  412. editor: anchor.editor,
  413. model
  414. };
  415. let value = '';
  416. let listener = (sender: any, selected: string) => {
  417. value = selected;
  418. };
  419. model.setOptions(['foo', 'bar']);
  420. Widget.attach(anchor, document.body);
  421. let widget = new Completer(options);
  422. widget.selected.connect(listener);
  423. Widget.attach(widget, document.body);
  424. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  425. expect(value).to.equal('');
  426. simulate(widget.node, 'mousedown', { button: 1 });
  427. expect(value).to.equal('');
  428. widget.dispose();
  429. anchor.dispose();
  430. });
  431. it('should ignore a mouse down that misses an item', () => {
  432. let anchor = createEditorWidget();
  433. let model = new CompleterModel();
  434. let options: Completer.IOptions = {
  435. editor: anchor.editor,
  436. model
  437. };
  438. let value = '';
  439. let listener = (sender: any, selected: string) => {
  440. value = selected;
  441. };
  442. model.setOptions(['foo', 'bar']);
  443. Widget.attach(anchor, document.body);
  444. let widget = new Completer(options);
  445. widget.selected.connect(listener);
  446. Widget.attach(widget, document.body);
  447. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  448. expect(value).to.equal('');
  449. simulate(widget.node, 'mousedown');
  450. expect(value).to.equal('');
  451. widget.dispose();
  452. anchor.dispose();
  453. });
  454. it('should hide widget if mouse down misses it', () => {
  455. let anchor = createEditorWidget();
  456. let model = new CompleterModel();
  457. let options: Completer.IOptions = {
  458. editor: anchor.editor,
  459. model
  460. };
  461. let listener = (sender: any, selected: string) => {
  462. // no op
  463. };
  464. model.setOptions(['foo', 'bar']);
  465. Widget.attach(anchor, document.body);
  466. let widget = new Completer(options);
  467. widget.selected.connect(listener);
  468. Widget.attach(widget, document.body);
  469. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  470. expect(widget.isHidden).to.equal(false);
  471. simulate(anchor.node, 'mousedown');
  472. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  473. expect(widget.isHidden).to.equal(true);
  474. widget.dispose();
  475. anchor.dispose();
  476. });
  477. });
  478. describe('scroll', () => {
  479. it.skip('should position itself according to the anchor', async () => {
  480. let panel = new Panel();
  481. let code = createEditorWidget();
  482. let editor = code.editor;
  483. let model = new CompleterModel();
  484. let text = '\n\n\n\n\n\na';
  485. code.node.style.height = '5000px';
  486. code.node.style.width = '400px';
  487. code.node.style.background = 'yellow';
  488. editor.model.value.text = text;
  489. panel.node.style.background = 'red';
  490. panel.node.style.height = '2000px';
  491. panel.node.style.width = '500px';
  492. panel.node.style.maxHeight = '500px';
  493. panel.node.style.overflow = 'auto';
  494. panel.node.style.position = 'absolute';
  495. panel.node.style.top = '0px';
  496. panel.node.style.left = '0px';
  497. panel.node.scrollTop = 10;
  498. panel.addWidget(code);
  499. Widget.attach(panel, document.body);
  500. editor.refresh();
  501. let position = code.editor.getPositionAt(text.length);
  502. let coords = code.editor.getCoordinateForPosition(position);
  503. editor.setCursorPosition(position);
  504. let request: Completer.ITextState = {
  505. column: position.column,
  506. lineHeight: editor.lineHeight,
  507. charWidth: editor.charWidth,
  508. line: position.line,
  509. text: 'a'
  510. };
  511. model.original = request;
  512. model.cursor = { start: text.length - 1, end: text.length };
  513. model.setOptions(['abc', 'abd', 'abe', 'abi']);
  514. let widget = new Completer({ model, editor: code.editor });
  515. Widget.attach(widget, document.body);
  516. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  517. simulate(document.body, 'scroll');
  518. // Because the scroll handling is asynchronous, this test uses a large
  519. // timeout (500ms) to guarantee the scroll handling has finished.
  520. await sleep(500);
  521. let top = parseInt(window.getComputedStyle(widget.node).top, 10);
  522. let bottom = Math.floor(coords.bottom);
  523. expect(top + panel.node.scrollTop).to.equal(bottom);
  524. widget.dispose();
  525. code.dispose();
  526. panel.dispose();
  527. });
  528. });
  529. });
  530. describe('#onUpdateRequest()', () => {
  531. it('should emit a selection if there is only one match', () => {
  532. let anchor = createEditorWidget();
  533. let model = new CompleterModel();
  534. let coords = { left: 0, right: 0, top: 100, bottom: 120 };
  535. let request: Completer.ITextState = {
  536. column: 0,
  537. lineHeight: 0,
  538. charWidth: 0,
  539. line: 0,
  540. coords: coords as CodeEditor.ICoordinate,
  541. text: 'f'
  542. };
  543. let value = '';
  544. let options: Completer.IOptions = {
  545. editor: anchor.editor,
  546. model
  547. };
  548. let listener = (sender: any, selected: string) => {
  549. value = selected;
  550. };
  551. Widget.attach(anchor, document.body);
  552. model.original = request;
  553. model.setOptions(['foo']);
  554. let widget = new Completer(options);
  555. widget.selected.connect(listener);
  556. Widget.attach(widget, document.body);
  557. expect(value).to.equal('');
  558. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  559. expect(value).to.equal('foo');
  560. widget.dispose();
  561. anchor.dispose();
  562. });
  563. it('should do nothing if a model does not exist', () => {
  564. let widget = new LogWidget({ editor: null });
  565. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  566. expect(widget.methods).to.contain('onUpdateRequest');
  567. });
  568. it('should un-hide widget if multiple options are available', () => {
  569. let anchor = createEditorWidget();
  570. let model = new CompleterModel();
  571. let coords = { left: 0, right: 0, top: 100, bottom: 120 };
  572. let request: Completer.ITextState = {
  573. column: 0,
  574. lineHeight: 0,
  575. charWidth: 0,
  576. line: 0,
  577. coords: coords as CodeEditor.ICoordinate,
  578. text: 'f'
  579. };
  580. let options: Completer.IOptions = {
  581. editor: anchor.editor,
  582. model
  583. };
  584. Widget.attach(anchor, document.body);
  585. model.original = request;
  586. model.setOptions(['foo', 'bar', 'baz']);
  587. let widget = new Completer(options);
  588. widget.hide();
  589. expect(widget.isHidden).to.equal(true);
  590. Widget.attach(widget, document.body);
  591. MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
  592. expect(widget.isVisible).to.equal(true);
  593. widget.dispose();
  594. anchor.dispose();
  595. });
  596. });
  597. });
  598. });