test: configure Vitest testing framework
- Add vitest, @testing-library/react, @vitest/ui, jsdom dependencies - Create vitest.config.ts with jsdom environment and coverage settings - Add tests/setup.ts with localStorage mock and crypto mock - Add tests/gateway/ws-client.test.ts for GatewayClient unit tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
18
package.json
18
package.json
@@ -12,7 +12,14 @@
|
|||||||
"gateway:status": "openclaw status",
|
"gateway:status": "openclaw status",
|
||||||
"gateway:doctor": "openclaw doctor"
|
"gateway:doctor": "openclaw doctor"
|
||||||
},
|
},
|
||||||
"keywords": ["ai", "agent", "openclaw", "tauri", "feishu", "chinese-models"],
|
"keywords": [
|
||||||
|
"ai",
|
||||||
|
"agent",
|
||||||
|
"openclaw",
|
||||||
|
"tauri",
|
||||||
|
"feishu",
|
||||||
|
"chinese-models"
|
||||||
|
],
|
||||||
"author": "ZCLAW Team",
|
"author": "ZCLAW Team",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -20,10 +27,15 @@
|
|||||||
"zod": "^3.22.0"
|
"zod": "^3.22.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@testing-library/react": "^16.3.2",
|
||||||
"@types/node": "^20.11.0",
|
"@types/node": "^20.11.0",
|
||||||
"@types/ws": "^8.5.10",
|
"@types/ws": "^8.5.10",
|
||||||
"typescript": "^5.3.0",
|
"@vitejs/plugin-react": "^5.1.4",
|
||||||
|
"@vitest/ui": "^4.0.18",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"jsdom": "^28.1.0",
|
||||||
"tsx": "^4.7.0",
|
"tsx": "^4.7.0",
|
||||||
"jest": "^29.7.0"
|
"typescript": "^5.3.0",
|
||||||
|
"vitest": "^4.0.18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1187
pnpm-lock.yaml
generated
1187
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
115
tests/gateway/ws-client.test.ts
Normal file
115
tests/gateway/ws-client.test.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* Tests for Gateway WebSocket Client (Browser version)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { GatewayClient } from '../../desktop/src/lib/gateway-client';
|
||||||
|
|
||||||
|
describe('GatewayClient', () => {
|
||||||
|
let client: GatewayClient;
|
||||||
|
let mockWs: any;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
client = new GatewayClient({
|
||||||
|
url: 'ws://localhost:18790',
|
||||||
|
token: '',
|
||||||
|
autoReconnect: false,
|
||||||
|
requestTimeout: 5000,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
client.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should initialize with default options', () => {
|
||||||
|
const defaultClient = new GatewayClient();
|
||||||
|
expect(defaultClient.getState()).toBe('disconnected');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize with custom options', () => {
|
||||||
|
const customClient = new GatewayClient({
|
||||||
|
url: 'ws://custom:1234',
|
||||||
|
token: 'test-token',
|
||||||
|
autoReconnect: false,
|
||||||
|
});
|
||||||
|
expect(customClient.getState()).toBe('disconnected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('connection state', () => {
|
||||||
|
it('should start in disconnected state', () => {
|
||||||
|
expect(client.getState()).toBe('disconnected');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit state changes', () => {
|
||||||
|
const stateChanges: string[] = [];
|
||||||
|
client.onStateChange = (state) => {
|
||||||
|
stateChanges.push(state);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note: actual connection requires a real WebSocket server
|
||||||
|
// This test verifies the callback mechanism
|
||||||
|
expect(typeof client.onStateChange).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('event subscription', () => {
|
||||||
|
it('should allow subscribing to events', () => {
|
||||||
|
const callback = vi.fn();
|
||||||
|
const unsubscribe = client.on('test', callback);
|
||||||
|
|
||||||
|
// Verify unsubscribe is a function
|
||||||
|
expect(typeof unsubscribe).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow multiple listeners for same event', () => {
|
||||||
|
const callback1 = vi.fn();
|
||||||
|
const callback2 = vi.fn();
|
||||||
|
|
||||||
|
client.on('test', callback1);
|
||||||
|
client.on('test', callback2);
|
||||||
|
|
||||||
|
// Both listeners should be registered
|
||||||
|
// (In real scenario, they would be called when event is emitted)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('agent stream subscription', () => {
|
||||||
|
it('should provide onAgentStream helper', () => {
|
||||||
|
const callback = vi.fn();
|
||||||
|
const unsubscribe = client.onAgentStream(callback);
|
||||||
|
|
||||||
|
expect(typeof unsubscribe).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('disconnect', () => {
|
||||||
|
it('should cleanup resources on disconnect', () => {
|
||||||
|
client.disconnect();
|
||||||
|
expect(client.getState()).toBe('disconnected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('API methods', () => {
|
||||||
|
it('should have chat method', () => {
|
||||||
|
expect(typeof client.chat).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have health method', () => {
|
||||||
|
expect(typeof client.health).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have status method', () => {
|
||||||
|
expect(typeof client.status).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have ZCLAW custom methods', () => {
|
||||||
|
expect(typeof client.listClones).toBe('function');
|
||||||
|
expect(typeof client.createClone).toBe('function');
|
||||||
|
expect(typeof client.getUsageStats).toBe('function');
|
||||||
|
expect(typeof client.getPluginStatus).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
50
tests/setup.ts
Normal file
50
tests/setup.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Test setup file for Vitest
|
||||||
|
* Configure global test environment
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { beforeAll, beforeEach } from 'vitest';
|
||||||
|
|
||||||
|
// Mock localStorage
|
||||||
|
const localStorageMock = (() => {
|
||||||
|
let store: Record<string, string> = {};
|
||||||
|
return {
|
||||||
|
getItem: (key: string) => store[key] || null,
|
||||||
|
setItem: (key: string, value: string) => {
|
||||||
|
store[key] = value.toString();
|
||||||
|
},
|
||||||
|
removeItem: (key: string) => {
|
||||||
|
delete store[key];
|
||||||
|
},
|
||||||
|
clear: () => {
|
||||||
|
store = {};
|
||||||
|
},
|
||||||
|
get length() {
|
||||||
|
return Object.keys(store).length;
|
||||||
|
},
|
||||||
|
key: (index: number) => {
|
||||||
|
return Object.keys(store)[index] || null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Setup global mocks
|
||||||
|
beforeAll(() => {
|
||||||
|
// Mock crypto.randomUUID if not available
|
||||||
|
if (!globalThis.crypto) {
|
||||||
|
globalThis.crypto = {
|
||||||
|
randomUUID: () => 'test-uuid-1234',
|
||||||
|
subtle: {
|
||||||
|
digest: async () => new ArrayBuffer(16),
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup before each test
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorageMock.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export for use in tests
|
||||||
|
export { localStorageMock };
|
||||||
33
vitest.config.ts
Normal file
33
vitest.config.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
setupFiles: ['./tests/setup.ts'],
|
||||||
|
include: ['tests/**/*.test.ts', 'tests/**/*.test.tsx'],
|
||||||
|
coverage: {
|
||||||
|
provider: 'v8',
|
||||||
|
reporter: ['text', 'json', 'html'],
|
||||||
|
reportsDirectory: './coverage',
|
||||||
|
include: ['src/**/*.ts', 'desktop/src/**/*.ts', 'desktop/src/**/*.tsx'],
|
||||||
|
exclude: [
|
||||||
|
'src/**/*.d.ts',
|
||||||
|
'desktop/src/**/*.d.ts',
|
||||||
|
'tests/**',
|
||||||
|
'**/*.test.ts',
|
||||||
|
'**/*.test.tsx',
|
||||||
|
'**/node_modules/**',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './desktop/src'),
|
||||||
|
'@lib': path.resolve(__dirname, './desktop/src/lib'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user