@fohte/storybook-addon 0.1.0 → 0.1.1
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/lib/checks/check.d.ts +1 -1
- package/lib/checks/overflow-check.d.ts +3 -0
- package/lib/checks/overflow-check.js +22 -36
- package/lib/preview.d.ts +1 -0
- package/lib/preview.js +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,16 @@ export default config
|
|
|
35
35
|
|
|
36
36
|
This wires up `overflow-check` and `external-resource-check` — no further setup needed.
|
|
37
37
|
|
|
38
|
+
### overflow-check
|
|
39
|
+
|
|
40
|
+
To exempt a selector across every story (e.g. a component whose oversized hit target never visibly clips anything), call `configureOverflowCheck()` once in `.storybook/preview.ts`. It's additive with a story's own `parameters.overflowCheck.ignoreSelectors` — both apply, since Storybook merges story-level parameters by replacing arrays rather than deep-merging them, and a story-level `ignoreSelectors` would otherwise silently drop the global list:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { configureOverflowCheck } from '@fohte/storybook-addon/preview'
|
|
44
|
+
|
|
45
|
+
configureOverflowCheck({ ignoreSelectors: ['[data-slot="checkbox"]'] })
|
|
46
|
+
```
|
|
47
|
+
|
|
38
48
|
### unhandled-api-request-check
|
|
39
49
|
|
|
40
50
|
This check needs to know which paths count as "API requests" (that's app-specific), and needs to be fed unhandled requests from your MSW setup. Configure it once and call `reportUnhandledApiRequest()` from `onUnhandledRequest` in `.storybook/preview.ts`:
|
package/lib/checks/check.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type StorybookCheck = {
|
|
2
2
|
reset: () => void;
|
|
3
|
-
assert: (storyParameters?: unknown) => void;
|
|
3
|
+
assert: (storyParameters?: unknown, canvasElement?: Element) => void;
|
|
4
4
|
};
|
|
5
5
|
export declare function throwIfNotEmpty(urls: string[], message: string): void;
|
|
6
6
|
//# sourceMappingURL=check.d.ts.map
|
|
@@ -1,32 +1,4 @@
|
|
|
1
1
|
import { throwIfNotEmpty } from '#checks/check.js';
|
|
2
|
-
// The consuming app's preview.ts calls reset()/assert() around each story
|
|
3
|
-
// render (see preview.ts's beforeEach/afterEach). The story's container is a
|
|
4
|
-
// bare <div> appended to document.body before anything is mounted into it —
|
|
5
|
-
// reset() never gets `context.canvasElement` itself, so this is the only way
|
|
6
|
-
// to reach the same element. Portal-based components (dialogs, popovers,
|
|
7
|
-
// selects, etc.) append their own <div>s directly to document.body too, but
|
|
8
|
-
// only *after* the story has mounted — so the container is reliably the
|
|
9
|
-
// *first* element appended to body once a story starts, not the last. A
|
|
10
|
-
// MutationObserver set up in reset() (which runs before the story mounts)
|
|
11
|
-
// captures it.
|
|
12
|
-
let storyRoot = null;
|
|
13
|
-
let bodyObserver = null;
|
|
14
|
-
function watchStoryRoot() {
|
|
15
|
-
storyRoot = null;
|
|
16
|
-
bodyObserver?.disconnect();
|
|
17
|
-
bodyObserver = new MutationObserver((mutations) => {
|
|
18
|
-
for (const mutation of mutations) {
|
|
19
|
-
for (const node of mutation.addedNodes) {
|
|
20
|
-
if (node instanceof Element) {
|
|
21
|
-
storyRoot = node;
|
|
22
|
-
bodyObserver?.disconnect();
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
bodyObserver.observe(document.body, { childList: true });
|
|
29
|
-
}
|
|
30
2
|
function describeElement(el) {
|
|
31
3
|
const id = el.id ? `#${el.id}` : '';
|
|
32
4
|
const classAttr = el.getAttribute('class');
|
|
@@ -111,6 +83,16 @@ function findOverflows(root, ignoreSelectors) {
|
|
|
111
83
|
}
|
|
112
84
|
return groupByAncestor(entries);
|
|
113
85
|
}
|
|
86
|
+
// Exempted everywhere, not per-story: configured once by the consuming app
|
|
87
|
+
// for markup that recurs across many stories (e.g. a component whose
|
|
88
|
+
// oversized hit target never visibly clips anything). Kept separate from
|
|
89
|
+
// parameters.overflowCheck.ignoreSelectors — which storybook's
|
|
90
|
+
// combineParameters replaces wholesale rather than merges — so a story
|
|
91
|
+
// setting its own ignoreSelectors can't silently drop this list.
|
|
92
|
+
let globalIgnoreSelectors = [];
|
|
93
|
+
export function configureOverflowCheck(options) {
|
|
94
|
+
globalIgnoreSelectors = options.ignoreSelectors;
|
|
95
|
+
}
|
|
114
96
|
function overflowCheckParameters(storyParameters) {
|
|
115
97
|
if (typeof storyParameters !== 'object' || storyParameters === null) {
|
|
116
98
|
return undefined;
|
|
@@ -144,19 +126,23 @@ function ignoreSelectorsOf(overflowCheck) {
|
|
|
144
126
|
return ignoreSelectors.filter((s) => typeof s === 'string');
|
|
145
127
|
}
|
|
146
128
|
export const overflowCheck = {
|
|
147
|
-
reset:
|
|
148
|
-
assert: (storyParameters) => {
|
|
129
|
+
reset: () => { },
|
|
130
|
+
assert: (storyParameters, canvasElement) => {
|
|
149
131
|
const params = overflowCheckParameters(storyParameters);
|
|
150
132
|
if (isDisabled(params))
|
|
151
133
|
return;
|
|
152
|
-
if (
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
// than fail, since there is no root
|
|
156
|
-
|
|
134
|
+
if (canvasElement == null) {
|
|
135
|
+
// Real Storybook runs always pass canvasElement (it's the story's
|
|
136
|
+
// mount container); this only triggers a caller invoking assert()
|
|
137
|
+
// directly without one. Skip rather than fail, since there is no root
|
|
138
|
+
// to scan.
|
|
139
|
+
console.warn('[overflow-check] no canvasElement in story context; skipping overflow scan');
|
|
157
140
|
return;
|
|
158
141
|
}
|
|
159
|
-
throwIfNotEmpty(findOverflows(
|
|
142
|
+
throwIfNotEmpty(findOverflows(canvasElement, [
|
|
143
|
+
...globalIgnoreSelectors,
|
|
144
|
+
...ignoreSelectorsOf(params),
|
|
145
|
+
]), 'Story has element(s) overflowing their container (clipped and invisible)');
|
|
160
146
|
},
|
|
161
147
|
};
|
|
162
148
|
//# sourceMappingURL=overflow-check.js.map
|
package/lib/preview.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AfterEach, BeforeEach, Parameters, WebRenderer } from 'storybook/internal/types';
|
|
2
|
+
export { configureOverflowCheck } from '#checks/overflow-check.js';
|
|
2
3
|
export { configureUnhandledApiRequestCheck, reportUnhandledApiRequest, } from '#checks/unhandled-api-request-check.js';
|
|
3
4
|
export declare const parameters: Parameters;
|
|
4
5
|
export declare const beforeEach: BeforeEach<WebRenderer>;
|
package/lib/preview.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { externalResourceCheck } from '#checks/external-resource-check.js';
|
|
2
2
|
import { overflowCheck } from '#checks/overflow-check.js';
|
|
3
3
|
import { unhandledApiRequestCheck } from '#checks/unhandled-api-request-check.js';
|
|
4
|
+
export { configureOverflowCheck } from '#checks/overflow-check.js';
|
|
4
5
|
export { configureUnhandledApiRequestCheck, reportUnhandledApiRequest, } from '#checks/unhandled-api-request-check.js';
|
|
5
6
|
function injectStyle(css) {
|
|
6
7
|
const style = document.createElement('style');
|
|
@@ -37,7 +38,8 @@ export const beforeEach = () => {
|
|
|
37
38
|
check.reset();
|
|
38
39
|
};
|
|
39
40
|
export const afterEach = (context) => {
|
|
40
|
-
for (const check of checks)
|
|
41
|
-
check.assert(context.parameters);
|
|
41
|
+
for (const check of checks) {
|
|
42
|
+
check.assert(context.parameters, context.canvasElement);
|
|
43
|
+
}
|
|
42
44
|
};
|
|
43
45
|
//# sourceMappingURL=preview.js.map
|