FE_Dev / server /lib /theme /theme-customizer.spec.ts
GitHub Actions
Deploy from GitHub Actions [dev] - 2025-10-31 07:28:50
68f7925
import type { ThemeExtractionResult } from '@/schema/theme-extraction';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
createBoundaryValueTheme,
createInvalidColorTheme,
createLowContrastTheme,
createPerformanceTestTheme,
createValidTheme,
} from './test-helpers';
import { ThemeCustomizer } from './theme-customizer';
/**
* 高コントラストテーマデータを生成
*/
function createHighContrastTheme(): ThemeExtractionResult {
const theme = createValidTheme();
return {
...theme,
colors: {
...theme.colors,
primary_text_color: '#000000',
primary_background_color: '#ffffff',
secondary_text_color: '#000000',
secondary_background_color: '#ffffff',
inverse_text_color: '#ffffff',
tertiary_background_color: '#000000',
},
};
}
function extractVariableValue(css: string, variableName: string): string | null {
const regex = new RegExp(`${variableName}:\s*([^;]+)`);
const match = css.match(regex);
return match ? match[1].trim() : null;
}
describe('ThemeCustomizer', () => {
describe('初期化・設定', () => {
describe('コンストラクタオプション', () => {
it('should initialize with default options when no options provided', () => {
// Arrange
// (no specific arrangement needed)
// Act
const customizer = new ThemeCustomizer();
const stats = customizer.getCacheStats();
// Assert
expect(stats.maxSize).toBe(100);
});
it('should override default options with provided options', () => {
// Arrange
const options = {
enableCache: false,
validateColors: false,
checkAccessibility: false,
performanceMode: true,
};
// Act
const customizer = new ThemeCustomizer(options);
const theme = createValidTheme();
const result = customizer.generateThemedCSS(theme);
// Assert
expect(result.cacheHit).toBe(false); // キャッシュ無効なので常にfalse
});
describe.each([
[true, true, true, false],
[false, false, false, true],
[true, false, true, false],
[false, true, false, true],
])(
'option combinations: cache=%s, validate=%s, accessibility=%s, performance=%s',
(enableCache, validateColors, checkAccessibility, performanceMode) => {
it('should work with given option combination', () => {
// Arrange
const options = { enableCache, validateColors, checkAccessibility, performanceMode };
const customizer = new ThemeCustomizer(options);
const theme = createValidTheme();
// Act
const result = customizer.generateThemedCSS(theme);
// Assert
expect(result).toBeDefined();
expect(result.fullCSS).toBeDefined();
});
},
);
});
});
describe('CSS生成機能', () => {
let customizer: ThemeCustomizer;
beforeEach(() => {
customizer = new ThemeCustomizer({
enableCache: true,
validateColors: true,
checkAccessibility: true,
});
});
describe('正常系', () => {
it('should generate complete CSS with valid theme', () => {
// Arrange
const validTheme = createValidTheme();
// Act
const result = customizer.generateThemedCSS(validTheme);
// Assert
expect(result.fullCSS).toBeDefined();
expect(result.themeCSS).toBeDefined();
expect(result.baseCSS).toBeDefined();
expect(result.cacheHit).toBe(false);
expect(result.generationTimeMs).toBeGreaterThan(0);
});
it('should include extracted color variables in generated CSS', () => {
// Arrange
const validTheme = createValidTheme();
// Act
const result = customizer.generateThemedCSS(validTheme);
// Assert
expect(extractVariableValue(result.themeCSS, '--extracted-primary-color')).toBe(validTheme.colors.primary_color);
expect(extractVariableValue(result.themeCSS, '--extracted-secondary-color')).toBe(validTheme.colors.secondary_color);
});
it('should include fallback values for theme variables', () => {
// Arrange
const validTheme = createValidTheme();
// Act
const result = customizer.generateThemedCSS(validTheme);
// Assert
expect(result.themeCSS).toContain('var(--extracted-primary-color, #5a6c7d)');
expect(result.themeCSS).toContain('var(--extracted-primary-background-color, white)');
});
it('should include legacy color mapping variables', () => {
// Arrange
const validTheme = createValidTheme();
// Act
const result = customizer.generateThemedCSS(validTheme);
// Assert
expect(result.themeCSS).toContain('--legacy-primary-text: var(--theme-primary-text)');
expect(result.themeCSS).toContain('--section-white-bg: var(--theme-primary-bg)');
});
});
describe('異常系', () => {
it('should return fallback CSS when validation fails', () => {
// Arrange
const invalidTheme = createInvalidColorTheme();
const customizerWithValidation = new ThemeCustomizer({ validateColors: true });
// Act
const result = customizerWithValidation.generateThemedCSS(invalidTheme);
// Assert
expect(result.fullCSS).toBeDefined();
expect(result.themeCSS).toBe(''); // テーマCSSは空
expect(result.baseCSS).toBeDefined(); // ベースCSSは提供される
});
it('should generate CSS with invalid colors when validation disabled', () => {
// Arrange
const invalidTheme = createInvalidColorTheme();
const customizerNoValidation = new ThemeCustomizer({ validateColors: false });
// Act
const result = customizerNoValidation.generateThemedCSS(invalidTheme);
// Assert
expect(result.fullCSS).toBeDefined();
expect(result.themeCSS).toContain('--extracted-primary-color: invalid-color');
});
});
describe('境界値・エッジケース', () => {
it('should handle theme with empty color values', () => {
// Arrange
const themeWithEmptyColors = createValidTheme();
themeWithEmptyColors.colors.primary_color = '';
const customizerNoValidation = new ThemeCustomizer({ validateColors: false });
// Act
const result = customizerNoValidation.generateThemedCSS(themeWithEmptyColors);
// Assert
expect(result.fullCSS).toBeDefined();
expect(result.themeCSS).toContain('--extracted-primary-color: ');
});
it('should handle theme with all required color properties', () => {
// Arrange
const minimalTheme = createValidTheme();
// Act
const result = customizer.generateThemedCSS(minimalTheme);
// Assert
expect(result.fullCSS).toBeDefined();
expect(Object.keys(minimalTheme.colors)).toHaveLength(15); // 15色全て確認
});
});
});
describe('バリデーション機能', () => {
let validator: ThemeCustomizer;
beforeEach(() => {
validator = new ThemeCustomizer({
validateColors: true,
checkAccessibility: true,
});
});
describe('カラーコード検証', () => {
it('should accept valid HEX color codes', () => {
// Arrange
const validTheme = createValidTheme();
// Act
const validation = validator.validateTheme(validTheme);
// Assert
expect(validation.isValid).toBe(true);
expect(validation.errors).toHaveLength(0);
});
describe.each([
['#000000', true],
['#FFFFFF', true],
['#5a6c7d', true],
['#F59E0B', true],
['#ggg', false],
['rgb(255,0,0)', false],
['#ff', false],
['#1234567', false],
['red', false],
['', false],
['invalid-color', false],
['#gggggg', false],
])('color validation: %s should be %s', (color, isValid) => {
it(`should ${isValid ? 'accept' : 'reject'} color: ${color}`, () => {
// Arrange
const theme = createValidTheme();
theme.colors.primary_color = color;
// Act
const validation = validator.validateTheme(theme);
// Assert
expect(validation.isValid).toBe(isValid);
if (!isValid) {
expect(validation.errors.some((err) => err.includes('primary_color'))).toBe(true);
}
});
});
it('should detect multiple invalid color codes', () => {
// Arrange
const invalidTheme = createInvalidColorTheme();
// Act
const validation = validator.validateTheme(invalidTheme);
// Assert
expect(validation.isValid).toBe(false);
expect(validation.errors).toContain('primary_color: 無効なカラーコード形式 (invalid-color)');
expect(validation.errors).toContain('secondary_color: 無効なカラーコード形式 (#gggggg)');
});
});
describe('アクセシビリティ検証', () => {
it('should not generate warnings for high contrast combinations', () => {
// Arrange
const highContrastTheme = createHighContrastTheme();
// Act
const validation = validator.validateTheme(highContrastTheme);
// Assert
const contrastWarnings = validation.warnings.filter((w) => w.includes('コントラスト比'));
expect(contrastWarnings).toHaveLength(0);
});
it('should generate warnings for low contrast combinations', () => {
// Arrange
const lowContrastTheme = createLowContrastTheme();
// Act
const validation = validator.validateTheme(lowContrastTheme);
// Assert
expect(validation.warnings.length).toBeGreaterThan(0);
expect(validation.warnings.some((w) => w.includes('コントラスト比が低すぎます'))).toBe(true);
});
it('should check multiple color combinations for accessibility', () => {
// Arrange
const theme = createValidTheme();
theme.colors.primary_text_color = '#dddddd';
theme.colors.primary_background_color = '#ffffff';
theme.colors.secondary_text_color = '#eeeeee';
theme.colors.secondary_background_color = '#ffffff';
// Act
const validation = validator.validateTheme(theme);
// Assert
const contrastWarnings = validation.warnings.filter((w) => w.includes('コントラスト比'));
expect(contrastWarnings.length).toBeGreaterThanOrEqual(1);
});
it('should handle accessibility check errors gracefully', () => {
// Arrange
const themeWithInvalidColors = createValidTheme();
themeWithInvalidColors.colors.primary_text_color = 'invalid';
const validatorNoColorCheck = new ThemeCustomizer({
validateColors: false,
checkAccessibility: true,
});
// Act
const validation = validatorNoColorCheck.validateTheme(themeWithInvalidColors);
// Assert
expect(validation).toBeDefined();
// エラーが発生してもvalidationオブジェクトは返される
});
});
describe('複合検証', () => {
it('should handle themes with both color and accessibility issues', () => {
// Arrange
const problematicTheme = createValidTheme();
problematicTheme.colors.primary_color = 'invalid';
problematicTheme.colors.primary_text_color = '#cccccc';
problematicTheme.colors.primary_background_color = '#ffffff';
// Act
const validation = validator.validateTheme(problematicTheme);
// Assert
expect(validation.isValid).toBe(false);
expect(validation.errors.length).toBeGreaterThan(0);
expect(validation.warnings.length).toBeGreaterThan(0);
});
});
});
describe('キャッシュ機能', () => {
let cachedCustomizer: ThemeCustomizer;
let nonCachedCustomizer: ThemeCustomizer;
beforeEach(() => {
cachedCustomizer = new ThemeCustomizer({ enableCache: true });
nonCachedCustomizer = new ThemeCustomizer({ enableCache: false });
});
describe('基本動作', () => {
it('should cache result on first generation and hit cache on second', () => {
// Arrange
const theme = createValidTheme();
// Act
const result1 = cachedCustomizer.generateThemedCSS(theme);
const result2 = cachedCustomizer.generateThemedCSS(theme);
// Assert
expect(result1.cacheHit).toBe(false);
expect(result2.cacheHit).toBe(true);
expect(result2.fullCSS).toBe(result1.fullCSS);
});
it('should not hit cache for different themes', () => {
// Arrange
const theme1 = createValidTheme();
const theme2 = createValidTheme();
theme2.colors.primary_color = '#ff0000';
// Act
const result1 = cachedCustomizer.generateThemedCSS(theme1);
const result2 = cachedCustomizer.generateThemedCSS(theme2);
// Assert
expect(result1.cacheHit).toBe(false);
expect(result2.cacheHit).toBe(false);
expect(result2.fullCSS).not.toBe(result1.fullCSS);
});
it('should not use cache when caching is disabled', () => {
// Arrange
const theme = createValidTheme();
// Act
const result1 = nonCachedCustomizer.generateThemedCSS(theme);
const result2 = nonCachedCustomizer.generateThemedCSS(theme);
// Assert
expect(result1.cacheHit).toBe(false);
expect(result2.cacheHit).toBe(false);
});
it('should clear cache successfully', () => {
// Arrange
const theme = createValidTheme();
cachedCustomizer.generateThemedCSS(theme);
// Act
cachedCustomizer.clearCache();
const stats = cachedCustomizer.getCacheStats();
const result = cachedCustomizer.generateThemedCSS(theme);
// Assert
expect(stats.size).toBe(0);
expect(result.cacheHit).toBe(false);
});
});
describe('TTL管理', () => {
it('should respect cache TTL expiration', () => {
// Arrange
const theme = createValidTheme();
const shortTTLCustomizer = new ThemeCustomizer({ enableCache: true });
// TTLを短く設定するためにプライベートプロパティにアクセス
// 理由: TTL期限切れロジックをテストするため、実行時間内でテスト可能な短い値に設定
// 代替手段: 依存性注入やテスト用設定があれば理想的だが、現在の設計では直接アクセスが最適
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const originalTTL = (shortTTLCustomizer as any).CACHE_TTL_MS;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(shortTTLCustomizer as any).CACHE_TTL_MS = 1; // 1ms
// Act
const result1 = shortTTLCustomizer.generateThemedCSS(theme);
// Wait for TTL expiration
return new Promise((resolve) => {
setTimeout(() => {
const result2 = shortTTLCustomizer.generateThemedCSS(theme);
// Assert
expect(result1.cacheHit).toBe(false);
expect(result2.cacheHit).toBe(false); // TTL切れでキャッシュミス
// Restore original TTL
// 理由: テスト後の状態復元のため、変更したプライベートプロパティを元に戻す
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(shortTTLCustomizer as any).CACHE_TTL_MS = originalTTL;
resolve(undefined);
}, 10);
});
});
});
describe('サイズ制限', () => {
it('should respect maximum cache size limit', () => {
// Arrange
const maxSize = 3;
const limitedCustomizer = new ThemeCustomizer({ enableCache: true });
// 理由: キャッシュサイズ制限ロジックをテストするため、テスト用の小さな値に動的変更
// 代替手段: コンストラクタオプションで設定可能にする設計変更が理想的
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(limitedCustomizer as any).MAX_CACHE_SIZE = maxSize;
// Act - キャッシュサイズを超えるまでテーマを生成
const themes = [];
for (let i = 0; i < maxSize + 2; i++) {
const theme = createValidTheme();
theme.colors.primary_color = `#${i.toString().padStart(6, '0')}`;
themes.push(theme);
limitedCustomizer.generateThemedCSS(theme);
}
// Assert
const stats = limitedCustomizer.getCacheStats();
expect(stats.size).toBeLessThanOrEqual(maxSize);
expect(stats.maxSize).toBe(maxSize);
// 最初のテーマはキャッシュから削除されているはず
const firstThemeResult = limitedCustomizer.generateThemedCSS(themes[0]);
expect(firstThemeResult.cacheHit).toBe(false);
});
it('should provide accurate cache statistics', () => {
// Arrange
const theme1 = createValidTheme();
const theme2 = createValidTheme();
theme2.colors.primary_color = '#ff0000';
// Act
cachedCustomizer.generateThemedCSS(theme1);
cachedCustomizer.generateThemedCSS(theme2);
const stats = cachedCustomizer.getCacheStats();
// Assert
expect(stats.size).toBe(2);
expect(stats.maxSize).toBe(100);
});
});
});
describe('パフォーマンス', () => {
let performanceCustomizer: ThemeCustomizer;
beforeEach(() => {
performanceCustomizer = new ThemeCustomizer({
enableCache: true,
validateColors: true,
checkAccessibility: true,
});
});
it('should complete CSS generation within 100ms', () => {
// Arrange
const theme = createValidTheme();
// Act
const result = performanceCustomizer.generateThemedCSS(theme);
// Assert
expect(result.generationTimeMs).toBeLessThan(100);
expect(result.generationTimeMs).toBeGreaterThan(0);
});
it('should process cache hits significantly faster', () => {
// Arrange
const theme = createValidTheme();
performanceCustomizer.generateThemedCSS(theme); // 初回実行でキャッシュ
// Act
const result = performanceCustomizer.generateThemedCSS(theme);
// Assert
expect(result.cacheHit).toBe(true);
expect(result.generationTimeMs).toBeLessThan(10); // キャッシュヒットは10ms以内
});
it('should handle multiple themes efficiently', () => {
// Arrange
const themes = Array.from({ length: 10 }, (_, i) => {
const theme = createValidTheme();
theme.colors.primary_color = `#${i.toString().padStart(6, '0')}`;
return theme;
});
// Act
const startTime = performance.now();
const results = themes.map((theme) => performanceCustomizer.generateThemedCSS(theme));
const totalTime = performance.now() - startTime;
// Assert
expect(totalTime).toBeLessThan(500); // 10テーマで500ms以内
expect(results).toHaveLength(10);
results.forEach((result) => {
expect(result.fullCSS).toBeDefined();
});
});
});
describe('エラーハンドリング', () => {
describe('バリデーションエラー', () => {
it('should return fallback CSS when validation fails', () => {
// Arrange
const invalidTheme = createInvalidColorTheme();
const validatingCustomizer = new ThemeCustomizer({ validateColors: true });
// Act
const result = validatingCustomizer.generateThemedCSS(invalidTheme);
// Assert
expect(result.fullCSS).toBeDefined();
expect(result.themeCSS).toBe(''); // テーマCSSは空
expect(result.baseCSS).toBeDefined(); // ベースCSSは提供される
expect(result.cacheHit).toBe(false);
});
it('should generate CSS with invalid colors when validation disabled', () => {
// Arrange
const invalidTheme = createInvalidColorTheme();
const nonValidatingCustomizer = new ThemeCustomizer({ validateColors: false });
// Act
const result = nonValidatingCustomizer.generateThemedCSS(invalidTheme);
// Assert
expect(result.fullCSS).toBeDefined();
expect(result.themeCSS).toContain('--extracted-primary-color: invalid-color');
});
});
describe('実行時エラー', () => {
it('should handle errors during CSS generation gracefully', () => {
// Arrange
const problematicTheme = createValidTheme();
const customizer = new ThemeCustomizer();
// Mock implementation to force an error
// 理由: エラーハンドリングロジックをテストするため、意図的にエラーを発生させる必要
// 代替手段: 依存性注入があれば理想的だが、現在の設計では直接モックが最適
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const originalMethod = (customizer as any).combineCSS;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(customizer as any).combineCSS = vi.fn(() => {
throw new Error('CSS combination failed');
});
// Act
const result = customizer.generateThemedCSS(problematicTheme);
// Assert
expect(result.fullCSS).toBeDefined(); // フォールバックが返される
expect(result.themeCSS).toBe('');
// Restore original method
// 理由: テスト後の状態復元のため、モックしたメソッドを元に戻す
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(customizer as any).combineCSS = originalMethod;
});
});
describe('フォールバック動作', () => {
it('should always return a usable CSS structure even on complete failure', () => {
// Arrange
const customizer = new ThemeCustomizer({ validateColors: true });
const extremelyInvalidTheme = {
// 理由: 完全に無効な構造でのフォールバック動作をテストするため
// 代替手段: 個別の無効プロパティテストも可能だが、最悪ケースのテストには全体無効化が必要
// 用途: validateTheme()でObject.entries()が空オブジェクトを処理する際のフォールバック確認
// eslint-disable-next-line @typescript-eslint/no-explicit-any
colors: {} as any, // 完全に無効な構造
// eslint-disable-next-line @typescript-eslint/no-explicit-any
design: {} as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
brand: {} as any,
analysis_notes: '',
};
// Act
const result = customizer.generateThemedCSS(extremelyInvalidTheme);
// Assert
expect(result).toBeDefined();
expect(result.fullCSS).toBeDefined();
expect(typeof result.fullCSS).toBe('string');
expect(result.fullCSS.length).toBeGreaterThan(0);
});
});
});
describe('デバッグ機能とユーティリティ', () => {
let debugCustomizer: ThemeCustomizer;
beforeEach(() => {
debugCustomizer = new ThemeCustomizer({
enableCache: true,
validateColors: true,
checkAccessibility: true,
});
});
describe('デバッグ情報提供', () => {
it('should provide comprehensive debug information', () => {
// Arrange
const theme = createValidTheme();
// Act
const debug = debugCustomizer.debugTheme(theme);
// Assert
expect(debug.validation).toBeDefined();
expect(debug.validation.isValid).toBeDefined();
expect(debug.cacheKey).toBeDefined();
expect(typeof debug.cacheKey).toBe('string');
expect(debug.generatedVariables).toBeDefined();
expect(debug.generatedVariables).toContain('--extracted-primary-color');
});
it('should provide debug info for invalid themes too', () => {
// Arrange
const invalidTheme = createInvalidColorTheme();
// Act
const debug = debugCustomizer.debugTheme(invalidTheme);
// Assert
expect(debug.validation).toBeDefined();
expect(debug.validation.isValid).toBe(false);
expect(debug.validation.errors.length).toBeGreaterThan(0);
expect(debug.cacheKey).toBeDefined();
});
});
describe('キャッシュ統計', () => {
it('should provide accurate cache statistics', () => {
// Arrange
const themes = [createValidTheme(), createHighContrastTheme(), createLowContrastTheme()];
// Act
themes.forEach((theme) => debugCustomizer.generateThemedCSS(theme));
const stats = debugCustomizer.getCacheStats();
// Assert
expect(stats.size).toBe(3);
expect(stats.maxSize).toBeDefined();
expect(stats.maxSize).toBe(100);
expect(typeof stats.size).toBe('number');
expect(typeof stats.maxSize).toBe('number');
});
it('should track cache size changes correctly', () => {
// Arrange
const theme = createValidTheme();
// Act & Assert
const initialStats = debugCustomizer.getCacheStats();
expect(initialStats.size).toBe(0);
debugCustomizer.generateThemedCSS(theme);
const afterGenerationStats = debugCustomizer.getCacheStats();
expect(afterGenerationStats.size).toBe(1);
debugCustomizer.clearCache();
const afterClearStats = debugCustomizer.getCacheStats();
expect(afterClearStats.size).toBe(0);
});
});
describe('ユーティリティ関数のカバレッジ', () => {
it('should handle hash generation for different inputs', () => {
// Arrange
const theme1 = createValidTheme();
const theme2 = createValidTheme();
theme2.colors.primary_color = '#ff0000';
// Act
const debug1 = debugCustomizer.debugTheme(theme1);
const debug2 = debugCustomizer.debugTheme(theme2);
// Assert
expect(debug1.cacheKey).not.toBe(debug2.cacheKey);
expect(debug1.cacheKey).toBe(debugCustomizer.debugTheme(theme1).cacheKey); // 同じ入力で同じハッシュ
});
it('should handle color utility functions with edge cases', () => {
// Arrange
const themeWithEdgeCaseColors = createValidTheme();
themeWithEdgeCaseColors.colors.primary_text_color = '#000000';
themeWithEdgeCaseColors.colors.primary_background_color = '#ffffff';
// Act
const validation = debugCustomizer.validateTheme(themeWithEdgeCaseColors);
// Assert
expect(validation).toBeDefined();
// エッジケース(最高コントラスト)でも正常に動作する
});
});
describe('プライベートメソッドのテスト(パブリックメソッド経由)', () => {
it('should calculate luminance values correctly through contrast ratio check', () => {
// Arrange - HEX色の輝度計算をテストするため、既知のコントラスト比を持つ色を使用
const blackOnWhiteTheme = createValidTheme();
blackOnWhiteTheme.colors.primary_text_color = '#000000';
blackOnWhiteTheme.colors.primary_background_color = '#ffffff';
const grayOnWhiteTheme = createValidTheme();
grayOnWhiteTheme.colors.primary_text_color = '#777777';
grayOnWhiteTheme.colors.primary_background_color = '#ffffff';
// Act
const blackOnWhiteValidation = debugCustomizer.validateTheme(blackOnWhiteTheme);
const grayOnWhiteValidation = debugCustomizer.validateTheme(grayOnWhiteTheme);
// Assert - 黒/白は高コントラスト、グレー/白は低コントラスト
const blackWarnings = blackOnWhiteValidation.warnings.filter((w) => w.includes('コントラスト比'));
const grayWarnings = grayOnWhiteValidation.warnings.filter((w) => w.includes('コントラスト比'));
expect(blackWarnings).toHaveLength(0); // 高コントラストなので警告なし
expect(grayWarnings.length).toBeGreaterThan(0); // 低コントラストなので警告あり
});
it('should convert HEX to RGB correctly through contrast calculation', () => {
// Arrange - RGB変換をテストするため、様々なHEX値でテーマを作成
const themes = [
{ hex: '#000000', expectHighContrast: true },
{ hex: '#ffffff', expectLowContrast: true },
{ hex: '#ff0000', expectMediumContrast: true },
{ hex: '#cccccc', expectLowContrast: true },
].map(({ hex, expectHighContrast, expectLowContrast, expectMediumContrast }) => {
const theme = createValidTheme();
theme.colors.primary_text_color = hex;
theme.colors.primary_background_color = '#ffffff';
return { theme, expectHighContrast, expectLowContrast, expectMediumContrast };
});
// Act & Assert
themes.forEach(({ theme }) => {
const validation = debugCustomizer.validateTheme(theme);
// RGB変換が正常に動作していることを間接的に確認
// (hexToRgb/getLuminanceが正常でないとvalidateThemeが例外を投げる)
expect(validation).toBeDefined();
expect(validation.isValid).toBeDefined();
});
});
it('should generate consistent hash values for identical inputs', () => {
// Arrange - simpleHashメソッドのテスト
const theme = createValidTheme();
// Act - 同じテーマで複数回ハッシュ生成
const debug1 = debugCustomizer.debugTheme(theme);
const debug2 = debugCustomizer.debugTheme(theme);
const debug3 = debugCustomizer.debugTheme(theme);
// Assert - 同じ入力に対して常に同じハッシュが生成される
expect(debug1.cacheKey).toBe(debug2.cacheKey);
expect(debug2.cacheKey).toBe(debug3.cacheKey);
expect(debug1.cacheKey.length).toBeGreaterThan(0);
});
it('should generate different hash values for different inputs', () => {
// Arrange - 異なるテーマでハッシュ値の違いをテスト
const themes = Array.from({ length: 5 }, (_, i) => {
const theme = createValidTheme();
theme.colors.primary_color = `#${i.toString().padStart(6, '0')}`;
return theme;
});
// Act
const hashes = themes.map((theme) => debugCustomizer.debugTheme(theme).cacheKey);
// Assert - 全て異なるハッシュ値が生成される
const uniqueHashes = new Set(hashes);
expect(uniqueHashes.size).toBe(themes.length);
});
});
describe('境界値テストケース', () => {
it('should handle boundary value theme data', () => {
// Arrange
const boundaryTheme = createBoundaryValueTheme();
// Act
const result = debugCustomizer.generateThemedCSS(boundaryTheme);
const validation = debugCustomizer.validateTheme(boundaryTheme);
// Assert
expect(result).toBeDefined();
expect(validation).toBeDefined();
// 境界値でも処理が完了する
});
it('should handle performance stress test with large data', () => {
// Arrange
const performanceTheme = createPerformanceTestTheme();
const customizer = new ThemeCustomizer({
validateColors: false, // パフォーマンステストなのでバリデーション無効
checkAccessibility: false,
});
// Act
const startTime = performance.now();
const result = customizer.generateThemedCSS(performanceTheme);
const endTime = performance.now();
// Assert
expect(result).toBeDefined();
expect(result.fullCSS).toBeDefined();
expect(endTime - startTime).toBeLessThan(1000); // 大量データでも1秒以内
});
});
});
});