@operato/shell 10.29.39 → 10.29.43

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@operato/shell",
3
3
  "description": "WebApplication architecturing shell following open-wc recommendations",
4
4
  "author": "heartyoh",
5
- "version": "10.29.39",
5
+ "version": "10.29.43",
6
6
  "type": "module",
7
7
  "main": "dist/src/index.js",
8
8
  "module": "dist/src/index.js",
@@ -60,11 +60,11 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "@material/web": "^2.0.0",
63
- "@operato/graphql": "^10.29.39",
63
+ "@operato/graphql": "^10.29.43",
64
64
  "@operato/i18n": "^10.29.27",
65
- "@operato/popup": "^10.29.39",
65
+ "@operato/popup": "^10.29.43",
66
66
  "@operato/styles": "^10.29.30",
67
- "@operato/utils": "^10.29.39",
67
+ "@operato/utils": "^10.29.43",
68
68
  "@webcomponents/scoped-custom-element-registry": "^0.0.9",
69
69
  "lit": "^3.1.2",
70
70
  "lodash-es": "^4.17.21",
@@ -103,5 +103,5 @@
103
103
  "prettier --write"
104
104
  ]
105
105
  },
106
- "gitHead": "f48742480ad2b9ce87f0b3b4c3ac5923f4866ad6"
106
+ "gitHead": "ccc06fed313fcc20a076a1cf7bc586fdacaba594"
107
107
  }
@@ -0,0 +1,94 @@
1
+ import { expect, fixture } from '@open-wc/testing'
2
+ import { html } from 'lit'
3
+
4
+ import { PageView, staysOn } from '../src/app/pages/page-view.js'
5
+
6
+ /**
7
+ * What leaving with unsaved edits asks (address-state-ownership §6.4, ruling 2026-10-19): only a change that would
8
+ * discard them. Grid edits stay in the cached page when another page is shown, as they did before 2026-10-17; a
9
+ * page's own unsaved state (a modeller's model) is asked about whenever it is left, as it was then.
10
+ */
11
+
12
+ class ListPage extends PageView {
13
+ render() {
14
+ return html`<ox-grist id="bound"></ox-grist><ox-grist id="detail"></ox-grist>`
15
+ }
16
+ }
17
+ customElements.define('leaving-list-page', ListPage)
18
+
19
+ class ModellerPage extends PageView {
20
+ unsaved = false
21
+ get addressDirty() {
22
+ return this.unsaved
23
+ }
24
+ }
25
+ customElements.define('leaving-modeller-page', ModellerPage)
26
+
27
+ const query = { route: false, query: true }
28
+ const route = { route: true, query: false }
29
+
30
+ const listPage = async (bound: number, detail: number) => {
31
+ const page = await fixture<ListPage>(html`<leaving-list-page active></leaving-list-page>`)
32
+ Object.assign(page.renderRoot.querySelector('#bound')!, { urlParamsSensitive: true, dirtyRecords: new Array(bound) })
33
+ Object.assign(page.renderRoot.querySelector('#detail')!, { dirtyRecords: new Array(detail) })
34
+ return page
35
+ }
36
+
37
+ describe('leaving with unsaved edits', () => {
38
+ it('showing another page asks nothing: the grid edits stay in the cached page', async () => {
39
+ const page = await listPage(1, 1)
40
+ expect(page.discards(route, false)).to.equal(false)
41
+ })
42
+
43
+ it('a new query for the url-bound grid that holds edits asks', async () => {
44
+ const page = await listPage(1, 0)
45
+ expect(page.discards(query, true)).to.equal(true)
46
+ })
47
+
48
+ it('a new query asks nothing when only a grid not bound to the address holds edits', async () => {
49
+ const page = await listPage(0, 1)
50
+ expect(page.discards(query, true)).to.equal(false)
51
+ })
52
+
53
+ it('new route arguments for the page on screen ask about every grid', async () => {
54
+ const page = await listPage(0, 1)
55
+ expect(page.discards(route, true)).to.equal(true)
56
+ })
57
+
58
+ it("a page's own unsaved state is asked about when the page is left", async () => {
59
+ const page = await fixture<ModellerPage>(html`<leaving-modeller-page></leaving-modeller-page>`)
60
+ page.unsaved = true
61
+ expect(page.discards(route, false)).to.equal(true)
62
+ })
63
+
64
+ it('closing the instance asks first when it holds unsaved edits, and answers with the user', async () => {
65
+ class Asking extends ListPage {
66
+ answer = false
67
+ asked = 0
68
+ async canDeactivate() {
69
+ this.asked++
70
+ return this.answer
71
+ }
72
+ }
73
+ customElements.define('leaving-asking-page', Asking)
74
+ const page = await fixture<Asking>(html`<leaving-asking-page active></leaving-asking-page>`)
75
+ Object.assign(page.renderRoot.querySelector('#detail')!, { dirtyRecords: [{}], reset() {} })
76
+ expect(await page.confirmClose()).to.equal(false)
77
+ page.answer = true
78
+ expect(await page.confirmClose()).to.equal(true)
79
+ expect(page.asked).to.equal(2)
80
+ })
81
+
82
+ it('closing a clean instance asks nothing', async () => {
83
+ const page = await listPage(0, 0)
84
+ expect(await page.confirmClose()).to.equal(true)
85
+ })
86
+
87
+ it('a change stays on the page when the path is the same, or names the same page', async () => {
88
+ const page = await fixture<ListPage>(html`<leaving-list-page data-page="ledger"></leaving-list-page>`)
89
+ const at = (path: string) => new URL(path, location.origin)
90
+ expect(staysOn(page, at('/ledger?filters=x'), at('/ledger'))).to.equal(true)
91
+ expect(staysOn(page, at('/ledger/2'), at('/ledger/1'))).to.equal(true)
92
+ expect(staysOn(page, at('/employees'), at('/ledger'))).to.equal(false)
93
+ })
94
+ })