refactor(skills): add skill-adapter and refactor SkillMarket

- Add skill-adapter.ts to bridge configStore and UI skill formats
- Refactor SkillMarket to use new skill-adapter instead of skill-discovery
- Add health check state to connectionStore
- Update multiple components with improved typing
- Clean up test artifacts and add new test results
- Update README and add skill-market-mvp plan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-21 00:28:03 +08:00
parent 54ccc0a7b0
commit 48a430fc97
50 changed files with 1523 additions and 360 deletions

View File

@@ -1,61 +1,68 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const useGatewayStoreMock = vi.fn();
const useChatStoreMock = vi.fn();
const getStoredGatewayTokenMock = vi.fn(() => 'stored-token');
const setStoredGatewayTokenMock = vi.fn();
const mockConnectionState = { value: 'connected' as const };
const mockQuickConfig = {
value: {
gatewayUrl: 'ws://127.0.0.1:50051',
gatewayToken: '',
theme: 'light' as const,
autoStart: false,
showToolCalls: false,
},
};
vi.mock('../../desktop/src/store/gatewayStore', () => ({
useGatewayStore: () => useGatewayStoreMock(),
const connectMock = vi.fn(async () => {});
const disconnectMock = vi.fn();
const saveQuickConfigMock = vi.fn(async () => {});
// Mock connectionStore with selector pattern
vi.mock('../../desktop/src/store/connectionStore', () => ({
useConnectionStore: vi.fn((selector) => {
const state = {
connectionState: mockConnectionState.value,
connect: connectMock,
disconnect: disconnectMock,
};
return selector ? selector(state) : state;
}),
}));
// Mock configStore with selector pattern
vi.mock('../../desktop/src/store/configStore', () => ({
useConfigStore: vi.fn((selector) => {
const state = {
quickConfig: mockQuickConfig.value,
saveQuickConfig: saveQuickConfigMock,
};
return selector ? selector(state) : state;
}),
}));
// Mock chatStore with selector pattern
vi.mock('../../desktop/src/store/chatStore', () => ({
useChatStore: () => useChatStoreMock(),
useChatStore: vi.fn((selector) => {
const state = {
currentModel: 'glm-5',
};
return selector ? selector(state) : state;
}),
}));
vi.mock('../../desktop/src/lib/gateway-client', () => ({
getStoredGatewayToken: () => getStoredGatewayTokenMock(),
setStoredGatewayToken: (token: string) => setStoredGatewayTokenMock(token),
getStoredGatewayToken: () => 'stored-token',
setStoredGatewayToken: vi.fn(),
}));
describe('General settings gateway connection', () => {
let connectMock: ReturnType<typeof vi.fn>;
let disconnectMock: ReturnType<typeof vi.fn>;
let saveQuickConfigMock: ReturnType<typeof vi.fn>;
beforeEach(() => {
vi.clearAllMocks();
connectMock = vi.fn(async () => {});
disconnectMock = vi.fn();
saveQuickConfigMock = vi.fn(async () => {});
useGatewayStoreMock.mockReturnValue({
connectionState: 'connected',
gatewayVersion: '2026.3.11',
error: null,
quickConfig: {
gatewayUrl: 'ws://127.0.0.1:50051',
gatewayToken: '',
theme: 'light',
autoStart: false,
showToolCalls: false,
},
connect: connectMock,
disconnect: disconnectMock,
saveQuickConfig: saveQuickConfigMock,
});
useChatStoreMock.mockReturnValue({
currentModel: 'glm-5',
});
mockConnectionState.value = 'connected';
});
it('renders gateway connection settings and displays connection status', async () => {
const reactModule = 'react';
const reactDomClientModule = 'react-dom/client';
const [{ act, createElement }, { createRoot }, { General }] = await Promise.all([
import(reactModule),
import(reactDomClientModule),
import('react'),
import('react-dom/client'),
import('../../desktop/src/components/Settings/General'),
]);
@@ -73,7 +80,6 @@ describe('General settings gateway connection', () => {
expect(container.textContent).toContain('Gateway 连接');
expect(container.textContent).toContain('已连接');
expect(container.textContent).toContain('ws://127.0.0.1:50051');
expect(container.textContent).toContain('2026.3.11');
expect(container.textContent).toContain('glm-5');
expect(container.textContent).toContain('断开连接');
@@ -90,21 +96,7 @@ describe('General settings gateway connection', () => {
});
it('displays disconnected state when not connected', async () => {
useGatewayStoreMock.mockReturnValue({
connectionState: 'disconnected',
gatewayVersion: null,
error: null,
quickConfig: {
gatewayUrl: 'ws://127.0.0.1:50051',
gatewayToken: '',
theme: 'light',
autoStart: false,
showToolCalls: false,
},
connect: connectMock,
disconnect: disconnectMock,
saveQuickConfig: saveQuickConfigMock,
});
mockConnectionState.value = 'disconnected';
const [{ act, createElement }, { createRoot }, { General }] = await Promise.all([
import('react'),

48
tests/e2e/README.md Normal file
View File

@@ -0,0 +1,48 @@
# E2E Tests
This directory contains end-to-end tests for ZCLAW desktop application.
## Test Strategy
Due to ZCLAW being a Tauri 22.0 desktop application, E2E testing requires special consideration:
### Option 1: Vitest + @testing-library/react (Current)
- Unit and integration tests use Vitest
- Component tests use @testing-library/react
- Already configured and working (312 tests passing)
### Option 2: Playwright for Web UI (Future)
- Playwright is available in `desktop/node_modules`
- Can test the React UI layer independently
- Cannot directly test Tauri native functionality
### Option 3: Tauri Driver (Recommended for Full E2E)
- Use Tauri's built-in test utilities
- Requires `tauri-driver` package
- Can test full application including native functionality
## Current Status
- **Unit Tests**: ✅ 312 tests passing
- **Integration Tests**: ✅ Store and component integration verified
- **E2E Tests**: 🚧 Framework setup in progress
## Running Tests
```bash
# Unit/Integration tests
pnpm vitest run
# E2E tests (when implemented)
pnpm test:e2e
```
## Test Coverage
| Area | Unit | Integration | E2E |
|------|------|-------------|-----|
| Stores | ✅ | ✅ | - |
| Components | ✅ | ✅ | - |
| Gateway Client | ✅ | ✅ | - |
| Tauri Commands | - | - | 🚧 |
| Full User Flow | - | - | 🚧 |

49
tests/e2e/app.spec.ts Normal file
View File

@@ -0,0 +1,49 @@
import { test, expect } from '@playwright/test';
/**
* ZCLAW E2E Tests
*
* These tests verify the UI layer of the application.
* For Tauri-specific tests, use Tauri's built-in testing tools.
*/
test.describe('ZCLAW App', () => {
test.skip('should load the main page', async ({ page }) => {
// This test is skipped because it requires the dev server to be running
// To run: pnpm dev & pnpm test:e2e
await page.goto('/');
// Verify the app loads
await expect(page.locator('text=ZCLAW')).toBeVisible();
});
test.skip('should show connection status', async ({ page }) => {
await page.goto('/');
// Wait for connection status to appear
const statusText = await page.locator('[data-testid="connection-status"]').textContent();
expect(statusText).toBeTruthy();
});
});
test.describe('Settings Page', () => {
test.skip('should navigate to settings', async ({ page }) => {
await page.goto('/');
// Click settings button
await page.click('[data-testid="settings-button"]');
// Verify settings page loaded
await expect(page.locator('text=通用')).toBeVisible();
});
});
test.describe('Chat Interface', () => {
test.skip('should display chat input', async ({ page }) => {
await page.goto('/');
// Verify chat input exists
const chatInput = page.locator('[data-testid="chat-input"]');
await expect(chatInput).toBeVisible();
});
});

View File

@@ -0,0 +1,32 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Playwright configuration for ZCLAW E2E tests
*
* Note: These tests focus on the React UI layer.
* For full Tauri integration, use Tauri's built-in testing tools.
*/
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:5173',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'pnpm dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
});