feat(FN-088): merge fusion/fn-088

This commit is contained in:
Fusion
2026-05-11 00:33:03 +00:00
parent 01463d04cb
commit 55c237b049
2 changed files with 124 additions and 6 deletions

View File

@@ -0,0 +1,115 @@
/**
* Regression tests for landing page VIN decode analytics events.
*
* These tests verify that the correct PostHog events are captured on
* successful VIN decode and on error conditions.
*/
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom";
import { vi } from "vitest";
// Mock the PostHog capture function
vi.mock("@/lib/posthog", () => ({
capture: vi.fn(),
}));
// Mock the API client used for VIN decode
vi.mock("@/lib/api-client", () => ({
api: {
post: vi.fn(),
},
ApiError: class ApiError extends Error {
constructor(message: string, public code?: string, public status?: number) {
super(message);
this.name = "ApiError";
}
},
}));
// Mock the auth store to simulate a loggedin user
vi.mock("@/stores/auth.store", () => {
const actual = vi.importActual("@/stores/auth.store");
return {
...actual,
useAuthStore: {
getState: vi.fn(),
subscribe: vi.fn(() => vi.fn()),
},
};
});
// Mock navigation to avoid real router sideeffects
vi.mock("@tanstack/react-router", async () => {
const actual = await vi.importActual<any>("@tanstack/react-router");
return {
...actual,
useNavigate: () => vi.fn(),
};
});
import { HomePage } from "@/routes/index"; // Exported component
import { capture } from "@/lib/posthog";
import { api, ApiError } from "@/lib/api-client";
import { useAuthStore } from "@/stores/auth.store";
beforeEach(() => {
vi.clearAllMocks();
(useAuthStore.getState as any).mockReturnValue({
user: { id: "user-1" },
isLoading: false,
});
});
test("captures vin_decode_success on successful decode", async () => {
(api.post as any).mockResolvedValue({ id: "test-vehicle-id" });
render(
<BrowserRouter>
<HomePage />
</BrowserRouter>,
);
const input = screen.getByPlaceholderText(/Örnek:/i);
fireEvent.change(input, { target: { value: "WVWZZZ1JZ3W597935" } });
const button = screen.getByRole("button", { name: /Ara/i });
fireEvent.click(button);
await waitFor(() => {
expect(capture).toHaveBeenCalledWith(
"vin_decode_success",
expect.objectContaining({
vin: "WVWZZZ1JZ3W597935",
source: "landing",
vehicle_id: "test-vehicle-id",
}),
);
});
});
test("captures vin_decode_error on API error", async () => {
const error = new ApiError("Invalid VIN", "INVALID", 400);
(api.post as any).mockRejectedValue(error);
render(
<BrowserRouter>
<HomePage />
</BrowserRouter>,
);
const input = screen.getByPlaceholderText(/Örnek:/i);
fireEvent.change(input, { target: { value: "INVALIDVIN1234567" } });
const button = screen.getByRole("button", { name: /Ara/i });
fireEvent.click(button);
await waitFor(async () => {
expect(capture).toHaveBeenCalledWith(
"vin_decode_error",
expect.objectContaining({
vin: "INVALIDVIN1234567",
source: "landing",
error: "Invalid VIN",
}),
);
});
});

View File

@@ -378,7 +378,7 @@ export const Route = createFileRoute("/")({
component: HomePage,
});
function HomePage() {
export function HomePage() {
usePageMeta({
title: "Şase Numarası Sorgulama & OEM Parça Kataloğu | Sase.tr",
description:
@@ -526,20 +526,23 @@ function HomePage() {
setDecodeLoading(true);
try {
const vehicle = await api.post<{ id: string }>("/vehicles/decode", { vin: trimmed });
capture("vin_decoded", { vin: trimmed, source: "landing" });
capture("vin_decode_success", { vin: trimmed, source: "landing", vehicle_id: vehicle.id });
navigate({ to: "/dashboard/vehicles/$id", params: { id: vehicle.id } });
} catch (err) {
const message =
err instanceof ApiError ? err.message : "Bir hata oluştu. Lütfen tekrar deneyin.";
capture("vin_decode_error", { vin: trimmed, source: "landing", error: message });
if (err instanceof ApiError) {
if (err.status === 403) {
toast.error(err.message);
toast.error(message);
navigate({ to: "/dashboard/subscription" });
} else if (err.status === 400) {
toast.error(err.message);
toast.error(message);
} else {
toast.error("Bir hata oluştu. Lütfen tekrar deneyin.");
toast.error(message);
}
} else {
toast.error("Bir hata oluştu. Lütfen tekrar deneyin.");
toast.error(message);
}
} finally {
setDecodeLoading(false);