@canonical/storybook-addon-relay 0.29.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 +142 -0
- package/dist/esm/constants.js +6 -0
- package/dist/esm/constants.js.map +1 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib/index.js +2 -0
- package/dist/esm/lib/index.js.map +1 -0
- package/dist/esm/lib/types.js +2 -0
- package/dist/esm/lib/types.js.map +1 -0
- package/dist/esm/lib/withRelayEnvironment.js +72 -0
- package/dist/esm/lib/withRelayEnvironment.js.map +1 -0
- package/dist/esm/preset.js +20 -0
- package/dist/esm/preset.js.map +1 -0
- package/dist/esm/preview.js +11 -0
- package/dist/esm/preview.js.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 +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/lib/index.d.ts +3 -0
- package/dist/types/lib/index.d.ts.map +1 -0
- package/dist/types/lib/types.d.ts +31 -0
- package/dist/types/lib/types.d.ts.map +1 -0
- package/dist/types/lib/withRelayEnvironment.d.ts +14 -0
- package/dist/types/lib/withRelayEnvironment.d.ts.map +1 -0
- package/dist/types/preset.d.ts +11 -0
- package/dist/types/preset.d.ts.map +1 -0
- package/dist/types/preview.d.ts +9 -0
- package/dist/types/preview.d.ts.map +1 -0
- package/package.json +103 -0
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# @canonical/storybook-addon-relay
|
|
2
|
+
|
|
3
|
+
Mock Relay environment integration for Storybook. This addon lets React stories that use Relay hooks (`useLazyLoadQuery`, `useFragment`, `usePaginationFragment`, ...) render against a `relay-test-utils` mock environment, so components with GraphQL data requirements work in Storybook without a running GraphQL server.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add -D @canonical/storybook-addon-relay
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`react-relay` and `relay-runtime` (18.x) are peer dependencies — your app already has them if it uses Relay.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
Register the addon in your `.storybook/main.ts`:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
const config: StorybookConfig = {
|
|
19
|
+
addons: [
|
|
20
|
+
"@canonical/storybook-addon-relay",
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default config;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If your Storybook config is built with `@canonical/storybook-config`, pass it through `extraAddons`:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createConfig } from "@canonical/storybook-config";
|
|
31
|
+
|
|
32
|
+
export default createConfig("react", {
|
|
33
|
+
extraAddons: ["@canonical/storybook-addon-relay"],
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Registering the addon does two things:
|
|
38
|
+
|
|
39
|
+
- its `preview` entry adds the `withRelayEnvironment` decorator globally;
|
|
40
|
+
- its `preset` entry adds `relay-test-utils` (a CJS-only package) to Vite's `optimizeDeps.include`, so its named exports resolve in the browser ESM context.
|
|
41
|
+
|
|
42
|
+
## Usage in Stories
|
|
43
|
+
|
|
44
|
+
Declare a `relay` key in your story parameters. The decorator wraps the story in a `RelayEnvironmentProvider` carrying a fresh mock environment, and every GraphQL operation the story issues resolves automatically with data from `MockPayloadGenerator`:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
48
|
+
import type { RelayParameters } from "@canonical/storybook-addon-relay";
|
|
49
|
+
import { UserProfile } from "./UserProfile.js";
|
|
50
|
+
|
|
51
|
+
const meta: Meta<typeof UserProfile> = {
|
|
52
|
+
title: "Components/UserProfile",
|
|
53
|
+
component: UserProfile,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default meta;
|
|
57
|
+
type Story = StoryObj<typeof meta>;
|
|
58
|
+
|
|
59
|
+
export const Default: Story = {
|
|
60
|
+
parameters: {
|
|
61
|
+
relay: {
|
|
62
|
+
mockResolvers: {
|
|
63
|
+
User: () => ({
|
|
64
|
+
name: "Ada Lovelace",
|
|
65
|
+
email: "ada@example.com",
|
|
66
|
+
}),
|
|
67
|
+
},
|
|
68
|
+
} satisfies RelayParameters,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Stories without a `relay` parameter pass through unchanged — the decorator is a no-op for them.
|
|
74
|
+
|
|
75
|
+
### Full payload control
|
|
76
|
+
|
|
77
|
+
For error states or hand-crafted payloads, provide `generateFunction` — it replaces `MockPayloadGenerator.generate()` entirely:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
export const CustomPayload: Story = {
|
|
81
|
+
parameters: {
|
|
82
|
+
relay: {
|
|
83
|
+
generateFunction: (operation, mockResolvers) => ({
|
|
84
|
+
data: { user: { id: "1", name: "Custom Name" } },
|
|
85
|
+
}),
|
|
86
|
+
} satisfies RelayParameters,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## API
|
|
92
|
+
|
|
93
|
+
### `parameters.relay: RelayParameters`
|
|
94
|
+
|
|
95
|
+
| Property | Type | Required | Description |
|
|
96
|
+
|----------|------|----------|-------------|
|
|
97
|
+
| `mockResolvers` | `MockResolvers` | | Mock resolvers passed to `MockPayloadGenerator.generate()`. Keys are GraphQL type names, values are factories returning field values. Fields without a resolver receive deterministic placeholder values |
|
|
98
|
+
| `generateFunction` | `(operation, mockResolvers?) => GraphQLSingularResponse` | | Full override for payload generation. When provided, `MockPayloadGenerator.generate()` is not called |
|
|
99
|
+
|
|
100
|
+
### `withRelayEnvironment`
|
|
101
|
+
|
|
102
|
+
The decorator itself, exported from the package root for manual composition (e.g. applying it to a single story file instead of globally). It is registered globally by the addon's `preview` entry, so most consumers never import it.
|
|
103
|
+
|
|
104
|
+
## How operations resolve
|
|
105
|
+
|
|
106
|
+
React components issue their Relay requests while rendering — after the decorator has returned — so the decorator queues an operation resolver on the mock environment rather than resolving up front. `relay-test-utils` consumes a queued resolver after a single operation, so the resolver re-arms itself after each one: initial queries, refetches, pagination, and mutations (including those triggered from play functions) all resolve with the same story configuration, no matter how many operations the story issues.
|
|
107
|
+
|
|
108
|
+
The mock environment is created once per story mount. Re-renders (e.g. changing args via controls) keep the environment and its store; switching stories or pressing the remount toolbar button starts from a fresh environment.
|
|
109
|
+
|
|
110
|
+
The environment is not exposed to the story context: operations resolve automatically, so stories and play functions do not need to drive the mock network by hand, and the `parameters.relay` contract stays minimal.
|
|
111
|
+
|
|
112
|
+
## Peer Dependencies
|
|
113
|
+
|
|
114
|
+
| Package | Version |
|
|
115
|
+
|---------|---------|
|
|
116
|
+
| `react` | ^18.0.0 \|\| ^19.0.0 |
|
|
117
|
+
| `react-dom` | ^18.0.0 \|\| ^19.0.0 |
|
|
118
|
+
| `react-relay` | >=18.0.0 <19.0.0 |
|
|
119
|
+
| `relay-runtime` | >=18.0.0 <19.0.0 |
|
|
120
|
+
| `storybook` | ^10.3.1 |
|
|
121
|
+
|
|
122
|
+
## Troubleshooting
|
|
123
|
+
|
|
124
|
+
### `does not provide an export named 'createMockEnvironment'`
|
|
125
|
+
|
|
126
|
+
`relay-test-utils` and `relay-runtime` are CJS-only. The addon's `preset` adds `relay-test-utils` to `optimizeDeps.include` automatically — make sure the addon is registered in `.storybook/main.ts` (not only imported in `preview.ts`), otherwise the preset does not run.
|
|
127
|
+
|
|
128
|
+
### A refetch or mutation never resolves
|
|
129
|
+
|
|
130
|
+
Operations only resolve for stories that declare a `relay` parameter (even an empty object enables the mock environment):
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
export const Default: Story = {
|
|
134
|
+
parameters: {
|
|
135
|
+
relay: {},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Deterministic data
|
|
141
|
+
|
|
142
|
+
`MockPayloadGenerator` generates placeholder values for any field not covered by `mockResolvers`. If a story must render exact values (e.g. for visual regression tests), resolve every displayed field in `mockResolvers`, or use `generateFunction` for full control.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @canonical/storybook-addon-relay — renders React stories that use Relay
|
|
3
|
+
* hooks against a `relay-test-utils` mock environment, configured per story
|
|
4
|
+
* via `parameters.relay` (`mockResolvers`, `generateFunction`).
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
export * from "./lib/index.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
// `react-relay`, `relay-runtime`, and `relay-test-utils` are CJS packages
|
|
4
|
+
// with member-expression exports that Node's `cjs-module-lexer` cannot
|
|
5
|
+
// detect, so these named imports fail under bare Node ESM. That is fine for
|
|
6
|
+
// this package: a Storybook addon is consumed through Storybook + Vite, and
|
|
7
|
+
// the `./preset` `viteFinal` pre-bundles `relay-test-utils` so the names
|
|
8
|
+
// resolve in the preview iframe.
|
|
9
|
+
import { RelayEnvironmentProvider } from "react-relay";
|
|
10
|
+
import { createMockEnvironment, MockPayloadGenerator, } from "relay-test-utils";
|
|
11
|
+
import { PARAM_KEY } from "../constants.js";
|
|
12
|
+
/**
|
|
13
|
+
* Creates a mock Relay environment whose network resolves every operation
|
|
14
|
+
* with the payload described by the story's `parameters.relay`.
|
|
15
|
+
*
|
|
16
|
+
* React components issue their Relay requests while rendering (e.g.
|
|
17
|
+
* `useLazyLoadQuery` fetches when the hook runs), which happens only after
|
|
18
|
+
* the decorator has returned its element tree — so the decorator cannot call
|
|
19
|
+
* `resolveMostRecentOperation` up front; it must queue a resolver instead.
|
|
20
|
+
* `relay-test-utils` consumes a queued resolver after a single operation, so
|
|
21
|
+
* a resolver queued once would only cover the story's first query and leave
|
|
22
|
+
* refetches, pagination, and mutations (e.g. from play functions) pending
|
|
23
|
+
* forever. The queued resolver therefore re-arms itself after every
|
|
24
|
+
* operation, resolving however many operations the story issues.
|
|
25
|
+
*/
|
|
26
|
+
const createStoryEnvironment = (parameters) => {
|
|
27
|
+
const environment = createMockEnvironment();
|
|
28
|
+
const { mockResolvers, generateFunction } = parameters;
|
|
29
|
+
const resolveOperation = (operation) => generateFunction
|
|
30
|
+
? generateFunction(operation, mockResolvers)
|
|
31
|
+
: MockPayloadGenerator.generate(operation, mockResolvers);
|
|
32
|
+
const armOperationResolver = () => {
|
|
33
|
+
environment.mock.queueOperationResolver((operation) => {
|
|
34
|
+
armOperationResolver();
|
|
35
|
+
return resolveOperation(operation);
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
armOperationResolver();
|
|
39
|
+
return environment;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Internal wrapper component that owns the mock environment's lifecycle.
|
|
43
|
+
*
|
|
44
|
+
* The environment is created lazily on mount and kept for the lifetime of
|
|
45
|
+
* the mounted story: re-renders (e.g. Storybook controls changing args) keep
|
|
46
|
+
* the same environment and store, while remounts (story switch, the remount
|
|
47
|
+
* toolbar button) start from a fresh one. `parameters.relay` is read when the
|
|
48
|
+
* environment is created; Storybook parameters are static per story.
|
|
49
|
+
*/
|
|
50
|
+
const MockRelayEnvironmentProvider = ({ parameters, children, }) => {
|
|
51
|
+
const [environment] = useState(() => createStoryEnvironment(parameters));
|
|
52
|
+
return (_jsx(RelayEnvironmentProvider, { environment: environment, children: children }));
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Storybook decorator that lets React stories using Relay hooks
|
|
56
|
+
* (`useLazyLoadQuery`, `useFragment`, `usePaginationFragment`, ...) render
|
|
57
|
+
* against a mock Relay environment.
|
|
58
|
+
*
|
|
59
|
+
* Reads `parameters.relay` from the story context. If absent, the story
|
|
60
|
+
* passes through unchanged. If present, the story is wrapped in a
|
|
61
|
+
* `RelayEnvironmentProvider` carrying a `relay-test-utils` mock environment
|
|
62
|
+
* that automatically resolves every operation the story issues, using the
|
|
63
|
+
* story's `mockResolvers` and/or `generateFunction`.
|
|
64
|
+
*/
|
|
65
|
+
export const withRelayEnvironment = (storyFn, context) => {
|
|
66
|
+
const relayParameters = context.parameters?.[PARAM_KEY];
|
|
67
|
+
if (!relayParameters) {
|
|
68
|
+
return storyFn();
|
|
69
|
+
}
|
|
70
|
+
return (_jsx(MockRelayEnvironmentProvider, { parameters: relayParameters, children: storyFn() }));
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=withRelayEnvironment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withRelayEnvironment.js","sourceRoot":"","sources":["../../../src/lib/withRelayEnvironment.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAqC,QAAQ,EAAE,MAAM,OAAO,CAAC;AACpE,0EAA0E;AAC1E,uEAAuE;AACvE,4EAA4E;AAC5E,4EAA4E;AAC5E,yEAAyE;AACzE,iCAAiC;AACjC,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAKvD,OAAO,EACL,qBAAqB,EAErB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAM1B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C;;;;;;;;;;;;;GAaG;AACH,MAAM,sBAAsB,GAAG,CAC7B,UAA2B,EACV,EAAE;IACnB,MAAM,WAAW,GAAG,qBAAqB,EAAE,CAAC;IAC5C,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,GAAG,UAAU,CAAC;IAEvD,MAAM,gBAAgB,GAAG,CACvB,SAA8B,EACL,EAAE,CAC3B,gBAAgB;QACd,CAAC,CAAC,gBAAgB,CAAC,SAAS,EAAE,aAAa,CAAC;QAC5C,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAE9D,MAAM,oBAAoB,GAAG,GAAS,EAAE;QACtC,WAAW,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,SAAS,EAAE,EAAE;YACpD,oBAAoB,EAAE,CAAC;YACvB,OAAO,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,oBAAoB,EAAE,CAAC;IAEvB,OAAO,WAAW,CAAC;AACrB,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,4BAA4B,GAAG,CAAC,EACpC,UAAU,EACV,QAAQ,GAIT,EAAgB,EAAE;IACjB,MAAM,CAAC,WAAW,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAC,CAAC;IAEzE,OAAO,CACL,KAAC,wBAAwB,IAAC,WAAW,EAAE,WAAW,YAC/C,QAAQ,GACgB,CAC5B,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,OAAgC,EAChC,OAA+B,EACM,EAAE;IACvC,MAAM,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,SAAS,CAEzC,CAAC;IAEd,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO,OAAO,EAAE,CAAC;IACnB,CAAC;IAED,OAAO,CACL,KAAC,4BAA4B,IAAC,UAAU,EAAE,eAAe,YACtD,OAAO,EAAe,GACM,CACO,CAAC;AAC3C,CAAC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storybook addon preset — configures Vite so that `relay-test-utils`
|
|
3
|
+
* (a CJS-only package) is pre-bundled, allowing its named exports
|
|
4
|
+
* (`createMockEnvironment`, `MockPayloadGenerator`) to work in
|
|
5
|
+
* the browser ESM context.
|
|
6
|
+
*
|
|
7
|
+
* @note Named `viteFinal` per Storybook preset API convention — not a
|
|
8
|
+
* verb-first function name by design.
|
|
9
|
+
*/
|
|
10
|
+
export const viteFinal = (config) => {
|
|
11
|
+
const optimizeDeps = (config.optimizeDeps ?? {});
|
|
12
|
+
const include = (optimizeDeps.include ?? []);
|
|
13
|
+
if (!include.includes("relay-test-utils")) {
|
|
14
|
+
include.push("relay-test-utils");
|
|
15
|
+
}
|
|
16
|
+
optimizeDeps.include = include;
|
|
17
|
+
config.optimizeDeps = optimizeDeps;
|
|
18
|
+
return config;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=preset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preset.js","sourceRoot":"","sources":["../../src/preset.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,MAA+B,EACN,EAAE;IAC3B,MAAM,YAAY,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAA4B,CAAC;IAC5E,MAAM,OAAO,GAAG,CAAC,YAAY,CAAC,OAAO,IAAI,EAAE,CAAa,CAAC;IAEzD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACnC,CAAC;IAED,YAAY,CAAC,OAAO,GAAG,OAAO,CAAC;IAC/B,MAAM,CAAC,YAAY,GAAG,YAAY,CAAC;IAEnC,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { withRelayEnvironment } from "./lib/withRelayEnvironment.js";
|
|
2
|
+
/**
|
|
3
|
+
* Storybook preview annotations — registers the `withRelayEnvironment`
|
|
4
|
+
* decorator globally, so adding this addon to the Storybook config is the
|
|
5
|
+
* only setup consumers need.
|
|
6
|
+
*/
|
|
7
|
+
const preview = {
|
|
8
|
+
decorators: [withRelayEnvironment],
|
|
9
|
+
};
|
|
10
|
+
export default preview;
|
|
11
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/preview.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE;;;;GAIG;AACH,MAAM,OAAO,GAAiC;IAC5C,UAAU,EAAE,CAAC,oBAAoB,CAAC;CACnC,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,SAAS,UAAU,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @canonical/storybook-addon-relay — renders React stories that use Relay
|
|
3
|
+
* hooks against a `relay-test-utils` mock environment, configured per story
|
|
4
|
+
* via `parameters.relay` (`mockResolvers`, `generateFunction`).
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
export * from "./lib/index.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { GraphQLSingularResponse, OperationDescriptor } from "relay-runtime";
|
|
2
|
+
import type { MockResolvers } from "relay-test-utils";
|
|
3
|
+
/**
|
|
4
|
+
* Shape of the `relay` key in a story's Storybook `parameters`.
|
|
5
|
+
*
|
|
6
|
+
* When present, the `withRelayEnvironment` decorator wraps the story in a
|
|
7
|
+
* mock Relay environment and resolves every GraphQL operation the story
|
|
8
|
+
* issues (initial queries, refetches, pagination, mutations) with the
|
|
9
|
+
* configuration declared here. When absent, the decorator is a no-op.
|
|
10
|
+
*
|
|
11
|
+
* The created mock environment is deliberately not exposed back to the story
|
|
12
|
+
* context: operations resolve automatically through the queued resolver, so
|
|
13
|
+
* stories and play functions have no need to drive the mock network by hand.
|
|
14
|
+
* Keeping the environment private keeps the `parameters.relay` contract to
|
|
15
|
+
* the two options below.
|
|
16
|
+
*/
|
|
17
|
+
export interface RelayParameters<TResolvers extends MockResolvers = MockResolvers> {
|
|
18
|
+
/**
|
|
19
|
+
* Mock resolvers passed to `MockPayloadGenerator.generate()`. Keys are
|
|
20
|
+
* GraphQL type names, values are factories returning field values for that
|
|
21
|
+
* type. Fields without a resolver receive deterministic placeholder values.
|
|
22
|
+
*/
|
|
23
|
+
mockResolvers?: TResolvers;
|
|
24
|
+
/**
|
|
25
|
+
* Full override for payload generation. When provided, it is called instead
|
|
26
|
+
* of `MockPayloadGenerator.generate()` for every operation the story
|
|
27
|
+
* issues, receiving the operation and the configured `mockResolvers`.
|
|
28
|
+
*/
|
|
29
|
+
generateFunction?: (operation: OperationDescriptor, mockResolvers?: TResolvers | null) => GraphQLSingularResponse;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,mBAAmB,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe,CAC9B,UAAU,SAAS,aAAa,GAAG,aAAa;IAEhD;;;;OAIG;IACH,aAAa,CAAC,EAAE,UAAU,CAAC;IAE3B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CACjB,SAAS,EAAE,mBAAmB,EAC9B,aAAa,CAAC,EAAE,UAAU,GAAG,IAAI,KAC9B,uBAAuB,CAAC;CAC9B"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Renderer, StoryContext, PartialStoryFn as StoryFunction } from "storybook/internal/types";
|
|
2
|
+
/**
|
|
3
|
+
* Storybook decorator that lets React stories using Relay hooks
|
|
4
|
+
* (`useLazyLoadQuery`, `useFragment`, `usePaginationFragment`, ...) render
|
|
5
|
+
* against a mock Relay environment.
|
|
6
|
+
*
|
|
7
|
+
* Reads `parameters.relay` from the story context. If absent, the story
|
|
8
|
+
* passes through unchanged. If present, the story is wrapped in a
|
|
9
|
+
* `RelayEnvironmentProvider` carrying a `relay-test-utils` mock environment
|
|
10
|
+
* that automatically resolves every operation the story issues, using the
|
|
11
|
+
* story's `mockResolvers` and/or `generateFunction`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const withRelayEnvironment: (storyFn: StoryFunction<Renderer>, context: StoryContext<Renderer>) => ReturnType<StoryFunction<Renderer>>;
|
|
14
|
+
//# sourceMappingURL=withRelayEnvironment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withRelayEnvironment.d.ts","sourceRoot":"","sources":["../../../src/lib/withRelayEnvironment.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,cAAc,IAAI,aAAa,EAChC,MAAM,0BAA0B,CAAC;AAmElC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAS,aAAa,CAAC,QAAQ,CAAC,EAChC,SAAS,YAAY,CAAC,QAAQ,CAAC,KAC9B,UAAU,CAAC,aAAa,CAAC,QAAQ,CAAC,CAcpC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storybook addon preset — configures Vite so that `relay-test-utils`
|
|
3
|
+
* (a CJS-only package) is pre-bundled, allowing its named exports
|
|
4
|
+
* (`createMockEnvironment`, `MockPayloadGenerator`) to work in
|
|
5
|
+
* the browser ESM context.
|
|
6
|
+
*
|
|
7
|
+
* @note Named `viteFinal` per Storybook preset API convention — not a
|
|
8
|
+
* verb-first function name by design.
|
|
9
|
+
*/
|
|
10
|
+
export declare const viteFinal: (config: Record<string, unknown>) => Record<string, unknown>;
|
|
11
|
+
//# sourceMappingURL=preset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preset.d.ts","sourceRoot":"","sources":["../../src/preset.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GACpB,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAYxB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProjectAnnotations, Renderer } from "storybook/internal/types";
|
|
2
|
+
/**
|
|
3
|
+
* Storybook preview annotations — registers the `withRelayEnvironment`
|
|
4
|
+
* decorator globally, so adding this addon to the Storybook config is the
|
|
5
|
+
* only setup consumers need.
|
|
6
|
+
*/
|
|
7
|
+
declare const preview: ProjectAnnotations<Renderer>;
|
|
8
|
+
export default preview;
|
|
9
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAG7E;;;;GAIG;AACH,QAAA,MAAM,OAAO,EAAE,kBAAkB,CAAC,QAAQ,CAEzC,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@canonical/storybook-addon-relay",
|
|
3
|
+
"version": "0.29.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"module": "dist/esm/index.js",
|
|
9
|
+
"types": "dist/types/index.d.ts",
|
|
10
|
+
"author": {
|
|
11
|
+
"email": "webteam@canonical.com",
|
|
12
|
+
"name": "Canonical Webteam"
|
|
13
|
+
},
|
|
14
|
+
"description": "Renders React stories that use Relay hooks against a mock Relay environment",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"storybook-addons",
|
|
17
|
+
"relay",
|
|
18
|
+
"react-relay",
|
|
19
|
+
"graphql"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/canonical/pragma"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/canonical/pragma/issues"
|
|
27
|
+
},
|
|
28
|
+
"license": "LGPL-3.0",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/types/index.d.ts",
|
|
32
|
+
"default": "./dist/esm/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./preview": {
|
|
35
|
+
"types": "./dist/types/preview.d.ts",
|
|
36
|
+
"default": "./dist/esm/preview.js"
|
|
37
|
+
},
|
|
38
|
+
"./preset": {
|
|
39
|
+
"types": "./dist/types/preset.d.ts",
|
|
40
|
+
"default": "./dist/esm/preset.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "bun run build:package",
|
|
49
|
+
"build:all": "bun run build:package",
|
|
50
|
+
"build:package": "bun run build:package:tsc",
|
|
51
|
+
"build:package:tsc": "tsc -p tsconfig.build.json",
|
|
52
|
+
"check": "bun run check:biome && bun run check:ts && bun run check:webarchitect",
|
|
53
|
+
"check:fix": "bun run check:biome:fix && bun run check:ts",
|
|
54
|
+
"check:biome": "biome check",
|
|
55
|
+
"check:biome:fix": "biome check --write",
|
|
56
|
+
"check:ts": "tsc --noEmit",
|
|
57
|
+
"check:webarchitect": "webarchitect library",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"test:coverage": "vitest run --coverage"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"relay-test-utils": "^18.2.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@biomejs/biome": "2.4.9",
|
|
67
|
+
"@canonical/biome-config": "^0.29.0",
|
|
68
|
+
"@canonical/typescript-config-react": "^0.29.0",
|
|
69
|
+
"@canonical/webarchitect": "^0.29.0",
|
|
70
|
+
"@testing-library/react": "^16.3.2",
|
|
71
|
+
"@types/node": "^24.12.0",
|
|
72
|
+
"@types/react": "^19.2.14",
|
|
73
|
+
"@types/react-dom": "^19.2.3",
|
|
74
|
+
"@types/react-relay": "^18.2.1",
|
|
75
|
+
"@types/relay-runtime": "^19.0.3",
|
|
76
|
+
"@types/relay-test-utils": "^18.0.0",
|
|
77
|
+
"@vitejs/plugin-react": "^6.0.0",
|
|
78
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
79
|
+
"jsdom": "^28.1.0",
|
|
80
|
+
"react": "^19.2.4",
|
|
81
|
+
"react-dom": "^19.2.4",
|
|
82
|
+
"react-relay": "^18.2.0",
|
|
83
|
+
"relay-runtime": "^18.2.0",
|
|
84
|
+
"storybook": "^10.3.1",
|
|
85
|
+
"typescript": "^5.9.3",
|
|
86
|
+
"vite": "^8.0.1",
|
|
87
|
+
"vitest": "^4.0.18"
|
|
88
|
+
},
|
|
89
|
+
"peerDependencies": {
|
|
90
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
91
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
92
|
+
"react-relay": ">=18.0.0 <19.0.0",
|
|
93
|
+
"relay-runtime": ">=18.0.0 <19.0.0",
|
|
94
|
+
"storybook": "^10.3.1"
|
|
95
|
+
},
|
|
96
|
+
"storybook": {
|
|
97
|
+
"displayName": "Relay",
|
|
98
|
+
"supportedFrameworks": [
|
|
99
|
+
"react"
|
|
100
|
+
],
|
|
101
|
+
"icon": "https://relay.dev/img/relay.svg"
|
|
102
|
+
}
|
|
103
|
+
}
|