302 lines
9.5 KiB
TypeScript
302 lines
9.5 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
assertNoPublicPageSpeedScores,
|
|
buildPageSpeedAuditInputs,
|
|
type PageSpeedMinimalAuditResult,
|
|
} from "../lib/pagespeed-audit-input";
|
|
|
|
const MOBILE_AND_DESKTOP_FIXTURES: PageSpeedMinimalAuditResult[] = [
|
|
{
|
|
strategy: "mobile",
|
|
status: "succeeded",
|
|
sourceUrl: "https://example.com",
|
|
normalized: {
|
|
metrics: {
|
|
firstContentfulPaintMs: 3200,
|
|
largestContentfulPaintMs: 5000,
|
|
cumulativeLayoutShift: 0.2,
|
|
},
|
|
implications: [
|
|
"Score 0.42: Der erste sichtbare Inhalt erscheint zu langsam.",
|
|
"Die Seite zeigt das Hauptbild zu langsam.",
|
|
"Die Inhalte verschieben sich beim Laden.",
|
|
],
|
|
opportunities: [
|
|
"Nicht verwendetes CSS kann entfernt werden.",
|
|
"Bilder ohne passende Komprimierung koennen verzichtet werden.",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
strategy: "desktop",
|
|
status: "succeeded",
|
|
sourceUrl: "https://example.com",
|
|
normalized: {
|
|
metrics: {
|
|
firstContentfulPaintMs: 1200,
|
|
largestContentfulPaintMs: 2200,
|
|
cumulativeLayoutShift: 0.04,
|
|
},
|
|
implications: [
|
|
"Die Seite zeigt das Hauptbild zu langsam.",
|
|
"Inhalte werden beim Laden sauber angezeigt.",
|
|
],
|
|
opportunities: ["Serverantworten sind stabil.", "Inhalte werden gestaffelt geladen."],
|
|
},
|
|
},
|
|
];
|
|
|
|
test("buildPageSpeedAuditInputs converts normalized implications into German customer impact statements", () => {
|
|
const actual = buildPageSpeedAuditInputs(MOBILE_AND_DESKTOP_FIXTURES);
|
|
|
|
assert.equal(actual.customerImplications.length > 0, true);
|
|
assert.equal(
|
|
actual.customerImplications.includes(
|
|
"Die Seite zeigt das Hauptbild zu langsam.",
|
|
),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
actual.customerImplications.includes("Die Inhalte verschieben sich beim Laden."),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
actual.customerImplications.some((line) => /mobile/i.test(line)),
|
|
true,
|
|
"Customer implications should include a mobile-centric statement.",
|
|
);
|
|
assert.equal(
|
|
assertNoPublicPageSpeedScores(actual.customerImplications),
|
|
true,
|
|
"Customer implications must not contain score-like values.",
|
|
);
|
|
assert.equal(
|
|
assertNoPublicPageSpeedScores(actual.technicalSignals),
|
|
true,
|
|
"Technical signals must not contain score-like values.",
|
|
);
|
|
});
|
|
|
|
test("buildPageSpeedAuditInputs detects meaningful mobile performance gaps versus desktop", () => {
|
|
const actual = buildPageSpeedAuditInputs(MOBILE_AND_DESKTOP_FIXTURES);
|
|
|
|
assert.equal(
|
|
actual.customerImplications.some((line) =>
|
|
/mobile/i.test(line) &&
|
|
/deutlich|spurbar|signifikant|langsamer/.test(line) &&
|
|
/desktop/i.test(line),
|
|
),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test("buildPageSpeedAuditInputs keeps quota/api/unavailable failures in internal notes only", () => {
|
|
const actual = buildPageSpeedAuditInputs([
|
|
{
|
|
strategy: "mobile",
|
|
status: "failed",
|
|
sourceUrl: "https://bad.example",
|
|
errorType: "quota",
|
|
errorSummary: "API quota has been exceeded for this host.",
|
|
},
|
|
{
|
|
strategy: "desktop",
|
|
status: "failed",
|
|
sourceUrl: "https://bad2.example",
|
|
errorType: "unavailable",
|
|
errorSummary: "Page not reachable at the moment.",
|
|
},
|
|
{
|
|
strategy: "mobile",
|
|
status: "failed",
|
|
sourceUrl: "https://bad3.example",
|
|
errorType: "api_error",
|
|
errorSummary: "Lighthouse processing failed due to API timeout.",
|
|
},
|
|
{
|
|
strategy: "desktop",
|
|
status: "succeeded",
|
|
sourceUrl: "https://example.com",
|
|
normalized: {
|
|
implications: ["Die wichtigste Information wird zu langsam sichtbar."],
|
|
},
|
|
},
|
|
]);
|
|
|
|
assert.equal(
|
|
actual.customerImplications.some((line) => /quota|unavailable|timeout|api/i.test(line)),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
actual.technicalSignals.some((line) => /quota|unavailable|timeout|api/i.test(line)),
|
|
false,
|
|
);
|
|
assert.equal(actual.internalNotes.length >= 3, true);
|
|
assert.equal(actual.internalNotes.some((line) => /quota/i.test(line)), true);
|
|
assert.equal(actual.internalNotes.some((line) => /not reachable|unreachable|erreich|timeout/i.test(line)), true);
|
|
assert.equal(actual.internalNotes.some((line) => /api/i.test(line)), true);
|
|
});
|
|
|
|
test("buildPageSpeedAuditInputs strips score-like and raw strings from public outputs", () => {
|
|
const actual = buildPageSpeedAuditInputs([
|
|
{
|
|
strategy: "mobile",
|
|
status: "succeeded",
|
|
sourceUrl: "https://example.com",
|
|
normalized: {
|
|
implications: [
|
|
"Score 0.42: FCP is high.",
|
|
"rawStorageId: file_123",
|
|
"Lighthouse category performance is present.",
|
|
"Die Seite laedt in 3.2 Sekunden.",
|
|
],
|
|
opportunities: [
|
|
"Ein { \"score\": 0.91 } kann optimiert werden.",
|
|
"redundante CSS Dateien.",
|
|
],
|
|
},
|
|
},
|
|
]);
|
|
|
|
assert.equal(assertNoPublicPageSpeedScores(actual.customerImplications), true);
|
|
assert.equal(assertNoPublicPageSpeedScores(actual.technicalSignals), true);
|
|
assert.equal(
|
|
actual.customerImplications.every((line) => !/\d/.test(line)),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test("buildPageSpeedAuditInputs strips URLs, markup, JSON-like payloads, and machine-like words from public outputs", () => {
|
|
const actual = buildPageSpeedAuditInputs([
|
|
{
|
|
strategy: "mobile",
|
|
status: "succeeded",
|
|
sourceUrl: "https://example.com",
|
|
normalized: {
|
|
implications: [
|
|
"Weitere Infos findest du in https://example.com/details",
|
|
"Das <strong>Element</strong> lädt stabil.",
|
|
"{ \"pagespeed\": 0.84, \"lighthouseResult\": {} }",
|
|
"[\"rawStorageId\":\"id-0123456789abcdef0123456789\"]",
|
|
"rawStorageId: run_2026_0001",
|
|
"lighthouseResult suggests a bad candidate.",
|
|
"Die Seite laedt insgesamt spuertbar langsam.",
|
|
],
|
|
opportunities: [
|
|
"Moeglichkeit: <img src=\"x\" />",
|
|
"Pagespeed Score should not appear.",
|
|
"[{\"audit\":\"speed\"}]",
|
|
"Reduziere ungenutzte JavaScript-Dateien.",
|
|
"A longMachineToken_0123456789abcdef0123456789 to test filtering.",
|
|
],
|
|
},
|
|
},
|
|
]);
|
|
|
|
assert.equal(assertNoPublicPageSpeedScores(actual.customerImplications), true);
|
|
assert.equal(assertNoPublicPageSpeedScores(actual.technicalSignals), true);
|
|
assert.equal(actual.customerImplications.includes("Die Seite laedt insgesamt spuertbar langsam."), true);
|
|
assert.equal(
|
|
actual.technicalSignals.some((line) => /unused|reduziere|javascript/i.test(line)),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
actual.customerImplications.every((line) => !/\bhttps?:\/\/|rawstorageid|lighthouseresult|pagespeed|score|<|>|\\{|\\}|\\[|\\]/i.test(line)),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
actual.technicalSignals.every((line) => !/\bhttps?:\/\/|rawstorageid|lighthouseresult|pagespeed|score|<|>|\\{|\\}|\\[|\\]/i.test(line)),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test("buildPageSpeedAuditInputs keeps failure categories in internal notes while removing URLs and JSON fragments", () => {
|
|
const actual = buildPageSpeedAuditInputs([
|
|
{
|
|
strategy: "mobile",
|
|
status: "failed",
|
|
sourceUrl: "https://example.com/audit?x=1",
|
|
errorType: "api_error",
|
|
errorSummary:
|
|
"PageSpeed API failed: { \"lighthouseResult\": {\"code\":\"timeout\"}, \"rawStorageId\": \"abc123\" }",
|
|
},
|
|
{
|
|
strategy: "desktop",
|
|
status: "succeeded",
|
|
sourceUrl: "https://example.com",
|
|
normalized: {
|
|
implications: [
|
|
"Die Seite laedt spuerbar schneller auf Desktop.",
|
|
],
|
|
},
|
|
},
|
|
]);
|
|
|
|
assert.equal(actual.internalNotes.length >= 1, true);
|
|
assert.equal(
|
|
actual.internalNotes.every(
|
|
(line) =>
|
|
!/https?:\/\//i.test(line) &&
|
|
!/\{|\}|\[|\]/i.test(line) &&
|
|
!/rawstorageid|lighthouseresult/i.test(line),
|
|
),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
actual.internalNotes.some((line) => /api|technisch/i.test(line)),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test("buildPageSpeedAuditInputs deduplicates and caps output lists", () => {
|
|
const manyImplications = Array.from({ length: 12 }, (_, index) => [
|
|
"Die Seite ist zu langsam.",
|
|
"Die Seite ist zu langsam.",
|
|
`Implication ${index}`,
|
|
"Wichtige Inhalte sind nicht sofort sichtbar.",
|
|
"Wichtige Inhalte sind nicht sofort sichtbar.",
|
|
]).flat();
|
|
const manyOpportunities = Array.from({ length: 12 }, (_, index) => [
|
|
"Komprimieren Sie Bilder.",
|
|
`Opportunity ${index}`,
|
|
"Komprimieren Sie Bilder.",
|
|
"Inhalte werden nachgeladen.",
|
|
]).flat();
|
|
|
|
const actual = buildPageSpeedAuditInputs([
|
|
{
|
|
strategy: "mobile",
|
|
status: "succeeded",
|
|
sourceUrl: "https://example.com",
|
|
normalized: {
|
|
implications: manyImplications,
|
|
opportunities: manyOpportunities,
|
|
},
|
|
},
|
|
...Array.from({ length: 10 }, (_, index) => ({
|
|
strategy: "desktop" as const,
|
|
status: "failed" as const,
|
|
sourceUrl: `https://example.com/${index}`,
|
|
errorType: "api_error" as const,
|
|
errorSummary: `Run ${String.fromCharCode(97 + (index % 26))} had internal problem.`,
|
|
})),
|
|
]);
|
|
|
|
assert.equal(actual.customerImplications.length <= 8, true);
|
|
assert.equal(actual.technicalSignals.length <= 8, true);
|
|
assert.equal(actual.customerImplications.length > 0, true);
|
|
assert.equal(actual.technicalSignals.length > 0, true);
|
|
assert.equal(actual.internalNotes.length, 6);
|
|
|
|
assert.equal(
|
|
new Set(actual.customerImplications).size,
|
|
actual.customerImplications.length,
|
|
);
|
|
assert.equal(
|
|
new Set(actual.technicalSignals).size,
|
|
actual.technicalSignals.length,
|
|
);
|
|
});
|