@operato/data-grist 10.17.0 → 10.22.0

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.17.0",
3
+ "version": "10.22.0",
4
4
  "description": "User interface for grid (desktop) and list (mobile)",
5
5
  "author": "heartyoh",
6
6
  "type": "module",
@@ -70,14 +70,14 @@
70
70
  "@material/web": "^2.0.0",
71
71
  "@operato/headroom": "^10.16.2",
72
72
  "@operato/i18n": "^10.16.2",
73
- "@operato/input": "^10.16.2",
74
- "@operato/list-state": "^10.16.2",
75
- "@operato/p13n": "^10.16.2",
76
- "@operato/popup": "^10.16.2",
73
+ "@operato/input": "^10.22.0",
74
+ "@operato/list-state": "^10.22.0",
75
+ "@operato/p13n": "^10.22.0",
76
+ "@operato/popup": "^10.22.0",
77
77
  "@operato/pull-to-refresh": "^10.16.2",
78
78
  "@operato/styles": "^10.16.2",
79
79
  "@operato/time-calculator": "^10.16.2",
80
- "@operato/utils": "^10.16.2",
80
+ "@operato/utils": "^10.22.0",
81
81
  "i18next": "^24.2.1",
82
82
  "json5": "^2.2.0",
83
83
  "lit": "^3.1.2",
@@ -116,5 +116,5 @@
116
116
  "prettier --write"
117
117
  ]
118
118
  },
119
- "gitHead": "f5851c0f8ea672b5369951395f2413cd4939ede9"
119
+ "gitHead": "1da01ac5d48c0b60ec27286d5f9f08d3ad6b971a"
120
120
  }
@@ -0,0 +1,156 @@
1
+ /* Elements are compared by presence, not by value: a failing element comparison makes chai render the DOM and hang the runner. */
2
+ import { expect, fixture } from '@open-wc/testing'
3
+ import { html } from 'lit'
4
+
5
+ import '../src/data-grist.js'
6
+
7
+ /**
8
+ * 검색 낱말이 서버까지 닿는 두 길.
9
+ *
10
+ * ── 옛 길(`columns`, 지금 기본) ────────────────────────────────────────────
11
+ * 격자가 **칸의 선언**을 보고 필드마다 `{name, operator:'search', value:'%낱말%'}` 를 만든다. 그래서
12
+ * 화면이 서버의 `searchables` 를 손으로 베껴 칸에 적어야 하고, 하나도 안 적으면 낱말이 **조용히
13
+ * 버려진다** — 목록은 오류 없이 통째로 오고 사람은 「그 번호가 없구나」로 읽는다. 프레임워크 화면
14
+ * 아홉이 그 상태였다(2026-09-19).
15
+ *
16
+ * ── 새 길(`term`) ─────────────────────────────────────────────────────────
17
+ * 격자가 낱말을 그대로 싣고 서버가 자기 `searchables` 로 푼다(`ListParam.search`). 화면에서 사본이
18
+ * 사라지므로 어긋날 자리도 사라진다.
19
+ *
20
+ * ── 기본을 안 바꾼다 ──────────────────────────────────────────────────────
21
+ * 이 부품은 우리 저장소 밖에서도 쓴다(dssp 40 파일 · fmsim 6). 그래서 새 길은 **켜는 쪽**이고, 옛
22
+ * 길의 거동은 글자까지 그대로 둔다. 기본값을 뒤집는 것은 그쪽에 알린 뒤의 별도 결정이다.
23
+ */
24
+
25
+ type Seen = { filters?: any[]; search?: string }
26
+
27
+ const columns = [
28
+ { type: 'string', name: 'id', hidden: true },
29
+ { type: 'string', name: 'name', header: 'name', filter: 'search' },
30
+ { type: 'string', name: 'description', header: 'description', filter: 'search' },
31
+ { type: 'string', name: 'state', header: 'state' }
32
+ ]
33
+
34
+ const settle = (ms = 150) => new Promise(resolve => setTimeout(resolve, ms))
35
+
36
+ async function grist(searchMode?: 'columns' | 'term') {
37
+ const seen: Seen[] = []
38
+ const el = (await fixture(html`
39
+ <ox-grist
40
+ style="height: 300px"
41
+ mode="GRID"
42
+ search-mode=${searchMode ?? 'columns'}
43
+ .config=${{ columns, pagination: { pages: [20] } }}
44
+ .fetchHandler=${async (param: any) => {
45
+ seen.push({ filters: param.filters, search: param.search })
46
+ return { page: 1, limit: 20, total: 0, records: [] }
47
+ }}
48
+ ></ox-grist>
49
+ `)) as any
50
+
51
+ await el.fetch().catch(() => {})
52
+ await settle()
53
+ return { el, seen }
54
+ }
55
+
56
+ const searchFilters = (seen: Seen[]) => (seen[seen.length - 1].filters ?? []).filter(one => one.operator === 'search')
57
+
58
+ describe('검색 낱말이 나가는 길', () => {
59
+ /*
60
+ * fixture 안의 격자는 붙는 순간 Chrome 의 `ResizeObserver loop completed` 를 던진다. 브라우저가 한
61
+ * 틱에 두 번 재는 것뿐이고 이 시험이 보는 것과 무관한데, mocha 는 그것을 **시험 실패로 센다** —
62
+ * 안 걸러 두면 여기 여섯이 통째로 빨강이고 까닭은 실패 이름에 안 나온다. `fetch-failure-state` 가
63
+ * 쓰는 것과 같은 거르개다(리스너 하나로는 안 된다 — mocha 는 `window.onerror` 쪽도 본다).
64
+ */
65
+ const ignoreResizeLoop = (e: ErrorEvent) => {
66
+ if (String(e.message).includes('ResizeObserver loop')) {
67
+ e.stopImmediatePropagation()
68
+ e.preventDefault()
69
+ }
70
+ }
71
+ let previousOnError: OnErrorEventHandler = null
72
+
73
+ before(() => {
74
+ window.addEventListener('error', ignoreResizeLoop, true)
75
+ previousOnError = window.onerror
76
+ window.onerror = (message, ...rest) =>
77
+ String(message).includes('ResizeObserver loop') ? true : previousOnError?.call(window, message, ...rest)
78
+ })
79
+
80
+ after(() => {
81
+ window.removeEventListener('error', ignoreResizeLoop, true)
82
+ window.onerror = previousOnError
83
+ })
84
+
85
+ it('옛 길은 칸마다 필터를 만든다 — 지금 거동 그대로', async () => {
86
+ const { el, seen } = await grist()
87
+
88
+ el.searchText = '구본'
89
+ await settle()
90
+
91
+ expect(searchFilters(seen).map(one => one.name)).to.eql(['name', 'description'])
92
+ expect(searchFilters(seen)[0].value).to.equal('%구본%')
93
+ expect(seen[seen.length - 1].search).to.equal(undefined)
94
+ })
95
+
96
+ it('옛 길에서 낱말을 되읽는다', async () => {
97
+ const { el } = await grist()
98
+
99
+ el.searchText = '구본'
100
+ await settle()
101
+
102
+ expect(el.searchText).to.equal('구본')
103
+
104
+ el.searchText = ''
105
+ await settle()
106
+
107
+ expect(el.searchText).to.equal('')
108
+ })
109
+
110
+ it('새 길은 낱말을 그대로 싣고 칸 선언을 읽지 않는다', async () => {
111
+ /*
112
+ * 칸 둘이 `filter: 'search'` 를 선언한 채로 둔다. 같은 낱말이 두 번 걸리지 않아야 한다 —
113
+ * 한 화면에 두 길이 같이 서는 것이 이 설계에서 제일 막아야 하는 것이다.
114
+ */
115
+ const { el, seen } = await grist('term')
116
+
117
+ el.searchText = '구본'
118
+ await settle()
119
+
120
+ expect(seen[seen.length - 1].search).to.equal('구본')
121
+ expect(searchFilters(seen)).to.eql([])
122
+ })
123
+
124
+ it('새 길에서도 낱말을 되읽는다 — 제목 줄이 이것으로 자기 값을 그린다', async () => {
125
+ /*
126
+ * 옛 길의 되읽기는 `filters` 에서 `%낱말%` 을 벗겨 답했다. 새 길에는 그 필터가 없으므로 격자가
127
+ * 자기 상태로 들어야 한다. 이 자리가 이 변경에서 제일 조용히 깨질 곳이다.
128
+ */
129
+ const { el } = await grist('term')
130
+
131
+ el.searchText = '구본'
132
+ await settle()
133
+
134
+ expect(el.searchText).to.equal('구본')
135
+ })
136
+
137
+ it('새 길에서 낱말을 비우면 되읽은 것도 비고, 낱말도 안 나간다', async () => {
138
+ const { el, seen } = await grist('term')
139
+
140
+ el.searchText = '구본'
141
+ await settle()
142
+ el.searchText = ''
143
+ await settle()
144
+
145
+ expect(el.searchText).to.equal('')
146
+ expect(seen[seen.length - 1].search || '').to.equal('')
147
+ })
148
+
149
+ it('길을 안 고르면 옛 길이다 — 밖의 소비자가 그날 안 바뀐다', async () => {
150
+ const el = (await fixture(html`
151
+ <ox-grist style="height: 300px" mode="GRID" .config=${{ columns, pagination: { pages: [20] } }}></ox-grist>
152
+ `)) as any
153
+
154
+ expect(el.searchMode).to.equal('columns')
155
+ })
156
+ })