test: add MVP QA gate
This commit is contained in:
parent
af09f705b1
commit
081d6085a5
10 changed files with 413 additions and 7 deletions
47
src/lib/presentation-boundary.test.ts
Normal file
47
src/lib/presentation-boundary.test.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
const presentationRoots = [
|
||||
join(process.cwd(), "src", "lib", "ui"),
|
||||
join(process.cwd(), "src", "routes"),
|
||||
];
|
||||
|
||||
const forbiddenTerms = [
|
||||
"dimensionlab",
|
||||
"dimension lab",
|
||||
"vaultwarden",
|
||||
"forgejo",
|
||||
"grafana",
|
||||
"uptime kuma",
|
||||
"prometheus",
|
||||
"backrest",
|
||||
"open webui",
|
||||
"comfyui",
|
||||
"adminer",
|
||||
"cockpit",
|
||||
"ollama",
|
||||
"dimensionlab.net",
|
||||
];
|
||||
|
||||
describe("presentation content boundary", () => {
|
||||
test("keeps environment-specific content out of route and UI implementation", () => {
|
||||
const source = presentationRoots.map(readPresentationSource).join("\n").toLowerCase();
|
||||
|
||||
expect(forbiddenTerms.filter((term) => source.includes(term))).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
function readPresentationSource(path: string): string {
|
||||
const stats = statSync(path);
|
||||
if (stats.isFile()) {
|
||||
if (path.endsWith(".test.ts")) return "";
|
||||
if (path.includes(`${join("src", "lib", "ui", "stories")}${"/"}`)) return "";
|
||||
if (!/\.(svelte|ts|css)$/.test(path)) return "";
|
||||
return readFileSync(path, "utf8");
|
||||
}
|
||||
|
||||
return readdirSync(path)
|
||||
.map((entry) => readPresentationSource(join(path, entry)))
|
||||
.join("\n");
|
||||
}
|
||||
13
src/lib/testing/external-api-mocks.test.ts
Normal file
13
src/lib/testing/external-api-mocks.test.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { externalApiHandlers } from "./external-api-mocks";
|
||||
|
||||
describe("external API mocks", () => {
|
||||
test("defines deterministic handlers for deferred datasource adapters", () => {
|
||||
expect(externalApiHandlers).toHaveLength(3);
|
||||
expect(externalApiHandlers.map((handler) => handler.info.header)).toEqual([
|
||||
"GET https://prometheus.dimensionlab.net/api/v1/query",
|
||||
"GET https://uptime.dimensionlab.net/api/status-page/*",
|
||||
"GET https://api.open-meteo.com/v1/forecast",
|
||||
]);
|
||||
});
|
||||
});
|
||||
33
src/lib/testing/external-api-mocks.ts
Normal file
33
src/lib/testing/external-api-mocks.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { http, HttpResponse } from "msw";
|
||||
|
||||
export const externalApiHandlers = [
|
||||
http.get("https://prometheus.dimensionlab.net/api/v1/query", () =>
|
||||
HttpResponse.json({
|
||||
status: "success",
|
||||
data: {
|
||||
resultType: "vector",
|
||||
result: [
|
||||
{
|
||||
metric: { instance: "fixture" },
|
||||
value: [1771430400, "1"],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
),
|
||||
http.get("https://uptime.dimensionlab.net/api/status-page/*", () =>
|
||||
HttpResponse.json({
|
||||
status: "ok",
|
||||
incidents: [],
|
||||
monitors: [{ name: "fixture", status: "up" }],
|
||||
}),
|
||||
),
|
||||
http.get("https://api.open-meteo.com/v1/forecast", () =>
|
||||
HttpResponse.json({
|
||||
current: {
|
||||
temperature_2m: 21.4,
|
||||
weather_code: 0,
|
||||
},
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
|
@ -2,13 +2,24 @@
|
|||
import type { UiModuleBlock } from "../types";
|
||||
import IconGlyph from "./IconGlyph.svelte";
|
||||
|
||||
const generatedId = $props.id();
|
||||
|
||||
let { module }: { module: UiModuleBlock } = $props();
|
||||
|
||||
let titleId = $derived(`${generatedId}-title`);
|
||||
let ariaLabel = $derived(module.title ? undefined : module.label || module.id);
|
||||
</script>
|
||||
|
||||
<aside class="module-card" data-severity={module.severity || "neutral"} data-model-id={module.id}>
|
||||
<aside
|
||||
class="module-card"
|
||||
data-severity={module.severity || "neutral"}
|
||||
data-model-id={module.id}
|
||||
aria-labelledby={module.title ? titleId : undefined}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<div>
|
||||
{#if module.title}
|
||||
<h2>{module.title}</h2>
|
||||
<h2 id={titleId}>{module.title}</h2>
|
||||
{/if}
|
||||
{#if module.value}
|
||||
<strong>{module.value}</strong>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue