A calm place to write long-form, and publish it to the open social web.
skypress.blog/
1/**
2 * Source-level guard for the client:only island loading fallback.
3 *
4 * `dashboard.astro` and `editor.astro` mount browser-only React islands
5 * (`client:only`). Until their JS bundle loads, Astro shows the `slot="fallback"`
6 * markup. That fallback must carry the durable page chrome — a logo-only header
7 * (matching AppBar's `status === 'loading'` state) plus a content skeleton — so
8 * the page has shape before hydration instead of a bare, left-aligned "Loading…".
9 *
10 * Rendering the pages through astro/container isn't viable here (the runner is
11 * pinned to jsdom for the WordPress block suites), so these assertions pin the
12 * wiring at the source level — same approach as `_index.phase.test.ts`.
13 */
14import { readFileSync } from 'node:fs';
15import { fileURLToPath } from 'node:url';
16import { describe, expect, it } from 'vitest';
17
18const read = ( rel: string ) =>
19 readFileSync( fileURLToPath( new URL( rel, import.meta.url ) ), 'utf8' );
20
21describe( 'LoadingScene fallback', () => {
22 const scene = read( './LoadingScene.astro' );
23
24 it( 'renders the durable logo header using the shared app-bar classes', () => {
25 expect( scene ).toMatch( /class="app-bar"/ );
26 expect( scene ).toMatch( /class="app-bar__home"/ );
27 expect( scene ).toMatch( /class="app-bar__word"/ );
28 expect( scene ).toMatch( /SkyPress/ );
29 } );
30
31 it( 'omits the dynamic nav + identity (auth state is unknown pre-hydration)', () => {
32 expect( scene ).not.toMatch( /app-bar__nav/ );
33 expect( scene ).not.toMatch( /app-bar__identity/ );
34 expect( scene ).not.toMatch( /app-bar__signout/ );
35 } );
36
37 it( 'renders shimmering skeleton placeholders', () => {
38 expect( scene ).toMatch( /class="sk\b/ );
39 expect( scene ).toMatch( /@keyframes/ );
40 } );
41
42 it( 'flags the region as busy and keeps an accessible loading label', () => {
43 expect( scene ).toMatch( /aria-busy="true"/ );
44 expect( scene ).toMatch( /Loading…/ );
45 } );
46
47 it( 'stills the shimmer for reduced-motion users', () => {
48 expect( scene ).toMatch( /prefers-reduced-motion/ );
49 } );
50
51 it( 'offers distinct dashboard and editor variants', () => {
52 expect( scene ).toMatch( /'dashboard'\s*\|\s*'editor'/ );
53 expect( scene ).toMatch( /variant === 'dashboard'/ );
54 } );
55} );
56
57describe( 'island fallbacks use LoadingScene', () => {
58 const dashboard = read( '../pages/dashboard.astro' );
59 const editor = read( '../pages/editor.astro' );
60
61 it( 'dashboard fallback renders LoadingScene, not a bare "Loading…"', () => {
62 expect( dashboard ).toMatch( /import LoadingScene from '[^']*LoadingScene.astro'/ );
63 expect( dashboard ).toMatch( /<LoadingScene[\s\S]*?slot="fallback"/ );
64 expect( dashboard ).toMatch( /variant="dashboard"/ );
65 expect( dashboard ).not.toMatch( /class="dash__loading">Loading…/ );
66 } );
67
68 it( 'editor fallback renders LoadingScene, not a bare "Loading…"', () => {
69 expect( editor ).toMatch( /import LoadingScene from '[^']*LoadingScene.astro'/ );
70 expect( editor ).toMatch( /<LoadingScene[\s\S]*?slot="fallback"/ );
71 expect( editor ).toMatch( /variant="editor"/ );
72 expect( editor ).not.toMatch( /class="editor-shell__loading">Loading…/ );
73 } );
74} );