@forgespace/branding-mcp 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- package/.github/workflows/ci.yml +73 -0
- package/.github/workflows/release-automation.yml +56 -0
- package/.github/workflows/security-scan.yml +37 -0
- package/.gitleaks.toml +14 -0
- package/.prettierrc.json +10 -0
- package/CHANGELOG.md +66 -0
- package/CONTRIBUTING.md +203 -0
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/data/README.md +13 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/branding-core/ai/brand-interpreter.d.ts +5 -0
- package/dist/lib/branding-core/ai/brand-interpreter.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/brand-interpreter.js +16 -0
- package/dist/lib/branding-core/ai/brand-interpreter.js.map +1 -0
- package/dist/lib/branding-core/ai/claude-interpreter.d.ts +5 -0
- package/dist/lib/branding-core/ai/claude-interpreter.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/claude-interpreter.js +55 -0
- package/dist/lib/branding-core/ai/claude-interpreter.js.map +1 -0
- package/dist/lib/branding-core/ai/intent-applier.d.ts +4 -0
- package/dist/lib/branding-core/ai/intent-applier.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/intent-applier.js +29 -0
- package/dist/lib/branding-core/ai/intent-applier.js.map +1 -0
- package/dist/lib/branding-core/ai/keyword-interpreter.d.ts +4 -0
- package/dist/lib/branding-core/ai/keyword-interpreter.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/keyword-interpreter.js +85 -0
- package/dist/lib/branding-core/ai/keyword-interpreter.js.map +1 -0
- package/dist/lib/branding-core/ai/prompts.d.ts +4 -0
- package/dist/lib/branding-core/ai/prompts.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/prompts.js +79 -0
- package/dist/lib/branding-core/ai/prompts.js.map +1 -0
- package/dist/lib/branding-core/ai/types.d.ts +27 -0
- package/dist/lib/branding-core/ai/types.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/types.js +2 -0
- package/dist/lib/branding-core/ai/types.js.map +1 -0
- package/dist/lib/branding-core/documents/html-generator.d.ts +3 -0
- package/dist/lib/branding-core/documents/html-generator.d.ts.map +1 -0
- package/dist/lib/branding-core/documents/html-generator.js +31 -0
- package/dist/lib/branding-core/documents/html-generator.js.map +1 -0
- package/dist/lib/branding-core/documents/pdf-generator.d.ts +3 -0
- package/dist/lib/branding-core/documents/pdf-generator.d.ts.map +1 -0
- package/dist/lib/branding-core/documents/pdf-generator.js +20 -0
- package/dist/lib/branding-core/documents/pdf-generator.js.map +1 -0
- package/dist/lib/branding-core/exporters/css-variables.d.ts +3 -0
- package/dist/lib/branding-core/exporters/css-variables.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/css-variables.js +62 -0
- package/dist/lib/branding-core/exporters/css-variables.js.map +1 -0
- package/dist/lib/branding-core/exporters/design-tokens.d.ts +3 -0
- package/dist/lib/branding-core/exporters/design-tokens.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/design-tokens.js +75 -0
- package/dist/lib/branding-core/exporters/design-tokens.js.map +1 -0
- package/dist/lib/branding-core/exporters/figma-tokens.d.ts +9 -0
- package/dist/lib/branding-core/exporters/figma-tokens.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/figma-tokens.js +69 -0
- package/dist/lib/branding-core/exporters/figma-tokens.js.map +1 -0
- package/dist/lib/branding-core/exporters/react-theme.d.ts +3 -0
- package/dist/lib/branding-core/exporters/react-theme.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/react-theme.js +61 -0
- package/dist/lib/branding-core/exporters/react-theme.js.map +1 -0
- package/dist/lib/branding-core/exporters/sass-variables.d.ts +3 -0
- package/dist/lib/branding-core/exporters/sass-variables.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/sass-variables.js +65 -0
- package/dist/lib/branding-core/exporters/sass-variables.js.map +1 -0
- package/dist/lib/branding-core/exporters/tailwind-preset.d.ts +3 -0
- package/dist/lib/branding-core/exporters/tailwind-preset.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/tailwind-preset.js +55 -0
- package/dist/lib/branding-core/exporters/tailwind-preset.js.map +1 -0
- package/dist/lib/branding-core/generators/border-system.d.ts +3 -0
- package/dist/lib/branding-core/generators/border-system.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/border-system.js +37 -0
- package/dist/lib/branding-core/generators/border-system.js.map +1 -0
- package/dist/lib/branding-core/generators/color-palette.d.ts +7 -0
- package/dist/lib/branding-core/generators/color-palette.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/color-palette.js +117 -0
- package/dist/lib/branding-core/generators/color-palette.js.map +1 -0
- package/dist/lib/branding-core/generators/favicon-generator.d.ts +3 -0
- package/dist/lib/branding-core/generators/favicon-generator.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/favicon-generator.js +23 -0
- package/dist/lib/branding-core/generators/favicon-generator.js.map +1 -0
- package/dist/lib/branding-core/generators/gradient-system.d.ts +3 -0
- package/dist/lib/branding-core/generators/gradient-system.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/gradient-system.js +74 -0
- package/dist/lib/branding-core/generators/gradient-system.js.map +1 -0
- package/dist/lib/branding-core/generators/logo-generator.d.ts +4 -0
- package/dist/lib/branding-core/generators/logo-generator.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/logo-generator.js +130 -0
- package/dist/lib/branding-core/generators/logo-generator.js.map +1 -0
- package/dist/lib/branding-core/generators/motion-system.d.ts +3 -0
- package/dist/lib/branding-core/generators/motion-system.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/motion-system.js +91 -0
- package/dist/lib/branding-core/generators/motion-system.js.map +1 -0
- package/dist/lib/branding-core/generators/og-image-generator.d.ts +3 -0
- package/dist/lib/branding-core/generators/og-image-generator.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/og-image-generator.js +72 -0
- package/dist/lib/branding-core/generators/og-image-generator.js.map +1 -0
- package/dist/lib/branding-core/generators/shadow-system.d.ts +3 -0
- package/dist/lib/branding-core/generators/shadow-system.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/shadow-system.js +44 -0
- package/dist/lib/branding-core/generators/shadow-system.js.map +1 -0
- package/dist/lib/branding-core/generators/spacing-scale.d.ts +3 -0
- package/dist/lib/branding-core/generators/spacing-scale.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/spacing-scale.js +27 -0
- package/dist/lib/branding-core/generators/spacing-scale.js.map +1 -0
- package/dist/lib/branding-core/generators/typography-system.d.ts +3 -0
- package/dist/lib/branding-core/generators/typography-system.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/typography-system.js +121 -0
- package/dist/lib/branding-core/generators/typography-system.js.map +1 -0
- package/dist/lib/branding-core/index.d.ts +24 -0
- package/dist/lib/branding-core/index.d.ts.map +1 -0
- package/dist/lib/branding-core/index.js +22 -0
- package/dist/lib/branding-core/index.js.map +1 -0
- package/dist/lib/branding-core/validators/brand-consistency.d.ts +3 -0
- package/dist/lib/branding-core/validators/brand-consistency.d.ts.map +1 -0
- package/dist/lib/branding-core/validators/brand-consistency.js +70 -0
- package/dist/lib/branding-core/validators/brand-consistency.js.map +1 -0
- package/dist/lib/branding-core/validators/contrast-checker.d.ts +3 -0
- package/dist/lib/branding-core/validators/contrast-checker.d.ts.map +1 -0
- package/dist/lib/branding-core/validators/contrast-checker.js +33 -0
- package/dist/lib/branding-core/validators/contrast-checker.js.map +1 -0
- package/dist/lib/branding-core/validators/token-schema.d.ts +10 -0
- package/dist/lib/branding-core/validators/token-schema.d.ts.map +1 -0
- package/dist/lib/branding-core/validators/token-schema.js +43 -0
- package/dist/lib/branding-core/validators/token-schema.js.map +1 -0
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +8 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/logger.d.ts +3 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +10 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/types.d.ts +208 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/resources/brand-knowledge.d.ts +3 -0
- package/dist/resources/brand-knowledge.d.ts.map +1 -0
- package/dist/resources/brand-knowledge.js +53 -0
- package/dist/resources/brand-knowledge.js.map +1 -0
- package/dist/resources/brand-templates.d.ts +3 -0
- package/dist/resources/brand-templates.d.ts.map +1 -0
- package/dist/resources/brand-templates.js +68 -0
- package/dist/resources/brand-templates.js.map +1 -0
- package/dist/tools/create-brand-guidelines.d.ts +3 -0
- package/dist/tools/create-brand-guidelines.d.ts.map +1 -0
- package/dist/tools/create-brand-guidelines.js +85 -0
- package/dist/tools/create-brand-guidelines.js.map +1 -0
- package/dist/tools/export-design-tokens.d.ts +3 -0
- package/dist/tools/export-design-tokens.d.ts.map +1 -0
- package/dist/tools/export-design-tokens.js +37 -0
- package/dist/tools/export-design-tokens.js.map +1 -0
- package/dist/tools/generate-brand-assets.d.ts +3 -0
- package/dist/tools/generate-brand-assets.d.ts.map +1 -0
- package/dist/tools/generate-brand-assets.js +37 -0
- package/dist/tools/generate-brand-assets.js.map +1 -0
- package/dist/tools/generate-brand-identity.d.ts +3 -0
- package/dist/tools/generate-brand-identity.d.ts.map +1 -0
- package/dist/tools/generate-brand-identity.js +73 -0
- package/dist/tools/generate-brand-identity.js.map +1 -0
- package/dist/tools/generate-color-palette.d.ts +3 -0
- package/dist/tools/generate-color-palette.d.ts.map +1 -0
- package/dist/tools/generate-color-palette.js +33 -0
- package/dist/tools/generate-color-palette.js.map +1 -0
- package/dist/tools/generate-typography-system.d.ts +3 -0
- package/dist/tools/generate-typography-system.d.ts.map +1 -0
- package/dist/tools/generate-typography-system.js +28 -0
- package/dist/tools/generate-typography-system.js.map +1 -0
- package/dist/tools/refine-brand-element.d.ts +3 -0
- package/dist/tools/refine-brand-element.d.ts.map +1 -0
- package/dist/tools/refine-brand-element.js +41 -0
- package/dist/tools/refine-brand-element.js.map +1 -0
- package/dist/tools/validate-brand-consistency.d.ts +3 -0
- package/dist/tools/validate-brand-consistency.d.ts.map +1 -0
- package/dist/tools/validate-brand-consistency.js +25 -0
- package/dist/tools/validate-brand-consistency.js.map +1 -0
- package/docs/API.md +110 -0
- package/docs/DATA_SOURCES.md +69 -0
- package/docs/INTEGRATION.md +58 -0
- package/eslint.config.js +52 -0
- package/jest.config.js +40 -0
- package/package.json +78 -0
- package/src/__tests__/integration/brand-generation.test.ts +84 -0
- package/src/__tests__/integration/mcp-server.test.ts +18 -0
- package/src/__tests__/unit/ai-interpreter.test.ts +172 -0
- package/src/__tests__/unit/border-system.test.ts +77 -0
- package/src/__tests__/unit/color-palette.test.ts +161 -0
- package/src/__tests__/unit/contrast-checker.test.ts +124 -0
- package/src/__tests__/unit/design-tokens.test.ts +184 -0
- package/src/__tests__/unit/favicon-generator.test.ts +80 -0
- package/src/__tests__/unit/gradient-system.test.ts +122 -0
- package/src/__tests__/unit/logo-generator.test.ts +146 -0
- package/src/__tests__/unit/motion-system.test.ts +91 -0
- package/src/__tests__/unit/og-image-generator.test.ts +115 -0
- package/src/__tests__/unit/shadow-system.test.ts +63 -0
- package/src/__tests__/unit/spacing-scale.test.ts +60 -0
- package/src/__tests__/unit/typography-system.test.ts +71 -0
- package/src/index.ts +59 -0
- package/src/lib/branding-core/ai/brand-interpreter.ts +30 -0
- package/src/lib/branding-core/ai/claude-interpreter.ts +76 -0
- package/src/lib/branding-core/ai/intent-applier.ts +59 -0
- package/src/lib/branding-core/ai/keyword-interpreter.ts +95 -0
- package/src/lib/branding-core/ai/prompts.ts +93 -0
- package/src/lib/branding-core/ai/types.ts +36 -0
- package/src/lib/branding-core/documents/html-generator.ts +32 -0
- package/src/lib/branding-core/documents/pdf-generator.ts +21 -0
- package/src/lib/branding-core/exporters/css-variables.ts +71 -0
- package/src/lib/branding-core/exporters/design-tokens.ts +86 -0
- package/src/lib/branding-core/exporters/figma-tokens.ts +87 -0
- package/src/lib/branding-core/exporters/react-theme.ts +69 -0
- package/src/lib/branding-core/exporters/sass-variables.ts +74 -0
- package/src/lib/branding-core/exporters/tailwind-preset.ts +67 -0
- package/src/lib/branding-core/generators/border-system.ts +41 -0
- package/src/lib/branding-core/generators/color-palette.ts +147 -0
- package/src/lib/branding-core/generators/favicon-generator.ts +33 -0
- package/src/lib/branding-core/generators/gradient-system.ts +120 -0
- package/src/lib/branding-core/generators/logo-generator.ts +152 -0
- package/src/lib/branding-core/generators/motion-system.ts +98 -0
- package/src/lib/branding-core/generators/og-image-generator.ts +97 -0
- package/src/lib/branding-core/generators/shadow-system.ts +66 -0
- package/src/lib/branding-core/generators/spacing-scale.ts +29 -0
- package/src/lib/branding-core/generators/typography-system.ts +128 -0
- package/src/lib/branding-core/index.ts +28 -0
- package/src/lib/branding-core/validators/brand-consistency.ts +79 -0
- package/src/lib/branding-core/validators/contrast-checker.ts +37 -0
- package/src/lib/branding-core/validators/token-schema.ts +50 -0
- package/src/lib/config.ts +13 -0
- package/src/lib/logger.ts +12 -0
- package/src/lib/types.ts +236 -0
- package/src/resources/brand-knowledge.ts +60 -0
- package/src/resources/brand-templates.ts +70 -0
- package/src/tools/create-brand-guidelines.ts +94 -0
- package/src/tools/export-design-tokens.ts +52 -0
- package/src/tools/generate-brand-assets.ts +48 -0
- package/src/tools/generate-brand-identity.ts +115 -0
- package/src/tools/generate-color-palette.ts +43 -0
- package/src/tools/generate-typography-system.ts +42 -0
- package/src/tools/refine-brand-element.ts +65 -0
- package/src/tools/validate-brand-consistency.ts +32 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { exportDesignTokens } from '../../lib/branding-core/exporters/design-tokens.js';
|
|
2
|
+
import { exportCssVariables } from '../../lib/branding-core/exporters/css-variables.js';
|
|
3
|
+
import { exportTailwindPreset } from '../../lib/branding-core/exporters/tailwind-preset.js';
|
|
4
|
+
import { exportSassVariables } from '../../lib/branding-core/exporters/sass-variables.js';
|
|
5
|
+
import { exportReactTheme } from '../../lib/branding-core/exporters/react-theme.js';
|
|
6
|
+
import { exportFigmaTokens } from '../../lib/branding-core/exporters/figma-tokens.js';
|
|
7
|
+
import { generateColorPalette } from '../../lib/branding-core/generators/color-palette.js';
|
|
8
|
+
import { generateTypographySystem } from '../../lib/branding-core/generators/typography-system.js';
|
|
9
|
+
import { generateSpacingScale } from '../../lib/branding-core/generators/spacing-scale.js';
|
|
10
|
+
import { generateShadowSystem } from '../../lib/branding-core/generators/shadow-system.js';
|
|
11
|
+
import { generateBorderSystem } from '../../lib/branding-core/generators/border-system.js';
|
|
12
|
+
import { generateMotionSystem } from '../../lib/branding-core/generators/motion-system.js';
|
|
13
|
+
import type { BrandIdentity } from '../../lib/types.js';
|
|
14
|
+
|
|
15
|
+
function createTestBrand(): BrandIdentity {
|
|
16
|
+
return {
|
|
17
|
+
id: 'test_brand',
|
|
18
|
+
name: 'Test Brand',
|
|
19
|
+
industry: 'tech',
|
|
20
|
+
style: 'tech',
|
|
21
|
+
colors: generateColorPalette('#6B4CE6'),
|
|
22
|
+
typography: generateTypographySystem(),
|
|
23
|
+
spacing: generateSpacingScale(),
|
|
24
|
+
createdAt: '2026-01-01T00:00:00Z',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function createFullTestBrand(): BrandIdentity {
|
|
29
|
+
return {
|
|
30
|
+
...createTestBrand(),
|
|
31
|
+
shadows: generateShadowSystem('#6B4CE6'),
|
|
32
|
+
borders: generateBorderSystem('tech'),
|
|
33
|
+
motion: generateMotionSystem('tech'),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('exportDesignTokens', () => {
|
|
38
|
+
it('exports W3C format tokens', () => {
|
|
39
|
+
const tokens = exportDesignTokens(createTestBrand());
|
|
40
|
+
expect(tokens.$schema).toContain('design-tokens');
|
|
41
|
+
expect(tokens.color).toBeDefined();
|
|
42
|
+
expect(tokens.typography).toBeDefined();
|
|
43
|
+
expect(tokens.spacing).toBeDefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('includes primary color token', () => {
|
|
47
|
+
const tokens = exportDesignTokens(createTestBrand());
|
|
48
|
+
expect(tokens.color.primary).toBeDefined();
|
|
49
|
+
expect(tokens.color.primary.value.$type).toBe('color');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('includes typography font tokens', () => {
|
|
53
|
+
const tokens = exportDesignTokens(createTestBrand());
|
|
54
|
+
expect(tokens.typography['font-heading']).toBeDefined();
|
|
55
|
+
expect(tokens.typography['font-body']).toBeDefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('includes spacing tokens', () => {
|
|
59
|
+
const tokens = exportDesignTokens(createTestBrand());
|
|
60
|
+
expect(Object.keys(tokens.spacing).length).toBeGreaterThan(0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('includes shadow tokens when present', () => {
|
|
64
|
+
const tokens = exportDesignTokens(createFullTestBrand());
|
|
65
|
+
expect(tokens.shadow).toBeDefined();
|
|
66
|
+
expect(tokens.shadow!['sm'].$type).toBe('shadow');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('includes border tokens when present', () => {
|
|
70
|
+
const tokens = exportDesignTokens(createFullTestBrand());
|
|
71
|
+
expect(tokens.border).toBeDefined();
|
|
72
|
+
expect(tokens.border!['radius-md'].$type).toBe('dimension');
|
|
73
|
+
expect(tokens.border!['width-thin'].$type).toBe('dimension');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('includes motion tokens when present', () => {
|
|
77
|
+
const tokens = exportDesignTokens(createFullTestBrand());
|
|
78
|
+
expect(tokens.motion).toBeDefined();
|
|
79
|
+
expect(tokens.motion!['duration-fast'].$type).toBe('duration');
|
|
80
|
+
expect(tokens.motion!['easing-ease-out'].$type).toBe('cubicBezier');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('omits new tokens when not in brand', () => {
|
|
84
|
+
const tokens = exportDesignTokens(createTestBrand());
|
|
85
|
+
expect(tokens.shadow).toBeUndefined();
|
|
86
|
+
expect(tokens.border).toBeUndefined();
|
|
87
|
+
expect(tokens.motion).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('exportCssVariables', () => {
|
|
92
|
+
it('generates valid CSS custom properties', () => {
|
|
93
|
+
const css = exportCssVariables(createTestBrand());
|
|
94
|
+
expect(css).toContain(':root {');
|
|
95
|
+
expect(css).toContain('--color-primary:');
|
|
96
|
+
expect(css).toContain('--font-heading:');
|
|
97
|
+
expect(css).toContain('--spacing-');
|
|
98
|
+
expect(css).toContain('}');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('includes shadow, border, and motion variables', () => {
|
|
102
|
+
const css = exportCssVariables(createFullTestBrand());
|
|
103
|
+
expect(css).toContain('--shadow-sm:');
|
|
104
|
+
expect(css).toContain('--radius-md:');
|
|
105
|
+
expect(css).toContain('--border-thin:');
|
|
106
|
+
expect(css).toContain('--duration-fast:');
|
|
107
|
+
expect(css).toContain('--ease-ease-out:');
|
|
108
|
+
expect(css).toContain('--transition-fade:');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('exportTailwindPreset', () => {
|
|
113
|
+
it('generates valid Tailwind config', () => {
|
|
114
|
+
const preset = exportTailwindPreset(createTestBrand());
|
|
115
|
+
expect(preset).toContain('export default');
|
|
116
|
+
expect(preset).toContain('"primary"');
|
|
117
|
+
expect(preset).toContain('"fontFamily"');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('includes shadow, border, and motion in Tailwind config', () => {
|
|
121
|
+
const preset = exportTailwindPreset(createFullTestBrand());
|
|
122
|
+
expect(preset).toContain('"boxShadow"');
|
|
123
|
+
expect(preset).toContain('"borderRadius"');
|
|
124
|
+
expect(preset).toContain('"borderWidth"');
|
|
125
|
+
expect(preset).toContain('"transitionDuration"');
|
|
126
|
+
expect(preset).toContain('"transitionTimingFunction"');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('exportSassVariables', () => {
|
|
131
|
+
it('generates valid Sass variables', () => {
|
|
132
|
+
const sass = exportSassVariables(createTestBrand());
|
|
133
|
+
expect(sass).toContain('$color-primary:');
|
|
134
|
+
expect(sass).toContain('$font-heading:');
|
|
135
|
+
expect(sass).toContain('$spacing-');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('includes shadow, border, and motion variables', () => {
|
|
139
|
+
const sass = exportSassVariables(createFullTestBrand());
|
|
140
|
+
expect(sass).toContain('$shadow-md:');
|
|
141
|
+
expect(sass).toContain('$radius-lg:');
|
|
142
|
+
expect(sass).toContain('$border-thick:');
|
|
143
|
+
expect(sass).toContain('$duration-normal:');
|
|
144
|
+
expect(sass).toContain('$ease-spring:');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('exportReactTheme', () => {
|
|
149
|
+
it('generates valid React theme', () => {
|
|
150
|
+
const theme = exportReactTheme(createTestBrand());
|
|
151
|
+
expect(theme).toContain('export const theme');
|
|
152
|
+
expect(theme).toContain('export type Theme');
|
|
153
|
+
expect(theme).toContain('"primary"');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('includes shadow, border, and motion in React theme', () => {
|
|
157
|
+
const theme = exportReactTheme(createFullTestBrand());
|
|
158
|
+
expect(theme).toContain('"shadows"');
|
|
159
|
+
expect(theme).toContain('"radii"');
|
|
160
|
+
expect(theme).toContain('"borderWidths"');
|
|
161
|
+
expect(theme).toContain('"motion"');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('exportFigmaTokens', () => {
|
|
166
|
+
it('generates Figma token structure', () => {
|
|
167
|
+
const tokens = exportFigmaTokens(createTestBrand());
|
|
168
|
+
expect(tokens.color).toBeDefined();
|
|
169
|
+
expect(tokens.typography).toBeDefined();
|
|
170
|
+
expect(tokens.spacing).toBeDefined();
|
|
171
|
+
expect(tokens.color.primary.type).toBe('color');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('includes shadow, border, and motion groups', () => {
|
|
175
|
+
const tokens = exportFigmaTokens(createFullTestBrand());
|
|
176
|
+
expect(tokens.shadow).toBeDefined();
|
|
177
|
+
expect(tokens.shadow!.md.type).toBe('boxShadow');
|
|
178
|
+
expect(tokens.border).toBeDefined();
|
|
179
|
+
expect(tokens.border!['radius-lg'].type).toBe('borderRadius');
|
|
180
|
+
expect(tokens.border!['width-medium'].type).toBe('borderWidth');
|
|
181
|
+
expect(tokens.motion).toBeDefined();
|
|
182
|
+
expect(tokens.motion!['duration-fast'].type).toBe('duration');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { generateFavicons } from '../../lib/branding-core/generators/favicon-generator.js';
|
|
2
|
+
import {
|
|
3
|
+
generateSvgLogo,
|
|
4
|
+
defaultLogoConfig,
|
|
5
|
+
} from '../../lib/branding-core/generators/logo-generator.js';
|
|
6
|
+
import type { FaviconSize } from '../../lib/types.js';
|
|
7
|
+
|
|
8
|
+
const SIZES: FaviconSize[] = [16, 32, 180, 512];
|
|
9
|
+
|
|
10
|
+
describe('generateFavicons', () => {
|
|
11
|
+
const logo = generateSvgLogo(defaultLogoConfig('Test', '#6B4CE6'));
|
|
12
|
+
const iconSvg = logo.variants.icon;
|
|
13
|
+
|
|
14
|
+
it('generates all 4 sizes', () => {
|
|
15
|
+
const favicons = generateFavicons(iconSvg, '#6B4CE6');
|
|
16
|
+
expect(
|
|
17
|
+
Object.keys(favicons.sizes)
|
|
18
|
+
.map(Number)
|
|
19
|
+
.sort((a, b) => a - b)
|
|
20
|
+
).toEqual(SIZES);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('all outputs are valid SVG strings', () => {
|
|
24
|
+
const favicons = generateFavicons(iconSvg, '#6B4CE6');
|
|
25
|
+
for (const size of SIZES) {
|
|
26
|
+
const svg = favicons.sizes[size];
|
|
27
|
+
expect(svg).toContain('xmlns="http://www.w3.org/2000/svg"');
|
|
28
|
+
expect(svg).toContain('</svg>');
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('each size has correct width/height attributes', () => {
|
|
33
|
+
const favicons = generateFavicons(iconSvg, '#6B4CE6');
|
|
34
|
+
for (const size of SIZES) {
|
|
35
|
+
expect(favicons.sizes[size]).toContain(`width="${size}"`);
|
|
36
|
+
expect(favicons.sizes[size]).toContain(`height="${size}"`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('preserves viewBox from icon SVG', () => {
|
|
41
|
+
const favicons = generateFavicons(iconSvg, '#6B4CE6');
|
|
42
|
+
for (const size of SIZES) {
|
|
43
|
+
expect(favicons.sizes[size]).toContain('viewBox="0 0 64 64"');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('16px has optimized stroke-width', () => {
|
|
48
|
+
const svgWithStroke = iconSvg.replace('<circle', '<circle stroke-width="1"');
|
|
49
|
+
const favicons = generateFavicons(svgWithStroke, '#6B4CE6');
|
|
50
|
+
expect(favicons.sizes[16]).toContain('stroke-width="2"');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('32px has optimized stroke-width', () => {
|
|
54
|
+
const svgWithStroke = iconSvg.replace('<circle', '<circle stroke-width="1"');
|
|
55
|
+
const favicons = generateFavicons(svgWithStroke, '#6B4CE6');
|
|
56
|
+
expect(favicons.sizes[32]).toContain('stroke-width="2"');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('512px preserves original stroke-width', () => {
|
|
60
|
+
const svgWithStroke = iconSvg.replace('<circle', '<circle stroke-width="1"');
|
|
61
|
+
const favicons = generateFavicons(svgWithStroke, '#6B4CE6');
|
|
62
|
+
expect(favicons.sizes[512]).toContain('stroke-width="1"');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('works with different brand colors', () => {
|
|
66
|
+
const favicons = generateFavicons(iconSvg, '#FF5500');
|
|
67
|
+
expect(Object.keys(favicons.sizes)).toHaveLength(4);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('uses brand color in SVG', () => {
|
|
71
|
+
const favicons = generateFavicons(iconSvg, '#6B4CE6');
|
|
72
|
+
expect(favicons.sizes[512]).toContain('#6B4CE6');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('180px size for apple-touch-icon', () => {
|
|
76
|
+
const favicons = generateFavicons(iconSvg, '#6B4CE6');
|
|
77
|
+
expect(favicons.sizes[180]).toContain('width="180"');
|
|
78
|
+
expect(favicons.sizes[180]).toContain('height="180"');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { generateGradientSystem } from '../../lib/branding-core/generators/gradient-system.js';
|
|
2
|
+
import { generateColorPalette } from '../../lib/branding-core/generators/color-palette.js';
|
|
3
|
+
import type { BrandStyle, GradientPresetName } from '../../lib/types.js';
|
|
4
|
+
|
|
5
|
+
const PRESET_NAMES: GradientPresetName[] = ['hero', 'button', 'card', 'text', 'background'];
|
|
6
|
+
|
|
7
|
+
const ALL_STYLES: BrandStyle[] = [
|
|
8
|
+
'minimal',
|
|
9
|
+
'bold',
|
|
10
|
+
'elegant',
|
|
11
|
+
'playful',
|
|
12
|
+
'corporate',
|
|
13
|
+
'tech',
|
|
14
|
+
'organic',
|
|
15
|
+
'retro',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
describe('generateGradientSystem', () => {
|
|
19
|
+
const colors = generateColorPalette('#6B4CE6');
|
|
20
|
+
|
|
21
|
+
it('generates all 5 gradient presets', () => {
|
|
22
|
+
const system = generateGradientSystem(colors);
|
|
23
|
+
expect(Object.keys(system.presets)).toEqual(PRESET_NAMES);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('generates valid CSS gradient syntax', () => {
|
|
27
|
+
const system = generateGradientSystem(colors);
|
|
28
|
+
for (const preset of Object.values(system.presets)) {
|
|
29
|
+
expect(preset.cssValue).toMatch(/^(linear-gradient|radial-gradient|conic-gradient)\(/);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('stops have sorted positions 0-100', () => {
|
|
34
|
+
const system = generateGradientSystem(colors);
|
|
35
|
+
for (const preset of Object.values(system.presets)) {
|
|
36
|
+
for (let i = 0; i < preset.stops.length; i++) {
|
|
37
|
+
expect(preset.stops[i].position).toBeGreaterThanOrEqual(0);
|
|
38
|
+
expect(preset.stops[i].position).toBeLessThanOrEqual(100);
|
|
39
|
+
if (i > 0) {
|
|
40
|
+
expect(preset.stops[i].position).toBeGreaterThanOrEqual(preset.stops[i - 1].position);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('stops contain valid hex colors', () => {
|
|
47
|
+
const system = generateGradientSystem(colors);
|
|
48
|
+
for (const preset of Object.values(system.presets)) {
|
|
49
|
+
for (const stop of preset.stops) {
|
|
50
|
+
expect(stop.color).toMatch(/^#[0-9a-f]{6}$/i);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('bold style produces 3-stop gradients for hero preset', () => {
|
|
56
|
+
const system = generateGradientSystem(colors, 'bold');
|
|
57
|
+
expect(system.presets.hero.stops.length).toBeGreaterThanOrEqual(3);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('minimal style produces 2-stop gradients for hero preset', () => {
|
|
61
|
+
const system = generateGradientSystem(colors, 'minimal');
|
|
62
|
+
expect(system.presets.hero.stops).toHaveLength(2);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('playful style uses conic gradient type', () => {
|
|
66
|
+
const system = generateGradientSystem(colors, 'playful');
|
|
67
|
+
expect(system.presets.hero.type).toBe('conic');
|
|
68
|
+
expect(system.presets.hero.cssValue).toContain('conic-gradient');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('organic style uses radial gradient type', () => {
|
|
72
|
+
const system = generateGradientSystem(colors, 'organic');
|
|
73
|
+
expect(system.presets.hero.type).toBe('radial');
|
|
74
|
+
expect(system.presets.hero.cssValue).toContain('radial-gradient');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('works with default parameters', () => {
|
|
78
|
+
const system = generateGradientSystem(colors);
|
|
79
|
+
expect(system.presets).toBeDefined();
|
|
80
|
+
expect(Object.keys(system.presets)).toHaveLength(5);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('each BrandStyle produces valid gradients', () => {
|
|
84
|
+
for (const style of ALL_STYLES) {
|
|
85
|
+
const system = generateGradientSystem(colors, style);
|
|
86
|
+
expect(Object.keys(system.presets)).toHaveLength(5);
|
|
87
|
+
for (const preset of Object.values(system.presets)) {
|
|
88
|
+
expect(preset.stops.length).toBeGreaterThanOrEqual(2);
|
|
89
|
+
expect(preset.cssValue).toBeTruthy();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('card preset uses neutral colors', () => {
|
|
95
|
+
const system = generateGradientSystem(colors);
|
|
96
|
+
const cardStops = system.presets.card.stops;
|
|
97
|
+
expect(cardStops).toHaveLength(2);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('background preset uses neutral extremes', () => {
|
|
101
|
+
const system = generateGradientSystem(colors);
|
|
102
|
+
const bgStops = system.presets.background.stops;
|
|
103
|
+
expect(bgStops).toHaveLength(2);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('radial gradients omit angle property', () => {
|
|
107
|
+
const system = generateGradientSystem(colors, 'organic');
|
|
108
|
+
expect(system.presets.hero.angle).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('linear gradients include angle property', () => {
|
|
112
|
+
const system = generateGradientSystem(colors, 'minimal');
|
|
113
|
+
expect(system.presets.hero.angle).toBeDefined();
|
|
114
|
+
expect(typeof system.presets.hero.angle).toBe('number');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('produces distinct gradients for different styles', () => {
|
|
118
|
+
const minimal = generateGradientSystem(colors, 'minimal');
|
|
119
|
+
const bold = generateGradientSystem(colors, 'bold');
|
|
120
|
+
expect(minimal.presets.hero.cssValue).not.toBe(bold.presets.hero.cssValue);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateSvgLogo,
|
|
3
|
+
defaultLogoConfig,
|
|
4
|
+
} from '../../lib/branding-core/generators/logo-generator.js';
|
|
5
|
+
import type { BrandStyle, LogoConfig, LogoVariant } from '../../lib/types.js';
|
|
6
|
+
|
|
7
|
+
const ALL_VARIANTS: LogoVariant[] = ['wordmark', 'monogram', 'abstract', 'icon'];
|
|
8
|
+
|
|
9
|
+
const ALL_STYLES: BrandStyle[] = [
|
|
10
|
+
'minimal',
|
|
11
|
+
'bold',
|
|
12
|
+
'elegant',
|
|
13
|
+
'playful',
|
|
14
|
+
'corporate',
|
|
15
|
+
'tech',
|
|
16
|
+
'organic',
|
|
17
|
+
'retro',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
describe('generateSvgLogo', () => {
|
|
21
|
+
const config: LogoConfig = {
|
|
22
|
+
...defaultLogoConfig('TestBrand', '#6B4CE6'),
|
|
23
|
+
font: 'Playfair Display',
|
|
24
|
+
style: 'elegant',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
it('returns all 4 variants', () => {
|
|
28
|
+
const result = generateSvgLogo(config);
|
|
29
|
+
expect(Object.keys(result.variants)).toEqual(ALL_VARIANTS);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('svg field matches wordmark variant (backward compat)', () => {
|
|
33
|
+
const result = generateSvgLogo(config);
|
|
34
|
+
expect(result.svg).toBe(result.variants.wordmark);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('wordmark contains brand name text', () => {
|
|
38
|
+
const result = generateSvgLogo(config);
|
|
39
|
+
expect(result.variants.wordmark).toContain('TestBrand');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('monogram contains first letter', () => {
|
|
43
|
+
const result = generateSvgLogo(config);
|
|
44
|
+
expect(result.variants.monogram).toContain('>T<');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('abstract has multiple shape elements', () => {
|
|
48
|
+
const result = generateSvgLogo(config);
|
|
49
|
+
const shapeCount = (
|
|
50
|
+
result.variants.abstract.match(/<(circle|rect|polygon|path|line|ellipse)/g) ?? []
|
|
51
|
+
).length;
|
|
52
|
+
expect(shapeCount).toBeGreaterThanOrEqual(2);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('icon has 64x64 dimensions', () => {
|
|
56
|
+
const result = generateSvgLogo(config);
|
|
57
|
+
expect(result.variants.icon).toContain('width="64"');
|
|
58
|
+
expect(result.variants.icon).toContain('height="64"');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('all variants are valid SVG', () => {
|
|
62
|
+
const result = generateSvgLogo(config);
|
|
63
|
+
for (const svg of Object.values(result.variants)) {
|
|
64
|
+
expect(svg).toContain('xmlns="http://www.w3.org/2000/svg"');
|
|
65
|
+
expect(svg).toContain('viewBox=');
|
|
66
|
+
expect(svg).toContain('</svg>');
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('uses brand font instead of hardcoded Inter', () => {
|
|
71
|
+
const result = generateSvgLogo(config);
|
|
72
|
+
expect(result.variants.wordmark).toContain('Playfair Display');
|
|
73
|
+
expect(result.variants.monogram).toContain('Playfair Display');
|
|
74
|
+
expect(result.variants.icon).toContain('Playfair Display');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('wordmark dimensions are 400x120', () => {
|
|
78
|
+
const result = generateSvgLogo(config);
|
|
79
|
+
expect(result.variants.wordmark).toContain('width="400"');
|
|
80
|
+
expect(result.variants.wordmark).toContain('height="120"');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('monogram dimensions are 120x120', () => {
|
|
84
|
+
const result = generateSvgLogo(config);
|
|
85
|
+
expect(result.variants.monogram).toContain('width="120"');
|
|
86
|
+
expect(result.variants.monogram).toContain('height="120"');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('abstract dimensions are 120x120', () => {
|
|
90
|
+
const result = generateSvgLogo(config);
|
|
91
|
+
expect(result.variants.abstract).toContain('width="120"');
|
|
92
|
+
expect(result.variants.abstract).toContain('height="120"');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('style-aware monogram shapes vary per BrandStyle', () => {
|
|
96
|
+
const shapes = new Set<string>();
|
|
97
|
+
for (const style of ALL_STYLES) {
|
|
98
|
+
const result = generateSvgLogo({ ...config, style });
|
|
99
|
+
shapes.add(result.variants.monogram);
|
|
100
|
+
}
|
|
101
|
+
expect(shapes.size).toBeGreaterThan(1);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('style-aware abstract shapes vary per BrandStyle', () => {
|
|
105
|
+
const shapes = new Set<string>();
|
|
106
|
+
for (const style of ALL_STYLES) {
|
|
107
|
+
const result = generateSvgLogo({ ...config, style });
|
|
108
|
+
shapes.add(result.variants.abstract);
|
|
109
|
+
}
|
|
110
|
+
expect(shapes.size).toBeGreaterThan(1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('works with default config (no style)', () => {
|
|
114
|
+
const defaultCfg = defaultLogoConfig('MyBrand', '#FF5500');
|
|
115
|
+
const result = generateSvgLogo(defaultCfg);
|
|
116
|
+
expect(result.svg).toBeTruthy();
|
|
117
|
+
expect(Object.keys(result.variants)).toHaveLength(4);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('uses brand color in SVG elements', () => {
|
|
121
|
+
const result = generateSvgLogo(config);
|
|
122
|
+
expect(result.variants.wordmark).toContain('#6B4CE6');
|
|
123
|
+
expect(result.variants.icon).toContain('#6B4CE6');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('no longer contains png field', () => {
|
|
127
|
+
const result = generateSvgLogo(config);
|
|
128
|
+
expect(result).not.toHaveProperty('png');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('handles single-character brand name', () => {
|
|
132
|
+
const shortConfig = { ...config, text: 'X' };
|
|
133
|
+
const result = generateSvgLogo(shortConfig);
|
|
134
|
+
expect(result.variants.wordmark).toContain('>X<');
|
|
135
|
+
expect(result.variants.monogram).toContain('>X<');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('default logo config returns expected structure', () => {
|
|
139
|
+
const cfg = defaultLogoConfig('Acme', '#000000');
|
|
140
|
+
expect(cfg.text).toBe('Acme');
|
|
141
|
+
expect(cfg.font).toBe('Inter');
|
|
142
|
+
expect(cfg.color).toBe('#000000');
|
|
143
|
+
expect(cfg.width).toBe(400);
|
|
144
|
+
expect(cfg.height).toBe(120);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { generateMotionSystem } from '../../lib/branding-core/generators/motion-system.js';
|
|
2
|
+
import type { BrandStyle } from '../../lib/types.js';
|
|
3
|
+
|
|
4
|
+
describe('generateMotionSystem', () => {
|
|
5
|
+
it('generates all duration levels', () => {
|
|
6
|
+
const system = generateMotionSystem();
|
|
7
|
+
const names = Object.keys(system.durations);
|
|
8
|
+
expect(names).toEqual(['instant', 'fast', 'normal', 'slow', 'slower']);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('instant duration is always 0ms', () => {
|
|
12
|
+
const styles: BrandStyle[] = ['minimal', 'elegant', 'playful'];
|
|
13
|
+
for (const style of styles) {
|
|
14
|
+
const system = generateMotionSystem(style);
|
|
15
|
+
expect(system.durations.instant).toBe('0ms');
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('durations increase monotonically', () => {
|
|
20
|
+
const system = generateMotionSystem();
|
|
21
|
+
const order = ['instant', 'fast', 'normal', 'slow', 'slower'] as const;
|
|
22
|
+
for (let i = 1; i < order.length; i++) {
|
|
23
|
+
const prev = parseInt(system.durations[order[i - 1]]);
|
|
24
|
+
const curr = parseInt(system.durations[order[i]]);
|
|
25
|
+
expect(curr).toBeGreaterThanOrEqual(prev);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('elegant has longer durations than tech', () => {
|
|
30
|
+
const elegant = generateMotionSystem('elegant');
|
|
31
|
+
const tech = generateMotionSystem('tech');
|
|
32
|
+
expect(parseInt(elegant.durations.normal)).toBeGreaterThan(parseInt(tech.durations.normal));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('generates all easing presets', () => {
|
|
36
|
+
const system = generateMotionSystem();
|
|
37
|
+
expect(system.easings['ease-in']).toBeDefined();
|
|
38
|
+
expect(system.easings['ease-out']).toBeDefined();
|
|
39
|
+
expect(system.easings['ease-in-out']).toBeDefined();
|
|
40
|
+
expect(system.easings.spring).toBeDefined();
|
|
41
|
+
expect(system.easings.bounce).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('easings are valid cubic-bezier strings', () => {
|
|
45
|
+
const system = generateMotionSystem();
|
|
46
|
+
for (const value of Object.values(system.easings)) {
|
|
47
|
+
expect(value).toMatch(/^cubic-bezier\([\d., -]+\)$/);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('generates transition presets', () => {
|
|
52
|
+
const system = generateMotionSystem();
|
|
53
|
+
expect(system.transitions.fade).toBeDefined();
|
|
54
|
+
expect(system.transitions.slide).toBeDefined();
|
|
55
|
+
expect(system.transitions.scale).toBeDefined();
|
|
56
|
+
expect(system.transitions.color).toBeDefined();
|
|
57
|
+
expect(system.transitions.all).toBeDefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('transition presets include duration and easing', () => {
|
|
61
|
+
const system = generateMotionSystem();
|
|
62
|
+
expect(system.transitions.fade).toContain('ms');
|
|
63
|
+
expect(system.transitions.fade).toContain('cubic-bezier');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('duration values are formatted as ms strings', () => {
|
|
67
|
+
const system = generateMotionSystem();
|
|
68
|
+
for (const value of Object.values(system.durations)) {
|
|
69
|
+
expect(value).toMatch(/^\d+ms$/);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('works for all brand styles', () => {
|
|
74
|
+
const styles: BrandStyle[] = [
|
|
75
|
+
'minimal',
|
|
76
|
+
'bold',
|
|
77
|
+
'elegant',
|
|
78
|
+
'playful',
|
|
79
|
+
'corporate',
|
|
80
|
+
'tech',
|
|
81
|
+
'organic',
|
|
82
|
+
'retro',
|
|
83
|
+
];
|
|
84
|
+
for (const style of styles) {
|
|
85
|
+
const system = generateMotionSystem(style);
|
|
86
|
+
expect(Object.keys(system.durations)).toHaveLength(5);
|
|
87
|
+
expect(Object.keys(system.easings)).toHaveLength(5);
|
|
88
|
+
expect(Object.keys(system.transitions)).toHaveLength(5);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|