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:
iven
2026-03-12 00:48:58 +08:00
parent b8a83b5a26
commit 1d831fb286
5 changed files with 1400 additions and 3 deletions

View 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
View 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 };