* refactor: move lighthouse audits to dataforseo * chore: remove obsolete audit settings modal * refactor: rename psi flows to lighthouse * save * refactor: simplify audit lighthouse storage flow * fix: separate lighthouse metrics from actionable audits * refactor: remove redundant audit project inputs * feat: redesign lighthouse issues screen with score gauges and table layout Replace flat score cards with circular SVG gauges, condense metrics into a compact grid, and switch issue list from cards to an expandable table with fixed column widths. * test: harden lighthouse regression coverage * fix: restore project-scoped audit inputs * refactor: simplify lighthouse payload handling * refactor: inline lighthouse server handlers * refactor: share audit workflow types * refactor: simplify lighthouse payload flows * save * refactor: drop project pagespeed api key * fix: restore lighthouse issues loading with resilient project context * fix: restore audit issues back navigation * refactor: simplify project context and lighthouse error handling * fix: tolerate DataForSEO lighthouse payload drift * refactor: route audit lighthouse through dataforseo client
251 lines
7.7 KiB
TypeScript
251 lines
7.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { parseDataforseoLighthousePayload } from "@/server/lib/dataforseoLighthousePayload";
|
|
import { readStoredLighthousePayload } from "@/server/lib/lighthousePayload";
|
|
|
|
describe("parseDataforseoLighthousePayload", () => {
|
|
it("stores only issue-level lighthouse data and key metadata", () => {
|
|
const parsed = parseDataforseoLighthousePayload(
|
|
{
|
|
status_code: 20000,
|
|
status_message: "Ok.",
|
|
tasks: [
|
|
{
|
|
id: "task-1",
|
|
status_code: 20000,
|
|
status_message: "Ok.",
|
|
cost: 0.00425,
|
|
result: [
|
|
{
|
|
requestedUrl: "https://everyapp.dev/",
|
|
finalUrl: "https://everyapp.dev/",
|
|
lighthouseVersion: "12.2.0",
|
|
categories: {
|
|
performance: {
|
|
score: 0.54,
|
|
auditRefs: [{ id: "unused-javascript" }],
|
|
},
|
|
accessibility: {
|
|
score: 0.93,
|
|
auditRefs: [{ id: "accesskeys" }],
|
|
},
|
|
"best-practices": { score: 0.79, auditRefs: [] },
|
|
seo: { score: 0.92, auditRefs: [] },
|
|
},
|
|
audits: {
|
|
"unused-javascript": {
|
|
title: "Reduce unused JavaScript",
|
|
description: "Trim dead code.",
|
|
score: 0,
|
|
scoreDisplayMode: "metricSavings",
|
|
displayValue: "Potential savings of 188 KiB",
|
|
numericValue: 193002,
|
|
details: {
|
|
overallSavingsMs: 1270,
|
|
overallSavingsBytes: 193002,
|
|
items: [
|
|
{
|
|
url: "https://cdn.example.com/app.js",
|
|
wastedBytes: 193002,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
accesskeys: {
|
|
title: "`[accesskey]` values are unique",
|
|
description: "Access keys should not conflict.",
|
|
score: null,
|
|
scoreDisplayMode: "error",
|
|
},
|
|
interactive: {
|
|
title: "Time to Interactive",
|
|
description: "Time until the page becomes interactive.",
|
|
score: 0.13,
|
|
scoreDisplayMode: "numeric",
|
|
displayValue: "12.8 s",
|
|
numericValue: 12800,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
url: "https://everyapp.dev/",
|
|
strategy: "mobile",
|
|
},
|
|
);
|
|
|
|
const { report } = readStoredLighthousePayload(JSON.stringify(parsed));
|
|
|
|
expect(parsed.metrics.timeToInteractive.displayValue).toBe("12.8 s");
|
|
expect(parsed).toMatchObject({
|
|
version: 2,
|
|
source: "dataforseo-lighthouse",
|
|
hasIssueDetails: true,
|
|
metadata: {
|
|
requestedUrl: "https://everyapp.dev/",
|
|
finalUrl: "https://everyapp.dev/",
|
|
strategy: "mobile",
|
|
lighthouseVersion: "12.2.0",
|
|
taskId: "task-1",
|
|
cost: 0.00425,
|
|
},
|
|
scores: {
|
|
performance: 54,
|
|
accessibility: 93,
|
|
"best-practices": 79,
|
|
seo: 92,
|
|
},
|
|
metrics: {
|
|
timeToInteractive: {
|
|
score: 13,
|
|
displayValue: "12.8 s",
|
|
numericValue: 12800,
|
|
},
|
|
},
|
|
});
|
|
expect(parsed.issues).toHaveLength(1);
|
|
expect(parsed.issues).not.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ auditKey: "interactive" }),
|
|
]),
|
|
);
|
|
expect(parsed).not.toHaveProperty("lighthouseResult");
|
|
|
|
expect(report.hasIssueDetails).toBe(true);
|
|
expect(report.issues).toEqual([
|
|
expect.objectContaining({
|
|
auditKey: "unused-javascript",
|
|
category: "performance",
|
|
impactMs: 1270,
|
|
impactBytes: 193002,
|
|
title: "Reduce unused JavaScript",
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("throws when the lighthouse response has no category scores", () => {
|
|
expect(() =>
|
|
parseDataforseoLighthousePayload(
|
|
{
|
|
status_code: 20000,
|
|
status_message: "Ok.",
|
|
tasks: [
|
|
{
|
|
id: "task-1",
|
|
status_code: 20000,
|
|
status_message: "Ok.",
|
|
cost: 0.00425,
|
|
result: [
|
|
{
|
|
requestedUrl:
|
|
"https://everyapp.dev/blog/category/cyber-security",
|
|
finalUrl:
|
|
"https://everyapp.dev/blog/category/cyber-security/",
|
|
lighthouseVersion: "12.2.0",
|
|
categories: {
|
|
performance: { score: null, auditRefs: [] },
|
|
accessibility: { score: null, auditRefs: [] },
|
|
"best-practices": { score: null, auditRefs: [] },
|
|
seo: { score: null, auditRefs: [] },
|
|
},
|
|
audits: {},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
url: "https://everyapp.dev/blog/category/cyber-security",
|
|
strategy: "desktop",
|
|
},
|
|
),
|
|
).toThrow("DataForSEO Lighthouse returned no category scores");
|
|
});
|
|
|
|
it("throws when DataForSEO returns a non-success task status", () => {
|
|
expect(() =>
|
|
parseDataforseoLighthousePayload(
|
|
{
|
|
status_code: 20000,
|
|
status_message: "Ok.",
|
|
tasks: [
|
|
{
|
|
id: "task-1",
|
|
status_code: 40501,
|
|
status_message: "Insufficient credits",
|
|
result: [],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
url: "https://everyapp.dev/",
|
|
strategy: "mobile",
|
|
},
|
|
),
|
|
).toThrow("Insufficient credits");
|
|
});
|
|
|
|
it("includes schema details when the payload shape is invalid", () => {
|
|
expect(() =>
|
|
parseDataforseoLighthousePayload(null, {
|
|
url: "https://everyapp.dev/",
|
|
strategy: "mobile",
|
|
}),
|
|
).toThrow("<root>");
|
|
});
|
|
|
|
it("accepts audits whose details.items is an object", () => {
|
|
expect(() =>
|
|
parseDataforseoLighthousePayload(
|
|
{
|
|
status_code: 20000,
|
|
status_message: "Ok.",
|
|
tasks: [
|
|
{
|
|
id: "task-1",
|
|
status_code: 20000,
|
|
status_message: "Ok.",
|
|
cost: 0.00425,
|
|
result: [
|
|
{
|
|
requestedUrl: "https://everyapp.dev/",
|
|
finalUrl: "https://everyapp.dev/",
|
|
lighthouseVersion: "12.2.0",
|
|
categories: {
|
|
performance: {
|
|
score: 0.54,
|
|
auditRefs: [{ id: "document-latency-insight" }],
|
|
},
|
|
accessibility: { score: 0.93, auditRefs: [] },
|
|
"best-practices": { score: 0.79, auditRefs: [] },
|
|
seo: { score: 0.92, auditRefs: [] },
|
|
},
|
|
audits: {
|
|
"document-latency-insight": {
|
|
title: "Document request latency",
|
|
description: "Latency insight.",
|
|
score: 0,
|
|
scoreDisplayMode: "informative",
|
|
details: {
|
|
items: {
|
|
latencyMs: 120,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
url: "https://everyapp.dev/",
|
|
strategy: "mobile",
|
|
},
|
|
),
|
|
).not.toThrow();
|
|
});
|
|
});
|