A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 3.2 kB View raw
1/** 2 * Regression guard for the signed-in account menu dropdown stacking on the landing page. 3 * 4 * The dropdown (`.account-menu__dropdown`, `z-index: 5`) lives inside `.masthead`, which is a 5 * positioned stacking context. `.masthead`, `.hero`, and `.showcase` are sibling children of 6 * `.page`. A positioned element's `z-index` only competes within its own stacking context, so the 7 * dropdown's `z-index: 5` is confined to `.masthead` and never escapes it. When `.masthead` and a 8 * later sibling carry the *same* `z-index`, DOM order wins and the later sibling paints on top of 9 * the entire masthead — including the dropdown. 10 * 11 * On mobile the dropdown extends down into the `.hero` region; if `.hero` ties `.masthead` on 12 * `z-index`, the hero text paints over the dropdown (it looks see-through) and the hero intercepts 13 * the dropdown's clicks. So `.masthead` must outrank every sibling it can overlap. 14 * 15 * Rendering the page through astro/container isn't viable here (the runner is pinned to jsdom for 16 * the WordPress block suites), so this pins the stacking order at the source level. 17 */ 18import { readFileSync } from 'node:fs'; 19import { fileURLToPath } from 'node:url'; 20import { describe, expect, it } from 'vitest'; 21 22// `import.meta.url` must be referenced from a top-level binding so vite's transform rewrites it 23// to a `file://` URL; referenced inside a `describe` callback it is left as a non-file URL at 24// collection time and `fileURLToPath` throws. (The sibling _index.phase.test.ts does the same.) 25const read = ( rel: string ) => 26 readFileSync( fileURLToPath( new URL( rel, import.meta.url ) ), 'utf8' ); 27 28describe( 'landing page masthead stacking', () => { 29 const index = read( './index.astro' ); 30 // Strip CSS comments so prose like "z-index: 2" inside a comment can't be mistaken for a 31 // declaration. 32 const style = ( index.match( /<style>([\s\S]*?)<\/style>/ )?.[ 1 ] ?? '' ).replace( 33 /\/\*[\s\S]*?\*\//g, 34 '' 35 ); 36 37 /** Read the `z-index` declared on the rule that positions `selector`. */ 38 const zIndexOf = ( selector: string ): number => { 39 // `.hero`/`.masthead` also appear in grouped per-phase rules (`… .hero {`) that only set 40 // colours; the positioning rule is the one declaring z-index. Scan every `<selector> { … }` 41 // block (body has no nested braces) and take the z-index wherever it is declared. `\b` keeps 42 // `.masthead` off `.masthead__right`; the leading `\.` keeps it off classes ending the same. 43 const blocks = style.matchAll( 44 new RegExp( `${ selector.replace( /\./g, '\\.' ) }\\b\\s*\\{([^{}]*)\\}`, 'g' ) 45 ); 46 let z: string | undefined; 47 for ( const [ , body ] of blocks ) { 48 z = body.match( /z-index:\s*(-?\d+)/ )?.[ 1 ] ?? z; 49 } 50 expect( z, `expected a rule positioning ${ selector } with a z-index` ).toBeDefined(); 51 return Number( z ); 52 }; 53 54 it( 'stacks the masthead above the hero so its dropdown can paint over hero content', () => { 55 expect( zIndexOf( '.masthead' ) ).toBeGreaterThan( zIndexOf( '.hero' ) ); 56 } ); 57 58 it( 'stacks the masthead above the showcase below the fold', () => { 59 expect( zIndexOf( '.masthead' ) ).toBeGreaterThan( zIndexOf( '.showcase' ) ); 60 } ); 61} );