widget.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { Message, MessageLoop } from '@lumino/messaging';
  4. import { Widget } from '@lumino/widgets';
  5. import { SessionContext } from '@jupyterlab/apputils';
  6. import { CodeConsole } from '../src';
  7. import {
  8. CodeCell,
  9. CodeCellModel,
  10. RawCellModel,
  11. RawCell
  12. } from '@jupyterlab/cells';
  13. import { createSessionContext, NBTestUtils } from '@jupyterlab/testutils';
  14. import {
  15. createConsoleFactory,
  16. rendermime,
  17. mimeTypeService,
  18. editorFactory
  19. } from './utils';
  20. class TestConsole extends CodeConsole {
  21. methods: string[] = [];
  22. protected newPromptCell(): void {
  23. this.methods.push('newPromptCell');
  24. super.newPromptCell();
  25. }
  26. protected onActivateRequest(msg: Message): void {
  27. this.methods.push('onActivateRequest');
  28. super.onActivateRequest(msg);
  29. }
  30. protected onAfterAttach(msg: Message): void {
  31. this.methods.push('onAfterAttach');
  32. super.onAfterAttach(msg);
  33. }
  34. protected onUpdateRequest(msg: Message): void {
  35. this.methods.push('onUpdateRequest');
  36. super.onUpdateRequest(msg);
  37. }
  38. }
  39. const contentFactory = createConsoleFactory();
  40. describe('console/widget', () => {
  41. describe('CodeConsole', () => {
  42. let widget: TestConsole;
  43. beforeEach(async () => {
  44. const sessionContext = await createSessionContext();
  45. widget = new TestConsole({
  46. contentFactory,
  47. rendermime,
  48. sessionContext,
  49. mimeTypeService
  50. });
  51. });
  52. afterEach(async () => {
  53. await widget.sessionContext.shutdown();
  54. widget.sessionContext.dispose();
  55. widget.dispose();
  56. });
  57. describe('#constructor()', () => {
  58. it('should create a new console content widget', () => {
  59. Widget.attach(widget, document.body);
  60. expect(widget).toBeInstanceOf(CodeConsole);
  61. expect(Array.from(widget.node.classList)).toEqual(
  62. expect.arrayContaining(['jp-CodeConsole'])
  63. );
  64. });
  65. });
  66. describe('#cells', () => {
  67. it('should exist upon instantiation', () => {
  68. expect(widget.cells).toBeTruthy();
  69. });
  70. it('should reflect the contents of the widget', async () => {
  71. const force = true;
  72. Widget.attach(widget, document.body);
  73. await (widget.sessionContext as SessionContext).initialize();
  74. await widget.execute(force);
  75. expect(widget.cells.length).toBe(1);
  76. widget.clear();
  77. expect(widget.cells.length).toBe(0);
  78. });
  79. });
  80. describe('#executed', () => {
  81. it('should emit a date upon execution', async () => {
  82. let called: Date | null = null;
  83. const force = true;
  84. Widget.attach(widget, document.body);
  85. widget.executed.connect((sender, time) => {
  86. called = time;
  87. });
  88. await (widget.sessionContext as SessionContext).initialize();
  89. await widget.execute(force);
  90. expect(called).toBeInstanceOf(Date);
  91. });
  92. });
  93. describe('#promptCell', () => {
  94. it('should be a code cell widget', () => {
  95. Widget.attach(widget, document.body);
  96. expect(widget.promptCell).toBeInstanceOf(CodeCell);
  97. });
  98. it('should be replaced after execution', async () => {
  99. const force = true;
  100. Widget.attach(widget, document.body);
  101. const old = widget.promptCell;
  102. expect(old).toBeInstanceOf(CodeCell);
  103. await (widget.sessionContext as SessionContext).initialize();
  104. await widget.execute(force);
  105. expect(widget.promptCell).toBeInstanceOf(CodeCell);
  106. expect(widget.promptCell).not.toBe(old);
  107. });
  108. });
  109. describe('#session', () => {
  110. it('should be a client session object', () => {
  111. expect(widget.sessionContext.sessionChanged).toBeTruthy();
  112. });
  113. });
  114. describe('#contentFactory', () => {
  115. it('should be the content factory used by the widget', () => {
  116. expect(widget.contentFactory).toBeInstanceOf(
  117. CodeConsole.ContentFactory
  118. );
  119. });
  120. });
  121. describe('#addCell()', () => {
  122. it('should add a code cell to the content widget', () => {
  123. const contentFactory = NBTestUtils.createCodeCellFactory();
  124. const model = new CodeCellModel({});
  125. const cell = new CodeCell({
  126. model,
  127. contentFactory,
  128. rendermime
  129. }).initializeState();
  130. Widget.attach(widget, document.body);
  131. expect(widget.cells.length).toBe(0);
  132. widget.addCell(cell);
  133. expect(widget.cells.length).toBe(1);
  134. });
  135. });
  136. describe('#clear()', () => {
  137. it('should clear all of the content cells except the banner', async () => {
  138. const force = true;
  139. Widget.attach(widget, document.body);
  140. await (widget.sessionContext as SessionContext).initialize();
  141. await widget.execute(force);
  142. expect(widget.cells.length).toBeGreaterThan(0);
  143. widget.clear();
  144. expect(widget.cells.length).toBe(0);
  145. expect(widget.promptCell!.model.value.text).toBe('');
  146. });
  147. });
  148. describe('#dispose()', () => {
  149. it('should dispose the content widget', () => {
  150. Widget.attach(widget, document.body);
  151. expect(widget.isDisposed).toBe(false);
  152. widget.dispose();
  153. expect(widget.isDisposed).toBe(true);
  154. });
  155. it('should be safe to dispose multiple times', () => {
  156. Widget.attach(widget, document.body);
  157. expect(widget.isDisposed).toBe(false);
  158. widget.dispose();
  159. widget.dispose();
  160. expect(widget.isDisposed).toBe(true);
  161. });
  162. });
  163. describe('#execute()', () => {
  164. it('should execute contents of the prompt if forced', async () => {
  165. const force = true;
  166. Widget.attach(widget, document.body);
  167. expect(widget.cells.length).toBe(0);
  168. await (widget.sessionContext as SessionContext).initialize();
  169. await widget.execute(force);
  170. expect(widget.cells.length).toBeGreaterThan(0);
  171. });
  172. it('should check if code is multiline and allow amending', async () => {
  173. const force = false;
  174. const timeout = 9000;
  175. Widget.attach(widget, document.body);
  176. widget.promptCell!.model.value.text = 'for x in range(5):';
  177. expect(widget.cells.length).toBe(0);
  178. const session = widget.sessionContext as SessionContext;
  179. session.kernelPreference = { name: 'ipython' };
  180. await session.initialize();
  181. await widget.execute(force, timeout);
  182. expect(widget.cells.length).toBe(0);
  183. });
  184. });
  185. describe('#inject()', () => {
  186. it('should add a code cell and execute it', async () => {
  187. const code = 'print("#inject()")';
  188. Widget.attach(widget, document.body);
  189. expect(widget.cells.length).toBe(0);
  190. await widget.inject(code);
  191. expect(widget.cells.length).toBeGreaterThan(0);
  192. });
  193. });
  194. describe('#insertLinebreak()', () => {
  195. it('should insert a line break into the prompt', () => {
  196. Widget.attach(widget, document.body);
  197. const model = widget.promptCell!.model;
  198. expect(model.value.text).toHaveLength(0);
  199. widget.insertLinebreak();
  200. expect(model.value.text).toBe('\n');
  201. });
  202. });
  203. describe('#serialize()', () => {
  204. it('should serialize the contents of a console', () => {
  205. Widget.attach(widget, document.body);
  206. widget.promptCell!.model.value.text = 'foo';
  207. const serialized = widget.serialize();
  208. expect(serialized).toHaveLength(1);
  209. expect(serialized[0].source).toBe('foo');
  210. });
  211. });
  212. describe('#newPromptCell()', () => {
  213. it('should be called after attach, creating a prompt', () => {
  214. expect(widget.promptCell).toBeFalsy();
  215. expect(widget.methods).toEqual(
  216. expect.not.arrayContaining(['newPromptCell'])
  217. );
  218. Widget.attach(widget, document.body);
  219. expect(widget.methods).toEqual(
  220. expect.arrayContaining(['newPromptCell'])
  221. );
  222. expect(widget.promptCell).toBeTruthy();
  223. });
  224. it('should be called after execution, creating a prompt', async () => {
  225. expect(widget.promptCell).toBeFalsy();
  226. expect(widget.methods).toEqual(
  227. expect.not.arrayContaining(['newPromptCell'])
  228. );
  229. Widget.attach(widget, document.body);
  230. expect(widget.methods).toEqual(
  231. expect.arrayContaining(['newPromptCell'])
  232. );
  233. const old = widget.promptCell;
  234. const force = true;
  235. expect(old).toBeInstanceOf(CodeCell);
  236. widget.methods = [];
  237. await (widget.sessionContext as SessionContext).initialize();
  238. await widget.execute(force);
  239. expect(widget.promptCell).toBeInstanceOf(CodeCell);
  240. expect(widget.promptCell).not.toBe(old);
  241. expect(widget.methods).toEqual(
  242. expect.arrayContaining(['newPromptCell'])
  243. );
  244. });
  245. });
  246. describe('#onActivateRequest()', () => {
  247. it('should focus the prompt editor', () => {
  248. expect(widget.promptCell).toBeFalsy();
  249. expect(widget.methods).toEqual(
  250. expect.not.arrayContaining(['onActivateRequest'])
  251. );
  252. Widget.attach(widget, document.body);
  253. MessageLoop.sendMessage(widget, Widget.Msg.ActivateRequest);
  254. expect(widget.methods).toEqual(
  255. expect.arrayContaining(['onActivateRequest'])
  256. );
  257. expect(widget.promptCell!.editor.hasFocus()).toBe(true);
  258. });
  259. });
  260. describe('#onAfterAttach()', () => {
  261. it('should be called after attach, creating a prompt', () => {
  262. expect(widget.promptCell).toBeFalsy();
  263. expect(widget.methods).toEqual(
  264. expect.not.arrayContaining(['onAfterAttach'])
  265. );
  266. Widget.attach(widget, document.body);
  267. expect(widget.methods).toEqual(
  268. expect.arrayContaining(['onAfterAttach'])
  269. );
  270. expect(widget.promptCell).toBeTruthy();
  271. });
  272. });
  273. describe('.ContentFactory', () => {
  274. describe('#constructor', () => {
  275. it('should create a new ContentFactory', () => {
  276. const factory = new CodeConsole.ContentFactory({ editorFactory });
  277. expect(factory).toBeInstanceOf(CodeConsole.ContentFactory);
  278. });
  279. });
  280. describe('#createCodeCell', () => {
  281. it('should create a code cell', () => {
  282. const model = new CodeCellModel({});
  283. const prompt = contentFactory.createCodeCell({
  284. rendermime: widget.rendermime,
  285. model,
  286. contentFactory
  287. });
  288. expect(prompt).toBeInstanceOf(CodeCell);
  289. });
  290. });
  291. describe('#createRawCell', () => {
  292. it('should create a foreign cell', () => {
  293. const model = new RawCellModel({});
  294. const prompt = contentFactory.createRawCell({
  295. model,
  296. contentFactory
  297. });
  298. expect(prompt).toBeInstanceOf(RawCell);
  299. });
  300. });
  301. });
  302. describe('.ModelFactory', () => {
  303. describe('#constructor()', () => {
  304. it('should create a new model factory', () => {
  305. const factory = new CodeConsole.ModelFactory({});
  306. expect(factory).toBeInstanceOf(CodeConsole.ModelFactory);
  307. });
  308. it('should accept a codeCellContentFactory', () => {
  309. const codeCellContentFactory = new CodeCellModel.ContentFactory();
  310. const factory = new CodeConsole.ModelFactory({
  311. codeCellContentFactory
  312. });
  313. expect(factory.codeCellContentFactory).toBe(codeCellContentFactory);
  314. });
  315. });
  316. describe('#codeCellContentFactory', () => {
  317. it('should be the code cell content factory used by the factory', () => {
  318. const factory = new CodeConsole.ModelFactory({});
  319. expect(factory.codeCellContentFactory).toBe(
  320. CodeCellModel.defaultContentFactory
  321. );
  322. });
  323. });
  324. describe('#createCodeCell()', () => {
  325. it('should create a code cell', () => {
  326. const factory = new CodeConsole.ModelFactory({});
  327. expect(factory.createCodeCell({})).toBeInstanceOf(CodeCellModel);
  328. });
  329. });
  330. describe('#createRawCell()', () => {
  331. it('should create a raw cell model', () => {
  332. const factory = new CodeConsole.ModelFactory({});
  333. expect(factory.createRawCell({})).toBeInstanceOf(RawCellModel);
  334. });
  335. });
  336. });
  337. describe('.defaultModelFactory', () => {
  338. it('should be a ModelFactory', () => {
  339. expect(CodeConsole.defaultModelFactory).toBeInstanceOf(
  340. CodeConsole.ModelFactory
  341. );
  342. });
  343. });
  344. });
  345. });