mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 19:46:16 -08:00
New guide at /blog/alternative-sources-guide/: where the Sources chip comes from (a local lookup across the reader's own Xtream playlists, never a search outside them), how to read fact tags versus ~guesses, check availability, switch playlists mid-film without losing the timecode, pin a preferred copy per movie, and what the opt-in auto-switch does and on which players. Eight FAQ entries, opening with the general ContentDisclaimer. The two screenshots are mock-backed: the Xtream mock gains a marketing2 scenario that serves the identical marketing catalog under a second credential pair (with a spec proving the catalogs match), the capture seeds it as a "Fictional Xtream Backup" source only for shots that walk into it, opens its category so the movies reach the local content cache that discovery reads, and two new setup actions open the chip and the checked popover. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
87 lines
3.1 KiB
JavaScript
87 lines
3.1 KiB
JavaScript
import { access, readFile } from 'node:fs/promises';
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
/**
|
|
* Structural checks for guide posts: they must carry FAQPage structured data
|
|
* next to the BlogPosting entry, link to the download hub, and reference only
|
|
* screenshots that the build actually shipped.
|
|
*/
|
|
|
|
const distRoot = new URL('../../dist/apps/website/', import.meta.url);
|
|
const SITE = 'https://4gray.github.io/iptvnator';
|
|
|
|
const GUIDES = [
|
|
{
|
|
slug: 'xtream-codes-setup-guide',
|
|
screenshots: [
|
|
'blog/guides/screenshots/guide-xtream-add-playlist-dark.png',
|
|
'blog/guides/screenshots/guide-xtream-auto-detect-dark.png',
|
|
'blog/guides/screenshots/guide-xtream-live-dark.png',
|
|
],
|
|
},
|
|
{
|
|
slug: 'stalker-portal-setup-guide',
|
|
screenshots: [
|
|
'blog/guides/screenshots/guide-stalker-add-playlist-dark.png',
|
|
'blog/guides/screenshots/guide-stalker-live-dark.png',
|
|
],
|
|
},
|
|
{
|
|
slug: 'm3u-playlist-epg-setup-guide',
|
|
screenshots: [
|
|
'blog/guides/screenshots/guide-m3u-add-playlist-dark.png',
|
|
'blog/guides/screenshots/guide-m3u-live-groups-dark.png',
|
|
'blog/guides/screenshots/guide-epg-settings-dark.png',
|
|
],
|
|
},
|
|
{
|
|
slug: 'offline-downloads-guide',
|
|
screenshots: [
|
|
'blog/guides/screenshots/guide-downloads-movie-detail-dark.png',
|
|
'blog/guides/screenshots/guide-downloads-manager-dark.png',
|
|
'blog/guides/screenshots/guide-downloads-offline-movie-dark.png',
|
|
],
|
|
},
|
|
{
|
|
slug: 'alternative-sources-guide',
|
|
screenshots: [
|
|
'blog/guides/screenshots/guide-sources-chip-dark.png',
|
|
'blog/guides/screenshots/guide-sources-menu-dark.png',
|
|
],
|
|
},
|
|
];
|
|
|
|
const readDist = (relativePath) => readFile(new URL(relativePath, distRoot), 'utf8');
|
|
|
|
function extractJsonLd(html) {
|
|
const blocks = [...html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)];
|
|
assert.ok(blocks.length > 0, 'Expected at least one JSON-LD script block.');
|
|
return blocks.flatMap((match) => JSON.parse(match[1]));
|
|
}
|
|
|
|
for (const guide of GUIDES) {
|
|
test(`${guide.slug}: BlogPosting and FAQPage structured data`, async () => {
|
|
const html = await readDist(`blog/${guide.slug}/index.html`);
|
|
const schema = extractJsonLd(html);
|
|
|
|
assert.ok(schema.some((entry) => entry['@type'] === 'BlogPosting'), 'Expected a BlogPosting entry.');
|
|
|
|
const faq = schema.find((entry) => entry['@type'] === 'FAQPage');
|
|
assert.ok(faq, 'Expected a FAQPage entry.');
|
|
assert.ok(faq.mainEntity.length >= 5, 'Expected at least five FAQ questions.');
|
|
assert.match(html, /Frequently asked questions/);
|
|
assert.match(html, new RegExp(`<link rel="canonical" href="${SITE}/blog/${guide.slug}/"`));
|
|
});
|
|
|
|
test(`${guide.slug}: links to the download hub and ships its screenshots`, async () => {
|
|
const html = await readDist(`blog/${guide.slug}/index.html`);
|
|
assert.match(html, /href="\/iptvnator\/download\/"/);
|
|
|
|
for (const screenshot of guide.screenshots) {
|
|
assert.match(html, new RegExp(`src="/iptvnator/${screenshot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`));
|
|
await access(new URL(screenshot, distRoot));
|
|
}
|
|
});
|
|
}
|