scavold 0.2.0-rc.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -3
- package/COMPONENTS.md +629 -429
- package/FRONTMATTER.md +30 -0
- package/components/ScavoldCover.vue +162 -0
- package/components/ScavoldImage.vue +5 -1
- package/components/ScavoldMenu.vue +20 -1
- package/components/ScavoldVideo.vue +12 -53
- package/composables/hierarchy.ts +72 -1
- package/composables/useAutoplay.js +128 -0
- package/composables/useCover.js +128 -0
- package/composables/useVideo.js +11 -21
- package/index.d.ts +3 -0
- package/l10n/de.json +4 -0
- package/l10n/en.json +4 -0
- package/lib/config.js +28 -6
- package/lib/containers.js +53 -7
- package/lib/media.js +9 -2
- package/lib/menus.d.ts +17 -0
- package/lib/menus.js +73 -0
- package/lib/sectionManifest.js +40 -10
- package/package.json +1 -1
- package/scripts/check-fixture.js +87 -8
package/scripts/check-fixture.js
CHANGED
|
@@ -154,7 +154,7 @@ expect( manifest.adapter?.version === version, "reports this package's version a
|
|
|
154
154
|
expect( manifest.sections?.video?.props?.src?.type === "media-file", "declares video.src as a media file" );
|
|
155
155
|
expect( manifest.sections?.pagelist?.props?.sort?.values?.join() === "order,date,date-asc,title",
|
|
156
156
|
"declares the page list's ordering options" );
|
|
157
|
-
expect( manifest.sections?.video?.props?.overlay
|
|
157
|
+
expect( manifest.sections?.video?.props?.overlay === undefined, "offers no background mode for a video; that is a cover's" );
|
|
158
158
|
expect( manifest.sections?.teaser?.props?.background?.type === "media-file",
|
|
159
159
|
"carries the site's own container with its typed props" );
|
|
160
160
|
expect( manifest.sections?.teaser?.props?.variant?.values?.join() === "plain,boxed",
|
|
@@ -271,18 +271,67 @@ expect( built( "index.html" ).includes( 'href="/containers.html"' ), "leaves a l
|
|
|
271
271
|
console.log( "\nvideo" );
|
|
272
272
|
|
|
273
273
|
const videos = tags( media, "video" );
|
|
274
|
-
const overlays = media.match( /<div class="scavold-video scavold-video--overlay"/g ) ?? [];
|
|
275
274
|
|
|
276
275
|
expect( videos.every( t => /src="\/media\/clip\.mp4"/.test( t ) ), "rewrites the video source to its published URL" );
|
|
277
276
|
expect( videos.some( t => /poster="\/media\/wide--\d+w-[0-9a-f]+\.jpg"/.test( t ) ),
|
|
278
277
|
"resolves a poster to a single generated variant rather than a srcset" );
|
|
279
|
-
expect( overlays.length === 2, "renders background mode for both spellings of its arguments",
|
|
280
|
-
`found ${overlays.length} overlay blocks, expected 2` );
|
|
281
278
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
279
|
+
// Autoplay implies muted, which is how it shows in the markup: the playing itself is
|
|
280
|
+
// started by script, once reduced motion has been ruled out.
|
|
281
|
+
const looping = videos.filter( t => /\bloop\b/.test( t ) );
|
|
282
|
+
|
|
283
|
+
expect( looping.length === 2 && looping.every( t => /\bmuted\b/.test( t ) ),
|
|
284
|
+
"gives both spellings of autoplay and loop the same attributes",
|
|
285
|
+
`found ${looping.length} looping video(s), expected 2` );
|
|
286
|
+
expect( videos.every( t => !/\bautoplay\b/.test( t ) ), "leaves starting an autoplay video to the script" );
|
|
287
|
+
expect( videos.every( t => /\bcontrols\b/.test( t ) ), "gives every video its playback controls" );
|
|
288
|
+
|
|
289
|
+
// ── cover ────────────────────────────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
console.log( "\ncover" );
|
|
292
|
+
|
|
293
|
+
expect( manifest.sections?.cover?.props?.src?.type === "media-file", "declares cover.src as a media file" );
|
|
294
|
+
|
|
295
|
+
const cover = built( "cover.html" );
|
|
296
|
+
const covers = cover.split( /<div class="scavold-cover[ "]/ ).slice( 1 );
|
|
297
|
+
const [ coverImage = "", coverVideo = "" ] = covers;
|
|
298
|
+
|
|
299
|
+
expect( covers.length === 2, "renders both covers of the page", `found ${covers.length}` );
|
|
300
|
+
expect( /<div class="scavold-cover[ "][^>]*\sdata-leading[\s=>]/.test( cover ) && cover.match( /\sdata-leading[\s=>]/g )?.length === 1,
|
|
301
|
+
"marks the cover opening the page, and only that one" );
|
|
302
|
+
expect( cover.includes( '<main data-leading-container="cover">' ),
|
|
303
|
+
"tells the layout before the content renders that the page opens with a cover" );
|
|
304
|
+
expect( !/<main[^>]*data-leading-container/.test( built( "media.html" ) ),
|
|
305
|
+
"tells it nothing on a page that opens with anything else" );
|
|
306
|
+
// The variants, not just the widest one: a cover is the largest image of a page and
|
|
307
|
+
// must not cost a phone the full 1920px file.
|
|
308
|
+
expect( /<source type="image\/webp" srcset="[^"]*wide--320w-[0-9a-f]+\.webp 320w[^"]*wide--1920w-[0-9a-f]+\.webp 1920w"/.test( coverImage ),
|
|
309
|
+
"renders a cover image from all its variants" );
|
|
310
|
+
// A 16:9 source cropped to an upright window is drawn wider than the window.
|
|
311
|
+
expect( coverImage.includes( 'sizes="(min-aspect-ratio: 2400/1350) 100vw, 177.78vh"' ),
|
|
312
|
+
"tells the browser how wide a cropped cover image is drawn" );
|
|
313
|
+
expect( /<img[^>]*loading="eager"/.test( coverImage ) && /<img[^>]*fetchpriority="high"/.test( coverImage ),
|
|
314
|
+
"loads a cover image at once rather than lazily" );
|
|
315
|
+
expect( /<img[^>]*alt="A wide blue field"/.test( coverImage ), "uses the label as the image's alternative text" );
|
|
316
|
+
expect( coverImage.includes( "--scavold-cover-focus:center top" ) || coverImage.includes( "--scavold-cover-focus: center top" ),
|
|
317
|
+
"passes the focal point on to the stylesheet" );
|
|
318
|
+
expect( /class="scavold-cover__overlay"[^>]*>\s*(?:<!--\[-->)?\s*<h1[^>]*>Over the cover image/.test( coverImage ),
|
|
319
|
+
"lays the section's content on top of the image" );
|
|
320
|
+
|
|
321
|
+
const coverVideoTag = tags( coverVideo, "video" )[0] ?? "";
|
|
322
|
+
|
|
323
|
+
expect( coverVideo.startsWith( "scavold-cover--video" ), "tells a video cover by its file type" );
|
|
324
|
+
// A mark for the theme to style, nothing more.
|
|
325
|
+
expect( /^scavold-cover--light"/.test( coverImage ), "marks the tone asked for the content on top" );
|
|
326
|
+
expect( !/scavold-cover--(light|dark)/.test( coverVideo ), "marks no tone where none is asked for" );
|
|
327
|
+
expect( !cover.includes( "data-poster-srcset" ), "keeps the variants of a still image out of the markup" );
|
|
328
|
+
expect( /src="\/media\/clip\.mp4"/.test( coverVideoTag ) && /poster="\/media\/wide--\d+w-[0-9a-f]+\.jpg"/.test( coverVideoTag ),
|
|
329
|
+
"resolves a cover video and its still image to their published URLs" );
|
|
330
|
+
// Started by script once reduced motion has been ruled out, never by the markup.
|
|
331
|
+
expect( /muted/.test( coverVideoTag ) && /loop/.test( coverVideoTag ) && !/autoplay/.test( coverVideoTag ),
|
|
332
|
+
"renders a cover video muted and looping, without autoplay in the markup" );
|
|
333
|
+
expect( /<button type="button" class="scavold-cover__toggle" aria-label="[^"]+"/.test( coverVideo ),
|
|
334
|
+
"offers a labelled button to pause a cover video" );
|
|
286
335
|
|
|
287
336
|
// ── page list ────────────────────────────────────────────────────────────────
|
|
288
337
|
|
|
@@ -303,6 +352,36 @@ expect( menu.includes( '<a href="/containers.html">' ),
|
|
|
303
352
|
expect( crumbs.includes( '<a href="/blog/index.html"' ) && crumbs.includes( 'aria-current="page"' ),
|
|
304
353
|
"marks the page the breadcrumb ends on" );
|
|
305
354
|
|
|
355
|
+
console.log( "\nnamed menus" );
|
|
356
|
+
|
|
357
|
+
const footer = ( blogPage.match( /<nav aria-label="Footer"[\s\S]*?<\/nav>/ ) ?? [""] )[0];
|
|
358
|
+
const footerTree = ( blogPage.match( /<nav aria-label="Footer tree"[\s\S]*?<\/nav>/ ) ?? [""] )[0];
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* @param {string} nav one rendered menu
|
|
362
|
+
* @param {string} label link text of one entry
|
|
363
|
+
* @returns {number} how often the menu links that entry
|
|
364
|
+
*/
|
|
365
|
+
function linked( nav, label ) {
|
|
366
|
+
return nav.split( `>${label}</a>` ).length - 1;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
expect( linked( footer, "Imprint" ) === 1 && linked( footer, "Contact" ) === 1,
|
|
370
|
+
"lists in a named menu the pages choosing it" );
|
|
371
|
+
expect( linked( menu, "Imprint" ) === 0 && linked( menu, "About" ) === 0,
|
|
372
|
+
"leaves a page choosing only a named menu out of the page tree's menus" );
|
|
373
|
+
expect( linked( menu, "Contact" ) === 1,
|
|
374
|
+
"keeps a page in the page tree's menus when it chooses them along with a named one" );
|
|
375
|
+
expect( linked( footer, "Team" ) === 1 && footer.split( "<ul" ).length === 2,
|
|
376
|
+
"keeps a menu without sub-pages flat, listing a page chosen along with its parent beside it" );
|
|
377
|
+
expect( linked( footerTree, "Team" ) === 1 &&
|
|
378
|
+
/>About<\/a><ul>(?:<!--\[-->)?<li[^>]*><a [^>]*>Team<\/a>/.test( footerTree ),
|
|
379
|
+
"lists a page chosen along with its parent below that parent where the menu shows sub-pages, not twice" );
|
|
380
|
+
expect( linked( footer, "Secret" ) === 0 && linked( menu, "Secret" ) === 0,
|
|
381
|
+
"lets hiding from menus rule over choosing one" );
|
|
382
|
+
expect( /chooses menu 'sidebar', which \.cratly\.config\.yaml does not declare/.test( log ),
|
|
383
|
+
"warns about a menu name the site does not declare" );
|
|
384
|
+
|
|
306
385
|
const sitemap = built( "sitemap.xml" );
|
|
307
386
|
|
|
308
387
|
expect( sitemap.includes( "<loc>https://fixture.example.com/index.html</loc>" ) &&
|