handler.spec.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import expect = require('expect.js');
  4. import {
  5. KernelMessage
  6. } from 'jupyter-js-services';
  7. import {
  8. MockKernel
  9. } from 'jupyter-js-services/lib/mockkernel';
  10. import {
  11. BaseCellWidget, CellModel
  12. } from '../../../../lib/notebook/cells';
  13. import {
  14. ICompletionRequest, ICellEditorWidget, ITextChange
  15. } from '../../../../lib/notebook/cells/editor';
  16. import {
  17. CompletionWidget, CellCompletionHandler, CompletionModel, ICompletionPatch
  18. } from '../../../../lib/notebook/completion';
  19. import {
  20. defaultCodeMirrorRenderer
  21. } from '../../../../lib/notebook/codemirror/cells/widget';
  22. class TestCompletionModel extends CompletionModel {
  23. methods: string[] = [];
  24. createPatch(patch: string): ICompletionPatch {
  25. this.methods.push('createPatch');
  26. return super.createPatch(patch);
  27. }
  28. handleTextChange(change: ITextChange): void {
  29. this.methods.push('handleTextChange');
  30. super.handleTextChange(change);
  31. }
  32. }
  33. class TestCompletionHandler extends CellCompletionHandler {
  34. methods: string[] = [];
  35. makeRequest(request: ICompletionRequest): Promise<void> {
  36. let promise = super.makeRequest(request);
  37. this.methods.push('makeRequest');
  38. return promise;
  39. }
  40. onReply(pending: number, request: ICompletionRequest, msg: KernelMessage.ICompleteReplyMsg): void {
  41. super.onReply(pending, request, msg);
  42. this.methods.push('onReply');
  43. }
  44. onTextChanged(editor: ICellEditorWidget, change: ITextChange): void {
  45. super.onTextChanged(editor, change);
  46. this.methods.push('onTextChanged');
  47. }
  48. onCompletionRequested(editor: ICellEditorWidget, request: ICompletionRequest): void {
  49. super.onCompletionRequested(editor, request);
  50. this.methods.push('onCompletionRequested');
  51. }
  52. onCompletionSelected(widget: CompletionWidget, value: string): void {
  53. super.onCompletionSelected(widget, value);
  54. this.methods.push('onCompletionSelected');
  55. }
  56. }
  57. describe('notebook/completion/handler', () => {
  58. describe('CellCompletionHandler', () => {
  59. describe('#constructor()', () => {
  60. it('should create a completion handler', () => {
  61. let handler = new CellCompletionHandler(new CompletionWidget());
  62. expect(handler).to.be.a(CellCompletionHandler);
  63. });
  64. });
  65. describe('#kernel', () => {
  66. it('should default to null', () => {
  67. let handler = new CellCompletionHandler(new CompletionWidget());
  68. expect(handler.kernel).to.be(null);
  69. });
  70. it('should be settable', () => {
  71. let handler = new CellCompletionHandler(new CompletionWidget());
  72. let kernel = new MockKernel();
  73. expect(handler.kernel).to.be(null);
  74. handler.kernel = kernel;
  75. expect(handler.kernel).to.be.a(MockKernel);
  76. expect(handler.kernel).to.be(kernel);
  77. });
  78. });
  79. describe('#activeCell', () => {
  80. it('should default to null', () => {
  81. let handler = new CellCompletionHandler(new CompletionWidget());
  82. expect(handler.activeCell).to.be(null);
  83. });
  84. it('should be settable', () => {
  85. let handler = new CellCompletionHandler(new CompletionWidget());
  86. let cell = new BaseCellWidget({renderer:defaultCodeMirrorRenderer});
  87. expect(handler.activeCell).to.be(null);
  88. handler.activeCell = cell;
  89. expect(handler.activeCell).to.be.a(BaseCellWidget);
  90. expect(handler.activeCell).to.be(cell);
  91. });
  92. it('should be resettable', () => {
  93. let handler = new CellCompletionHandler(new CompletionWidget());
  94. let one = new BaseCellWidget({renderer:defaultCodeMirrorRenderer});
  95. let two = new BaseCellWidget({renderer:defaultCodeMirrorRenderer});
  96. expect(handler.activeCell).to.be(null);
  97. handler.activeCell = one;
  98. expect(handler.activeCell).to.be.a(BaseCellWidget);
  99. expect(handler.activeCell).to.be(one);
  100. handler.activeCell = two;
  101. expect(handler.activeCell).to.be.a(BaseCellWidget);
  102. expect(handler.activeCell).to.be(two);
  103. });
  104. });
  105. describe('#isDisposed', () => {
  106. it('should be true if handler has been disposed', () => {
  107. let handler = new CellCompletionHandler(new CompletionWidget());
  108. expect(handler.isDisposed).to.be(false);
  109. handler.dispose();
  110. expect(handler.isDisposed).to.be(true);
  111. });
  112. });
  113. describe('#dispose()', () => {
  114. it('should dispose of the handler resources', () => {
  115. let handler = new CellCompletionHandler(new CompletionWidget());
  116. let kernel = new MockKernel();
  117. handler.kernel = kernel;
  118. expect(handler.isDisposed).to.be(false);
  119. expect(handler.kernel).to.be.ok();
  120. handler.dispose();
  121. expect(handler.isDisposed).to.be(true);
  122. expect(handler.kernel).to.not.be.ok();
  123. });
  124. it('should be safe to call multiple times', () => {
  125. let handler = new CellCompletionHandler(new CompletionWidget());
  126. expect(handler.isDisposed).to.be(false);
  127. handler.dispose();
  128. handler.dispose();
  129. expect(handler.isDisposed).to.be(true);
  130. });
  131. });
  132. describe('#makeRequest()', () => {
  133. it('should reject if handler has no kernel', (done) => {
  134. let handler = new TestCompletionHandler(new CompletionWidget());
  135. let request: ICompletionRequest = {
  136. ch: 0,
  137. chHeight: 0,
  138. chWidth: 0,
  139. line: 0,
  140. coords: null,
  141. position: 0,
  142. currentValue: 'foo'
  143. };
  144. handler.makeRequest(request).catch((reason: Error) => {
  145. expect(reason).to.be.an(Error);
  146. done();
  147. });
  148. });
  149. // TODO: This test needs to be fixed when MockKernel is updated.
  150. it('should resolve if handler has a kernel', () => {
  151. console.warn('This test needs to be fixed when MockKernel is updated.');
  152. let handler = new TestCompletionHandler(new CompletionWidget());
  153. let kernel = new MockKernel();
  154. let request: ICompletionRequest = {
  155. ch: 0,
  156. chHeight: 0,
  157. chWidth: 0,
  158. line: 0,
  159. coords: null,
  160. position: 0,
  161. currentValue: 'foo'
  162. };
  163. handler.kernel = kernel;
  164. expect(handler.makeRequest(request)).to.be.a(Promise);
  165. });
  166. });
  167. describe('#onReply()', () => {
  168. it('should do nothing if handler has been disposed', () => {
  169. let completion = new CompletionWidget();
  170. let handler = new TestCompletionHandler(completion);
  171. completion.model = new CompletionModel();
  172. completion.model.options = ['foo', 'bar', 'baz'];
  173. handler.dispose();
  174. handler.onReply(0, null, null);
  175. expect(completion.model).to.be.ok();
  176. });
  177. it('should do nothing if pending request ID does not match', () => {
  178. let completion = new CompletionWidget();
  179. let handler = new TestCompletionHandler(completion);
  180. completion.model = new CompletionModel();
  181. completion.model.options = ['foo', 'bar', 'baz'];
  182. handler.onReply(2, null, null);
  183. expect(completion.model).to.be.ok();
  184. });
  185. it('should reset model if status is not ok', () => {
  186. let completion = new CompletionWidget();
  187. let handler = new TestCompletionHandler(completion);
  188. let options = ['a', 'b', 'c'];
  189. let request: ICompletionRequest = {
  190. ch: 0,
  191. chHeight: 0,
  192. chWidth: 0,
  193. line: 0,
  194. coords: null,
  195. position: 0,
  196. currentValue: 'f'
  197. };
  198. let reply: KernelMessage.ICompleteReplyMsg = {
  199. header: null,
  200. parent_header: {},
  201. metadata: {},
  202. buffers: null,
  203. channel: 'shell',
  204. content: {
  205. status: 'error',
  206. cursor_start: 0,
  207. cursor_end: 0,
  208. metadata: {},
  209. matches: ['foo']
  210. }
  211. };
  212. completion.model = new CompletionModel();
  213. completion.model.options = options;
  214. expect(completion.model.options).to.eql(options);
  215. handler.onReply(0, request, reply);
  216. expect(completion.model.options).to.be(null);
  217. });
  218. it('should update model if status is ok', () => {
  219. let completion = new CompletionWidget();
  220. let handler = new TestCompletionHandler(completion);
  221. let options = ['a', 'b', 'c'];
  222. let request: ICompletionRequest = {
  223. ch: 0,
  224. chHeight: 0,
  225. chWidth: 0,
  226. line: 0,
  227. coords: null,
  228. position: 0,
  229. currentValue: 'f'
  230. };
  231. let reply: KernelMessage.ICompleteReplyMsg = {
  232. header: null,
  233. parent_header: {},
  234. metadata: {},
  235. buffers: null,
  236. channel: 'shell',
  237. content: {
  238. status: 'ok',
  239. cursor_start: 0,
  240. cursor_end: 0,
  241. metadata: {},
  242. matches: ['foo']
  243. }
  244. };
  245. completion.model = new CompletionModel();
  246. completion.model.options = options;
  247. expect(completion.model.options).to.eql(options);
  248. handler.onReply(0, request, reply);
  249. expect(completion.model.options).to.eql(reply.content.matches);
  250. });
  251. });
  252. describe('#onTextChanged()', () => {
  253. it('should fire when the active editor emits a text change', () => {
  254. let handler = new TestCompletionHandler(new CompletionWidget());
  255. let change: ITextChange = {
  256. ch: 0,
  257. chHeight: 0,
  258. chWidth: 0,
  259. line: 0,
  260. position: 0,
  261. coords: null,
  262. oldValue: 'fo',
  263. newValue: 'foo'
  264. };
  265. let cell = new BaseCellWidget({renderer:defaultCodeMirrorRenderer});
  266. handler.activeCell = cell;
  267. expect(handler.methods).to.not.contain('onTextChanged');
  268. cell.editor.textChanged.emit(change);
  269. expect(handler.methods).to.contain('onTextChanged');
  270. });
  271. it('should call model change handler if model exists', () => {
  272. let completion = new CompletionWidget({
  273. model: new TestCompletionModel()
  274. });
  275. let handler = new TestCompletionHandler(completion);
  276. let change: ITextChange = {
  277. ch: 0,
  278. chHeight: 0,
  279. chWidth: 0,
  280. line: 0,
  281. position: 0,
  282. coords: null,
  283. oldValue: 'fo',
  284. newValue: 'foo'
  285. };
  286. let cell = new BaseCellWidget({renderer:defaultCodeMirrorRenderer});
  287. let model = completion.model as TestCompletionModel;
  288. handler.activeCell = cell;
  289. expect(model.methods).to.not.contain('handleTextChange');
  290. cell.editor.textChanged.emit(change);
  291. expect(model.methods).to.contain('handleTextChange');
  292. });
  293. });
  294. describe('#onCompletionRequested()', () => {
  295. it('should fire when the active editor emits a request', () => {
  296. let handler = new TestCompletionHandler(new CompletionWidget());
  297. let request: ICompletionRequest = {
  298. ch: 0,
  299. chHeight: 0,
  300. chWidth: 0,
  301. line: 0,
  302. coords: null,
  303. position: 0,
  304. currentValue: 'foo'
  305. };
  306. let cell = new BaseCellWidget({renderer:defaultCodeMirrorRenderer});
  307. handler.activeCell = cell;
  308. expect(handler.methods).to.not.contain('onCompletionRequested');
  309. cell.editor.completionRequested.emit(request);
  310. expect(handler.methods).to.contain('onCompletionRequested');
  311. });
  312. it('should make a kernel request if kernel and model exist', () => {
  313. let completion = new CompletionWidget({
  314. model: new TestCompletionModel()
  315. });
  316. let handler = new TestCompletionHandler(completion);
  317. let request: ICompletionRequest = {
  318. ch: 0,
  319. chHeight: 0,
  320. chWidth: 0,
  321. line: 0,
  322. coords: null,
  323. position: 0,
  324. currentValue: 'foo'
  325. };
  326. let cell = new BaseCellWidget({renderer:defaultCodeMirrorRenderer});
  327. let model = completion.model as TestCompletionModel;
  328. handler.kernel = new MockKernel();
  329. handler.activeCell = cell;
  330. expect(handler.methods).to.not.contain('makeRequest');
  331. cell.editor.completionRequested.emit(request);
  332. expect(handler.methods).to.contain('makeRequest');
  333. });
  334. });
  335. describe('#onCompletionSelected()', () => {
  336. it('should fire when the completion widget emits a signal', () => {
  337. let completion = new CompletionWidget();
  338. let handler = new TestCompletionHandler(completion);
  339. expect(handler.methods).to.not.contain('onCompletionSelected');
  340. completion.selected.emit('foo');
  341. expect(handler.methods).to.contain('onCompletionSelected');
  342. });
  343. it('should call model create patch method if model exists', () => {
  344. let completion = new CompletionWidget({
  345. model: new TestCompletionModel()
  346. });
  347. let handler = new TestCompletionHandler(completion);
  348. let model = completion.model as TestCompletionModel;
  349. handler.activeCell = new BaseCellWidget({renderer:defaultCodeMirrorRenderer});
  350. expect(model.methods).to.not.contain('createPatch');
  351. completion.selected.emit('foo');
  352. expect(model.methods).to.contain('createPatch');
  353. });
  354. it('should update cell if patch exists', () => {
  355. let model = new CompletionModel();
  356. let patch = 'foobar';
  357. let completion = new CompletionWidget({ model });
  358. let handler = new TestCompletionHandler(completion);
  359. let cell = new BaseCellWidget({renderer:defaultCodeMirrorRenderer});
  360. let request: ICompletionRequest = {
  361. ch: 0,
  362. chHeight: 0,
  363. chWidth: 0,
  364. line: 0,
  365. coords: null,
  366. position: 0,
  367. currentValue: 'foo'
  368. };
  369. cell.model = new CellModel();
  370. model.original = request;
  371. model.cursor = { start: 0, end: 3 };
  372. handler.activeCell = cell;
  373. handler.activeCell.model.source = request.currentValue;
  374. completion.selected.emit(patch);
  375. expect(handler.activeCell.model.source).to.equal(patch);
  376. });
  377. });
  378. });
  379. });