@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.
- package/README.md +166 -0
- package/dist/esm/components/Panel.js +37 -0
- package/dist/esm/components/Panel.js.map +1 -0
- package/dist/esm/components/Tool.js +28 -0
- package/dist/esm/components/Tool.js.map +1 -0
- package/dist/esm/constants.js +6 -0
- package/dist/esm/constants.js.map +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/manager.js +23 -0
- package/dist/esm/manager.js.map +1 -0
- package/dist/esm/preset.js +16 -0
- package/dist/esm/preset.js.map +1 -0
- package/dist/esm/preview.js +14 -0
- package/dist/esm/preview.js.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/withMSW.js +64 -0
- package/dist/esm/withMSW.js.map +1 -0
- package/dist/types/components/Panel.d.ts +9 -0
- package/dist/types/components/Panel.d.ts.map +1 -0
- package/dist/types/components/Tool.d.ts +6 -0
- package/dist/types/components/Tool.d.ts.map +1 -0
- package/dist/types/constants.d.ts +6 -0
- package/dist/types/constants.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/manager.d.ts +2 -0
- package/dist/types/manager.d.ts.map +1 -0
- package/dist/types/preset.d.ts +3 -0
- package/dist/types/preset.d.ts.map +1 -0
- package/dist/types/preview.d.ts +18 -0
- package/dist/types/preview.d.ts.map +1 -0
- package/dist/types/types.d.ts +9 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/withMSW.d.ts +3 -0
- package/dist/types/withMSW.d.ts.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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":"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"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/manager.tsx"],"names":[],"mappings":""}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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
|
+
}
|