A calm place to write long-form, and publish it to the open social web.
skypress.blog/
1/**
2 * Source-level guard for the Open Graph wiring in Base.astro.
3 *
4 * Rendering the layout through astro/container is not viable in this suite (the
5 * runner is pinned to jsdom for the WordPress block tests — see
6 * src/pages/index.phase.test.ts). So, like that test, we pin the wiring at the
7 * source level: Base must compute absolute URLs, call buildMetaTags, render the
8 * tags, and emit a canonical link. The tag *shapes* are unit-tested in
9 * src/lib/seo/meta.test.ts.
10 */
11import { readFileSync } from 'node:fs';
12import { dirname, join } from 'node:path';
13import { fileURLToPath } from 'node:url';
14import { describe, expect, it } from 'vitest';
15
16const here = dirname( fileURLToPath( import.meta.url ) );
17const read = ( rel: string ) => readFileSync( join( here, rel ), 'utf8' );
18
19describe( 'Base.astro Open Graph wiring', () => {
20 const base = read( './Base.astro' );
21
22 it( 'imports the buildMetaTags helper', () => {
23 expect( base ).toMatch( /import\s*\{\s*buildMetaTags\s*\}\s*from\s*'\.\.\/lib\/seo\/meta'/ );
24 } );
25
26 it( 'derives an absolute canonical URL and image URL from Astro.site', () => {
27 expect( base ).toMatch( /new URL\(\s*Astro\.url\.pathname,\s*Astro\.site\s*\)/ );
28 expect( base ).toMatch( /new URL\(\s*image,\s*Astro\.site\s*\)/ );
29 } );
30
31 it( 'renders the built meta tags into <head>', () => {
32 expect( base ).toMatch( /metaTags\.map/ );
33 expect( base ).toMatch( /<meta\s+property=\{/ );
34 expect( base ).toMatch( /<meta\s+name=\{/ );
35 } );
36
37 it( 'emits a canonical link', () => {
38 expect( base ).toMatch( /<link\s+rel="canonical"/ );
39 } );
40
41 it( 'defaults the share image to the shared og-default.png', () => {
42 expect( base ).toMatch( /\/og-default\.png/ );
43 } );
44
45 it( 'guards the canonical link + social tags behind an opt-out socialMeta prop', () => {
46 // Defaults on, so callers that pass nothing are unaffected.
47 expect( base ).toMatch( /socialMeta\s*=\s*true/ );
48 // The canonical + meta block only renders when socialMeta is truthy, so
49 // pages that manage their own head tags (e.g. articles) can opt out.
50 expect( base ).toMatch( /socialMeta\s*&&/ );
51 } );
52
53 it( 'accepts optional imageWidth/imageHeight props', () => {
54 expect( base ).toMatch( /imageWidth\??:\s*number/ );
55 expect( base ).toMatch( /imageHeight\??:\s*number/ );
56 } );
57
58 it( 'only defaults the 1200x630 dimensions for the built-in default image', () => {
59 // A flag distinguishes the default image from a caller-supplied one.
60 expect( base ).toMatch( /og-default\.png/ );
61 // The 1200x630 defaults must be gated behind the isDefaultImage guard, so
62 // caller-supplied images don't silently inherit the default dimensions.
63 expect( base ).toMatch( /isDefaultImage\s*\?\s*1200/ );
64 expect( base ).toMatch( /isDefaultImage\s*\?\s*630/ );
65 // Dimensions are threaded into buildMetaTags.
66 expect( base ).toMatch( /imageWidth:/ );
67 expect( base ).toMatch( /imageHeight:/ );
68 } );
69} );
70
71describe( 'article page metadata', () => {
72 const article = read( '../pages/[author]/[slug]/[rkey].astro' );
73
74 it( 'opts out of Base social meta so its own OG + canonical tags are not duplicated', () => {
75 expect( article ).toMatch( /<Base[^>]*\ssocialMeta=\{\s*false\s*\}/s );
76 } );
77} );