@inglorious/web 3.0.0 → 4.0.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.
@@ -1,393 +0,0 @@
1
- /**
2
- * @vitest-environment jsdom
3
- */
4
- import { html } from "lit-html"
5
- import { beforeEach, describe, expect, it, vi } from "vitest"
6
-
7
- // Mock the filters module to prevent devtools-related errors in the test environment.
8
- vi.mock("./table/filters.js", () => ({
9
- filters: { render: () => html`` },
10
- }))
11
-
12
- import { table } from "."
13
- import {
14
- getPaginationInfo,
15
- getRows,
16
- getSortDirection,
17
- getTotalRows,
18
- isAllSelected,
19
- isSomeSelected,
20
- } from "./logic.js"
21
-
22
- const sampleData = [
23
- { id: 1, name: "Charlie", age: 35, active: true, role: "Admin" },
24
- { id: 2, name: "Alice", age: 30, active: true, role: "User" },
25
- { id: 3, name: "Bob", age: 25, active: false, role: "User" },
26
- { id: 4, name: "David", age: 40, active: true, role: "Admin" },
27
- ]
28
-
29
- const sampleColumns = [
30
- { id: "name", title: "Name", isSortable: true, isFilterable: true },
31
- {
32
- id: "age",
33
- title: "Age",
34
- type: "number",
35
- isSortable: true,
36
- isFilterable: true,
37
- },
38
- {
39
- id: "active",
40
- title: "Active",
41
- type: "boolean",
42
- isFilterable: true,
43
- },
44
- {
45
- id: "role",
46
- title: "Role",
47
- isFilterable: true,
48
- filter: { type: "select", options: ["Admin", "User"] },
49
- },
50
- ]
51
-
52
- describe("table", () => {
53
- let entity
54
-
55
- beforeEach(() => {
56
- entity = {
57
- id: "test-table",
58
- type: "table",
59
- data: JSON.parse(JSON.stringify(sampleData)), // Deep clone
60
- columns: JSON.parse(JSON.stringify(sampleColumns)),
61
- pagination: { page: 0, pageSize: 2 },
62
- search: { value: "" },
63
- }
64
- })
65
-
66
- describe("logic", () => {
67
- describe("create()", () => {
68
- it("should initialize with default state", () => {
69
- const newEntity = { data: [{ id: 1, name: "Test" }] }
70
- table.create(newEntity)
71
- expect(newEntity.sorts).toEqual([])
72
- expect(newEntity.filters).toEqual({})
73
- expect(newEntity.selection).toEqual([])
74
- expect(newEntity.columns).toBeDefined()
75
- expect(newEntity.columns[0].id).toBe("id")
76
- expect(newEntity.columns[0].title).toBe("Id")
77
- })
78
- })
79
-
80
- describe("sortChange()", () => {
81
- beforeEach(() => {
82
- table.create(entity)
83
- })
84
-
85
- it("should add a new sort", () => {
86
- table.sortChange(entity, "name")
87
- expect(entity.sorts).toEqual([{ column: "name", direction: "asc" }])
88
- })
89
-
90
- it("should toggle sort direction from asc to desc", () => {
91
- table.sortChange(entity, "name") // asc
92
- table.sortChange(entity, "name") // desc
93
- expect(entity.sorts).toEqual([{ column: "name", direction: "desc" }])
94
- })
95
-
96
- it("should remove sort when toggling from desc", () => {
97
- table.sortChange(entity, "name") // asc
98
- table.sortChange(entity, "name") // desc
99
- table.sortChange(entity, "name") // remove
100
- expect(entity.sorts).toEqual([])
101
- })
102
-
103
- it("should reset to page 0 on sort change", () => {
104
- entity.pagination.page = 1
105
- table.sortChange(entity, "name")
106
- expect(entity.pagination.page).toBe(0)
107
- })
108
- })
109
-
110
- describe("filterChange()", () => {
111
- beforeEach(() => {
112
- table.create(entity)
113
- })
114
-
115
- it("should add a filter", () => {
116
- table.filterChange(entity, { columnId: "name", value: "Alice" })
117
- expect(entity.filters.name).toBe("Alice")
118
- })
119
-
120
- it("should remove a filter when value is empty", () => {
121
- table.filterChange(entity, { columnId: "name", value: "Alice" })
122
- table.filterChange(entity, { columnId: "name", value: "" })
123
- expect(entity.filters.name).toBeUndefined()
124
- })
125
-
126
- it("should reset to page 0 on filter change", () => {
127
- entity.pagination.page = 1
128
- table.filterChange(entity, { columnId: "name", value: "Alice" })
129
- expect(entity.pagination.page).toBe(0)
130
- })
131
- })
132
-
133
- describe("pagination", () => {
134
- beforeEach(() => {
135
- table.create(entity)
136
- })
137
-
138
- it("pageNext: should go to the next page", () => {
139
- table.pageNext(entity)
140
- expect(entity.pagination.page).toBe(1)
141
- })
142
-
143
- it("pageNext: should not go past the last page", () => {
144
- table.pageNext(entity) // page 1
145
- table.pageNext(entity) // still page 1 (total 4 items, size 2 -> 2 pages)
146
- expect(entity.pagination.page).toBe(1)
147
- })
148
-
149
- it("pagePrev: should go to the previous page", () => {
150
- entity.pagination.page = 1
151
- table.pagePrev(entity)
152
- expect(entity.pagination.page).toBe(0)
153
- })
154
-
155
- it("pagePrev: should not go before the first page", () => {
156
- table.pagePrev(entity)
157
- expect(entity.pagination.page).toBe(0)
158
- })
159
-
160
- it("pageSizeChange: should change page size and reset to page 0", () => {
161
- entity.pagination.page = 1
162
- table.pageSizeChange(entity, 4)
163
- expect(entity.pagination.pageSize).toBe(4)
164
- expect(entity.pagination.page).toBe(0)
165
- })
166
- })
167
-
168
- describe("selection", () => {
169
- beforeEach(() => {
170
- table.create(entity)
171
- })
172
-
173
- it("rowToggle: should select an unselected row", () => {
174
- table.rowToggle(entity, 1)
175
- expect(entity.selection).toContain(1)
176
- })
177
-
178
- it("rowToggle: should deselect a selected row", () => {
179
- entity.selection = [1]
180
- table.rowToggle(entity, 1)
181
- expect(entity.selection).not.toContain(1)
182
- })
183
-
184
- it("rowToggle: should replace selection in single-select mode", () => {
185
- entity.isMultiSelect = false
186
- entity.selection = [1]
187
- table.rowToggle(entity, 2)
188
- expect(entity.selection).toEqual([2])
189
- })
190
-
191
- it("rowsToggleAll: should select all visible rows if not all are selected", () => {
192
- entity.selection = [1] // Bob (id 3) is on page 2, so not visible
193
- table.rowsToggleAll(entity) // Selects visible rows: Charlie (1), Alice (2)
194
- expect(isAllSelected(entity)).toBe(true)
195
- expect(entity.selection).toContain(1)
196
- expect(entity.selection).toContain(2)
197
- })
198
-
199
- it("rowsToggleAll: should deselect all visible rows if all are selected", () => {
200
- entity.selection = [1, 2] // All visible rows on page 0 are selected
201
- table.rowsToggleAll(entity)
202
- expect(entity.selection).toEqual([])
203
- })
204
- })
205
-
206
- describe("getters and selectors", () => {
207
- beforeEach(() => {
208
- table.create(entity)
209
- })
210
-
211
- it("getRows: should return sorted, filtered, and paginated rows", () => {
212
- // Sort by age descending
213
- table.sortChange(entity, "age")
214
- table.sortChange(entity, "age")
215
- // Filter for active users
216
- table.filterChange(entity, { columnId: "active", value: true })
217
-
218
- // Expected after filter: Charlie (35), Alice (30), David (40)
219
- // Expected after sort: David (40), Charlie (35), Alice (30)
220
- // Expected after pagination (size 2): David (40), Charlie (35)
221
- const rows = getRows(entity)
222
- expect(rows.map((r) => r.name)).toEqual(["David", "Charlie"])
223
- })
224
-
225
- it("getTotalRows: should return total count after filtering", () => {
226
- table.filterChange(entity, { columnId: "role", value: "Admin" })
227
- expect(getTotalRows(entity)).toBe(2) // Charlie, David
228
- })
229
-
230
- it("getPaginationInfo: should return correct pagination details", () => {
231
- const info = getPaginationInfo(entity)
232
- expect(info).toEqual({
233
- page: 0,
234
- pageSize: 2,
235
- totalPages: 2,
236
- totalRows: 4,
237
- start: 0,
238
- end: 2,
239
- hasNextPage: true,
240
- hasPrevPage: false,
241
- })
242
- })
243
-
244
- it("getSortDirection: should return the sort direction of a column", () => {
245
- table.sortChange(entity, "name")
246
- expect(getSortDirection(entity, "name")).toBe("asc")
247
- expect(getSortDirection(entity, "age")).toBeNull()
248
- })
249
-
250
- it("isAllSelected: should return true if all visible rows are selected", () => {
251
- entity.selection = [1, 2]
252
- expect(isAllSelected(entity)).toBe(true)
253
- })
254
-
255
- it("isSomeSelected: should return true if some (but not all) visible rows are selected", () => {
256
- entity.selection = [1]
257
- expect(isSomeSelected(entity)).toBe(true)
258
- entity.selection = [1, 2]
259
- expect(isSomeSelected(entity)).toBe(false) // All are selected
260
- })
261
- })
262
- })
263
-
264
- describe("rendering", () => {
265
- let api
266
-
267
- beforeEach(() => {
268
- // Mock the API and sub-renderers for focused testing
269
- api = {
270
- notify: vi.fn(),
271
- getType: vi.fn().mockReturnValue({
272
- ...table, // Use the real methods
273
- renderHeader: vi.fn(() => html`<div>Header</div>`),
274
- renderBody: vi.fn(() => html`<div>Body</div>`),
275
- renderFooter: vi.fn(() => html`<div>Footer</div>`),
276
- }),
277
- }
278
-
279
- table.create(entity)
280
- })
281
-
282
- it("render: should call sub-renderers", () => {
283
- table.render(entity, api)
284
- const type = api.getType()
285
- expect(type.renderHeader).toHaveBeenCalledWith(entity, api)
286
- expect(type.renderBody).toHaveBeenCalledWith(entity, api)
287
- expect(type.renderFooter).toHaveBeenCalledWith(entity, api)
288
- })
289
-
290
- it("renderHeader: should render a header row with columns", () => {
291
- // Use the real renderHeader
292
- api.getType.mockReturnValue(table)
293
- const result = table.renderHeader(entity, api)
294
- const renderedItems = result.values.find(Array.isArray)
295
- expect(renderedItems).toHaveLength(entity.columns.length)
296
- })
297
-
298
- it("renderHeaderColumn: should render a title and sort icon", () => {
299
- table.sortChange(entity, "name") // sort asc
300
- const column = entity.columns[0] // name column
301
- const result = table.renderHeaderColumn(entity, column, api)
302
- const renderedText = result.strings.join("")
303
- const title = result.values[2]
304
- const icon = result.values[3]
305
- expect(renderedText).toContain("iw-table-header-title")
306
- expect(`${title} ${icon}`).toBe("Name ▲")
307
- })
308
-
309
- it("renderBody: should render a row for each visible item", () => {
310
- api.getType.mockReturnValue(table)
311
- const result = table.renderBody(entity, api)
312
- const [renderedRows] = result.values
313
- const visibleRows = getRows(entity)
314
- expect(renderedRows).toHaveLength(visibleRows.length)
315
- })
316
-
317
- it("renderRow: should render a row with correct classes", () => {
318
- api.getType.mockReturnValue(table)
319
- entity.selection = [1] // Select the first row
320
- const rowData = entity.data[0] // Charlie
321
-
322
- // The classMap directive is inside a string, making it hard to inspect.
323
- // Instead, we can verify the logic that would be passed to it.
324
- const isSelected = entity.selection?.includes(rowData.id)
325
- const isEven = 0 % 2 !== 0 // The test passes index 0
326
-
327
- // Assert that our logic correctly determines the class states.
328
- expect(isEven).toBe(false)
329
- expect(isSelected).toBe(true)
330
- })
331
-
332
- it("renderCell: should render a cell with correct style", () => {
333
- api.getType.mockReturnValue(table)
334
- const cellData = "Test"
335
- const column = entity.columns[0] // name column
336
- column.width = 150
337
- const result = table.renderCell(entity, cellData, 0, api)
338
- const styleString = result.values[1]
339
- expect(styleString).toContain("width: 150px")
340
- })
341
-
342
- it("renderFooter: should render pagination info", () => {
343
- api.getType.mockReturnValue(table)
344
- // Ensure pagination is present for this test
345
- entity.pagination = { page: 0, pageSize: 2 }
346
- const result = table.renderFooter(entity, api)
347
- // Reconstruct a simplified string from the template parts to check content
348
- const renderedText = result.strings.reduce(
349
- (acc, str, i) => acc + str + (result.values[i] ?? ""),
350
- "",
351
- )
352
- // The reconstructed text might have extra whitespace, so we check for parts.
353
- expect(renderedText).toContain("1 to 2 of 4")
354
- expect(renderedText).toContain("entries")
355
- })
356
-
357
- it("renderPagination: should render page controls", () => {
358
- api.getType.mockReturnValue(table)
359
- entity.pagination = { page: 0, pageSize: 2 }
360
- const paginationInfo = getPaginationInfo(entity)
361
- const result = table.renderPagination(entity, paginationInfo, api)
362
-
363
- // Reconstruct a simplified string from the template parts to check content
364
- // This is more robust than inspecting the internal structure of lit-html's TemplateResult
365
- const renderedText = result.strings.reduce(
366
- (acc, str, i) => acc + str + (result.values[i] ?? ""),
367
- "",
368
- )
369
- expect(renderedText).toContain('class="iw-table-page-input"')
370
- expect(result.values).toContain(1) // Check that the dynamic value is correct
371
- })
372
-
373
- it("mount: should measure and set column widths", () => {
374
- const mockColumn1 = document.createElement("div")
375
- vi.spyOn(mockColumn1, "offsetWidth", "get").mockReturnValue(150)
376
- const mockColumn2 = document.createElement("div")
377
- vi.spyOn(mockColumn2, "offsetWidth", "get").mockReturnValue(200)
378
-
379
- const containerEl = document.createElement("div")
380
- containerEl.appendChild(mockColumn1)
381
- containerEl.appendChild(mockColumn2)
382
-
383
- // Only set width if it's a string (e.g., "auto")
384
- entity.columns[0].width = "auto"
385
- entity.columns[1].width = "auto"
386
-
387
- table.mount(entity, containerEl)
388
-
389
- expect(entity.columns[0].width).toBe(150)
390
- expect(entity.columns[1].width).toBe(200)
391
- })
392
- })
393
- })