actions.spec.ts 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { expect } from 'chai';
  4. import { ClientSession } from '@jupyterlab/apputils';
  5. import { each } from '@phosphor/algorithm';
  6. import { CodeCell, MarkdownCell, RawCell } from '@jupyterlab/cells';
  7. import { NotebookModel } from '@jupyterlab/notebook';
  8. import { NotebookActions } from '@jupyterlab/notebook';
  9. import { Notebook } from '@jupyterlab/notebook';
  10. import {
  11. acceptDialog,
  12. createClientSession,
  13. dismissDialog,
  14. sleep,
  15. NBTestUtils
  16. } from '@jupyterlab/testutils';
  17. import { JSONObject, JSONArray } from '@phosphor/coreutils';
  18. const ERROR_INPUT = 'a = foo';
  19. const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
  20. describe('@jupyterlab/notebook', () => {
  21. const rendermime = NBTestUtils.defaultRenderMime();
  22. describe('NotebookActions', () => {
  23. let widget: Notebook;
  24. let session: ClientSession;
  25. let ipySession: ClientSession;
  26. before(async function() {
  27. // tslint:disable-next-line:no-invalid-this
  28. this.timeout(60000);
  29. [session, ipySession] = await Promise.all([
  30. createClientSession(),
  31. createClientSession({ kernelPreference: { name: 'ipython' } })
  32. ]);
  33. await Promise.all([ipySession.initialize(), session.initialize()]);
  34. await Promise.all([ipySession.kernel.ready, session.kernel.ready]);
  35. });
  36. beforeEach(() => {
  37. widget = new Notebook({
  38. rendermime,
  39. contentFactory: NBTestUtils.createNotebookFactory(),
  40. mimeTypeService: NBTestUtils.mimeTypeService
  41. });
  42. const model = new NotebookModel();
  43. model.fromJSON(NBTestUtils.DEFAULT_CONTENT);
  44. widget.model = model;
  45. widget.activeCellIndex = 0;
  46. });
  47. afterEach(() => {
  48. widget.dispose();
  49. NBTestUtils.clipboard.clear();
  50. });
  51. after(async () => {
  52. await Promise.all([session.shutdown(), ipySession.shutdown()]);
  53. });
  54. describe('#executed', () => {
  55. it('should emit when Markdown and code cells are run', async () => {
  56. const cell = widget.activeCell as CodeCell;
  57. const next = widget.widgets[1] as MarkdownCell;
  58. let emitted = 0;
  59. widget.select(next);
  60. cell.model.outputs.clear();
  61. next.rendered = false;
  62. NotebookActions.executed.connect(() => {
  63. emitted += 1;
  64. });
  65. await NotebookActions.run(widget, session);
  66. expect(emitted).to.equal(2);
  67. expect(next.rendered).to.equal(true);
  68. });
  69. });
  70. describe('#splitCell({})', () => {
  71. it('should split the active cell into two cells', () => {
  72. const cell = widget.activeCell;
  73. const source = 'thisisasamplestringwithnospaces';
  74. cell.model.value.text = source;
  75. const index = widget.activeCellIndex;
  76. const editor = cell.editor;
  77. editor.setCursorPosition(editor.getPositionAt(10));
  78. NotebookActions.splitCell(widget);
  79. const cells = widget.model.cells;
  80. const newSource =
  81. cells.get(index).value.text + cells.get(index + 1).value.text;
  82. expect(newSource).to.equal(source);
  83. });
  84. it('should preserve leading white space in the second cell', () => {
  85. const cell = widget.activeCell;
  86. const source = 'this\n\n is a test';
  87. cell.model.value.text = source;
  88. const editor = cell.editor;
  89. editor.setCursorPosition(editor.getPositionAt(4));
  90. NotebookActions.splitCell(widget);
  91. expect(widget.activeCell.model.value.text).to.equal(' is a test');
  92. });
  93. it('should clear the existing selection', () => {
  94. each(widget.widgets, child => {
  95. widget.select(child);
  96. });
  97. NotebookActions.splitCell(widget);
  98. for (let i = 0; i < widget.widgets.length; i++) {
  99. if (i === widget.activeCellIndex) {
  100. continue;
  101. }
  102. expect(widget.isSelected(widget.widgets[i])).to.equal(false);
  103. }
  104. });
  105. it('should activate the second cell', () => {
  106. NotebookActions.splitCell(widget);
  107. expect(widget.activeCellIndex).to.equal(1);
  108. });
  109. it('should preserve the types of each cell', () => {
  110. NotebookActions.changeCellType(widget, 'markdown');
  111. NotebookActions.splitCell(widget);
  112. expect(widget.activeCell).to.be.an.instanceof(MarkdownCell);
  113. const prev = widget.widgets[0];
  114. expect(prev).to.be.an.instanceof(MarkdownCell);
  115. });
  116. it('should create two empty cells if there is no content', () => {
  117. widget.activeCell.model.value.text = '';
  118. NotebookActions.splitCell(widget);
  119. expect(widget.activeCell.model.value.text).to.equal('');
  120. const prev = widget.widgets[0];
  121. expect(prev.model.value.text).to.equal('');
  122. });
  123. it('should be a no-op if there is no model', () => {
  124. widget.model = null;
  125. NotebookActions.splitCell(widget);
  126. expect(widget.activeCell).to.be.undefined;
  127. });
  128. it('should preserve the widget mode', () => {
  129. NotebookActions.splitCell(widget);
  130. expect(widget.mode).to.equal('command');
  131. widget.mode = 'edit';
  132. NotebookActions.splitCell(widget);
  133. expect(widget.mode).to.equal('edit');
  134. });
  135. it('should be undo-able', () => {
  136. const source = widget.activeCell.model.value.text;
  137. const count = widget.widgets.length;
  138. NotebookActions.splitCell(widget);
  139. NotebookActions.undo(widget);
  140. expect(widget.widgets.length).to.equal(count);
  141. const cell = widget.widgets[0];
  142. expect(cell.model.value.text).to.equal(source);
  143. });
  144. });
  145. describe('#mergeCells', () => {
  146. it('should merge the selected cells', () => {
  147. let source = widget.activeCell.model.value.text + '\n\n';
  148. let next = widget.widgets[1];
  149. widget.select(next);
  150. source += next.model.value.text + '\n\n';
  151. next = widget.widgets[2];
  152. widget.select(next);
  153. source += next.model.value.text;
  154. const count = widget.widgets.length;
  155. NotebookActions.mergeCells(widget);
  156. expect(widget.widgets.length).to.equal(count - 2);
  157. expect(widget.activeCell.model.value.text).to.equal(source);
  158. });
  159. it('should be a no-op if there is no model', () => {
  160. widget.model = null;
  161. NotebookActions.mergeCells(widget);
  162. expect(widget.activeCell).to.be.undefined;
  163. });
  164. it('should select the next cell if there is only one cell selected', () => {
  165. let source = widget.activeCell.model.value.text + '\n\n';
  166. const next = widget.widgets[1];
  167. source += next.model.value.text;
  168. NotebookActions.mergeCells(widget);
  169. expect(widget.activeCell.model.value.text).to.equal(source);
  170. });
  171. it('should clear the outputs of a code cell', () => {
  172. NotebookActions.mergeCells(widget);
  173. const cell = widget.activeCell as CodeCell;
  174. expect(cell.model.outputs.length).to.equal(0);
  175. });
  176. it('should preserve the widget mode', () => {
  177. widget.mode = 'edit';
  178. NotebookActions.mergeCells(widget);
  179. expect(widget.mode).to.equal('edit');
  180. widget.mode = 'command';
  181. NotebookActions.mergeCells(widget);
  182. expect(widget.mode).to.equal('command');
  183. });
  184. it('should be undo-able', () => {
  185. const source = widget.activeCell.model.value.text;
  186. const count = widget.widgets.length;
  187. NotebookActions.mergeCells(widget);
  188. NotebookActions.undo(widget);
  189. expect(widget.widgets.length).to.equal(count);
  190. const cell = widget.widgets[0];
  191. expect(cell.model.value.text).to.equal(source);
  192. });
  193. it('should unrender a markdown cell', () => {
  194. NotebookActions.changeCellType(widget, 'markdown');
  195. let cell = widget.activeCell as MarkdownCell;
  196. cell.rendered = true;
  197. NotebookActions.mergeCells(widget);
  198. cell = widget.activeCell as MarkdownCell;
  199. expect(cell.rendered).to.equal(false);
  200. expect(widget.mode).to.equal('command');
  201. });
  202. it('should preserve the cell type of the active cell', () => {
  203. NotebookActions.changeCellType(widget, 'raw');
  204. NotebookActions.mergeCells(widget);
  205. expect(widget.activeCell).to.be.an.instanceof(RawCell);
  206. expect(widget.mode).to.equal('command');
  207. });
  208. });
  209. describe('#deleteCells()', () => {
  210. it('should delete the selected cells', () => {
  211. const next = widget.widgets[1];
  212. widget.select(next);
  213. const count = widget.widgets.length;
  214. NotebookActions.deleteCells(widget);
  215. expect(widget.widgets.length).to.equal(count - 2);
  216. });
  217. it('should increment deletedCells model when cells deleted', () => {
  218. let next = widget.widgets[1];
  219. widget.select(next);
  220. let count = widget.model.deletedCells.length;
  221. NotebookActions.deleteCells(widget);
  222. expect(widget.model.deletedCells.length).to.equal(count + 2);
  223. });
  224. it('should be a no-op if there is no model', () => {
  225. widget.model = null;
  226. NotebookActions.deleteCells(widget);
  227. expect(widget.activeCell).to.be.undefined;
  228. });
  229. it('should switch to command mode', () => {
  230. widget.mode = 'edit';
  231. NotebookActions.deleteCells(widget);
  232. expect(widget.mode).to.equal('command');
  233. });
  234. it('should activate the cell after the last selected cell', () => {
  235. widget.activeCellIndex = 4;
  236. const prev = widget.widgets[2];
  237. widget.select(prev);
  238. NotebookActions.deleteCells(widget);
  239. expect(widget.activeCellIndex).to.equal(3);
  240. });
  241. it('should select the previous cell if the last cell is deleted', () => {
  242. widget.select(widget.widgets[widget.widgets.length - 1]);
  243. NotebookActions.deleteCells(widget);
  244. expect(widget.activeCellIndex).to.equal(widget.widgets.length - 1);
  245. });
  246. it('should add a code cell if all cells are deleted', async () => {
  247. for (let i = 0; i < widget.widgets.length; i++) {
  248. widget.select(widget.widgets[i]);
  249. }
  250. NotebookActions.deleteCells(widget);
  251. await sleep();
  252. expect(widget.widgets.length).to.equal(1);
  253. expect(widget.activeCell).to.be.an.instanceof(CodeCell);
  254. });
  255. it('should be undo-able', () => {
  256. const next = widget.widgets[1];
  257. widget.select(next);
  258. const source = widget.activeCell.model.value.text;
  259. const count = widget.widgets.length;
  260. NotebookActions.deleteCells(widget);
  261. NotebookActions.undo(widget);
  262. expect(widget.widgets.length).to.equal(count);
  263. const cell = widget.widgets[0];
  264. expect(cell.model.value.text).to.equal(source);
  265. });
  266. it('should be undo-able if all the cells are deleted', () => {
  267. for (let i = 0; i < widget.widgets.length; i++) {
  268. widget.select(widget.widgets[i]);
  269. }
  270. const count = widget.widgets.length;
  271. const source = widget.widgets[1].model.value.text;
  272. NotebookActions.deleteCells(widget);
  273. NotebookActions.undo(widget);
  274. expect(widget.widgets.length).to.equal(count);
  275. expect(widget.widgets[1].model.value.text).to.equal(source);
  276. });
  277. });
  278. describe('#insertAbove()', () => {
  279. it('should insert a code cell above the active cell', () => {
  280. const count = widget.widgets.length;
  281. NotebookActions.insertAbove(widget);
  282. expect(widget.activeCellIndex).to.equal(0);
  283. expect(widget.widgets.length).to.equal(count + 1);
  284. expect(widget.activeCell).to.be.an.instanceof(CodeCell);
  285. });
  286. it('should be a no-op if there is no model', () => {
  287. widget.model = null;
  288. NotebookActions.insertAbove(widget);
  289. expect(widget.activeCell).to.be.undefined;
  290. });
  291. it('should widget mode should be preserved', () => {
  292. NotebookActions.insertAbove(widget);
  293. expect(widget.mode).to.equal('command');
  294. widget.mode = 'edit';
  295. NotebookActions.insertAbove(widget);
  296. expect(widget.mode).to.equal('edit');
  297. });
  298. it('should be undo-able', () => {
  299. const count = widget.widgets.length;
  300. NotebookActions.insertAbove(widget);
  301. NotebookActions.undo(widget);
  302. expect(widget.widgets.length).to.equal(count);
  303. });
  304. it('should clear the existing selection', () => {
  305. for (let i = 0; i < widget.widgets.length; i++) {
  306. widget.select(widget.widgets[i]);
  307. }
  308. NotebookActions.insertAbove(widget);
  309. for (let i = 0; i < widget.widgets.length; i++) {
  310. if (i === widget.activeCellIndex) {
  311. continue;
  312. }
  313. expect(widget.isSelected(widget.widgets[i])).to.equal(false);
  314. }
  315. });
  316. it('should be the new active cell', () => {
  317. NotebookActions.insertAbove(widget);
  318. expect(widget.activeCell.model.value.text).to.equal('');
  319. });
  320. });
  321. describe('#insertBelow()', () => {
  322. it('should insert a code cell below the active cell', () => {
  323. const count = widget.widgets.length;
  324. NotebookActions.insertBelow(widget);
  325. expect(widget.activeCellIndex).to.equal(1);
  326. expect(widget.widgets.length).to.equal(count + 1);
  327. expect(widget.activeCell).to.be.an.instanceof(CodeCell);
  328. });
  329. it('should be a no-op if there is no model', () => {
  330. widget.model = null;
  331. NotebookActions.insertBelow(widget);
  332. expect(widget.activeCell).to.be.undefined;
  333. });
  334. it('should widget mode should be preserved', () => {
  335. NotebookActions.insertBelow(widget);
  336. expect(widget.mode).to.equal('command');
  337. widget.mode = 'edit';
  338. NotebookActions.insertBelow(widget);
  339. expect(widget.mode).to.equal('edit');
  340. });
  341. it('should be undo-able', () => {
  342. const count = widget.widgets.length;
  343. NotebookActions.insertBelow(widget);
  344. NotebookActions.undo(widget);
  345. expect(widget.widgets.length).to.equal(count);
  346. });
  347. it('should clear the existing selection', () => {
  348. for (let i = 0; i < widget.widgets.length; i++) {
  349. widget.select(widget.widgets[i]);
  350. }
  351. NotebookActions.insertBelow(widget);
  352. for (let i = 0; i < widget.widgets.length; i++) {
  353. if (i === widget.activeCellIndex) {
  354. continue;
  355. }
  356. expect(widget.isSelected(widget.widgets[i])).to.equal(false);
  357. }
  358. });
  359. it('should be the new active cell', () => {
  360. NotebookActions.insertBelow(widget);
  361. expect(widget.activeCell.model.value.text).to.equal('');
  362. });
  363. });
  364. describe('#changeCellType()', () => {
  365. it('should change the selected cell type(s)', () => {
  366. let next = widget.widgets[1];
  367. widget.select(next);
  368. NotebookActions.changeCellType(widget, 'raw');
  369. expect(widget.activeCell).to.be.an.instanceof(RawCell);
  370. next = widget.widgets[widget.activeCellIndex + 1];
  371. expect(next).to.be.an.instanceof(RawCell);
  372. });
  373. it('should be a no-op if there is no model', () => {
  374. widget.model = null;
  375. NotebookActions.changeCellType(widget, 'code');
  376. expect(widget.activeCell).to.be.undefined;
  377. });
  378. it('should preserve the widget mode', () => {
  379. NotebookActions.changeCellType(widget, 'code');
  380. expect(widget.mode).to.equal('command');
  381. widget.mode = 'edit';
  382. NotebookActions.changeCellType(widget, 'raw');
  383. expect(widget.mode).to.equal('edit');
  384. });
  385. it('should be undo-able', () => {
  386. NotebookActions.changeCellType(widget, 'raw');
  387. NotebookActions.undo(widget);
  388. const cell = widget.widgets[0];
  389. expect(cell).to.be.an.instanceof(CodeCell);
  390. });
  391. it('should clear the existing selection', () => {
  392. for (let i = 0; i < widget.widgets.length; i++) {
  393. widget.select(widget.widgets[i]);
  394. }
  395. NotebookActions.changeCellType(widget, 'raw');
  396. for (let i = 0; i < widget.widgets.length; i++) {
  397. if (i === widget.activeCellIndex) {
  398. continue;
  399. }
  400. expect(widget.isSelected(widget.widgets[i])).to.equal(false);
  401. }
  402. });
  403. it('should unrender markdown cells', () => {
  404. NotebookActions.changeCellType(widget, 'markdown');
  405. const cell = widget.activeCell as MarkdownCell;
  406. expect(cell.rendered).to.equal(false);
  407. });
  408. });
  409. describe('#run()', () => {
  410. it('should run the selected cells', async () => {
  411. const next = widget.widgets[1] as MarkdownCell;
  412. widget.select(next);
  413. const cell = widget.activeCell as CodeCell;
  414. cell.model.outputs.clear();
  415. next.rendered = false;
  416. const result = await NotebookActions.run(widget, session);
  417. expect(result).to.equal(true);
  418. expect(cell.model.outputs.length).to.be.above(0);
  419. expect(next.rendered).to.equal(true);
  420. });
  421. it('should delete deletedCells metadata when cell run', () => {
  422. let cell = widget.activeCell as CodeCell;
  423. cell.model.outputs.clear();
  424. return NotebookActions.run(widget, session).then(result => {
  425. expect(result).to.equal(true);
  426. expect(widget.model.deletedCells.length).to.equal(0);
  427. });
  428. });
  429. it('should be a no-op if there is no model', async () => {
  430. widget.model = null;
  431. const result = await NotebookActions.run(widget, session);
  432. expect(result).to.equal(false);
  433. });
  434. it('should activate the last selected cell', async () => {
  435. const other = widget.widgets[2];
  436. widget.select(other);
  437. other.model.value.text = 'a = 1';
  438. const result = await NotebookActions.run(widget, session);
  439. expect(result).to.equal(true);
  440. expect(widget.activeCell).to.equal(other);
  441. });
  442. it('should clear the selection', async () => {
  443. const next = widget.widgets[1];
  444. widget.select(next);
  445. const result = await NotebookActions.run(widget, session);
  446. expect(result).to.equal(true);
  447. expect(widget.isSelected(widget.widgets[0])).to.equal(false);
  448. });
  449. it('should change to command mode', async () => {
  450. widget.mode = 'edit';
  451. const result = await NotebookActions.run(widget, session);
  452. expect(result).to.equal(true);
  453. expect(widget.mode).to.equal('command');
  454. });
  455. it('should handle no session', async () => {
  456. const result = await NotebookActions.run(widget, null);
  457. expect(result).to.equal(true);
  458. const cell = widget.activeCell as CodeCell;
  459. expect(cell.model.executionCount).to.be.null;
  460. });
  461. it('should stop executing code cells on an error', async () => {
  462. let cell = widget.model.contentFactory.createCodeCell({});
  463. cell.value.text = ERROR_INPUT;
  464. widget.model.cells.insert(2, cell);
  465. widget.select(widget.widgets[2]);
  466. cell = widget.model.contentFactory.createCodeCell({});
  467. widget.model.cells.push(cell);
  468. widget.select(widget.widgets[widget.widgets.length - 1]);
  469. const result = await NotebookActions.run(widget, ipySession);
  470. expect(result).to.equal(false);
  471. expect(cell.executionCount).to.be.null;
  472. await ipySession.kernel.restart();
  473. }).timeout(30000); // Allow for slower CI
  474. it('should render all markdown cells on an error', async () => {
  475. const cell = widget.model.contentFactory.createMarkdownCell({});
  476. widget.model.cells.push(cell);
  477. const child = widget.widgets[widget.widgets.length - 1] as MarkdownCell;
  478. child.rendered = false;
  479. widget.select(child);
  480. widget.activeCell.model.value.text = ERROR_INPUT;
  481. const result = await NotebookActions.run(widget, ipySession);
  482. // Markdown rendering is asynchronous, but the cell
  483. // provides no way to hook into that. Sleep here
  484. // to make sure it finishes.
  485. await sleep(100);
  486. expect(result).to.equal(false);
  487. expect(child.rendered).to.equal(true);
  488. await ipySession.kernel.restart();
  489. }).timeout(60000); // Allow for slower CI
  490. });
  491. describe('#runAndAdvance()', () => {
  492. it('should run the selected cells ', async () => {
  493. const next = widget.widgets[1] as MarkdownCell;
  494. widget.select(next);
  495. const cell = widget.activeCell as CodeCell;
  496. cell.model.outputs.clear();
  497. next.rendered = false;
  498. const result = await NotebookActions.runAndAdvance(widget, session);
  499. expect(result).to.equal(true);
  500. expect(cell.model.outputs.length).to.be.above(0);
  501. expect(next.rendered).to.equal(true);
  502. }).timeout(30000); // Allow for slower CI
  503. it('should be a no-op if there is no model', async () => {
  504. widget.model = null;
  505. const result = await NotebookActions.runAndAdvance(widget, session);
  506. expect(result).to.equal(false);
  507. });
  508. it('should clear the existing selection', async () => {
  509. const next = widget.widgets[2];
  510. widget.select(next);
  511. const result = await NotebookActions.runAndAdvance(widget, ipySession);
  512. expect(result).to.equal(false);
  513. expect(widget.isSelected(widget.widgets[0])).to.equal(false);
  514. await ipySession.kernel.restart();
  515. }).timeout(30000); // Allow for slower CI
  516. it('should change to command mode', async () => {
  517. widget.mode = 'edit';
  518. const result = await NotebookActions.runAndAdvance(widget, session);
  519. expect(result).to.equal(true);
  520. expect(widget.mode).to.equal('command');
  521. });
  522. it('should activate the cell after the last selected cell', async () => {
  523. const next = widget.widgets[3] as MarkdownCell;
  524. widget.select(next);
  525. const result = await NotebookActions.runAndAdvance(widget, session);
  526. expect(result).to.equal(true);
  527. expect(widget.activeCellIndex).to.equal(4);
  528. });
  529. it('should create a new code cell in edit mode if necessary', async () => {
  530. const count = widget.widgets.length;
  531. widget.activeCellIndex = count - 1;
  532. const result = await NotebookActions.runAndAdvance(widget, session);
  533. expect(result).to.equal(true);
  534. expect(widget.widgets.length).to.equal(count + 1);
  535. expect(widget.activeCell).to.be.an.instanceof(CodeCell);
  536. expect(widget.mode).to.equal('edit');
  537. });
  538. it('should allow an undo of the new cell', async () => {
  539. const count = widget.widgets.length;
  540. widget.activeCellIndex = count - 1;
  541. const result = await NotebookActions.runAndAdvance(widget, session);
  542. expect(result).to.equal(true);
  543. NotebookActions.undo(widget);
  544. expect(widget.widgets.length).to.equal(count);
  545. });
  546. it('should stop executing code cells on an error', async () => {
  547. widget.activeCell.model.value.text = ERROR_INPUT;
  548. const cell = widget.model.contentFactory.createCodeCell({});
  549. widget.model.cells.push(cell);
  550. widget.select(widget.widgets[widget.widgets.length - 1]);
  551. const result = await NotebookActions.runAndAdvance(widget, ipySession);
  552. expect(result).to.equal(false);
  553. expect(cell.executionCount).to.be.null;
  554. await ipySession.kernel.restart();
  555. }).timeout(30000); // Allow for slower CI
  556. it('should render all markdown cells on an error', async () => {
  557. widget.activeCell.model.value.text = ERROR_INPUT;
  558. const cell = widget.widgets[1] as MarkdownCell;
  559. cell.rendered = false;
  560. widget.select(cell);
  561. const result = await NotebookActions.runAndAdvance(widget, ipySession);
  562. // Markdown rendering is asynchronous, but the cell
  563. // provides no way to hook into that. Sleep here
  564. // to make sure it finishes.
  565. await sleep(100);
  566. expect(result).to.equal(false);
  567. expect(cell.rendered).to.equal(true);
  568. expect(widget.activeCellIndex).to.equal(2);
  569. await ipySession.kernel.restart();
  570. }).timeout(60000); // Allow for slower CI
  571. });
  572. describe('#runAndInsert()', () => {
  573. it('should run the selected cells ', async () => {
  574. const next = widget.widgets[1] as MarkdownCell;
  575. widget.select(next);
  576. const cell = widget.activeCell as CodeCell;
  577. cell.model.outputs.clear();
  578. next.rendered = false;
  579. const result = await NotebookActions.runAndInsert(widget, session);
  580. expect(result).to.equal(true);
  581. expect(cell.model.outputs.length).to.be.above(0);
  582. expect(next.rendered).to.equal(true);
  583. });
  584. it('should be a no-op if there is no model', async () => {
  585. widget.model = null;
  586. const result = await NotebookActions.runAndInsert(widget, session);
  587. expect(result).to.equal(false);
  588. });
  589. it('should clear the existing selection', async () => {
  590. const next = widget.widgets[1];
  591. widget.select(next);
  592. const result = await NotebookActions.runAndInsert(widget, session);
  593. expect(result).to.equal(true);
  594. expect(widget.isSelected(widget.widgets[0])).to.equal(false);
  595. });
  596. it('should insert a new code cell in edit mode after the last selected cell', async () => {
  597. const next = widget.widgets[2];
  598. widget.select(next);
  599. next.model.value.text = 'a = 1';
  600. const count = widget.widgets.length;
  601. const result = await NotebookActions.runAndInsert(widget, session);
  602. expect(result).to.equal(true);
  603. expect(widget.activeCell).to.be.an.instanceof(CodeCell);
  604. expect(widget.mode).to.equal('edit');
  605. expect(widget.widgets.length).to.equal(count + 1);
  606. });
  607. it('should allow an undo of the cell insert', async () => {
  608. const next = widget.widgets[2];
  609. widget.select(next);
  610. next.model.value.text = 'a = 1';
  611. const count = widget.widgets.length;
  612. const result = await NotebookActions.runAndInsert(widget, session);
  613. expect(result).to.equal(true);
  614. NotebookActions.undo(widget);
  615. expect(widget.widgets.length).to.equal(count);
  616. });
  617. it('should stop executing code cells on an error', async () => {
  618. widget.activeCell.model.value.text = ERROR_INPUT;
  619. const cell = widget.model.contentFactory.createCodeCell({});
  620. widget.model.cells.push(cell);
  621. widget.select(widget.widgets[widget.widgets.length - 1]);
  622. const result = await NotebookActions.runAndInsert(widget, ipySession);
  623. expect(result).to.equal(false);
  624. expect(cell.executionCount).to.be.null;
  625. await ipySession.kernel.restart();
  626. }).timeout(30000); // Allow for slower CI
  627. it('should render all markdown cells on an error', async () => {
  628. widget.activeCell.model.value.text = ERROR_INPUT;
  629. const cell = widget.widgets[1] as MarkdownCell;
  630. cell.rendered = false;
  631. widget.select(cell);
  632. const result = await NotebookActions.runAndInsert(widget, ipySession);
  633. // Markdown rendering is asynchronous, but the cell
  634. // provides no way to hook into that. Sleep here
  635. // to make sure it finishes.
  636. await sleep(100);
  637. expect(result).to.equal(false);
  638. expect(cell.rendered).to.equal(true);
  639. expect(widget.activeCellIndex).to.equal(2);
  640. await ipySession.kernel.restart();
  641. }).timeout(60000); // Allow for slower CI
  642. });
  643. describe('#runAll()', () => {
  644. beforeEach(() => {
  645. // Make sure all cells have valid code.
  646. widget.widgets[2].model.value.text = 'a = 1';
  647. });
  648. it('should run all of the cells in the notebok', async () => {
  649. const next = widget.widgets[1] as MarkdownCell;
  650. const cell = widget.activeCell as CodeCell;
  651. cell.model.outputs.clear();
  652. next.rendered = false;
  653. const result = await NotebookActions.runAll(widget, session);
  654. expect(result).to.equal(true);
  655. expect(cell.model.outputs.length).to.be.above(0);
  656. expect(next.rendered).to.equal(true);
  657. }).timeout(30000); // Allow for slower CI
  658. it('should be a no-op if there is no model', async () => {
  659. widget.model = null;
  660. const result = await NotebookActions.runAll(widget, session);
  661. expect(result).to.equal(false);
  662. });
  663. it('should change to command mode', async () => {
  664. widget.mode = 'edit';
  665. const result = await NotebookActions.runAll(widget, session);
  666. expect(result).to.equal(true);
  667. expect(widget.mode).to.equal('command');
  668. }).timeout(30000); // Allow for slower CI
  669. it('should clear the existing selection', async () => {
  670. const next = widget.widgets[2];
  671. widget.select(next);
  672. const result = await NotebookActions.runAll(widget, session);
  673. expect(result).to.equal(true);
  674. expect(widget.isSelected(widget.widgets[2])).to.equal(false);
  675. }).timeout(30000); // Allow for slower CI
  676. it('should activate the last cell', async () => {
  677. await NotebookActions.runAll(widget, session);
  678. expect(widget.activeCellIndex).to.equal(widget.widgets.length - 1);
  679. }).timeout(30000); // Allow for slower CI
  680. it('should stop executing code cells on an error', async () => {
  681. widget.activeCell.model.value.text = ERROR_INPUT;
  682. const cell = widget.model.contentFactory.createCodeCell({});
  683. widget.model.cells.push(cell);
  684. const result = await NotebookActions.runAll(widget, ipySession);
  685. expect(result).to.equal(false);
  686. expect(cell.executionCount).to.be.null;
  687. expect(widget.activeCellIndex).to.equal(widget.widgets.length - 1);
  688. await ipySession.kernel.restart();
  689. }).timeout(30000); // Allow for slower CI
  690. it('should render all markdown cells on an error', async () => {
  691. widget.activeCell.model.value.text = ERROR_INPUT;
  692. const cell = widget.widgets[1] as MarkdownCell;
  693. cell.rendered = false;
  694. const result = await NotebookActions.runAll(widget, ipySession);
  695. // Markdown rendering is asynchronous, but the cell
  696. // provides no way to hook into that. Sleep here
  697. // to make sure it finishes.
  698. await sleep(100);
  699. expect(result).to.equal(false);
  700. expect(cell.rendered).to.equal(true);
  701. await ipySession.kernel.restart();
  702. }).timeout(60000); // Allow for slower CI
  703. });
  704. describe('#selectAbove()', () => {
  705. it('should select the cell above the active cell', () => {
  706. widget.activeCellIndex = 1;
  707. NotebookActions.selectAbove(widget);
  708. expect(widget.activeCellIndex).to.equal(0);
  709. });
  710. it('should be a no-op if there is no model', () => {
  711. widget.model = null;
  712. NotebookActions.selectAbove(widget);
  713. expect(widget.activeCellIndex).to.equal(-1);
  714. });
  715. it('should not wrap around to the bottom', () => {
  716. NotebookActions.selectAbove(widget);
  717. expect(widget.activeCellIndex).to.equal(0);
  718. });
  719. it('should preserve the mode', () => {
  720. widget.activeCellIndex = 2;
  721. NotebookActions.selectAbove(widget);
  722. expect(widget.mode).to.equal('command');
  723. widget.mode = 'edit';
  724. NotebookActions.selectAbove(widget);
  725. expect(widget.mode).to.equal('edit');
  726. });
  727. it('should skip collapsed cells in edit mode', () => {
  728. widget.activeCellIndex = 3;
  729. widget.mode = 'edit';
  730. widget.widgets[1].inputHidden = true;
  731. widget.widgets[2].inputHidden = true;
  732. widget.widgets[3].inputHidden = false;
  733. NotebookActions.selectAbove(widget);
  734. expect(widget.activeCellIndex).to.equal(0);
  735. });
  736. it('should not change if in edit mode and no non-collapsed cells above', () => {
  737. widget.activeCellIndex = 1;
  738. widget.mode = 'edit';
  739. widget.widgets[0].inputHidden = true;
  740. NotebookActions.selectAbove(widget);
  741. expect(widget.activeCellIndex).to.equal(1);
  742. });
  743. it('should not skip collapsed cells and in command mode', () => {
  744. widget.activeCellIndex = 3;
  745. widget.mode = 'command';
  746. widget.widgets[1].inputHidden = true;
  747. widget.widgets[2].inputHidden = true;
  748. widget.widgets[3].inputHidden = false;
  749. NotebookActions.selectAbove(widget);
  750. expect(widget.activeCellIndex).to.equal(2);
  751. });
  752. });
  753. describe('#selectBelow()', () => {
  754. it('should select the cell below the active cell', () => {
  755. NotebookActions.selectBelow(widget);
  756. expect(widget.activeCellIndex).to.equal(1);
  757. });
  758. it('should be a no-op if there is no model', () => {
  759. widget.model = null;
  760. NotebookActions.selectBelow(widget);
  761. expect(widget.activeCellIndex).to.equal(-1);
  762. });
  763. it('should not wrap around to the top', () => {
  764. widget.activeCellIndex = widget.widgets.length - 1;
  765. NotebookActions.selectBelow(widget);
  766. expect(widget.activeCellIndex).to.not.equal(0);
  767. });
  768. it('should preserve the mode', () => {
  769. widget.activeCellIndex = 2;
  770. NotebookActions.selectBelow(widget);
  771. expect(widget.mode).to.equal('command');
  772. widget.mode = 'edit';
  773. NotebookActions.selectBelow(widget);
  774. expect(widget.mode).to.equal('edit');
  775. });
  776. it('should skip collapsed cells in edit mode', () => {
  777. widget.activeCellIndex = 0;
  778. widget.mode = 'edit';
  779. widget.widgets[1].inputHidden = true;
  780. widget.widgets[2].inputHidden = true;
  781. widget.widgets[3].inputHidden = false;
  782. NotebookActions.selectBelow(widget);
  783. expect(widget.activeCellIndex).to.equal(3);
  784. });
  785. it('should not change if in edit mode and no non-collapsed cells below', () => {
  786. widget.activeCellIndex = widget.widgets.length - 2;
  787. widget.mode = 'edit';
  788. widget.widgets[widget.widgets.length - 1].inputHidden = true;
  789. NotebookActions.selectBelow(widget);
  790. expect(widget.activeCellIndex).to.equal(widget.widgets.length - 2);
  791. });
  792. it('should not skip collapsed cells and in command mode', () => {
  793. widget.activeCellIndex = 0;
  794. widget.mode = 'command';
  795. widget.widgets[1].inputHidden = true;
  796. widget.widgets[2].inputHidden = true;
  797. widget.widgets[3].inputHidden = false;
  798. NotebookActions.selectBelow(widget);
  799. expect(widget.activeCellIndex).to.equal(1);
  800. });
  801. });
  802. describe('#extendSelectionAbove()', () => {
  803. it('should extend the selection to the cell above', () => {
  804. widget.activeCellIndex = 1;
  805. NotebookActions.extendSelectionAbove(widget);
  806. expect(widget.isSelected(widget.widgets[0])).to.equal(true);
  807. });
  808. it('should be a no-op if there is no model', () => {
  809. widget.model = null;
  810. NotebookActions.extendSelectionAbove(widget);
  811. expect(widget.activeCellIndex).to.equal(-1);
  812. });
  813. it('should change to command mode if there is a selection', () => {
  814. widget.mode = 'edit';
  815. widget.activeCellIndex = 1;
  816. NotebookActions.extendSelectionAbove(widget);
  817. expect(widget.mode).to.equal('command');
  818. });
  819. it('should not wrap around to the bottom', () => {
  820. widget.mode = 'edit';
  821. NotebookActions.extendSelectionAbove(widget);
  822. expect(widget.activeCellIndex).to.equal(0);
  823. const last = widget.widgets[widget.widgets.length - 1];
  824. expect(widget.isSelected(last)).to.equal(false);
  825. expect(widget.mode).to.equal('edit');
  826. });
  827. it('should deselect the current cell if the cell above is selected', () => {
  828. NotebookActions.extendSelectionBelow(widget);
  829. NotebookActions.extendSelectionBelow(widget);
  830. const cell = widget.activeCell;
  831. NotebookActions.extendSelectionAbove(widget);
  832. expect(widget.isSelected(cell)).to.equal(false);
  833. });
  834. it('should select only the first cell if we move from the second to first', () => {
  835. NotebookActions.extendSelectionBelow(widget);
  836. const cell = widget.activeCell;
  837. NotebookActions.extendSelectionAbove(widget);
  838. expect(widget.isSelected(cell)).to.equal(false);
  839. expect(widget.activeCellIndex).to.equal(0);
  840. });
  841. it('should activate the cell', () => {
  842. widget.activeCellIndex = 1;
  843. NotebookActions.extendSelectionAbove(widget);
  844. expect(widget.activeCellIndex).to.equal(0);
  845. });
  846. });
  847. describe('#extendSelectionBelow()', () => {
  848. it('should extend the selection to the cell below', () => {
  849. NotebookActions.extendSelectionBelow(widget);
  850. expect(widget.isSelected(widget.widgets[0])).to.equal(true);
  851. expect(widget.isSelected(widget.widgets[1])).to.equal(true);
  852. });
  853. it('should be a no-op if there is no model', () => {
  854. widget.model = null;
  855. NotebookActions.extendSelectionBelow(widget);
  856. expect(widget.activeCellIndex).to.equal(-1);
  857. });
  858. it('should change to command mode if there is a selection', () => {
  859. widget.mode = 'edit';
  860. NotebookActions.extendSelectionBelow(widget);
  861. expect(widget.mode).to.equal('command');
  862. });
  863. it('should not wrap around to the top', () => {
  864. const last = widget.widgets.length - 1;
  865. widget.activeCellIndex = last;
  866. widget.mode = 'edit';
  867. NotebookActions.extendSelectionBelow(widget);
  868. expect(widget.activeCellIndex).to.equal(last);
  869. expect(widget.isSelected(widget.widgets[0])).to.equal(false);
  870. expect(widget.mode).to.equal('edit');
  871. });
  872. it('should deselect the current cell if the cell below is selected', () => {
  873. const last = widget.widgets.length - 1;
  874. widget.activeCellIndex = last;
  875. NotebookActions.extendSelectionAbove(widget);
  876. NotebookActions.extendSelectionAbove(widget);
  877. const current = widget.activeCell;
  878. NotebookActions.extendSelectionBelow(widget);
  879. expect(widget.isSelected(current)).to.equal(false);
  880. });
  881. it('should select only the last cell if we move from the second last to last', () => {
  882. const last = widget.widgets.length - 1;
  883. widget.activeCellIndex = last;
  884. NotebookActions.extendSelectionAbove(widget);
  885. const current = widget.activeCell;
  886. NotebookActions.extendSelectionBelow(widget);
  887. expect(widget.isSelected(current)).to.equal(false);
  888. expect(widget.activeCellIndex).to.equal(last);
  889. });
  890. it('should activate the cell', () => {
  891. NotebookActions.extendSelectionBelow(widget);
  892. expect(widget.activeCellIndex).to.equal(1);
  893. });
  894. });
  895. describe('#moveUp()', () => {
  896. it('should move the selected cells up', () => {
  897. widget.activeCellIndex = 2;
  898. NotebookActions.extendSelectionAbove(widget);
  899. NotebookActions.moveUp(widget);
  900. expect(widget.isSelected(widget.widgets[0])).to.equal(true);
  901. expect(widget.isSelected(widget.widgets[1])).to.equal(true);
  902. expect(widget.isSelected(widget.widgets[2])).to.equal(false);
  903. expect(widget.activeCellIndex).to.equal(0);
  904. });
  905. it('should be a no-op if there is no model', () => {
  906. widget.model = null;
  907. NotebookActions.moveUp(widget);
  908. expect(widget.activeCellIndex).to.equal(-1);
  909. });
  910. it('should not wrap around to the bottom', () => {
  911. expect(widget.activeCellIndex).to.equal(0);
  912. NotebookActions.moveUp(widget);
  913. expect(widget.activeCellIndex).to.equal(0);
  914. });
  915. it('should be undo-able', () => {
  916. widget.activeCellIndex++;
  917. const source = widget.activeCell.model.value.text;
  918. NotebookActions.moveUp(widget);
  919. expect(widget.model.cells.get(0).value.text).to.equal(source);
  920. NotebookActions.undo(widget);
  921. expect(widget.model.cells.get(1).value.text).to.equal(source);
  922. });
  923. });
  924. describe('#moveDown()', () => {
  925. it('should move the selected cells down', () => {
  926. NotebookActions.extendSelectionBelow(widget);
  927. NotebookActions.moveDown(widget);
  928. expect(widget.isSelected(widget.widgets[0])).to.equal(false);
  929. expect(widget.isSelected(widget.widgets[1])).to.equal(true);
  930. expect(widget.isSelected(widget.widgets[2])).to.equal(true);
  931. expect(widget.activeCellIndex).to.equal(2);
  932. });
  933. it('should be a no-op if there is no model', () => {
  934. widget.model = null;
  935. NotebookActions.moveUp(widget);
  936. expect(widget.activeCellIndex).to.equal(-1);
  937. });
  938. it('should not wrap around to the top', () => {
  939. widget.activeCellIndex = widget.widgets.length - 1;
  940. NotebookActions.moveDown(widget);
  941. expect(widget.activeCellIndex).to.equal(widget.widgets.length - 1);
  942. });
  943. it('should be undo-able', () => {
  944. const source = widget.activeCell.model.value.text;
  945. NotebookActions.moveDown(widget);
  946. expect(widget.model.cells.get(1).value.text).to.equal(source);
  947. NotebookActions.undo(widget);
  948. expect(widget.model.cells.get(0).value.text).to.equal(source);
  949. });
  950. });
  951. describe('#copy()', () => {
  952. it('should copy the selected cells to a NBTestUtils.clipboard', () => {
  953. const next = widget.widgets[1];
  954. widget.select(next);
  955. NotebookActions.copy(widget);
  956. expect(NBTestUtils.clipboard.hasData(JUPYTER_CELL_MIME)).to.equal(true);
  957. const data = NBTestUtils.clipboard.getData(JUPYTER_CELL_MIME);
  958. expect(data.length).to.equal(2);
  959. });
  960. it('should be a no-op if there is no model', () => {
  961. widget.model = null;
  962. NotebookActions.copy(widget);
  963. expect(NBTestUtils.clipboard.hasData(JUPYTER_CELL_MIME)).to.equal(
  964. false
  965. );
  966. });
  967. it('should change to command mode', () => {
  968. widget.mode = 'edit';
  969. NotebookActions.copy(widget);
  970. expect(widget.mode).to.equal('command');
  971. });
  972. it('should delete metadata.deletable', () => {
  973. const next = widget.widgets[1];
  974. widget.select(next);
  975. next.model.metadata.set('deletable', false);
  976. NotebookActions.copy(widget);
  977. const data = NBTestUtils.clipboard.getData(
  978. JUPYTER_CELL_MIME
  979. ) as JSONArray;
  980. data.map(cell => {
  981. expect(((cell as JSONObject).metadata as JSONObject).deletable).to.be
  982. .undefined;
  983. });
  984. });
  985. });
  986. describe('#cut()', () => {
  987. it('should cut the selected cells to a NBTestUtils.clipboard', () => {
  988. const next = widget.widgets[1];
  989. widget.select(next);
  990. const count = widget.widgets.length;
  991. NotebookActions.cut(widget);
  992. expect(widget.widgets.length).to.equal(count - 2);
  993. });
  994. it('should be a no-op if there is no model', () => {
  995. widget.model = null;
  996. NotebookActions.cut(widget);
  997. expect(NBTestUtils.clipboard.hasData(JUPYTER_CELL_MIME)).to.equal(
  998. false
  999. );
  1000. });
  1001. it('should change to command mode', () => {
  1002. widget.mode = 'edit';
  1003. NotebookActions.cut(widget);
  1004. expect(widget.mode).to.equal('command');
  1005. });
  1006. it('should be undo-able', () => {
  1007. const source = widget.activeCell.model.value.text;
  1008. NotebookActions.cut(widget);
  1009. NotebookActions.undo(widget);
  1010. expect(widget.widgets[0].model.value.text).to.equal(source);
  1011. });
  1012. it('should add a new code cell if all cells were cut', async () => {
  1013. for (let i = 0; i < widget.widgets.length; i++) {
  1014. widget.select(widget.widgets[i]);
  1015. }
  1016. NotebookActions.cut(widget);
  1017. await sleep();
  1018. expect(widget.widgets.length).to.equal(1);
  1019. expect(widget.activeCell).to.be.an.instanceof(CodeCell);
  1020. });
  1021. });
  1022. describe('#paste()', () => {
  1023. it('should paste cells from a NBTestUtils.clipboard', () => {
  1024. const source = widget.activeCell.model.value.text;
  1025. const next = widget.widgets[1];
  1026. widget.select(next);
  1027. const count = widget.widgets.length;
  1028. NotebookActions.cut(widget);
  1029. widget.activeCellIndex = 1;
  1030. NotebookActions.paste(widget);
  1031. expect(widget.widgets.length).to.equal(count);
  1032. expect(widget.widgets[2].model.value.text).to.equal(source);
  1033. expect(widget.activeCellIndex).to.equal(3);
  1034. });
  1035. it('should be a no-op if there is no model', () => {
  1036. NotebookActions.copy(widget);
  1037. widget.model = null;
  1038. NotebookActions.paste(widget);
  1039. expect(widget.activeCellIndex).to.equal(-1);
  1040. });
  1041. it('should be a no-op if there is no cell data on the NBTestUtils.clipboard', () => {
  1042. const count = widget.widgets.length;
  1043. NotebookActions.paste(widget);
  1044. expect(widget.widgets.length).to.equal(count);
  1045. });
  1046. it('should change to command mode', () => {
  1047. widget.mode = 'edit';
  1048. NotebookActions.cut(widget);
  1049. NotebookActions.paste(widget);
  1050. expect(widget.mode).to.equal('command');
  1051. });
  1052. it('should be undo-able', () => {
  1053. const next = widget.widgets[1];
  1054. widget.select(next);
  1055. const count = widget.widgets.length;
  1056. NotebookActions.cut(widget);
  1057. widget.activeCellIndex = 1;
  1058. NotebookActions.paste(widget);
  1059. NotebookActions.undo(widget);
  1060. expect(widget.widgets.length).to.equal(count - 2);
  1061. });
  1062. });
  1063. describe('#undo()', () => {
  1064. it('should undo a cell action', () => {
  1065. const count = widget.widgets.length;
  1066. const next = widget.widgets[1];
  1067. widget.select(next);
  1068. NotebookActions.deleteCells(widget);
  1069. NotebookActions.undo(widget);
  1070. expect(widget.widgets.length).to.equal(count);
  1071. });
  1072. it('should switch the widget to command mode', () => {
  1073. widget.mode = 'edit';
  1074. NotebookActions.undo(widget);
  1075. expect(widget.mode).to.equal('command');
  1076. });
  1077. it('should be a no-op if there is no model', () => {
  1078. widget.model = null;
  1079. NotebookActions.undo(widget);
  1080. expect(widget.activeCellIndex).to.equal(-1);
  1081. });
  1082. it('should be a no-op if there are no cell actions to undo', () => {
  1083. const count = widget.widgets.length;
  1084. NotebookActions.deleteCells(widget);
  1085. widget.model.cells.clearUndo();
  1086. NotebookActions.undo(widget);
  1087. expect(widget.widgets.length).to.equal(count - 1);
  1088. });
  1089. });
  1090. describe('#redo()', () => {
  1091. it('should redo a cell action', () => {
  1092. const count = widget.widgets.length;
  1093. const next = widget.widgets[1];
  1094. widget.select(next);
  1095. NotebookActions.deleteCells(widget);
  1096. NotebookActions.undo(widget);
  1097. NotebookActions.redo(widget);
  1098. expect(widget.widgets.length).to.equal(count - 2);
  1099. });
  1100. it('should switch the widget to command mode', () => {
  1101. NotebookActions.undo(widget);
  1102. widget.mode = 'edit';
  1103. NotebookActions.redo(widget);
  1104. expect(widget.mode).to.equal('command');
  1105. });
  1106. it('should be a no-op if there is no model', () => {
  1107. NotebookActions.undo(widget);
  1108. widget.model = null;
  1109. NotebookActions.redo(widget);
  1110. expect(widget.activeCellIndex).to.equal(-1);
  1111. });
  1112. it('should be a no-op if there are no cell actions to redo', () => {
  1113. const count = widget.widgets.length;
  1114. NotebookActions.redo(widget);
  1115. expect(widget.widgets.length).to.equal(count);
  1116. });
  1117. });
  1118. describe('#toggleAllLineNumbers()', () => {
  1119. it('should toggle line numbers on all cells', () => {
  1120. const state = widget.activeCell.editor.getOption('lineNumbers');
  1121. NotebookActions.toggleAllLineNumbers(widget);
  1122. for (let i = 0; i < widget.widgets.length; i++) {
  1123. const lineNumbers = widget.widgets[i].editor.getOption('lineNumbers');
  1124. expect(lineNumbers).to.equal(!state);
  1125. }
  1126. });
  1127. it('should be based on the state of the active cell', () => {
  1128. const state = widget.activeCell.editor.getOption('lineNumbers');
  1129. for (let i = 1; i < widget.widgets.length; i++) {
  1130. widget.widgets[i].editor.setOption('lineNumbers', !state);
  1131. }
  1132. NotebookActions.toggleAllLineNumbers(widget);
  1133. for (let i = 0; i < widget.widgets.length; i++) {
  1134. const lineNumbers = widget.widgets[i].editor.getOption('lineNumbers');
  1135. expect(lineNumbers).to.equal(!state);
  1136. }
  1137. });
  1138. it('should preserve the widget mode', () => {
  1139. NotebookActions.toggleAllLineNumbers(widget);
  1140. expect(widget.mode).to.equal('command');
  1141. widget.mode = 'edit';
  1142. NotebookActions.toggleAllLineNumbers(widget);
  1143. expect(widget.mode).to.equal('edit');
  1144. });
  1145. it('should be a no-op if there is no model', () => {
  1146. widget.model = null;
  1147. NotebookActions.toggleAllLineNumbers(widget);
  1148. expect(widget.activeCellIndex).to.equal(-1);
  1149. });
  1150. });
  1151. describe('#clearOutputs()', () => {
  1152. it('should clear the outputs on the selected cells', () => {
  1153. // Select the next code cell that has outputs.
  1154. let index = 0;
  1155. for (let i = 1; i < widget.widgets.length; i++) {
  1156. const cell = widget.widgets[i];
  1157. if (cell instanceof CodeCell && cell.model.outputs.length) {
  1158. widget.select(cell);
  1159. index = i;
  1160. break;
  1161. }
  1162. }
  1163. NotebookActions.clearOutputs(widget);
  1164. let cell = widget.widgets[0] as CodeCell;
  1165. expect(cell.model.outputs.length).to.equal(0);
  1166. cell = widget.widgets[index] as CodeCell;
  1167. expect(cell.model.outputs.length).to.equal(0);
  1168. });
  1169. it('should preserve the widget mode', () => {
  1170. NotebookActions.clearOutputs(widget);
  1171. expect(widget.mode).to.equal('command');
  1172. widget.mode = 'edit';
  1173. NotebookActions.clearOutputs(widget);
  1174. expect(widget.mode).to.equal('edit');
  1175. });
  1176. it('should be a no-op if there is no model', () => {
  1177. widget.model = null;
  1178. NotebookActions.clearOutputs(widget);
  1179. expect(widget.activeCellIndex).to.equal(-1);
  1180. });
  1181. });
  1182. describe('#clearAllOutputs()', () => {
  1183. it('should clear the outputs on all cells', () => {
  1184. const next = widget.widgets[1];
  1185. widget.select(next);
  1186. NotebookActions.clearAllOutputs(widget);
  1187. for (let i = 0; i < widget.widgets.length; i++) {
  1188. const cell = widget.widgets[i];
  1189. if (cell instanceof CodeCell) {
  1190. expect(cell.model.outputs.length).to.equal(0);
  1191. }
  1192. }
  1193. });
  1194. it('should preserve the widget mode', () => {
  1195. NotebookActions.clearAllOutputs(widget);
  1196. expect(widget.mode).to.equal('command');
  1197. widget.mode = 'edit';
  1198. NotebookActions.clearAllOutputs(widget);
  1199. expect(widget.mode).to.equal('edit');
  1200. });
  1201. it('should be a no-op if there is no model', () => {
  1202. widget.model = null;
  1203. NotebookActions.clearAllOutputs(widget);
  1204. expect(widget.activeCellIndex).to.equal(-1);
  1205. });
  1206. });
  1207. describe('#setMarkdownHeader()', () => {
  1208. it('should set the markdown header level of selected cells', () => {
  1209. const next = widget.widgets[1];
  1210. widget.select(next);
  1211. NotebookActions.setMarkdownHeader(widget, 2);
  1212. expect(widget.activeCell.model.value.text.slice(0, 3)).to.equal('## ');
  1213. expect(next.model.value.text.slice(0, 3)).to.equal('## ');
  1214. });
  1215. it('should convert the cells to markdown type', () => {
  1216. NotebookActions.setMarkdownHeader(widget, 2);
  1217. expect(widget.activeCell).to.be.an.instanceof(MarkdownCell);
  1218. });
  1219. it('should be clamped between 1 and 6', () => {
  1220. NotebookActions.setMarkdownHeader(widget, -1);
  1221. expect(widget.activeCell.model.value.text.slice(0, 2)).to.equal('# ');
  1222. NotebookActions.setMarkdownHeader(widget, 10);
  1223. expect(widget.activeCell.model.value.text.slice(0, 7)).to.equal(
  1224. '###### '
  1225. );
  1226. });
  1227. it('should be a no-op if there is no model', () => {
  1228. widget.model = null;
  1229. NotebookActions.setMarkdownHeader(widget, 1);
  1230. expect(widget.activeCellIndex).to.equal(-1);
  1231. });
  1232. it('should replace an existing header', () => {
  1233. widget.activeCell.model.value.text = '# foo';
  1234. NotebookActions.setMarkdownHeader(widget, 2);
  1235. expect(widget.activeCell.model.value.text).to.equal('## foo');
  1236. });
  1237. it('should replace leading white space', () => {
  1238. widget.activeCell.model.value.text = ' foo';
  1239. NotebookActions.setMarkdownHeader(widget, 2);
  1240. expect(widget.activeCell.model.value.text).to.equal('## foo');
  1241. });
  1242. it('should unrender the cells', () => {
  1243. NotebookActions.setMarkdownHeader(widget, 1);
  1244. expect((widget.activeCell as MarkdownCell).rendered).to.equal(false);
  1245. });
  1246. });
  1247. describe('#trust()', () => {
  1248. it('should trust the notebook cells if the user accepts', async () => {
  1249. const model = widget.model;
  1250. widget.model.fromJSON(NBTestUtils.DEFAULT_CONTENT);
  1251. const cell = model.cells.get(0);
  1252. expect(cell.trusted).to.not.equal(true);
  1253. let promise = NotebookActions.trust(widget);
  1254. await acceptDialog();
  1255. await promise;
  1256. expect(cell.trusted).to.equal(true);
  1257. });
  1258. it('should not trust the notebook cells if the user aborts', async () => {
  1259. const model = widget.model;
  1260. model.fromJSON(NBTestUtils.DEFAULT_CONTENT);
  1261. const cell = model.cells.get(0);
  1262. expect(cell.trusted).to.not.equal(true);
  1263. let promise = NotebookActions.trust(widget);
  1264. await dismissDialog();
  1265. await promise;
  1266. expect(cell.trusted).to.not.equal(true);
  1267. });
  1268. it('should be a no-op if the model is `null`', async () => {
  1269. widget.model = null;
  1270. await NotebookActions.trust(widget);
  1271. });
  1272. it('should show a dialog if all cells are trusted', async () => {
  1273. const model = widget.model;
  1274. widget.model.fromJSON(NBTestUtils.DEFAULT_CONTENT);
  1275. model.fromJSON(NBTestUtils.DEFAULT_CONTENT);
  1276. for (let i = 0; i < model.cells.length; i++) {
  1277. const cell = model.cells.get(i);
  1278. cell.trusted = true;
  1279. }
  1280. const promise = NotebookActions.trust(widget);
  1281. await acceptDialog();
  1282. await promise;
  1283. });
  1284. });
  1285. });
  1286. });