mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-22 05:28:02 -05:00
Compare commits
12 Commits
dev
...
abhi/integ
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cdbd48423 | ||
|
|
9ab5d1704b | ||
|
|
cd76330183 | ||
|
|
1b4ced3a13 | ||
|
|
25dbab8cf7 | ||
|
|
d9ca19672d | ||
|
|
6e2c7ffbeb | ||
|
|
da978a7410 | ||
|
|
6960ba8f3e | ||
|
|
b22a4098bb | ||
|
|
1a5010f9e5 | ||
|
|
12f47eda10 |
@@ -29,4 +29,4 @@ NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY=
|
|||||||
NEXT_PUBLIC_TURNSTILE=disabled
|
NEXT_PUBLIC_TURNSTILE=disabled
|
||||||
|
|
||||||
# PR previews
|
# PR previews
|
||||||
NEXT_PUBLIC_PREVIEW_STEALING_DEV=
|
NEXT_PUBLIC_PREVIEW_STEALING_DEV=
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ export default defineConfig({
|
|||||||
client: "react-query",
|
client: "react-query",
|
||||||
httpClient: "fetch",
|
httpClient: "fetch",
|
||||||
indexFiles: false,
|
indexFiles: false,
|
||||||
|
mock: {
|
||||||
|
type: "msw",
|
||||||
|
baseUrl: "http://localhost:3000/api/proxy",
|
||||||
|
generateEachHttpStatus: true,
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
override: {
|
override: {
|
||||||
mutator: {
|
mutator: {
|
||||||
path: "./mutators/custom-mutator.ts",
|
path: "./mutators/custom-mutator.ts",
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
"types": "tsc --noEmit",
|
"types": "tsc --noEmit",
|
||||||
"test": "NEXT_PUBLIC_PW_TEST=true next build --turbo && playwright test",
|
"test": "NEXT_PUBLIC_PW_TEST=true next build --turbo && playwright test",
|
||||||
"test-ui": "NEXT_PUBLIC_PW_TEST=true next build --turbo && playwright test --ui",
|
"test-ui": "NEXT_PUBLIC_PW_TEST=true next build --turbo && playwright test --ui",
|
||||||
|
"test:unit": "vitest run",
|
||||||
|
"test:unit:watch": "vitest",
|
||||||
"test:no-build": "playwright test",
|
"test:no-build": "playwright test",
|
||||||
"gentests": "playwright codegen http://localhost:3000",
|
"gentests": "playwright codegen http://localhost:3000",
|
||||||
"storybook": "storybook dev -p 6006",
|
"storybook": "storybook dev -p 6006",
|
||||||
@@ -118,6 +120,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@chromatic-com/storybook": "4.1.2",
|
"@chromatic-com/storybook": "4.1.2",
|
||||||
|
"happy-dom": "20.3.4",
|
||||||
"@opentelemetry/instrumentation": "0.209.0",
|
"@opentelemetry/instrumentation": "0.209.0",
|
||||||
"@playwright/test": "1.56.1",
|
"@playwright/test": "1.56.1",
|
||||||
"@storybook/addon-a11y": "9.1.5",
|
"@storybook/addon-a11y": "9.1.5",
|
||||||
@@ -127,6 +130,8 @@
|
|||||||
"@storybook/nextjs": "9.1.5",
|
"@storybook/nextjs": "9.1.5",
|
||||||
"@tanstack/eslint-plugin-query": "5.91.2",
|
"@tanstack/eslint-plugin-query": "5.91.2",
|
||||||
"@tanstack/react-query-devtools": "5.90.2",
|
"@tanstack/react-query-devtools": "5.90.2",
|
||||||
|
"@testing-library/dom": "10.4.1",
|
||||||
|
"@testing-library/react": "16.3.2",
|
||||||
"@types/canvas-confetti": "1.9.0",
|
"@types/canvas-confetti": "1.9.0",
|
||||||
"@types/lodash": "4.17.20",
|
"@types/lodash": "4.17.20",
|
||||||
"@types/negotiator": "0.6.4",
|
"@types/negotiator": "0.6.4",
|
||||||
@@ -135,6 +140,7 @@
|
|||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "18.3.5",
|
||||||
"@types/react-modal": "3.16.3",
|
"@types/react-modal": "3.16.3",
|
||||||
"@types/react-window": "1.8.8",
|
"@types/react-window": "1.8.8",
|
||||||
|
"@vitejs/plugin-react": "5.1.2",
|
||||||
"axe-playwright": "2.2.2",
|
"axe-playwright": "2.2.2",
|
||||||
"chromatic": "13.3.3",
|
"chromatic": "13.3.3",
|
||||||
"concurrently": "9.2.1",
|
"concurrently": "9.2.1",
|
||||||
@@ -153,7 +159,9 @@
|
|||||||
"require-in-the-middle": "8.0.1",
|
"require-in-the-middle": "8.0.1",
|
||||||
"storybook": "9.1.5",
|
"storybook": "9.1.5",
|
||||||
"tailwindcss": "3.4.17",
|
"tailwindcss": "3.4.17",
|
||||||
"typescript": "5.9.3"
|
"typescript": "5.9.3",
|
||||||
|
"vite-tsconfig-paths": "6.0.4",
|
||||||
|
"vitest": "4.0.17"
|
||||||
},
|
},
|
||||||
"msw": {
|
"msw": {
|
||||||
"workerDirectory": [
|
"workerDirectory": [
|
||||||
|
|||||||
1118
autogpt_platform/frontend/pnpm-lock.yaml
generated
1118
autogpt_platform/frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,81 +0,0 @@
|
|||||||
// import { render, screen } from "@testing-library/react";
|
|
||||||
// import { describe, expect, it } from "vitest";
|
|
||||||
// import { Badge } from "./Badge";
|
|
||||||
|
|
||||||
// describe("Badge Component", () => {
|
|
||||||
// it("renders badge with content", () => {
|
|
||||||
// render(<Badge variant="success">Success</Badge>);
|
|
||||||
|
|
||||||
// expect(screen.getByText("Success")).toBeInTheDocument();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it("applies correct variant styles", () => {
|
|
||||||
// const { rerender } = render(<Badge variant="success">Success</Badge>);
|
|
||||||
// let badge = screen.getByText("Success");
|
|
||||||
// expect(badge).toHaveClass("bg-green-100", "text-green-800");
|
|
||||||
|
|
||||||
// rerender(<Badge variant="error">Error</Badge>);
|
|
||||||
// badge = screen.getByText("Error");
|
|
||||||
// expect(badge).toHaveClass("bg-red-100", "text-red-800");
|
|
||||||
|
|
||||||
// rerender(<Badge variant="info">Info</Badge>);
|
|
||||||
// badge = screen.getByText("Info");
|
|
||||||
// expect(badge).toHaveClass("bg-slate-100", "text-slate-800");
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it("applies custom className", () => {
|
|
||||||
// render(
|
|
||||||
// <Badge variant="success" className="custom-class">
|
|
||||||
// Success
|
|
||||||
// </Badge>,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const badge = screen.getByText("Success");
|
|
||||||
// expect(badge).toHaveClass("custom-class");
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it("renders as span element", () => {
|
|
||||||
// render(<Badge variant="success">Success</Badge>);
|
|
||||||
|
|
||||||
// const badge = screen.getByText("Success");
|
|
||||||
// expect(badge.tagName).toBe("SPAN");
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it("renders children correctly", () => {
|
|
||||||
// render(
|
|
||||||
// <Badge variant="success">
|
|
||||||
// <span>Custom</span> Content
|
|
||||||
// </Badge>,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// expect(screen.getByText("Custom")).toBeInTheDocument();
|
|
||||||
// expect(screen.getByText("Content")).toBeInTheDocument();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it("supports all badge variants", () => {
|
|
||||||
// const variants = ["success", "error", "info"] as const;
|
|
||||||
|
|
||||||
// variants.forEach((variant) => {
|
|
||||||
// const { unmount } = render(
|
|
||||||
// <Badge variant={variant} data-testid={`badge-${variant}`}>
|
|
||||||
// {variant}
|
|
||||||
// </Badge>,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// expect(screen.getByTestId(`badge-${variant}`)).toBeInTheDocument();
|
|
||||||
// unmount();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it("handles long text content", () => {
|
|
||||||
// render(
|
|
||||||
// <Badge variant="info">
|
|
||||||
// Very long text that should be handled properly by the component
|
|
||||||
// </Badge>,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const badge = screen.getByText(/Very long text/);
|
|
||||||
// expect(badge).toBeInTheDocument();
|
|
||||||
// expect(badge).toHaveClass("overflow-hidden", "text-ellipsis");
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
13
autogpt_platform/frontend/src/mocks/index.ts
Normal file
13
autogpt_platform/frontend/src/mocks/index.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// We are not using this for tests because Vitest runs our tests in a Node.js environment.
|
||||||
|
// However, we can use it for development purposes to test our UI in the browser with fake data.
|
||||||
|
export async function initMocks() {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
const { server } = await import("./mock-server");
|
||||||
|
server.listen({ onUnhandledRequest: "bypass" });
|
||||||
|
console.log("[MSW] Server mock initialized");
|
||||||
|
} else {
|
||||||
|
const { worker } = await import("./mock-browser");
|
||||||
|
await worker.start({ onUnhandledRequest: "bypass" });
|
||||||
|
console.log("[MSW] Browser mock initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
4
autogpt_platform/frontend/src/mocks/mock-browser.ts
Normal file
4
autogpt_platform/frontend/src/mocks/mock-browser.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { setupWorker } from "msw/browser";
|
||||||
|
import { mockHandlers } from "./mock-handlers";
|
||||||
|
|
||||||
|
export const worker = setupWorker(...mockHandlers);
|
||||||
48
autogpt_platform/frontend/src/mocks/mock-handlers.ts
Normal file
48
autogpt_platform/frontend/src/mocks/mock-handlers.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { getAdminMock } from "@/app/api/__generated__/endpoints/admin/admin.msw";
|
||||||
|
import { getAnalyticsMock } from "@/app/api/__generated__/endpoints/analytics/analytics.msw";
|
||||||
|
import { getApiKeysMock } from "@/app/api/__generated__/endpoints/api-keys/api-keys.msw";
|
||||||
|
import { getAuthMock } from "@/app/api/__generated__/endpoints/auth/auth.msw";
|
||||||
|
import { getBlocksMock } from "@/app/api/__generated__/endpoints/blocks/blocks.msw";
|
||||||
|
import { getChatMock } from "@/app/api/__generated__/endpoints/chat/chat.msw";
|
||||||
|
import { getCreditsMock } from "@/app/api/__generated__/endpoints/credits/credits.msw";
|
||||||
|
import { getDefaultMock } from "@/app/api/__generated__/endpoints/default/default.msw";
|
||||||
|
import { getEmailMock } from "@/app/api/__generated__/endpoints/email/email.msw";
|
||||||
|
import { getExecutionsMock } from "@/app/api/__generated__/endpoints/executions/executions.msw";
|
||||||
|
import { getFilesMock } from "@/app/api/__generated__/endpoints/files/files.msw";
|
||||||
|
import { getGraphsMock } from "@/app/api/__generated__/endpoints/graphs/graphs.msw";
|
||||||
|
import { getHealthMock } from "@/app/api/__generated__/endpoints/health/health.msw";
|
||||||
|
import { getIntegrationsMock } from "@/app/api/__generated__/endpoints/integrations/integrations.msw";
|
||||||
|
import { getLibraryMock } from "@/app/api/__generated__/endpoints/library/library.msw";
|
||||||
|
import { getMetricsMock } from "@/app/api/__generated__/endpoints/metrics/metrics.msw";
|
||||||
|
import { getOauthMock } from "@/app/api/__generated__/endpoints/oauth/oauth.msw";
|
||||||
|
import { getOnboardingMock } from "@/app/api/__generated__/endpoints/onboarding/onboarding.msw";
|
||||||
|
import { getOttoMock } from "@/app/api/__generated__/endpoints/otto/otto.msw";
|
||||||
|
import { getPresetsMock } from "@/app/api/__generated__/endpoints/presets/presets.msw";
|
||||||
|
import { getSchedulesMock } from "@/app/api/__generated__/endpoints/schedules/schedules.msw";
|
||||||
|
import { getStoreMock } from "@/app/api/__generated__/endpoints/store/store.msw";
|
||||||
|
|
||||||
|
// Pass hard-coded data to individual handler functions to override faker-generated data.
|
||||||
|
export const mockHandlers = [
|
||||||
|
...getAdminMock(),
|
||||||
|
...getAnalyticsMock(),
|
||||||
|
...getApiKeysMock(),
|
||||||
|
...getAuthMock(),
|
||||||
|
...getBlocksMock(),
|
||||||
|
...getChatMock(),
|
||||||
|
...getCreditsMock(),
|
||||||
|
...getDefaultMock(),
|
||||||
|
...getEmailMock(),
|
||||||
|
...getExecutionsMock(),
|
||||||
|
...getFilesMock(),
|
||||||
|
...getGraphsMock(),
|
||||||
|
...getHealthMock(),
|
||||||
|
...getIntegrationsMock(),
|
||||||
|
...getLibraryMock(),
|
||||||
|
...getMetricsMock(),
|
||||||
|
...getOauthMock(),
|
||||||
|
...getOnboardingMock(),
|
||||||
|
...getOttoMock(),
|
||||||
|
...getPresetsMock(),
|
||||||
|
...getSchedulesMock(),
|
||||||
|
...getStoreMock(),
|
||||||
|
];
|
||||||
4
autogpt_platform/frontend/src/mocks/mock-server.ts
Normal file
4
autogpt_platform/frontend/src/mocks/mock-server.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { setupServer } from "msw/node";
|
||||||
|
import { mockHandlers } from "./mock-handlers";
|
||||||
|
|
||||||
|
export const server = setupServer(...mockHandlers);
|
||||||
220
autogpt_platform/frontend/src/tests/CLAUDE.md
Normal file
220
autogpt_platform/frontend/src/tests/CLAUDE.md
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# Frontend Testing Rules 🧪
|
||||||
|
|
||||||
|
## Testing Types Overview
|
||||||
|
|
||||||
|
| Type | Tool | Speed | Purpose |
|
||||||
|
| --------------- | --------------------- | --------------- | -------------------------------- |
|
||||||
|
| **E2E** | Playwright | Slow (~5s/test) | Real browser, full user journeys |
|
||||||
|
| **Integration** | Vitest + RTL | Fast (~100ms) | Component + mocked API |
|
||||||
|
| **Unit** | Vitest + RTL | Fastest (~10ms) | Individual functions/components |
|
||||||
|
| **Visual** | Storybook + Chromatic | N/A | UI appearance, design system |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## When to Use Each
|
||||||
|
|
||||||
|
### ✅ E2E Tests (Playwright)
|
||||||
|
|
||||||
|
**Use for:** Critical user journeys that MUST work in a real browser.
|
||||||
|
|
||||||
|
- Authentication flows (login, signup, logout)
|
||||||
|
- Payment or sensitive transactions
|
||||||
|
- Flows requiring real browser APIs (clipboard, downloads)
|
||||||
|
- Cross-page navigation that must work end-to-end
|
||||||
|
|
||||||
|
**Location:** `src/tests/*.spec.ts` (centralized, as there will be fewer of them)
|
||||||
|
|
||||||
|
### ✅ Integration Tests (Vitest + RTL)
|
||||||
|
|
||||||
|
**Use for:** Testing components with their dependencies (API calls, state).
|
||||||
|
|
||||||
|
- Page-level behavior with mocked API responses
|
||||||
|
- Components that fetch data
|
||||||
|
- User interactions that trigger API calls
|
||||||
|
- Feature flows within a single page
|
||||||
|
|
||||||
|
**Location:** Place tests in a `__tests__` folder next to the component:
|
||||||
|
|
||||||
|
```
|
||||||
|
ComponentName/
|
||||||
|
__tests__/
|
||||||
|
main.test.tsx
|
||||||
|
some-flow.test.tsx
|
||||||
|
ComponentName.tsx
|
||||||
|
useComponentName.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**Start at page level:** Initially write integration tests at the "page" level. No need to write them for every small component.
|
||||||
|
|
||||||
|
```
|
||||||
|
/library/
|
||||||
|
__tests__/
|
||||||
|
main.test.tsx
|
||||||
|
searching-agents.test.tsx
|
||||||
|
agents-pagination.test.tsx
|
||||||
|
page.tsx
|
||||||
|
useLibraryPage.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Start with a `main.test.tsx` file and split into smaller files as it grows.
|
||||||
|
|
||||||
|
**What integration tests should do:**
|
||||||
|
|
||||||
|
1. Render a page or complex modal (e.g., `AgentPublishModal`)
|
||||||
|
2. Mock API requests via MSW
|
||||||
|
3. Assert UI scenarios via Testing Library
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Example: Test page renders data from API
|
||||||
|
import { server } from "@/mocks/mock-server";
|
||||||
|
import { getDeleteV2DeleteStoreSubmissionMockHandler422 } from "@/app/api/__generated__/endpoints/store/store.msw";
|
||||||
|
|
||||||
|
test("shows error when submission fails", async () => {
|
||||||
|
// Override default handler to return error status
|
||||||
|
server.use(getDeleteV2DeleteStoreSubmissionMockHandler422());
|
||||||
|
|
||||||
|
render(<MarketplacePage />);
|
||||||
|
await screen.findByText("Featured Agents");
|
||||||
|
// ... assert error UI
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tip:** Use `findBy...` methods most of the time—they wait for elements to appear, so async code won't cause flaky tests. The regular `getBy...` methods don't wait and error immediately.
|
||||||
|
|
||||||
|
### ✅ Unit Tests (Vitest + RTL)
|
||||||
|
|
||||||
|
**Use for:** Testing isolated components and utility functions.
|
||||||
|
|
||||||
|
- Pure utility functions (`lib/utils.ts`)
|
||||||
|
- Component rendering with various props
|
||||||
|
- Component state changes
|
||||||
|
- Custom hooks
|
||||||
|
|
||||||
|
**Location:** Co-located with the file: `Component.test.tsx` next to `Component.tsx`
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Example: Test component renders correctly
|
||||||
|
render(<AgentCard title="My Agent" />);
|
||||||
|
expect(screen.getByText("My Agent")).toBeInTheDocument();
|
||||||
|
```
|
||||||
|
|
||||||
|
### ✅ Storybook Tests (Visual)
|
||||||
|
|
||||||
|
**Use for:** Design system, visual appearance, component documentation.
|
||||||
|
|
||||||
|
- Atoms (Button, Input, Badge)
|
||||||
|
- Molecules (Dialog, Card)
|
||||||
|
- Visual states (hover, disabled, loading)
|
||||||
|
- Responsive layouts
|
||||||
|
|
||||||
|
**Location:** Co-located: `Component.stories.tsx` next to `Component.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decision Flowchart
|
||||||
|
|
||||||
|
```
|
||||||
|
Does it need a REAL browser/backend?
|
||||||
|
├─ YES → E2E (Playwright)
|
||||||
|
└─ NO
|
||||||
|
└─ Does it involve API calls or complex state?
|
||||||
|
├─ YES → Integration (Vitest + RTL)
|
||||||
|
└─ NO
|
||||||
|
└─ Is it about visual appearance?
|
||||||
|
├─ YES → Storybook
|
||||||
|
└─ NO → Unit (Vitest + RTL)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What NOT to Test
|
||||||
|
|
||||||
|
❌ Third-party library internals (Radix UI, React Query)
|
||||||
|
❌ CSS styling details (use Storybook)
|
||||||
|
❌ Simple prop-passing components with no logic
|
||||||
|
❌ TypeScript types
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── components/
|
||||||
|
│ └── atoms/
|
||||||
|
│ └── Button/
|
||||||
|
│ ├── Button.tsx
|
||||||
|
│ ├── Button.test.tsx # Unit test
|
||||||
|
│ └── Button.stories.tsx # Visual test
|
||||||
|
├── app/
|
||||||
|
│ └── (platform)/
|
||||||
|
│ └── marketplace/
|
||||||
|
│ └── components/
|
||||||
|
│ └── MainMarketplacePage/
|
||||||
|
│ ├── __tests__/
|
||||||
|
│ │ ├── main.test.tsx # Integration test
|
||||||
|
│ │ └── search-agents.test.tsx # Integration test
|
||||||
|
│ ├── MainMarketplacePage.tsx
|
||||||
|
│ └── useMainMarketplacePage.ts
|
||||||
|
├── lib/
|
||||||
|
│ ├── utils.ts
|
||||||
|
│ └── utils.test.ts # Unit test
|
||||||
|
├── mocks/
|
||||||
|
│ ├── mock-handlers.ts # MSW handlers (auto-generated via Orval)
|
||||||
|
│ └── mock-server.ts # MSW server setup
|
||||||
|
└── tests/
|
||||||
|
├── integrations/
|
||||||
|
│ ├── test-utils.tsx # Testing utilities
|
||||||
|
│ └── vitest.setup.tsx # Integration test setup
|
||||||
|
└── *.spec.ts # E2E tests (Playwright) - centralized
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority Matrix
|
||||||
|
|
||||||
|
| Component Type | Test Priority | Recommended Test |
|
||||||
|
| ------------------- | ------------- | ---------------- |
|
||||||
|
| Pages/Features | **Highest** | Integration |
|
||||||
|
| Custom Hooks | High | Unit |
|
||||||
|
| Utility Functions | High | Unit |
|
||||||
|
| Organisms (complex) | High | Integration |
|
||||||
|
| Molecules | Medium | Unit + Storybook |
|
||||||
|
| Atoms | Medium | Storybook only\* |
|
||||||
|
|
||||||
|
\*Atoms are typically simple enough that Storybook visual tests suffice.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MSW Mocking
|
||||||
|
|
||||||
|
API mocking is handled via MSW (Mock Service Worker). Handlers are auto-generated by Orval from the OpenAPI schema.
|
||||||
|
|
||||||
|
**Default behavior:** All client-side requests are intercepted and return 200 status with faker-generated data.
|
||||||
|
|
||||||
|
**Override for specific tests:** Use generated error handlers to test non-OK status scenarios:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { server } from "@/mocks/mock-server";
|
||||||
|
import { getDeleteV2DeleteStoreSubmissionMockHandler422 } from "@/app/api/__generated__/endpoints/store/store.msw";
|
||||||
|
|
||||||
|
test("shows error when deletion fails", async () => {
|
||||||
|
server.use(getDeleteV2DeleteStoreSubmissionMockHandler422());
|
||||||
|
|
||||||
|
render(<MyComponent />);
|
||||||
|
// ... assert error UI
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Generated handlers location:** `src/app/api/__generated__/endpoints/*/` - each endpoint has handlers for different status codes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Golden Rules
|
||||||
|
|
||||||
|
1. **Test behavior, not implementation** - Query by role/text, not class names
|
||||||
|
2. **One assertion per concept** - Tests should be focused
|
||||||
|
3. **Mock at boundaries** - Mock API calls, not internal functions
|
||||||
|
4. **Co-locate integration tests** - Keep `__tests__/` folder next to the component
|
||||||
|
5. **E2E is expensive** - Only for critical happy paths; prefer integration tests
|
||||||
|
6. **AI agents are good at writing integration tests** - Start with these when adding test coverage
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { vi } from "vitest";
|
||||||
|
|
||||||
|
const mockSupabaseClient = {
|
||||||
|
auth: {
|
||||||
|
getUser: vi.fn().mockResolvedValue({
|
||||||
|
data: { user: null },
|
||||||
|
error: null,
|
||||||
|
}),
|
||||||
|
getSession: vi.fn().mockResolvedValue({
|
||||||
|
data: { session: null },
|
||||||
|
error: null,
|
||||||
|
}),
|
||||||
|
signOut: vi.fn().mockResolvedValue({ error: null }),
|
||||||
|
refreshSession: vi.fn().mockResolvedValue({
|
||||||
|
data: { session: null, user: null },
|
||||||
|
error: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockSupabaseRequest = () => {
|
||||||
|
vi.mock("@/lib/supabase/server/getServerSupabase", () => ({
|
||||||
|
getServerSupabase: vi.fn().mockResolvedValue(mockSupabaseClient),
|
||||||
|
}));
|
||||||
|
};
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { vi } from "vitest";
|
||||||
|
|
||||||
|
export const mockNextjsModules = () => {
|
||||||
|
vi.mock("next/image", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: ({
|
||||||
|
fill: _fill,
|
||||||
|
priority: _priority,
|
||||||
|
quality: _quality,
|
||||||
|
placeholder: _placeholder,
|
||||||
|
blurDataURL: _blurDataURL,
|
||||||
|
loader: _loader,
|
||||||
|
...props
|
||||||
|
}: any) => {
|
||||||
|
// eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element
|
||||||
|
return <img {...props} />;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("next/headers", () => ({
|
||||||
|
cookies: vi.fn(() => ({
|
||||||
|
get: vi.fn(() => undefined),
|
||||||
|
getAll: vi.fn(() => []),
|
||||||
|
set: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
has: vi.fn(() => false),
|
||||||
|
})),
|
||||||
|
headers: vi.fn(() => new Headers()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("next/dist/server/request/cookies", () => ({
|
||||||
|
cookies: vi.fn(() => ({
|
||||||
|
get: vi.fn(() => undefined),
|
||||||
|
getAll: vi.fn(() => []),
|
||||||
|
set: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
has: vi.fn(() => false),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("next/navigation", () => ({
|
||||||
|
useRouter: () => ({
|
||||||
|
push: vi.fn(),
|
||||||
|
replace: vi.fn(),
|
||||||
|
prefetch: vi.fn(),
|
||||||
|
back: vi.fn(),
|
||||||
|
forward: vi.fn(),
|
||||||
|
refresh: vi.fn(),
|
||||||
|
}),
|
||||||
|
usePathname: () => "/marketplace",
|
||||||
|
useSearchParams: () => new URLSearchParams(),
|
||||||
|
useParams: () => ({}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("next/link", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: ({ children, href, ...props }: any) => (
|
||||||
|
<a href={href} {...props}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { BackendAPIProvider } from "@/lib/autogpt-server-api/context";
|
||||||
|
import OnboardingProvider from "@/providers/onboarding/onboarding-provider";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { render, RenderOptions } from "@testing-library/react";
|
||||||
|
import { ReactElement, ReactNode } from "react";
|
||||||
|
|
||||||
|
function createTestQueryClient() {
|
||||||
|
return new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestProviders({ children }: { children: ReactNode }) {
|
||||||
|
const queryClient = createTestQueryClient();
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<BackendAPIProvider>
|
||||||
|
<OnboardingProvider>{children}</OnboardingProvider>
|
||||||
|
</BackendAPIProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function customRender(
|
||||||
|
ui: ReactElement,
|
||||||
|
options?: Omit<RenderOptions, "wrapper">,
|
||||||
|
) {
|
||||||
|
return render(ui, { wrapper: TestProviders, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from "@testing-library/react";
|
||||||
|
export { customRender as render };
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { beforeAll, afterAll, afterEach } from "vitest";
|
||||||
|
import { server } from "@/mocks/mock-server";
|
||||||
|
import { mockNextjsModules } from "./setup-nextjs-mocks";
|
||||||
|
import { mockSupabaseRequest } from "./mock-supabase-request";
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
mockNextjsModules();
|
||||||
|
mockSupabaseRequest(); // If you need user's data - please mock supabase actions in your specific test - it sends null user [It's only to avoid cookies() call]
|
||||||
|
return server.listen({ onUnhandledRequest: "error" });
|
||||||
|
});
|
||||||
|
afterEach(() => server.resetHandlers());
|
||||||
|
afterAll(() => server.close());
|
||||||
12
autogpt_platform/frontend/vitest.config.mts
Normal file
12
autogpt_platform/frontend/vitest.config.mts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import tsconfigPaths from "vite-tsconfig-paths";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [tsconfigPaths(), react()],
|
||||||
|
test: {
|
||||||
|
environment: "happy-dom",
|
||||||
|
include: ["src/**/*.test.tsx"],
|
||||||
|
setupFiles: ["./src/tests/integrations/vitest.setup.tsx"],
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user