@charcuterie/ui 3.0.0 → 3.1.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/README.md +10 -0
- package/dist/Badge/Badge.d.ts +33 -3
- package/dist/Badge/Badge.d.ts.map +1 -1
- package/dist/Badge/Badge.js +20 -2
- package/dist/Badge/Badge.js.map +1 -1
- package/dist/Board/Board.d.ts +119 -0
- package/dist/Board/Board.d.ts.map +1 -0
- package/dist/Board/Board.js +165 -0
- package/dist/Board/Board.js.map +1 -0
- package/dist/Board/BoardCard.d.ts +120 -0
- package/dist/Board/BoardCard.d.ts.map +1 -0
- package/dist/Board/BoardCard.js +77 -0
- package/dist/Board/BoardCard.js.map +1 -0
- package/dist/Board/BoardLaneList.d.ts +103 -0
- package/dist/Board/BoardLaneList.d.ts.map +1 -0
- package/dist/Board/BoardLaneList.js +69 -0
- package/dist/Board/BoardLaneList.js.map +1 -0
- package/dist/Board/boardMove.d.ts +116 -0
- package/dist/Board/boardMove.d.ts.map +1 -0
- package/dist/Board/boardMove.js +116 -0
- package/dist/Board/boardMove.js.map +1 -0
- package/dist/Board/useBoardDrag.d.ts +44 -0
- package/dist/Board/useBoardDrag.d.ts.map +1 -0
- package/dist/Board/useBoardDrag.js +237 -0
- package/dist/Board/useBoardDrag.js.map +1 -0
- package/dist/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/Checkbox/Checkbox.js +15 -4
- package/dist/Checkbox/Checkbox.js.map +1 -1
- package/dist/DataTable/DataTable.d.ts +159 -0
- package/dist/DataTable/DataTable.d.ts.map +1 -0
- package/dist/DataTable/DataTable.js +197 -0
- package/dist/DataTable/DataTable.js.map +1 -0
- package/dist/DatePicker/DateGrid.d.ts +79 -0
- package/dist/DatePicker/DateGrid.d.ts.map +1 -0
- package/dist/DatePicker/DateGrid.js +220 -0
- package/dist/DatePicker/DateGrid.js.map +1 -0
- package/dist/DatePicker/DatePicker.d.ts +138 -0
- package/dist/DatePicker/DatePicker.d.ts.map +1 -0
- package/dist/DatePicker/DatePicker.js +384 -0
- package/dist/DatePicker/DatePicker.js.map +1 -0
- package/dist/DatePicker/datePresets.d.ts +29 -0
- package/dist/DatePicker/datePresets.d.ts.map +1 -0
- package/dist/DatePicker/datePresets.js +28 -0
- package/dist/DatePicker/datePresets.js.map +1 -0
- package/dist/DatePicker/parseDateInput.d.ts +106 -0
- package/dist/DatePicker/parseDateInput.d.ts.map +1 -0
- package/dist/DatePicker/parseDateInput.js +419 -0
- package/dist/DatePicker/parseDateInput.js.map +1 -0
- package/dist/DatePicker/plainDate.d.ts +167 -0
- package/dist/DatePicker/plainDate.d.ts.map +1 -0
- package/dist/DatePicker/plainDate.js +272 -0
- package/dist/DatePicker/plainDate.js.map +1 -0
- package/dist/MarkdownEditor/MarkdownEditor.d.ts +147 -0
- package/dist/MarkdownEditor/MarkdownEditor.d.ts.map +1 -0
- package/dist/MarkdownEditor/MarkdownEditor.js +492 -0
- package/dist/MarkdownEditor/MarkdownEditor.js.map +1 -0
- package/dist/MarkdownEditor/markdownCommands.d.ts +202 -0
- package/dist/MarkdownEditor/markdownCommands.d.ts.map +1 -0
- package/dist/MarkdownEditor/markdownCommands.js +379 -0
- package/dist/MarkdownEditor/markdownCommands.js.map +1 -0
- package/dist/MarkdownEditor/markdownSpans.d.ts +92 -0
- package/dist/MarkdownEditor/markdownSpans.d.ts.map +1 -0
- package/dist/MarkdownEditor/markdownSpans.js +303 -0
- package/dist/MarkdownEditor/markdownSpans.js.map +1 -0
- package/dist/categoricalStyles.d.ts +53 -0
- package/dist/categoricalStyles.d.ts.map +1 -0
- package/dist/categoricalStyles.js +195 -0
- package/dist/categoricalStyles.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/Badge/Badge.mdx +46 -0
- package/src/Badge/Badge.stories.tsx +98 -4
- package/src/Badge/Badge.test.tsx +157 -2
- package/src/Badge/Badge.tsx +49 -3
- package/src/Board/Board.mdx +199 -0
- package/src/Board/Board.stories.tsx +707 -0
- package/src/Board/Board.test.tsx +310 -0
- package/src/Board/Board.tsx +333 -0
- package/src/Board/BoardCard.tsx +290 -0
- package/src/Board/BoardLaneList.tsx +275 -0
- package/src/Board/boardMove.test.ts +213 -0
- package/src/Board/boardMove.ts +225 -0
- package/src/Board/useBoardDrag.ts +413 -0
- package/src/Checkbox/Checkbox.tsx +34 -3
- package/src/DataTable/DataTable.mdx +123 -0
- package/src/DataTable/DataTable.stories.tsx +411 -0
- package/src/DataTable/DataTable.test.tsx +386 -0
- package/src/DataTable/DataTable.tsx +497 -0
- package/src/DatePicker/DateGrid.tsx +467 -0
- package/src/DatePicker/DatePicker.mdx +170 -0
- package/src/DatePicker/DatePicker.stories.tsx +268 -0
- package/src/DatePicker/DatePicker.test.tsx +455 -0
- package/src/DatePicker/DatePicker.tsx +852 -0
- package/src/DatePicker/datePresets.ts +34 -0
- package/src/DatePicker/parseDateInput.test.ts +187 -0
- package/src/DatePicker/parseDateInput.ts +629 -0
- package/src/DatePicker/plainDate.test.ts +375 -0
- package/src/DatePicker/plainDate.ts +465 -0
- package/src/MarkdownEditor/MarkdownEditor.mdx +189 -0
- package/src/MarkdownEditor/MarkdownEditor.stories.tsx +267 -0
- package/src/MarkdownEditor/MarkdownEditor.test.tsx +627 -0
- package/src/MarkdownEditor/MarkdownEditor.tsx +862 -0
- package/src/MarkdownEditor/markdownCommands.test.ts +384 -0
- package/src/MarkdownEditor/markdownCommands.ts +566 -0
- package/src/MarkdownEditor/markdownSpans.test.ts +217 -0
- package/src/MarkdownEditor/markdownSpans.ts +458 -0
- package/src/argTypes.storyHelpers.ts +39 -0
- package/src/categoricalStyles.ts +244 -0
- package/src/index.ts +89 -0
- package/src/sourceRules.test.ts +52 -1
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { composeStories } from "@storybook/react"
|
|
2
|
+
import { expect, userEvent, waitFor } from "storybook/test"
|
|
3
|
+
import { test } from "vitest"
|
|
4
|
+
|
|
5
|
+
import { expectNoAxeViolations } from "../expectNoAxeViolations.testHelpers.ts"
|
|
6
|
+
import { mountStory } from "../mountStory.testHelpers.ts"
|
|
7
|
+
import { expectAgentDrivable } from "../testing/index.ts"
|
|
8
|
+
import {
|
|
9
|
+
DESKTOP,
|
|
10
|
+
setViewport,
|
|
11
|
+
} from "../viewport.testHelpers.ts"
|
|
12
|
+
import * as stories from "./DataTable.stories.tsx"
|
|
13
|
+
|
|
14
|
+
const { AllVariants, Default, Interactive, Responsive } =
|
|
15
|
+
composeStories(stories)
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The `<caption>` is the table's accessible name. Without one a
|
|
19
|
+
* screen reader announces "table, 6 columns, 5 rows" and nothing a
|
|
20
|
+
* reader could use to decide whether to enter it — and
|
|
21
|
+
* `getByRole("table", { name })`, the query an agent writes, has
|
|
22
|
+
* nothing to match on.
|
|
23
|
+
*/
|
|
24
|
+
test("the table is drivable by role and name", async () => {
|
|
25
|
+
const { canvas, canvasElement } =
|
|
26
|
+
await mountStory(Default)
|
|
27
|
+
|
|
28
|
+
const table = expectAgentDrivable(canvas, {
|
|
29
|
+
name: "Tasks",
|
|
30
|
+
role: "table",
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
await expect(table.tagName).toBe("TABLE")
|
|
34
|
+
|
|
35
|
+
await expectNoAxeViolations(canvasElement)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The composition, asserted rather than assumed: a sortable column
|
|
40
|
+
* is a `SortableTableHeader`, so it is a `<th>` carrying `aria-sort`
|
|
41
|
+
* with a `<button>` inside it, and a non-sortable one has **no**
|
|
42
|
+
* `aria-sort` at all — absent is how the attribute says "this column
|
|
43
|
+
* cannot be sorted", where `"none"` would say the opposite.
|
|
44
|
+
*/
|
|
45
|
+
test("sortable columns announce, unsortable ones stay silent", async () => {
|
|
46
|
+
const { canvas } = await mountStory(Default)
|
|
47
|
+
|
|
48
|
+
const updated = expectAgentDrivable(canvas, {
|
|
49
|
+
name: "Updated",
|
|
50
|
+
role: "columnheader",
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
await expect(updated).toHaveAttribute(
|
|
54
|
+
"aria-sort",
|
|
55
|
+
"descending",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
await expect(
|
|
59
|
+
expectAgentDrivable(canvas, {
|
|
60
|
+
name: "Task",
|
|
61
|
+
role: "columnheader",
|
|
62
|
+
}),
|
|
63
|
+
).toHaveAttribute("aria-sort", "none")
|
|
64
|
+
|
|
65
|
+
const assignee = expectAgentDrivable(canvas, {
|
|
66
|
+
name: "Assignee",
|
|
67
|
+
role: "columnheader",
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
await expect(assignee).not.toHaveAttribute("aria-sort")
|
|
71
|
+
|
|
72
|
+
await expect(
|
|
73
|
+
canvas.queryAllByRole("button", { name: "Assignee" }),
|
|
74
|
+
).toHaveLength(0)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The half the component does *not* own, proved by the half it does:
|
|
79
|
+
* clicking a header changes `aria-sort` **and** the story's rows
|
|
80
|
+
* come back in a different order, because the story sorted them.
|
|
81
|
+
*
|
|
82
|
+
* `priority` is the column that makes the split worth having. Its
|
|
83
|
+
* order is high → medium → low and its alphabetical order is
|
|
84
|
+
* high → low → medium, so a table that sorted its own rows would
|
|
85
|
+
* have to be handed a comparator to get this right anyway.
|
|
86
|
+
*/
|
|
87
|
+
test("a sort request re-orders the consumer's rows", async () => {
|
|
88
|
+
const { canvas } = await mountStory(Interactive)
|
|
89
|
+
|
|
90
|
+
await userEvent.click(
|
|
91
|
+
expectAgentDrivable(canvas, {
|
|
92
|
+
name: "Priority",
|
|
93
|
+
role: "button",
|
|
94
|
+
}),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
await waitFor(() => {
|
|
98
|
+
expect(
|
|
99
|
+
expectAgentDrivable(canvas, {
|
|
100
|
+
name: "Priority",
|
|
101
|
+
role: "columnheader",
|
|
102
|
+
}),
|
|
103
|
+
).toHaveAttribute("aria-sort", "ascending")
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const getFirstRowPriority = () =>
|
|
107
|
+
canvas
|
|
108
|
+
.getAllByRole("row")[1]
|
|
109
|
+
?.textContent?.includes("high")
|
|
110
|
+
|
|
111
|
+
await expect(getFirstRowPriority()).toBe(true)
|
|
112
|
+
|
|
113
|
+
await userEvent.click(
|
|
114
|
+
expectAgentDrivable(canvas, {
|
|
115
|
+
name: "Priority",
|
|
116
|
+
role: "button",
|
|
117
|
+
}),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
await waitFor(() => {
|
|
121
|
+
expect(
|
|
122
|
+
expectAgentDrivable(canvas, {
|
|
123
|
+
name: "Priority",
|
|
124
|
+
role: "columnheader",
|
|
125
|
+
}),
|
|
126
|
+
).toHaveAttribute("aria-sort", "descending")
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
await expect(getFirstRowPriority()).toBe(false)
|
|
130
|
+
|
|
131
|
+
// Exactly one column ever holds a direction.
|
|
132
|
+
await expect(
|
|
133
|
+
expectAgentDrivable(canvas, {
|
|
134
|
+
name: "Task",
|
|
135
|
+
role: "columnheader",
|
|
136
|
+
}),
|
|
137
|
+
).toHaveAttribute("aria-sort", "none")
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A gate, not a nicety. The sort control is reachable with `Tab` in
|
|
142
|
+
* document order and the ring is a **measured** outline — a class
|
|
143
|
+
* assertion would pass on a component whose ring never paints.
|
|
144
|
+
*/
|
|
145
|
+
test("a sortable header takes keyboard focus with a visible ring", async () => {
|
|
146
|
+
const { canvas } = await mountStory(Interactive)
|
|
147
|
+
|
|
148
|
+
const sortButton = expectAgentDrivable(canvas, {
|
|
149
|
+
name: "Task",
|
|
150
|
+
role: "button",
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// The selection column is first in document order, so the first
|
|
154
|
+
// stop is its header box and the second is the first sortable
|
|
155
|
+
// column. Asserting both is the tab order itself.
|
|
156
|
+
await userEvent.tab()
|
|
157
|
+
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(document.activeElement).toBe(
|
|
160
|
+
expectAgentDrivable(canvas, {
|
|
161
|
+
name: "Select every row",
|
|
162
|
+
role: "checkbox",
|
|
163
|
+
}),
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
await userEvent.tab()
|
|
168
|
+
|
|
169
|
+
await waitFor(() => {
|
|
170
|
+
expect(document.activeElement).toBe(sortButton)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const { outlineStyle, outlineWidth } =
|
|
174
|
+
globalThis.getComputedStyle(sortButton)
|
|
175
|
+
|
|
176
|
+
await expect(outlineStyle).toBe("solid")
|
|
177
|
+
|
|
178
|
+
await expect(
|
|
179
|
+
Number.parseFloat(outlineWidth),
|
|
180
|
+
).toBeGreaterThan(0)
|
|
181
|
+
|
|
182
|
+
await userEvent.keyboard("{Enter}")
|
|
183
|
+
|
|
184
|
+
await waitFor(() => {
|
|
185
|
+
expect(
|
|
186
|
+
expectAgentDrivable(canvas, {
|
|
187
|
+
name: "Task",
|
|
188
|
+
role: "columnheader",
|
|
189
|
+
}),
|
|
190
|
+
).toHaveAttribute("aria-sort", "descending")
|
|
191
|
+
})
|
|
192
|
+
}, 15000)
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* `indeterminate` is a **DOM property with no HTML attribute**, so
|
|
196
|
+
* there is no markup that could have set it — which is the shortest
|
|
197
|
+
* proof that the header box is written to rather than rendered.
|
|
198
|
+
*/
|
|
199
|
+
test("selecting some rows puts the header box in its third state", async () => {
|
|
200
|
+
const { canvas, canvasElement } =
|
|
201
|
+
await mountStory(Interactive)
|
|
202
|
+
|
|
203
|
+
const firstRowBox = expectAgentDrivable(canvas, {
|
|
204
|
+
name: "Book the chimney sweep",
|
|
205
|
+
role: "checkbox",
|
|
206
|
+
}) as HTMLInputElement
|
|
207
|
+
|
|
208
|
+
await userEvent.click(firstRowBox)
|
|
209
|
+
|
|
210
|
+
const selectAllBox = expectAgentDrivable(canvas, {
|
|
211
|
+
name: "Select every row",
|
|
212
|
+
role: "checkbox",
|
|
213
|
+
}) as HTMLInputElement
|
|
214
|
+
|
|
215
|
+
await waitFor(() => {
|
|
216
|
+
expect(selectAllBox.indeterminate).toBe(true)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
await expect(selectAllBox.checked).toBe(false)
|
|
220
|
+
|
|
221
|
+
// …and it is *painted*. `Checkbox` renders an `appearance-none`
|
|
222
|
+
// box, so before `indeterminate:` reached it the property was set,
|
|
223
|
+
// a screen reader announced "mixed", and the box on screen was
|
|
224
|
+
// indistinguishable from empty.
|
|
225
|
+
const mixedMark =
|
|
226
|
+
selectAllBox.parentElement?.querySelector(
|
|
227
|
+
"svg:last-of-type",
|
|
228
|
+
) as SVGElement
|
|
229
|
+
|
|
230
|
+
await expect(
|
|
231
|
+
globalThis.getComputedStyle(mixedMark).visibility,
|
|
232
|
+
).toBe("visible")
|
|
233
|
+
|
|
234
|
+
await userEvent.click(selectAllBox)
|
|
235
|
+
|
|
236
|
+
await waitFor(() => {
|
|
237
|
+
expect(selectAllBox.indeterminate).toBe(false)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
await expect(selectAllBox.checked).toBe(true)
|
|
241
|
+
|
|
242
|
+
for (const box of canvas.getAllByRole("checkbox")) {
|
|
243
|
+
await expect((box as HTMLInputElement).checked).toBe(
|
|
244
|
+
true,
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await expectNoAxeViolations(canvasElement)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The falsifiable half of "the checkboxes are written to, not
|
|
253
|
+
* re-mounted": a re-mount would take the focus with it, one row into
|
|
254
|
+
* a keyboard pass down the column. This is the assertion that fails
|
|
255
|
+
* if anyone ever swaps the effect for a state-bearing `key`.
|
|
256
|
+
*/
|
|
257
|
+
test("ticking a row keeps the focus on the box that was ticked", async () => {
|
|
258
|
+
const { canvas } = await mountStory(Interactive)
|
|
259
|
+
|
|
260
|
+
const rowBox = expectAgentDrivable(canvas, {
|
|
261
|
+
name: "Label the spare keys",
|
|
262
|
+
role: "checkbox",
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
rowBox.focus()
|
|
266
|
+
|
|
267
|
+
await userEvent.keyboard(" ")
|
|
268
|
+
|
|
269
|
+
await waitFor(() => {
|
|
270
|
+
expect((rowBox as HTMLInputElement).checked).toBe(true)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
await expect(document.activeElement).toBe(rowBox)
|
|
274
|
+
|
|
275
|
+
// And the row says so to CSS, which is what paints it — not
|
|
276
|
+
// `aria-selected`, which belongs to `grid` rows rather than
|
|
277
|
+
// `table` rows.
|
|
278
|
+
await waitFor(() => {
|
|
279
|
+
expect(rowBox.closest("tr")).toHaveAttribute(
|
|
280
|
+
"data-is-selected",
|
|
281
|
+
"true",
|
|
282
|
+
)
|
|
283
|
+
})
|
|
284
|
+
}, 15000)
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* The container query, measured — the claim the whole component
|
|
288
|
+
* rests on.
|
|
289
|
+
*
|
|
290
|
+
* `Responsive` renders the same table into three fixed panels
|
|
291
|
+
* inside **one** window, so nothing about the viewport changes
|
|
292
|
+
* between these two assertions. At 15rem a cell is a `flex` block
|
|
293
|
+
* carrying its own column label; at 34rem — past `--cq-md` (32rem)
|
|
294
|
+
* — the same cell is a `table-cell` and the label is gone, because
|
|
295
|
+
* the header row is back above it.
|
|
296
|
+
*
|
|
297
|
+
* A `@media` query cannot produce this result at all: both tables
|
|
298
|
+
* are in the same window at the same zoom.
|
|
299
|
+
*/
|
|
300
|
+
test("the layout follows the container, not the window", async () => {
|
|
301
|
+
// 1440px of window, so the three panels can be the widths they
|
|
302
|
+
// claim — and so the wide table's own container is genuinely the
|
|
303
|
+
// thing being measured. The first panel is 240px inside that same
|
|
304
|
+
// 1440px window, which is the sentence a media query cannot say.
|
|
305
|
+
await setViewport(DESKTOP)
|
|
306
|
+
|
|
307
|
+
const { canvas, canvasElement } =
|
|
308
|
+
await mountStory(Responsive)
|
|
309
|
+
|
|
310
|
+
const narrowTable = expectAgentDrivable(canvas, {
|
|
311
|
+
name: "Tasks at 15rem",
|
|
312
|
+
role: "table",
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
const wideTable = expectAgentDrivable(canvas, {
|
|
316
|
+
name: "Tasks at 34rem",
|
|
317
|
+
role: "table",
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
const getFirstCell = (table: HTMLElement) =>
|
|
321
|
+
table.querySelector("tbody td") as HTMLElement
|
|
322
|
+
|
|
323
|
+
const narrowCell = getFirstCell(narrowTable)
|
|
324
|
+
|
|
325
|
+
const wideCell = getFirstCell(wideTable)
|
|
326
|
+
|
|
327
|
+
await expect(
|
|
328
|
+
globalThis.getComputedStyle(narrowCell).display,
|
|
329
|
+
).toBe("flex")
|
|
330
|
+
|
|
331
|
+
await expect(
|
|
332
|
+
globalThis.getComputedStyle(wideCell).display,
|
|
333
|
+
).toBe("table-cell")
|
|
334
|
+
|
|
335
|
+
// The in-cell column label: present and painted when narrow,
|
|
336
|
+
// `display: none` when the header row is doing that job.
|
|
337
|
+
const getLabel = (cell: HTMLElement) =>
|
|
338
|
+
cell.querySelector(
|
|
339
|
+
"[aria-hidden='true']",
|
|
340
|
+
) as HTMLElement
|
|
341
|
+
|
|
342
|
+
await expect(
|
|
343
|
+
globalThis.getComputedStyle(getFirstCell(narrowTable))
|
|
344
|
+
.display,
|
|
345
|
+
).toBe("flex")
|
|
346
|
+
|
|
347
|
+
await expect(
|
|
348
|
+
globalThis.getComputedStyle(getLabel(narrowCell))
|
|
349
|
+
.display,
|
|
350
|
+
).not.toBe("none")
|
|
351
|
+
|
|
352
|
+
await expect(
|
|
353
|
+
globalThis.getComputedStyle(getLabel(wideCell)).display,
|
|
354
|
+
).toBe("none")
|
|
355
|
+
|
|
356
|
+
// Reflow, not a pan surface: nothing in the narrow panel is
|
|
357
|
+
// wider than the panel.
|
|
358
|
+
const panel = narrowTable.parentElement as HTMLElement
|
|
359
|
+
|
|
360
|
+
await expect(narrowTable.scrollWidth).toBeLessThanOrEqual(
|
|
361
|
+
panel.clientWidth + 1,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
await expectNoAxeViolations(canvasElement)
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* The rows go, the table stays. A filter that matches nothing
|
|
369
|
+
* swapping the whole table for a message takes the sort the reader
|
|
370
|
+
* just set — and every column header — away with it.
|
|
371
|
+
*/
|
|
372
|
+
test("an empty table keeps its headers", async () => {
|
|
373
|
+
const { canvas } = await mountStory(AllVariants)
|
|
374
|
+
|
|
375
|
+
const emptyHeading = canvas.getByRole("heading", {
|
|
376
|
+
name: "No tasks",
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
await expect(emptyHeading.closest("table")).not.toBeNull()
|
|
380
|
+
|
|
381
|
+
await expect(
|
|
382
|
+
emptyHeading
|
|
383
|
+
.closest("table")
|
|
384
|
+
?.querySelectorAll("thead th"),
|
|
385
|
+
).toHaveLength(5)
|
|
386
|
+
})
|