@operato/shell 10.29.68 → 10.29.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/src/app/pages/page-view.d.ts +2 -1
  3. package/dist/src/app/pages/page-view.js +25 -7
  4. package/dist/src/app/pages/page-view.js.map +1 -1
  5. package/dist/src/drafts/draft-controller.d.ts +74 -0
  6. package/dist/src/drafts/draft-controller.js +164 -0
  7. package/dist/src/drafts/draft-controller.js.map +1 -0
  8. package/dist/src/drafts/draft-owner.d.ts +6 -0
  9. package/dist/src/drafts/draft-owner.js +10 -0
  10. package/dist/src/drafts/draft-owner.js.map +1 -0
  11. package/dist/src/drafts/unsaved-draft-controller.d.ts +5 -0
  12. package/dist/src/drafts/unsaved-draft-controller.js +19 -0
  13. package/dist/src/drafts/unsaved-draft-controller.js.map +1 -1
  14. package/dist/src/index.d.ts +2 -0
  15. package/dist/src/index.js +2 -0
  16. package/dist/src/index.js.map +1 -1
  17. package/dist/test/draft-controller.test.d.ts +1 -0
  18. package/dist/test/draft-controller.test.js +168 -0
  19. package/dist/test/draft-controller.test.js.map +1 -0
  20. package/dist/test/draft-owner.test.d.ts +1 -0
  21. package/dist/test/draft-owner.test.js +100 -0
  22. package/dist/test/draft-owner.test.js.map +1 -0
  23. package/dist/test/page-drafts.test.d.ts +1 -0
  24. package/dist/test/page-drafts.test.js +24 -0
  25. package/dist/test/page-drafts.test.js.map +1 -0
  26. package/dist/test/page-refresh-queries.test.d.ts +1 -0
  27. package/dist/test/page-refresh-queries.test.js +89 -0
  28. package/dist/test/page-refresh-queries.test.js.map +1 -0
  29. package/dist/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +6 -6
  31. package/test/draft-controller.test.ts +186 -0
  32. package/test/draft-owner.test.ts +109 -0
  33. package/test/page-drafts.test.ts +23 -0
  34. package/test/page-refresh-queries.test.ts +96 -0
@@ -91,6 +91,25 @@ export class UnsavedDraftController {
91
91
  });
92
92
  await this.writes;
93
93
  }
94
+ /**
95
+ * A save sent from a record the screen has since left answered: what was kept for that record is gone. What the
96
+ * screen edits now is not touched - if it is that record again, the caller writes it anew.
97
+ */
98
+ async forget(address) {
99
+ const key = draftKey(address);
100
+ const current = this.source.address();
101
+ if (current && draftKey(current) === key)
102
+ this.persisted = undefined;
103
+ this.writes = this.writes.then(async () => {
104
+ try {
105
+ await removeDraft(key);
106
+ }
107
+ catch {
108
+ this.cannotKeep = true;
109
+ }
110
+ });
111
+ await this.writes;
112
+ }
94
113
  /** Capture before yielding: a query switch must never store its edit under the next query's key. */
95
114
  async flush() {
96
115
  clearTimeout(this.timer);
@@ -1 +1 @@
1
- {"version":3,"file":"unsaved-draft-controller.js","sourceRoot":"","sources":["../../../src/drafts/unsaved-draft-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit'\nimport { i18next } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup'\n\nimport {\n draftDecision,\n draftKey,\n draftsKept,\n readDraft,\n removeDraft,\n writeDraft,\n type DraftAddress\n} from './draft-store.js'\n\nexport interface UnsavedDraftSource<T> {\n /** Where the draft lives - who, which site, which screen, what is edited. Undefined until it is known. */\n address(): DraftAddress | undefined\n /** The unsaved model and the saved revision it began from; undefined when nothing is unsaved. */\n snapshot(): { value: T; base: string | number | null } | undefined\n /** The saved copy's revision now, or null when it was never saved. */\n savedRevision(): string | number | null\n /** Put a draft back on the canvas. The next save goes out against `base`, so a conflict is reported. */\n restore(value: T, base: string | number | null): void\n}\n\n/**\n * Keeps a screen's unsaved edit as a draft, and offers it back when the screen opens (design/ui/unsaved-drafts.md).\n *\n * The screen calls `changed()` when its edit changes, `settled()` when it saved or discarded, and `offer()` once\n * the saved copy is loaded. Nothing asks at reload - the edit is already written.\n *\n * Where a draft cannot be written (a private window, blocked storage), this screen falls back to the browser's\n * own leave question while something is unsaved: without either, the edit would be lost without a word.\n */\nexport class UnsavedDraftController<T> implements ReactiveController {\n /** How long an edit rests before it is written. */\n static WAIT_MS = 1000\n\n private timer?: ReturnType<typeof setTimeout>\n private cannotKeep = false\n private persisted?: string\n private writes: Promise<void> = Promise.resolve()\n private offerAbort?: AbortController\n private connected = false\n\n private identity() {\n const address = this.source.address()\n const snapshot = this.source.snapshot()\n return address && snapshot ? JSON.stringify([draftKey(address), snapshot]) : undefined\n }\n\n dispose() {\n this.hostDisconnected()\n this.host.removeController(this)\n }\n\n constructor(\n private host: ReactiveControllerHost,\n private source: UnsavedDraftSource<T>\n ) {\n host.addController(this)\n }\n\n hostConnected() {\n this.connected = true\n window.addEventListener('beforeunload', this.holdIfNotKept)\n /* Known before the first edit, so a reload in that first second is not the one that loses it. */\n draftsKept().then(kept => (this.cannotKeep = !kept))\n }\n\n hostDisconnected() {\n this.connected = false\n this.offerAbort?.abort()\n window.removeEventListener('beforeunload', this.holdIfNotKept)\n /* Leaving the screen with a write still waiting: write it now, not never. */\n if (this.timer) this.flush()\n }\n\n /** Whether the current edit, including its identity, has a completed durable write. */\n get keeps() {\n try {\n return (\n !this.cannotKeep && (!this.source.snapshot() || (!!this.source.address() && this.identity() === this.persisted))\n )\n } catch {\n return false\n }\n }\n\n /** The edit changed. It is written after it rests for a moment. */\n changed() {\n clearTimeout(this.timer)\n this.timer = setTimeout(() => this.flush(), UnsavedDraftController.WAIT_MS)\n }\n\n /** Saved, or discarded on purpose: there is nothing to keep. */\n async settled() {\n clearTimeout(this.timer)\n this.timer = undefined\n this.offerAbort?.abort()\n this.persisted = undefined\n const address = this.source.address()\n if (!address) return\n const key = draftKey(address)\n this.writes = this.writes.then(async () => {\n try {\n await removeDraft(key)\n } catch {\n this.cannotKeep = true\n }\n })\n await this.writes\n }\n\n /** Capture before yielding: a query switch must never store its edit under the next query's key. */\n async flush() {\n clearTimeout(this.timer)\n this.timer = undefined\n const address = this.source.address()\n if (!address) return\n let snapshot: ReturnType<UnsavedDraftSource<T>['snapshot']>\n try {\n snapshot = structuredClone(this.source.snapshot())\n } catch {\n this.cannotKeep = true\n return\n }\n const key = draftKey(address)\n const identity = snapshot ? JSON.stringify([key, snapshot]) : undefined\n this.writes = this.writes.then(async () => {\n try {\n if (!snapshot) await removeDraft(key)\n else await writeDraft({ key, value: snapshot.value, base: snapshot.base, at: Date.now() })\n this.persisted = identity\n this.cannotKeep = false\n } catch {\n this.cannotKeep = true\n }\n })\n await this.writes\n }\n\n /**\n * Offer a kept draft back - call once the saved copy is loaded. Resolves true when the draft was restored.\n * A draft too old to trust is dropped without asking.\n */\n async offer(): Promise<boolean> {\n this.offerAbort?.abort()\n const request = (this.offerAbort = new AbortController())\n await this.writes\n if (request.signal.aborted) return false\n const address = this.source.address()\n if (!address) return false\n const key = draftKey(address)\n\n let draft\n try {\n draft = await readDraft<T>(key)\n } catch {\n this.cannotKeep = true\n return false\n }\n const currentAddress = () => {\n const current = this.source.address()\n return current && draftKey(current) === key\n }\n if (!draft || request.signal.aborted || !currentAddress()) return false\n\n const decision = draftDecision(draft, this.source.savedRevision())\n if (decision === 'drop') {\n await removeDraft(key).catch(() => {})\n return false\n }\n\n const t = (name: string, fallback: string, params: Record<string, unknown> = {}) =>\n String(i18next.t(name, { defaultValue: fallback, ...params }))\n const minutes = Math.max(1, Math.round((Date.now() - draft.at) / 60000))\n const lines = [\n t('text.unsaved-draft-kept', 'what you changed {minutes} minutes ago is still kept in this browser.', { minutes })\n ]\n if (decision === 'offer-behind') {\n lines.push(\n t('text.unsaved-draft-behind', 'it has been saved again since then. saving will tell you about the conflict.')\n )\n }\n\n const resume = await OxPrompt.open({\n type: 'info',\n title: t('title.unsaved-draft', 'there is an unsaved edit'),\n text: lines.join(' '),\n confirmButton: { text: t('button.resume-editing', 'resume editing') },\n cancelButton: { text: t('button.discard', 'discard') },\n signal: request.signal\n })\n\n if (request.signal.aborted || !this.connected || !currentAddress()) return false\n if (resume) {\n this.source.restore(draft.value, draft.base)\n this.persisted = this.identity()\n return true\n }\n await removeDraft(key).catch(() => {})\n return false\n }\n\n /** Only an edit without a durable copy needs the browser's unload question. */\n private holdIfNotKept = (event: BeforeUnloadEvent) => {\n try {\n if (!this.source.snapshot() || this.keeps) return\n } catch {\n /* An unreadable edit is not safely kept. */\n }\n void this.flush()\n event.preventDefault()\n event.returnValue = ''\n }\n}\n"]}
1
+ {"version":3,"file":"unsaved-draft-controller.js","sourceRoot":"","sources":["../../../src/drafts/unsaved-draft-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit'\nimport { i18next } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup'\n\nimport {\n draftDecision,\n draftKey,\n draftsKept,\n readDraft,\n removeDraft,\n writeDraft,\n type DraftAddress\n} from './draft-store.js'\n\nexport interface UnsavedDraftSource<T> {\n /** Where the draft lives - who, which site, which screen, what is edited. Undefined until it is known. */\n address(): DraftAddress | undefined\n /** The unsaved model and the saved revision it began from; undefined when nothing is unsaved. */\n snapshot(): { value: T; base: string | number | null } | undefined\n /** The saved copy's revision now, or null when it was never saved. */\n savedRevision(): string | number | null\n /** Put a draft back on the canvas. The next save goes out against `base`, so a conflict is reported. */\n restore(value: T, base: string | number | null): void\n}\n\n/**\n * Keeps a screen's unsaved edit as a draft, and offers it back when the screen opens (design/ui/unsaved-drafts.md).\n *\n * The screen calls `changed()` when its edit changes, `settled()` when it saved or discarded, and `offer()` once\n * the saved copy is loaded. Nothing asks at reload - the edit is already written.\n *\n * Where a draft cannot be written (a private window, blocked storage), this screen falls back to the browser's\n * own leave question while something is unsaved: without either, the edit would be lost without a word.\n */\nexport class UnsavedDraftController<T> implements ReactiveController {\n /** How long an edit rests before it is written. */\n static WAIT_MS = 1000\n\n private timer?: ReturnType<typeof setTimeout>\n private cannotKeep = false\n private persisted?: string\n private writes: Promise<void> = Promise.resolve()\n private offerAbort?: AbortController\n private connected = false\n\n private identity() {\n const address = this.source.address()\n const snapshot = this.source.snapshot()\n return address && snapshot ? JSON.stringify([draftKey(address), snapshot]) : undefined\n }\n\n dispose() {\n this.hostDisconnected()\n this.host.removeController(this)\n }\n\n constructor(\n private host: ReactiveControllerHost,\n private source: UnsavedDraftSource<T>\n ) {\n host.addController(this)\n }\n\n hostConnected() {\n this.connected = true\n window.addEventListener('beforeunload', this.holdIfNotKept)\n /* Known before the first edit, so a reload in that first second is not the one that loses it. */\n draftsKept().then(kept => (this.cannotKeep = !kept))\n }\n\n hostDisconnected() {\n this.connected = false\n this.offerAbort?.abort()\n window.removeEventListener('beforeunload', this.holdIfNotKept)\n /* Leaving the screen with a write still waiting: write it now, not never. */\n if (this.timer) this.flush()\n }\n\n /** Whether the current edit, including its identity, has a completed durable write. */\n get keeps() {\n try {\n return (\n !this.cannotKeep && (!this.source.snapshot() || (!!this.source.address() && this.identity() === this.persisted))\n )\n } catch {\n return false\n }\n }\n\n /** The edit changed. It is written after it rests for a moment. */\n changed() {\n clearTimeout(this.timer)\n this.timer = setTimeout(() => this.flush(), UnsavedDraftController.WAIT_MS)\n }\n\n /** Saved, or discarded on purpose: there is nothing to keep. */\n async settled() {\n clearTimeout(this.timer)\n this.timer = undefined\n this.offerAbort?.abort()\n this.persisted = undefined\n const address = this.source.address()\n if (!address) return\n const key = draftKey(address)\n this.writes = this.writes.then(async () => {\n try {\n await removeDraft(key)\n } catch {\n this.cannotKeep = true\n }\n })\n await this.writes\n }\n\n /**\n * A save sent from a record the screen has since left answered: what was kept for that record is gone. What the\n * screen edits now is not touched - if it is that record again, the caller writes it anew.\n */\n async forget(address: DraftAddress) {\n const key = draftKey(address)\n const current = this.source.address()\n if (current && draftKey(current) === key) this.persisted = undefined\n this.writes = this.writes.then(async () => {\n try {\n await removeDraft(key)\n } catch {\n this.cannotKeep = true\n }\n })\n await this.writes\n }\n\n /** Capture before yielding: a query switch must never store its edit under the next query's key. */\n async flush() {\n clearTimeout(this.timer)\n this.timer = undefined\n const address = this.source.address()\n if (!address) return\n let snapshot: ReturnType<UnsavedDraftSource<T>['snapshot']>\n try {\n snapshot = structuredClone(this.source.snapshot())\n } catch {\n this.cannotKeep = true\n return\n }\n const key = draftKey(address)\n const identity = snapshot ? JSON.stringify([key, snapshot]) : undefined\n this.writes = this.writes.then(async () => {\n try {\n if (!snapshot) await removeDraft(key)\n else await writeDraft({ key, value: snapshot.value, base: snapshot.base, at: Date.now() })\n this.persisted = identity\n this.cannotKeep = false\n } catch {\n this.cannotKeep = true\n }\n })\n await this.writes\n }\n\n /**\n * Offer a kept draft back - call once the saved copy is loaded. Resolves true when the draft was restored.\n * A draft too old to trust is dropped without asking.\n */\n async offer(): Promise<boolean> {\n this.offerAbort?.abort()\n const request = (this.offerAbort = new AbortController())\n await this.writes\n if (request.signal.aborted) return false\n const address = this.source.address()\n if (!address) return false\n const key = draftKey(address)\n\n let draft\n try {\n draft = await readDraft<T>(key)\n } catch {\n this.cannotKeep = true\n return false\n }\n const currentAddress = () => {\n const current = this.source.address()\n return current && draftKey(current) === key\n }\n if (!draft || request.signal.aborted || !currentAddress()) return false\n\n const decision = draftDecision(draft, this.source.savedRevision())\n if (decision === 'drop') {\n await removeDraft(key).catch(() => {})\n return false\n }\n\n const t = (name: string, fallback: string, params: Record<string, unknown> = {}) =>\n String(i18next.t(name, { defaultValue: fallback, ...params }))\n const minutes = Math.max(1, Math.round((Date.now() - draft.at) / 60000))\n const lines = [\n t('text.unsaved-draft-kept', 'what you changed {minutes} minutes ago is still kept in this browser.', { minutes })\n ]\n if (decision === 'offer-behind') {\n lines.push(\n t('text.unsaved-draft-behind', 'it has been saved again since then. saving will tell you about the conflict.')\n )\n }\n\n const resume = await OxPrompt.open({\n type: 'info',\n title: t('title.unsaved-draft', 'there is an unsaved edit'),\n text: lines.join(' '),\n confirmButton: { text: t('button.resume-editing', 'resume editing') },\n cancelButton: { text: t('button.discard', 'discard') },\n signal: request.signal\n })\n\n if (request.signal.aborted || !this.connected || !currentAddress()) return false\n if (resume) {\n this.source.restore(draft.value, draft.base)\n this.persisted = this.identity()\n return true\n }\n await removeDraft(key).catch(() => {})\n return false\n }\n\n /** Only an edit without a durable copy needs the browser's unload question. */\n private holdIfNotKept = (event: BeforeUnloadEvent) => {\n try {\n if (!this.source.snapshot() || this.keeps) return\n } catch {\n /* An unreadable edit is not safely kept. */\n }\n void this.flush()\n event.preventDefault()\n event.returnValue = ''\n }\n}\n"]}
@@ -8,4 +8,6 @@ export { connect } from './connect-mixin.js';
8
8
  export * from './controllers/index.js';
9
9
  export * from './require-custom-element.js';
10
10
  export * from './drafts/draft-store.js';
11
+ export * from './drafts/draft-owner.js';
11
12
  export * from './drafts/unsaved-draft-controller.js';
13
+ export * from './drafts/draft-controller.js';
package/dist/src/index.js CHANGED
@@ -8,5 +8,7 @@ export { connect } from './connect-mixin.js';
8
8
  export * from './controllers/index.js';
9
9
  export * from './require-custom-element.js';
10
10
  export * from './drafts/draft-store.js';
11
+ export * from './drafts/draft-owner.js';
11
12
  export * from './drafts/unsaved-draft-controller.js';
13
+ export * from './drafts/draft-controller.js';
12
14
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,0BAA0B,CAAA;AACxC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5C,cAAc,wBAAwB,CAAA;AACtC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,sCAAsC,CAAA","sourcesContent":["export * from './types/index.js'\nexport * from './store.js'\nexport * from './actions/index.js'\nexport * from './app/pages/page-view.js'\nexport * from './object-store.js'\nexport * from './custom-alert.js'\nexport { connect } from './connect-mixin.js'\nexport * from './controllers/index.js'\nexport * from './require-custom-element.js'\nexport * from './drafts/draft-store.js'\nexport * from './drafts/unsaved-draft-controller.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,0BAA0B,CAAA;AACxC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5C,cAAc,wBAAwB,CAAA;AACtC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,sCAAsC,CAAA;AACpD,cAAc,8BAA8B,CAAA","sourcesContent":["export * from './types/index.js'\nexport * from './store.js'\nexport * from './actions/index.js'\nexport * from './app/pages/page-view.js'\nexport * from './object-store.js'\nexport * from './custom-alert.js'\nexport { connect } from './connect-mixin.js'\nexport * from './controllers/index.js'\nexport * from './require-custom-element.js'\nexport * from './drafts/draft-store.js'\nexport * from './drafts/draft-owner.js'\nexport * from './drafts/unsaved-draft-controller.js'\nexport * from './drafts/draft-controller.js'\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,168 @@
1
+ import { expect, fixture } from '@open-wc/testing';
2
+ import { html, LitElement } from 'lit';
3
+ import { QUERY_ANSWERED } from '@operato/graphql';
4
+ import { DraftController, draftsOn } from '../src/drafts/draft-controller.js';
5
+ import { draftKey, readDraft, removeDraft } from '../src/drafts/draft-store.js';
6
+ import { UnsavedDraftController } from '../src/drafts/unsaved-draft-controller.js';
7
+ class RuleForm extends LitElement {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.recordId = 'a';
11
+ this.draft = new DraftController(this, { read: () => this.read, key: () => this.recordId });
12
+ }
13
+ static { this.properties = { read: { attribute: false }, recordId: {} }; }
14
+ }
15
+ customElements.define('rule-form', RuleForm);
16
+ const open = async () => {
17
+ const form = await fixture(html `<rule-form .read=${{ name: 'A', body: 'old' }}></rule-form>`);
18
+ await form.updateComplete;
19
+ return form;
20
+ };
21
+ describe('DraftController', () => {
22
+ it('shows an edit, else what was read', async () => {
23
+ const form = await open();
24
+ expect(form.draft.value('name')).to.equal('A');
25
+ form.draft.set('name', 'A2');
26
+ expect(form.draft.value('name')).to.equal('A2');
27
+ expect(form.draft.value('body')).to.equal('old');
28
+ expect(form.draft.dirty).to.equal(true);
29
+ });
30
+ it('a save clears only what it sent and is still as sent; a failed save clears nothing', async () => {
31
+ const form = await open();
32
+ form.draft.set('name', 'A2');
33
+ let release;
34
+ const saving = form.draft.submit(sent => {
35
+ expect(sent).to.deep.equal({ name: 'A2' });
36
+ return new Promise(resolve => (release = resolve));
37
+ });
38
+ form.draft.set('body', 'typed while saving');
39
+ release('saved');
40
+ await saving;
41
+ expect(form.draft.edits).to.deep.equal({ body: 'typed while saving' });
42
+ form.draft.set('name', 'A3');
43
+ await form.draft.submit(async () => undefined);
44
+ expect(form.draft.edits).to.deep.equal({ body: 'typed while saving', name: 'A3' });
45
+ });
46
+ it('a value put back to what was read is no edit', async () => {
47
+ const form = await open();
48
+ form.draft.set('name', 'A2');
49
+ form.draft.set('name', 'A');
50
+ expect(form.draft.dirty).to.equal(false);
51
+ expect(form.draft.value('name')).to.equal('A');
52
+ });
53
+ it('a field typed back to what was read while a save is out stays an edit after the answer', async () => {
54
+ const form = await open();
55
+ form.draft.set('name', 'B');
56
+ let release;
57
+ const saving = form.draft.submit(() => new Promise(resolve => (release = resolve)));
58
+ form.draft.set('name', 'A');
59
+ release('saved');
60
+ await saving;
61
+ expect(form.draft.edits).to.deep.equal({ name: 'A' });
62
+ expect(form.draft.value('name')).to.equal('A');
63
+ });
64
+ it('another record drops the edits', async () => {
65
+ const form = await open();
66
+ form.draft.set('name', 'A2');
67
+ form.recordId = 'b';
68
+ await form.updateComplete;
69
+ expect(form.draft.dirty).to.equal(false);
70
+ });
71
+ it('is known to its host while connected', async () => {
72
+ const form = await open();
73
+ expect(draftsOn(form)).to.deep.equal([form.draft]);
74
+ form.remove();
75
+ expect(draftsOn(form)).to.deep.equal([]);
76
+ });
77
+ });
78
+ class KeptForm extends LitElement {
79
+ constructor() {
80
+ super(...arguments);
81
+ this.recordId = 'a';
82
+ this.draft = new DraftController(this, {
83
+ read: () => this.read,
84
+ key: () => this.recordId,
85
+ keep: { address: () => ({ user: 'u', domain: 'd', screen: '/rules', target: this.recordId }) }
86
+ });
87
+ }
88
+ static { this.properties = { read: { attribute: false }, recordId: {} }; }
89
+ }
90
+ customElements.define('kept-rule-form', KeptForm);
91
+ describe('DraftController keep', () => {
92
+ const at = (target) => draftKey({ user: 'u', domain: 'd', screen: '/rules', target });
93
+ let wait;
94
+ beforeEach(async () => {
95
+ wait = UnsavedDraftController.WAIT_MS;
96
+ UnsavedDraftController.WAIT_MS = 0;
97
+ await removeDraft(at('a'));
98
+ await removeDraft(at('b'));
99
+ });
100
+ afterEach(() => (UnsavedDraftController.WAIT_MS = wait));
101
+ const settle = () => new Promise(resolve => setTimeout(resolve, 50));
102
+ it('writes the edits for the record, and keeps them when the form moves to another', async () => {
103
+ const form = await fixture(html `<kept-rule-form .read=${{ name: 'A', body: 'old' }}></kept-rule-form>`);
104
+ form.draft.set('name', 'A2');
105
+ await settle();
106
+ expect((await readDraft(at('a')))?.value).to.deep.equal({ name: 'A2' });
107
+ expect(form.draft.kept).to.equal(true);
108
+ /* Typed and moved at once: what was typed is written for the record left, not lost or written for the next. */
109
+ form.draft.set('name', 'A3');
110
+ form.recordId = 'b';
111
+ await form.updateComplete;
112
+ await settle();
113
+ expect(form.draft.dirty).to.equal(false);
114
+ expect((await readDraft(at('a')))?.value).to.deep.equal({ name: 'A3' });
115
+ expect(await readDraft(at('b'))).to.equal(undefined);
116
+ });
117
+ it('a save that clears the edits drops what was kept', async () => {
118
+ const form = await fixture(html `<kept-rule-form .read=${{ name: 'A', body: 'old' }}></kept-rule-form>`);
119
+ form.draft.set('name', 'A2');
120
+ await settle();
121
+ await form.draft.submit(async () => 'saved');
122
+ await settle();
123
+ expect(await readDraft(at('a'))).to.equal(undefined);
124
+ });
125
+ it('a save that answers after a move clears what was kept for the record it was sent from, and nothing else', async () => {
126
+ const form = await fixture(html `<kept-rule-form .read=${{ name: 'A', body: 'old' }}></kept-rule-form>`);
127
+ form.draft.set('name', 'A2');
128
+ await settle();
129
+ let answer;
130
+ const saving = form.draft.submit(() => new Promise(resolve => (answer = resolve)));
131
+ form.recordId = 'b';
132
+ await form.updateComplete;
133
+ form.draft.set('name', 'A2');
134
+ await settle();
135
+ answer('saved');
136
+ await saving;
137
+ await settle();
138
+ expect(await readDraft(at('a'))).to.equal(undefined);
139
+ expect(form.draft.edits).to.deep.equal({ name: 'A2' }, "b's edit equal to what was sent stays");
140
+ expect((await readDraft(at('b')))?.value).to.deep.equal({ name: 'A2' });
141
+ });
142
+ it('a save that answers after the form came back to its record leaves what is edited there now kept', async () => {
143
+ const form = await fixture(html `<kept-rule-form .read=${{ name: 'A', body: 'old' }}></kept-rule-form>`);
144
+ form.draft.set('name', 'A2');
145
+ await settle();
146
+ let answer;
147
+ const saving = form.draft.submit(() => new Promise(resolve => (answer = resolve)));
148
+ form.recordId = 'b';
149
+ await form.updateComplete;
150
+ form.recordId = 'a';
151
+ await form.updateComplete;
152
+ form.draft.set('name', 'A2');
153
+ await settle();
154
+ answer('saved');
155
+ await saving;
156
+ await settle();
157
+ expect(form.draft.edits).to.deep.equal({ name: 'A2' });
158
+ expect((await readDraft(at('a')))?.value).to.deep.equal({ name: 'A2' });
159
+ expect(form.draft.kept).to.equal(true);
160
+ });
161
+ it('offers nothing when the record answers with nothing kept', async () => {
162
+ const form = await fixture(html `<kept-rule-form .read=${{ name: 'A', body: 'old' }}></kept-rule-form>`);
163
+ form.dispatchEvent(new Event(QUERY_ANSWERED));
164
+ await settle();
165
+ expect(form.draft.dirty).to.equal(false);
166
+ });
167
+ });
168
+ //# sourceMappingURL=draft-controller.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft-controller.test.js","sourceRoot":"","sources":["../../test/draft-controller.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport { html, LitElement } from 'lit'\n\nimport { QUERY_ANSWERED } from '@operato/graphql'\nimport { DraftController, draftsOn } from '../src/drafts/draft-controller.js'\nimport { draftKey, readDraft, removeDraft } from '../src/drafts/draft-store.js'\nimport { UnsavedDraftController } from '../src/drafts/unsaved-draft-controller.js'\n\n/**\n * An edit form over what was read (ADR-0190 decision 2): a field shows its edit, else the read; a save clears only\n * the edits still as sent; another record drops the edits.\n */\ntype Rule = { name: string; body: string }\n\nclass RuleForm extends LitElement {\n static properties = { read: { attribute: false }, recordId: {} }\n read?: Rule\n recordId = 'a'\n draft = new DraftController<Rule>(this, { read: () => this.read, key: () => this.recordId })\n}\ncustomElements.define('rule-form', RuleForm)\n\nconst open = async () => {\n const form = await fixture<RuleForm>(html`<rule-form .read=${{ name: 'A', body: 'old' }}></rule-form>`)\n await form.updateComplete\n return form\n}\n\ndescribe('DraftController', () => {\n it('shows an edit, else what was read', async () => {\n const form = await open()\n expect(form.draft.value('name')).to.equal('A')\n form.draft.set('name', 'A2')\n expect(form.draft.value('name')).to.equal('A2')\n expect(form.draft.value('body')).to.equal('old')\n expect(form.draft.dirty).to.equal(true)\n })\n\n it('a save clears only what it sent and is still as sent; a failed save clears nothing', async () => {\n const form = await open()\n form.draft.set('name', 'A2')\n let release!: (answer: string | undefined) => void\n const saving = form.draft.submit(sent => {\n expect(sent).to.deep.equal({ name: 'A2' })\n return new Promise<string | undefined>(resolve => (release = resolve))\n })\n form.draft.set('body', 'typed while saving')\n release('saved')\n await saving\n expect(form.draft.edits).to.deep.equal({ body: 'typed while saving' })\n\n form.draft.set('name', 'A3')\n await form.draft.submit(async () => undefined)\n expect(form.draft.edits).to.deep.equal({ body: 'typed while saving', name: 'A3' })\n })\n\n it('a value put back to what was read is no edit', async () => {\n const form = await open()\n form.draft.set('name', 'A2')\n form.draft.set('name', 'A')\n expect(form.draft.dirty).to.equal(false)\n expect(form.draft.value('name')).to.equal('A')\n })\n\n it('a field typed back to what was read while a save is out stays an edit after the answer', async () => {\n const form = await open()\n form.draft.set('name', 'B')\n let release!: (answer: string | undefined) => void\n const saving = form.draft.submit(() => new Promise<string | undefined>(resolve => (release = resolve)))\n form.draft.set('name', 'A')\n release('saved')\n await saving\n expect(form.draft.edits).to.deep.equal({ name: 'A' })\n expect(form.draft.value('name')).to.equal('A')\n })\n\n it('another record drops the edits', async () => {\n const form = await open()\n form.draft.set('name', 'A2')\n form.recordId = 'b'\n await form.updateComplete\n expect(form.draft.dirty).to.equal(false)\n })\n\n it('is known to its host while connected', async () => {\n const form = await open()\n expect(draftsOn(form)).to.deep.equal([form.draft])\n form.remove()\n expect(draftsOn(form)).to.deep.equal([])\n })\n})\n\nclass KeptForm extends LitElement {\n static properties = { read: { attribute: false }, recordId: {} }\n read?: Rule\n recordId = 'a'\n draft = new DraftController<Rule>(this, {\n read: () => this.read,\n key: () => this.recordId,\n keep: { address: () => ({ user: 'u', domain: 'd', screen: '/rules', target: this.recordId }) }\n })\n}\ncustomElements.define('kept-rule-form', KeptForm)\n\ndescribe('DraftController keep', () => {\n const at = (target: string) => draftKey({ user: 'u', domain: 'd', screen: '/rules', target })\n let wait: number\n beforeEach(async () => {\n wait = UnsavedDraftController.WAIT_MS\n UnsavedDraftController.WAIT_MS = 0\n await removeDraft(at('a'))\n await removeDraft(at('b'))\n })\n afterEach(() => (UnsavedDraftController.WAIT_MS = wait))\n const settle = () => new Promise(resolve => setTimeout(resolve, 50))\n\n it('writes the edits for the record, and keeps them when the form moves to another', async () => {\n const form = await fixture<KeptForm>(html`<kept-rule-form .read=${{ name: 'A', body: 'old' }}></kept-rule-form>`)\n form.draft.set('name', 'A2')\n await settle()\n expect((await readDraft(at('a')))?.value).to.deep.equal({ name: 'A2' })\n expect(form.draft.kept).to.equal(true)\n /* Typed and moved at once: what was typed is written for the record left, not lost or written for the next. */\n form.draft.set('name', 'A3')\n form.recordId = 'b'\n await form.updateComplete\n await settle()\n expect(form.draft.dirty).to.equal(false)\n expect((await readDraft(at('a')))?.value).to.deep.equal({ name: 'A3' })\n expect(await readDraft(at('b'))).to.equal(undefined)\n })\n\n it('a save that clears the edits drops what was kept', async () => {\n const form = await fixture<KeptForm>(html`<kept-rule-form .read=${{ name: 'A', body: 'old' }}></kept-rule-form>`)\n form.draft.set('name', 'A2')\n await settle()\n await form.draft.submit(async () => 'saved')\n await settle()\n expect(await readDraft(at('a'))).to.equal(undefined)\n })\n\n it('a save that answers after a move clears what was kept for the record it was sent from, and nothing else', async () => {\n const form = await fixture<KeptForm>(html`<kept-rule-form .read=${{ name: 'A', body: 'old' }}></kept-rule-form>`)\n form.draft.set('name', 'A2')\n await settle()\n let answer!: (value: string) => void\n const saving = form.draft.submit(() => new Promise<string>(resolve => (answer = resolve)))\n form.recordId = 'b'\n await form.updateComplete\n form.draft.set('name', 'A2')\n await settle()\n answer('saved')\n await saving\n await settle()\n expect(await readDraft(at('a'))).to.equal(undefined)\n expect(form.draft.edits).to.deep.equal({ name: 'A2' }, \"b's edit equal to what was sent stays\")\n expect((await readDraft(at('b')))?.value).to.deep.equal({ name: 'A2' })\n })\n\n it('a save that answers after the form came back to its record leaves what is edited there now kept', async () => {\n const form = await fixture<KeptForm>(html`<kept-rule-form .read=${{ name: 'A', body: 'old' }}></kept-rule-form>`)\n form.draft.set('name', 'A2')\n await settle()\n let answer!: (value: string) => void\n const saving = form.draft.submit(() => new Promise<string>(resolve => (answer = resolve)))\n form.recordId = 'b'\n await form.updateComplete\n form.recordId = 'a'\n await form.updateComplete\n form.draft.set('name', 'A2')\n await settle()\n answer('saved')\n await saving\n await settle()\n expect(form.draft.edits).to.deep.equal({ name: 'A2' })\n expect((await readDraft(at('a')))?.value).to.deep.equal({ name: 'A2' })\n expect(form.draft.kept).to.equal(true)\n })\n\n it('offers nothing when the record answers with nothing kept', async () => {\n const form = await fixture<KeptForm>(html`<kept-rule-form .read=${{ name: 'A', body: 'old' }}></kept-rule-form>`)\n form.dispatchEvent(new Event(QUERY_ANSWERED))\n await settle()\n expect(form.draft.dirty).to.equal(false)\n })\n})\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,100 @@
1
+ import { expect, fixture } from '@open-wc/testing';
2
+ import { html, LitElement } from 'lit';
3
+ import { OxPrompt } from '@operato/popup';
4
+ import { QUERY_ANSWERED } from '@operato/graphql';
5
+ import { DraftController } from '../src/drafts/draft-controller.js';
6
+ import { draftOwner } from '../src/drafts/draft-owner.js';
7
+ import { draftKey, readDraft, removeDraftsOf } from '../src/drafts/draft-store.js';
8
+ import { UnsavedDraftController } from '../src/drafts/unsaved-draft-controller.js';
9
+ import { store } from '../src/store.js';
10
+ /**
11
+ * A draft belongs to the signed-in person (ADR-0190): kept and offered back to them, never to someone else, and
12
+ * taken away when they sign out. With no one signed in nothing is written.
13
+ */
14
+ let signedIn = null;
15
+ store.addReducers({ auth: () => ({ user: signedIn }) });
16
+ const signIn = (user) => {
17
+ signedIn = user;
18
+ store.dispatch({ type: 'test-sign-in' });
19
+ };
20
+ class OwnedForm extends LitElement {
21
+ constructor() {
22
+ super(...arguments);
23
+ this.read = { body: 'old' };
24
+ this.draft = new DraftController(this, {
25
+ read: () => this.read,
26
+ key: () => 'n-1',
27
+ keep: {
28
+ address: () => {
29
+ const user = draftOwner();
30
+ return user ? { user, domain: 'd', screen: '/notes', target: 'n-1' } : undefined;
31
+ }
32
+ }
33
+ });
34
+ }
35
+ }
36
+ customElements.define('owned-note-form', OwnedForm);
37
+ describe('draftOwner', () => {
38
+ const settle = () => new Promise(resolve => setTimeout(resolve, 50));
39
+ const keyOf = (user) => draftKey({ user, domain: 'd', screen: '/notes', target: 'n-1' });
40
+ const open = OxPrompt.open;
41
+ let wait;
42
+ beforeEach(async () => {
43
+ wait = UnsavedDraftController.WAIT_MS;
44
+ UnsavedDraftController.WAIT_MS = 0;
45
+ await removeDraftsOf('kim');
46
+ await removeDraftsOf('lee');
47
+ });
48
+ afterEach(() => {
49
+ UnsavedDraftController.WAIT_MS = wait;
50
+ OxPrompt.open = open;
51
+ signIn(null);
52
+ });
53
+ it('is the signed-in id, and nobody while no one is signed in', () => {
54
+ signIn({ id: 'kim' });
55
+ expect(draftOwner()).to.equal('kim');
56
+ signIn({});
57
+ expect(draftOwner()).to.equal(undefined);
58
+ signIn(null);
59
+ expect(draftOwner()).to.equal(undefined);
60
+ });
61
+ it('a draft is written for its owner and offered back to them', async () => {
62
+ signIn({ id: 'kim' });
63
+ const form = await fixture(html `<owned-note-form></owned-note-form>`);
64
+ form.draft.set('body', 'typed');
65
+ await settle();
66
+ expect((await readDraft(keyOf('kim')))?.value).to.deep.equal({ body: 'typed' });
67
+ form.remove();
68
+ OxPrompt.open = async () => true;
69
+ const again = await fixture(html `<owned-note-form></owned-note-form>`);
70
+ again.dispatchEvent(new Event(QUERY_ANSWERED));
71
+ await settle();
72
+ expect(again.draft.value('body')).to.equal('typed');
73
+ });
74
+ it('someone else is offered nothing, and signing out takes the draft away', async () => {
75
+ signIn({ id: 'kim' });
76
+ const form = await fixture(html `<owned-note-form></owned-note-form>`);
77
+ form.draft.set('body', 'typed');
78
+ await settle();
79
+ form.remove();
80
+ signIn({ id: 'lee' });
81
+ let asked = false;
82
+ OxPrompt.open = async () => (asked = true);
83
+ const theirs = await fixture(html `<owned-note-form></owned-note-form>`);
84
+ theirs.dispatchEvent(new Event(QUERY_ANSWERED));
85
+ await settle();
86
+ expect(asked).to.equal(false);
87
+ expect(theirs.draft.value('body')).to.equal('old');
88
+ await removeDraftsOf('kim');
89
+ expect(await readDraft(keyOf('kim'))).to.equal(undefined);
90
+ });
91
+ it('with no one signed in nothing is written', async () => {
92
+ signIn(null);
93
+ const form = await fixture(html `<owned-note-form></owned-note-form>`);
94
+ form.draft.set('body', 'typed');
95
+ await settle();
96
+ expect(await readDraft(keyOf('kim'))).to.equal(undefined);
97
+ expect(form.draft.kept).to.equal(false);
98
+ });
99
+ });
100
+ //# sourceMappingURL=draft-owner.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft-owner.test.js","sourceRoot":"","sources":["../../test/draft-owner.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport { html, LitElement } from 'lit'\nimport { OxPrompt } from '@operato/popup'\n\nimport { QUERY_ANSWERED } from '@operato/graphql'\nimport { DraftController } from '../src/drafts/draft-controller.js'\nimport { draftOwner } from '../src/drafts/draft-owner.js'\nimport { draftKey, readDraft, removeDraftsOf } from '../src/drafts/draft-store.js'\nimport { UnsavedDraftController } from '../src/drafts/unsaved-draft-controller.js'\nimport { store } from '../src/store.js'\n\n/**\n * A draft belongs to the signed-in person (ADR-0190): kept and offered back to them, never to someone else, and\n * taken away when they sign out. With no one signed in nothing is written.\n */\nlet signedIn: { id?: string } | null = null\nstore.addReducers({ auth: () => ({ user: signedIn }) })\nconst signIn = (user: { id?: string } | null) => {\n signedIn = user\n store.dispatch({ type: 'test-sign-in' })\n}\n\ntype Note = { body: string }\n\nclass OwnedForm extends LitElement {\n read?: Note = { body: 'old' }\n draft = new DraftController<Note>(this, {\n read: () => this.read,\n key: () => 'n-1',\n keep: {\n address: () => {\n const user = draftOwner()\n return user ? { user, domain: 'd', screen: '/notes', target: 'n-1' } : undefined\n }\n }\n })\n}\ncustomElements.define('owned-note-form', OwnedForm)\n\ndescribe('draftOwner', () => {\n const settle = () => new Promise(resolve => setTimeout(resolve, 50))\n const keyOf = (user: string) => draftKey({ user, domain: 'd', screen: '/notes', target: 'n-1' })\n const open = OxPrompt.open\n let wait: number\n beforeEach(async () => {\n wait = UnsavedDraftController.WAIT_MS\n UnsavedDraftController.WAIT_MS = 0\n await removeDraftsOf('kim')\n await removeDraftsOf('lee')\n })\n afterEach(() => {\n UnsavedDraftController.WAIT_MS = wait\n ;(OxPrompt as any).open = open\n signIn(null)\n })\n\n it('is the signed-in id, and nobody while no one is signed in', () => {\n signIn({ id: 'kim' })\n expect(draftOwner()).to.equal('kim')\n signIn({})\n expect(draftOwner()).to.equal(undefined)\n signIn(null)\n expect(draftOwner()).to.equal(undefined)\n })\n\n it('a draft is written for its owner and offered back to them', async () => {\n signIn({ id: 'kim' })\n const form = await fixture<OwnedForm>(html`<owned-note-form></owned-note-form>`)\n form.draft.set('body', 'typed')\n await settle()\n expect((await readDraft(keyOf('kim')))?.value).to.deep.equal({ body: 'typed' })\n form.remove()\n\n ;(OxPrompt as any).open = async () => true\n const again = await fixture<OwnedForm>(html`<owned-note-form></owned-note-form>`)\n again.dispatchEvent(new Event(QUERY_ANSWERED))\n await settle()\n expect(again.draft.value('body')).to.equal('typed')\n })\n\n it('someone else is offered nothing, and signing out takes the draft away', async () => {\n signIn({ id: 'kim' })\n const form = await fixture<OwnedForm>(html`<owned-note-form></owned-note-form>`)\n form.draft.set('body', 'typed')\n await settle()\n form.remove()\n\n signIn({ id: 'lee' })\n let asked = false\n ;(OxPrompt as any).open = async () => (asked = true)\n const theirs = await fixture<OwnedForm>(html`<owned-note-form></owned-note-form>`)\n theirs.dispatchEvent(new Event(QUERY_ANSWERED))\n await settle()\n expect(asked).to.equal(false)\n expect(theirs.draft.value('body')).to.equal('old')\n\n await removeDraftsOf('kim')\n expect(await readDraft(keyOf('kim'))).to.equal(undefined)\n })\n\n it('with no one signed in nothing is written', async () => {\n signIn(null)\n const form = await fixture<OwnedForm>(html`<owned-note-form></owned-note-form>`)\n form.draft.set('body', 'typed')\n await settle()\n expect(await readDraft(keyOf('kim'))).to.equal(undefined)\n expect(form.draft.kept).to.equal(false)\n })\n})\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { expect, fixture } from '@open-wc/testing';
2
+ import { html } from 'lit';
3
+ import { DraftController } from '../src/drafts/draft-controller.js';
4
+ import { PageView } from '../src/app/pages/page-view.js';
5
+ /** A page's drafts (ADR-0190) are its unsaved state: the shell asks before they are lost, and drops them on discard. */
6
+ class EditingPage extends PageView {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.draft = new DraftController(this, { read: () => ({ name: 'A' }) });
10
+ }
11
+ }
12
+ customElements.define('editing-page', EditingPage);
13
+ describe('page drafts', () => {
14
+ it('an edited draft makes the page dirty; a discard drops it', async () => {
15
+ const page = await fixture(html `<editing-page></editing-page>`);
16
+ expect(page.addressDirty).to.equal(false);
17
+ page.draft.set('name', 'A2');
18
+ expect(page.addressDirty).to.equal(true);
19
+ page.discardAddressEdits({ route: false });
20
+ expect(page.addressDirty).to.equal(false);
21
+ expect(page.draft.value('name')).to.equal('A');
22
+ });
23
+ });
24
+ //# sourceMappingURL=page-drafts.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-drafts.test.js","sourceRoot":"","sources":["../../test/page-drafts.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAA;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAA;AAExD,wHAAwH;AACxH,MAAM,WAAY,SAAQ,QAAQ;IAAlC;;QACE,UAAK,GAAG,IAAI,eAAe,CAAmB,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IACtF,CAAC;CAAA;AACD,cAAc,CAAC,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC,CAAA;AAElD,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,MAAM,IAAI,GAAG,MAAM,OAAO,CAAc,IAAI,CAAA,+BAA+B,CAAC,CAAA;QAC5E,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACzC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;QAC5B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACxC,IAAI,CAAC,mBAAmB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;QAC1C,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACzC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport { html } from 'lit'\n\nimport { DraftController } from '../src/drafts/draft-controller.js'\nimport { PageView } from '../src/app/pages/page-view.js'\n\n/** A page's drafts (ADR-0190) are its unsaved state: the shell asks before they are lost, and drops them on discard. */\nclass EditingPage extends PageView {\n draft = new DraftController<{ name: string }>(this, { read: () => ({ name: 'A' }) })\n}\ncustomElements.define('editing-page', EditingPage)\n\ndescribe('page drafts', () => {\n it('an edited draft makes the page dirty; a discard drops it', async () => {\n const page = await fixture<EditingPage>(html`<editing-page></editing-page>`)\n expect(page.addressDirty).to.equal(false)\n page.draft.set('name', 'A2')\n expect(page.addressDirty).to.equal(true)\n page.discardAddressEdits({ route: false })\n expect(page.addressDirty).to.equal(false)\n expect(page.draft.value('name')).to.equal('A')\n })\n})\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,89 @@
1
+ import { expect, fixture } from '@open-wc/testing';
2
+ import gql from 'graphql-tag';
3
+ import { html } from 'lit';
4
+ import { QueryController } from '@operato/graphql';
5
+ import { PageView } from '../src/app/pages/page-view.js';
6
+ /** A page refresh (ADR-0174) reads again what the page declared it reads (ADR-0190), as it reads its grids again. */
7
+ const frame = () => new Promise(resolve => requestAnimationFrame(resolve));
8
+ const settle = () => new Promise(resolve => setTimeout(resolve, 20));
9
+ class DeclaringPage extends PageView {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.contexts = [];
13
+ this.rule = new QueryController(this, gql `
14
+ query {
15
+ rule {
16
+ name
17
+ }
18
+ }
19
+ `);
20
+ }
21
+ get context() {
22
+ return { title: this.rule?.data?.rule?.name ?? '' };
23
+ }
24
+ updateContext() {
25
+ this.contexts.push(this.context.title);
26
+ }
27
+ render() {
28
+ return html `${this.rule.data?.rule?.name ?? ''}`;
29
+ }
30
+ }
31
+ customElements.define('declaring-page', DeclaringPage);
32
+ describe('page refresh and declared queries', () => {
33
+ let asked = 0;
34
+ let fetched;
35
+ beforeEach(() => {
36
+ asked = 0;
37
+ fetched = window.fetch;
38
+ window.fetch = async () => {
39
+ asked++;
40
+ return new Response(JSON.stringify({ data: { rule: { name: `read ${asked}` } } }), {
41
+ headers: { 'content-type': 'application/json' }
42
+ });
43
+ };
44
+ });
45
+ afterEach(() => (window.fetch = fetched));
46
+ it('reads the declared query again', async () => {
47
+ const page = await fixture(html `<declaring-page></declaring-page>`);
48
+ page.lifecycle = { active: true, params: {} };
49
+ page.active = true;
50
+ await page.updateComplete;
51
+ await frame();
52
+ await settle();
53
+ expect(asked).to.equal(1);
54
+ await page.refresh();
55
+ await settle();
56
+ expect(asked).to.equal(2);
57
+ expect(page.rule.data?.rule.name).to.equal('read 2');
58
+ });
59
+ it('says its context again once a declared query answered - a title read from the data', async () => {
60
+ const answer = window.fetch;
61
+ window.fetch = async (...args) => (await new Promise(resolve => setTimeout(resolve, 150)), answer(...args));
62
+ const page = await fixture(html `<declaring-page></declaring-page>`);
63
+ page.lifecycle = { active: true, params: {} };
64
+ page.active = true;
65
+ await page.updateComplete;
66
+ await frame();
67
+ expect(page.contexts[page.contexts.length - 1]).to.equal('');
68
+ await new Promise(resolve => setTimeout(resolve, 300));
69
+ expect(page.contexts[page.contexts.length - 1]).to.equal('read 1');
70
+ });
71
+ it('reads the declared query again when the page is shown again, not on its first opening', async () => {
72
+ const page = await fixture(html `<declaring-page></declaring-page>`);
73
+ page.lifecycle = { active: true, params: {} };
74
+ page.active = true;
75
+ await page.updateComplete;
76
+ await frame();
77
+ await settle();
78
+ expect(asked).to.equal(1);
79
+ page.active = false;
80
+ await page.updateComplete;
81
+ await settle();
82
+ expect(asked).to.equal(1);
83
+ page.active = true;
84
+ await page.updateComplete;
85
+ await settle();
86
+ expect(asked).to.equal(2);
87
+ });
88
+ });
89
+ //# sourceMappingURL=page-refresh-queries.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-refresh-queries.test.js","sourceRoot":"","sources":["../../test/page-refresh-queries.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport gql from 'graphql-tag'\nimport { html } from 'lit'\n\nimport { QueryController } from '@operato/graphql'\nimport { PageView } from '../src/app/pages/page-view.js'\n\n/** A page refresh (ADR-0174) reads again what the page declared it reads (ADR-0190), as it reads its grids again. */\n\nconst frame = () => new Promise(resolve => requestAnimationFrame(resolve))\nconst settle = () => new Promise(resolve => setTimeout(resolve, 20))\n\nclass DeclaringPage extends PageView {\n contexts: string[] = []\n get context() {\n return { title: this.rule?.data?.rule?.name ?? '' }\n }\n updateContext() {\n this.contexts.push(this.context.title)\n }\n rule = new QueryController(\n this,\n gql`\n query {\n rule {\n name\n }\n }\n `\n )\n render() {\n return html`${this.rule.data?.rule?.name ?? ''}`\n }\n}\ncustomElements.define('declaring-page', DeclaringPage)\n\ndescribe('page refresh and declared queries', () => {\n let asked = 0\n let fetched: typeof window.fetch\n beforeEach(() => {\n asked = 0\n fetched = window.fetch\n window.fetch = async () => {\n asked++\n return new Response(JSON.stringify({ data: { rule: { name: `read ${asked}` } } }), {\n headers: { 'content-type': 'application/json' }\n })\n }\n })\n afterEach(() => (window.fetch = fetched))\n\n it('reads the declared query again', async () => {\n const page = await fixture<DeclaringPage>(html`<declaring-page></declaring-page>`)\n page.lifecycle = { active: true, params: {} }\n page.active = true\n await page.updateComplete\n await frame()\n await settle()\n expect(asked).to.equal(1)\n await page.refresh()\n await settle()\n expect(asked).to.equal(2)\n expect(page.rule.data?.rule.name).to.equal('read 2')\n })\n\n it('says its context again once a declared query answered - a title read from the data', async () => {\n const answer = window.fetch\n window.fetch = async (...args) => (await new Promise(resolve => setTimeout(resolve, 150)), answer(...args))\n const page = await fixture<DeclaringPage>(html`<declaring-page></declaring-page>`)\n page.lifecycle = { active: true, params: {} }\n page.active = true\n await page.updateComplete\n await frame()\n expect(page.contexts[page.contexts.length - 1]).to.equal('')\n await new Promise(resolve => setTimeout(resolve, 300))\n expect(page.contexts[page.contexts.length - 1]).to.equal('read 1')\n })\n\n it('reads the declared query again when the page is shown again, not on its first opening', async () => {\n const page = await fixture<DeclaringPage>(html`<declaring-page></declaring-page>`)\n page.lifecycle = { active: true, params: {} }\n page.active = true\n await page.updateComplete\n await frame()\n await settle()\n expect(asked).to.equal(1)\n page.active = false\n await page.updateComplete\n await settle()\n expect(asked).to.equal(1)\n page.active = true\n await page.updateComplete\n await settle()\n expect(asked).to.equal(2)\n })\n})\n"]}