Skip to content

Commit 3756a89

Browse files
Copilothotlong
andcommitted
Add MSW server setup for testing and basic tests
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 7f5d9f6 commit 3756a89

6 files changed

Lines changed: 811 additions & 38 deletions

File tree

examples/msw-object-form/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
"test:ui": "vitest --ui"
1515
},
1616
"dependencies": {
17+
"@object-ui/fields": "workspace:*",
1718
"@object-ui/plugin-form": "workspace:*",
1819
"@object-ui/react": "workspace:*",
1920
"@object-ui/types": "workspace:*",
20-
"@object-ui/fields": "workspace:*",
2121
"@objectstack/client": "^0.7.2",
2222
"@objectstack/driver-memory": "^0.7.2",
2323
"@objectstack/objectql": "^0.7.2",
@@ -32,6 +32,7 @@
3232
"@testing-library/jest-dom": "^6.6.3",
3333
"@testing-library/react": "^16.1.0",
3434
"@testing-library/user-event": "^14.5.2",
35+
"@types/node": "^25.0.10",
3536
"@types/react": "^19.0.6",
3637
"@types/react-dom": "^19.0.2",
3738
"@vitejs/plugin-react": "^4.3.4",
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Simple MSW Integration Test
3+
*
4+
* Minimal test to verify MSW server is working
5+
*/
6+
7+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
8+
import '@testing-library/jest-dom';
9+
import { startMockServer, stopMockServer, getDriver } from '../mocks/server';
10+
11+
describe('MSW Server Integration', () => {
12+
beforeAll(async () => {
13+
await startMockServer();
14+
});
15+
16+
afterAll(() => {
17+
stopMockServer();
18+
});
19+
20+
it('should initialize MSW server with data', async () => {
21+
const driver = getDriver();
22+
expect(driver).toBeDefined();
23+
24+
// Check that initial data was loaded
25+
const contacts = await driver!.find('contact', {});
26+
expect(contacts).toHaveLength(3);
27+
expect(contacts[0].name).toBe('John Doe');
28+
});
29+
30+
it('should create a new contact via driver', async () => {
31+
const driver = getDriver();
32+
33+
const newContact = await driver!.create('contact', {
34+
name: 'Test User',
35+
email: 'test@example.com',
36+
is_active: true,
37+
priority: 5
38+
});
39+
40+
expect(newContact.name).toBe('Test User');
41+
expect(newContact.email).toBe('test@example.com');
42+
});
43+
});

examples/msw-object-form/src/__tests__/ObjectForm.test.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
* Tests ObjectForm component with real ObjectStack MSW runtime
55
*/
66

7-
import { describe, it, expect, beforeAll, afterEach, vi } from 'vitest';
7+
import { describe, it, expect, beforeAll, afterAll, afterEach, vi } from 'vitest';
88
import { render, screen, waitFor } from '@testing-library/react';
99
import userEvent from '@testing-library/user-event';
1010
import '@testing-library/jest-dom';
1111
import { ObjectStackClient } from '@objectstack/client';
1212
import { ObjectForm } from '@object-ui/plugin-form';
1313
import { ObjectStackDataSource } from '../dataSource';
14-
import { startMockServer, getDriver } from '../mocks/browser';
14+
import { startMockServer, stopMockServer, getDriver } from '../mocks/server';
1515

1616
describe('ObjectForm with MSW Integration', () => {
1717
let client: ObjectStackClient;
@@ -21,13 +21,17 @@ describe('ObjectForm with MSW Integration', () => {
2121
// Start MSW mock server
2222
await startMockServer();
2323

24-
// Initialize client
25-
client = new ObjectStackClient({ baseUrl: '' });
24+
// Initialize client - use localhost to match MSW handlers
25+
client = new ObjectStackClient({ baseUrl: 'http://localhost:3000' });
2626
await client.connect();
2727

2828
dataSource = new ObjectStackDataSource(client);
2929
});
3030

31+
afterAll(() => {
32+
stopMockServer();
33+
});
34+
3135
afterEach(async () => {
3236
// Clean up created contacts after each test
3337
const driver = getDriver();
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/**
2+
* MSW Server Setup for Tests
3+
*
4+
* This creates a complete ObjectStack environment for testing using MSW setupServer
5+
* instead of setupWorker (which is for browser only).
6+
*/
7+
8+
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
9+
import { ObjectQLPlugin } from '@objectstack/objectql';
10+
import { InMemoryDriver } from '@objectstack/driver-memory';
11+
import { setupServer } from 'msw/node';
12+
import { http, HttpResponse } from 'msw';
13+
import appConfig from '../../objectstack.config';
14+
15+
let kernel: ObjectKernel | null = null;
16+
let driver: InMemoryDriver | null = null;
17+
let server: ReturnType<typeof setupServer> | null = null;
18+
19+
export async function startMockServer() {
20+
if (kernel) {
21+
console.log('[MSW] ObjectStack Runtime already initialized');
22+
return kernel;
23+
}
24+
25+
console.log('[MSW] Starting ObjectStack Runtime (Test Mode)...');
26+
27+
driver = new InMemoryDriver();
28+
29+
// Create kernel
30+
kernel = new ObjectKernel();
31+
32+
kernel
33+
.use(new ObjectQLPlugin())
34+
.use(new DriverPlugin(driver, 'memory'))
35+
.use(new AppPlugin(appConfig));
36+
37+
// Bootstrap kernel WITHOUT MSW plugin (we'll handle MSW separately for tests)
38+
await kernel.bootstrap();
39+
40+
// Load initial data from manifest
41+
const manifest = (appConfig as any).manifest;
42+
if (manifest && Array.isArray(manifest.data)) {
43+
console.log('[MSW] Loading initial data...');
44+
for (const dataset of manifest.data) {
45+
if (dataset.object && Array.isArray(dataset.records)) {
46+
for (const record of dataset.records) {
47+
await driver.create(dataset.object, record);
48+
}
49+
console.log(`[MSW] Loaded ${dataset.records.length} records for ${dataset.object}`);
50+
}
51+
}
52+
}
53+
54+
// Create MSW handlers manually
55+
const baseUrl = 'http://localhost:3000/api/v1';
56+
const handlers = createHandlers(baseUrl, kernel);
57+
58+
// Setup MSW server for Node.js environment
59+
server = setupServer(...handlers);
60+
server.listen({ onUnhandledRequest: 'bypass' });
61+
62+
console.log('[MSW] ObjectStack Runtime ready');
63+
return kernel;
64+
}
65+
66+
export function stopMockServer() {
67+
if (server) {
68+
server.close();
69+
server = null;
70+
}
71+
kernel = null;
72+
driver = null;
73+
}
74+
75+
export function getKernel(): ObjectKernel | null {
76+
return kernel;
77+
}
78+
79+
export function getDriver(): InMemoryDriver | null {
80+
return driver;
81+
}
82+
83+
/**
84+
* Create MSW request handlers for ObjectStack API
85+
*/
86+
function createHandlers(baseUrl: string, kernel: ObjectKernel) {
87+
const protocol = kernel.getService('protocol');
88+
89+
return [
90+
// Discovery endpoint
91+
http.get(`${baseUrl}/`, async () => {
92+
const response = await protocol.handleDiscovery();
93+
return HttpResponse.json(response, { status: 200 });
94+
}),
95+
96+
// Metadata endpoints
97+
http.get(`${baseUrl}/meta/objects`, async () => {
98+
const response = await protocol.handleMetadataListObjects();
99+
return HttpResponse.json(response, { status: 200 });
100+
}),
101+
102+
http.get(`${baseUrl}/meta/objects/:objectName`, async ({ params }) => {
103+
const response = await protocol.handleMetadataGetObject({ objectName: params.objectName as string });
104+
return HttpResponse.json(response, { status: 200 });
105+
}),
106+
107+
// Data endpoints - Find all
108+
http.get(`${baseUrl}/data/:objectName`, async ({ params, request }) => {
109+
const url = new URL(request.url);
110+
const query: any = {};
111+
112+
// Parse query parameters
113+
url.searchParams.forEach((value, key) => {
114+
try {
115+
query[key] = JSON.parse(value);
116+
} catch {
117+
query[key] = value;
118+
}
119+
});
120+
121+
const response = await protocol.handleFind({
122+
objectName: params.objectName as string,
123+
query
124+
});
125+
return HttpResponse.json(response, { status: 200 });
126+
}),
127+
128+
// Data endpoints - Find by ID
129+
http.get(`${baseUrl}/data/:objectName/:id`, async ({ params }) => {
130+
const response = await protocol.handleFindById({
131+
objectName: params.objectName as string,
132+
id: params.id as string
133+
});
134+
return HttpResponse.json(response, { status: 200 });
135+
}),
136+
137+
// Data endpoints - Create
138+
http.post(`${baseUrl}/data/:objectName`, async ({ params, request }) => {
139+
const body = await request.json();
140+
const response = await protocol.handleCreate({
141+
objectName: params.objectName as string,
142+
data: body
143+
});
144+
return HttpResponse.json(response, { status: 201 });
145+
}),
146+
147+
// Data endpoints - Update
148+
http.patch(`${baseUrl}/data/:objectName/:id`, async ({ params, request }) => {
149+
const body = await request.json();
150+
const response = await protocol.handleUpdate({
151+
objectName: params.objectName as string,
152+
id: params.id as string,
153+
data: body
154+
});
155+
return HttpResponse.json(response, { status: 200 });
156+
}),
157+
158+
// Data endpoints - Delete
159+
http.delete(`${baseUrl}/data/:objectName/:id`, async ({ params }) => {
160+
const response = await protocol.handleDelete({
161+
objectName: params.objectName as string,
162+
id: params.id as string
163+
});
164+
return HttpResponse.json(response, { status: 200 });
165+
}),
166+
];
167+
}

examples/msw-object-form/vite.config.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,47 @@ import react from '@vitejs/plugin-react';
44
// https://vitejs.dev/config/
55
export default defineConfig({
66
plugins: [react()],
7+
resolve: {
8+
extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']
9+
},
710
optimizeDeps: {
811
include: [
12+
'msw',
13+
'msw/browser',
914
'@objectstack/spec',
1015
'@objectstack/spec/data',
11-
'@objectstack/spec/system'
12-
]
16+
'@objectstack/spec/system',
17+
'@objectstack/spec/ui'
18+
],
19+
esbuildOptions: {
20+
resolveExtensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']
21+
}
22+
},
23+
build: {
24+
commonjsOptions: {
25+
include: [/node_modules/, /packages/],
26+
transformMixedEsModules: true
27+
},
28+
rollupOptions: {
29+
onwarn(warning, warn) {
30+
if (
31+
warning.code === 'UNRESOLVED_IMPORT' &&
32+
warning.message.includes('@objectstack/driver-memory')
33+
) {
34+
return;
35+
}
36+
warn(warning);
37+
}
38+
}
1339
},
1440
test: {
1541
globals: true,
1642
environment: 'happy-dom',
1743
setupFiles: ['./vitest.setup.ts'],
44+
server: {
45+
deps: {
46+
inline: [/@objectstack/]
47+
}
48+
}
1849
}
1950
});

0 commit comments

Comments
 (0)