Skip to content

Commit 51c5f66

Browse files
committed
feat: add MSW integration for API mocking and define data protocol with sample handlers
1 parent 88ab8b0 commit 51c5f66

6 files changed

Lines changed: 171 additions & 0 deletions

File tree

.storybook/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import path from 'path';
44

55
const config: StorybookConfig = {
66
stories: ["../packages/**/src/**/*.mdx", "../packages/**/src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
7+
staticDirs: ['../public'],
78
addons: [
89
"@storybook/addon-links",
910
"@storybook/addon-essentials",

.storybook/mocks.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// .storybook/mocks.ts
2+
import { http, HttpResponse } from 'msw';
3+
import { ObjectStackServer } from '@objectstack/plugin-msw';
4+
5+
export const protocol = {
6+
objects: [
7+
{
8+
name: "contact",
9+
fields: {
10+
name: { type: "text" },
11+
email: { type: "email" },
12+
title: { type: "text" },
13+
company: { type: "text" },
14+
status: { type: "select", options: ["Active", "Lead", "Customer"] }
15+
}
16+
},
17+
{
18+
name: "opportunity",
19+
fields: {
20+
name: { type: "text" },
21+
amount: { type: "currency" },
22+
stage: { type: "select" },
23+
closeDate: { type: "date" },
24+
accountId: { type: "lookup", reference_to: "account" }
25+
}
26+
},
27+
{
28+
name: "account",
29+
fields: {
30+
name: { type: "text" },
31+
industry: { type: "text" }
32+
}
33+
}
34+
]
35+
};
36+
37+
// Initialize the mock server
38+
// @ts-ignore
39+
ObjectStackServer.init(protocol);
40+
41+
// Seed basic data
42+
(async () => {
43+
await ObjectStackServer.createData('contact', { id: '1', name: 'John Doe', title: 'Developer', company: 'Tech', status: 'Active' });
44+
await ObjectStackServer.createData('contact', { id: '2', name: 'Jane Smith', title: 'Manager', company: 'Corp', status: 'Customer' });
45+
await ObjectStackServer.createData('account', { id: '1', name: 'Big Corp', industry: 'Finance' });
46+
await ObjectStackServer.createData('opportunity', { id: '1', name: 'Big Deal', amount: 50000, stage: 'Negotiation', accountId: '1' });
47+
})();
48+
49+
export const handlers = [
50+
// Standard CRUD handlers using ObjectStackServer
51+
http.get('/api/v1/data/:object', async ({ params }) => {
52+
const { object } = params;
53+
const result = await ObjectStackServer.findData(object as string);
54+
return HttpResponse.json({ value: result.data });
55+
}),
56+
57+
http.get('/api/v1/data/:object/:id', async ({ params }) => {
58+
const { object, id } = params;
59+
const result = await ObjectStackServer.getData(object as string, id as string);
60+
return HttpResponse.json(result.data);
61+
}),
62+
63+
http.post('/api/v1/data/:object', async ({ params, request }) => {
64+
const { object } = params;
65+
const body = await request.json();
66+
const result = await ObjectStackServer.createData(object as string, body);
67+
return HttpResponse.json(result.data);
68+
}),
69+
70+
// Custom bootstrap if needed
71+
http.get('/api/bootstrap', async () => {
72+
const contacts = (await ObjectStackServer.findData('contact')).data;
73+
return HttpResponse.json({ contacts });
74+
})
75+
];

.storybook/preview.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import type { Preview } from '@storybook/react-vite'
2+
import { initialize, mswLoader } from 'msw-storybook-addon';
3+
import { handlers } from './mocks';
24
import '../packages/components/src/index.css';
35
import { ComponentRegistry } from '@object-ui/core';
46
import * as components from '../packages/components/src/index';
57

8+
// Initialize MSW
9+
initialize({
10+
onUnhandledRequest: 'bypass'
11+
});
12+
613
// Register all base components for Storybook
714
Object.values(components);
815

@@ -24,7 +31,11 @@ import '@object-ui/plugin-timeline';
2431
import '@object-ui/plugin-view';
2532

2633
const preview: Preview = {
34+
loaders: [mswLoader],
2735
parameters: {
36+
msw: {
37+
handlers: handlers
38+
},
2839
options: {
2940
storySort: {
3041
method: 'alphabetical',

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"devDependencies": {
5151
"@changesets/cli": "^2.29.8",
5252
"@eslint/js": "^9.39.1",
53+
"@objectstack/plugin-msw": "^0.6.1",
5354
"@storybook/addon-essentials": "^8.6.14",
5455
"@storybook/addon-interactions": "^8.6.14",
5556
"@storybook/addon-links": "^8.6.15",
@@ -74,6 +75,8 @@
7475
"globals": "^17.1.0",
7576
"happy-dom": "^20.3.9",
7677
"jsdom": "^27.4.0",
78+
"msw": "^2.12.7",
79+
"msw-storybook-addon": "^2.0.6",
7780
"playwright": "^1.58.0",
7881
"prettier": "^3.8.1",
7982
"react": "19.2.3",
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import React, { useEffect, useState } from 'react';
3+
import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react';
4+
import { PageSchema } from '@object-ui/types';
5+
6+
// Define a schema that binds to fetched data
7+
const contactDetailSchema: PageSchema = {
8+
type: "page",
9+
props: { title: "Contact Details (Mocked)" },
10+
children: [
11+
{
12+
type: "page:header",
13+
props: {
14+
title: "${data.name}",
15+
description: "${data.title} at ${data.company}"
16+
}
17+
},
18+
{
19+
type: "view:simple",
20+
props: { columns: 2, className: "mt-4 border p-4 rounded" },
21+
children: [
22+
{ type: "field:text", bind: "name", props: { label: "Full Name", readonly: true } },
23+
{ type: "field:email", bind: "email", props: { label: "Email", readonly: true } },
24+
{ type: "field:text", bind: "status", props: { label: "Status", readonly: true } }
25+
]
26+
}
27+
]
28+
};
29+
30+
// A wrapper component that fetches data from the MSW mock
31+
const DataFetcher = () => {
32+
const [data, setData] = useState<any>(null);
33+
34+
useEffect(() => {
35+
// Fetch from the mocked API
36+
fetch('/api/v1/data/contact/1')
37+
.then(res => res.json())
38+
.then(setData)
39+
.catch(e => console.error("Fetch failed", e));
40+
}, []);
41+
42+
if (!data) return <div className="p-4">Loading data from MSW...</div>;
43+
44+
return (
45+
<SchemaRendererProvider dataSource={data}>
46+
<SchemaRenderer schema={contactDetailSchema} />
47+
</SchemaRendererProvider>
48+
);
49+
};
50+
51+
const meta: Meta = {
52+
title: 'Guide/Mocked Data',
53+
component: DataFetcher,
54+
parameters: {
55+
// We can also override handlers per story here if needed
56+
// msw: { handlers: [...] }
57+
}
58+
};
59+
60+
export default meta;
61+
62+
export const Default: StoryObj = {};

pnpm-lock.yaml

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)