@anselmdk/feature-spec-md 0.1.0 → 0.1.1-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -7
- package/SPEC_FORMAT.md +175 -35
- package/dist/cli.js +169 -29
- package/dist/cli.js.map +1 -1
- package/dist/filePatterns.d.ts +5 -0
- package/dist/filePatterns.d.ts.map +1 -0
- package/dist/filePatterns.js +24 -0
- package/dist/filePatterns.js.map +1 -0
- package/dist/html.d.ts +2 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +9 -0
- package/dist/html.js.map +1 -0
- package/dist/index.d.ts +14 -69
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -43
- package/dist/index.js.map +1 -1
- package/dist/playwright.d.ts +45 -0
- package/dist/playwright.d.ts.map +1 -0
- package/dist/playwright.js +109 -0
- package/dist/playwright.js.map +1 -0
- package/dist/reportTemplate.d.ts +14 -0
- package/dist/reportTemplate.d.ts.map +1 -0
- package/dist/reportTemplate.js +154 -0
- package/dist/reportTemplate.js.map +1 -0
- package/dist/screenshots.d.ts +6 -0
- package/dist/screenshots.d.ts.map +1 -0
- package/dist/screenshots.js +49 -0
- package/dist/screenshots.js.map +1 -0
- package/dist/specDocuments.d.ts +72 -0
- package/dist/specDocuments.d.ts.map +1 -0
- package/dist/specDocuments.js +414 -0
- package/dist/specDocuments.js.map +1 -0
- package/dist/testImplementationReport.d.ts +31 -0
- package/dist/testImplementationReport.d.ts.map +1 -0
- package/dist/testImplementationReport.js +88 -0
- package/dist/testImplementationReport.js.map +1 -0
- package/dist/types.d.ts +142 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +20 -4
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { expandFilePatterns } from "./filePatterns.js";
|
|
4
|
+
/** Create a Playwright helper that captures screenshot evidence for spec step lines. */
|
|
5
|
+
export function createPlaywrightSpecEvidence(test, options) {
|
|
6
|
+
const cwd = options.cwd ?? process.cwd();
|
|
7
|
+
const reportDir = path.resolve(cwd, options.reportDir ?? "test-results/spec-report");
|
|
8
|
+
const screenshotsDirName = options.screenshotsDirName ?? "screenshots";
|
|
9
|
+
const screenshotDir = path.join(reportDir, screenshotsDirName);
|
|
10
|
+
const stepsPromise = loadSpecSteps(options.specs, cwd);
|
|
11
|
+
const entriesByWorker = new Map();
|
|
12
|
+
return {
|
|
13
|
+
async specStep(page, testInfo, scenarioId, stepText, body) {
|
|
14
|
+
const step = await findSpecStep(stepsPromise, scenarioId, stepText);
|
|
15
|
+
await test.step(`${step.keyword} ${step.text}`, async () => {
|
|
16
|
+
await body();
|
|
17
|
+
await captureStepScreenshot({
|
|
18
|
+
entriesByWorker,
|
|
19
|
+
page,
|
|
20
|
+
reportDir,
|
|
21
|
+
screenshotDir,
|
|
22
|
+
screenshotsDirName,
|
|
23
|
+
step,
|
|
24
|
+
testInfo,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Parse spec files and return all scenario step lines addressable by Playwright tests. */
|
|
31
|
+
export async function loadSpecSteps(patterns, cwd = process.cwd()) {
|
|
32
|
+
const previousCwd = process.cwd();
|
|
33
|
+
const steps = [];
|
|
34
|
+
try {
|
|
35
|
+
process.chdir(cwd);
|
|
36
|
+
for (const specPath of await expandFilePatterns(patterns)) {
|
|
37
|
+
const source = await readFile(path.resolve(cwd, specPath), "utf8");
|
|
38
|
+
let scenarioId = "";
|
|
39
|
+
for (const [index, line] of source
|
|
40
|
+
.replace(/\r\n/g, "\n")
|
|
41
|
+
.split("\n")
|
|
42
|
+
.entries()) {
|
|
43
|
+
const scenario = line.match(/^###\s+([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*-S\d{3}):/);
|
|
44
|
+
if (scenario) {
|
|
45
|
+
scenarioId = scenario[1];
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const step = line.trim().match(/^(Given|When|Then|And|But)\s+(.+)$/);
|
|
49
|
+
if (scenarioId && step) {
|
|
50
|
+
steps.push({
|
|
51
|
+
scenarioId,
|
|
52
|
+
specPath,
|
|
53
|
+
line: index + 1,
|
|
54
|
+
keyword: step[1],
|
|
55
|
+
text: step[2].trim(),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
process.chdir(previousCwd);
|
|
63
|
+
}
|
|
64
|
+
return steps;
|
|
65
|
+
}
|
|
66
|
+
async function findSpecStep(stepsPromise, scenarioId, stepText) {
|
|
67
|
+
const steps = await stepsPromise;
|
|
68
|
+
const normalizedStepText = stepText.replace(/^(Given|When|Then|And|But)\s+/, "");
|
|
69
|
+
const step = steps.find((candidate) => candidate.scenarioId === scenarioId &&
|
|
70
|
+
candidate.text === normalizedStepText);
|
|
71
|
+
if (!step) {
|
|
72
|
+
throw new Error(`No spec step found for ${scenarioId}: ${stepText}`);
|
|
73
|
+
}
|
|
74
|
+
return step;
|
|
75
|
+
}
|
|
76
|
+
async function captureStepScreenshot(options) {
|
|
77
|
+
await mkdir(options.screenshotDir, { recursive: true });
|
|
78
|
+
const fileName = `${options.step.scenarioId}-line-${options.step.line}-${slug(options.step.text)}.png`;
|
|
79
|
+
const screenshotPath = path.join(options.screenshotDir, fileName);
|
|
80
|
+
const relativePath = `${options.screenshotsDirName}/${fileName}`;
|
|
81
|
+
const title = `${options.step.scenarioId}:${options.step.line} ${options.step.keyword} ${options.step.text}`;
|
|
82
|
+
await options.page.screenshot({ fullPage: true, path: screenshotPath });
|
|
83
|
+
await options.testInfo.attach(title, {
|
|
84
|
+
contentType: "image/png",
|
|
85
|
+
path: screenshotPath,
|
|
86
|
+
});
|
|
87
|
+
const entries = options.entriesByWorker.get(options.testInfo.workerIndex) ?? [];
|
|
88
|
+
entries.push({
|
|
89
|
+
specPath: options.step.specPath,
|
|
90
|
+
line: options.step.line,
|
|
91
|
+
path: relativePath,
|
|
92
|
+
title,
|
|
93
|
+
testPath: options.testInfo.file,
|
|
94
|
+
});
|
|
95
|
+
options.entriesByWorker.set(options.testInfo.workerIndex, entries);
|
|
96
|
+
await writeWorkerManifest(options.reportDir, options.testInfo.workerIndex, entries);
|
|
97
|
+
}
|
|
98
|
+
async function writeWorkerManifest(reportDir, workerIndex, screenshots) {
|
|
99
|
+
await mkdir(reportDir, { recursive: true });
|
|
100
|
+
await writeFile(path.join(reportDir, `screenshots-${workerIndex}.json`), JSON.stringify({ screenshots }, null, 2), "utf8");
|
|
101
|
+
}
|
|
102
|
+
function slug(value) {
|
|
103
|
+
return value
|
|
104
|
+
.toLowerCase()
|
|
105
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
106
|
+
.replace(/^-|-$/g, "")
|
|
107
|
+
.slice(0, 72);
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=playwright.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright.js","sourceRoot":"","sources":["../src/playwright.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CoverageSummary, DesignSpec, FeatureSpec, ModelSpec, SpecScreenshot, StackSpec, ValidationIssue } from "./types.js";
|
|
2
|
+
export type ReportOptions = {
|
|
3
|
+
coverage?: CoverageSummary;
|
|
4
|
+
models?: ModelSpec[];
|
|
5
|
+
stacks?: StackSpec[];
|
|
6
|
+
designs?: DesignSpec[];
|
|
7
|
+
screenshots?: SpecScreenshot[];
|
|
8
|
+
validationIssues?: ValidationIssue[];
|
|
9
|
+
title?: string;
|
|
10
|
+
generatedAt?: string;
|
|
11
|
+
};
|
|
12
|
+
/** Render a complete self-contained feature spec report as HTML. */
|
|
13
|
+
export declare function renderHtmlReport(specs: FeatureSpec[], options?: ReportOptions): string;
|
|
14
|
+
//# sourceMappingURL=reportTemplate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportTemplate.d.ts","sourceRoot":"","sources":["../src/reportTemplate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,eAAe,EACf,UAAU,EACV,WAAW,EAEX,SAAS,EACT,cAAc,EACd,SAAS,EACT,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,GAAE,aAAkB,UAqC5B"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { html } from "./html.js";
|
|
2
|
+
import { screenshotKey } from "./screenshots.js";
|
|
3
|
+
/** Render a complete self-contained feature spec report as HTML. */
|
|
4
|
+
export function renderHtmlReport(specs, options = {}) {
|
|
5
|
+
const title = options.title ?? "Feature Spec Report";
|
|
6
|
+
const issues = options.validationIssues ?? [];
|
|
7
|
+
const screenshots = options.screenshots ?? [];
|
|
8
|
+
return `<!doctype html>
|
|
9
|
+
<html lang="en">
|
|
10
|
+
<head>
|
|
11
|
+
<meta charset="utf-8">
|
|
12
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
13
|
+
<title>${html(title)}</title>
|
|
14
|
+
<style>
|
|
15
|
+
body{font-family:system-ui,sans-serif;max-width:1180px;margin:0 auto;padding:40px 24px;color:#1f2328}
|
|
16
|
+
.panel{border:1px solid #d0d7de;border-radius:8px;padding:20px;margin:18px 0}
|
|
17
|
+
.ok{color:#1a7f37}.missing,.error{color:#cf222e}.warning{color:#9a6700}
|
|
18
|
+
.badge{border:1px solid #d0d7de;border-radius:999px;padding:2px 8px;font-size:12px;white-space:nowrap}
|
|
19
|
+
.feature-header{display:flex;gap:12px;align-items:center;justify-content:space-between}
|
|
20
|
+
.scenario{border:1px solid #d0d7de;border-radius:8px;margin:12px 0;background:#fff}
|
|
21
|
+
.scenario summary{cursor:pointer;padding:14px 16px;font-weight:600}
|
|
22
|
+
.scenario-body{padding:0 16px 16px}
|
|
23
|
+
.step{border-left:3px solid #d0d7de;margin:12px 0;padding:2px 0 2px 12px}
|
|
24
|
+
.step p{margin:8px 0}
|
|
25
|
+
.screenshots{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;margin:10px 0 14px}
|
|
26
|
+
.screenshot{border:1px solid #d0d7de;border-radius:8px;overflow:hidden;background:#f6f8fa}
|
|
27
|
+
.screenshot img{display:block;width:100%;height:auto}
|
|
28
|
+
.screenshot figcaption{font-size:12px;padding:8px;color:#57606a}
|
|
29
|
+
</style>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<h1>${html(title)}</h1>
|
|
33
|
+
<p>Generated ${html(options.generatedAt ?? new Date().toISOString())}.</p>
|
|
34
|
+
${renderIssues(issues)}
|
|
35
|
+
${renderModels(options.models ?? [], options.coverage)}
|
|
36
|
+
${specs.map((spec) => renderSpec(spec, options.coverage, screenshots)).join("\n")}
|
|
37
|
+
</body>
|
|
38
|
+
</html>`;
|
|
39
|
+
}
|
|
40
|
+
function renderIssues(issues) {
|
|
41
|
+
if (!issues.length) {
|
|
42
|
+
return `<section class="panel"><h2>Validation</h2><p class="ok">No validation issues found.</p></section>`;
|
|
43
|
+
}
|
|
44
|
+
return `<section class="panel"><h2>Validation</h2><ul>${issues
|
|
45
|
+
.map((issue) => `<li class="${issue.severity}"><code>${html(`${issue.filePath ?? ""}${issue.line ? `:${issue.line}` : ""}`)}</code> ${html(issue.message)}</li>`)
|
|
46
|
+
.join("")}</ul></section>`;
|
|
47
|
+
}
|
|
48
|
+
function renderModels(models, coverage) {
|
|
49
|
+
if (!models.length)
|
|
50
|
+
return "";
|
|
51
|
+
const modelCoverage = coverage?.modelCoverage ?? [];
|
|
52
|
+
return `<section class="panel">
|
|
53
|
+
<h2>Models</h2>
|
|
54
|
+
${models
|
|
55
|
+
.map((model) => `<section>
|
|
56
|
+
<div class="feature-header">
|
|
57
|
+
<h3>${html(model.frontmatter.id)} ${html(model.title)}</h3>
|
|
58
|
+
<span class="badge">${html(model.frontmatter.status ?? "draft")}</span>
|
|
59
|
+
</div>
|
|
60
|
+
<p>${html(model.purpose)}</p>
|
|
61
|
+
<h4>Model</h4>
|
|
62
|
+
<ul>${model.modelItems
|
|
63
|
+
.map((item) => {
|
|
64
|
+
const coverageItem = modelCoverage.find((candidate) => candidate.id === item.id);
|
|
65
|
+
return `<li><code>${html(item.id)}</code>: ${html(item.title)} ${coverageBadge(coverageItem?.covered)}</li>`;
|
|
66
|
+
})
|
|
67
|
+
.join("")}</ul>
|
|
68
|
+
${model.rules.length
|
|
69
|
+
? `<h4>Rules</h4><ul>${model.rules
|
|
70
|
+
.map((rule) => `<li><code>${html(rule.id)}</code>: ${html(rule.text)}</li>`)
|
|
71
|
+
.join("")}</ul>`
|
|
72
|
+
: ""}
|
|
73
|
+
</section>`)
|
|
74
|
+
.join("\n")}
|
|
75
|
+
</section>`;
|
|
76
|
+
}
|
|
77
|
+
function renderSpec(spec, coverage, screenshots = []) {
|
|
78
|
+
const screenshotsByLine = groupScreenshotsByLine(screenshots);
|
|
79
|
+
const ruleCoverage = coverage?.ruleCoverage ?? [];
|
|
80
|
+
const scenarioCoverage = coverage?.scenarioCoverage ?? [];
|
|
81
|
+
return `<section class="panel">
|
|
82
|
+
<div class="feature-header">
|
|
83
|
+
<h2>${html(spec.frontmatter.id)} ${html(spec.title)}</h2>
|
|
84
|
+
<span class="badge">${html(spec.frontmatter.status ?? "draft")}</span>
|
|
85
|
+
</div>
|
|
86
|
+
<p>${html(spec.purpose)}</p>
|
|
87
|
+
<h3>Rules</h3>
|
|
88
|
+
<ul>${spec.rules
|
|
89
|
+
.map((rule) => {
|
|
90
|
+
const item = ruleCoverage.find((coverageItem) => coverageItem.id === rule.id);
|
|
91
|
+
return `<li><code>${html(rule.id)}</code>: ${html(rule.text)} ${coverageBadge(item?.covered, ruleScenarioShortIds(item, scenarioCoverage))}</li>`;
|
|
92
|
+
})
|
|
93
|
+
.join("")}</ul>
|
|
94
|
+
<h3>Scenarios</h3>
|
|
95
|
+
${spec.scenarios
|
|
96
|
+
.map((scenario) => {
|
|
97
|
+
const screenshotCount = scenario.steps.reduce((count, step) => count +
|
|
98
|
+
(screenshotsByLine.get(screenshotKey(spec.filePath, step.line))
|
|
99
|
+
?.length ?? 0), 0);
|
|
100
|
+
return `<details class="scenario">
|
|
101
|
+
<summary><code>${html(scenario.id)}</code>: ${html(scenario.title)} ${coverageBadge(scenarioCoverage.find((item) => item.id === scenario.id)?.covered)} <span class="badge">${screenshotCount} screenshot${screenshotCount === 1 ? "" : "s"}</span></summary>
|
|
102
|
+
<div class="scenario-body">${scenario.steps.map((step) => renderStep(spec, step, screenshotsByLine)).join("")}</div>
|
|
103
|
+
</details>`;
|
|
104
|
+
})
|
|
105
|
+
.join("\n")}
|
|
106
|
+
</section>`;
|
|
107
|
+
}
|
|
108
|
+
function renderStep(spec, step, screenshotsByLine) {
|
|
109
|
+
const screenshots = screenshotsByLine.get(screenshotKey(spec.filePath, step.line)) ?? [];
|
|
110
|
+
const evidenceBadge = screenshots.length
|
|
111
|
+
? `<span class="badge ok">${screenshots.length} screenshot${screenshots.length === 1 ? "" : "s"}</span>`
|
|
112
|
+
: `<span class="badge missing">missing screenshot</span>`;
|
|
113
|
+
return `<div class="step"><p><strong>${html(step.keyword)}</strong> ${html(step.text)} <span class="badge">line ${step.line}</span> ${evidenceBadge}</p>${renderScreenshots(screenshots)}</div>`;
|
|
114
|
+
}
|
|
115
|
+
function renderScreenshots(screenshots) {
|
|
116
|
+
if (!screenshots.length)
|
|
117
|
+
return "";
|
|
118
|
+
return `<div class="screenshots">${screenshots
|
|
119
|
+
.map((screenshot) => `<figure class="screenshot"><img src="${html(screenshot.path)}" alt="${html(screenshot.title ?? `Screenshot for ${screenshot.specPath}:${screenshot.line}`)}"><figcaption>${html(screenshot.title ?? `${screenshot.specPath}:${screenshot.line}`)}</figcaption></figure>`)
|
|
120
|
+
.join("")}</div>`;
|
|
121
|
+
}
|
|
122
|
+
function coverageBadge(covered, suffixes = []) {
|
|
123
|
+
return covered === undefined
|
|
124
|
+
? ""
|
|
125
|
+
: covered
|
|
126
|
+
? `<span class="badge ok">covered${suffixes.length ? ` ${suffixes.map(html).join(" ")}` : ""}</span>`
|
|
127
|
+
: `<span class="badge missing">missing coverage</span>`;
|
|
128
|
+
}
|
|
129
|
+
function ruleScenarioShortIds(ruleCoverage, scenarioCoverage) {
|
|
130
|
+
if (!ruleCoverage?.covered)
|
|
131
|
+
return [];
|
|
132
|
+
const scenarioIds = new Set();
|
|
133
|
+
for (const ruleReference of ruleCoverage.references) {
|
|
134
|
+
for (const scenario of scenarioCoverage) {
|
|
135
|
+
const coversRule = scenario.references.some((scenarioReference) => scenarioReference.filePath === ruleReference.filePath &&
|
|
136
|
+
scenarioReference.line === ruleReference.line);
|
|
137
|
+
if (coversRule)
|
|
138
|
+
scenarioIds.add(shortScenarioId(scenario.id));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Array.from(scenarioIds).sort();
|
|
142
|
+
}
|
|
143
|
+
function shortScenarioId(id) {
|
|
144
|
+
return id.match(/-(S\d{3})$/)?.[1] ?? id;
|
|
145
|
+
}
|
|
146
|
+
function groupScreenshotsByLine(screenshots) {
|
|
147
|
+
const grouped = new Map();
|
|
148
|
+
for (const screenshot of screenshots) {
|
|
149
|
+
const key = screenshotKey(screenshot.specPath, screenshot.line);
|
|
150
|
+
grouped.set(key, [...(grouped.get(key) ?? []), screenshot]);
|
|
151
|
+
}
|
|
152
|
+
return grouped;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=reportTemplate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportTemplate.js","sourceRoot":"","sources":["../src/reportTemplate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SpecScreenshot } from "./types.js";
|
|
2
|
+
/** Load screenshot evidence manifests and normalize entries for report rendering. */
|
|
3
|
+
export declare function collectSpecScreenshots(patterns: string[]): Promise<SpecScreenshot[]>;
|
|
4
|
+
export declare function screenshotKey(filePath: string, line: number): string;
|
|
5
|
+
export declare function normalizeFilePath(filePath: string): string;
|
|
6
|
+
//# sourceMappingURL=screenshots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshots.d.ts","sourceRoot":"","sources":["../src/screenshots.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,qFAAqF;AACrF,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,6BAgB9D;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAE3D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,UAEjD"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { expandArtifactPatterns } from "./filePatterns.js";
|
|
3
|
+
/** Load screenshot evidence manifests and normalize entries for report rendering. */
|
|
4
|
+
export async function collectSpecScreenshots(patterns) {
|
|
5
|
+
const screenshots = [];
|
|
6
|
+
for (const file of await expandArtifactPatterns(patterns)) {
|
|
7
|
+
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
8
|
+
const entries = Array.isArray(parsed)
|
|
9
|
+
? parsed
|
|
10
|
+
: isRecord(parsed) && Array.isArray(parsed.screenshots)
|
|
11
|
+
? parsed.screenshots
|
|
12
|
+
: [];
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const screenshot = normalizeScreenshot(entry);
|
|
15
|
+
if (screenshot)
|
|
16
|
+
screenshots.push(screenshot);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return screenshots;
|
|
20
|
+
}
|
|
21
|
+
export function screenshotKey(filePath, line) {
|
|
22
|
+
return `${normalizeFilePath(filePath)}:${line}`;
|
|
23
|
+
}
|
|
24
|
+
export function normalizeFilePath(filePath) {
|
|
25
|
+
return filePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
26
|
+
}
|
|
27
|
+
function normalizeScreenshot(value) {
|
|
28
|
+
if (!isRecord(value))
|
|
29
|
+
return null;
|
|
30
|
+
const specPath = value.specPath;
|
|
31
|
+
const line = value.line;
|
|
32
|
+
const imagePath = value.path ?? value.imagePath;
|
|
33
|
+
if (typeof specPath !== "string" ||
|
|
34
|
+
typeof line !== "number" ||
|
|
35
|
+
typeof imagePath !== "string") {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
specPath: normalizeFilePath(specPath),
|
|
40
|
+
line,
|
|
41
|
+
path: imagePath,
|
|
42
|
+
title: typeof value.title === "string" ? value.title : undefined,
|
|
43
|
+
testPath: typeof value.testPath === "string" ? value.testPath : undefined,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function isRecord(value) {
|
|
47
|
+
return typeof value === "object" && value !== null;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=screenshots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshots.js","sourceRoot":"","sources":["../src/screenshots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,QAAkB;IAC7D,MAAM,WAAW,GAAqB,EAAE,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,MAAM,sBAAsB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAY,CAAC;QACnE,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YACnC,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;gBACrD,CAAC,CAAC,MAAM,CAAC,WAAW;gBACpB,CAAC,CAAC,EAAE,CAAC;QAET,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,UAAU,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAC9C,IAAI,UAAU;gBAAE,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,IAAY;IAC1D,OAAO,GAAG,iBAAiB,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,OAAO,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAc;IACzC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC;IAChD,IACE,OAAO,QAAQ,KAAK,QAAQ;QAC5B,OAAO,IAAI,KAAK,QAAQ;QACxB,OAAO,SAAS,KAAK,QAAQ,EAC7B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC;QACrC,IAAI;QACJ,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;QAChE,QAAQ,EAAE,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;KAC1E,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { DesignSpec, FeatureSpec, ModelSpec, SpecDocument, StackSpec, TestReference, ValidationIssue } from "./types.js";
|
|
2
|
+
type SpecKind = "model" | "feature" | "stack" | "design";
|
|
3
|
+
export declare function parseSpecDocument(source: string, options?: {
|
|
4
|
+
filePath?: string;
|
|
5
|
+
kind?: SpecKind;
|
|
6
|
+
}): SpecDocument;
|
|
7
|
+
export declare function parseModelSpec(source: string, options?: {
|
|
8
|
+
filePath?: string;
|
|
9
|
+
}): ModelSpec;
|
|
10
|
+
export declare function parseStackSpec(source: string, options?: {
|
|
11
|
+
filePath?: string;
|
|
12
|
+
}): StackSpec;
|
|
13
|
+
export declare function parseDesignSpec(source: string, options?: {
|
|
14
|
+
filePath?: string;
|
|
15
|
+
}): DesignSpec;
|
|
16
|
+
export declare function loadSpecDocuments(patterns: string[]): Promise<SpecDocument[]>;
|
|
17
|
+
export declare function parseSpecTestReferences(source: string, filePath?: string): TestReference[];
|
|
18
|
+
export declare function collectSpecTestReferences(patterns: string[]): Promise<TestReference[]>;
|
|
19
|
+
export declare function validateModelSpec(spec: ModelSpec): ValidationIssue[];
|
|
20
|
+
export declare function validateStackSpec(spec: StackSpec): ValidationIssue[];
|
|
21
|
+
export declare function validateDesignSpec(spec: DesignSpec): ValidationIssue[];
|
|
22
|
+
export declare function validateSpecDocument(spec: SpecDocument): ValidationIssue[];
|
|
23
|
+
export declare function validateSpecGraph(documents: SpecDocument[]): ValidationIssue[];
|
|
24
|
+
export declare function buildSpecCoverageSummary(documents: SpecDocument[], references: TestReference[]): {
|
|
25
|
+
modelCoverage: {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
filePath: string;
|
|
29
|
+
line: number;
|
|
30
|
+
references: TestReference[];
|
|
31
|
+
covered: boolean;
|
|
32
|
+
}[];
|
|
33
|
+
orphanModelReferences: TestReference[];
|
|
34
|
+
ruleCoverage: import("./types.js").CoverageItem[];
|
|
35
|
+
scenarioCoverage: import("./types.js").CoverageItem[];
|
|
36
|
+
orphanRuleReferences: TestReference[];
|
|
37
|
+
orphanScenarioReferences: TestReference[];
|
|
38
|
+
};
|
|
39
|
+
export declare function checkSpecDocuments(options: {
|
|
40
|
+
specs: string[];
|
|
41
|
+
tests?: string[];
|
|
42
|
+
requireModelCoverage?: boolean;
|
|
43
|
+
requireRuleCoverage?: boolean;
|
|
44
|
+
requireScenarioCoverage?: boolean;
|
|
45
|
+
}): Promise<{
|
|
46
|
+
documents: SpecDocument[];
|
|
47
|
+
models: ModelSpec[];
|
|
48
|
+
features: FeatureSpec[];
|
|
49
|
+
stacks: StackSpec[];
|
|
50
|
+
designs: DesignSpec[];
|
|
51
|
+
specs: FeatureSpec[];
|
|
52
|
+
validationIssues: ValidationIssue[];
|
|
53
|
+
coverage: {
|
|
54
|
+
modelCoverage: {
|
|
55
|
+
id: string;
|
|
56
|
+
title: string;
|
|
57
|
+
filePath: string;
|
|
58
|
+
line: number;
|
|
59
|
+
references: TestReference[];
|
|
60
|
+
covered: boolean;
|
|
61
|
+
}[];
|
|
62
|
+
orphanModelReferences: TestReference[];
|
|
63
|
+
ruleCoverage: import("./types.js").CoverageItem[];
|
|
64
|
+
scenarioCoverage: import("./types.js").CoverageItem[];
|
|
65
|
+
orphanRuleReferences: TestReference[];
|
|
66
|
+
orphanScenarioReferences: TestReference[];
|
|
67
|
+
} | undefined;
|
|
68
|
+
coverageIssues: ValidationIssue[];
|
|
69
|
+
ok: boolean;
|
|
70
|
+
}>;
|
|
71
|
+
export {};
|
|
72
|
+
//# sourceMappingURL=specDocuments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specDocuments.d.ts","sourceRoot":"","sources":["../src/specDocuments.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EAEX,SAAS,EACT,YAAY,EAEZ,SAAS,EACT,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAIpB,KAAK,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEzD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,QAAQ,CAAA;CAAO,GACnD,YAAY,CASd;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,SAAS,CAaX;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,SAAS,CAgBX;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,UAAU,CAiBZ;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,2BAQzD;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,QAAQ,SAAa,GACpB,aAAa,EAAE,CAYjB;AAED,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,4BAMjE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,EAAE,CA8BpE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,EAAE,CAgBpE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,EAAE,CAgBtE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,eAAe,EAAE,CAK1E;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,YAAY,EAAE,GACxB,eAAe,EAAE,CAqCnB;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,YAAY,EAAE,EACzB,UAAU,EAAE,aAAa,EAAE;;;;;;;;;;;;;;EA2B5B;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;;;;;;;;;;;;;;;;;;;;;;;;;GAqCA"}
|