@canonical/storybook-addon-msw 0.9.0-experimental.21

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.
Files changed (38) hide show
  1. package/README.md +166 -0
  2. package/dist/esm/components/Panel.js +37 -0
  3. package/dist/esm/components/Panel.js.map +1 -0
  4. package/dist/esm/components/Tool.js +28 -0
  5. package/dist/esm/components/Tool.js.map +1 -0
  6. package/dist/esm/constants.js +6 -0
  7. package/dist/esm/constants.js.map +1 -0
  8. package/dist/esm/index.js +3 -0
  9. package/dist/esm/index.js.map +1 -0
  10. package/dist/esm/manager.js +23 -0
  11. package/dist/esm/manager.js.map +1 -0
  12. package/dist/esm/preset.js +16 -0
  13. package/dist/esm/preset.js.map +1 -0
  14. package/dist/esm/preview.js +14 -0
  15. package/dist/esm/preview.js.map +1 -0
  16. package/dist/esm/types.js +2 -0
  17. package/dist/esm/types.js.map +1 -0
  18. package/dist/esm/withMSW.js +64 -0
  19. package/dist/esm/withMSW.js.map +1 -0
  20. package/dist/types/components/Panel.d.ts +9 -0
  21. package/dist/types/components/Panel.d.ts.map +1 -0
  22. package/dist/types/components/Tool.d.ts +6 -0
  23. package/dist/types/components/Tool.d.ts.map +1 -0
  24. package/dist/types/constants.d.ts +6 -0
  25. package/dist/types/constants.d.ts.map +1 -0
  26. package/dist/types/index.d.ts +3 -0
  27. package/dist/types/index.d.ts.map +1 -0
  28. package/dist/types/manager.d.ts +2 -0
  29. package/dist/types/manager.d.ts.map +1 -0
  30. package/dist/types/preset.d.ts +3 -0
  31. package/dist/types/preset.d.ts.map +1 -0
  32. package/dist/types/preview.d.ts +18 -0
  33. package/dist/types/preview.d.ts.map +1 -0
  34. package/dist/types/types.d.ts +9 -0
  35. package/dist/types/types.d.ts.map +1 -0
  36. package/dist/types/withMSW.d.ts +3 -0
  37. package/dist/types/withMSW.d.ts.map +1 -0
  38. package/package.json +79 -0
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # Storybook Addon DS Baseline Grid
2
+
3
+ Displays the baseline grid overlay in the Storybook preview.
4
+
5
+ ## Usage
6
+
7
+ ### Installation
8
+
9
+ ```bash
10
+ npm install @canonical/storybook-addon-baseline-grid
11
+ ```
12
+
13
+ In your `.storybook/main.js` file, add the following:
14
+
15
+ ```js
16
+ module.exports = {
17
+ addons: ['@canonical/storybook-addon-baseline-grid'],
18
+ };
19
+ ```
20
+
21
+ Please note that this addon does rely on ESM only and does not have a cjs build at all. This means you need a version of node >= 20 and a modern browser to use it.
22
+
23
+ ## Local Testing Guide
24
+
25
+ ## Prerequisites
26
+
27
+ 1. First, ensure you have the MSW service worker file initialized:
28
+
29
+ ```bash
30
+ # Run this in your addon directory
31
+ npx msw init public/
32
+ ```
33
+
34
+ This creates `public/mockServiceWorker.js` which is necessary for MSW to intercept requests.
35
+
36
+ ## Building and Testing Locally
37
+
38
+ ### 1. Build the Addon
39
+
40
+ ```bash
41
+ # Build the addon
42
+ bun run build
43
+ ```
44
+
45
+ This will compile your TypeScript files to the `dist/` directory.
46
+
47
+ ### 2. Run Storybook with the Addon
48
+
49
+ ```bash
50
+ # Start Storybook
51
+ bun run storybook
52
+ ```
53
+
54
+ Your Storybook should now be running at `http://localhost:6007`.
55
+
56
+ ### 3. Verify the Addon is Working
57
+
58
+ 1. **Check the Toolbar**: You should see a lightning bolt icon in the Storybook toolbar. This toggles MSW on/off globally.
59
+
60
+ 2. **Check the Panel**: When viewing a story with MSW handlers, you should see an "MSW" tab in the addon panel (bottom of the screen). This shows information about active handlers.
61
+
62
+ 3. **Test the Stories**: Navigate to "Addon/MSW Test" in the sidebar. You should see various test scenarios:
63
+ - **Success Response**: Shows a successful API response
64
+ - **Error Response**: Demonstrates error handling
65
+ - **Delayed Response**: Shows loading states with a 2-second delay
66
+ - **Multiple Handlers**: Tests multiple MSW handlers
67
+ - **No Handlers**: Shows what happens when no handlers are defined
68
+ - **Disabled MSW**: Tests the disable functionality
69
+ - **Dynamic Response**: Interactive example with search functionality
70
+
71
+ ## Troubleshooting
72
+
73
+ ### "Failed to fetch" errors
74
+
75
+ If you see network errors, check:
76
+
77
+ 1. The MSW service worker is loaded (check DevTools > Application > Service Workers)
78
+ 2. The `mockServiceWorker.js` file exists in your `public/` directory
79
+ 3. MSW is enabled (lightning bolt icon should be active/blue)
80
+
81
+ ### Handlers not working
82
+
83
+ 1. Check the MSW panel to see if handlers are registered
84
+ 2. Ensure the request URLs match exactly (including leading slashes)
85
+ 3. Check the browser console for any MSW-related warnings
86
+
87
+ ### TypeScript errors
88
+
89
+ If you still see TypeScript errors after the fixes:
90
+
91
+ 1. Ensure all dependencies are installed: `bun install`
92
+ 2. Try cleaning and rebuilding: `rm -rf dist/ && bun run build`
93
+ 3. Restart your TypeScript language server in your editor
94
+
95
+ ## Understanding the Test Stories
96
+
97
+ Each test story demonstrates a different aspect of the MSW addon:
98
+
99
+ ### Success Response
100
+ This is the simplest case - it intercepts a GET request to `/api/user` and returns a successful JSON response. Use this to verify basic MSW functionality.
101
+
102
+ ### Error Response
103
+ Shows how to simulate API errors. The handler returns a 404 status with an error message. This is useful for testing error states in your components.
104
+
105
+ ### Delayed Response
106
+ Demonstrates async handlers with artificial delays. This is perfect for testing loading states and ensuring your UI handles slow networks gracefully.
107
+
108
+ ### Multiple Handlers
109
+ Shows that you can define multiple handlers for different endpoints. This mimics a real API with multiple routes.
110
+
111
+ ### Dynamic Response
112
+ The most complex example - it shows how MSW can read request parameters and return dynamic responses based on user input. The search functionality demonstrates real-world usage.
113
+
114
+ ## Next Steps
115
+
116
+ Once you've verified the addon works locally:
117
+
118
+ 1. **Add more test cases**: Create stories that test specific scenarios you need
119
+ 2. **Test with your components**: Import your actual components and test them with mocked APIs
120
+ 3. **Add to your main project**: Install the addon in your main Storybook project
121
+ 4. **Customize the Panel**: Enhance the panel to show more detailed handler information
122
+
123
+ ## Development Tips
124
+
125
+ ### Debugging MSW Handlers
126
+
127
+ Add console logs to your handlers to debug:
128
+
129
+ ```typescript
130
+ http.get("/api/test", ({ request }) => {
131
+ console.log("MSW: Intercepted request to", request.url);
132
+ return HttpResponse.json({ success: true });
133
+ })
134
+ ```
135
+
136
+ ### Testing POST/PUT/DELETE
137
+
138
+ Create interactive stories that trigger different HTTP methods:
139
+
140
+ ```typescript
141
+ http.post("/api/users", async ({ request }) => {
142
+ const body = await request.json();
143
+ console.log("MSW: Received POST data:", body);
144
+ return HttpResponse.json({ id: 1, ...body }, { status: 201 });
145
+ })
146
+ ```
147
+
148
+ ### Simulating Network Conditions
149
+
150
+ Test different scenarios:
151
+
152
+ ```typescript
153
+ // Random failures
154
+ http.get("/api/flaky", () => {
155
+ if (Math.random() > 0.5) {
156
+ return HttpResponse.json({ data: "Success!" });
157
+ }
158
+ return HttpResponse.error();
159
+ })
160
+
161
+ // Timeout simulation
162
+ http.get("/api/timeout", async () => {
163
+ await new Promise(resolve => setTimeout(resolve, 30000));
164
+ return HttpResponse.json({ data: "Too late!" });
165
+ })
166
+ ```
@@ -0,0 +1,37 @@
1
+ import React, { memo } from "react";
2
+ import { AddonPanel, SyntaxHighlighter } from "storybook/internal/components";
3
+ import { useParameter, useStorybookState, } from "storybook/internal/manager-api";
4
+ import { PARAM_KEY } from "../constants.js";
5
+ export const Panel = memo(function MswPanel({ api, active }) {
6
+ const state = useStorybookState();
7
+ const parameter = useParameter(PARAM_KEY);
8
+ if (!active || !parameter?.handlers) {
9
+ return null;
10
+ }
11
+ // Extract handler information for display
12
+ // MSW handlers have an info property that contains request, resolver, and options
13
+ const handlerInfo = parameter.handlers.map((handler, index) => {
14
+ // The handler info structure in MSW v2 is different
15
+ // We'll extract what we can from the handler
16
+ const info = handler.info;
17
+ // Try to extract meaningful information
18
+ // In MSW v2, handlers have a request property that might contain the predicate
19
+ return {
20
+ index,
21
+ // We'll use a generic description since MSW doesn't expose method/path directly
22
+ type: info?.header ? "HTTP Handler" : "Unknown Handler",
23
+ // You could potentially inspect the handler's toString() or other properties
24
+ description: `Handler ${index + 1}`,
25
+ };
26
+ });
27
+ return (React.createElement(AddonPanel, { active: active },
28
+ React.createElement("div", { style: { padding: "1rem" } },
29
+ React.createElement("h3", null, "Active MSW Handlers"),
30
+ React.createElement("p", { style: { marginBottom: "1rem", opacity: 0.7 } },
31
+ parameter.handlers.length,
32
+ " handler",
33
+ parameter.handlers.length !== 1 ? "s" : "",
34
+ " registered"),
35
+ React.createElement(SyntaxHighlighter, { language: "json" }, JSON.stringify(handlerInfo, null, 2)))));
36
+ });
37
+ //# sourceMappingURL=Panel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Panel.js","sourceRoot":"","sources":["../../../src/components/Panel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,EAEL,YAAY,EACZ,iBAAiB,GAClB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAQ5C,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,QAAQ,CAAC,EAAE,GAAG,EAAE,MAAM,EAAc;IACrE,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAC;IAClC,MAAM,SAAS,GAAG,YAAY,CAAe,SAAS,CAAC,CAAC;IAExD,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0CAA0C;IAC1C,kFAAkF;IAClF,MAAM,WAAW,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;QAC5D,oDAAoD;QACpD,6CAA6C;QAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAE1B,wCAAwC;QACxC,+EAA+E;QAC/E,OAAO;YACL,KAAK;YACL,gFAAgF;YAChF,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,iBAAiB;YACvD,6EAA6E;YAC7E,WAAW,EAAE,WAAW,KAAK,GAAG,CAAC,EAAE;SACpC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,CACL,oBAAC,UAAU,IAAC,MAAM,EAAE,MAAM;QACxB,6BAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;YAC7B,sDAA4B;YAC5B,2BAAG,KAAK,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE;gBAC7C,SAAS,CAAC,QAAQ,CAAC,MAAM;;gBACzB,SAAS,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;8BACzC;YACJ,oBAAC,iBAAiB,IAAC,QAAQ,EAAC,MAAM,IAC/B,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CACnB,CAChB,CACK,CACd,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { LightningIcon } from "@storybook/icons";
2
+ import React, { memo, useCallback } from "react";
3
+ import { IconButton } from "storybook/internal/components";
4
+ import { useGlobals } from "storybook/internal/manager-api";
5
+ import { KEY, TOOL_ID } from "../constants.js";
6
+ export const Tool = memo(function MyAddonSelector({ api }) {
7
+ const [globals, updateGlobals, storyGlobals] = useGlobals();
8
+ const isLocked = KEY in storyGlobals;
9
+ const isActive = !!globals[KEY];
10
+ const toggle = useCallback(() => {
11
+ updateGlobals({
12
+ [KEY]: !isActive,
13
+ });
14
+ }, [isActive, updateGlobals]);
15
+ // TODO: can be used to add keyboard shortcut
16
+ // useEffect(() => {
17
+ // api.setAddonShortcut(ADDON_ID, {
18
+ // label: "Toggle Baseline Grid",
19
+ // defaultShortcut: ["O"],
20
+ // actionName: "baseline",
21
+ // showInMenu: false,
22
+ // action: toggle,
23
+ // });
24
+ // }, [toggle, api]);
25
+ return (React.createElement(IconButton, { key: TOOL_ID, active: isActive, disabled: isLocked, title: isActive ? "MSW Active" : "MSW Inactive", onClick: toggle },
26
+ React.createElement(LightningIcon, null)));
27
+ });
28
+ //# sourceMappingURL=Tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tool.js","sourceRoot":"","sources":["../../../src/components/Tool.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAa,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAY,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,eAAe,CAAC,EAAE,GAAG,EAAgB;IACrE,MAAM,CAAC,OAAO,EAAE,aAAa,EAAE,YAAY,CAAC,GAAG,UAAU,EAAE,CAAC;IAE5D,MAAM,QAAQ,GAAG,GAAG,IAAI,YAAY,CAAC;IACrC,MAAM,QAAQ,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAEhC,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC9B,aAAa,CAAC;YACZ,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ;SACjB,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC;IAE9B,6CAA6C;IAC7C,oBAAoB;IACpB,qCAAqC;IACrC,qCAAqC;IACrC,8BAA8B;IAC9B,8BAA8B;IAC9B,yBAAyB;IACzB,sBAAsB;IACtB,QAAQ;IACR,qBAAqB;IAErB,OAAO,CACL,oBAAC,UAAU,IACT,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,QAAQ,EAChB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,EAC/C,OAAO,EAAE,MAAM;QAEf,oBAAC,aAAa,OAAG,CACN,CACd,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export const ADDON_ID = "ds-msw-addon";
2
+ export const TOOL_ID = `${ADDON_ID}/tool`;
3
+ export const PANEL_ID = `${ADDON_ID}/panel`;
4
+ export const PARAM_KEY = "msw";
5
+ export const KEY = `${ADDON_ID}`;
6
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG,cAAc,CAAC;AACvC,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,QAAQ,OAAO,CAAC;AAC1C,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,QAAQ,QAAQ,CAAC;AAC5C,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,CAAC;AAC/B,MAAM,CAAC,MAAM,GAAG,GAAG,GAAG,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ // make it work with --isolatedModules
2
+ export { http, HttpResponse } from "msw";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC"}
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import { addons, types } from "storybook/internal/manager-api";
3
+ import { Panel } from "./components/Panel.js";
4
+ import { Tool } from "./components/Tool.js";
5
+ import { ADDON_ID, PANEL_ID, TOOL_ID } from "./constants.js";
6
+ // Register the addon
7
+ addons.register(ADDON_ID, (api) => {
8
+ // Register a tool in the toolbar
9
+ addons.add(TOOL_ID, {
10
+ type: types.TOOL,
11
+ title: "Toggle MSW",
12
+ match: ({ viewMode, tabId }) => !!viewMode?.match(/^(story|docs)$/),
13
+ render: () => React.createElement(Tool, { api: api }),
14
+ });
15
+ // Register a panel to show active handlers
16
+ addons.add(PANEL_ID, {
17
+ type: types.PANEL,
18
+ title: "MSW",
19
+ match: ({ viewMode }) => viewMode === "story",
20
+ render: ({ active }) => React.createElement(Panel, { api: api, active: active }),
21
+ });
22
+ });
23
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/manager.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE7D,qBAAqB;AACrB,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE;IAChC,iCAAiC;IACjC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE;QAClB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,gBAAgB,CAAC;QACnE,MAAM,EAAE,GAAG,EAAE,CAAC,oBAAC,IAAI,IAAC,GAAG,EAAE,GAAG,GAAI;KACjC,CAAC,CAAC;IAEH,2CAA2C;IAC3C,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE;QACnB,IAAI,EAAE,KAAK,CAAC,KAAK;QACjB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,QAAQ,KAAK,OAAO;QAC7C,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,oBAAC,KAAK,IAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAI;KAC5D,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ // This entry is a node-specific file, contrary to the other files in this folder which are made to be loaded in the browser.
2
+ // You can use presets to augment the Storybook configuration
3
+ // You rarely want to do this in addons,
4
+ // so often you want to delete this file and remove the reference to it in package.json#exports and package.json#bunder.nodeEntries
5
+ // Read more about presets at https://storybook.js.org/docs/addons/writing-presets
6
+ // biome-ignore lint: untouched boilerplate
7
+ export const viteFinal = async (config) => {
8
+ console.log("This addon is augmenting the Vite config");
9
+ return config;
10
+ };
11
+ // biome-ignore lint: untouched boilerplate
12
+ export const webpack = async (config) => {
13
+ console.log("This addon is augmenting the Webpack config");
14
+ return config;
15
+ };
16
+ //# sourceMappingURL=preset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preset.js","sourceRoot":"","sources":["../../src/preset.ts"],"names":[],"mappings":"AAAA,6HAA6H;AAE7H,6DAA6D;AAC7D,wCAAwC;AACxC,mIAAmI;AACnI,kFAAkF;AAElF,2CAA2C;AAC3C,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,MAAW,EAAE,EAAE;IAC7C,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;IACxD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,2CAA2C;AAC3C,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,EAAE,MAAW,EAAE,EAAE;IAC3C,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAC3D,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { KEY } from "./constants.js";
2
+ import { withMSW } from "./withMSW.js";
3
+ /**
4
+ * Note: if you want to use JSX in this file, rename it to `preview.tsx`
5
+ * and update the entry prop in tsup.config.ts to use "src/preview.tsx",
6
+ */
7
+ const preview = {
8
+ decorators: [withMSW],
9
+ initialGlobals: {
10
+ [KEY]: true,
11
+ },
12
+ };
13
+ export default preview;
14
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/preview.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC;;;GAGG;AAEH,MAAM,OAAO,GAAiC;IAC5C,UAAU,EAAE,CAAC,OAAO,CAAC;IACrB,cAAc,EAAE;QACd,CAAC,GAAG,CAAC,EAAE,IAAI;KACZ;CACF,CAAC;AAEF,eAAe,OAAO,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import { setupWorker } from "msw/browser";
2
+ // src/withMSW.ts
3
+ import React from "react";
4
+ import { useEffect, useGlobals, useParameter, useState, } from "storybook/internal/preview-api";
5
+ import { KEY, PARAM_KEY } from "./constants.js";
6
+ // Singleton worker instance
7
+ let worker = null;
8
+ let workerPromise = null;
9
+ const initializeWorker = async () => {
10
+ if (!worker) {
11
+ worker = setupWorker();
12
+ await worker.start({
13
+ serviceWorker: { url: "/mockServiceWorker.js" },
14
+ onUnhandledRequest: "bypass",
15
+ });
16
+ console.log("Worker initialized and started");
17
+ workerPromise = Promise.resolve(worker);
18
+ }
19
+ return workerPromise;
20
+ };
21
+ export const withMSW = (StoryFn) => {
22
+ const [isWorkerReady, setIsWorkerReady] = useState(false);
23
+ const [globals] = useGlobals();
24
+ const mswGlobals = globals[KEY];
25
+ const mswParameter = useParameter(PARAM_KEY);
26
+ const isEnabled = mswGlobals?.enabled ?? true;
27
+ const isDisabled = mswParameter?.disable ?? false;
28
+ const handlers = mswParameter?.handlers ?? [];
29
+ useEffect(() => {
30
+ console.log("useEffect running", { isEnabled, isDisabled, handlers });
31
+ if (!isEnabled || isDisabled) {
32
+ setIsWorkerReady(true); // Skip MSW setup and render immediately
33
+ return;
34
+ }
35
+ const setupHandlers = async () => {
36
+ console.log("Setting up handlers");
37
+ const activeWorker = await initializeWorker();
38
+ if (activeWorker && handlers.length > 0) {
39
+ activeWorker.resetHandlers();
40
+ activeWorker.use(...handlers);
41
+ console.log("Handlers applied", handlers);
42
+ }
43
+ else if (activeWorker) {
44
+ activeWorker.resetHandlers();
45
+ console.log("Handlers reset");
46
+ }
47
+ setIsWorkerReady(true); // Signal that setup is complete
48
+ };
49
+ setupHandlers();
50
+ return () => {
51
+ if (worker) {
52
+ worker.resetHandlers();
53
+ console.log("Cleanup: handlers reset");
54
+ }
55
+ };
56
+ }, [isEnabled, isDisabled, handlers]);
57
+ // Delay rendering until the worker is ready
58
+ if (!isWorkerReady) {
59
+ console.log("Waiting for MSW worker...");
60
+ return React.createElement("div", {}, "Loading MSW...");
61
+ }
62
+ return StoryFn();
63
+ };
64
+ //# sourceMappingURL=withMSW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withMSW.js","sourceRoot":"","sources":["../../src/withMSW.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5D,iBAAiB;AACjB,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EACL,SAAS,EACT,UAAU,EACV,YAAY,EACZ,QAAQ,GACT,MAAM,gCAAgC,CAAC;AAKxC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,4BAA4B;AAC5B,IAAI,MAAM,GAAuB,IAAI,CAAC;AACtC,IAAI,aAAa,GAAgC,IAAI,CAAC;AAEtD,MAAM,gBAAgB,GAAG,KAAK,IAA0B,EAAE;IACxD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,WAAW,EAAE,CAAC;QACvB,MAAM,MAAM,CAAC,KAAK,CAAC;YACjB,aAAa,EAAE,EAAE,GAAG,EAAE,uBAAuB,EAAE;YAC/C,kBAAkB,EAAE,QAAQ;SAC7B,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC9C,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,aAAqC,CAAC;AAC/C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,OAAgC,EAAE,EAAE;IAC1D,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC1D,MAAM,CAAC,OAAO,CAAC,GAAG,UAAU,EAAE,CAAC;IAC/B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAA2B,CAAC;IAC1D,MAAM,YAAY,GAAG,YAAY,CAAe,SAAS,CAAC,CAAC;IAE3D,MAAM,SAAS,GAAG,UAAU,EAAE,OAAO,IAAI,IAAI,CAAC;IAC9C,MAAM,UAAU,GAAG,YAAY,EAAE,OAAO,IAAI,KAAK,CAAC;IAClD,MAAM,QAAQ,GAAG,YAAY,EAAE,QAAQ,IAAI,EAAE,CAAC;IAE9C,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,SAAS,IAAI,UAAU,EAAE,CAAC;YAC7B,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,wCAAwC;YAChE,OAAO;QACT,CAAC;QAED,MAAM,aAAa,GAAG,KAAK,IAAI,EAAE;YAC/B,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;YACnC,MAAM,YAAY,GAAG,MAAM,gBAAgB,EAAE,CAAC;YAC9C,IAAI,YAAY,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxC,YAAY,CAAC,aAAa,EAAE,CAAC;gBAC7B,YAAY,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;YAC5C,CAAC;iBAAM,IAAI,YAAY,EAAE,CAAC;gBACxB,YAAY,CAAC,aAAa,EAAE,CAAC;gBAC7B,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAChC,CAAC;YACD,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,gCAAgC;QAC1D,CAAC,CAAC;QAEF,aAAa,EAAE,CAAC;QAChB,OAAO,GAAG,EAAE;YACV,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,CAAC,aAAa,EAAE,CAAC;gBACvB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;YACzC,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEtC,4CAA4C;IAC5C,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QACzC,OAAO,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,OAAO,EAAE,CAAC;AACnB,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { type API } from "storybook/internal/manager-api";
3
+ interface PanelProps {
4
+ api: API;
5
+ active?: boolean;
6
+ }
7
+ export declare const Panel: React.NamedExoticComponent<PanelProps>;
8
+ export {};
9
+ //# sourceMappingURL=Panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Panel.d.ts","sourceRoot":"","sources":["../../../src/components/Panel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AAEpC,OAAO,EACL,KAAK,GAAG,EAGT,MAAM,gCAAgC,CAAC;AAIxC,UAAU,UAAU;IAClB,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,KAAK,wCAwChB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ import { type API } from "storybook/internal/manager-api";
3
+ export declare const Tool: React.NamedExoticComponent<{
4
+ api: API;
5
+ }>;
6
+ //# sourceMappingURL=Tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tool.d.ts","sourceRoot":"","sources":["../../../src/components/Tool.tsx"],"names":[],"mappings":"AACA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAE5D,OAAO,EAAE,KAAK,GAAG,EAAc,MAAM,gCAAgC,CAAC;AAGtE,eAAO,MAAM,IAAI;SAAiD,GAAG;EAkCnE,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare const ADDON_ID = "ds-msw-addon";
2
+ export declare const TOOL_ID = "ds-msw-addon/tool";
3
+ export declare const PANEL_ID = "ds-msw-addon/panel";
4
+ export declare const PARAM_KEY = "msw";
5
+ export declare const KEY = "ds-msw-addon";
6
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,iBAAiB,CAAC;AACvC,eAAO,MAAM,OAAO,sBAAqB,CAAC;AAC1C,eAAO,MAAM,QAAQ,uBAAsB,CAAC;AAC5C,eAAO,MAAM,SAAS,QAAQ,CAAC;AAC/B,eAAO,MAAM,GAAG,iBAAgB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { http, HttpResponse } from "msw";
2
+ export type { MswParameter } from "./types.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AACzC,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/manager.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ export declare const viteFinal: (config: any) => Promise<any>;
2
+ export declare const webpack: (config: any) => Promise<any>;
3
+ //# sourceMappingURL=preset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preset.d.ts","sourceRoot":"","sources":["../../src/preset.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,SAAS,GAAU,QAAQ,GAAG,iBAG1C,CAAC;AAGF,eAAO,MAAM,OAAO,GAAU,QAAQ,GAAG,iBAGxC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * A decorator is a way to wrap a story in extra “rendering” functionality. Many addons define decorators
3
+ * in order to augment stories:
4
+ * - with extra rendering
5
+ * - gather details about how a story is rendered
6
+ *
7
+ * When writing stories, decorators are typically used to wrap stories with extra markup or context mocking.
8
+ *
9
+ * https://storybook.js.org/docs/react/writing-stories/decorators
10
+ */
11
+ import type { ProjectAnnotations, Renderer } from "storybook/internal/types";
12
+ /**
13
+ * Note: if you want to use JSX in this file, rename it to `preview.tsx`
14
+ * and update the entry prop in tsup.config.ts to use "src/preview.tsx",
15
+ */
16
+ declare const preview: ProjectAnnotations<Renderer>;
17
+ export default preview;
18
+ //# sourceMappingURL=preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/preview.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAK7E;;;GAGG;AAEH,QAAA,MAAM,OAAO,EAAE,kBAAkB,CAAC,QAAQ,CAKzC,CAAC;AAEF,eAAe,OAAO,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { RequestHandler } from "msw";
2
+ export interface MswParameter {
3
+ handlers?: RequestHandler[];
4
+ disable?: boolean;
5
+ }
6
+ export interface MswGlobals {
7
+ enabled: boolean;
8
+ }
9
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAE1C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAE5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;CAElB"}
@@ -0,0 +1,3 @@
1
+ import type { Renderer, PartialStoryFn as StoryFunction } from "storybook/internal/types";
2
+ export declare const withMSW: (StoryFn: StoryFunction<Renderer>) => any;
3
+ //# sourceMappingURL=withMSW.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withMSW.d.ts","sourceRoot":"","sources":["../../src/withMSW.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,IAAI,aAAa,EAChC,MAAM,0BAA0B,CAAC;AAqBlC,eAAO,MAAM,OAAO,GAAI,SAAS,aAAa,CAAC,QAAQ,CAAC,QA+CvD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@canonical/storybook-addon-msw",
3
+ "version": "0.9.0-experimental.21",
4
+ "type": "module",
5
+ "files": ["dist"],
6
+ "types": "dist/types/index.d.ts",
7
+ "author": {
8
+ "email": "webteam@canonical.com",
9
+ "name": "Canonical Webteam"
10
+ },
11
+ "description": "Enhances stories with back-end testing",
12
+ "keywords": ["storybook-addons", "storybook-addons"],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/canonical/ds25"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/canonical/ds25/issues"
19
+ },
20
+ "license": "LGPL-3.0",
21
+ "exports": {
22
+ ".": "./dist/esm/index.js",
23
+ "./preview": "./dist/esm/preview.js",
24
+ "./preset": "./dist/esm/preset.js",
25
+ "./manager": "./dist/esm/manager.js",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "scripts": {
29
+ "build": "bun run build:package",
30
+ "build:package": "bun run build:package:tsc",
31
+ "build:package:tsc": "tsc -p tsconfig.build.json",
32
+ "storybook": "storybook dev -p 6007 --no-open --host 0.0.0.0",
33
+ "check": "bun run check:biome && bun run check:ts",
34
+ "check:fix": "bun run check:biome:fix && bun run check:ts",
35
+ "check:biome": "biome check",
36
+ "check:biome:fix": "biome check --write",
37
+ "check:ts": "tsc --noEmit"
38
+ },
39
+ "dependencies": {
40
+ "@canonical/styles-debug": "^0.9.0-experimental.19",
41
+ "@storybook/icons": "^1.2.10"
42
+ },
43
+ "devDependencies": {
44
+ "@biomejs/biome": "^1.9.4",
45
+ "@canonical/biome-config": "^0.9.0-experimental.12",
46
+ "@canonical/typescript-config-react": "^0.9.0-experimental.21",
47
+ "@types/node": "^24.0.0",
48
+ "@types/react": "^19.1.8",
49
+ "@types/react-dom": "^19.1.6",
50
+ "@vitejs/plugin-react": "^4.5.2",
51
+ "msw": "^2.10.2",
52
+ "react": "^19.1.0",
53
+ "react-dom": "^19.1.0",
54
+ "storybook": "^9.0.8",
55
+ "typescript": "^5.8.3",
56
+ "vite": "^6.3.5"
57
+ },
58
+ "peerDependencies": {
59
+ "storybook": "^9.0.8"
60
+ },
61
+ "storybook": {
62
+ "displayName": "DS Baseline Grid",
63
+ "supportedFrameworks": [
64
+ "react",
65
+ "vue",
66
+ "angular",
67
+ "web-components",
68
+ "ember",
69
+ "html",
70
+ "svelte",
71
+ "preact",
72
+ "react-native"
73
+ ],
74
+ "icon": "https://user-images.githubusercontent.com/321738/63501763-88dbf600-c4cc-11e9-96cd-94adadc2fd72.png"
75
+ },
76
+ "msw": {
77
+ "workerDirectory": ["public"]
78
+ }
79
+ }