establish db
This commit is contained in:
125
apps/web/src/app/api/diseases/diseases-api.test.ts
Normal file
125
apps/web/src/app/api/diseases/diseases-api.test.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { GET } from "./route";
|
||||
import * as diseasesLib from "@/lib/api/diseases";
|
||||
|
||||
// Mock the diseases library
|
||||
vi.mock("@/lib/api/diseases", () => ({
|
||||
listDiseases: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("GET /api/diseases", () => {
|
||||
const createRequest = (searchParams: string) => {
|
||||
const url = new URL(`http://localhost/api/diseases${searchParams}`);
|
||||
const req = new Request(url);
|
||||
// Mock NextRequest.nextUrl
|
||||
(req as any).nextUrl = url;
|
||||
return req;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns all diseases with no filters", async () => {
|
||||
(diseasesLib.listDiseases as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{ id: "early-blight", name: "Early Blight" },
|
||||
{ id: "late-blight", name: "Late Blight" },
|
||||
]);
|
||||
|
||||
const response = await GET(createRequest(""));
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const body = await response.json();
|
||||
expect(body.diseases).toHaveLength(2);
|
||||
expect(body.total).toBe(2);
|
||||
});
|
||||
|
||||
it("filters diseases by plantId", async () => {
|
||||
(diseasesLib.listDiseases as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{ id: "early-blight", name: "Early Blight", plantId: "tomato" },
|
||||
]);
|
||||
|
||||
const response = await GET(createRequest("?plantId=tomato"));
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
it("filters diseases by search term", async () => {
|
||||
(diseasesLib.listDiseases as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{ id: "early-blight", name: "Early Blight" },
|
||||
]);
|
||||
|
||||
const response = await GET(createRequest("?search=blight"));
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
it("filters diseases by causalAgentType", async () => {
|
||||
(diseasesLib.listDiseases as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{ id: "early-blight", name: "Early Blight", causalAgentType: "fungal" },
|
||||
]);
|
||||
|
||||
const response = await GET(createRequest("?causalAgentType=fungal"));
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
it("filters diseases by severity", async () => {
|
||||
(diseasesLib.listDiseases as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{ id: "early-blight", name: "Early Blight", severity: "moderate" },
|
||||
]);
|
||||
|
||||
const response = await GET(createRequest("?severity=moderate"));
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
it("returns 400 for empty search term", async () => {
|
||||
const response = await GET(createRequest("?search="));
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const body = await response.json();
|
||||
expect(body.error).toBe("Bad Request");
|
||||
});
|
||||
|
||||
it("returns 400 for invalid causalAgentType", async () => {
|
||||
const response = await GET(createRequest("?causalAgentType=invalid"));
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const body = await response.json();
|
||||
expect(body.message).toMatch(/Invalid causalAgentType/i);
|
||||
});
|
||||
|
||||
it("returns 400 for invalid severity", async () => {
|
||||
const response = await GET(createRequest("?severity=invalid"));
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const body = await response.json();
|
||||
expect(body.message).toMatch(/Invalid severity/i);
|
||||
});
|
||||
|
||||
it("accepts valid causalAgentTypes", async () => {
|
||||
const validTypes = ["fungal", "bacterial", "viral", "environmental"];
|
||||
|
||||
(diseasesLib.listDiseases as ReturnType<typeof vi.fn>).mockReturnValue([]);
|
||||
|
||||
for (const type of validTypes) {
|
||||
const response = await GET(createRequest(`?causalAgentType=${type}`));
|
||||
expect(response.status).toBe(200);
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts valid severities", async () => {
|
||||
const validSeverities = ["low", "moderate", "high", "critical"];
|
||||
|
||||
(diseasesLib.listDiseases as ReturnType<typeof vi.fn>).mockReturnValue([]);
|
||||
|
||||
for (const severity of validSeverities) {
|
||||
const response = await GET(createRequest(`?severity=${severity}`));
|
||||
expect(response.status).toBe(200);
|
||||
}
|
||||
});
|
||||
|
||||
it("returns cache control header", async () => {
|
||||
(diseasesLib.listDiseases as ReturnType<typeof vi.fn>).mockReturnValue([]);
|
||||
const response = await GET(createRequest(""));
|
||||
const cacheControl = response.headers.get("Cache-Control");
|
||||
expect(cacheControl).toContain("max-age=3600");
|
||||
});
|
||||
});
|
||||
27
apps/web/src/app/api/health/health.test.ts
Normal file
27
apps/web/src/app/api/health/health.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { GET } from "./route";
|
||||
|
||||
describe("GET /api/health", () => {
|
||||
it("returns 200 with status ok", async () => {
|
||||
const response = await GET();
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const body = await response.json();
|
||||
expect(body.status).toBe("ok");
|
||||
expect(body.timestamp).toBeDefined();
|
||||
});
|
||||
|
||||
it("returns valid ISO timestamp", async () => {
|
||||
const response = await GET();
|
||||
const body = await response.json();
|
||||
|
||||
const date = new Date(body.timestamp);
|
||||
expect(date.toString()).not.toBe("Invalid Date");
|
||||
});
|
||||
|
||||
it("returns JSON content type", async () => {
|
||||
const response = await GET();
|
||||
const contentType = response.headers.get("content-type");
|
||||
expect(contentType).toContain("application/json");
|
||||
});
|
||||
});
|
||||
95
apps/web/src/app/api/plants/plants.test.ts
Normal file
95
apps/web/src/app/api/plants/plants.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { GET } from "./route";
|
||||
import * as diseasesLib from "@/lib/api/diseases";
|
||||
|
||||
// Mock the diseases library
|
||||
vi.mock("@/lib/api/diseases", () => ({
|
||||
listPlants: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("GET /api/plants", () => {
|
||||
const createRequest = (searchParams: string) => {
|
||||
const url = new URL(`http://localhost/api/plants${searchParams}`);
|
||||
const req = new Request(url);
|
||||
(req as any).nextUrl = url;
|
||||
return req;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns all plants with no filters", async () => {
|
||||
(diseasesLib.listPlants as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{ id: "tomato", commonName: "Tomato" },
|
||||
{ id: "pepper", commonName: "Pepper" },
|
||||
]);
|
||||
|
||||
const response = await GET(createRequest(""));
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const body = await response.json();
|
||||
expect(body.plants).toHaveLength(2);
|
||||
expect(body.total).toBe(2);
|
||||
});
|
||||
|
||||
it("filters plants by search term", async () => {
|
||||
(diseasesLib.listPlants as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{ id: "tomato", commonName: "Tomato" },
|
||||
]);
|
||||
|
||||
const response = await GET(createRequest("?search=tomato"));
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const body = await response.json();
|
||||
expect(body.plants[0].commonName).toBe("Tomato");
|
||||
});
|
||||
|
||||
it("filters plants by category", async () => {
|
||||
(diseasesLib.listPlants as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{ id: "tomato", commonName: "Tomato", category: "vegetables" },
|
||||
]);
|
||||
|
||||
const response = await GET(createRequest("?category=vegetables"));
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
it("returns 400 for empty search term", async () => {
|
||||
const response = await GET(createRequest("?search="));
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const body = await response.json();
|
||||
expect(body.error).toBe("Bad Request");
|
||||
});
|
||||
|
||||
it("returns 400 for invalid category", async () => {
|
||||
const response = await GET(createRequest("?category=invalid"));
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const body = await response.json();
|
||||
expect(body.message).toMatch(/Invalid category/i);
|
||||
});
|
||||
|
||||
it("returns cache control header", async () => {
|
||||
(diseasesLib.listPlants as ReturnType<typeof vi.fn>).mockReturnValue([]);
|
||||
const response = await GET(createRequest(""));
|
||||
const cacheControl = response.headers.get("Cache-Control");
|
||||
expect(cacheControl).toContain("max-age=3600");
|
||||
});
|
||||
|
||||
it("accepts valid categories", async () => {
|
||||
const validCategories = [
|
||||
"vegetables",
|
||||
"herbs",
|
||||
"houseplants",
|
||||
"flowers",
|
||||
];
|
||||
|
||||
(diseasesLib.listPlants as ReturnType<typeof vi.fn>).mockReturnValue([]);
|
||||
|
||||
for (const cat of validCategories) {
|
||||
const response = await GET(createRequest(`?category=${cat}`));
|
||||
expect(response.status).toBe(200);
|
||||
}
|
||||
});
|
||||
});
|
||||
152
apps/web/src/app/browse/BrowseContent.test.tsx
Normal file
152
apps/web/src/app/browse/BrowseContent.test.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import BrowseContent from "@/app/browse/BrowseContent";
|
||||
|
||||
// Mock Next.js navigation
|
||||
vi.mock("next/navigation", () => ({
|
||||
useSearchParams: vi.fn(() => ({
|
||||
get: vi.fn(() => null),
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock PlantCard
|
||||
vi.mock("@/components/PlantCard", () => ({
|
||||
default: ({ plant }: any) => (
|
||||
<div data-testid={`plant-card-${plant.id}`}>
|
||||
<span>{plant.commonName}</span>
|
||||
<span>{plant.emoji}</span>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock EmptyState
|
||||
vi.mock("@/components/EmptyState", () => ({
|
||||
default: ({ title, description, actionLabel }: any) => (
|
||||
<div data-testid="empty-state">
|
||||
<span data-testid="empty-title">{title}</span>
|
||||
<span data-testid="empty-desc">{description}</span>
|
||||
{actionLabel && <span>{actionLabel}</span>}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("BrowseContent", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders page header with plant count", () => {
|
||||
render(<BrowseContent />);
|
||||
expect(screen.getByText("Browse Plants")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders search input", () => {
|
||||
render(<BrowseContent />);
|
||||
const searchInput = screen.getByRole("searchbox", {
|
||||
name: /Search plants and diseases/i,
|
||||
});
|
||||
expect(searchInput).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("filters plants by search query", () => {
|
||||
render(<BrowseContent />);
|
||||
const searchInput = screen.getByRole("searchbox") as HTMLInputElement;
|
||||
|
||||
fireEvent.change(searchInput, { target: { value: "tomato" } });
|
||||
|
||||
// Should show tomato plant
|
||||
expect(screen.getByText("Tomato")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows results count", () => {
|
||||
render(<BrowseContent />);
|
||||
expect(screen.getByText(/Showing \d+ plants/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders category filter tabs", () => {
|
||||
render(<BrowseContent />);
|
||||
const tablist = screen.getByRole("tablist", { name: /Plant categories/i });
|
||||
expect(tablist).toBeInTheDocument();
|
||||
|
||||
// Should have category tabs
|
||||
const tabs = screen.getAllByRole("tab");
|
||||
expect(tabs.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("filters by category when tab is clicked", () => {
|
||||
render(<BrowseContent />);
|
||||
const tabs = screen.getAllByRole("tab");
|
||||
|
||||
// Click a category tab (not 'all')
|
||||
const vegTab = tabs.find((t) => t.textContent?.toLowerCase().includes("vegetable"));
|
||||
if (vegTab) {
|
||||
fireEvent.click(vegTab);
|
||||
expect(screen.getByText(/in vegetable/i)).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
|
||||
it("clears search when clear button is clicked", () => {
|
||||
render(<BrowseContent />);
|
||||
const searchInput = screen.getByRole("searchbox") as HTMLInputElement;
|
||||
|
||||
fireEvent.change(searchInput, { target: { value: "tomato" } });
|
||||
expect(searchInput.value).toBe("tomato");
|
||||
|
||||
const clearBtn = screen.getByRole("button", { name: /Clear search/i });
|
||||
fireEvent.click(clearBtn);
|
||||
|
||||
expect(searchInput.value).toBe("");
|
||||
});
|
||||
|
||||
it("shows empty state when no plants match search", () => {
|
||||
render(<BrowseContent />);
|
||||
const searchInput = screen.getByRole("searchbox") as HTMLInputElement;
|
||||
|
||||
fireEvent.change(searchInput, { target: { value: "xyznonexistent123" } });
|
||||
|
||||
expect(screen.getByTestId("empty-title")).toHaveTextContent("No plants found");
|
||||
});
|
||||
|
||||
it("shows empty state with search query in description", () => {
|
||||
render(<BrowseContent />);
|
||||
const searchInput = screen.getByRole("searchbox") as HTMLInputElement;
|
||||
|
||||
fireEvent.change(searchInput, { target: { value: "xyznonexistent123" } });
|
||||
|
||||
expect(screen.getByTestId("empty-desc")).toHaveTextContent(/xyznonexistent123/i);
|
||||
});
|
||||
|
||||
it("shows matching text in results count", () => {
|
||||
render(<BrowseContent />);
|
||||
const searchInput = screen.getByRole("searchbox") as HTMLInputElement;
|
||||
|
||||
fireEvent.change(searchInput, { target: { value: "tomato" } });
|
||||
|
||||
expect(screen.getByText(/matching "tomato"/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders all plant cards when no filter applied", () => {
|
||||
render(<BrowseContent />);
|
||||
// Should show all plants
|
||||
const plantCards = screen.getAllByTestId(/plant-card-/);
|
||||
expect(plantCards.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("searches by scientific name", () => {
|
||||
render(<BrowseContent />);
|
||||
const searchInput = screen.getByRole("searchbox") as HTMLInputElement;
|
||||
|
||||
fireEvent.change(searchInput, { target: { value: "solanum" } });
|
||||
|
||||
expect(screen.getByText("Tomato")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("searches by family name", () => {
|
||||
render(<BrowseContent />);
|
||||
const searchInput = screen.getByRole("searchbox") as HTMLInputElement;
|
||||
|
||||
fireEvent.change(searchInput, { target: { value: "solanaceae" } });
|
||||
|
||||
expect(screen.getByText("Tomato")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
30
apps/web/src/app/not-found.test.tsx
Normal file
30
apps/web/src/app/not-found.test.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import NotFound from "@/app/not-found";
|
||||
|
||||
describe("NotFound (404 page)", () => {
|
||||
it("renders 404 heading", () => {
|
||||
render(<NotFound />);
|
||||
expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders plant-themed messaging", () => {
|
||||
render(<NotFound />);
|
||||
// Should have plant-themed content
|
||||
const container = screen.container;
|
||||
expect(container.textContent).toMatch(/plant|leaf|garden|grow/i);
|
||||
});
|
||||
|
||||
it("renders link to go home", () => {
|
||||
render(<NotFound />);
|
||||
const homeLink = screen.getByRole("link", { name: /home/i });
|
||||
expect(homeLink).toHaveAttribute("href", "/");
|
||||
});
|
||||
|
||||
it("renders illustration or emoji", () => {
|
||||
render(<NotFound />);
|
||||
// Should have some visual element
|
||||
const container = screen.container;
|
||||
expect(container.textContent).toMatch(/[🍂🌿🌱🌻🍃]/);
|
||||
});
|
||||
});
|
||||
66
apps/web/src/app/page.test.tsx
Normal file
66
apps/web/src/app/page.test.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import Page from "@/app/page";
|
||||
|
||||
// Mock components that are used in the homepage
|
||||
vi.mock("@/components/Navbar", () => ({
|
||||
default: () => <nav data-testid="navbar">Navbar</nav>,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/Footer", () => ({
|
||||
default: () => <footer data-testid="footer">Footer</footer>,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/ImageUpload", () => ({
|
||||
default: () => <div data-testid="image-upload">Upload</div>,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/PlantCard", () => ({
|
||||
default: ({ plant }: any) => (
|
||||
<div data-testid={`plant-card-${plant.id}`}>{plant.commonName}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("Homepage (page.tsx)", () => {
|
||||
it("renders hero section with title", () => {
|
||||
render(<Page />);
|
||||
expect(screen.getByRole("banner")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders image upload component", () => {
|
||||
render(<Page />);
|
||||
expect(screen.getByTestId("image-upload")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders trust signals section", () => {
|
||||
render(<Page />);
|
||||
// Trust signals should be present
|
||||
const trustSignals = screen.queryAllByText(/95/i);
|
||||
expect(trustSignals.length).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
it("renders how it works section", () => {
|
||||
render(<Page />);
|
||||
expect(screen.getByText(/How It Works/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders featured plants section", () => {
|
||||
render(<Page />);
|
||||
expect(screen.getByText(/Featured Plants/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders navbar", () => {
|
||||
render(<Page />);
|
||||
expect(screen.getByTestId("navbar")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders footer", () => {
|
||||
render(<Page />);
|
||||
expect(screen.getByTestId("footer")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders beta disclaimer", () => {
|
||||
render(<Page />);
|
||||
expect(screen.getByText(/beta/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
99
apps/web/src/app/results/results-page.test.tsx
Normal file
99
apps/web/src/app/results/results-page.test.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import ResultsPage from "@/app/results/[imageId]/page";
|
||||
|
||||
// Mock Next.js navigation
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: vi.fn(() => ({
|
||||
push: vi.fn(),
|
||||
back: vi.fn(),
|
||||
})),
|
||||
useParams: vi.fn(() => ({ imageId: "test-image-123" })),
|
||||
}));
|
||||
|
||||
// Mock API
|
||||
vi.mock("@/lib/api/identify", () => ({
|
||||
identifyPlant: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock ResultsDashboard
|
||||
vi.mock("@/components/ResultsDashboard", () => ({
|
||||
default: ({ loading, error, response }: any) => (
|
||||
<div data-testid="results-dashboard">
|
||||
{loading && <span>Loading...</span>}
|
||||
{error && <span>Error: {error}</span>}
|
||||
{response && <span>Results for {response.predictions?.length} predictions</span>}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock LoadingSkeleton
|
||||
vi.mock("@/components/LoadingSkeleton", () => ({
|
||||
default: () => <div data-testid="loading-skeleton">Loading...</div>,
|
||||
}));
|
||||
|
||||
// Mock EmptyState
|
||||
vi.mock("@/components/EmptyState", () => ({
|
||||
default: ({ title }: any) => <div data-testid="empty-state">{title}</div>,
|
||||
}));
|
||||
|
||||
describe("ResultsPage", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders loading state initially", () => {
|
||||
const { identifyPlant } = require("@/lib/api/identify");
|
||||
// Make identifyPlant never resolve
|
||||
identifyPlant.mockReturnValue(new Promise(() => {}));
|
||||
|
||||
render(<ResultsPage />);
|
||||
expect(screen.getByTestId("results-dashboard")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders error state when identification fails", async () => {
|
||||
const { identifyPlant } = require("@/lib/api/identify");
|
||||
identifyPlant.mockRejectedValue(new Error("Image not found"));
|
||||
|
||||
render(<ResultsPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("results-dashboard")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders results when identification succeeds", async () => {
|
||||
const { identifyPlant } = require("@/lib/api/identify");
|
||||
identifyPlant.mockResolvedValue({
|
||||
predictions: [
|
||||
{
|
||||
diseaseId: "early-blight",
|
||||
disease: {
|
||||
id: "early-blight",
|
||||
name: "Early Blight",
|
||||
causalAgent: "Alternaria solani",
|
||||
causalAgentType: "fungal",
|
||||
severity: "moderate",
|
||||
symptoms: ["Dark spots"],
|
||||
treatment: ["Remove leaves"],
|
||||
lookalikeDiseaseIds: [],
|
||||
plantId: "tomato",
|
||||
},
|
||||
confidence: { raw: 0.85, adjusted: 0.82 },
|
||||
lookalikes: [],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
model: "mock-model",
|
||||
inferenceTimeMs: 150,
|
||||
imageId: "test-image-123",
|
||||
},
|
||||
});
|
||||
|
||||
render(<ResultsPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("results-dashboard")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user