qfai 1.4.37 → 1.4.38
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/assets/init/.qfai/contracts/ui/README.md +8 -0
- package/dist/cli/index.cjs +55 -14
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +55 -14
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +55 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +55 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -31,6 +31,14 @@ The contract must describe both screen structure and minimum mockable behavior.
|
|
|
31
31
|
3. `.qfai/evidence/prototyping.json` (`uiFidelity` snapshot)
|
|
32
32
|
- If label text is intentionally hidden (icon-only, aria-only), add stable `data-qfai` markers and document mapping in the contract comments.
|
|
33
33
|
|
|
34
|
+
### `data-qfai` marker convention
|
|
35
|
+
|
|
36
|
+
- Recommended marker value: `CONTRACT_ID:ELEMENT_ID` (example: `data-qfai="CON-UI-0001:search_input"`).
|
|
37
|
+
- Use `elements[].id` (stable ID) for the marker suffix, not `elements[].label`.
|
|
38
|
+
- Even when label text is not visible in the UI, markers ensure fidelity coverage.
|
|
39
|
+
- autogen generates expected markers from `elements[].id` automatically.
|
|
40
|
+
- Legacy format (`CONTRACT_ID:ELEMENT_LABEL`) is accepted for backward compatibility but new implementations should use the id-based format.
|
|
41
|
+
|
|
34
42
|
## Mockable prototype minimum (L2)
|
|
35
43
|
|
|
36
44
|
Add `prototype` at the top level.
|
package/dist/cli/index.cjs
CHANGED
|
@@ -1589,8 +1589,8 @@ var import_promises7 = require("fs/promises");
|
|
|
1589
1589
|
var import_node_path8 = __toESM(require("path"), 1);
|
|
1590
1590
|
var import_node_url2 = require("url");
|
|
1591
1591
|
async function resolveToolVersion() {
|
|
1592
|
-
if ("1.4.
|
|
1593
|
-
return "1.4.
|
|
1592
|
+
if ("1.4.38".length > 0) {
|
|
1593
|
+
return "1.4.38";
|
|
1594
1594
|
}
|
|
1595
1595
|
try {
|
|
1596
1596
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -3648,15 +3648,29 @@ function buildUiFidelityScreens(expectedScreens, crawledRoutes, mockPathResults)
|
|
|
3648
3648
|
const hasPass = mockPath?.entries.some(
|
|
3649
3649
|
(entry) => entry.status.toLowerCase() === "pass"
|
|
3650
3650
|
) ?? false;
|
|
3651
|
-
const expectedMarkers = screen.
|
|
3652
|
-
(
|
|
3653
|
-
);
|
|
3654
|
-
const
|
|
3655
|
-
const
|
|
3656
|
-
|
|
3657
|
-
|
|
3651
|
+
const expectedMarkers = screen.elementIds.map(
|
|
3652
|
+
(id) => `${screen.uiContractId}:${id}`
|
|
3653
|
+
);
|
|
3654
|
+
const crawledMarkersSet = new Set(crawl?.markers ?? []);
|
|
3655
|
+
const labelToIdMarker = /* @__PURE__ */ new Map();
|
|
3656
|
+
for (const pair of screen.elementPairs) {
|
|
3657
|
+
const labelMarker = `${screen.uiContractId}:${pair.label}`;
|
|
3658
|
+
const idMarker = `${screen.uiContractId}:${pair.id}`;
|
|
3659
|
+
if (labelMarker !== idMarker) {
|
|
3660
|
+
labelToIdMarker.set(labelMarker, idMarker);
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
const foundMarkers = expectedMarkers.filter((marker) => {
|
|
3664
|
+
if (crawledMarkersSet.has(marker)) return true;
|
|
3665
|
+
for (const [labelMarker, idMarker] of labelToIdMarker) {
|
|
3666
|
+
if (idMarker === marker && crawledMarkersSet.has(labelMarker)) {
|
|
3667
|
+
return true;
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
return false;
|
|
3671
|
+
});
|
|
3658
3672
|
const missingMarkers = expectedMarkers.filter(
|
|
3659
|
-
(marker) => !
|
|
3673
|
+
(marker) => !foundMarkers.includes(marker)
|
|
3660
3674
|
);
|
|
3661
3675
|
return {
|
|
3662
3676
|
route: screen.route,
|
|
@@ -3664,7 +3678,8 @@ function buildUiFidelityScreens(expectedScreens, crawledRoutes, mockPathResults)
|
|
|
3664
3678
|
expected: {
|
|
3665
3679
|
elements: screen.elementCount,
|
|
3666
3680
|
actions: screen.actionIds.length,
|
|
3667
|
-
labels: screen.labels
|
|
3681
|
+
labels: screen.labels,
|
|
3682
|
+
ids: screen.elementIds
|
|
3668
3683
|
},
|
|
3669
3684
|
found: {
|
|
3670
3685
|
labels: coverage.found,
|
|
@@ -3784,6 +3799,8 @@ function collectContractScreens(contractId, doc, mockPathIds) {
|
|
|
3784
3799
|
route,
|
|
3785
3800
|
uiContractId: contractId,
|
|
3786
3801
|
expectedLabels: elements.labels,
|
|
3802
|
+
elementIds: elements.ids,
|
|
3803
|
+
elementPairs: elements.pairs,
|
|
3787
3804
|
elementCount: elements.count,
|
|
3788
3805
|
actionIds,
|
|
3789
3806
|
mockPathIds,
|
|
@@ -3795,8 +3812,15 @@ function collectContractScreens(contractId, doc, mockPathIds) {
|
|
|
3795
3812
|
function collectElements(value) {
|
|
3796
3813
|
const entries = Array.isArray(value) ? value : [];
|
|
3797
3814
|
const validEntries = entries.map((entry) => readRecord(entry)).filter((entry) => Boolean(entry));
|
|
3815
|
+
const ids = validEntries.map((entry) => readText(entry.id)).filter((id) => id.length > 0);
|
|
3798
3816
|
const labels = validEntries.map((entry) => readText(entry.label)).filter((label) => label.length > 0);
|
|
3799
|
-
|
|
3817
|
+
const pairs = validEntries.map((entry) => ({ id: readText(entry.id), label: readText(entry.label) })).filter((pair) => pair.id.length > 0 && pair.label.length > 0);
|
|
3818
|
+
return {
|
|
3819
|
+
ids: Array.from(new Set(ids)).sort((a, b) => a.localeCompare(b)),
|
|
3820
|
+
labels: dedupeLabels(labels),
|
|
3821
|
+
pairs,
|
|
3822
|
+
count: validEntries.length
|
|
3823
|
+
};
|
|
3800
3824
|
}
|
|
3801
3825
|
function collectActionIds(value) {
|
|
3802
3826
|
const entries = Array.isArray(value) ? value : [];
|
|
@@ -3842,6 +3866,10 @@ function dedupeExpectedScreens(screens) {
|
|
|
3842
3866
|
route: screen.route,
|
|
3843
3867
|
uiContractId: screen.uiContractId,
|
|
3844
3868
|
labels: dedupeLabels(screen.expectedLabels),
|
|
3869
|
+
elementIds: Array.from(new Set(screen.elementIds)).sort(
|
|
3870
|
+
(a, b) => a.localeCompare(b)
|
|
3871
|
+
),
|
|
3872
|
+
elementPairs: [...screen.elementPairs],
|
|
3845
3873
|
elementCount: screen.elementCount,
|
|
3846
3874
|
actionIds: Array.from(new Set(screen.actionIds)).sort(
|
|
3847
3875
|
(a, b) => a.localeCompare(b)
|
|
@@ -3859,6 +3887,16 @@ function dedupeExpectedScreens(screens) {
|
|
|
3859
3887
|
...current.labels,
|
|
3860
3888
|
...screen.expectedLabels
|
|
3861
3889
|
]);
|
|
3890
|
+
current.elementIds = Array.from(
|
|
3891
|
+
/* @__PURE__ */ new Set([...current.elementIds, ...screen.elementIds])
|
|
3892
|
+
).sort((a, b) => a.localeCompare(b));
|
|
3893
|
+
const existingIds = new Set(current.elementPairs.map((p) => p.id));
|
|
3894
|
+
for (const pair of screen.elementPairs) {
|
|
3895
|
+
if (!existingIds.has(pair.id)) {
|
|
3896
|
+
current.elementPairs.push(pair);
|
|
3897
|
+
existingIds.add(pair.id);
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3862
3900
|
current.elementCount += screen.elementCount;
|
|
3863
3901
|
current.actionIds = Array.from(
|
|
3864
3902
|
/* @__PURE__ */ new Set([...current.actionIds, ...screen.actionIds])
|
|
@@ -11723,7 +11761,8 @@ async function validateUiFidelity(root, config, evidenceJsonPath, evidence) {
|
|
|
11723
11761
|
refs,
|
|
11724
11762
|
"change",
|
|
11725
11763
|
[
|
|
11726
|
-
'\u753B\u9762\u306E\u5404\u8981\u7D20\u306B data-qfai="CONTRACT_ID:
|
|
11764
|
+
'\u753B\u9762\u306E\u5404\u8981\u7D20\u306B data-qfai="CONTRACT_ID:ELEMENT_ID" \u30DE\u30FC\u30AB\u30FC\u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002',
|
|
11765
|
+
'\u30DE\u30FC\u30AB\u30FC\u306E\u5024\u306F contracts/ui \u306E elements[].id \u3092\u4F7F\u3044\u307E\u3059\uFF08\u4F8B: data-qfai="CON-UI-0001:search_input"\uFF09\u3002',
|
|
11727
11766
|
"autogen \u3092\u518D\u5B9F\u884C\u3057\u3001missing.markers \u304C\u7A7A\u306B\u306A\u308B\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
11728
11767
|
].join("\n")
|
|
11729
11768
|
)
|
|
@@ -12033,12 +12072,14 @@ function normalizeUiFidelityExpected(value) {
|
|
|
12033
12072
|
};
|
|
12034
12073
|
}
|
|
12035
12074
|
const labels = toOptionalStringArray(value.labels);
|
|
12075
|
+
const ids = toOptionalStringArray(value.ids);
|
|
12036
12076
|
return {
|
|
12037
12077
|
ok: true,
|
|
12038
12078
|
value: {
|
|
12039
12079
|
elements: value.elements,
|
|
12040
12080
|
actions: value.actions,
|
|
12041
|
-
...labels ? { labels } : {}
|
|
12081
|
+
...labels ? { labels } : {},
|
|
12082
|
+
...ids ? { ids } : {}
|
|
12042
12083
|
}
|
|
12043
12084
|
};
|
|
12044
12085
|
}
|