Files
iptvnator/tools/testing/website-guides.test.mjs
T
4grayandClaude Fable 5.1 98da686cea feat(website): add the phone remote control guide
New guide at /blog/remote-control-guide/: enabling the remote in Settings,
opening it on a phone from the QR code, what each control does and which
list it navigates, a checklist for a page that does not load, and why the
remote must stay on the local network. Eight FAQ entries. The remote-control
feature page now links to it instead of the M3U guide.

Both screenshots are mock-backed. The phone view is the first "browser" shot:
a manifest entry names a loopback URL and a mobile viewport, and the capture
frames it in a separate Chromium page behind the same network and content
guards, with the manifest validator accepting loopback origins only. The
setup saves the remote-control setting so the app's own server answers, then
selects a live channel; the Xtream mock's marketing scenario now serves live
stream URLs from local bytes so that selection never leaves the machine.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 22:06:23 +02:00

94 lines
3.3 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',
],
},
{
slug: 'remote-control-guide',
screenshots: [
'blog/guides/screenshots/guide-remote-settings-dark.png',
'blog/guides/screenshots/guide-remote-phone-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));
}
});
}