@charcuterie/ui 4.20.1 → 4.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/LICENSE +21 -0
- package/README.md +6 -6
- package/dist/AdaptiveGrid/AdaptiveGrid.d.ts +4 -1
- package/dist/AdaptiveGrid/AdaptiveGrid.d.ts.map +1 -1
- package/dist/AdaptiveGrid/AdaptiveGrid.js +3 -1
- package/dist/AdaptiveGrid/AdaptiveGrid.js.map +1 -1
- package/dist/AdaptiveGrid/chooseColumns.d.ts +10 -2
- package/dist/AdaptiveGrid/chooseColumns.d.ts.map +1 -1
- package/dist/AdaptiveGrid/chooseColumns.js +10 -3
- package/dist/AdaptiveGrid/chooseColumns.js.map +1 -1
- package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts +6 -2
- package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts.map +1 -1
- package/dist/AdaptiveGrid/useAdaptiveColumns.js +4 -1
- package/dist/AdaptiveGrid/useAdaptiveColumns.js.map +1 -1
- package/dist/ComparisonChart/ComparisonChart.d.ts +12 -0
- package/dist/ComparisonChart/ComparisonChart.d.ts.map +1 -0
- package/dist/ComparisonChart/ComparisonChart.js +64 -0
- package/dist/ComparisonChart/ComparisonChart.js.map +1 -0
- package/dist/Lightbox/Lightbox.d.ts +10 -2
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +46 -3
- package/dist/Lightbox/Lightbox.js.map +1 -1
- package/dist/Overlay/useAnchoredOverlay.d.ts +2 -2
- package/dist/TableColumns/TableColumns.d.ts +14 -0
- package/dist/TableColumns/TableColumns.d.ts.map +1 -0
- package/dist/TableColumns/TableColumns.js +19 -0
- package/dist/TableColumns/TableColumns.js.map +1 -0
- package/dist/VirtualizedGrid/VirtualizedGrid.d.ts +4 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.d.ts.map +1 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.js +3 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +12 -9
- package/src/AdaptiveGrid/AdaptiveGrid.mdx +10 -0
- package/src/AdaptiveGrid/AdaptiveGrid.stories.tsx +17 -0
- package/src/AdaptiveGrid/AdaptiveGrid.test.tsx +36 -2
- package/src/AdaptiveGrid/AdaptiveGrid.tsx +6 -0
- package/src/AdaptiveGrid/chooseColumns.ts +29 -1
- package/src/AdaptiveGrid/useAdaptiveColumns.ts +13 -1
- package/src/ComparisonChart/ComparisonChart.mdx +14 -0
- package/src/ComparisonChart/ComparisonChart.stories.tsx +85 -0
- package/src/ComparisonChart/ComparisonChart.test.tsx +33 -0
- package/src/ComparisonChart/ComparisonChart.tsx +161 -0
- package/src/CopyButton/CopyButton.test.tsx +25 -12
- package/src/Dialog/Dialog.test.tsx +38 -8
- package/src/LaneTimeline/LaneTimelineBar.tsx +3 -3
- package/src/Lightbox/Lightbox.mdx +9 -0
- package/src/Lightbox/Lightbox.stories.tsx +26 -0
- package/src/Lightbox/Lightbox.test.tsx +53 -0
- package/src/Lightbox/Lightbox.tsx +109 -5
- package/src/PortraitTiles/PortraitTiles.test.tsx +17 -11
- package/src/TableColumns/TableColumns.mdx +14 -0
- package/src/TableColumns/TableColumns.stories.tsx +81 -0
- package/src/TableColumns/TableColumns.test.tsx +35 -0
- package/src/TableColumns/TableColumns.tsx +93 -0
- package/src/VirtualizedGrid/VirtualizedGrid.test.tsx +21 -8
- package/src/VirtualizedGrid/VirtualizedGrid.tsx +6 -0
- package/src/board.storyHelpers.tsx +7 -1
- package/src/htmlEditor/HtmlEditor.test.tsx +32 -2
- package/src/index.ts +11 -1
- package/src/markdownEditorCodeMirror/MarkdownView.test.tsx +28 -10
- package/src/mdxReferences.test.ts +1 -1
- package/src/sourceRules.test.ts +2 -1
- package/src/vitestProvidedContext.d.ts +6 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
import { ContainerBoard } from "../board.storyHelpers.tsx"
|
|
5
|
+
import { TableColumns } from "./TableColumns.tsx"
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Components/Data/TableColumns",
|
|
9
|
+
component: TableColumns,
|
|
10
|
+
args: {
|
|
11
|
+
columns: [
|
|
12
|
+
{
|
|
13
|
+
key: "name",
|
|
14
|
+
label: "Name",
|
|
15
|
+
isVisible: true,
|
|
16
|
+
isRequired: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
key: "duration",
|
|
20
|
+
label: "Duration",
|
|
21
|
+
isVisible: true,
|
|
22
|
+
},
|
|
23
|
+
{ key: "median", label: "Median", isVisible: false },
|
|
24
|
+
],
|
|
25
|
+
onChange: () => {},
|
|
26
|
+
},
|
|
27
|
+
} satisfies Meta<typeof TableColumns>
|
|
28
|
+
export default meta
|
|
29
|
+
type Story = StoryObj<typeof meta>
|
|
30
|
+
const Controlled = (
|
|
31
|
+
props: React.ComponentProps<typeof TableColumns>,
|
|
32
|
+
) => {
|
|
33
|
+
const [columns, setColumns] = useState([...props.columns])
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
<TableColumns
|
|
37
|
+
{...props}
|
|
38
|
+
columns={columns}
|
|
39
|
+
onChange={setColumns}
|
|
40
|
+
/>
|
|
41
|
+
<p>
|
|
42
|
+
{columns
|
|
43
|
+
.filter((column) => column.isVisible)
|
|
44
|
+
.map((column) => column.label)
|
|
45
|
+
.join(", ")}
|
|
46
|
+
</p>
|
|
47
|
+
</>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
export const Playground: Story = {
|
|
51
|
+
parameters: playgroundParameters,
|
|
52
|
+
render: (props) => <Controlled {...props} />,
|
|
53
|
+
}
|
|
54
|
+
export const AllVariants: Story = {
|
|
55
|
+
render: (props) => (
|
|
56
|
+
<Controlled {...props} label="Choose columns" />
|
|
57
|
+
),
|
|
58
|
+
}
|
|
59
|
+
export const AllStates: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
columns: [
|
|
62
|
+
{
|
|
63
|
+
key: "name",
|
|
64
|
+
label: "Name",
|
|
65
|
+
isVisible: true,
|
|
66
|
+
isRequired: true,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
render: (props) => <Controlled {...props} />,
|
|
71
|
+
}
|
|
72
|
+
export const Responsive: Story = {
|
|
73
|
+
render: (props) => (
|
|
74
|
+
<ContainerBoard>
|
|
75
|
+
{() => <Controlled {...props} />}
|
|
76
|
+
</ContainerBoard>
|
|
77
|
+
),
|
|
78
|
+
}
|
|
79
|
+
export const Interactive: Story = {
|
|
80
|
+
render: (props) => <Controlled {...props} />,
|
|
81
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { composeStories } from "@storybook/react"
|
|
2
|
+
import { expect, userEvent } from "storybook/test"
|
|
3
|
+
import { test } from "vitest"
|
|
4
|
+
import { mountStory } from "../mountStory.testHelpers.ts"
|
|
5
|
+
import { expectAgentDrivable } from "../testing/index.ts"
|
|
6
|
+
import * as stories from "./TableColumns.stories.tsx"
|
|
7
|
+
|
|
8
|
+
const { Interactive } = composeStories(stories)
|
|
9
|
+
test("column visibility and order can be changed with named controls", async () => {
|
|
10
|
+
const { canvas, body } = await mountStory(Interactive)
|
|
11
|
+
await userEvent.click(
|
|
12
|
+
expectAgentDrivable(canvas, {
|
|
13
|
+
role: "button",
|
|
14
|
+
name: "Columns",
|
|
15
|
+
}),
|
|
16
|
+
)
|
|
17
|
+
await userEvent.click(
|
|
18
|
+
expectAgentDrivable(body, {
|
|
19
|
+
role: "checkbox",
|
|
20
|
+
name: "Median",
|
|
21
|
+
}),
|
|
22
|
+
)
|
|
23
|
+
await expect(
|
|
24
|
+
canvas.getByText("Name, Duration, Median"),
|
|
25
|
+
).toBeVisible()
|
|
26
|
+
await userEvent.click(
|
|
27
|
+
expectAgentDrivable(body, {
|
|
28
|
+
role: "button",
|
|
29
|
+
name: "Move Median earlier",
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
32
|
+
await expect(
|
|
33
|
+
canvas.getByText("Name, Median, Duration"),
|
|
34
|
+
).toBeVisible()
|
|
35
|
+
})
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { Button } from "../Button/Button.tsx"
|
|
3
|
+
import { Checkbox } from "../Checkbox/Checkbox.tsx"
|
|
4
|
+
import { Popover } from "../Popover/Popover.tsx"
|
|
5
|
+
import { ReorderList } from "../ReorderList/ReorderList.tsx"
|
|
6
|
+
|
|
7
|
+
export type TableColumnChoice = {
|
|
8
|
+
key: string
|
|
9
|
+
label: string
|
|
10
|
+
isVisible: boolean
|
|
11
|
+
isRequired?: boolean
|
|
12
|
+
}
|
|
13
|
+
export type TableColumnsProps = {
|
|
14
|
+
columns: readonly TableColumnChoice[]
|
|
15
|
+
onChange: (columns: TableColumnChoice[]) => void
|
|
16
|
+
label?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Column visibility and ordering, with keyboard moves as the primary path. */
|
|
20
|
+
export const TableColumns = ({
|
|
21
|
+
columns,
|
|
22
|
+
onChange,
|
|
23
|
+
label = "Columns",
|
|
24
|
+
}: TableColumnsProps) => {
|
|
25
|
+
const [isVisible, setIsVisible] = useState(false)
|
|
26
|
+
return (
|
|
27
|
+
<Popover
|
|
28
|
+
heading="Table columns"
|
|
29
|
+
isVisible={isVisible}
|
|
30
|
+
onDismiss={() => setIsVisible(false)}
|
|
31
|
+
trigger={
|
|
32
|
+
<Button
|
|
33
|
+
intent="neutral"
|
|
34
|
+
appearance="soft"
|
|
35
|
+
onClick={() => setIsVisible(!isVisible)}
|
|
36
|
+
>
|
|
37
|
+
{label}
|
|
38
|
+
</Button>
|
|
39
|
+
}
|
|
40
|
+
>
|
|
41
|
+
<ReorderList
|
|
42
|
+
label="Column order"
|
|
43
|
+
items={columns}
|
|
44
|
+
onReorder={(from, to) => {
|
|
45
|
+
const next = [...columns]
|
|
46
|
+
const [column] = next.splice(from, 1)
|
|
47
|
+
next.splice(to, 0, column)
|
|
48
|
+
onChange(next)
|
|
49
|
+
}}
|
|
50
|
+
renderItem={({ item, isFirst, isLast, moveBy }) => (
|
|
51
|
+
<div className="flex flex-wrap items-center justify-between gap-2 py-1">
|
|
52
|
+
<Checkbox
|
|
53
|
+
label={item.label}
|
|
54
|
+
isChecked={item.isVisible}
|
|
55
|
+
isDisabled={item.isRequired}
|
|
56
|
+
onChange={(isChecked) =>
|
|
57
|
+
onChange(
|
|
58
|
+
columns.map((column) =>
|
|
59
|
+
column.key === item.key
|
|
60
|
+
? { ...column, isVisible: isChecked }
|
|
61
|
+
: column,
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
/>
|
|
66
|
+
<div className="flex gap-1">
|
|
67
|
+
<Button
|
|
68
|
+
size="sm"
|
|
69
|
+
intent="neutral"
|
|
70
|
+
appearance="ghost"
|
|
71
|
+
aria-label={`Move ${item.label} earlier`}
|
|
72
|
+
isDisabled={isFirst}
|
|
73
|
+
onClick={() => moveBy(-1)}
|
|
74
|
+
>
|
|
75
|
+
Earlier
|
|
76
|
+
</Button>
|
|
77
|
+
<Button
|
|
78
|
+
size="sm"
|
|
79
|
+
intent="neutral"
|
|
80
|
+
appearance="ghost"
|
|
81
|
+
aria-label={`Move ${item.label} later`}
|
|
82
|
+
isDisabled={isLast}
|
|
83
|
+
onClick={() => moveBy(1)}
|
|
84
|
+
>
|
|
85
|
+
Later
|
|
86
|
+
</Button>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
/>
|
|
91
|
+
</Popover>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
@@ -308,15 +308,28 @@ test("a grid inside a hidden panel mounts nothing and reveals without scrolling"
|
|
|
308
308
|
// Wait for the revealed grid to have MEASURED, not merely to
|
|
309
309
|
// have mounted — the adjustment, when it happens, happens on the
|
|
310
310
|
// virtualizer's resize path.
|
|
311
|
+
//
|
|
312
|
+
// Two signals, because one of them depends on the window. The end
|
|
313
|
+
// padding stands in for rows below the window, so it is only ever
|
|
314
|
+
// positive when the list is longer than the window. In a 3440x1440
|
|
315
|
+
// window all 48 cards fit, the padding is rightly 0, and waiting for
|
|
316
|
+
// it timed out on a grid that was working. There the signal is the
|
|
317
|
+
// LAST card drawn with a real height.
|
|
311
318
|
await waitFor(async () => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
319
|
+
const list = getShownList()
|
|
320
|
+
|
|
321
|
+
const isPadded =
|
|
322
|
+
Number(list.style.paddingBlockEnd.replace("px", "")) >
|
|
323
|
+
0
|
|
324
|
+
|
|
325
|
+
const lastCell = list.querySelector<HTMLElement>(
|
|
326
|
+
`li[aria-posinset="${list.querySelector("li")?.getAttribute("aria-setsize")}"]`,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
const isEveryCardDrawn =
|
|
330
|
+
(lastCell?.getBoundingClientRect().height ?? 0) > 0
|
|
331
|
+
|
|
332
|
+
await expect(isPadded || isEveryCardDrawn).toBe(true)
|
|
320
333
|
})
|
|
321
334
|
|
|
322
335
|
// The symptom, kept as a guard rather than as the proof. It is
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
2
|
import { useLayoutEffect, useState } from "react"
|
|
3
3
|
|
|
4
|
+
import type { GridLayout } from "../AdaptiveGrid/chooseColumns.ts"
|
|
4
5
|
import type { BlockSizeResolver } from "../AdaptiveGrid/useAdaptiveColumns.ts"
|
|
5
6
|
import { useAdaptiveColumns } from "../AdaptiveGrid/useAdaptiveColumns.ts"
|
|
6
7
|
import { toClassName } from "../toClassName.ts"
|
|
@@ -62,6 +63,8 @@ export type VirtualizedGridProps<Item> = {
|
|
|
62
63
|
* is one whose length a reader cannot count for itself.
|
|
63
64
|
*/
|
|
64
65
|
label?: string
|
|
66
|
+
/** Opt in to width-first media layout. */
|
|
67
|
+
layout?: GridLayout
|
|
65
68
|
/** The most columns the automatic answer will take. */
|
|
66
69
|
maxColumns?: number
|
|
67
70
|
/** The narrowest a column may be, in CSS px. */
|
|
@@ -203,6 +206,7 @@ export const VirtualizedGrid = <Item,>({
|
|
|
203
206
|
itemBlockSize,
|
|
204
207
|
items,
|
|
205
208
|
label,
|
|
209
|
+
layout: gridLayout,
|
|
206
210
|
maxColumns,
|
|
207
211
|
minColumnInlineSize,
|
|
208
212
|
overscanRows = 4,
|
|
@@ -214,6 +218,8 @@ export const VirtualizedGrid = <Item,>({
|
|
|
214
218
|
chromeBlockSize,
|
|
215
219
|
itemBlockSize,
|
|
216
220
|
itemCount: items.length,
|
|
221
|
+
gap,
|
|
222
|
+
layout: gridLayout,
|
|
217
223
|
maxAutoColumns: maxColumns,
|
|
218
224
|
minColumnInlineSize,
|
|
219
225
|
storageKey,
|
|
@@ -133,7 +133,13 @@ export const ContainerBoard = ({
|
|
|
133
133
|
},
|
|
134
134
|
].map((panel) => (
|
|
135
135
|
<div
|
|
136
|
-
|
|
136
|
+
// `shrink-0`, or the promise above is false: a flex item
|
|
137
|
+
// shrinks to its line, so in a window narrower than 34rem the
|
|
138
|
+
// widest panel quietly became the window's width and every
|
|
139
|
+
// "container, not window" test measured the window after all.
|
|
140
|
+
// A board wider than the window scrolls, which is the honest
|
|
141
|
+
// picture of three fixed containers on a phone.
|
|
142
|
+
className="flex shrink-0 flex-col gap-2"
|
|
137
143
|
key={panel.label}
|
|
138
144
|
style={{ inlineSize: panel.inlineSize }}
|
|
139
145
|
>
|
|
@@ -212,6 +212,35 @@ test("external defaults never overwrite the document; real edits emit HTML and k
|
|
|
212
212
|
).not.toHaveTextContent("data-old-format")
|
|
213
213
|
})
|
|
214
214
|
|
|
215
|
+
/**
|
|
216
|
+
* A toolbar action wherever the toolbar put it. On the phone the whole
|
|
217
|
+
* row folds into the overflow panel, so a test that looked for "Bold"
|
|
218
|
+
* in the row asserted about the window rather than the editor — and a
|
|
219
|
+
* read-only editor must refuse formatting from the panel too.
|
|
220
|
+
*/
|
|
221
|
+
const findToolbarAction = async (
|
|
222
|
+
toolbar: HTMLElement,
|
|
223
|
+
name: string,
|
|
224
|
+
) => {
|
|
225
|
+
const inRow = within(toolbar).queryByRole("button", {
|
|
226
|
+
name,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
if (inRow) {
|
|
230
|
+
return inRow
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
await userEvent.click(
|
|
234
|
+
within(toolbar).getByRole("button", {
|
|
235
|
+
name: "More actions",
|
|
236
|
+
}),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return within(
|
|
240
|
+
await within(document.body).findByRole("dialog"),
|
|
241
|
+
).getByRole("button", { name })
|
|
242
|
+
}
|
|
243
|
+
|
|
215
244
|
test("read-only and disabled editors refuse typing and formatting", async () => {
|
|
216
245
|
const { canvas } = await mountStory(AllStates)
|
|
217
246
|
const readOnly = await canvas.findByRole("textbox", {
|
|
@@ -234,11 +263,12 @@ test("read-only and disabled editors refuse typing and formatting", async () =>
|
|
|
234
263
|
)
|
|
235
264
|
await expect(disabled).toHaveAttribute("tabindex", "-1")
|
|
236
265
|
await expect(
|
|
237
|
-
|
|
266
|
+
await findToolbarAction(
|
|
238
267
|
canvas.getByRole("toolbar", {
|
|
239
268
|
name: "Read-only note formatting",
|
|
240
269
|
}),
|
|
241
|
-
|
|
270
|
+
"Bold",
|
|
271
|
+
),
|
|
242
272
|
).toBeDisabled()
|
|
243
273
|
const required = canvas.getByRole("textbox", {
|
|
244
274
|
name: "Required notes",
|
package/src/index.ts
CHANGED
|
@@ -130,6 +130,8 @@ export type {
|
|
|
130
130
|
export { ColorSchemeToggle } from "./ColorSchemeToggle/ColorSchemeToggle.tsx"
|
|
131
131
|
export type { ComboboxProps } from "./Combobox/Combobox.tsx"
|
|
132
132
|
export { Combobox } from "./Combobox/Combobox.tsx"
|
|
133
|
+
export type { ComparisonChartProps } from "./ComparisonChart/ComparisonChart.tsx"
|
|
134
|
+
export { ComparisonChart } from "./ComparisonChart/ComparisonChart.tsx"
|
|
133
135
|
export type {
|
|
134
136
|
CopyButtonProps,
|
|
135
137
|
CopyStatus,
|
|
@@ -260,7 +262,10 @@ export {
|
|
|
260
262
|
toBarPlacement,
|
|
261
263
|
toTimelineAxis,
|
|
262
264
|
} from "./LaneTimeline/laneTimelineGeometry.ts"
|
|
263
|
-
export type {
|
|
265
|
+
export type {
|
|
266
|
+
LightboxImage,
|
|
267
|
+
LightboxProps,
|
|
268
|
+
} from "./Lightbox/Lightbox.tsx"
|
|
264
269
|
export { Lightbox } from "./Lightbox/Lightbox.tsx"
|
|
265
270
|
export type {
|
|
266
271
|
ListboxItem,
|
|
@@ -496,6 +501,11 @@ export {
|
|
|
496
501
|
getConnectionLabel,
|
|
497
502
|
getIsConnectionBusy,
|
|
498
503
|
} from "./statusIntent.ts"
|
|
504
|
+
export type {
|
|
505
|
+
TableColumnChoice,
|
|
506
|
+
TableColumnsProps,
|
|
507
|
+
} from "./TableColumns/TableColumns.tsx"
|
|
508
|
+
export { TableColumns } from "./TableColumns/TableColumns.tsx"
|
|
499
509
|
export type { TableViewportProps } from "./TableViewport/TableViewport.tsx"
|
|
500
510
|
export { TableViewport } from "./TableViewport/TableViewport.tsx"
|
|
501
511
|
export type {
|
|
@@ -59,14 +59,17 @@ test("it is an article whose headings are headings", async () => {
|
|
|
59
59
|
"false",
|
|
60
60
|
)
|
|
61
61
|
|
|
62
|
+
// `findBy`, not `getBy`: CodeMirror draws its decorations after the
|
|
63
|
+
// first paint, and with four browser windows sharing the runner the
|
|
64
|
+
// second heading was not drawn yet when a synchronous query looked.
|
|
62
65
|
await expect(
|
|
63
|
-
canvas.
|
|
66
|
+
await canvas.findByRole("heading", {
|
|
64
67
|
name: "Rack move, phase two",
|
|
65
68
|
}),
|
|
66
69
|
).toHaveAttribute("aria-level", "1")
|
|
67
70
|
|
|
68
71
|
await expect(
|
|
69
|
-
canvas.
|
|
72
|
+
await canvas.findByRole("heading", {
|
|
70
73
|
name: "Before the window",
|
|
71
74
|
}),
|
|
72
75
|
).toHaveAttribute("aria-level", "2")
|
|
@@ -202,7 +205,7 @@ test("a link is a real anchor in the tab order", async () => {
|
|
|
202
205
|
test("a checkbox is inert until a consumer says where the tick goes", async () => {
|
|
203
206
|
const { canvas } = await mountStory(AllStates)
|
|
204
207
|
|
|
205
|
-
const inert = canvas.
|
|
208
|
+
const inert = await canvas.findByRole("checkbox", {
|
|
206
209
|
name: "Photograph the current cabling",
|
|
207
210
|
})
|
|
208
211
|
|
|
@@ -239,9 +242,11 @@ test("hostile markdown renders as characters, not as behaviour", async () => {
|
|
|
239
242
|
const { canvas, canvasElement } =
|
|
240
243
|
await mountStory(HostileMarkdown)
|
|
241
244
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
// `findAllBy`: the links are CodeMirror decorations, drawn after the
|
|
246
|
+
// first paint — the same race as the headings test above.
|
|
247
|
+
const hrefs = (await canvas.findAllByRole("link")).map(
|
|
248
|
+
(link: HTMLElement) => link.getAttribute("href"),
|
|
249
|
+
)
|
|
245
250
|
|
|
246
251
|
await expect(hrefs).toEqual([
|
|
247
252
|
"https://example.invalid/runbook",
|
|
@@ -336,7 +341,7 @@ test("inline markup in link text does not drop the document to raw source", asyn
|
|
|
336
341
|
// The canary. This heading has nothing to do with links, and it
|
|
337
342
|
// is what stopped rendering when the plugin died.
|
|
338
343
|
await expect(
|
|
339
|
-
canvas.
|
|
344
|
+
await canvas.findByRole("heading", {
|
|
340
345
|
name: "Where the fix landed",
|
|
341
346
|
}),
|
|
342
347
|
).toHaveAttribute("aria-level", "2")
|
|
@@ -344,11 +349,16 @@ test("inline markup in link text does not drop the document to raw source", asyn
|
|
|
344
349
|
// No raw source anywhere in the document.
|
|
345
350
|
const article = canvas.getByRole("article")
|
|
346
351
|
|
|
347
|
-
|
|
352
|
+
// The heading can paint before lower link decorations finish parsing.
|
|
353
|
+
// Retry the complete contract rather than treating that partial paint
|
|
354
|
+
// as the final document; all three source-marker assertions still apply.
|
|
355
|
+
await waitFor(async () => {
|
|
356
|
+
await expect(article.textContent).not.toContain("](")
|
|
348
357
|
|
|
349
|
-
|
|
358
|
+
await expect(article.textContent).not.toContain("**")
|
|
350
359
|
|
|
351
|
-
|
|
360
|
+
await expect(article.textContent).not.toContain("~~")
|
|
361
|
+
})
|
|
352
362
|
})
|
|
353
363
|
|
|
354
364
|
/**
|
|
@@ -415,6 +425,14 @@ test("a code span is smaller than the prose it sits in", async () => {
|
|
|
415
425
|
|
|
416
426
|
const article = canvas.getByRole("article")
|
|
417
427
|
|
|
428
|
+
// CodeMirror can finish its syntax decorations after the story mounts.
|
|
429
|
+
// Wait for the measured span itself rather than capturing a stale null.
|
|
430
|
+
await waitFor(() => {
|
|
431
|
+
expect(
|
|
432
|
+
article.querySelector(".cm-md-code"),
|
|
433
|
+
).not.toBeNull()
|
|
434
|
+
})
|
|
435
|
+
|
|
418
436
|
const code = article.querySelector(".cm-md-code")
|
|
419
437
|
|
|
420
438
|
await expect(code).not.toBeNull()
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* and does not fail `vitest` — MDX resolves `of` at *runtime*, so a
|
|
7
7
|
* renamed story turns into `of={undefined}` and the docs page
|
|
8
8
|
* renders Storybook's "component failed to render" panel instead of
|
|
9
|
-
* the page. `
|
|
9
|
+
* the page. `pnpm smoke:storybook` catches it, which is good, but
|
|
10
10
|
* only after a full Storybook build; this catches it in under a
|
|
11
11
|
* second, which is the difference between noticing during the edit
|
|
12
12
|
* and noticing in CI.
|
package/src/sourceRules.test.ts
CHANGED
|
@@ -622,7 +622,8 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
622
622
|
// Chart shares portable SVG geometry with Preact and image consumers.
|
|
623
623
|
// TableViewport owns readable comparisons and full-window expansion.
|
|
624
624
|
// MetricCard shares prominent values and category-coloured icons through Card.
|
|
625
|
-
|
|
625
|
+
// ComparisonChart and TableColumns share compact comparisons and column controls.
|
|
626
|
+
expect(componentNames.length).toBe(77)
|
|
626
627
|
|
|
627
628
|
for (const name of componentNames) {
|
|
628
629
|
expect(barrel).toContain(`export { ${name} }`)
|