savehandler.spec.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import 'jest';
  4. import { ServiceManager } from '@jupyterlab/services';
  5. import {
  6. Context,
  7. DocumentRegistry,
  8. TextModelFactory
  9. } from '@jupyterlab/docregistry';
  10. import { SaveHandler } from '../src';
  11. import { PromiseDelegate, UUID } from '@lumino/coreutils';
  12. import {
  13. acceptDialog,
  14. signalToPromise,
  15. testEmission,
  16. waitForDialog
  17. } from '@jupyterlab/testutils';
  18. import * as Mock from '@jupyterlab/testutils/lib/mock';
  19. describe('docregistry/savehandler', () => {
  20. let manager: ServiceManager.IManager;
  21. const factory = new TextModelFactory();
  22. let context: Context<DocumentRegistry.IModel>;
  23. let handler: SaveHandler;
  24. beforeAll(() => {
  25. manager = new Mock.ServiceManagerMock();
  26. });
  27. beforeEach(() => {
  28. context = new Context({
  29. manager,
  30. factory,
  31. path: UUID.uuid4() + '.txt'
  32. });
  33. handler = new SaveHandler({ context });
  34. return context.initialize(true);
  35. });
  36. afterEach(() => {
  37. context.dispose();
  38. handler.dispose();
  39. });
  40. describe('SaveHandler', () => {
  41. describe('#constructor()', () => {
  42. it('should create a new save handler', () => {
  43. expect(handler).toBeInstanceOf(SaveHandler);
  44. });
  45. });
  46. describe('#saveInterval()', () => {
  47. it('should be the save interval of the handler', () => {
  48. expect(handler.saveInterval).toBe(120);
  49. });
  50. it('should be set-able', () => {
  51. handler.saveInterval = 200;
  52. expect(handler.saveInterval).toBe(200);
  53. });
  54. });
  55. describe('#isActive', () => {
  56. it('should test whether the handler is active', () => {
  57. expect(handler.isActive).toBe(false);
  58. handler.start();
  59. expect(handler.isActive).toBe(true);
  60. });
  61. });
  62. describe('#isDisposed', () => {
  63. it('should test whether the handler is disposed', () => {
  64. expect(handler.isDisposed).toBe(false);
  65. handler.dispose();
  66. expect(handler.isDisposed).toBe(true);
  67. });
  68. it('should be true after the context is disposed', () => {
  69. context.dispose();
  70. expect(handler.isDisposed).toBe(true);
  71. });
  72. });
  73. describe('#dispose()', () => {
  74. it('should dispose of the resources used by the handler', () => {
  75. expect(handler.isDisposed).toBe(false);
  76. handler.dispose();
  77. expect(handler.isDisposed).toBe(true);
  78. handler.dispose();
  79. expect(handler.isDisposed).toBe(true);
  80. });
  81. });
  82. describe('#start()', () => {
  83. it('should start the save handler', () => {
  84. handler.start();
  85. expect(handler.isActive).toBe(true);
  86. });
  87. it('should trigger a save', () => {
  88. const promise = signalToPromise(context.fileChanged);
  89. context.model.fromString('bar');
  90. expect(handler.isActive).toBe(false);
  91. handler.saveInterval = 0.1;
  92. handler.start();
  93. return promise;
  94. });
  95. it('should continue to save', async () => {
  96. let called = 0;
  97. // Lower the duration multiplier.
  98. (handler as any)._multiplier = 1;
  99. const promise = testEmission(context.fileChanged, {
  100. test: () => {
  101. if (called === 0) {
  102. context.model.fromString('bar');
  103. called++;
  104. }
  105. return called === 1;
  106. }
  107. });
  108. context.model.fromString('foo');
  109. expect(handler.isActive).toBe(false);
  110. handler.saveInterval = 0.1;
  111. handler.start();
  112. return promise;
  113. });
  114. it('should overwrite the file on disk', async () => {
  115. const delegate = new PromiseDelegate();
  116. // Lower the duration multiplier.
  117. (handler as any)._multiplier = 1;
  118. context.model.fromString('foo');
  119. await context.initialize(true);
  120. // The context allows up to 0.5 difference in timestamps before complaining.
  121. setTimeout(async () => {
  122. await manager.contents.save(context.path, {
  123. type: factory.contentType,
  124. format: factory.fileFormat,
  125. content: 'bar'
  126. });
  127. handler.saveInterval = 1;
  128. handler.start();
  129. context.model.fromString('baz');
  130. context.fileChanged.connect(() => {
  131. expect(context.model.toString()).toBe('baz');
  132. delegate.resolve(undefined);
  133. });
  134. }, 1500);
  135. // Extend the timeout to wait for the dialog because of the setTimeout.
  136. await acceptDialog(document.body, 3000);
  137. await delegate.promise;
  138. });
  139. it('should revert to the file on disk', async () => {
  140. const delegate = new PromiseDelegate();
  141. const revert = () => {
  142. const dialog = document.body.getElementsByClassName('jp-Dialog')[0];
  143. const buttons = dialog.getElementsByTagName('button');
  144. for (let i = 0; i < buttons.length; i++) {
  145. if (buttons[i].textContent === 'Revert') {
  146. buttons[i].click();
  147. return;
  148. }
  149. }
  150. };
  151. // Lower the duration multiplier.
  152. (handler as any)._multiplier = 1;
  153. await context.initialize(true);
  154. context.model.fromString('foo');
  155. context.fileChanged.connect(() => {
  156. expect(context.model.toString()).toBe('bar');
  157. delegate.resolve(undefined);
  158. });
  159. // The context allows up to 0.5 difference in timestamps before complaining.
  160. setTimeout(async () => {
  161. await manager.contents.save(context.path, {
  162. type: factory.contentType,
  163. format: factory.fileFormat,
  164. content: 'bar'
  165. });
  166. handler.saveInterval = 1;
  167. handler.start();
  168. context.model.fromString('baz');
  169. }, 1500);
  170. // Extend the timeout to wait for the dialog because of the setTimeout.
  171. await waitForDialog(document.body, 3000);
  172. revert();
  173. await delegate.promise;
  174. });
  175. });
  176. describe('#stop()', () => {
  177. it('should stop the save timer', () => {
  178. handler.start();
  179. expect(handler.isActive).toBe(true);
  180. handler.stop();
  181. expect(handler.isActive).toBe(false);
  182. });
  183. });
  184. });
  185. });