registry.spec.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  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 '@jupyterlab/coreutils';
  7. import {
  8. toArray
  9. } from '@phosphor/algorithm';
  10. import {
  11. DisposableDelegate, IDisposable
  12. } from '@phosphor/disposable';
  13. import {
  14. Widget
  15. } from '@phosphor/widgets';
  16. import {
  17. ABCWidgetFactory, Base64ModelFactory, DocumentRegistry, DocumentWidget, IDocumentWidget
  18. } from '@jupyterlab/docregistry';
  19. class WidgetFactory extends ABCWidgetFactory<IDocumentWidget> {
  20. protected createNewWidget(context: DocumentRegistry.Context): IDocumentWidget {
  21. const content = new Widget();
  22. const widget = new DocumentWidget({ content, context });
  23. widget.addClass('WidgetFactory');
  24. return widget;
  25. }
  26. }
  27. class WidgetExtension implements DocumentRegistry.WidgetExtension {
  28. createNew(widget: Widget, context: DocumentRegistry.Context): IDisposable {
  29. return new DisposableDelegate(null);
  30. }
  31. }
  32. function createFactory(modelName?: string) {
  33. return new WidgetFactory({
  34. name: uuid(),
  35. modelName: modelName || 'text',
  36. fileTypes: ['text', 'foobar'],
  37. defaultFor: ['text', 'foobar']
  38. });
  39. }
  40. describe('docregistry/registry', () => {
  41. describe('DocumentRegistry', () => {
  42. let registry: DocumentRegistry;
  43. beforeEach(() => {
  44. registry = new DocumentRegistry();
  45. registry.addFileType({
  46. name: 'foobar',
  47. extensions: ['.foo.bar']
  48. });
  49. });
  50. afterEach(() => {
  51. registry.dispose();
  52. });
  53. describe('#isDisposed', () => {
  54. it('should get whether the registry has been disposed', () => {
  55. expect(registry.isDisposed).to.be(false);
  56. registry.dispose();
  57. expect(registry.isDisposed).to.be(true);
  58. });
  59. });
  60. describe('#dispose()', () => {
  61. it('should dispose of the resources held by the registry', () => {
  62. registry.addFileType({ name: 'notebook', extensions: ['.ipynb'] });
  63. registry.dispose();
  64. expect(registry.isDisposed).to.be(true);
  65. });
  66. it('should be safe to call multiple times', () => {
  67. registry.dispose();
  68. registry.dispose();
  69. expect(registry.isDisposed).to.be(true);
  70. });
  71. });
  72. describe('#addWidgetFactory()', () => {
  73. it('should add the widget factory to the registry', () => {
  74. let factory = createFactory();
  75. registry.addWidgetFactory(factory);
  76. expect(registry.getWidgetFactory(factory.name)).to.be(factory);
  77. expect(registry.getWidgetFactory(factory.name.toUpperCase())).to.be(factory);
  78. });
  79. it('should become the global default if `*` is given as a defaultFor', () => {
  80. let factory = new WidgetFactory({
  81. name: 'global',
  82. fileTypes: ['*'],
  83. defaultFor: ['*']
  84. });
  85. registry.addWidgetFactory(factory);
  86. expect(registry.defaultWidgetFactory('*').name).to.be('global');
  87. });
  88. it('should override an existing global default', () => {
  89. registry.addWidgetFactory(new WidgetFactory({
  90. name: 'global',
  91. fileTypes: ['*'],
  92. defaultFor: ['*']
  93. }));
  94. let factory = new WidgetFactory({
  95. name: 'bar',
  96. fileTypes: ['*'],
  97. defaultFor: ['*']
  98. });
  99. registry.addWidgetFactory(factory);
  100. expect(registry.defaultWidgetFactory('*').name).to.be('bar');
  101. });
  102. it('should override an existing extension default', () => {
  103. registry.addWidgetFactory(createFactory());
  104. let factory = createFactory();
  105. registry.addWidgetFactory(factory);
  106. expect(registry.defaultWidgetFactory('a.foo.bar')).to.be(factory);
  107. });
  108. it('should be removed from the registry when disposed', () => {
  109. let factory = createFactory();
  110. let disposable = registry.addWidgetFactory(factory);
  111. disposable.dispose();
  112. expect(registry.getWidgetFactory('test')).to.be(void 0);
  113. });
  114. });
  115. describe('#addModelFactory()', () => {
  116. it('should add the model factory to the registry', () => {
  117. let factory = new Base64ModelFactory();
  118. registry.addModelFactory(factory);
  119. });
  120. it('should be a no-op a factory with the given `name` is already registered', () => {
  121. let factory = new Base64ModelFactory();
  122. registry.addModelFactory(factory);
  123. let disposable = registry.addModelFactory(new Base64ModelFactory());
  124. disposable.dispose();
  125. });
  126. it('should be a no-op if the same factory is already registered', () => {
  127. let factory = new Base64ModelFactory();
  128. registry.addModelFactory(factory);
  129. let disposable = registry.addModelFactory(factory);
  130. disposable.dispose();
  131. });
  132. it('should be removed from the registry when disposed', () => {
  133. let factory = new Base64ModelFactory();
  134. let disposable = registry.addModelFactory(factory);
  135. disposable.dispose();
  136. });
  137. });
  138. describe('#addWidgetExtension()', () => {
  139. it('should add a widget extension to the registry', () => {
  140. let extension = new WidgetExtension();
  141. registry.addWidgetExtension('foo', extension);
  142. expect(registry.widgetExtensions('foo').next()).to.be(extension);
  143. });
  144. it('should be a no-op if the extension is already registered for a given widget factory', () => {
  145. let extension = new WidgetExtension();
  146. registry.addWidgetExtension('foo', extension);
  147. let disposable = registry.addWidgetExtension('foo', extension);
  148. disposable.dispose();
  149. expect(registry.widgetExtensions('foo').next()).to.be(extension);
  150. });
  151. it('should be removed from the registry when disposed', () => {
  152. let extension = new WidgetExtension();
  153. let disposable = registry.addWidgetExtension('foo', extension);
  154. disposable.dispose();
  155. expect(toArray(registry.widgetExtensions('foo')).length).to.be(0);
  156. });
  157. });
  158. describe('#addFileType()', () => {
  159. it('should add a file type to the document registry', () => {
  160. registry = new DocumentRegistry({ initialFileTypes: [] });
  161. let fileType = { name: 'notebook', extensions: ['.ipynb'] };
  162. registry.addFileType(fileType);
  163. expect(registry.fileTypes().next().name).to.be(fileType.name);
  164. });
  165. it('should be removed from the registry when disposed', () => {
  166. registry = new DocumentRegistry({ initialFileTypes: [] });
  167. let fileType = { name: 'notebook', extensions: ['.ipynb'] };
  168. let disposable = registry.addFileType(fileType);
  169. disposable.dispose();
  170. expect(toArray(registry.fileTypes()).length).to.be(0);
  171. });
  172. it('should be a no-op if a file type of the same name is registered', () => {
  173. registry = new DocumentRegistry({ initialFileTypes: [] });
  174. let fileType = { name: 'notebook', extensions: ['.ipynb'] };
  175. registry.addFileType(fileType);
  176. let disposable = registry.addFileType(fileType);
  177. disposable.dispose();
  178. expect(registry.fileTypes().next().name).to.be(fileType.name);
  179. });
  180. });
  181. describe('#preferredWidgetFactories()', () => {
  182. beforeEach(() => {
  183. registry.addFileType({
  184. name: 'tablejson',
  185. extensions: ['.table.json']
  186. });
  187. });
  188. it('should give the valid registered widget factories', () => {
  189. expect(toArray(registry.preferredWidgetFactories('foo.txt'))).to.eql([]);
  190. let factory = createFactory();
  191. registry.addWidgetFactory(factory);
  192. let gFactory = new WidgetFactory({
  193. name: 'global',
  194. fileTypes: ['*'],
  195. defaultFor: ['*']
  196. });
  197. registry.addWidgetFactory(gFactory);
  198. let factories = registry.preferredWidgetFactories('a.foo.bar');
  199. expect(toArray(factories)).to.eql([factory, gFactory]);
  200. });
  201. it('should not list a factory whose model is not registered', () => {
  202. registry.addWidgetFactory(createFactory('foobar'));
  203. expect(registry.preferredWidgetFactories('a.foo.bar').length).to.eql(0);
  204. });
  205. it('should select the factory for a given extension', () => {
  206. let factory = createFactory();
  207. registry.addWidgetFactory(factory);
  208. let mdFactory = new WidgetFactory({
  209. name: 'markdown',
  210. fileTypes: ['markdown'],
  211. });
  212. registry.addWidgetFactory(mdFactory);
  213. expect(registry.preferredWidgetFactories('a.txt')[0]).to.be(factory);
  214. expect(registry.preferredWidgetFactories('a.md')[0]).to.be(mdFactory);
  215. });
  216. it('should respect the priority order', () => {
  217. let factory = createFactory();
  218. registry.addWidgetFactory(factory);
  219. let gFactory = new WidgetFactory({
  220. name: 'global',
  221. fileTypes: ['*'],
  222. defaultFor: ['*']
  223. });
  224. registry.addWidgetFactory(gFactory);
  225. let mdFactory = new WidgetFactory({
  226. name: 'markdown',
  227. fileTypes: ['markdown'],
  228. });
  229. registry.addWidgetFactory(mdFactory);
  230. let factories = registry.preferredWidgetFactories('a.txt');
  231. expect(toArray(factories)).to.eql([factory, gFactory]);
  232. });
  233. it('should handle multi-part extensions', () => {
  234. let factory = createFactory();
  235. registry.addWidgetFactory(factory);
  236. let tFactory = new WidgetFactory({
  237. name: 'table',
  238. fileTypes: ['tablejson'],
  239. });
  240. registry.addWidgetFactory(tFactory);
  241. let jFactory = new WidgetFactory({
  242. name: 'json',
  243. fileTypes: ['json'],
  244. });
  245. registry.addWidgetFactory(jFactory);
  246. let factories = registry.preferredWidgetFactories('foo.table.json');
  247. expect(toArray(factories)).to.eql([tFactory, jFactory]);
  248. factories = registry.preferredWidgetFactories('foo.json');
  249. expect(toArray(factories)).to.eql([jFactory]);
  250. });
  251. it('should handle just a multi-part extension', () => {
  252. let factory = new WidgetFactory({
  253. name: 'table',
  254. fileTypes: ['tablejson'],
  255. });
  256. registry.addWidgetFactory(factory);
  257. let factories = registry.preferredWidgetFactories('foo.table.json');
  258. expect(toArray(factories)).to.eql([factory]);
  259. factories = registry.preferredWidgetFactories('foo.json');
  260. expect(toArray(factories)).to.eql([]);
  261. });
  262. });
  263. describe('#defaultWidgetFactory()', () => {
  264. it('should get the default widget factory for a given extension', () => {
  265. let factory = createFactory();
  266. registry.addWidgetFactory(factory);
  267. let gFactory = new WidgetFactory({
  268. name: 'global',
  269. fileTypes: ['*'],
  270. defaultFor: ['*']
  271. });
  272. registry.addWidgetFactory(gFactory);
  273. let mdFactory = new WidgetFactory({
  274. name: 'markdown',
  275. fileTypes: ['markdown'],
  276. defaultFor: ['markdown']
  277. });
  278. registry.addWidgetFactory(mdFactory);
  279. expect(registry.defaultWidgetFactory('a.foo.bar')).to.be(factory);
  280. expect(registry.defaultWidgetFactory('a.md')).to.be(mdFactory);
  281. expect(registry.defaultWidgetFactory()).to.be(gFactory);
  282. });
  283. });
  284. describe('#fileTypes()', () => {
  285. it('should get the registered file types', () => {
  286. registry = new DocumentRegistry({ initialFileTypes: [] });
  287. expect(toArray(registry.fileTypes()).length).to.be(0);
  288. let fileTypes = [
  289. { name: 'notebook', extensions: ['.ipynb'] },
  290. { name: 'python', extensions: ['.py'] },
  291. { name: 'table', extensions: ['.table.json'] }
  292. ];
  293. registry.addFileType(fileTypes[0]);
  294. registry.addFileType(fileTypes[1]);
  295. registry.addFileType(fileTypes[2]);
  296. let values = registry.fileTypes();
  297. expect(values.next().name).to.be(fileTypes[0].name);
  298. expect(values.next().name).to.be(fileTypes[1].name);
  299. expect(values.next().name).to.be(fileTypes[2].name);
  300. });
  301. });
  302. describe('#getFileType()', () => {
  303. it('should get a file type by name', () => {
  304. expect(registry.getFileType('notebook')).to.be.ok();
  305. expect(registry.getFileType('python')).to.be.ok();
  306. expect(registry.getFileType('fizzbuzz')).to.be(void 0);
  307. });
  308. });
  309. describe('#getKernelPreference()', () => {
  310. it('should get a kernel preference', () => {
  311. registry.addWidgetFactory(createFactory());
  312. registry.addWidgetFactory(new WidgetFactory({
  313. name: 'python',
  314. fileTypes: ['python'],
  315. preferKernel: true,
  316. canStartKernel: true
  317. }));
  318. registry.addWidgetFactory(new WidgetFactory({
  319. name: 'global',
  320. fileTypes: ['*'],
  321. defaultFor: ['*']
  322. }));
  323. let pref = registry.getKernelPreference('.c', 'global');
  324. expect(pref.language).to.be('clike');
  325. expect(pref.shouldStart).to.be(false);
  326. expect(pref.canStart).to.be(false);
  327. pref = registry.getKernelPreference('.py', 'python');
  328. expect(pref.language).to.be('python');
  329. expect(pref.shouldStart).to.be(true);
  330. expect(pref.canStart).to.be(true);
  331. pref = registry.getKernelPreference('.py', 'baz');
  332. expect(pref).to.be(void 0);
  333. });
  334. });
  335. describe('#getModelFactory()', () => {
  336. it('should get a registered model factory by name', () => {
  337. let mFactory = new Base64ModelFactory();
  338. registry.addModelFactory(mFactory);
  339. expect(registry.getModelFactory('base64')).to.be(mFactory);
  340. });
  341. });
  342. describe('#getWidgetFactory()', () => {
  343. it('should get a widget factory by name', () => {
  344. registry.addModelFactory(new Base64ModelFactory());
  345. let factory = createFactory();
  346. registry.addWidgetFactory(factory);
  347. let mdFactory = new WidgetFactory({
  348. name: 'markdown',
  349. fileTypes: ['markdown'],
  350. });
  351. registry.addWidgetFactory(mdFactory);
  352. expect(registry.getWidgetFactory(factory.name)).to.be(factory);
  353. expect(registry.getWidgetFactory('markdown')).to.be(mdFactory);
  354. expect(registry.getWidgetFactory('baz')).to.be(void 0);
  355. });
  356. });
  357. describe('#widgetExtensions()', () => {
  358. it('should get the registered extensions for a given widget', () => {
  359. let foo = new WidgetExtension();
  360. let bar = new WidgetExtension();
  361. registry.addWidgetExtension('fizz', foo);
  362. registry.addWidgetExtension('fizz', bar);
  363. registry.addWidgetExtension('buzz', foo);
  364. let fizz = toArray(registry.widgetExtensions('fizz'));
  365. expect(fizz[0]).to.be(foo);
  366. expect(fizz[1]).to.be(bar);
  367. expect(fizz.length).to.be(2);
  368. let buzz = toArray(registry.widgetExtensions('buzz'));
  369. expect(buzz[0]).to.be(foo);
  370. expect(toArray(buzz).length).to.be(1);
  371. expect(registry.widgetExtensions('baz').next()).to.be(void 0);
  372. });
  373. });
  374. describe('#getFileTypeForModel()', () => {
  375. beforeEach(() => {
  376. DocumentRegistry.defaultFileTypes.forEach(ft => {
  377. registry.addFileType(ft);
  378. });
  379. });
  380. it('should handle a directory', () => {
  381. let ft = registry.getFileTypeForModel({
  382. type: 'directory'
  383. });
  384. expect(ft.name).to.be('directory');
  385. });
  386. it('should handle a notebook', () => {
  387. let ft = registry.getFileTypeForModel({
  388. type: 'notebook'
  389. });
  390. expect(ft.name).to.be('notebook');
  391. });
  392. it('should handle a python file', () => {
  393. let ft = registry.getFileTypeForModel({
  394. name: 'foo.py'
  395. });
  396. expect(ft.name).to.be('python');
  397. });
  398. it('should handle an unknown file', () => {
  399. let ft = registry.getFileTypeForModel({
  400. name: 'foo.bar'
  401. });
  402. expect(ft.name).to.be('text');
  403. });
  404. it('should get the most specific extension', () => {
  405. [
  406. { name: 'json', extensions: ['.json'] },
  407. { name: 'vega', extensions: ['.vg.json'] }
  408. ].forEach(ft => {registry.addFileType(ft); });
  409. let ft = registry.getFileTypeForModel({
  410. name: 'foo.vg.json'
  411. });
  412. expect(ft.name).to.be('vega');
  413. });
  414. it('should be case insensitive', () => {
  415. let ft = registry.getFileTypeForModel({
  416. name: 'foo.PY'
  417. });
  418. expect(ft.name).to.be('python');
  419. });
  420. });
  421. describe('#getFileTypesForPath()', () => {
  422. beforeEach(() => {
  423. DocumentRegistry.defaultFileTypes.forEach(ft => {
  424. registry.addFileType(ft);
  425. });
  426. });
  427. it('should handle a notebook', () => {
  428. let ft = registry.getFileTypesForPath('foo/bar/baz.ipynb');
  429. expect(ft[0].name).to.be('notebook');
  430. });
  431. it('should handle a python file', () => {
  432. let ft = registry.getFileTypesForPath('foo/bar/baz.py');
  433. expect(ft[0].name).to.be('python');
  434. });
  435. it('should return an empty list for an unknown file', () => {
  436. let ft = registry.getFileTypesForPath('foo/bar/baz.weird');
  437. expect(ft.length).to.be(0);
  438. });
  439. it('should get the most specific extension first', () => {
  440. [
  441. { name: 'json', extensions: ['.json'] },
  442. { name: 'vega', extensions: ['.vg.json'] }
  443. ].forEach(ft => {registry.addFileType(ft); });
  444. let ft = registry.getFileTypesForPath('foo/bar/baz.vg.json');
  445. expect(ft[0].name).to.be('vega');
  446. expect(ft[1].name).to.be('json');
  447. });
  448. it('should be case insensitive', () => {
  449. let ft = registry.getFileTypesForPath('foo/bar/baz.PY');
  450. expect(ft[0].name).to.be('python');
  451. });
  452. });
  453. });
  454. });