feat: add mobile shell and unified search
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 50s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 20s
ci / .NET API 서버 테스트 (push) Successful in 12s
deploy-site / deploy (push) Successful in 39s
ci / 모바일 린트·타입·Jest (push) Failing after 3h10m31s
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 50s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 20s
ci / .NET API 서버 테스트 (push) Successful in 12s
deploy-site / deploy (push) Successful in 39s
ci / 모바일 린트·타입·Jest (push) Failing after 3h10m31s
This commit is contained in:
parent
f505a03d34
commit
5ca3709b32
98 changed files with 5168 additions and 2093 deletions
48
apps/mobile-rn/__tests__/theme-parity.test.ts
Normal file
48
apps/mobile-rn/__tests__/theme-parity.test.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { RAW, STATUS } from '../../../packages/ui/src/theme-raw'
|
||||
import {
|
||||
D3RO_NATIVE_RAW,
|
||||
D3RO_NATIVE_STATUS,
|
||||
getD3roNativePalette,
|
||||
} from '@d3ro/ui-native'
|
||||
|
||||
type Rgb = readonly [number, number, number]
|
||||
|
||||
function rgb(hex: string): Rgb {
|
||||
const normalized = hex.replace('#', '')
|
||||
if (!/^[0-9a-f]{6}$/i.test(normalized)) throw new Error(`Expected 6-digit hex, got ${hex}`)
|
||||
return [0, 2, 4].map((offset) => Number.parseInt(normalized.slice(offset, offset + 2), 16)) as unknown as Rgb
|
||||
}
|
||||
|
||||
function luminance(hex: string): number {
|
||||
const channels = rgb(hex).map((value) => {
|
||||
const channel = value / 255
|
||||
return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4
|
||||
})
|
||||
return (0.2126 * channels[0]!) + (0.7152 * channels[1]!) + (0.0722 * channels[2]!)
|
||||
}
|
||||
|
||||
function contrast(foreground: string, background: string): number {
|
||||
const [lighter, darker] = [luminance(foreground), luminance(background)].sort((a, b) => b - a)
|
||||
return (lighter! + 0.05) / (darker! + 0.05)
|
||||
}
|
||||
|
||||
describe('mobile v4 theme contract', () => {
|
||||
test.each(['dark', 'light'] as const)('%s raw tokens stay in desktop SSOT parity', (mode) => {
|
||||
for (const group of ['bg', 'text', 'border', 'accent'] as const) {
|
||||
for (const [key, value] of Object.entries(D3RO_NATIVE_RAW[mode][group])) {
|
||||
expect(RAW[mode][group][key as keyof typeof RAW[typeof mode][typeof group]]).toBe(value)
|
||||
}
|
||||
}
|
||||
expect(D3RO_NATIVE_STATUS[mode]).toEqual(STATUS[mode])
|
||||
})
|
||||
|
||||
test.each(['dark', 'light'] as const)('%s readable roles meet 4.5:1 contrast', (mode) => {
|
||||
const palette = getD3roNativePalette(mode)
|
||||
for (const background of [palette.bg.app, palette.bg.card, palette.bg.elevated, palette.bg.sidebar]) {
|
||||
for (const foreground of [palette.text.primary, palette.text.secondary, palette.text.tertiary]) {
|
||||
expect(contrast(foreground, background)).toBeGreaterThanOrEqual(4.5)
|
||||
}
|
||||
}
|
||||
expect(contrast(palette.accent.ink, palette.accent.solid)).toBeGreaterThanOrEqual(4.5)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue