actions.spec.ts 53 KB

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