open-design/apps/daemon/tests/swift-colors.test.ts
Chris Seifert 0f5ac37c22
Capture native (Swift) source in the GitHub design import (#2847)
A design system imported from a SwiftUI repo could never be published. The
import's file scorer was web-only, so every .swift file scored 0 while the
repo's config dotfiles (.zenflow, .vscode, .zed) scored on the generic text
bonus and won the top-N selection. Even when a source file was selected, the
snapshot writer's text-file allowlist didn't include .swift, so it was dropped.
The result: snapshots were all config dotfiles, which the project files API
hides, so the publish gate saw zero evidence snapshots and stayed blocked no
matter how many times the intake ran.

Three layers fixed in tools-connectors-cli.ts:
- scoreDesignFile now scores native/design-token source (Swift, Kotlin, Go,
  Rust, etc.), with a high boost for token files like ColorSystem.swift,
  Typography.swift, Spacing.swift.
- shouldSkipRepoPath skips editor/CI/agent tooling dirs (.vscode, .zed, .idea,
  .zenflow, .github, .husky, ...) so their files stop crowding out real source.
- isTextSnapshotPath recognizes native source extensions so selected files are
  actually written.

Also teach the design-system swatch extraction to read SwiftUI colors:
swift-colors.ts parses Color(red:green:blue:), Color(hue:saturation:brightness:)
(HSB), and Color(white:), evaluating decimal, hex-byte, and division component
expressions (0xF4 / 255, 220 / 360), and design-systems.ts uses it as a new
swatch form. scoreDesignFile, shouldSkipRepoPath, and isTextSnapshotPath are
exported for unit tests.

Verified against a real SwiftUI repo: the intake now captures ColorSystem,
TypographySystem, SpacingSystem and the view/model source instead of config
dotfiles.
2026-05-25 05:17:47 +00:00

58 lines
2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
evalSwiftNumber,
extractSwiftColors,
hsbToHex,
rgbUnitToHex,
} from '../src/swift-colors.js';
describe('evalSwiftNumber', () => {
it('reads decimals, ints, hex bytes, and single divisions', () => {
expect(evalSwiftNumber('0.99')).toBeCloseTo(0.99, 5);
expect(evalSwiftNumber('255')).toBe(255);
expect(evalSwiftNumber('0xF4')).toBe(244);
expect(evalSwiftNumber('0xF4 / 255')).toBeCloseTo(244 / 255, 5);
expect(evalSwiftNumber('220 / 360')).toBeCloseTo(220 / 360, 5);
});
it('returns null for anything it cannot evaluate safely', () => {
expect(evalSwiftNumber('someVar')).toBeNull();
expect(evalSwiftNumber('1 / 0')).toBeNull();
expect(evalSwiftNumber('1 + 2 + 3')).toBeNull();
});
});
describe('hsbToHex / rgbUnitToHex', () => {
it('converts the SwiftUI HSB grey50 token to hex', () => {
// static let grey50 = Color(hue: 220 / 360, saturation: 0.02, brightness: 0.99)
expect(hsbToHex(220 / 360, 0.02, 0.99)).toBe('#f7f9fc');
});
it('converts unit RGB to hex', () => {
expect(rgbUnitToHex(0xf4 / 255, 0xf4 / 255, 0xf4 / 255)).toBe('#f4f4f4');
expect(rgbUnitToHex(0, 0, 0)).toBe('#000000');
expect(rgbUnitToHex(1, 1, 1)).toBe('#ffffff');
});
});
describe('extractSwiftColors', () => {
it('parses HSB and RGB Color declarations with names', () => {
const src = [
'enum ColorSystem {',
' static let grey50 = Color(hue: 220 / 360, saturation: 0.02, brightness: 0.99)',
' static let appShellLight = Color(red: 0xF4 / 255, green: 0xF4 / 255, blue: 0xF4 / 255)',
' static let scrim = Color(white: 0.0, opacity: 0.4)',
'}',
].join('\n');
expect(extractSwiftColors(src)).toEqual([
{ name: 'grey50', hex: '#f7f9fc' },
{ name: 'appShellLight', hex: '#f4f4f4' },
{ name: 'scrim', hex: '#000000' },
]);
});
it('skips named asset colors that carry no component values', () => {
expect(extractSwiftColors('let accent = Color("AccentColor")')).toEqual([]);
});
});