default.spec.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. ABCWidgetFactory,
  5. Base64ModelFactory,
  6. Context,
  7. DocumentModel,
  8. DocumentRegistry,
  9. DocumentWidget,
  10. IDocumentWidget,
  11. TextModelFactory
  12. } from '@jupyterlab/docregistry';
  13. import { ServiceManager } from '@jupyterlab/services';
  14. import { sleep } from '@jupyterlab/testutils';
  15. import * as Mock from '@jupyterlab/testutils/lib/mock';
  16. import { toArray } from '@lumino/algorithm';
  17. import { UUID } from '@lumino/coreutils';
  18. import { Widget } from '@lumino/widgets';
  19. class WidgetFactory extends ABCWidgetFactory<IDocumentWidget> {
  20. protected createNewWidget(
  21. context: DocumentRegistry.Context
  22. ): IDocumentWidget {
  23. const content = new Widget();
  24. const widget = new DocumentWidget({ content, context });
  25. widget.addClass('WidgetFactory');
  26. return widget;
  27. }
  28. }
  29. function createFactory(): WidgetFactory {
  30. return new WidgetFactory({
  31. name: 'test',
  32. fileTypes: ['text']
  33. });
  34. }
  35. describe('docregistry/default', () => {
  36. describe('ABCWidgetFactory', () => {
  37. describe('#fileTypes', () => {
  38. it('should be the value passed in', () => {
  39. const factory = new WidgetFactory({
  40. name: 'test',
  41. fileTypes: ['text']
  42. });
  43. expect(factory.fileTypes).toEqual(['text']);
  44. });
  45. });
  46. describe('#name', () => {
  47. it('should be the value passed in', () => {
  48. const factory = new WidgetFactory({
  49. name: 'test',
  50. fileTypes: ['text']
  51. });
  52. expect(factory.name).toBe('test');
  53. });
  54. });
  55. describe('#defaultFor', () => {
  56. it('should default to an empty array', () => {
  57. const factory = new WidgetFactory({
  58. name: 'test',
  59. fileTypes: ['text']
  60. });
  61. expect(factory.defaultFor).toEqual([]);
  62. });
  63. it('should be the value passed in', () => {
  64. const factory = new WidgetFactory({
  65. name: 'test',
  66. fileTypes: ['text'],
  67. defaultFor: ['text']
  68. });
  69. expect(factory.defaultFor).toEqual(['text']);
  70. });
  71. });
  72. describe('#defaultRendered', () => {
  73. it('should default to an empty array', () => {
  74. const factory = new WidgetFactory({
  75. name: 'test',
  76. fileTypes: ['text']
  77. });
  78. expect(factory.defaultRendered).toEqual([]);
  79. });
  80. it('should be the value passed in', () => {
  81. const factory = new WidgetFactory({
  82. name: 'test',
  83. fileTypes: ['text'],
  84. defaultRendered: ['text']
  85. });
  86. expect(factory.defaultRendered).toEqual(['text']);
  87. });
  88. });
  89. describe('#readOnly', () => {
  90. it('should default to false', () => {
  91. const factory = new WidgetFactory({
  92. name: 'test',
  93. fileTypes: ['text']
  94. });
  95. expect(factory.readOnly).toBe(false);
  96. });
  97. it('should be the value passed in', () => {
  98. const factory = new WidgetFactory({
  99. name: 'test',
  100. fileTypes: ['text'],
  101. readOnly: true
  102. });
  103. expect(factory.readOnly).toBe(true);
  104. });
  105. });
  106. describe('#modelName', () => {
  107. it('should default to `text`', () => {
  108. const factory = new WidgetFactory({
  109. name: 'test',
  110. fileTypes: ['text']
  111. });
  112. expect(factory.modelName).toBe('text');
  113. });
  114. it('should be the value passed in', () => {
  115. const factory = new WidgetFactory({
  116. name: 'test',
  117. fileTypes: ['text'],
  118. modelName: 'notebook'
  119. });
  120. expect(factory.modelName).toBe('notebook');
  121. });
  122. });
  123. describe('#preferKernel', () => {
  124. it('should default to false', () => {
  125. const factory = new WidgetFactory({
  126. name: 'test',
  127. fileTypes: ['text']
  128. });
  129. expect(factory.preferKernel).toBe(false);
  130. });
  131. it('should be the value passed in', () => {
  132. const factory = new WidgetFactory({
  133. name: 'test',
  134. fileTypes: ['text'],
  135. preferKernel: true
  136. });
  137. expect(factory.preferKernel).toBe(true);
  138. });
  139. });
  140. describe('#canStartKernel', () => {
  141. it('should default to false', () => {
  142. const factory = new WidgetFactory({
  143. name: 'test',
  144. fileTypes: ['text']
  145. });
  146. expect(factory.canStartKernel).toBe(false);
  147. });
  148. it('should be the value passed in', () => {
  149. const factory = new WidgetFactory({
  150. name: 'test',
  151. fileTypes: ['text'],
  152. canStartKernel: true
  153. });
  154. expect(factory.canStartKernel).toBe(true);
  155. });
  156. it('should have toolbar items', async () => {
  157. const factory = new WidgetFactory({
  158. name: 'test',
  159. fileTypes: ['text'],
  160. toolbarFactory: () => [
  161. {
  162. name: 'foo',
  163. widget: new Widget()
  164. },
  165. {
  166. name: 'bar',
  167. widget: new Widget()
  168. }
  169. ]
  170. });
  171. const context = await Mock.createFileContext();
  172. const widget = factory.createNew(context);
  173. const widget2 = factory.createNew(context);
  174. expect(toArray(widget.toolbar.names())).toEqual(['foo', 'bar']);
  175. expect(toArray(widget2.toolbar.names())).toEqual(['foo', 'bar']);
  176. expect(toArray(widget.toolbar.children()).length).toBe(2);
  177. expect(toArray(widget2.toolbar.children()).length).toBe(2);
  178. });
  179. });
  180. describe('#isDisposed', () => {
  181. it('should get whether the factory has been disposed', () => {
  182. const factory = createFactory();
  183. expect(factory.isDisposed).toBe(false);
  184. factory.dispose();
  185. expect(factory.isDisposed).toBe(true);
  186. });
  187. });
  188. describe('#dispose()', () => {
  189. it('should dispose of the resources held by the factory', () => {
  190. const factory = createFactory();
  191. factory.dispose();
  192. expect(factory.isDisposed).toBe(true);
  193. });
  194. it('should be safe to call multiple times', () => {
  195. const factory = createFactory();
  196. factory.dispose();
  197. factory.dispose();
  198. expect(factory.isDisposed).toBe(true);
  199. });
  200. });
  201. describe('#createNew()', () => {
  202. it('should create a new widget given a document model and a context', async () => {
  203. const factory = createFactory();
  204. const context = await Mock.createFileContext();
  205. const widget = factory.createNew(context);
  206. expect(widget).toBeInstanceOf(Widget);
  207. });
  208. it('should take an optional source widget for cloning', async () => {
  209. const factory = createFactory();
  210. const context = await Mock.createFileContext();
  211. const widget = factory.createNew(context);
  212. const clonedWidget: IDocumentWidget = factory.createNew(
  213. context,
  214. widget
  215. );
  216. expect(clonedWidget).not.toBe(widget);
  217. expect(clonedWidget.hasClass('WidgetFactory')).toBe(true);
  218. expect(clonedWidget.context).toBe(widget.context);
  219. });
  220. });
  221. });
  222. describe('Base64ModelFactory', () => {
  223. describe('#name', () => {
  224. it('should get the name of the model type', () => {
  225. const factory = new Base64ModelFactory();
  226. expect(factory.name).toBe('base64');
  227. });
  228. });
  229. describe('#contentType', () => {
  230. it('should get the file type', () => {
  231. const factory = new Base64ModelFactory();
  232. expect(factory.contentType).toBe('file');
  233. });
  234. });
  235. describe('#fileFormat', () => {
  236. it('should get the file format', () => {
  237. const factory = new Base64ModelFactory();
  238. expect(factory.fileFormat).toBe('base64');
  239. });
  240. });
  241. });
  242. describe('DocumentModel', () => {
  243. describe('#constructor()', () => {
  244. it('should create a new document model', () => {
  245. const model = new DocumentModel();
  246. expect(model).toBeInstanceOf(DocumentModel);
  247. });
  248. it('should accept an optional language preference', () => {
  249. const model = new DocumentModel('foo');
  250. expect(model.defaultKernelLanguage).toBe('foo');
  251. });
  252. });
  253. describe('#isDisposed', () => {
  254. it('should get whether the model has been disposed', () => {
  255. const model = new DocumentModel();
  256. expect(model.isDisposed).toBe(false);
  257. model.dispose();
  258. expect(model.isDisposed).toBe(true);
  259. });
  260. });
  261. describe('#contentChanged', () => {
  262. it('should be emitted when the content of the model changes', () => {
  263. const model = new DocumentModel();
  264. let called = false;
  265. model.contentChanged.connect((sender, args) => {
  266. expect(sender).toBe(model);
  267. expect(args).toBeUndefined();
  268. called = true;
  269. });
  270. model.fromString('foo');
  271. expect(called).toBe(true);
  272. });
  273. it('should not be emitted if the content does not change', () => {
  274. const model = new DocumentModel();
  275. let called = false;
  276. model.contentChanged.connect(() => {
  277. called = true;
  278. });
  279. model.fromString('');
  280. expect(called).toBe(false);
  281. });
  282. });
  283. describe('#stateChanged', () => {
  284. it('should be emitted when the state of the model changes', () => {
  285. const model = new DocumentModel();
  286. let called = false;
  287. model.stateChanged.connect((sender, args) => {
  288. expect(sender).toBe(model);
  289. expect(args.name).toBe('readOnly');
  290. expect(args.oldValue).toBe(false);
  291. expect(args.newValue).toBe(true);
  292. called = true;
  293. });
  294. model.readOnly = true;
  295. expect(called).toBe(true);
  296. });
  297. it('should not be emitted if the state does not change', () => {
  298. const model = new DocumentModel();
  299. let called = false;
  300. model.stateChanged.connect(() => {
  301. called = true;
  302. });
  303. model.dirty = false;
  304. expect(called).toBe(false);
  305. });
  306. });
  307. describe('#dirty', () => {
  308. it('should get the dirty state of the document', () => {
  309. const model = new DocumentModel();
  310. expect(model.dirty).toBe(false);
  311. });
  312. it('should emit `stateChanged` when changed', () => {
  313. const model = new DocumentModel();
  314. let called = false;
  315. model.stateChanged.connect((sender, args) => {
  316. expect(sender).toBe(model);
  317. expect(args.name).toBe('dirty');
  318. expect(args.oldValue).toBe(false);
  319. expect(args.newValue).toBe(true);
  320. called = true;
  321. });
  322. model.dirty = true;
  323. expect(called).toBe(true);
  324. });
  325. it('should not emit `stateChanged` when not changed', () => {
  326. const model = new DocumentModel();
  327. let called = false;
  328. model.stateChanged.connect(() => {
  329. called = true;
  330. });
  331. model.dirty = false;
  332. expect(called).toBe(false);
  333. });
  334. });
  335. describe('#readOnly', () => {
  336. it('should get the read only state of the document', () => {
  337. const model = new DocumentModel();
  338. expect(model.readOnly).toBe(false);
  339. });
  340. it('should emit `stateChanged` when changed', () => {
  341. const model = new DocumentModel();
  342. let called = false;
  343. model.stateChanged.connect((sender, args) => {
  344. expect(sender).toBe(model);
  345. expect(args.name).toBe('readOnly');
  346. expect(args.oldValue).toBe(false);
  347. expect(args.newValue).toBe(true);
  348. called = true;
  349. });
  350. model.readOnly = true;
  351. expect(called).toBe(true);
  352. });
  353. it('should not emit `stateChanged` when not changed', () => {
  354. const model = new DocumentModel();
  355. let called = false;
  356. model.stateChanged.connect(() => {
  357. called = true;
  358. });
  359. model.readOnly = false;
  360. expect(called).toBe(false);
  361. });
  362. });
  363. describe('#defaultKernelName', () => {
  364. it('should get the default kernel name of the document', () => {
  365. const model = new DocumentModel();
  366. expect(model.defaultKernelName).toBe('');
  367. });
  368. });
  369. describe('defaultKernelLanguage', () => {
  370. it('should get the default kernel language of the document', () => {
  371. const model = new DocumentModel();
  372. expect(model.defaultKernelLanguage).toBe('');
  373. });
  374. it('should be set by the constructor arg', () => {
  375. const model = new DocumentModel('foo');
  376. expect(model.defaultKernelLanguage).toBe('foo');
  377. });
  378. });
  379. describe('#dispose()', () => {
  380. it('should dispose of the resources held by the document manager', () => {
  381. const model = new DocumentModel();
  382. model.dispose();
  383. expect(model.isDisposed).toBe(true);
  384. });
  385. it('should be safe to call more than once', () => {
  386. const model = new DocumentModel();
  387. model.dispose();
  388. model.dispose();
  389. expect(model.isDisposed).toBe(true);
  390. });
  391. });
  392. describe('#toString()', () => {
  393. it('should serialize the model to a string', () => {
  394. const model = new DocumentModel();
  395. expect(model.toString()).toBe('');
  396. });
  397. });
  398. describe('#fromString()', () => {
  399. it('should deserialize the model from a string', () => {
  400. const model = new DocumentModel();
  401. model.fromString('foo');
  402. expect(model.toString()).toBe('foo');
  403. });
  404. });
  405. describe('#toJSON()', () => {
  406. it('should serialize the model to JSON', () => {
  407. const model = new DocumentModel();
  408. const data = { foo: 1 };
  409. model.fromJSON(data);
  410. expect(model.toJSON()).toEqual(data);
  411. });
  412. });
  413. describe('#fromJSON()', () => {
  414. it('should deserialize the model from JSON', () => {
  415. const model = new DocumentModel();
  416. const data: null = null;
  417. model.fromJSON(data);
  418. expect(model.toString()).toBe('null');
  419. });
  420. });
  421. });
  422. describe('TextModelFactory', () => {
  423. describe('#name', () => {
  424. it('should get the name of the model type', () => {
  425. const factory = new TextModelFactory();
  426. expect(factory.name).toBe('text');
  427. });
  428. });
  429. describe('#contentType', () => {
  430. it('should get the file type', () => {
  431. const factory = new TextModelFactory();
  432. expect(factory.contentType).toBe('file');
  433. });
  434. });
  435. describe('#fileFormat', () => {
  436. it('should get the file format', () => {
  437. const factory = new TextModelFactory();
  438. expect(factory.fileFormat).toBe('text');
  439. });
  440. });
  441. describe('#isDisposed', () => {
  442. it('should get whether the factory is disposed', () => {
  443. const factory = new TextModelFactory();
  444. expect(factory.isDisposed).toBe(false);
  445. factory.dispose();
  446. expect(factory.isDisposed).toBe(true);
  447. });
  448. });
  449. describe('#dispose()', () => {
  450. it('should dispose of the resources held by the factory', () => {
  451. const factory = new TextModelFactory();
  452. factory.dispose();
  453. expect(factory.isDisposed).toBe(true);
  454. });
  455. it('should be safe to call multiple times', () => {
  456. const factory = new TextModelFactory();
  457. factory.dispose();
  458. factory.dispose();
  459. expect(factory.isDisposed).toBe(true);
  460. });
  461. });
  462. describe('#createNew()', () => {
  463. it('should create a new model', () => {
  464. const factory = new TextModelFactory();
  465. const model = factory.createNew();
  466. expect(model).toBeInstanceOf(DocumentModel);
  467. });
  468. it('should accept a language preference', () => {
  469. const factory = new TextModelFactory();
  470. const model = factory.createNew('foo');
  471. expect(model.defaultKernelLanguage).toBe('foo');
  472. });
  473. });
  474. describe('#preferredLanguage()', () => {
  475. it('should get the preferred kernel language given an extension', () => {
  476. const factory = new TextModelFactory();
  477. expect(factory.preferredLanguage('.py')).toBe('python');
  478. expect(factory.preferredLanguage('.jl')).toBe('julia');
  479. });
  480. });
  481. });
  482. describe('DocumentWidget', () => {
  483. let manager: ServiceManager.IManager;
  484. let context: Context<DocumentRegistry.IModel>;
  485. let content: Widget;
  486. let widget: DocumentWidget;
  487. const setup = async () => {
  488. context = await Mock.createFileContext(false, manager);
  489. content = new Widget();
  490. widget = new DocumentWidget({ context, content });
  491. };
  492. beforeAll(async () => {
  493. manager = new Mock.ServiceManagerMock();
  494. await manager.ready;
  495. });
  496. describe('#constructor', () => {
  497. beforeEach(setup);
  498. it('should set the title for the path', () => {
  499. expect(widget.title.label).toBe(context.localPath);
  500. });
  501. it('should update the title when the path changes', async () => {
  502. const path = UUID.uuid4() + '.jl';
  503. await manager.contents.rename(context.path, path);
  504. expect(widget.title.label).toBe(path);
  505. });
  506. it('should add the dirty class when the model is dirty', async () => {
  507. context.model.fromString('bar');
  508. expect(widget.title.className).toContain('jp-mod-dirty');
  509. });
  510. it('should store the context', () => {
  511. expect(widget.context).toBe(context);
  512. });
  513. });
  514. describe('#revealed', () => {
  515. beforeEach(setup);
  516. it('should resolve after the reveal and context ready promises', async () => {
  517. const thisContext = new Context({
  518. manager,
  519. factory: new TextModelFactory(),
  520. path: UUID.uuid4()
  521. });
  522. const x = Object.create(null);
  523. const reveal = sleep(300, x);
  524. const contextReady = Promise.all([thisContext.ready, x]);
  525. const widget = new DocumentWidget({
  526. context: thisContext,
  527. content,
  528. reveal
  529. });
  530. expect(widget.isRevealed).toBe(false);
  531. // Our promise should resolve before the widget reveal promise.
  532. expect(await Promise.race([widget.revealed, reveal])).toBe(x);
  533. // The context ready promise should also resolve first.
  534. void thisContext.initialize(true);
  535. expect(await Promise.race([widget.revealed, contextReady])).toEqual([
  536. undefined,
  537. x
  538. ]);
  539. // The widget.revealed promise should finally resolve.
  540. expect(await widget.revealed).toBeUndefined();
  541. thisContext.dispose();
  542. });
  543. });
  544. });
  545. });