default-toolbar.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. // Copyright (c) Jupyter Development Team.
  2. import 'jest';
  3. import { simulate } from 'simulate-event';
  4. import { PromiseDelegate } from '@lumino/coreutils';
  5. import { Widget } from '@lumino/widgets';
  6. import { Context } from '@jupyterlab/docregistry';
  7. import { CodeCell, MarkdownCell } from '@jupyterlab/cells';
  8. import {
  9. INotebookModel,
  10. NotebookActions,
  11. NotebookPanel,
  12. ToolbarItems
  13. } from '../src';
  14. import {
  15. initNotebookContext,
  16. signalToPromise,
  17. sleep,
  18. framePromise,
  19. acceptDialog
  20. } from '@jupyterlab/testutils';
  21. import { JupyterServer } from '@jupyterlab/testutils/lib/start_jupyter_server';
  22. import * as utils from './utils';
  23. const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
  24. const server = new JupyterServer();
  25. beforeAll(async () => {
  26. jest.setTimeout(20000);
  27. await server.start();
  28. });
  29. afterAll(async () => {
  30. await server.shutdown();
  31. });
  32. describe('@jupyterlab/notebook', () => {
  33. describe('ToolbarItems', () => {
  34. describe('noKernel', () => {
  35. let context: Context<INotebookModel>;
  36. let panel: NotebookPanel;
  37. beforeEach(async () => {
  38. context = await initNotebookContext();
  39. panel = utils.createNotebookPanel(context);
  40. context.model.fromJSON(utils.DEFAULT_CONTENT);
  41. });
  42. afterEach(() => {
  43. panel.dispose();
  44. context.dispose();
  45. });
  46. describe('#createSaveButton()', () => {
  47. it('should save when clicked', async () => {
  48. const button = ToolbarItems.createSaveButton(panel);
  49. Widget.attach(button, document.body);
  50. const promise = signalToPromise(context.fileChanged);
  51. await framePromise();
  52. simulate(button.node.firstChild as HTMLElement, 'mousedown');
  53. await promise;
  54. button.dispose();
  55. });
  56. it("should add an inline svg node with the 'save' icon", async () => {
  57. const button = ToolbarItems.createSaveButton(panel);
  58. Widget.attach(button, document.body);
  59. await framePromise();
  60. expect(
  61. button.node.querySelector("[data-icon$='save']")
  62. ).toBeDefined();
  63. });
  64. });
  65. describe('#createInsertButton()', () => {
  66. it('should insert below when clicked', async () => {
  67. const button = ToolbarItems.createInsertButton(panel);
  68. Widget.attach(button, document.body);
  69. await framePromise();
  70. simulate(button.node.firstChild as HTMLElement, 'mousedown');
  71. expect(panel.content.activeCellIndex).toBe(1);
  72. expect(panel.content.activeCell).toBeInstanceOf(CodeCell);
  73. button.dispose();
  74. });
  75. it("should add an inline svg node with the 'add' icon", async () => {
  76. const button = ToolbarItems.createInsertButton(panel);
  77. Widget.attach(button, document.body);
  78. await framePromise();
  79. expect(button.node.querySelector("[data-icon$='add']")).toBeDefined();
  80. button.dispose();
  81. });
  82. });
  83. describe('#createCutButton()', () => {
  84. it('should cut when clicked', async () => {
  85. const button = ToolbarItems.createCutButton(panel);
  86. const count = panel.content.widgets.length;
  87. Widget.attach(button, document.body);
  88. await framePromise();
  89. simulate(button.node.firstChild as HTMLElement, 'mousedown');
  90. expect(panel.content.widgets.length).toBe(count - 1);
  91. expect(utils.clipboard.hasData(JUPYTER_CELL_MIME)).toBe(true);
  92. button.dispose();
  93. });
  94. it("should add an inline svg node with the 'cut' icon", async () => {
  95. const button = ToolbarItems.createCutButton(panel);
  96. Widget.attach(button, document.body);
  97. await framePromise();
  98. expect(button.node.querySelector("[data-icon$='cut']")).toBeDefined();
  99. button.dispose();
  100. });
  101. });
  102. describe('#createCopyButton()', () => {
  103. it('should copy when clicked', async () => {
  104. const button = ToolbarItems.createCopyButton(panel);
  105. const count = panel.content.widgets.length;
  106. Widget.attach(button, document.body);
  107. await framePromise();
  108. simulate(button.node.firstChild as HTMLElement, 'mousedown');
  109. expect(panel.content.widgets.length).toBe(count);
  110. expect(utils.clipboard.hasData(JUPYTER_CELL_MIME)).toBe(true);
  111. button.dispose();
  112. });
  113. it("should add an inline svg node with the 'copy' icon", async () => {
  114. const button = ToolbarItems.createCopyButton(panel);
  115. Widget.attach(button, document.body);
  116. await framePromise();
  117. expect(
  118. button.node.querySelector("[data-icon$='copy']")
  119. ).toBeDefined();
  120. button.dispose();
  121. });
  122. });
  123. describe('#createPasteButton()', () => {
  124. it('should paste when clicked', async () => {
  125. const button = ToolbarItems.createPasteButton(panel);
  126. const count = panel.content.widgets.length;
  127. Widget.attach(button, document.body);
  128. await framePromise();
  129. NotebookActions.copy(panel.content);
  130. simulate(button.node.firstChild as HTMLElement, 'mousedown');
  131. await sleep();
  132. expect(panel.content.widgets.length).toBe(count + 1);
  133. button.dispose();
  134. });
  135. it("should add an inline svg node with the 'paste' icon", async () => {
  136. const button = ToolbarItems.createPasteButton(panel);
  137. Widget.attach(button, document.body);
  138. await framePromise();
  139. expect(
  140. button.node.querySelector("[data-icon$='paste']")
  141. ).toBeDefined();
  142. button.dispose();
  143. });
  144. });
  145. describe('#createCellTypeItem()', () => {
  146. it('should track the cell type of the current cell', async () => {
  147. const item = ToolbarItems.createCellTypeItem(panel);
  148. Widget.attach(item, document.body);
  149. await framePromise();
  150. const node = item.node.getElementsByTagName(
  151. 'select'
  152. )[0] as HTMLSelectElement;
  153. expect(node.value).toBe('code');
  154. panel.content.activeCellIndex++;
  155. await framePromise();
  156. expect(node.value).toBe('markdown');
  157. item.dispose();
  158. });
  159. it("should display `'-'` if multiple cell types are selected", async () => {
  160. const item = ToolbarItems.createCellTypeItem(panel);
  161. Widget.attach(item, document.body);
  162. await framePromise();
  163. const node = item.node.getElementsByTagName(
  164. 'select'
  165. )[0] as HTMLSelectElement;
  166. expect(node.value).toBe('code');
  167. panel.content.select(panel.content.widgets[1]);
  168. await framePromise();
  169. expect(node.value).toBe('-');
  170. item.dispose();
  171. });
  172. it('should display the active cell type if multiple cells of the same type are selected', async () => {
  173. const item = ToolbarItems.createCellTypeItem(panel);
  174. Widget.attach(item, document.body);
  175. await framePromise();
  176. const node = item.node.getElementsByTagName(
  177. 'select'
  178. )[0] as HTMLSelectElement;
  179. expect(node.value).toBe('code');
  180. const cell = panel.model!.contentFactory.createCodeCell({});
  181. panel.model!.cells.insert(1, cell);
  182. panel.content.select(panel.content.widgets[1]);
  183. await framePromise();
  184. expect(node.value).toBe('code');
  185. item.dispose();
  186. });
  187. });
  188. describe('#getDefaultItems()', () => {
  189. it('should return the default items of the panel toolbar', () => {
  190. const names = ToolbarItems.getDefaultItems(panel).map(item => {
  191. const name = item.name;
  192. item.widget.dispose();
  193. return name;
  194. });
  195. expect(names).toEqual([
  196. 'save',
  197. 'insert',
  198. 'cut',
  199. 'copy',
  200. 'paste',
  201. 'run',
  202. 'interrupt',
  203. 'restart',
  204. 'restart-and-run',
  205. 'cellType',
  206. 'spacer',
  207. 'kernelName',
  208. 'kernelStatus'
  209. ]);
  210. });
  211. });
  212. });
  213. describe('kernelRequired', () => {
  214. let context: Context<INotebookModel>;
  215. let panel: NotebookPanel;
  216. beforeEach(async function() {
  217. context = await initNotebookContext({ startKernel: true });
  218. panel = utils.createNotebookPanel(context);
  219. context.model.fromJSON(utils.DEFAULT_CONTENT);
  220. });
  221. afterEach(async () => {
  222. await context.sessionContext.shutdown();
  223. panel.dispose();
  224. context.dispose();
  225. });
  226. describe('#createRunButton()', () => {
  227. it('should run and advance when clicked', async () => {
  228. const button = ToolbarItems.createRunButton(panel);
  229. const widget = panel.content;
  230. // Clear and select the first two cells.
  231. const codeCell = widget.widgets[0] as CodeCell;
  232. codeCell.model.outputs.clear();
  233. widget.select(codeCell);
  234. const mdCell = widget.widgets[1] as MarkdownCell;
  235. mdCell.rendered = false;
  236. widget.select(mdCell);
  237. Widget.attach(button, document.body);
  238. const p = new PromiseDelegate();
  239. context.sessionContext.statusChanged.connect((sender, status) => {
  240. // Find the right status idle message
  241. if (status === 'idle' && codeCell.model.outputs.length > 0) {
  242. expect(mdCell.rendered).toBe(true);
  243. expect(widget.activeCellIndex).toBe(2);
  244. button.dispose();
  245. p.resolve(0);
  246. }
  247. });
  248. await framePromise();
  249. simulate(button.node.firstChild as HTMLElement, 'mousedown');
  250. await p.promise;
  251. });
  252. it("should add an inline svg node with the 'run' icon", async () => {
  253. const button = ToolbarItems.createRunButton(panel);
  254. Widget.attach(button, document.body);
  255. await framePromise();
  256. expect(button.node.querySelector("[data-icon$='run']")).toBeDefined();
  257. });
  258. });
  259. describe('#createRestartRunAllButton()', () => {
  260. it('should restart and run all when clicked', async () => {
  261. const button = ToolbarItems.createRestartRunAllButton(panel);
  262. const widget = panel.content;
  263. // Clear the first two cells.
  264. const codeCell = widget.widgets[0] as CodeCell;
  265. codeCell.model.outputs.clear();
  266. const mdCell = widget.widgets[1] as MarkdownCell;
  267. mdCell.rendered = false;
  268. Widget.attach(button, document.body);
  269. const p = new PromiseDelegate();
  270. context.sessionContext.statusChanged.connect((sender, status) => {
  271. // Find the right status idle message
  272. if (status === 'idle' && codeCell.model.outputs.length > 0) {
  273. expect(
  274. widget.widgets
  275. .filter(cell => cell.model.type === 'markdown')
  276. .every(cell => (cell as MarkdownCell).rendered)
  277. );
  278. expect(widget.activeCellIndex).toBe(
  279. widget.widgets.filter(cell => cell.model.type === 'code').length
  280. );
  281. button.dispose();
  282. p.resolve(0);
  283. }
  284. });
  285. await framePromise();
  286. simulate(button.node.firstChild as HTMLElement, 'mousedown');
  287. await acceptDialog();
  288. await p.promise;
  289. });
  290. it("should add an inline svg node with the 'fast-forward' icon", async () => {
  291. const button = ToolbarItems.createRestartRunAllButton(panel);
  292. Widget.attach(button, document.body);
  293. await framePromise();
  294. expect(
  295. button.node.querySelector("[data-icon$='fast-forward']")
  296. ).toBeDefined();
  297. });
  298. });
  299. });
  300. });
  301. });