@operato/shell 10.29.52 → 10.29.60
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.
- package/CHANGELOG.md +17 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +1076 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/test/fixtures/guarded-define.ts.html +118 -0
- package/coverage/lcov-report/test/fixtures/index.html +161 -0
- package/coverage/lcov-report/test/fixtures/no-registration.ts.html +91 -0
- package/coverage/lcov-report/test/fixtures/registers.ts.html +118 -0
- package/coverage/lcov-report/test/fixtures/unguarded-define.ts.html +112 -0
- package/coverage/lcov.info +58812 -0
- package/dist/src/app/pages/page-view.d.ts +22 -0
- package/dist/src/app/pages/page-view.js +50 -6
- package/dist/src/app/pages/page-view.js.map +1 -1
- package/dist/test/page-refresh.test.d.ts +1 -0
- package/dist/test/page-refresh.test.js +151 -0
- package/dist/test/page-refresh.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/test/page-refresh.test.ts +166 -0
|
@@ -81,6 +81,28 @@ export declare class PageView extends LitElement {
|
|
|
81
81
|
* @param force - If true, forces an update of the page.
|
|
82
82
|
*/
|
|
83
83
|
pageUpdate(changes?: any, force?: boolean): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Reads the page again, as if it had just been opened at the address it shows (ADR-0174). The tab menu's
|
|
86
|
+
* refresh calls this; a page draws no refresh control of its own.
|
|
87
|
+
*
|
|
88
|
+
* Unsaved edits follow the rule of a route change on the page on screen: grid edits a draft keeps are offered
|
|
89
|
+
* back once the grid has read again; anything else is asked about first, and on "keep" nothing happens.
|
|
90
|
+
*
|
|
91
|
+
* The lifecycle is played from nothing, so `pageInitialized` runs and `pageUpdated` sees `initialized`,
|
|
92
|
+
* `active`, `params` and `resourceId` all changed. A page not on screen plays it when it is next shown.
|
|
93
|
+
* A grid that had been read and that the page did not read again in that play reads again itself.
|
|
94
|
+
*
|
|
95
|
+
* A refresh nobody asked for (a record changed elsewhere) passes `ask: false`: it shows nothing the user did not
|
|
96
|
+
* ask for, so with unsaved edits of any kind - even ones a draft keeps - it reads nothing.
|
|
97
|
+
*
|
|
98
|
+
* @returns false when the user kept the unsaved edits, or when `ask: false` met unsaved edits.
|
|
99
|
+
*/
|
|
100
|
+
refresh({ ask }?: {
|
|
101
|
+
ask?: boolean;
|
|
102
|
+
}): Promise<boolean>;
|
|
103
|
+
/** The grids of a refresh asked for while the page was not on screen: read once it is shown. */
|
|
104
|
+
private unread?;
|
|
105
|
+
private readAgain;
|
|
84
106
|
/**
|
|
85
107
|
* Resets the page. Subclasses can override this method to perform custom reset logic.
|
|
86
108
|
*/
|
|
@@ -195,9 +195,13 @@ export class PageView extends LitElement {
|
|
|
195
195
|
* case 2. page가 active 상태에서 lifecycle 정보가 바뀌었다.
|
|
196
196
|
**/
|
|
197
197
|
if (active) {
|
|
198
|
-
this.
|
|
198
|
+
const unread = this.unread;
|
|
199
|
+
this.unread = undefined;
|
|
200
|
+
const played = this.pageUpdate({
|
|
199
201
|
active
|
|
200
202
|
});
|
|
203
|
+
if (unread)
|
|
204
|
+
void played.then(() => this.readAgain(unread));
|
|
201
205
|
}
|
|
202
206
|
else if (oldActive) {
|
|
203
207
|
this.pageUpdate({
|
|
@@ -263,11 +267,10 @@ export class PageView extends LitElement {
|
|
|
263
267
|
* 방금 초기화된 경우라면, 엘리먼트들이 만들어지지 않았을 가능성이 있으므로,
|
|
264
268
|
* 다음 animationFrame에서 pageUpdated 콜백을 호출한다.
|
|
265
269
|
*/
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
});
|
|
270
|
+
await new Promise(resolve => requestAnimationFrame(resolve));
|
|
271
|
+
await this.pageUpdated(changed, after, before);
|
|
272
|
+
/* active page인 경우에는, page Context 갱신도 필요할 것이다. */
|
|
273
|
+
after.active && this.updateContext();
|
|
271
274
|
}
|
|
272
275
|
else {
|
|
273
276
|
await this.pageDisposed(after);
|
|
@@ -279,6 +282,47 @@ export class PageView extends LitElement {
|
|
|
279
282
|
after.active && this.updateContext();
|
|
280
283
|
}
|
|
281
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Reads the page again, as if it had just been opened at the address it shows (ADR-0174). The tab menu's
|
|
287
|
+
* refresh calls this; a page draws no refresh control of its own.
|
|
288
|
+
*
|
|
289
|
+
* Unsaved edits follow the rule of a route change on the page on screen: grid edits a draft keeps are offered
|
|
290
|
+
* back once the grid has read again; anything else is asked about first, and on "keep" nothing happens.
|
|
291
|
+
*
|
|
292
|
+
* The lifecycle is played from nothing, so `pageInitialized` runs and `pageUpdated` sees `initialized`,
|
|
293
|
+
* `active`, `params` and `resourceId` all changed. A page not on screen plays it when it is next shown.
|
|
294
|
+
* A grid that had been read and that the page did not read again in that play reads again itself.
|
|
295
|
+
*
|
|
296
|
+
* A refresh nobody asked for (a record changed elsewhere) passes `ask: false`: it shows nothing the user did not
|
|
297
|
+
* ask for, so with unsaved edits of any kind - even ones a draft keeps - it reads nothing.
|
|
298
|
+
*
|
|
299
|
+
* @returns false when the user kept the unsaved edits, or when `ask: false` met unsaved edits.
|
|
300
|
+
*/
|
|
301
|
+
async refresh({ ask = true } = {}) {
|
|
302
|
+
if (!ask && (this.addressDirty || this.listDirty(false)))
|
|
303
|
+
return false;
|
|
304
|
+
if (this.addressDirty || (this.listDirty(false) && !this.addressEditsKept)) {
|
|
305
|
+
if (!(await this.canDeactivate()))
|
|
306
|
+
return false;
|
|
307
|
+
this.discardAddressEdits();
|
|
308
|
+
}
|
|
309
|
+
const grids = Array.from(this.renderRoot.querySelectorAll('ox-grist')).map(grid => ({
|
|
310
|
+
grid,
|
|
311
|
+
reads: grid.reads || 0
|
|
312
|
+
}));
|
|
313
|
+
await this.pageDispose();
|
|
314
|
+
this._oldLifecycleInfo$ = undefined;
|
|
315
|
+
if (!this.active) {
|
|
316
|
+
this.unread = grids;
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
await this.pageUpdate();
|
|
320
|
+
await this.readAgain(grids);
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
async readAgain(grids) {
|
|
324
|
+
await Promise.all(grids.filter(({ grid, reads }) => reads > 0 && grid.reads === reads).map(({ grid }) => grid.fetch()));
|
|
325
|
+
}
|
|
282
326
|
/**
|
|
283
327
|
* Resets the page. Subclasses can override this method to perform custom reset logic.
|
|
284
328
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-view.js","sourceRoot":"","sources":["../../../../src/app/pages/page-view.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, PropertyValues, type ReactiveControllerHost } from 'lit'\nimport { property } from 'lit/decorators.js'\nimport isEqual from 'lodash-es/isEqual.js'\nimport { i18next } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\n\nimport { UPDATE_CONTEXT } from '../../actions/const.js'\nimport { store } from '../../store.js'\nimport { getPathInfo } from '@operato/utils'\nimport { addressController } from '@operato/utils/address.js'\nimport { UnsavedDraftController } from '../../drafts/unsaved-draft-controller.js'\n\ntype ListDraftSource = HTMLElement &\n ReactiveControllerHost & {\n draftSnapshot?: { value: unknown; base: string | number | null }\n draftRevision: string | number | null\n dirtyRecords?: unknown[]\n restoreUnsavedDraft(value: unknown, base: string | number | null): void\n flushEdits(): void\n }\n\nexport type PageLifecycle = {\n active: boolean\n params: object\n resourceId?: string\n page?: string\n}\n\nfunction diff(after: any, before: any): any {\n var changed = false\n var changes: { [key: string]: any } = {}\n\n Object.getOwnPropertyNames(after).forEach(function (key) {\n let before_val = before[key]\n let after_val = after[key]\n\n if (!isEqual(before_val, after_val)) {\n changes[key] = after_val\n changed = true\n }\n })\n\n return changed && changes\n}\n\n/**\n * PageView is a base class for creating page elements with lifecycle management.\n * Subclasses can extend PageView to define custom behavior and handle page lifecycle events.\n */\nexport class PageView extends LitElement {\n /**\n * Closing this page instance (an MDI tab) destroys it, so its unsaved edits are asked about first: the bar\n * removes the tab only on yes. A page switch only caches the instance and asks nothing.\n */\n async confirmClose(): Promise<boolean> {\n if (!this.addressDirty && !this.listDirty(false)) return true\n if (!(await this.canDeactivate())) return false\n this.discardAddressEdits()\n return true\n }\n private listDrafts = new Map<ListDraftSource, { query: string; controller: UnsavedDraftController<unknown> }>()\n\n private listDraft = (event: Event) => {\n const { source, query, action, pathname } = (\n event as CustomEvent<{\n source: ListDraftSource\n query: string\n pathname: string\n action: 'edit' | 'loaded' | 'leave' | 'discard'\n }>\n ).detail\n let binding = this.listDrafts.get(source)\n if (binding && binding.query !== query) {\n binding.controller.dispose()\n this.listDrafts.delete(source)\n binding = undefined\n }\n if (!binding && action !== 'leave' && action !== 'discard') {\n const state = store.getState() as { auth?: { user?: { id?: string } } }\n const user = state.auth?.user?.id\n const target = JSON.stringify([\n source.id || Array.from(this.renderRoot.querySelectorAll('ox-grist')).indexOf(source),\n query\n ])\n const controller = new UnsavedDraftController(source, {\n address: () => {\n const current = store.getState() as { auth?: { user?: { id?: string } } }\n return user && current.auth?.user?.id === user\n ? { user, domain: getPathInfo(pathname).domain || '', screen: pathname, target }\n : undefined\n },\n snapshot: () => {\n source.flushEdits()\n return source.draftSnapshot\n },\n savedRevision: () => source.draftRevision,\n restore: (value, base) => source.restoreUnsavedDraft(value, base)\n })\n binding = { query, controller }\n this.listDrafts.set(source, binding)\n }\n if (!binding) return\n if (action === 'discard') void binding.controller.settled()\n if (action === 'edit') void binding.controller.flush()\n if (action === 'loaded' && this.active && pathname === location.pathname) void binding.controller.offer()\n if (action === 'leave') {\n if (source.draftSnapshot) void binding.controller.flush()\n binding.controller.dispose()\n this.listDrafts.delete(source)\n }\n }\n\n /** True only for the current edit's completed IndexedDB write; an unwritten edit still needs protection. */\n get addressEditsKept(): boolean {\n this.flushAddressEdits()\n const grids = Array.from(this.renderRoot.querySelectorAll('ox-grist')) as ListDraftSource[]\n const dirty = [...new Set([...grids, ...this.listDrafts.keys()])].filter(source => source.dirtyRecords?.length)\n return dirty.length > 0 && dirty.every(source => this.listDrafts.get(source)?.controller.keeps)\n }\n\n private queryChanged = () => {\n if (this.active) this.updateContext()\n }\n\n connectedCallback() {\n super.connectedCallback()\n this.addEventListener('query-state-change', this.queryChanged)\n this.addEventListener('list-draft', this.listDraft)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('query-state-change', this.queryChanged)\n for (const [source, binding] of this.listDrafts) {\n if (source.draftSnapshot) void binding.controller.flush()\n binding.controller.dispose()\n }\n this.listDrafts.clear()\n this.removeEventListener('list-draft', this.listDraft)\n }\n /**\n * Whether `change` would discard unsaved edits, so the shell asks first (address-state-ownership §6.4,\n * 2026-10-19). `stays`: this page instance stays on screen. Grid edits stay in the cached page when another\n * page is shown; they are lost when this page gets new route arguments (every grid reloads) or a new list\n * query (the url-bound grids reload). A page's own unsaved state (`addressDirty`) is asked about whenever the\n * page is left or changed, as `canDeactivate` was before 2026-10-17.\n */\n discards(change: { route: boolean; query: boolean }, stays: boolean): boolean {\n if (!change.route && !change.query) return false\n if (this.addressDirty) return true\n return stays && this.listDirty(!change.route)\n }\n\n /** Whether the page's grids hold unsaved edits - only those bound to the address when `urlBound`. */\n protected listDirty(urlBound: boolean): boolean {\n return this.listGrids(urlBound).some(grid => grid.dirtyRecords?.length)\n }\n\n private listGrids(urlBound: boolean) {\n this.flushAddressEdits()\n const grids = Array.from(this.renderRoot.querySelectorAll('ox-grist')) as (HTMLElement & {\n dirtyRecords?: unknown[]\n urlParamsSensitive?: boolean\n reset(): void\n })[]\n return urlBound ? grids.filter(grid => grid.urlParamsSensitive) : grids\n }\n\n /** The question asked when `discards` says so: asked before an app request, or at a traversal's arrival. */\n get deactivation(): boolean | Parameters<typeof OxPrompt.open>[0] {\n if (!this.addressDirty && !this.listDirty(false)) return true\n return {\n type: 'warning',\n title: i18next.t('title.unsaved-draft', { defaultValue: 'there is an unsaved edit' }),\n text: this.addressDiscardText,\n confirmButton: { text: i18next.t('button.discard', { defaultValue: 'discard' }), destructive: true },\n cancelButton: { text: i18next.t('button.cancel', { defaultValue: 'cancel' }) }\n }\n }\n\n async canDeactivate(signal?: AbortSignal): Promise<boolean> {\n const decision = this.deactivation\n return typeof decision === 'boolean' ? decision : OxPrompt.open({ ...decision, signal })\n }\n\n protected get addressDiscardText(): string {\n return i18next.t('text.unsaved-may-be-lost', { defaultValue: 'anything not saved is lost.' })\n }\n\n /** The page's own unsaved state, apart from its grids (a modeller's model). */\n get addressDirty(): boolean {\n return false\n }\n\n protected flushAddressEdits(): void {\n const grids = Array.from(this.renderRoot.querySelectorAll('ox-grist')) as (HTMLElement & {\n flushEdits?: () => void\n })[]\n grids.forEach(grid => grid.flushEdits?.())\n }\n\n /**\n * Runs only once a change `discards` approved has committed, never while a question is pending or canceled.\n * A list query change resets only the url-bound grids: the others keep their edits.\n */\n discardAddressEdits(change?: { route: boolean }): void {\n this.listGrids(!!change && !change.route)\n .filter(grid => grid.dirtyRecords?.length)\n .forEach(grid => grid.reset())\n }\n\n /**\n * Determines whether the page should update. This method is called whenever there are\n * changes to the page's properties.\n * @param changes - A map of changed property values.\n * @returns True if the page should update, or false otherwise.\n */\n shouldUpdate(changes: PropertyValues<this>) {\n var active = String(this.active) == 'true'\n var { active: oldActive = false } = this._oldLifecycleInfo$ || {}\n\n /*\n * page lifecycle\n * case 1. page가 새로 activate 되었다.\n * case 2. page가 active 상태에서 lifecycle 정보가 바뀌었다.\n **/\n if (active) {\n this.pageUpdate({\n active\n })\n } else if (oldActive) {\n this.pageUpdate({\n active\n })\n }\n\n return active\n }\n\n /**\n * Indicates whether the page is currently active.\n */\n @property({ type: Boolean }) active: boolean = false\n\n /**\n * Stores information about the page's lifecycle.\n */\n @property({ type: Object }) lifecycle!: PageLifecycle\n\n /**\n * The context path for the page.\n */\n @property({ type: String, attribute: 'context-path' }) contextPath?: string\n\n _oldLifecycleInfo$: any\n\n /* lifecycle */\n\n /** The path this page is shown at: its route arguments are what that path's claims leave of the address. */\n private shownAt?: string\n\n private unclaimed(params: Record<string, any>): Record<string, string> {\n const url = new URL(this.shownAt!, location.origin)\n url.search = new URLSearchParams(params).toString()\n return addressController().params({ ...addressController().entry, url })\n }\n\n /**\n * Handles page updates and lifecycle events. Subclasses can override this method\n * to implement custom logic for initializing, updating, and disposing of the page.\n * @param changes - A map of changed properties.\n * @param force - If true, forces an update of the page.\n */\n async pageUpdate(changes: any = {}, force: boolean = false) {\n var before = this._oldLifecycleInfo$ || {}\n\n var after = {\n ...before,\n ...this.lifecycle,\n contextPath: this.contextPath,\n ...changes\n }\n\n /*\n * Route arguments are compared under today's claims. A grid or tab claims its keys only after the page is drawn,\n * so the page opened by an address with `filters` stored them as an argument; on the next route dispatch they\n * are none. Counting that as a change made a screen read its list again and drop the edits it held.\n */\n if (after.active) this.shownAt = location.pathname\n if (this.shownAt) {\n if (before.params) before = { ...before, params: this.unclaimed(before.params) }\n if (after.params) after = { ...after, params: this.unclaimed(after.params) }\n }\n\n if (!('initialized' in changes) && after.active && !before.initialized) {\n after.initialized = true\n }\n\n if (force) {\n after.updated = Date.now()\n }\n\n var changed = diff(after, before)\n if (!changed) {\n return\n }\n\n this._oldLifecycleInfo$ = after\n\n /* page의 이미 초기화된 상태에서 contextPath가 바뀐다면, 무조건 page가 리셋되어야 한다. */\n if (before.initialized && changed.contextPath) {\n await this.pageReset()\n return\n }\n\n if (changed.initialized) {\n await this.pageInitialized(after)\n }\n\n if ('initialized' in changed) {\n if (changed.initialized) {\n /*\n * 방금 초기화된 경우라면, 엘리먼트들이 만들어지지 않았을 가능성이 있으므로,\n * 다음 animationFrame에서 pageUpdated 콜백을 호출한다.\n */\n requestAnimationFrame(async () => {\n await this.pageUpdated(changed, after, before)\n /* active page인 경우에는, page Context 갱신도 필요할 것이다. */\n after.active && this.updateContext()\n })\n } else {\n await this.pageDisposed(after)\n }\n } else {\n await this.pageUpdated(changed, after, before)\n /* active page인 경우에는, page Context 갱신도 필요할 것이다. */\n after.active && this.updateContext()\n }\n }\n\n /**\n * Resets the page. Subclasses can override this method to perform custom reset logic.\n */\n async pageReset() {\n var { initialized } = this._oldLifecycleInfo$ || {}\n\n if (initialized) {\n await this.pageDispose()\n await this.pageUpdate({}, true)\n }\n }\n\n /**\n * Disposes of the page. Subclasses can override this method to perform custom disposal logic.\n */\n async pageDispose() {\n await this.pageUpdate({\n initialized: false\n })\n }\n\n /**\n * Initializes the page. Subclasses can override this method to perform custom initialization logic.\n * @param pageInfo - Information about the page's state.\n */\n pageInitialized(pageInfo: any) {}\n\n /**\n * Handles page updates and changes in properties.\n * Subclasses can override this method to implement custom update logic.\n * @param changes - A map of changed properties.\n * @param after - The current state of the page.\n * @param before - The previous state of the page.\n */\n pageUpdated(changes: any, after: any, before: any) {}\n\n /**\n * Handles the disposal of the page. Subclasses can override this method\n * to implement custom disposal logic.\n * @param pageInfo - Information about the page's state.\n */\n pageDisposed(pageInfo: any) {}\n\n /* context */\n updateContext(override?: any) {\n store.dispatch({\n type: UPDATE_CONTEXT,\n context: override ? { ...this.context, ...override } : this.context\n })\n }\n\n /**\n * Updates the context of the page. Subclasses can override the `context` getter\n * to provide specific context information for the page. The context will be updated\n * using the `updateContext` method inherited from PageView.\n * @param override - An optional object with context properties to override.\n */\n get context() {\n return {}\n }\n}\n\n/** Whether the page shown at `before` stays on screen at `after`: the same path, or a path naming the same page. */\nexport function staysOn(page: PageView, after: URL, before: URL): boolean {\n if (after.origin === before.origin && after.pathname === before.pathname) return true\n const name = decodeURIComponent(getPathInfo(after.pathname).path || '').match(/^\\/([^\\/]+)/)?.[1] || ''\n return !!name && page.getAttribute('data-page') === name\n}\n"]}
|
|
1
|
+
{"version":3,"file":"page-view.js","sourceRoot":"","sources":["../../../../src/app/pages/page-view.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, PropertyValues, type ReactiveControllerHost } from 'lit'\nimport { property } from 'lit/decorators.js'\nimport isEqual from 'lodash-es/isEqual.js'\nimport { i18next } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\n\nimport { UPDATE_CONTEXT } from '../../actions/const.js'\nimport { store } from '../../store.js'\nimport { getPathInfo } from '@operato/utils'\nimport { addressController } from '@operato/utils/address.js'\nimport { UnsavedDraftController } from '../../drafts/unsaved-draft-controller.js'\n\ntype ListDraftSource = HTMLElement &\n ReactiveControllerHost & {\n draftSnapshot?: { value: unknown; base: string | number | null }\n draftRevision: string | number | null\n dirtyRecords?: unknown[]\n restoreUnsavedDraft(value: unknown, base: string | number | null): void\n flushEdits(): void\n }\n\ntype ReadGrid = HTMLElement & { reads?: number; fetch(): Promise<void> }\n\nexport type PageLifecycle = {\n active: boolean\n params: object\n resourceId?: string\n page?: string\n}\n\nfunction diff(after: any, before: any): any {\n var changed = false\n var changes: { [key: string]: any } = {}\n\n Object.getOwnPropertyNames(after).forEach(function (key) {\n let before_val = before[key]\n let after_val = after[key]\n\n if (!isEqual(before_val, after_val)) {\n changes[key] = after_val\n changed = true\n }\n })\n\n return changed && changes\n}\n\n/**\n * PageView is a base class for creating page elements with lifecycle management.\n * Subclasses can extend PageView to define custom behavior and handle page lifecycle events.\n */\nexport class PageView extends LitElement {\n /**\n * Closing this page instance (an MDI tab) destroys it, so its unsaved edits are asked about first: the bar\n * removes the tab only on yes. A page switch only caches the instance and asks nothing.\n */\n async confirmClose(): Promise<boolean> {\n if (!this.addressDirty && !this.listDirty(false)) return true\n if (!(await this.canDeactivate())) return false\n this.discardAddressEdits()\n return true\n }\n private listDrafts = new Map<ListDraftSource, { query: string; controller: UnsavedDraftController<unknown> }>()\n\n private listDraft = (event: Event) => {\n const { source, query, action, pathname } = (\n event as CustomEvent<{\n source: ListDraftSource\n query: string\n pathname: string\n action: 'edit' | 'loaded' | 'leave' | 'discard'\n }>\n ).detail\n let binding = this.listDrafts.get(source)\n if (binding && binding.query !== query) {\n binding.controller.dispose()\n this.listDrafts.delete(source)\n binding = undefined\n }\n if (!binding && action !== 'leave' && action !== 'discard') {\n const state = store.getState() as { auth?: { user?: { id?: string } } }\n const user = state.auth?.user?.id\n const target = JSON.stringify([\n source.id || Array.from(this.renderRoot.querySelectorAll('ox-grist')).indexOf(source),\n query\n ])\n const controller = new UnsavedDraftController(source, {\n address: () => {\n const current = store.getState() as { auth?: { user?: { id?: string } } }\n return user && current.auth?.user?.id === user\n ? { user, domain: getPathInfo(pathname).domain || '', screen: pathname, target }\n : undefined\n },\n snapshot: () => {\n source.flushEdits()\n return source.draftSnapshot\n },\n savedRevision: () => source.draftRevision,\n restore: (value, base) => source.restoreUnsavedDraft(value, base)\n })\n binding = { query, controller }\n this.listDrafts.set(source, binding)\n }\n if (!binding) return\n if (action === 'discard') void binding.controller.settled()\n if (action === 'edit') void binding.controller.flush()\n if (action === 'loaded' && this.active && pathname === location.pathname) void binding.controller.offer()\n if (action === 'leave') {\n if (source.draftSnapshot) void binding.controller.flush()\n binding.controller.dispose()\n this.listDrafts.delete(source)\n }\n }\n\n /** True only for the current edit's completed IndexedDB write; an unwritten edit still needs protection. */\n get addressEditsKept(): boolean {\n this.flushAddressEdits()\n const grids = Array.from(this.renderRoot.querySelectorAll('ox-grist')) as ListDraftSource[]\n const dirty = [...new Set([...grids, ...this.listDrafts.keys()])].filter(source => source.dirtyRecords?.length)\n return dirty.length > 0 && dirty.every(source => this.listDrafts.get(source)?.controller.keeps)\n }\n\n private queryChanged = () => {\n if (this.active) this.updateContext()\n }\n\n connectedCallback() {\n super.connectedCallback()\n this.addEventListener('query-state-change', this.queryChanged)\n this.addEventListener('list-draft', this.listDraft)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('query-state-change', this.queryChanged)\n for (const [source, binding] of this.listDrafts) {\n if (source.draftSnapshot) void binding.controller.flush()\n binding.controller.dispose()\n }\n this.listDrafts.clear()\n this.removeEventListener('list-draft', this.listDraft)\n }\n /**\n * Whether `change` would discard unsaved edits, so the shell asks first (address-state-ownership §6.4,\n * 2026-10-19). `stays`: this page instance stays on screen. Grid edits stay in the cached page when another\n * page is shown; they are lost when this page gets new route arguments (every grid reloads) or a new list\n * query (the url-bound grids reload). A page's own unsaved state (`addressDirty`) is asked about whenever the\n * page is left or changed, as `canDeactivate` was before 2026-10-17.\n */\n discards(change: { route: boolean; query: boolean }, stays: boolean): boolean {\n if (!change.route && !change.query) return false\n if (this.addressDirty) return true\n return stays && this.listDirty(!change.route)\n }\n\n /** Whether the page's grids hold unsaved edits - only those bound to the address when `urlBound`. */\n protected listDirty(urlBound: boolean): boolean {\n return this.listGrids(urlBound).some(grid => grid.dirtyRecords?.length)\n }\n\n private listGrids(urlBound: boolean) {\n this.flushAddressEdits()\n const grids = Array.from(this.renderRoot.querySelectorAll('ox-grist')) as (HTMLElement & {\n dirtyRecords?: unknown[]\n urlParamsSensitive?: boolean\n reset(): void\n })[]\n return urlBound ? grids.filter(grid => grid.urlParamsSensitive) : grids\n }\n\n /** The question asked when `discards` says so: asked before an app request, or at a traversal's arrival. */\n get deactivation(): boolean | Parameters<typeof OxPrompt.open>[0] {\n if (!this.addressDirty && !this.listDirty(false)) return true\n return {\n type: 'warning',\n title: i18next.t('title.unsaved-draft', { defaultValue: 'there is an unsaved edit' }),\n text: this.addressDiscardText,\n confirmButton: { text: i18next.t('button.discard', { defaultValue: 'discard' }), destructive: true },\n cancelButton: { text: i18next.t('button.cancel', { defaultValue: 'cancel' }) }\n }\n }\n\n async canDeactivate(signal?: AbortSignal): Promise<boolean> {\n const decision = this.deactivation\n return typeof decision === 'boolean' ? decision : OxPrompt.open({ ...decision, signal })\n }\n\n protected get addressDiscardText(): string {\n return i18next.t('text.unsaved-may-be-lost', { defaultValue: 'anything not saved is lost.' })\n }\n\n /** The page's own unsaved state, apart from its grids (a modeller's model). */\n get addressDirty(): boolean {\n return false\n }\n\n protected flushAddressEdits(): void {\n const grids = Array.from(this.renderRoot.querySelectorAll('ox-grist')) as (HTMLElement & {\n flushEdits?: () => void\n })[]\n grids.forEach(grid => grid.flushEdits?.())\n }\n\n /**\n * Runs only once a change `discards` approved has committed, never while a question is pending or canceled.\n * A list query change resets only the url-bound grids: the others keep their edits.\n */\n discardAddressEdits(change?: { route: boolean }): void {\n this.listGrids(!!change && !change.route)\n .filter(grid => grid.dirtyRecords?.length)\n .forEach(grid => grid.reset())\n }\n\n /**\n * Determines whether the page should update. This method is called whenever there are\n * changes to the page's properties.\n * @param changes - A map of changed property values.\n * @returns True if the page should update, or false otherwise.\n */\n shouldUpdate(changes: PropertyValues<this>) {\n var active = String(this.active) == 'true'\n var { active: oldActive = false } = this._oldLifecycleInfo$ || {}\n\n /*\n * page lifecycle\n * case 1. page가 새로 activate 되었다.\n * case 2. page가 active 상태에서 lifecycle 정보가 바뀌었다.\n **/\n if (active) {\n const unread = this.unread\n this.unread = undefined\n const played = this.pageUpdate({\n active\n })\n if (unread) void played.then(() => this.readAgain(unread))\n } else if (oldActive) {\n this.pageUpdate({\n active\n })\n }\n\n return active\n }\n\n /**\n * Indicates whether the page is currently active.\n */\n @property({ type: Boolean }) active: boolean = false\n\n /**\n * Stores information about the page's lifecycle.\n */\n @property({ type: Object }) lifecycle!: PageLifecycle\n\n /**\n * The context path for the page.\n */\n @property({ type: String, attribute: 'context-path' }) contextPath?: string\n\n _oldLifecycleInfo$: any\n\n /* lifecycle */\n\n /** The path this page is shown at: its route arguments are what that path's claims leave of the address. */\n private shownAt?: string\n\n private unclaimed(params: Record<string, any>): Record<string, string> {\n const url = new URL(this.shownAt!, location.origin)\n url.search = new URLSearchParams(params).toString()\n return addressController().params({ ...addressController().entry, url })\n }\n\n /**\n * Handles page updates and lifecycle events. Subclasses can override this method\n * to implement custom logic for initializing, updating, and disposing of the page.\n * @param changes - A map of changed properties.\n * @param force - If true, forces an update of the page.\n */\n async pageUpdate(changes: any = {}, force: boolean = false) {\n var before = this._oldLifecycleInfo$ || {}\n\n var after = {\n ...before,\n ...this.lifecycle,\n contextPath: this.contextPath,\n ...changes\n }\n\n /*\n * Route arguments are compared under today's claims. A grid or tab claims its keys only after the page is drawn,\n * so the page opened by an address with `filters` stored them as an argument; on the next route dispatch they\n * are none. Counting that as a change made a screen read its list again and drop the edits it held.\n */\n if (after.active) this.shownAt = location.pathname\n if (this.shownAt) {\n if (before.params) before = { ...before, params: this.unclaimed(before.params) }\n if (after.params) after = { ...after, params: this.unclaimed(after.params) }\n }\n\n if (!('initialized' in changes) && after.active && !before.initialized) {\n after.initialized = true\n }\n\n if (force) {\n after.updated = Date.now()\n }\n\n var changed = diff(after, before)\n if (!changed) {\n return\n }\n\n this._oldLifecycleInfo$ = after\n\n /* page의 이미 초기화된 상태에서 contextPath가 바뀐다면, 무조건 page가 리셋되어야 한다. */\n if (before.initialized && changed.contextPath) {\n await this.pageReset()\n return\n }\n\n if (changed.initialized) {\n await this.pageInitialized(after)\n }\n\n if ('initialized' in changed) {\n if (changed.initialized) {\n /*\n * 방금 초기화된 경우라면, 엘리먼트들이 만들어지지 않았을 가능성이 있으므로,\n * 다음 animationFrame에서 pageUpdated 콜백을 호출한다.\n */\n await new Promise(resolve => requestAnimationFrame(resolve))\n await this.pageUpdated(changed, after, before)\n /* active page인 경우에는, page Context 갱신도 필요할 것이다. */\n after.active && this.updateContext()\n } else {\n await this.pageDisposed(after)\n }\n } else {\n await this.pageUpdated(changed, after, before)\n /* active page인 경우에는, page Context 갱신도 필요할 것이다. */\n after.active && this.updateContext()\n }\n }\n\n /**\n * Reads the page again, as if it had just been opened at the address it shows (ADR-0174). The tab menu's\n * refresh calls this; a page draws no refresh control of its own.\n *\n * Unsaved edits follow the rule of a route change on the page on screen: grid edits a draft keeps are offered\n * back once the grid has read again; anything else is asked about first, and on \"keep\" nothing happens.\n *\n * The lifecycle is played from nothing, so `pageInitialized` runs and `pageUpdated` sees `initialized`,\n * `active`, `params` and `resourceId` all changed. A page not on screen plays it when it is next shown.\n * A grid that had been read and that the page did not read again in that play reads again itself.\n *\n * A refresh nobody asked for (a record changed elsewhere) passes `ask: false`: it shows nothing the user did not\n * ask for, so with unsaved edits of any kind - even ones a draft keeps - it reads nothing.\n *\n * @returns false when the user kept the unsaved edits, or when `ask: false` met unsaved edits.\n */\n async refresh({ ask = true }: { ask?: boolean } = {}): Promise<boolean> {\n if (!ask && (this.addressDirty || this.listDirty(false))) return false\n if (this.addressDirty || (this.listDirty(false) && !this.addressEditsKept)) {\n if (!(await this.canDeactivate())) return false\n this.discardAddressEdits()\n }\n\n const grids = (Array.from(this.renderRoot.querySelectorAll('ox-grist')) as ReadGrid[]).map(grid => ({\n grid,\n reads: grid.reads || 0\n }))\n\n await this.pageDispose()\n this._oldLifecycleInfo$ = undefined\n if (!this.active) {\n this.unread = grids\n return true\n }\n\n await this.pageUpdate()\n await this.readAgain(grids)\n return true\n }\n\n /** The grids of a refresh asked for while the page was not on screen: read once it is shown. */\n private unread?: { grid: ReadGrid; reads: number }[]\n\n private async readAgain(grids: { grid: ReadGrid; reads: number }[]) {\n await Promise.all(grids.filter(({ grid, reads }) => reads > 0 && grid.reads === reads).map(({ grid }) => grid.fetch()))\n }\n\n /**\n * Resets the page. Subclasses can override this method to perform custom reset logic.\n */\n async pageReset() {\n var { initialized } = this._oldLifecycleInfo$ || {}\n\n if (initialized) {\n await this.pageDispose()\n await this.pageUpdate({}, true)\n }\n }\n\n /**\n * Disposes of the page. Subclasses can override this method to perform custom disposal logic.\n */\n async pageDispose() {\n await this.pageUpdate({\n initialized: false\n })\n }\n\n /**\n * Initializes the page. Subclasses can override this method to perform custom initialization logic.\n * @param pageInfo - Information about the page's state.\n */\n pageInitialized(pageInfo: any) {}\n\n /**\n * Handles page updates and changes in properties.\n * Subclasses can override this method to implement custom update logic.\n * @param changes - A map of changed properties.\n * @param after - The current state of the page.\n * @param before - The previous state of the page.\n */\n pageUpdated(changes: any, after: any, before: any) {}\n\n /**\n * Handles the disposal of the page. Subclasses can override this method\n * to implement custom disposal logic.\n * @param pageInfo - Information about the page's state.\n */\n pageDisposed(pageInfo: any) {}\n\n /* context */\n updateContext(override?: any) {\n store.dispatch({\n type: UPDATE_CONTEXT,\n context: override ? { ...this.context, ...override } : this.context\n })\n }\n\n /**\n * Updates the context of the page. Subclasses can override the `context` getter\n * to provide specific context information for the page. The context will be updated\n * using the `updateContext` method inherited from PageView.\n * @param override - An optional object with context properties to override.\n */\n get context() {\n return {}\n }\n}\n\n/** Whether the page shown at `before` stays on screen at `after`: the same path, or a path naming the same page. */\nexport function staysOn(page: PageView, after: URL, before: URL): boolean {\n if (after.origin === before.origin && after.pathname === before.pathname) return true\n const name = decodeURIComponent(getPathInfo(after.pathname).path || '').match(/^\\/([^\\/]+)/)?.[1] || ''\n return !!name && page.getAttribute('data-page') === name\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { expect, fixture } from '@open-wc/testing';
|
|
2
|
+
import { html } from 'lit';
|
|
3
|
+
import { PageView } from '../src/app/pages/page-view.js';
|
|
4
|
+
/**
|
|
5
|
+
* A page refresh (ADR-0174) reads the page again as if it had just been opened at its address: every way a page
|
|
6
|
+
* reads - once at initialization, on activation, on new route arguments - runs again, and so does a grid the page
|
|
7
|
+
* left to read itself. Unsaved edits follow the rule of a route change on the page on screen.
|
|
8
|
+
*/
|
|
9
|
+
class FakeGrist extends HTMLElement {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
this.reads = 0;
|
|
13
|
+
this.dirtyRecords = [];
|
|
14
|
+
}
|
|
15
|
+
async fetch() {
|
|
16
|
+
this.reads++;
|
|
17
|
+
}
|
|
18
|
+
reset() {
|
|
19
|
+
this.dirtyRecords = [];
|
|
20
|
+
}
|
|
21
|
+
flushEdits() { }
|
|
22
|
+
}
|
|
23
|
+
customElements.define('ox-grist', FakeGrist);
|
|
24
|
+
const frame = () => new Promise(resolve => requestAnimationFrame(resolve));
|
|
25
|
+
class ReadingPage extends PageView {
|
|
26
|
+
constructor() {
|
|
27
|
+
super(...arguments);
|
|
28
|
+
this.initialized = 0;
|
|
29
|
+
this.seen = [];
|
|
30
|
+
/** Reads the first grid on new route arguments only, as Office's ledger does. */
|
|
31
|
+
this.readsOnParams = true;
|
|
32
|
+
this.answer = true;
|
|
33
|
+
this.asked = 0;
|
|
34
|
+
}
|
|
35
|
+
render() {
|
|
36
|
+
return html `<ox-grist id="own"></ox-grist><ox-grist id="self"></ox-grist>`;
|
|
37
|
+
}
|
|
38
|
+
pageInitialized() {
|
|
39
|
+
this.initialized++;
|
|
40
|
+
}
|
|
41
|
+
pageUpdated(changes) {
|
|
42
|
+
this.seen.push(Object.keys(changes).sort());
|
|
43
|
+
if (this.readsOnParams && changes.params)
|
|
44
|
+
void this.grid('own').fetch();
|
|
45
|
+
}
|
|
46
|
+
async canDeactivate() {
|
|
47
|
+
this.asked++;
|
|
48
|
+
return this.answer;
|
|
49
|
+
}
|
|
50
|
+
grid(id) {
|
|
51
|
+
return this.renderRoot.querySelector(`#${id}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
customElements.define('refresh-reading-page', ReadingPage);
|
|
55
|
+
const open = async () => {
|
|
56
|
+
const page = await fixture(html `<refresh-reading-page></refresh-reading-page>`);
|
|
57
|
+
page.lifecycle = { active: true, params: { year: '2026' } };
|
|
58
|
+
page.active = true;
|
|
59
|
+
await page.updateComplete;
|
|
60
|
+
await frame();
|
|
61
|
+
await frame();
|
|
62
|
+
return page;
|
|
63
|
+
};
|
|
64
|
+
describe('page refresh', () => {
|
|
65
|
+
it('opening the page again at the same address reads nothing: the reason a refresh is needed', async () => {
|
|
66
|
+
const page = await open();
|
|
67
|
+
const seen = page.seen.length;
|
|
68
|
+
page.lifecycle = { active: true, params: { year: '2026' } };
|
|
69
|
+
await page.updateComplete;
|
|
70
|
+
await frame();
|
|
71
|
+
expect(page.seen.length).to.equal(seen);
|
|
72
|
+
});
|
|
73
|
+
it('plays the lifecycle from nothing: initialization and every change a page reads on', async () => {
|
|
74
|
+
const page = await open();
|
|
75
|
+
expect(await page.refresh()).to.equal(true);
|
|
76
|
+
expect(page.initialized).to.equal(2);
|
|
77
|
+
expect(page.seen[page.seen.length - 1]).to.include.members(['initialized', 'active', 'params']);
|
|
78
|
+
});
|
|
79
|
+
it('a grid the page read again is not read twice; a grid that reads itself is read again', async () => {
|
|
80
|
+
const page = await open();
|
|
81
|
+
page.grid('self').reads = 1;
|
|
82
|
+
const own = page.grid('own').reads;
|
|
83
|
+
await page.refresh();
|
|
84
|
+
expect(page.grid('own').reads).to.equal(own + 1);
|
|
85
|
+
expect(page.grid('self').reads).to.equal(2);
|
|
86
|
+
});
|
|
87
|
+
it('a grid never read is left alone: it may be waiting for a selection', async () => {
|
|
88
|
+
const page = await open();
|
|
89
|
+
await page.refresh();
|
|
90
|
+
expect(page.grid('self').reads).to.equal(0);
|
|
91
|
+
});
|
|
92
|
+
it('unsaved grid edits no draft keeps are asked about; on "keep" nothing is read', async () => {
|
|
93
|
+
const page = await open();
|
|
94
|
+
page.grid('self').dirtyRecords = [{}];
|
|
95
|
+
page.answer = false;
|
|
96
|
+
const initialized = page.initialized;
|
|
97
|
+
expect(await page.refresh()).to.equal(false);
|
|
98
|
+
expect(page.asked).to.equal(1);
|
|
99
|
+
expect(page.initialized).to.equal(initialized);
|
|
100
|
+
expect(page.grid('self').dirtyRecords.length).to.equal(1);
|
|
101
|
+
});
|
|
102
|
+
it('a refresh nobody asked for never asks: with unsaved edits it reads nothing', async () => {
|
|
103
|
+
const page = await open();
|
|
104
|
+
page.grid('self').dirtyRecords = [{}];
|
|
105
|
+
const initialized = page.initialized;
|
|
106
|
+
expect(await page.refresh({ ask: false })).to.equal(false);
|
|
107
|
+
expect(page.asked).to.equal(0);
|
|
108
|
+
expect(page.initialized).to.equal(initialized);
|
|
109
|
+
expect(page.grid('self').dirtyRecords.length).to.equal(1);
|
|
110
|
+
});
|
|
111
|
+
it('a refresh nobody asked for reads nothing even over edits a draft keeps: no restore offer follows', async () => {
|
|
112
|
+
const page = await open();
|
|
113
|
+
page.grid('self').dirtyRecords = [{}];
|
|
114
|
+
Object.defineProperty(page, 'addressEditsKept', { get: () => true });
|
|
115
|
+
const initialized = page.initialized;
|
|
116
|
+
expect(await page.refresh({ ask: false })).to.equal(false);
|
|
117
|
+
expect(page.initialized).to.equal(initialized);
|
|
118
|
+
expect(await page.refresh()).to.equal(true);
|
|
119
|
+
expect(page.asked).to.equal(0);
|
|
120
|
+
expect(page.initialized).to.equal(initialized + 1);
|
|
121
|
+
});
|
|
122
|
+
it('without unsaved edits it reads like any refresh', async () => {
|
|
123
|
+
const page = await open();
|
|
124
|
+
expect(await page.refresh({ ask: false })).to.equal(true);
|
|
125
|
+
expect(page.initialized).to.equal(2);
|
|
126
|
+
});
|
|
127
|
+
it('on "discard" the edits are reset and the page is read', async () => {
|
|
128
|
+
const page = await open();
|
|
129
|
+
page.grid('self').dirtyRecords = [{}];
|
|
130
|
+
expect(await page.refresh()).to.equal(true);
|
|
131
|
+
expect(page.grid('self').dirtyRecords.length).to.equal(0);
|
|
132
|
+
expect(page.initialized).to.equal(2);
|
|
133
|
+
});
|
|
134
|
+
it('a page not on screen is read when it is next shown', async () => {
|
|
135
|
+
const page = await open();
|
|
136
|
+
page.grid('self').reads = 1;
|
|
137
|
+
page.active = false;
|
|
138
|
+
await page.updateComplete;
|
|
139
|
+
await page.refresh();
|
|
140
|
+
expect(page.initialized).to.equal(1);
|
|
141
|
+
page.active = true;
|
|
142
|
+
await page.updateComplete;
|
|
143
|
+
await frame();
|
|
144
|
+
await frame();
|
|
145
|
+
await frame();
|
|
146
|
+
expect(page.initialized).to.equal(2);
|
|
147
|
+
expect(page.seen[page.seen.length - 1]).to.include.members(['initialized', 'active', 'params']);
|
|
148
|
+
expect(page.grid('self').reads).to.equal(2);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
//# sourceMappingURL=page-refresh.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-refresh.test.js","sourceRoot":"","sources":["../../test/page-refresh.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport { html } from 'lit'\n\nimport { PageView } from '../src/app/pages/page-view.js'\n\n/**\n * A page refresh (ADR-0174) reads the page again as if it had just been opened at its address: every way a page\n * reads - once at initialization, on activation, on new route arguments - runs again, and so does a grid the page\n * left to read itself. Unsaved edits follow the rule of a route change on the page on screen.\n */\n\nclass FakeGrist extends HTMLElement {\n reads = 0\n dirtyRecords: unknown[] = []\n async fetch() {\n this.reads++\n }\n reset() {\n this.dirtyRecords = []\n }\n flushEdits() {}\n}\ncustomElements.define('ox-grist', FakeGrist)\n\nconst frame = () => new Promise(resolve => requestAnimationFrame(resolve))\n\nclass ReadingPage extends PageView {\n initialized = 0\n seen: string[][] = []\n /** Reads the first grid on new route arguments only, as Office's ledger does. */\n readsOnParams = true\n answer = true\n asked = 0\n\n render() {\n return html`<ox-grist id=\"own\"></ox-grist><ox-grist id=\"self\"></ox-grist>`\n }\n\n pageInitialized() {\n this.initialized++\n }\n\n pageUpdated(changes: any) {\n this.seen.push(Object.keys(changes).sort())\n if (this.readsOnParams && changes.params) void this.grid('own').fetch()\n }\n\n async canDeactivate() {\n this.asked++\n return this.answer\n }\n\n grid(id: string) {\n return this.renderRoot.querySelector(`#${id}`) as FakeGrist\n }\n}\ncustomElements.define('refresh-reading-page', ReadingPage)\n\nconst open = async () => {\n const page = await fixture<ReadingPage>(html`<refresh-reading-page></refresh-reading-page>`)\n page.lifecycle = { active: true, params: { year: '2026' } }\n page.active = true\n await page.updateComplete\n await frame()\n await frame()\n return page\n}\n\ndescribe('page refresh', () => {\n it('opening the page again at the same address reads nothing: the reason a refresh is needed', async () => {\n const page = await open()\n const seen = page.seen.length\n page.lifecycle = { active: true, params: { year: '2026' } }\n await page.updateComplete\n await frame()\n expect(page.seen.length).to.equal(seen)\n })\n\n it('plays the lifecycle from nothing: initialization and every change a page reads on', async () => {\n const page = await open()\n expect(await page.refresh()).to.equal(true)\n expect(page.initialized).to.equal(2)\n expect(page.seen[page.seen.length - 1]).to.include.members(['initialized', 'active', 'params'])\n })\n\n it('a grid the page read again is not read twice; a grid that reads itself is read again', async () => {\n const page = await open()\n page.grid('self').reads = 1\n const own = page.grid('own').reads\n await page.refresh()\n expect(page.grid('own').reads).to.equal(own + 1)\n expect(page.grid('self').reads).to.equal(2)\n })\n\n it('a grid never read is left alone: it may be waiting for a selection', async () => {\n const page = await open()\n await page.refresh()\n expect(page.grid('self').reads).to.equal(0)\n })\n\n it('unsaved grid edits no draft keeps are asked about; on \"keep\" nothing is read', async () => {\n const page = await open()\n page.grid('self').dirtyRecords = [{}]\n page.answer = false\n const initialized = page.initialized\n expect(await page.refresh()).to.equal(false)\n expect(page.asked).to.equal(1)\n expect(page.initialized).to.equal(initialized)\n expect(page.grid('self').dirtyRecords.length).to.equal(1)\n })\n\n it('a refresh nobody asked for never asks: with unsaved edits it reads nothing', async () => {\n const page = await open()\n page.grid('self').dirtyRecords = [{}]\n const initialized = page.initialized\n expect(await page.refresh({ ask: false })).to.equal(false)\n expect(page.asked).to.equal(0)\n expect(page.initialized).to.equal(initialized)\n expect(page.grid('self').dirtyRecords.length).to.equal(1)\n })\n\n it('a refresh nobody asked for reads nothing even over edits a draft keeps: no restore offer follows', async () => {\n const page = await open()\n page.grid('self').dirtyRecords = [{}]\n Object.defineProperty(page, 'addressEditsKept', { get: () => true })\n const initialized = page.initialized\n expect(await page.refresh({ ask: false })).to.equal(false)\n expect(page.initialized).to.equal(initialized)\n\n expect(await page.refresh()).to.equal(true)\n expect(page.asked).to.equal(0)\n expect(page.initialized).to.equal(initialized + 1)\n })\n\n it('without unsaved edits it reads like any refresh', async () => {\n const page = await open()\n expect(await page.refresh({ ask: false })).to.equal(true)\n expect(page.initialized).to.equal(2)\n })\n\n it('on \"discard\" the edits are reset and the page is read', async () => {\n const page = await open()\n page.grid('self').dirtyRecords = [{}]\n expect(await page.refresh()).to.equal(true)\n expect(page.grid('self').dirtyRecords.length).to.equal(0)\n expect(page.initialized).to.equal(2)\n })\n\n it('a page not on screen is read when it is next shown', async () => {\n const page = await open()\n page.grid('self').reads = 1\n page.active = false\n await page.updateComplete\n await page.refresh()\n expect(page.initialized).to.equal(1)\n\n page.active = true\n await page.updateComplete\n await frame()\n await frame()\n await frame()\n expect(page.initialized).to.equal(2)\n expect(page.seen[page.seen.length - 1]).to.include.members(['initialized', 'active', 'params'])\n expect(page.grid('self').reads).to.equal(2)\n })\n})\n"]}
|