55import { test , expect } from '@playwright/test' ;
66import path from 'node:path' ;
77import { pathToFileURL } from 'node:url' ;
8+ import { resolveTokenLuminance , contrastBetween } from './color-helpers.js' ;
89
910// fixture.html loads slashed.full.css (palette tokens are core)
1011const FIXTURE = pathToFileURL ( path . join ( import . meta. dirname , 'fixture.html' ) ) . href ;
1112
12- // ── Serialisable in-browser helpers ─────────────────────────────
13- // Functions passed directly to page.evaluate() must be self-contained
14- // (no closure over Node.js variables). Single-token helpers are called
15- // from Node.js with page.evaluate(fn, tokenName) so callers can compose
16- // them without duplicating the canvas boilerplate.
17-
18- // Returns WCAG luminance of the resolved value of one CSS custom property.
19- // Self-contained: safe to pass to page.evaluate(resolveTokenLuminance, tok).
20- function resolveTokenLuminance ( tok ) {
21- const cv = document . createElement ( 'canvas' ) ; cv . width = cv . height = 1 ;
22- const ctx = cv . getContext ( '2d' , { willReadFrequently : true } ) ;
23- const el = document . createElement ( 'div' ) ; el . style . backgroundColor = `var(${ tok } )` ;
24- document . body . appendChild ( el ) ;
25- ctx . clearRect ( 0 , 0 , 1 , 1 ) ; ctx . fillStyle = '#000' ; ctx . fillStyle = getComputedStyle ( el ) . backgroundColor ; el . remove ( ) ;
26- ctx . fillRect ( 0 , 0 , 1 , 1 ) ;
27- const [ r , g , b ] = ctx . getImageData ( 0 , 0 , 1 , 1 ) . data ;
28- const lin = v => { v /= 255 ; return v <= 0.03928 ? v / 12.92 : ( ( v + 0.055 ) / 1.055 ) ** 2.4 ; } ;
29- return 0.2126 * lin ( r ) + 0.7152 * lin ( g ) + 0.0722 * lin ( b ) ;
30- }
31-
32- // Returns WCAG contrast ratio between two CSS custom property tokens.
33- // Accepts a two-element array [token1, token2] for Playwright 1.60 compatibility
34- // (page.evaluate only accepts a single argument).
35- function contrastBetween ( [ token1 , token2 ] ) {
36- // Must be self-contained: re-declare helpers (page.evaluate serialises the fn body).
37- const cv = document . createElement ( 'canvas' ) ; cv . width = cv . height = 1 ;
38- const ctx = cv . getContext ( '2d' , { willReadFrequently : true } ) ;
39- const toLum = ( color ) => {
40- ctx . clearRect ( 0 , 0 , 1 , 1 ) ; ctx . fillStyle = '#000' ; ctx . fillStyle = color ; ctx . fillRect ( 0 , 0 , 1 , 1 ) ;
41- const [ r , g , b ] = ctx . getImageData ( 0 , 0 , 1 , 1 ) . data ;
42- const lin = v => { v /= 255 ; return v <= 0.03928 ? v / 12.92 : ( ( v + 0.055 ) / 1.055 ) ** 2.4 ; } ;
43- return 0.2126 * lin ( r ) + 0.7152 * lin ( g ) + 0.0722 * lin ( b ) ;
44- } ;
45- const resolve = ( tok ) => {
46- const el = document . createElement ( 'div' ) ; el . style . backgroundColor = `var(${ tok } )` ;
47- document . body . appendChild ( el ) ; const c = getComputedStyle ( el ) . backgroundColor ; el . remove ( ) ; return c ;
48- } ;
49- const a = toLum ( resolve ( token1 ) ) ;
50- const b = toLum ( resolve ( token2 ) ) ;
51- return ( Math . max ( a , b ) + 0.05 ) / ( Math . min ( a , b ) + 0.05 ) ;
52- }
53-
54- // Node.js async helpers — compose page.evaluate(resolveTokenLuminance) calls
55- // so the canvas boilerplate lives in exactly one place.
13+ // Node.js async helpers — compose page.evaluate(resolveTokenLuminance) calls.
5614
5715async function getSurfaceLuminances ( page ) {
5816 const [ bg , raised , inset ] = await Promise . all ( [
@@ -63,18 +21,19 @@ async function getSurfaceLuminances(page) {
6321 return { bg, raised, inset } ;
6422}
6523
66- // Returns {step, lum} pairs for the primary palette steps provided.
24+ // Returns {step, lum} pairs for the primary palette steps.
25+ // Self-contained for page.evaluate. One canvas reused across all steps.
6726function paletteLuminances ( steps ) {
6827 const cv = document . createElement ( 'canvas' ) ; cv . width = cv . height = 1 ;
6928 const ctx = cv . getContext ( '2d' , { willReadFrequently : true } ) ;
29+ const lin = v => { v /= 255 ; return v <= 0.03928 ? v / 12.92 : ( ( v + 0.055 ) / 1.055 ) ** 2.4 ; } ;
7030 return steps . map ( s => {
71- ctx . clearRect ( 0 , 0 , 1 , 1 ) ; ctx . fillStyle = '#000' ;
72- const el = document . createElement ( 'div' ) ; el . style . backgroundColor = `var(--sf-color-primary-${ s } )` ;
73- document . body . appendChild ( el ) ; ctx . fillStyle = getComputedStyle ( el ) . backgroundColor ; el . remove ( ) ;
74- ctx . fillRect ( 0 , 0 , 1 , 1 ) ;
75- const [ r , g , b ] = ctx . getImageData ( 0 , 0 , 1 , 1 ) . data ;
76- const lin = v => { v /= 255 ; return v <= 0.03928 ?v / 12.92 :( ( v + 0.055 ) / 1.055 ) ** 2.4 ; } ;
77- return { step : s , lum : 0.2126 * lin ( r ) + 0.7152 * lin ( g ) + 0.0722 * lin ( b ) } ;
31+ ctx . clearRect ( 0 , 0 , 1 , 1 ) ; ctx . fillStyle = '#000' ;
32+ const el = document . createElement ( 'div' ) ; el . style . backgroundColor = `var(--sf-color-primary-${ s } )` ;
33+ document . body . appendChild ( el ) ; ctx . fillStyle = getComputedStyle ( el ) . backgroundColor ; el . remove ( ) ;
34+ ctx . fillRect ( 0 , 0 , 1 , 1 ) ;
35+ const [ r , g , b ] = ctx . getImageData ( 0 , 0 , 1 , 1 ) . data ;
36+ return { step : s , lum : 0.2126 * lin ( r ) + 0.7152 * lin ( g ) + 0.0722 * lin ( b ) } ;
7837 } ) ;
7938}
8039
0 commit comments