savehandler.spec.ts 6.1 KB

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