// Theme engine v3, step 6: pair-aware remap. X fills that are no palette colour take a theme surface and // everything inside reads in that surface's ink; X's tooltip is the tooltip surface. import assert from 'node:assert/strict'; import test from 'node:test'; import { contrastRatio } from '../../../waybetter/packages/runtime/src/themeApi.ts'; import { X_COLOUR_KEYS, X_MEANING_KEYS, X_PALETTES, accentWash, buildThemeCss, foreignFillSurface, paletteSurfaces } from '../src/inject/theme.ts'; import { BUILTIN_THEMES } from '../src/inject/themeSpec.ts'; const rule = (selectorText: string, style: Record) => ({ selectorText, style: { getPropertyValue: (name: string) => style[name] ?? '' } }); for (const theme of BUILTIN_THEMES.filter(t => t.colors)) { test(`${theme.id}: Creator Studio's indigo pill (class or inline) gets a fill and an ink that read (was 1.19:1)`, () => { const palette = { ...theme.colors! }; const css = buildThemeCss([rule('.bg-indigo-950', { 'background-color': 'rgb(30, 27, 75)' })], X_PALETTES.light, palette); const surfaces = paletteSurfaces(palette)!; const s = foreignFillSurface('30,27,75', X_PALETTES.light, surfaces); assert.ok(contrastRatio(s.ink, s.fill) >= 4.5, `${s.ink} on ${s.fill}`); assert.ok(css.includes(`.bg-indigo-950`) && css.includes(`background-color:${s.fill} !important`), 'class fill remapped'); assert.ok(css.includes('background-color: rgb(30, 27, 75)"]'), 'inline fill remapped'); assert.ok(css.includes(`--jf-text-color:${s.ink} !important`), 'jetfuel text token follows the fill'); // The tooltip: its label reads on its fill (the "More" HoverLabel was 2.7:1). assert.ok(contrastRatio(surfaces.tooltip.ink, surfaces.tooltip.fill) >= 4.5); assert.ok(css.includes('[data-testid="HoverLabel"]')); }); } test('meaning colours and X accent swatches stay X\'s; every palette key is listed for the leak check', () => { const palette = { ...BUILTIN_THEMES.find(t => t.id === 'windows-98')!.colors! }; const css = buildThemeCss([rule('.swatch', { 'background-color': 'rgb(120, 86, 255)' }), rule('.like', { 'background-color': 'rgb(249, 24, 128)' })], X_PALETTES.light, palette); assert.ok(!css.includes('.swatch{') && !css.includes(':is(.swatch')); assert.ok(!css.includes(':is(.like')); for (const key of ['120,86,255', '249,24,128']) assert.ok(X_MEANING_KEYS.includes(key), key); for (const key of ['255,255,255', '15,20,25', '29,155,240', '0,0,0', '115,115,115']) assert.ok(X_COLOUR_KEYS.includes(key), key); }); test('X\'s blue washes become the content surface\'s neutral hover and its selection (no lavender rows)', () => { const palette = { ...BUILTIN_THEMES.find(t => t.id === 'windows-98')!.colors! }; const surfaces = paletteSurfaces(palette)!; const css = buildThemeCss([rule('.r-hover', { 'background-color': 'rgba(29, 155, 240, 0.1)' }), rule('.r-sel', { 'background-color': 'rgba(29, 155, 240, 0.15)' })], X_PALETTES.light, palette); assert.ok(css.includes(`.r-hover{background-color:${surfaces.content.hover} !important}`), css.split('\n').find(l => l.startsWith('.r-hover'))); assert.ok(css.includes(`.r-sel{background-color:${surfaces.content.selected} !important}`)); assert.equal(accentWash('rgba(29, 155, 240, 0.5)', X_PALETTES.light, surfaces), null, 'a strong tint is a fill, not a state'); }); test('the live rule of the "New" pill: a compound selector with jetfuel\'s variable alpha is a fill class too', () => { const palette = { ...BUILTIN_THEMES.find(t => t.id === 'windows-98')!.colors! }; const sel = '[data-theme="dark"] .jetfuel-style-root .j-j6ij7u11'; const css = buildThemeCss([rule(sel, { 'background-color': 'rgba(30, 27, 75, var(--jbgo))' })], X_PALETTES.light, palette); const s = foreignFillSurface('30,27,75', X_PALETTES.light, paletteSurfaces(palette)!); assert.ok(css.includes(`:is(${sel}`) && css.includes(`--jf-text-color:${s.ink} !important`)); });