123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import { SessionContext } from '@jupyterlab/apputils';
- import {
- Context,
- DocumentRegistry,
- TextModelFactory
- } from '@jupyterlab/docregistry';
- import { RenderMimeRegistry } from '@jupyterlab/rendermime';
- import { Contents, ServiceManager } from '@jupyterlab/services';
- import {
- acceptDialog,
- dismissDialog,
- initNotebookContext,
- NBTestUtils,
- waitForDialog
- } from '@jupyterlab/testutils';
- import * as Mock from '@jupyterlab/testutils/lib/mock';
- import { UUID } from '@lumino/coreutils';
- import { Widget } from '@lumino/widgets';
- describe('docregistry/context', () => {
- let manager: ServiceManager.IManager;
- const factory = new TextModelFactory();
- beforeAll(() => {
- manager = new Mock.ServiceManagerMock();
- return manager.ready;
- });
- describe('Context', () => {
- let context: Context<DocumentRegistry.IModel>;
- beforeEach(() => {
- context = new Context({
- manager,
- factory,
- path: UUID.uuid4() + '.txt'
- });
- });
- afterEach(async () => {
- await context.sessionContext.shutdown();
- context.dispose();
- });
- describe('#constructor()', () => {
- it('should create a new context', () => {
- context = new Context({
- manager,
- factory,
- path: UUID.uuid4() + '.txt'
- });
- expect(context).toBeInstanceOf(Context);
- });
- });
- describe('#pathChanged', () => {
- it('should be emitted when the path changes', async () => {
- const newPath = UUID.uuid4() + '.txt';
- let called = false;
- context.pathChanged.connect((sender, args) => {
- expect(sender).toBe(context);
- expect(args).toBe(newPath);
- called = true;
- });
- await context.initialize(true);
- await manager.contents.rename(context.path, newPath);
- expect(called).toBe(true);
- });
- });
- describe('#fileChanged', () => {
- it('should be emitted when the file is saved', async () => {
- const path = context.path;
- let called = false;
- context.fileChanged.connect((sender, args) => {
- expect(sender).toBe(context);
- expect(args.path).toBe(path);
- called = true;
- });
- await context.initialize(true);
- expect(called).toBe(true);
- });
- });
- describe('#saving', () => {
- it("should emit 'starting' when the file starts saving", async () => {
- let called = false;
- let checked = false;
- context.saveState.connect((sender, args) => {
- if (!called) {
- expect(sender).toBe(context);
- expect(args).toBe('started');
- checked = true;
- }
- called = true;
- });
- await context.initialize(true);
- expect(called).toBe(true);
- expect(checked).toBe(true);
- });
- it("should emit 'completed' when the file ends saving", async () => {
- let called = 0;
- let checked = false;
- context.saveState.connect((sender, args) => {
- if (called > 0) {
- expect(sender).toBe(context);
- expect(args).toBe('completed');
- checked = true;
- }
- called += 1;
- });
- await context.initialize(true);
- expect(called).toBe(2);
- expect(checked).toBe(true);
- });
- it("should emit 'failed' when the save operation fails out", async () => {
- context = new Context({
- manager,
- factory,
- path: 'readonly.txt'
- });
- let called = 0;
- let checked;
- context.saveState.connect((sender, args) => {
- if (called > 0) {
- expect(sender).toBe(context);
- checked = args;
- }
- called += 1;
- });
- await expect(context.initialize(true)).rejects.toThrowError(
- 'Invalid response: 403 Forbidden'
- );
- expect(called).toBe(2);
- expect(checked).toBe('failed');
- await acceptDialog();
- });
- });
- describe('#isReady', () => {
- it('should indicate whether the context is ready', async () => {
- expect(context.isReady).toBe(false);
- const func = async () => {
- await context.ready;
- expect(context.isReady).toBe(true);
- };
- const promise = func();
- await context.initialize(true);
- await promise;
- });
- });
- describe('#ready()', () => {
- it('should resolve when the file is saved for the first time', async () => {
- await context.initialize(true);
- await context.ready;
- });
- it('should resolve when the file is reverted for the first time', async () => {
- await manager.contents.save(context.path, {
- type: factory.contentType,
- format: factory.fileFormat,
- content: 'foo'
- });
- await context.initialize(false);
- await context.ready;
- });
- it('should initialize the model when the file is saved for the first time', async () => {
- const context = await initNotebookContext({ manager });
- context.model.fromJSON(NBTestUtils.DEFAULT_CONTENT);
- expect(context.model.cells.canUndo).toBe(true);
- await context.initialize(true);
- await context.ready;
- expect(context.model.cells.canUndo).toBe(false);
- });
- it('should initialize the model when the file is reverted for the first time', async () => {
- const context = await initNotebookContext({ manager });
- await manager.contents.save(context.path, {
- type: 'notebook',
- format: 'json',
- content: NBTestUtils.DEFAULT_CONTENT
- });
- context.model.fromJSON(NBTestUtils.DEFAULT_CONTENT);
- expect(context.model.cells.canUndo).toBe(true);
- await context.initialize(false);
- await context.ready;
- expect(context.model.cells.canUndo).toBe(false);
- });
- });
- describe('#disposed', () => {
- it('should be emitted when the context is disposed', () => {
- let called = false;
- context.disposed.connect((sender, args) => {
- expect(sender).toBe(context);
- expect(args).toBeUndefined();
- called = true;
- });
- context.dispose();
- expect(called).toBe(true);
- });
- });
- describe('#model', () => {
- it('should be the model associated with the document', () => {
- expect(context.model.toString()).toBe('');
- });
- });
- describe('#sessionContext', () => {
- it('should be a ISessionContext object', () => {
- expect(context.sessionContext).toBeInstanceOf(SessionContext);
- });
- });
- describe('#path', () => {
- it('should be the current path for the context', () => {
- expect(typeof context.path).toBe('string');
- });
- });
- describe('#lastModifiedCheckMargin', () => {
- it('should be 500ms by default', () => {
- expect(context.lastModifiedCheckMargin).toBe(500);
- });
- it('should be set-able', () => {
- context.lastModifiedCheckMargin = 600;
- expect(context.lastModifiedCheckMargin).toBe(600);
- });
- });
- describe('#contentsModel', () => {
- it('should be `null` before population', () => {
- expect(context.contentsModel).toBeNull();
- });
- it('should be set after population', async () => {
- const { path } = context;
- void context.initialize(true);
- await context.ready;
- expect(context.contentsModel!.path).toBe(path);
- });
- });
- describe('#factoryName', () => {
- it('should be the name of the factory used by the context', () => {
- expect(context.factoryName).toBe(factory.name);
- });
- });
- describe('#isDisposed', () => {
- it('should test whether the context is disposed', () => {
- expect(context.isDisposed).toBe(false);
- context.dispose();
- expect(context.isDisposed).toBe(true);
- });
- });
- describe('#dispose()', () => {
- it('should dispose of the resources used by the context', () => {
- context.dispose();
- expect(context.isDisposed).toBe(true);
- context.dispose();
- expect(context.isDisposed).toBe(true);
- });
- });
- describe('#rename()', () => {
- it('should change the name of the file to the new name', async () => {
- await context.initialize(true);
- context.model.fromString('foo');
- const newName = UUID.uuid4() + '.txt';
- await context.rename(newName);
- await context.save();
- const opts: Contents.IFetchOptions = {
- format: factory.fileFormat,
- type: factory.contentType,
- content: true
- };
- const model = await manager.contents.get(newName, opts);
- expect(model.content).toBe('foo');
- });
- });
- describe('#save()', () => {
- it('should save the contents of the file to disk', async () => {
- await context.initialize(true);
- context.model.fromString('foo');
- await context.save();
- const opts: Contents.IFetchOptions = {
- format: factory.fileFormat,
- type: factory.contentType,
- content: true
- };
- const model = await manager.contents.get(context.path, opts);
- expect(model.content).toBe('foo');
- });
- it('should should preserve LF line endings upon save', async () => {
- await context.initialize(true);
- await manager.contents.save(context.path, {
- type: factory.contentType,
- format: factory.fileFormat,
- content: 'foo\nbar'
- });
- await context.revert();
- await context.save();
- const opts: Contents.IFetchOptions = {
- format: factory.fileFormat,
- type: factory.contentType,
- content: true
- };
- const model = await manager.contents.get(context.path, opts);
- expect(model.content).toBe('foo\nbar');
- });
- it('should should preserve CRLF line endings upon save', async () => {
- await context.initialize(true);
- await manager.contents.save(context.path, {
- type: factory.contentType,
- format: factory.fileFormat,
- content: 'foo\r\nbar'
- });
- await context.revert();
- await context.save();
- const opts: Contents.IFetchOptions = {
- format: factory.fileFormat,
- type: factory.contentType,
- content: true
- };
- const model = await manager.contents.get(context.path, opts);
- expect(model.content).toBe('foo\r\nbar');
- });
- });
- describe('#saveAs()', () => {
- it('should save the document to a different path chosen by the user', async () => {
- const initialize = context.initialize(true);
- const newPath = UUID.uuid4() + '.txt';
- const func = async () => {
- await initialize;
- await waitForDialog();
- const dialog = document.body.getElementsByClassName('jp-Dialog')[0];
- const input = dialog.getElementsByTagName('input')[0];
- input.value = newPath;
- await acceptDialog();
- };
- const promise = func();
- await initialize;
- const oldPath = context.path;
- await context.saveAs();
- await promise;
- expect(context.path).toBe(newPath);
- // Make sure the both files are there now.
- const model = await manager.contents.get('', { content: true });
- expect(model.content.find((x: any) => x.name === oldPath)).toBeTruthy();
- expect(model.content.find((x: any) => x.name === newPath)).toBeTruthy();
- });
- it('should bring up a conflict dialog', async () => {
- const newPath = UUID.uuid4() + '.txt';
- const func = async () => {
- await waitForDialog();
- const dialog = document.body.getElementsByClassName('jp-Dialog')[0];
- const input = dialog.getElementsByTagName('input')[0];
- input.value = newPath;
- await acceptDialog(); // Accept rename dialog
- await acceptDialog(); // Accept conflict dialog
- };
- await manager.contents.save(newPath, {
- type: factory.contentType,
- format: factory.fileFormat,
- content: 'foo'
- });
- await context.initialize(true);
- const promise = func();
- await context.saveAs();
- await promise;
- expect(context.path).toBe(newPath);
- });
- it('should keep the file if overwrite is aborted', async () => {
- const oldPath = context.path;
- const newPath = UUID.uuid4() + '.txt';
- const func = async () => {
- await waitForDialog();
- const dialog = document.body.getElementsByClassName('jp-Dialog')[0];
- const input = dialog.getElementsByTagName('input')[0];
- input.value = newPath;
- await acceptDialog(); // Accept rename dialog
- await dismissDialog(); // Reject conflict dialog
- };
- await manager.contents.save(newPath, {
- type: factory.contentType,
- format: factory.fileFormat,
- content: 'foo'
- });
- await context.initialize(true);
- const promise = func();
- await context.saveAs();
- await promise;
- expect(context.path).toBe(oldPath);
- });
- it('should just save if the file name does not change', async () => {
- const path = context.path;
- await context.initialize(true);
- const promise = context.saveAs();
- await acceptDialog();
- await promise;
- expect(context.path).toBe(path);
- });
- });
- describe('#revert()', () => {
- it('should revert the contents of the file to the disk', async () => {
- await context.initialize(true);
- context.model.fromString('foo');
- await context.save();
- context.model.fromString('bar');
- await context.revert();
- expect(context.model.toString()).toBe('foo');
- });
- it('should normalize CRLF line endings to LF', async () => {
- await context.initialize(true);
- await manager.contents.save(context.path, {
- type: factory.contentType,
- format: factory.fileFormat,
- content: 'foo\r\nbar'
- });
- await context.revert();
- expect(context.model.toString()).toBe('foo\nbar');
- });
- });
- describe('#createCheckpoint()', () => {
- it('should create a checkpoint for the file', async () => {
- await context.initialize(true);
- const model = await context.createCheckpoint();
- expect(model.id).toBeTruthy();
- expect(model.last_modified).toBeTruthy();
- });
- });
- describe('#deleteCheckpoint()', () => {
- it('should delete the given checkpoint', async () => {
- await context.initialize(true);
- const model = await context.createCheckpoint();
- await context.deleteCheckpoint(model.id);
- const models = await context.listCheckpoints();
- expect(models.length).toBe(0);
- });
- });
- describe('#restoreCheckpoint()', () => {
- it('should restore the value to the last checkpoint value', async () => {
- context.model.fromString('bar');
- await context.initialize(true);
- const model = await context.createCheckpoint();
- context.model.fromString('foo');
- const id = model.id;
- await context.save();
- await context.restoreCheckpoint(id);
- await context.revert();
- expect(context.model.toString()).toBe('bar');
- });
- });
- describe('#listCheckpoints()', () => {
- it('should list the checkpoints for the file', async () => {
- await context.initialize(true);
- const model = await context.createCheckpoint();
- const id = model.id;
- const models = await context.listCheckpoints();
- let found = false;
- for (const model of models) {
- if (model.id === id) {
- found = true;
- }
- }
- expect(found).toBe(true);
- });
- });
- describe('#urlResolver', () => {
- it('should be a url resolver', () => {
- expect(context.urlResolver).toBeInstanceOf(
- RenderMimeRegistry.UrlResolver
- );
- });
- });
- describe('#addSibling()', () => {
- it('should add a sibling widget', () => {
- let called = false;
- const opener = (widget: Widget) => {
- called = true;
- };
- context = new Context({
- manager,
- factory,
- path: UUID.uuid4() + '.txt',
- opener
- });
- context.addSibling(new Widget());
- expect(called).toBe(true);
- });
- });
- });
- });
|