A calm place to write long-form, and publish it to the open social web.
skypress.blog/
1/**
2 * Source-level guards for the landing redesign (spec 2026-06-09). The .astro pages
3 * can't render through the jsdom-pinned vitest, so we assert on source — same approach
4 * as src/pages/index.phase.test.ts.
5 */
6import { existsSync, readFileSync } from 'node:fs';
7import { fileURLToPath } from 'node:url';
8import { describe, expect, it } from 'vitest';
9
10// Resolve at module scope: jsdom swaps the global `URL` for one based at
11// http://localhost, so `new URL( rel, import.meta.url )` only stays a file://
12// URL when it runs at collection time (module/describe-body evaluation, before
13// jsdom takes over) rather than inside an `it()` callback.
14const resolve = ( rel: string ) => fileURLToPath( new URL( rel, import.meta.url ) );
15const read = ( rel: string ) => readFileSync( resolve( rel ), 'utf8' );
16
17describe( 'site footer links', () => {
18 const footer = read( '../../components/Footer.astro' );
19
20 it( 'links the lexicon under a Docs label', () => {
21 expect( footer ).toMatch( /href=["']\/lexicon["'][^>]*>[\s\S]*?Docs/ );
22 } );
23
24 it( 'links the SkyPress launch article under a News label', () => {
25 expect( footer ).toMatch(
26 /href=["']https:\/\/skypress\.blog\/@skypress\.blog\/its-always-sunny-on-skypress["'][^>]*>[\s\S]*?News/
27 );
28 } );
29
30 it( 'links the SkyPress source repo on tangled.org with an accessible label and icon', () => {
31 expect( footer ).toMatch(
32 /href=["']https:\/\/tangled\.org\/jeremy\.herve\.bzh\/skypress["'][\s\S]*?aria-label=[\s\S]*?<svg/
33 );
34 } );
35} );
36
37describe( 'sample/preview page is retired', () => {
38 const previewRoute = resolve( '../../pages/preview.astro' );
39
40 it( 'no longer ships the /preview route', () => {
41 expect( existsSync( previewRoute ) ).toBe( false );
42 } );
43
44 it( 'is not linked from the landing or lexicon pages', () => {
45 expect( read( '../../pages/index.astro' ) ).not.toMatch( /\/preview/ );
46 expect( read( '../../pages/lexicon.astro' ) ).not.toMatch( /\/preview/ );
47 } );
48} );
49
50describe( 'landing hero redesign', () => {
51 const index = read( '../../pages/index.astro' );
52
53 it( 'leads with a single "Start writing" CTA to /write and no on-page sign-in', () => {
54 // Writing-first: the hero's only action starts a draft on /write. The handle sign-in
55 // island was removed — signing in happens via Publish on /write, not from the home page.
56 expect( index ).toMatch( /href="\/write"[^>]*>\s*Start writing/ );
57 expect( index ).not.toMatch( /HandleStart/ );
58 } );
59
60 it( 'drops the old sample / lexicon / studio hero buttons', () => {
61 expect( index ).not.toMatch( /Read a sample/ );
62 expect( index ).not.toMatch( /See the lexicon/ );
63 expect( index ).not.toMatch( /href="\/editor">Studio/ );
64 } );
65
66 it( 'states that SkyPress is free', () => {
67 expect( index ).toMatch( /[Ff]ree/ );
68 } );
69
70 it( 'no longer carries the Bluesky cross-post note on the home page', () => {
71 // The "One thing worth knowing…" notice was removed from the landing page
72 // (commit c1508f9); the cross-post disclosure now lives on the publish panel.
73 expect( index ).not.toMatch( /posts to Bluesky/ );
74 } );
75
76 it( 'shows the three-up "see it in action" screenshot strip', () => {
77 expect( index ).toMatch( /\/screenshots\/editor\.png/ );
78 expect( index ).toMatch( /\/screenshots\/published-article\.png/ );
79 expect( index ).toMatch( /\/screenshots\/author-page\.png/ );
80 } );
81
82 it( 'no longer renders the three-step how-it-works list', () => {
83 expect( index ).not.toMatch( /class="steps"/ );
84 } );
85} );
86
87describe( 'playful: shooting star', () => {
88 const index = read( '../../pages/index.astro' );
89
90 it( 'renders a shooting-star element inside the decorative sky', () => {
91 expect( index ).toMatch( /class="shootingstar"/ );
92 } );
93
94 it( 'animates only in dark phases', () => {
95 const style = index.match( /<style>([\s\S]*?)<\/style>/ )?.[ 1 ] ?? '';
96 expect( style ).toMatch( /:global\(\s*\[data-phase='night'\]\s*\)\s*\.shootingstar/ );
97 } );
98
99 it( 'is silenced under reduced motion', () => {
100 const style = index.match( /<style>([\s\S]*?)<\/style>/ )?.[ 1 ] ?? '';
101 const reduced = style.match( /@media \(prefers-reduced-motion: reduce\) \{([\s\S]*?)\}/ )?.[ 1 ] ?? '';
102 expect( reduced ).toMatch( /\.shootingstar/ );
103 } );
104} );