feat(FN-415): add locator-stability + hit-target verification tests
Commits merged: - feat(FN-415): add locator-stability + hit-target verification tests Files changed: apps/api/src/telemetry/__tests__/telemetry.spec.ts | 8 +++ .../src/routes/__tests__/dashboard-search.test.tsx | 67 ++++++++++++++++++++++ 2 files changed, 75 insertions(+) Fusion-Task-Id: FN-415 Fusion-Task-Lineage: 01e6f3d2-cd2e-4023-b538-ea54926b4f87
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
// Mock the heavy @opentelemetry/sdk-node meta-package to avoid slow/cascading imports
|
||||
// that can cause the test to time out under system load.
|
||||
vi.mock("@opentelemetry/sdk-node", () => ({
|
||||
NodeSDK: class MockNodeSDK {
|
||||
start() {}
|
||||
},
|
||||
}));
|
||||
|
||||
describe("Telemetry module", () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
|
||||
@@ -170,3 +170,70 @@ test("retry suppressed for subscription block (abone olun)", async () => {
|
||||
|
||||
expect(screen.queryByRole("button", { name: /Tekrar Dene/i })).toBeNull();
|
||||
});
|
||||
|
||||
// ─── FN-415: locator-stability + hit-target audit (AC 1, 3, 4) ────────────
|
||||
test("locator stability: canonical Faro/ARIA hooks resolve and no data-testid is used", async () => {
|
||||
const { ApiError: MockApiError } = await import("@/lib/api-client");
|
||||
(api.post as any).mockRejectedValueOnce(
|
||||
new (MockApiError as any)(503, "Servis geçici olarak kullanılamıyor"),
|
||||
);
|
||||
|
||||
const { container } = renderSearch();
|
||||
|
||||
// (d) VIN input — getByRole('textbox') + canonical placeholder.
|
||||
const vinInput = screen.getByPlaceholderText("Şase numarasını girin (17 karakter)");
|
||||
expect(vinInput).toBe(screen.getByRole("textbox"));
|
||||
|
||||
// (e) Submit — getByRole('button', { name: 'Şase Çöz' }).
|
||||
const submit = screen.getByRole("button", { name: "Şase Çöz" });
|
||||
expect(submit).toBeInTheDocument();
|
||||
|
||||
typeVin(TEST_VIN);
|
||||
await submitForm();
|
||||
|
||||
// (b) Error banner — role="alert" + aria-live="assertive".
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("alert")).toBeInTheDocument();
|
||||
});
|
||||
const alert = screen.getByRole("alert");
|
||||
expect(alert).toHaveAttribute("aria-live", "assertive");
|
||||
expect(alert).toHaveTextContent("Şase çözümlenemedi");
|
||||
|
||||
// (a) Retry — [data-faro-user-action-name="vin-decode-retry"].
|
||||
const retryByFaro = container.querySelector(
|
||||
'[data-faro-user-action-name="vin-decode-retry"]',
|
||||
) as HTMLButtonElement | null;
|
||||
expect(retryByFaro).not.toBeNull();
|
||||
|
||||
// (c) Retry accessible-name fallback.
|
||||
const retryByName = screen.getByRole("button", { name: "Tekrar Dene" });
|
||||
expect(retryByName).toBe(retryByFaro);
|
||||
|
||||
// AC 4 (drift hardening) — no data-testid attributes anywhere in the render.
|
||||
expect(container.querySelector("[data-testid]")).toBeNull();
|
||||
});
|
||||
|
||||
test("hit-target audit: retry button meets the 44px Apple HIG / Material floor", async () => {
|
||||
const { ApiError: MockApiError } = await import("@/lib/api-client");
|
||||
(api.post as any).mockRejectedValueOnce(
|
||||
new (MockApiError as any)(503, "Servis geçici olarak kullanılamıyor"),
|
||||
);
|
||||
|
||||
renderSearch();
|
||||
typeVin(TEST_VIN);
|
||||
await submitForm();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("alert")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const retry = screen.getByRole("button", { name: "Tekrar Dene" });
|
||||
|
||||
// The retry button uses Tailwind `h-11` (= 2.75rem = 44px at the default
|
||||
// 16px root) and `w-full`, so it inherits the parent error-banner width on
|
||||
// any viewport ≥ ~280px (well below the iPhone SE 375px baseline). Assert
|
||||
// the class contract — jsdom does not run layout, so a getBoundingClientRect
|
||||
// measurement would always be 0×0 and is intentionally avoided here.
|
||||
expect(retry.className).toMatch(/(^|\s)h-11(\s|$)/);
|
||||
expect(retry.className).toMatch(/(^|\s)w-full(\s|$)/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user