@operato/data-grist 10.29.30 → 10.29.36

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/data-grist",
3
- "version": "10.29.30",
3
+ "version": "10.29.36",
4
4
  "description": "User interface for grid (desktop) and list (mobile)",
5
5
  "author": "heartyoh",
6
6
  "type": "module",
@@ -74,14 +74,14 @@
74
74
  "@material/web": "^2.0.0",
75
75
  "@operato/headroom": "^10.29.1",
76
76
  "@operato/i18n": "^10.29.27",
77
- "@operato/input": "^10.29.30",
78
- "@operato/list-state": "^10.29.27",
79
- "@operato/p13n": "^10.29.30",
80
- "@operato/popup": "^10.29.30",
77
+ "@operato/input": "^10.29.36",
78
+ "@operato/list-state": "^10.29.36",
79
+ "@operato/p13n": "^10.29.36",
80
+ "@operato/popup": "^10.29.36",
81
81
  "@operato/pull-to-refresh": "^10.29.23",
82
82
  "@operato/styles": "^10.29.30",
83
83
  "@operato/time-calculator": "^10.29.1",
84
- "@operato/utils": "^10.29.23",
84
+ "@operato/utils": "^10.29.36",
85
85
  "i18next": "^24.2.1",
86
86
  "json5": "^2.2.0",
87
87
  "lit": "^3.1.2",
@@ -120,5 +120,5 @@
120
120
  "prettier --write"
121
121
  ]
122
122
  },
123
- "gitHead": "44682a97746b11fb380a3a5ce7718f3428e4943c"
123
+ "gitHead": "90c8bc8593e5d68745aeddd45752fab83c50cc16"
124
124
  }
@@ -65,4 +65,35 @@ describe('필터 라벨', () => {
65
65
  expect(label!.querySelector('span')?.textContent?.trim()).to.equal(name)
66
66
  }
67
67
  })
68
+
69
+ /*
70
+ * A header may draw more than its words - a help `?` beside them. The filter is not the header: it names the column
71
+ * by its words, or a press on that `?` would also tick the checkbox around it.
72
+ */
73
+ it('names a filter by the header words when the header draws more', async () => {
74
+ const drawn = [
75
+ { type: 'checkbox', name: 'flag', header: { text: 'flag', renderer: () => html`flag <button class="extra">?</button>` }, filter: true },
76
+ { type: 'select', name: 'kind', header: { text: 'kind', renderer: () => html`kind <button class="extra">?</button>` }, record: { options: ['', 'A'] }, filter: true }
77
+ ]
78
+ const el = (await fixture(html`
79
+ <ox-grist
80
+ style="height: 300px"
81
+ mode="GRID"
82
+ .config=${{ columns: drawn, pagination: { pages: [20] } }}
83
+ .fetchHandler=${async () => ({ page: 1, limit: 20, total: 0, records: [] })}
84
+ >
85
+ <div slot="headroom"><ox-filters-form without-search></ox-filters-form></div>
86
+ </ox-grist>
87
+ `)) as any
88
+ await settle()
89
+
90
+ const form = el.querySelector('ox-filters-form') as any
91
+ await form.updateComplete
92
+ const root = form.renderRoot as ShadowRoot
93
+ const deep = (node: ParentNode): boolean =>
94
+ Array.from(node.querySelectorAll('*')).some(one => one.matches('button.extra') || (!!one.shadowRoot && deep(one.shadowRoot)))
95
+ expect(deep(root), 'the header drawing is not in the filter form').to.equal(false)
96
+ expect(root.textContent).to.contain('flag')
97
+ expect(root.textContent).to.contain('kind')
98
+ })
68
99
  })
@@ -0,0 +1,114 @@
1
+ import { expect, fixture } from '@open-wc/testing'
2
+ import { html } from 'lit'
3
+
4
+ import '../src/data-grist.js'
5
+
6
+ /**
7
+ * The last fetch asked is the one the grid shows.
8
+ *
9
+ * A ledger opened from a dashboard card asked twice in a row - once with no conditions as it read its address,
10
+ * then with the month the card named. The grid kept whichever answer arrived first - the read with no conditions -
11
+ * and showed every line under a headroom that said October (2026-10-16).
12
+ */
13
+
14
+ const config = {
15
+ columns: [
16
+ { type: 'string', name: 'id', hidden: true },
17
+ { type: 'string', name: 'name', header: 'name' }
18
+ ],
19
+ pagination: { pages: [20] }
20
+ }
21
+
22
+ const answer = (total: number) => ({
23
+ total,
24
+ records: Array.from({ length: Math.min(total, 20) }, (_, i) => ({ id: `${total}-${i}`, name: `row ${i}` }))
25
+ })
26
+
27
+ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
28
+
29
+ type Grist = HTMLElement & { fetch(): Promise<void>; data: { total: number }; updateComplete: Promise<unknown> }
30
+
31
+ describe('ox-grist with two fetches out at once', () => {
32
+ /* Resizing inside a fixture trips Chrome's benign ResizeObserver loop error; it says nothing about fetching. */
33
+ const ignoreResizeLoop = (e: ErrorEvent) => {
34
+ if (String(e.message).includes('ResizeObserver loop')) {
35
+ e.stopImmediatePropagation()
36
+ e.preventDefault()
37
+ }
38
+ }
39
+ let previousOnError: OnErrorEventHandler = null
40
+ before(() => {
41
+ window.addEventListener('error', ignoreResizeLoop, true)
42
+ previousOnError = window.onerror
43
+ window.onerror = (message, ...rest) =>
44
+ String(message).includes('ResizeObserver loop') ? true : previousOnError?.call(window, message, ...rest)
45
+ })
46
+ after(() => {
47
+ window.removeEventListener('error', ignoreResizeLoop, true)
48
+ window.onerror = previousOnError
49
+ })
50
+
51
+ /*
52
+ * The answer depends on the conditions, as on the ledger: the read with none, asked first, comes back first; the
53
+ * month's comes back after it. The grid fetches once by itself when attached; the test starts after that settled.
54
+ */
55
+ const MONTH = [{ name: 'month', operator: 'eq', value: 10 }]
56
+ const byConditions = (failUnfiltered = false) => async ({ filters }: { filters?: unknown[] }) => {
57
+ if (!filters?.length) {
58
+ await wait(20)
59
+ if (failUnfiltered) throw new Error('the unfiltered read failed')
60
+ return answer(2908)
61
+ }
62
+ await wait(300)
63
+ return answer(4)
64
+ }
65
+
66
+ async function attached(handler: any) {
67
+ const el = (await fixture(html`<ox-grist style="height: 300px" .config=${config} .fetchHandler=${handler}></ox-grist>`)) as Grist & {
68
+ filters: unknown[]
69
+ renderRoot: ShadowRoot
70
+ }
71
+ await wait(400)
72
+ return el
73
+ }
74
+
75
+ it('shows the answer to the later fetch, though the earlier one came back first', async () => {
76
+ const el = await attached(byConditions())
77
+
78
+ el.filters = []
79
+ const unfiltered = el.fetch()
80
+ el.filters = MONTH
81
+ const month = el.fetch()
82
+ await Promise.all([unfiltered, month])
83
+ await el.updateComplete
84
+
85
+ expect(el.data.total).to.equal(4)
86
+ })
87
+
88
+ it('does not report the failure of a fetch a later one replaced', async () => {
89
+ const el = await attached(byConditions(true))
90
+
91
+ el.filters = []
92
+ const unfiltered = el.fetch().then(
93
+ () => 'kept quiet',
94
+ () => 'reported'
95
+ )
96
+ el.filters = MONTH
97
+ await el.fetch()
98
+
99
+ expect(await unfiltered).to.equal('kept quiet')
100
+ await el.updateComplete
101
+ expect(el.data.total).to.equal(4)
102
+ /* The grid draws "could not load" from fetchFailed - a replaced read must not make it say so. */
103
+ const body = el.renderRoot.querySelector('#grist') as (HTMLElement & { renderRoot: ShadowRoot }) | null
104
+ expect(!!body?.renderRoot.querySelector('ox-empty-note')).to.equal(false)
105
+ })
106
+
107
+ it('still shows a single fetch as before', async () => {
108
+ const el = (await fixture(html`<ox-grist style="height: 300px" .config=${config} .fetchHandler=${async () => answer(7)}></ox-grist>`)) as Grist
109
+ await el.fetch()
110
+ await el.updateComplete
111
+
112
+ expect(el.data.total).to.equal(7)
113
+ })
114
+ })