Skip to content

Commit 820fb22

Browse files
author
iexitdev
committed
test(release): sync qa evidence manifests
1 parent 433fa84 commit 820fb22

3 files changed

Lines changed: 188 additions & 0 deletions

File tree

scripts/check-release-gates.mjs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,30 @@ const getIds = (items) =>
187187

188188
const isExternalEvidenceLink = (value) => /^https?:\/\//.test(value);
189189

190+
const getMatrixStatus = (rows = []) => {
191+
if (rows.length === 0) {
192+
return "pending";
193+
}
194+
195+
if (rows.every((row) => row.status === "pass")) {
196+
return "complete";
197+
}
198+
199+
if (rows.some((row) => row.status === "fail")) {
200+
return "fail";
201+
}
202+
203+
if (rows.some((row) => row.status === "blocked")) {
204+
return "blocked";
205+
}
206+
207+
if (rows.some((row) => row.status === "pass")) {
208+
return "partial";
209+
}
210+
211+
return "pending";
212+
};
213+
190214
const validateEvidenceMatrix = async (matrix) => {
191215
const errors = [];
192216
const pageIds = getIds(matrix.pages);
@@ -200,6 +224,14 @@ const validateEvidenceMatrix = async (matrix) => {
200224
errors.push("matrix must define at least one row");
201225
}
202226

227+
const derivedStatus = getMatrixStatus(matrix.rows ?? []);
228+
229+
if (matrix.status && matrix.status !== derivedStatus) {
230+
errors.push(
231+
`matrix status ${matrix.status} does not match row-derived status ${derivedStatus}`
232+
);
233+
}
234+
203235
if (Array.isArray(matrix.pages)) {
204236
for (const page of matrix.pages) {
205237
const missingGroups = (page.requiredCheckGroups ?? []).filter(
@@ -406,6 +438,14 @@ const validateReleaseEvidenceManifest = async ({
406438
if (!matrix && completedEntries.length === 0) {
407439
errors.push("complete manifest must include completedEntries");
408440
}
441+
} else if (
442+
matrix &&
443+
Array.isArray(matrix.rows) &&
444+
matrix.rows.every((row) => row.status === "pass")
445+
) {
446+
errors.push(
447+
"matrix-backed manifest must be complete when all matrix rows pass"
448+
);
409449
} else if (missingEvidence.length === 0) {
410450
errors.push(`${status} manifest must list missingEvidence`);
411451
}

scripts/record-native-qa-evidence.mjs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,30 @@ const validStatuses = new Set([
1717
const matrixConfigs = {
1818
accessibility: {
1919
label: "Accessibility QA",
20+
completeSummary:
21+
"Native accessibility QA matrix is complete for required VoiceOver and TalkBack showcase pages.",
22+
manifestPath: "docs/release/evidence/native-accessibility-qa.json",
2023
path: "docs/release/evidence/native-accessibility-matrix.json"
2124
},
2225
performance: {
2326
label: "Native Performance",
27+
completeSummary:
28+
"Native performance matrix is complete for required iOS and Android release scenarios.",
29+
manifestPath: "docs/release/evidence/native-performance-benchmark.json",
2430
path: "docs/release/evidence/native-performance-matrix.json"
2531
},
2632
runtime: {
2733
label: "Runtime QA",
34+
completeSummary:
35+
"Native runtime QA matrix is complete for required iOS and Android showcase pages.",
36+
manifestPath: "docs/release/evidence/native-runtime-qa.json",
2837
path: "docs/release/evidence/native-runtime-matrix.json"
2938
},
3039
skia: {
3140
label: "Skia Renderer",
41+
completeSummary:
42+
"Skia renderer native install, renderer parity, and performance evidence matrix is complete.",
43+
manifestPath: "docs/release/evidence/skia-renderer-evidence.json",
3244
path: "docs/release/evidence/skia-renderer-matrix.json"
3345
}
3446
};
@@ -134,6 +146,47 @@ const getMatrixStatus = (rows) => {
134146
return "pending";
135147
};
136148

149+
const getManifestStatus = (matrixStatus) => {
150+
if (matrixStatus === "complete") {
151+
return "complete";
152+
}
153+
154+
if (matrixStatus === "blocked" || matrixStatus === "fail") {
155+
return "blocked";
156+
}
157+
158+
return "partial";
159+
};
160+
161+
const getManifestMissingEvidence = (rows) =>
162+
rows
163+
.filter((row) => row.status !== "pass")
164+
.map(
165+
(row) =>
166+
`${row.id} is ${row.status}; evidence is required before this matrix can be complete.`
167+
);
168+
169+
const syncEvidenceManifest = async ({
170+
config,
171+
matrix,
172+
matrixStatus,
173+
repoRoot,
174+
updated
175+
}) => {
176+
const manifest = await readJson(repoRoot, config.manifestPath);
177+
const manifestStatus = getManifestStatus(matrixStatus);
178+
const complete = manifestStatus === "complete";
179+
const nextManifest = {
180+
...manifest,
181+
lastUpdated: updated,
182+
missingEvidence: complete ? [] : getManifestMissingEvidence(matrix.rows),
183+
status: manifestStatus,
184+
summary: complete ? config.completeSummary : manifest.summary
185+
};
186+
187+
return nextManifest;
188+
};
189+
137190
const getRowTarget = (matrix, row) => {
138191
const page = matrix.pages?.find((item) => item.id === row.pageId);
139192
const platform = matrix.platforms?.find((item) => item.id === row.platform);
@@ -279,9 +332,17 @@ export const recordNativeQaEvidence = async ({
279332
rows: nextRows,
280333
status: getMatrixStatus(nextRows)
281334
};
335+
const nextManifest = await syncEvidenceManifest({
336+
config,
337+
matrix: nextMatrix,
338+
matrixStatus: nextMatrix.status,
339+
repoRoot,
340+
updated
341+
});
282342

283343
if (!dryRun) {
284344
await writeJson(repoRoot, config.path, nextMatrix);
345+
await writeJson(repoRoot, config.manifestPath, nextManifest);
285346
await writeFile(
286347
path.join(repoRoot, checklistPath),
287348
await generateNativeQaChecklist({ repoRoot }),
@@ -292,6 +353,8 @@ export const recordNativeQaEvidence = async ({
292353
return {
293354
checklistPath,
294355
dryRun,
356+
manifestPath: config.manifestPath,
357+
manifestStatus: nextManifest.status,
295358
matrixPath: config.path,
296359
row: nextRow,
297360
status: nextMatrix.status

scripts/record-native-qa-evidence.test.mjs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ import {
1717
const repoRoot = process.cwd();
1818
const matrixFiles = [
1919
"native-accessibility-matrix.json",
20+
"native-accessibility-qa.json",
21+
"native-performance-benchmark.json",
2022
"native-performance-matrix.json",
2123
"native-runtime-matrix.json",
24+
"native-runtime-qa.json",
25+
"skia-renderer-evidence.json",
2226
"skia-renderer-matrix.json"
2327
];
2428

@@ -141,6 +145,8 @@ describe("native QA evidence recorder", () => {
141145

142146
expect(result).toMatchObject({
143147
dryRun: false,
148+
manifestPath: "docs/release/evidence/native-runtime-qa.json",
149+
manifestStatus: "partial",
144150
matrixPath: "docs/release/evidence/native-runtime-matrix.json",
145151
status: "partial"
146152
});
@@ -154,6 +160,19 @@ describe("native QA evidence recorder", () => {
154160
expect(checklist).toContain(
155161
"`docs/release/artifacts/ios-line-charts-runtime.md`"
156162
);
163+
const manifest = JSON.parse(
164+
await readFile(
165+
join(tempRepo, "docs/release/evidence/native-runtime-qa.json"),
166+
"utf8"
167+
)
168+
);
169+
expect(manifest).toMatchObject({
170+
lastUpdated: "2026-05-06",
171+
status: "partial"
172+
});
173+
expect(manifest.missingEvidence).toContain(
174+
"ios-bar-charts is pending; evidence is required before this matrix can be complete."
175+
);
157176
});
158177

159178
it("supports dry-run without writing matrix changes", async () => {
@@ -213,6 +232,8 @@ describe("native QA evidence recorder", () => {
213232

214233
expect(result).toMatchObject({
215234
checklistPath: "docs/release/native-qa-checklists.md",
235+
manifestPath: "docs/release/evidence/skia-renderer-evidence.json",
236+
manifestStatus: "partial",
216237
matrixPath: "docs/release/evidence/skia-renderer-matrix.json",
217238
status: "partial"
218239
});
@@ -223,4 +244,68 @@ describe("native QA evidence recorder", () => {
223244
expect(checklist).toContain("| Skia Renderer | 8 | 1 | 7 | 0 | 0 | 0 |");
224245
expect(checklist).toContain("`ios-skia-native-install`");
225246
});
247+
248+
it("marks aggregate evidence complete when the last matrix row passes", async () => {
249+
const tempRepo = await createTempRepo();
250+
await createArtifact(
251+
tempRepo,
252+
"docs/release/artifacts/ios-line-charts-runtime.md"
253+
);
254+
255+
const matrixPath = join(
256+
tempRepo,
257+
"docs/release/evidence/native-runtime-matrix.json"
258+
);
259+
const matrix = JSON.parse(await readFile(matrixPath, "utf8"));
260+
const preparedRows = matrix.rows.map((row) =>
261+
row.id === "ios-line-charts"
262+
? row
263+
: {
264+
...row,
265+
evidence: [`https://example.test/${row.id}.mp4`],
266+
status: "pass"
267+
}
268+
);
269+
270+
await writeFile(
271+
matrixPath,
272+
`${JSON.stringify(
273+
{
274+
...matrix,
275+
rows: preparedRows,
276+
status: "partial"
277+
},
278+
null,
279+
2
280+
)}\n`,
281+
"utf8"
282+
);
283+
284+
const result = await recordNativeQaEvidence({
285+
evidence: ["docs/release/artifacts/ios-line-charts-runtime.md"],
286+
matrixName: "runtime",
287+
repoRoot: tempRepo,
288+
rowId: "ios-line-charts",
289+
status: "pass",
290+
updated: "2026-05-06"
291+
});
292+
const manifest = JSON.parse(
293+
await readFile(
294+
join(tempRepo, "docs/release/evidence/native-runtime-qa.json"),
295+
"utf8"
296+
)
297+
);
298+
299+
expect(result).toMatchObject({
300+
manifestStatus: "complete",
301+
status: "complete"
302+
});
303+
expect(manifest).toMatchObject({
304+
lastUpdated: "2026-05-06",
305+
missingEvidence: [],
306+
status: "complete",
307+
summary:
308+
"Native runtime QA matrix is complete for required iOS and Android showcase pages."
309+
});
310+
});
226311
});

0 commit comments

Comments
 (0)