default.spec.ts 16 KB

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