mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-28 20:01:17 +08:00
feat: add Accordion component for UI and Zustand store for coordinator management
- Implemented Accordion component using Radix UI for collapsible sections. - Created Zustand store to manage coordinator execution state, command chains, logs, and interactive questions. - Added validation tests for CLI settings type definitions, ensuring type safety and correct behavior of helper functions.
This commit is contained in:
491
ccw/tests/types/cli-settings.test.ts
Normal file
491
ccw/tests/types/cli-settings.test.ts
Normal file
@@ -0,0 +1,491 @@
|
||||
/**
|
||||
* CLI Settings Type Definitions Tests
|
||||
*
|
||||
* Test coverage:
|
||||
* - ClaudeCliSettings interface type safety
|
||||
* - validateSettings function with deep env validation
|
||||
* - mapProviderToClaudeEnv helper function
|
||||
* - createDefaultSettings helper function
|
||||
*/
|
||||
|
||||
import { describe, it, before } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
validateSettings,
|
||||
mapProviderToClaudeEnv,
|
||||
createDefaultSettings,
|
||||
} from '../../dist/types/cli-settings.js';
|
||||
|
||||
// Type for testing (interfaces are erased in JS)
|
||||
type ClaudeCliSettings = {
|
||||
env: Record<string, string | undefined>;
|
||||
model?: string;
|
||||
includeCoAuthoredBy?: boolean;
|
||||
tags?: string[];
|
||||
availableModels?: string[];
|
||||
settingsFile?: string;
|
||||
};
|
||||
|
||||
describe('cli-settings.ts', () => {
|
||||
describe('validateSettings', () => {
|
||||
describe('should validate valid ClaudeCliSettings objects', () => {
|
||||
it('should accept a complete valid settings object', () => {
|
||||
const validSettings = {
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-ant-123',
|
||||
ANTHROPIC_BASE_URL: 'https://api.anthropic.com',
|
||||
DISABLE_AUTOUPDATER: '1',
|
||||
},
|
||||
model: 'sonnet',
|
||||
includeCoAuthoredBy: true,
|
||||
tags: ['分析', 'Debug'],
|
||||
availableModels: ['opus', 'sonnet', 'haiku'],
|
||||
settingsFile: '/path/to/settings.json',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(validSettings), true);
|
||||
});
|
||||
|
||||
it('should accept settings with only required env field', () => {
|
||||
const minimalSettings = {
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-ant-123',
|
||||
},
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(minimalSettings), true);
|
||||
});
|
||||
|
||||
it('should accept settings with empty env object', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should accept settings with undefined optional properties', () => {
|
||||
const settings = {
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-ant-123',
|
||||
},
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should reject invalid or non-object inputs', () => {
|
||||
it('should reject null', () => {
|
||||
assert.strictEqual(validateSettings(null), false);
|
||||
});
|
||||
|
||||
it('should reject undefined', () => {
|
||||
assert.strictEqual(validateSettings(undefined), false);
|
||||
});
|
||||
|
||||
it('should reject number', () => {
|
||||
assert.strictEqual(validateSettings(123), false);
|
||||
});
|
||||
|
||||
it('should reject string', () => {
|
||||
assert.strictEqual(validateSettings('invalid'), false);
|
||||
});
|
||||
|
||||
it('should reject boolean', () => {
|
||||
assert.strictEqual(validateSettings(true), false);
|
||||
});
|
||||
|
||||
it('should reject array', () => {
|
||||
assert.strictEqual(validateSettings([]), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should validate env field', () => {
|
||||
it('should reject missing env field', () => {
|
||||
const settings = {
|
||||
model: 'sonnet',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
|
||||
it('should reject non-object env', () => {
|
||||
const settings = {
|
||||
env: 'invalid',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
|
||||
it('should reject env with null value', () => {
|
||||
const settings = {
|
||||
env: null,
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
|
||||
describe('deep env validation (optimization)', () => {
|
||||
it('should reject env with null value (DEEP VALIDATION)', () => {
|
||||
const settings = {
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: null,
|
||||
},
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
|
||||
it('should reject env with number value (DEEP VALIDATION)', () => {
|
||||
const settings = {
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 12345,
|
||||
},
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
|
||||
it('should reject env with boolean value (DEEP VALIDATION)', () => {
|
||||
const settings = {
|
||||
env: {
|
||||
DISABLE_AUTOUPDATER: true,
|
||||
},
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
|
||||
it('should reject env with object value (DEEP VALIDATION)', () => {
|
||||
const settings = {
|
||||
env: {
|
||||
CUSTOM: { nested: 'value' },
|
||||
},
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
|
||||
it('should reject env with array value (DEEP VALIDATION)', () => {
|
||||
const settings = {
|
||||
env: {
|
||||
TAGS: ['tag1', 'tag2'],
|
||||
},
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
|
||||
it('should accept env with string values', () => {
|
||||
const settings = {
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-ant-123',
|
||||
ANTHROPIC_BASE_URL: 'https://api.anthropic.com',
|
||||
DISABLE_AUTOUPDATER: '1',
|
||||
},
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should accept env with undefined values (optional env vars)', () => {
|
||||
const settings = {
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-ant-123',
|
||||
ANTHROPIC_BASE_URL: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should accept env with all undefined values', () => {
|
||||
const settings = {
|
||||
env: {
|
||||
OPTIONAL_VAR: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should validate model field', () => {
|
||||
it('should accept predefined model values', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
model: 'opus',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should accept custom model string', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
model: 'custom-model-3.5',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should reject non-string model', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
model: 123,
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should validate includeCoAuthoredBy field', () => {
|
||||
it('should accept boolean includeCoAuthoredBy', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
includeCoAuthoredBy: true,
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should reject non-boolean includeCoAuthoredBy', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
includeCoAuthoredBy: 'true',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should validate tags field', () => {
|
||||
it('should accept valid tags array', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
tags: ['分析', 'Debug', 'testing'],
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should accept empty tags array', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
tags: [],
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should reject non-array tags', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
tags: 'invalid',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should validate availableModels field', () => {
|
||||
it('should accept valid availableModels array', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
availableModels: ['opus', 'sonnet', 'haiku', 'custom-model'],
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should accept empty availableModels array', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
availableModels: [],
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should reject non-array availableModels', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
availableModels: 'invalid',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should validate settingsFile field', () => {
|
||||
it('should accept valid settingsFile string', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
settingsFile: '/path/to/settings.json',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should accept empty settingsFile string', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
settingsFile: '',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should reject non-string settingsFile', () => {
|
||||
const settings = {
|
||||
env: {},
|
||||
settingsFile: 123,
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases and boundary conditions', () => {
|
||||
it('should handle settings with many optional fields', () => {
|
||||
const settings = {
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-ant-123',
|
||||
ANTHROPIC_BASE_URL: 'https://api.anthropic.com',
|
||||
DISABLE_AUTOUPDATER: '1',
|
||||
CUSTOM_VAR: 'custom-value',
|
||||
},
|
||||
model: 'custom-model',
|
||||
includeCoAuthoredBy: false,
|
||||
tags: [],
|
||||
availableModels: [],
|
||||
settingsFile: '/path/to/settings.json',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should handle settings with only env and one other field', () => {
|
||||
const settings = {
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-ant-123',
|
||||
},
|
||||
model: 'sonnet',
|
||||
};
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapProviderToClaudeEnv', () => {
|
||||
it('should map provider with apiKey only', () => {
|
||||
const provider = { apiKey: 'sk-test-key' };
|
||||
const env = mapProviderToClaudeEnv(provider);
|
||||
|
||||
assert.deepStrictEqual(env, {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-test-key',
|
||||
DISABLE_AUTOUPDATER: '1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should map provider with apiBase only', () => {
|
||||
const provider = { apiBase: 'https://custom.api.com' };
|
||||
const env = mapProviderToClaudeEnv(provider);
|
||||
|
||||
assert.deepStrictEqual(env, {
|
||||
ANTHROPIC_BASE_URL: 'https://custom.api.com',
|
||||
DISABLE_AUTOUPDATER: '1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should map provider with both apiKey and apiBase', () => {
|
||||
const provider = {
|
||||
apiKey: 'sk-test-key',
|
||||
apiBase: 'https://custom.api.com',
|
||||
};
|
||||
const env = mapProviderToClaudeEnv(provider);
|
||||
|
||||
assert.deepStrictEqual(env, {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-test-key',
|
||||
ANTHROPIC_BASE_URL: 'https://custom.api.com',
|
||||
DISABLE_AUTOUPDATER: '1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should map empty provider to default DISABLE_AUTOUPDATER', () => {
|
||||
const provider = {};
|
||||
const env = mapProviderToClaudeEnv(provider);
|
||||
|
||||
assert.deepStrictEqual(env, {
|
||||
DISABLE_AUTOUPDATER: '1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should always set DISABLE_AUTOUPDATER to "1"', () => {
|
||||
const env1 = mapProviderToClaudeEnv({ apiKey: 'key' });
|
||||
const env2 = mapProviderToClaudeEnv({ apiBase: 'url' });
|
||||
const env3 = mapProviderToClaudeEnv({});
|
||||
|
||||
assert.strictEqual(env1.DISABLE_AUTOUPDATER, '1');
|
||||
assert.strictEqual(env2.DISABLE_AUTOUPDATER, '1');
|
||||
assert.strictEqual(env3.DISABLE_AUTOUPDATER, '1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createDefaultSettings', () => {
|
||||
it('should create valid default settings', () => {
|
||||
const settings = createDefaultSettings();
|
||||
|
||||
assert.strictEqual(validateSettings(settings), true);
|
||||
});
|
||||
|
||||
it('should include all default fields', () => {
|
||||
const settings = createDefaultSettings();
|
||||
|
||||
assert.ok('env' in settings);
|
||||
assert.ok('model' in settings);
|
||||
assert.ok('includeCoAuthoredBy' in settings);
|
||||
assert.ok('tags' in settings);
|
||||
assert.ok('availableModels' in settings);
|
||||
});
|
||||
|
||||
it('should have correct default values', () => {
|
||||
const settings = createDefaultSettings();
|
||||
|
||||
assert.deepStrictEqual(settings.env, {
|
||||
DISABLE_AUTOUPDATER: '1',
|
||||
});
|
||||
assert.strictEqual(settings.model, 'sonnet');
|
||||
assert.strictEqual(settings.includeCoAuthoredBy, false);
|
||||
assert.deepStrictEqual(settings.tags, []);
|
||||
assert.deepStrictEqual(settings.availableModels, []);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TypeScript type safety', () => {
|
||||
it('should enforce ClaudeCliSettings interface structure', () => {
|
||||
// This test verifies TypeScript compilation catches type errors
|
||||
const validSettings = {
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-ant-123',
|
||||
},
|
||||
model: 'opus',
|
||||
includeCoAuthoredBy: true,
|
||||
tags: ['tag1'],
|
||||
availableModels: ['model1'],
|
||||
settingsFile: '/path/to/file',
|
||||
};
|
||||
|
||||
// Type assertion: all fields should be present and of correct type
|
||||
assert.strictEqual(typeof validSettings.env, 'object');
|
||||
assert.strictEqual(typeof validSettings.model, 'string');
|
||||
assert.strictEqual(typeof validSettings.includeCoAuthoredBy, 'boolean');
|
||||
assert.strictEqual(Array.isArray(validSettings.tags), true);
|
||||
assert.strictEqual(Array.isArray(validSettings.availableModels), true);
|
||||
assert.strictEqual(typeof validSettings.settingsFile, 'string');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user