@fohte/storybook-addon 0.1.1 → 0.1.2

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 CHANGED
@@ -37,12 +37,14 @@ This wires up `overflow-check` and `external-resource-check` — no further setu
37
37
 
38
38
  ### overflow-check
39
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:
40
+ To exempt a selector across every story (e.g. a component whose oversized hit target never visibly clips anything), set `parameters.overflowCheck.globalIgnoreSelectors` once in `.storybook/preview.ts`. It's additive with a story's own `parameters.overflowCheck.ignoreSelectors` — both apply, since they're separate keys and Storybook deep-merges parameter objects by key (only arrays at the same key replace wholesale, so a story-level `ignoreSelectors` can't drop the global list):
41
41
 
42
42
  ```ts
43
- import { configureOverflowCheck } from '@fohte/storybook-addon/preview'
43
+ import type { Parameters } from 'storybook/internal/types'
44
44
 
45
- configureOverflowCheck({ ignoreSelectors: ['[data-slot="checkbox"]'] })
45
+ export const parameters: Parameters = {
46
+ overflowCheck: { globalIgnoreSelectors: ['[data-slot="checkbox"]'] },
47
+ }
46
48
  ```
47
49
 
48
50
  ### unhandled-api-request-check
@@ -1,6 +1,3 @@
1
1
  import type { StorybookCheck } from '#checks/check.js';
2
- export declare function configureOverflowCheck(options: {
3
- ignoreSelectors: string[];
4
- }): void;
5
2
  export declare const overflowCheck: StorybookCheck;
6
3
  //# sourceMappingURL=overflow-check.d.ts.map
@@ -83,16 +83,6 @@ function findOverflows(root, ignoreSelectors) {
83
83
  }
84
84
  return groupByAncestor(entries);
85
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
- }
96
86
  function overflowCheckParameters(storyParameters) {
97
87
  if (typeof storyParameters !== 'object' || storyParameters === null) {
98
88
  return undefined;
@@ -125,6 +115,25 @@ function ignoreSelectorsOf(overflowCheck) {
125
115
  return [];
126
116
  return ignoreSelectors.filter((s) => typeof s === 'string');
127
117
  }
118
+ // A separate key from `ignoreSelectors`, set once by the consuming app in
119
+ // its top-level `preview.ts` `parameters` export (not via a module-level
120
+ // setter — a story's own `parameters.overflowCheck` and the app's global one
121
+ // live in different Storybook parameter scopes, which Storybook deep-merges
122
+ // by object key, so a story setting `ignoreSelectors` can't drop this list).
123
+ // This also sidesteps bundlers that resolve this addon's module twice for
124
+ // one consumer (once from the app's own import, once via Storybook's addon
125
+ // loader) — `context.parameters` comes from Storybook itself, not from
126
+ // either module instance, so it's unaffected either way.
127
+ function globalIgnoreSelectorsOf(overflowCheck) {
128
+ if (overflowCheck === undefined)
129
+ return [];
130
+ if (!('globalIgnoreSelectors' in overflowCheck))
131
+ return [];
132
+ const { globalIgnoreSelectors } = overflowCheck;
133
+ if (!Array.isArray(globalIgnoreSelectors))
134
+ return [];
135
+ return globalIgnoreSelectors.filter((s) => typeof s === 'string');
136
+ }
128
137
  export const overflowCheck = {
129
138
  reset: () => { },
130
139
  assert: (storyParameters, canvasElement) => {
@@ -140,7 +149,7 @@ export const overflowCheck = {
140
149
  return;
141
150
  }
142
151
  throwIfNotEmpty(findOverflows(canvasElement, [
143
- ...globalIgnoreSelectors,
152
+ ...globalIgnoreSelectorsOf(params),
144
153
  ...ignoreSelectorsOf(params),
145
154
  ]), 'Story has element(s) overflowing their container (clipped and invisible)');
146
155
  },
@@ -1,7 +1,15 @@
1
1
  import type { StorybookCheck } from '#checks/check.js';
2
+ interface UnhandledApiRequestState {
3
+ pathPrefixes: string[];
4
+ unhandledApiRequestUrls: string[];
5
+ }
6
+ declare global {
7
+ var __fohteStorybookAddonUnhandledApiRequestState__: UnhandledApiRequestState | undefined;
8
+ }
2
9
  export declare function configureUnhandledApiRequestCheck(options: {
3
10
  pathPrefixes: string[];
4
11
  }): void;
5
12
  export declare function reportUnhandledApiRequest(url: string): boolean;
6
13
  export declare const unhandledApiRequestCheck: StorybookCheck;
14
+ export {};
7
15
  //# sourceMappingURL=unhandled-api-request-check.d.ts.map
@@ -1,15 +1,13 @@
1
1
  import { throwIfNotEmpty } from '#checks/check.js';
2
- // Populated by reportUnhandledApiRequest(), which the consuming app's MSW
3
- // `onUnhandledRequest` callback calls — a story hitting an API endpoint with
4
- // no MSW handler gets MSW's error response instead of real data, so the
5
- // screenshot captures a broken UI state without failing otherwise.
6
- const unhandledApiRequestUrls = [];
7
- // Which paths count as "API requests" is app-specific (tq uses `/api/`, a
8
- // future app might use a different prefix or none), so it's configured once
9
- // by the consuming app rather than hardcoded here.
10
- let pathPrefixes = [];
2
+ function globalState() {
3
+ globalThis.__fohteStorybookAddonUnhandledApiRequestState__ ??= {
4
+ pathPrefixes: [],
5
+ unhandledApiRequestUrls: [],
6
+ };
7
+ return globalThis.__fohteStorybookAddonUnhandledApiRequestState__;
8
+ }
11
9
  export function configureUnhandledApiRequestCheck(options) {
12
- pathPrefixes = options.pathPrefixes;
10
+ globalState().pathPrefixes = options.pathPrefixes;
13
11
  }
14
12
  // Returns whether the request was recorded, so the caller can decide whether
15
13
  // to also print MSW's own error for it.
@@ -17,18 +15,19 @@ export function reportUnhandledApiRequest(url) {
17
15
  const parsed = new URL(url);
18
16
  if (parsed.origin !== window.location.origin)
19
17
  return false;
20
- if (!pathPrefixes.some((prefix) => parsed.pathname.startsWith(prefix))) {
18
+ const state = globalState();
19
+ if (!state.pathPrefixes.some((prefix) => parsed.pathname.startsWith(prefix))) {
21
20
  return false;
22
21
  }
23
- unhandledApiRequestUrls.push(parsed.pathname);
22
+ state.unhandledApiRequestUrls.push(parsed.pathname);
24
23
  return true;
25
24
  }
26
25
  export const unhandledApiRequestCheck = {
27
26
  reset: () => {
28
- unhandledApiRequestUrls.length = 0;
27
+ globalState().unhandledApiRequestUrls.length = 0;
29
28
  },
30
29
  assert: () => {
31
- throwIfNotEmpty(unhandledApiRequestUrls, 'Story made unhandled API request(s); add an MSW handler for');
30
+ throwIfNotEmpty(globalState().unhandledApiRequestUrls, 'Story made unhandled API request(s); add an MSW handler for');
32
31
  },
33
32
  };
34
33
  //# sourceMappingURL=unhandled-api-request-check.js.map
package/lib/preview.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { AfterEach, BeforeEach, Parameters, WebRenderer } from 'storybook/internal/types';
2
- export { configureOverflowCheck } from '#checks/overflow-check.js';
3
2
  export { configureUnhandledApiRequestCheck, reportUnhandledApiRequest, } from '#checks/unhandled-api-request-check.js';
4
3
  export declare const parameters: Parameters;
5
4
  export declare const beforeEach: BeforeEach<WebRenderer>;
package/lib/preview.js CHANGED
@@ -1,7 +1,6 @@
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';
5
4
  export { configureUnhandledApiRequestCheck, reportUnhandledApiRequest, } from '#checks/unhandled-api-request-check.js';
6
5
  function injectStyle(css) {
7
6
  const style = document.createElement('style');
package/package.json CHANGED
@@ -51,7 +51,7 @@
51
51
  "typescript": "6.0.3",
52
52
  "vitest": "4.1.10"
53
53
  },
54
- "version": "0.1.1",
54
+ "version": "0.1.2",
55
55
  "scripts": {
56
56
  "clean": "rimraf lib tsconfig.tsbuildinfo",
57
57
  "prebuild": "pnpm run clean",