test(06-05): failing guidance-card suite GS8-GS12 — evolved card + confirm/trip-end cards + token-only shell.css block scan (RED)
This commit is contained in:
@@ -45,6 +45,9 @@ const {
|
|||||||
buildManeuverList,
|
buildManeuverList,
|
||||||
startGantryBlink,
|
startGantryBlink,
|
||||||
stopGantryBlink,
|
stopGantryBlink,
|
||||||
|
buildGuidanceCard,
|
||||||
|
buildConfirmCard,
|
||||||
|
buildTripEndCard,
|
||||||
MANEUVER_LIST_ID,
|
MANEUVER_LIST_ID,
|
||||||
} = await import("../src/guidance/banner.ts");
|
} = await import("../src/guidance/banner.ts");
|
||||||
|
|
||||||
@@ -471,3 +474,151 @@ test("GS7b svgHost parses a committed glyph into a sized inline-SVG element", ()
|
|||||||
assert.equal(svg.attributes.viewBox, "0 0 32 32");
|
assert.equal(svg.attributes.viewBox, "0 0 32 32");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* ————— GS8 — the guidance card (D-06 — the route card evolved) ————— */
|
||||||
|
|
||||||
|
const CARD_VIEW = {
|
||||||
|
destinationName: "Suria KLCC",
|
||||||
|
minutesRemaining: 18,
|
||||||
|
kmRemaining: 7.842,
|
||||||
|
etaClock: "18:42",
|
||||||
|
progressPct: 42,
|
||||||
|
showNoReroute: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
test("GS8 guidance card — GUIDE register, verbatim headline, live telemetry row, 4px progressbar, ONE btn-secondary END TRIP", () => {
|
||||||
|
withDocument(() => {
|
||||||
|
let ended = 0;
|
||||||
|
const card = buildGuidanceCard("en", CARD_VIEW, { onEndTrip() { ended += 1; } });
|
||||||
|
assert.ok(card.className.includes("card"), "same card class as the route card it evolves");
|
||||||
|
const ref = findByClass(card, "card-ref");
|
||||||
|
assert.equal(ref.textContent, strings.en.guideRegister, "the ROUTE register, evolved to GUIDE");
|
||||||
|
assert.equal(findByClass(card, "text-headline-md").textContent, "Suria KLCC", "the continuity anchor holds (verbatim)");
|
||||||
|
// telemetry row: minutes + MIN + {km} KM + ETA clock — instrument formats
|
||||||
|
const telemetry = findByClass(card, "route-telemetry");
|
||||||
|
assert.ok(telemetry, "the route card's telemetry row class, evolved");
|
||||||
|
assert.deepEqual(
|
||||||
|
telemetry.children.map((c) => c.textContent),
|
||||||
|
["18", "MIN", "7.8 KM", "ETA 18:42"],
|
||||||
|
);
|
||||||
|
// progress bar: role + full aria value set + inline width
|
||||||
|
const progress = findByClass(card, "guide-progress");
|
||||||
|
assert.equal(progress.attributes.role, "progressbar");
|
||||||
|
assert.equal(progress.attributes["aria-valuemin"], "0");
|
||||||
|
assert.equal(progress.attributes["aria-valuemax"], "100");
|
||||||
|
assert.equal(progress.attributes["aria-valuenow"], "42");
|
||||||
|
assert.equal(progress.attributes["aria-label"], strings.en.ariaTripProgress);
|
||||||
|
const fill = findByClass(progress, "guide-progress-fill");
|
||||||
|
assert.equal(fill.style.width, "42%", "width set by inline style from the pct — instant, no transition");
|
||||||
|
// state line ABSENT in the normal state (less, but better)
|
||||||
|
assert.equal(findByClass(card, "text-body-sm"), null, "no state line unless degrading");
|
||||||
|
// actions: exactly ONE button, btn-secondary, END TRIP
|
||||||
|
const actionsRow = findByClass(card, "card-actions");
|
||||||
|
assert.equal(actionsRow.children.length, 1, "the guidance card has exactly one action");
|
||||||
|
const button = actionsRow.children[0];
|
||||||
|
assert.equal(button.className, "btn-secondary", "NO .btn-primary on the guidance card — the drive's forward action is driving");
|
||||||
|
assert.equal(button.textContent, strings.en.ctaEndTrip);
|
||||||
|
button.click();
|
||||||
|
assert.equal(ended, 1, "END TRIP opens the D-09 confirm surface");
|
||||||
|
assert.equal(findAllByClass(card, "btn-primary").length, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("GS8b guidance card no-reroute state line — the longer honest copy ONLY when showNoReroute (standard ink, no red)", () => {
|
||||||
|
withDocument(() => {
|
||||||
|
const card = buildGuidanceCard("en", { ...CARD_VIEW, showNoReroute: true }, { onEndTrip() {} });
|
||||||
|
const stateLine = findByClass(card, "text-body-sm");
|
||||||
|
assert.ok(stateLine, "the degradation adds the state line");
|
||||||
|
assert.equal(stateLine.textContent, strings.en.guideNoRerouteBody);
|
||||||
|
assert.equal(findByClass(card, "card-actions").children.length, 1, "still exactly one action");
|
||||||
|
// ms: same key, ms copy
|
||||||
|
const cardMs = buildGuidanceCard("ms", { ...CARD_VIEW, showNoReroute: true }, { onEndTrip() {} });
|
||||||
|
assert.equal(findByClass(cardMs, "text-body-sm").textContent, strings.ms.guideNoRerouteBody);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ————— GS9 — the abandon-route confirm card (D-09/PX-03) ————— */
|
||||||
|
|
||||||
|
test("GS9 confirm card — GUIDE // CONFIRM, the exact privacy copy, btn-primary YES ABANDON + btn-secondary CONTINUE, bottom-register hook", () => {
|
||||||
|
withDocument(() => {
|
||||||
|
for (const locale of ["en", "ms"]) {
|
||||||
|
let abandoned = 0;
|
||||||
|
let continued = 0;
|
||||||
|
const card = buildConfirmCard(locale, { onAbandon() { abandoned += 1; }, onContinue() { continued += 1; } });
|
||||||
|
assert.equal(findByClass(card, "card-ref").textContent, `${strings[locale].guideRegister} // CONFIRM`);
|
||||||
|
assert.equal(findByClass(card, "text-headline-md").textContent, strings[locale].guideConfirmHeadline);
|
||||||
|
const body = findByClass(card, "text-body-sm");
|
||||||
|
assert.equal(body.textContent, strings[locale].guideConfirmBody, "the privacy clause at the exact moment it executes");
|
||||||
|
const primary = findByClass(card, "btn-primary");
|
||||||
|
const secondary = findByClass(card, "btn-secondary");
|
||||||
|
assert.ok(primary && secondary, "both CTAs exist");
|
||||||
|
assert.equal(primary.textContent, strings[locale].ctaConfirmAbandon, "the working forward path is primary — deliberately NOT red");
|
||||||
|
assert.equal(secondary.textContent, strings[locale].ctaConfirmContinue);
|
||||||
|
primary.click();
|
||||||
|
secondary.click();
|
||||||
|
assert.equal(abandoned, 1);
|
||||||
|
assert.equal(continued, 1);
|
||||||
|
// the ONE .btn-primary in all guidance chrome lives on THIS card
|
||||||
|
assert.equal(findAllByClass(card, "btn-primary").length, 1);
|
||||||
|
}
|
||||||
|
// the bottom-register placement hook (thumb-reach, D-09) rides the card
|
||||||
|
withDocument(() => {
|
||||||
|
const card = buildConfirmCard("en", { onAbandon() {}, onContinue() {} });
|
||||||
|
assert.ok(card.className.includes("guide-confirm"), "the mount reads guide-confirm → bottom register");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ————— GS10 — the trip-ended card (D-14) ————— */
|
||||||
|
|
||||||
|
test("GS10 trip-end card — GUIDE // END, the exact deletion copy, NO actions", () => {
|
||||||
|
withDocument(() => {
|
||||||
|
for (const locale of ["en", "ms"]) {
|
||||||
|
const card = buildTripEndCard(locale);
|
||||||
|
assert.equal(findByClass(card, "card-ref").textContent, `${strings[locale].guideRegister} // END`);
|
||||||
|
assert.equal(findByClass(card, "text-headline-md").textContent, strings[locale].guideEndHeadline);
|
||||||
|
const body = findByClass(card, "text-body-sm");
|
||||||
|
assert.equal(body.textContent, strings[locale].guideEndBody);
|
||||||
|
assert.equal(findByClass(card, "card-actions"), null, "no actions — ONE card for arrival and abandon");
|
||||||
|
const buttons = [];
|
||||||
|
const walk = (el) => {
|
||||||
|
for (const child of el.children) {
|
||||||
|
if (child.tagName === "BUTTON") buttons.push(child);
|
||||||
|
walk(child);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walk(card);
|
||||||
|
assert.equal(buttons.length, 0, "the trip-end card carries no buttons at all");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ————— GS11 — the shell.css guidance block (token-only CSS law) ————— */
|
||||||
|
|
||||||
|
test("GS11 shell.css guidance block — zero transition/animation/box-shadow/border-radius, zero hex literals, token vars only; strip compaction is CSS-only", () => {
|
||||||
|
const cssPath = path.join(HERE, "..", "src", "shell.css");
|
||||||
|
const css = readFileSync(cssPath, "utf8");
|
||||||
|
const marker = css.indexOf("guidance cockpit");
|
||||||
|
assert.ok(marker > 0, "the guidance block exists in shell.css");
|
||||||
|
const block = css.slice(marker);
|
||||||
|
assert.ok(block.length > 500, `the block dresses the whole cockpit (found ${block.length} chars)`);
|
||||||
|
for (const banned of ["transition", "animation", "box-shadow", "border-radius"]) {
|
||||||
|
assert.ok(!block.includes(banned), `guidance block uses ${banned} — states are instant hard swaps, zero depth`);
|
||||||
|
}
|
||||||
|
const hex = /#[0-9a-fA-F]{3,8}/.exec(block);
|
||||||
|
assert.equal(hex, null, `guidance block carries a hex literal (${hex && hex[0]}) — token vars only`);
|
||||||
|
assert.ok(block.includes('[data-guidance="active"]'), "the strip compaction hook exists");
|
||||||
|
assert.ok(block.includes("--color-signal-orange"), "the accent rides the token");
|
||||||
|
// the compaction is PURE CSS — strip.ts logic untouched (Phase 3 table stands)
|
||||||
|
const stripSrc = codeText(path.join(HERE, "..", "src", "ui", "strip.ts"));
|
||||||
|
assert.ok(!stripSrc.includes("data-guidance"), "strip.ts never reads data-guidance — compaction is layout-only CSS");
|
||||||
|
assert.ok(!stripSrc.includes("guide"), "strip.ts carries no guidance furniture");
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ————— GS12 — the ONE .btn-primary placement ————— */
|
||||||
|
|
||||||
|
test("GS12 banner.ts places .btn-primary exactly once — the confirm card's YES, ABANDON (comment-stripped)", () => {
|
||||||
|
const src = codeText(path.join(GUIDANCE_DIR, "banner.ts"));
|
||||||
|
assert.equal((src.match(/btn-primary/g) ?? []).length, 1, "only the confirm card builds a primary CTA");
|
||||||
|
assert.ok((src.match(/btn-secondary/g) ?? []).length >= 2, "END TRIP + CONTINUE are secondary");
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user