feat: enhance testing setup and API schema

- Updated `orval.config.ts` to configure MSW for API mocking with detailed settings.
- Added `happy-dom` as a new dependency for testing environment.
- Modified `vitest.config.mts` to use `happy-dom` for the testing environment.
- Introduced new boolean properties `has_sensitive_action` and `sensitive_action_safe_mode` in `openapi.json` for better control over agent behavior.
- Removed obsolete test files and utility functions to streamline the testing process.
- Updated test setup to improve isolation and configuration.
This commit is contained in:
abhi1992002
2026-01-21 18:53:15 +05:30
parent 6960ba8f3e
commit da978a7410
13 changed files with 475 additions and 200 deletions

View File

@@ -16,7 +16,12 @@ export default defineConfig({
client: "react-query",
httpClient: "fetch",
indexFiles: false,
mock: true,
mock: {
type: "msw",
baseUrl: "http://localhost:3000/api/proxy",
generateEachHttpStatus: true,
delay: 0,
},
override: {
mutator: {
path: "./mutators/custom-mutator.ts",

View File

@@ -80,6 +80,7 @@
"flatbush": "4.5.0",
"framer-motion": "12.23.24",
"geist": "1.5.1",
"happy-dom": "20.3.4",
"highlight.js": "11.11.1",
"jaro-winkler": "0.2.8",
"katex": "0.16.25",
@@ -148,7 +149,6 @@
"eslint-config-next": "15.5.7",
"eslint-plugin-storybook": "9.1.5",
"import-in-the-middle": "2.0.2",
"jsdom": "27.4.0",
"msw": "2.11.6",
"msw-storybook-addon": "2.0.6",
"orval": "7.13.0",

View File

@@ -161,6 +161,9 @@ importers:
geist:
specifier: 1.5.1
version: 1.5.1(next@15.4.10(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
happy-dom:
specifier: 20.3.4
version: 20.3.4
highlight.js:
specifier: 11.11.1
version: 11.11.1
@@ -360,9 +363,6 @@ importers:
import-in-the-middle:
specifier: 2.0.2
version: 2.0.2
jsdom:
specifier: 27.4.0
version: 27.4.0
msw:
specifier: 2.11.6
version: 2.11.6(@types/node@24.10.0)(typescript@5.9.3)
@@ -401,7 +401,7 @@ importers:
version: 6.0.4(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))
vitest:
specifier: 4.0.17
version: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@24.10.0)(jiti@2.6.1)(jsdom@27.4.0)(msw@2.11.6(@types/node@24.10.0)(typescript@5.9.3))(terser@5.44.1)(yaml@2.8.2)
version: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@24.10.0)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.4.0)(msw@2.11.6(@types/node@24.10.0)(typescript@5.9.3))(terser@5.44.1)(yaml@2.8.2)
packages:
@@ -3413,6 +3413,9 @@ packages:
'@types/use-sync-external-store@0.0.6':
resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
'@types/whatwg-mimetype@3.0.2':
resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==}
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
@@ -5146,6 +5149,10 @@ packages:
resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==}
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
happy-dom@20.3.4:
resolution: {integrity: sha512-rfbiwB6OKxZFIFQ7SRnCPB2WL9WhyXsFoTfecYgeCeFSOBxvkWLaXsdv5ehzJrfqwXQmDephAKWLRQoFoJwrew==}
engines: {node: '>=20.0.0'}
has-bigints@1.1.0:
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
engines: {node: '>= 0.4'}
@@ -7853,6 +7860,10 @@ packages:
webpack-cli:
optional: true
whatwg-mimetype@3.0.0:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
engines: {node: '>=12'}
whatwg-mimetype@4.0.0:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
@@ -8017,7 +8028,8 @@ packages:
snapshots:
'@acemir/cssom@0.9.31': {}
'@acemir/cssom@0.9.31':
optional: true
'@adobe/css-tools@4.4.4': {}
@@ -8059,6 +8071,7 @@ snapshots:
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
'@csstools/css-tokenizer': 3.0.4
lru-cache: 11.2.4
optional: true
'@asamuzakjp/dom-selector@6.7.6':
dependencies:
@@ -8067,8 +8080,10 @@ snapshots:
css-tree: 3.1.0
is-potential-custom-element-name: 1.0.1
lru-cache: 11.2.4
optional: true
'@asamuzakjp/nwsapi@2.3.9': {}
'@asamuzakjp/nwsapi@2.3.9':
optional: true
'@asyncapi/specs@6.10.0':
dependencies:
@@ -8849,12 +8864,14 @@ snapshots:
dependencies:
commander: 14.0.2
'@csstools/color-helpers@5.1.0': {}
'@csstools/color-helpers@5.1.0':
optional: true
'@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
dependencies:
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
'@csstools/css-tokenizer': 3.0.4
optional: true
'@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
dependencies:
@@ -8862,14 +8879,18 @@ snapshots:
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
'@csstools/css-tokenizer': 3.0.4
optional: true
'@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
dependencies:
'@csstools/css-tokenizer': 3.0.4
optional: true
'@csstools/css-syntax-patches-for-csstree@1.0.25': {}
'@csstools/css-syntax-patches-for-csstree@1.0.25':
optional: true
'@csstools/css-tokenizer@3.0.4': {}
'@csstools/css-tokenizer@3.0.4':
optional: true
'@date-fns/tz@1.4.1': {}
@@ -9078,7 +9099,8 @@ snapshots:
'@eslint/js@8.57.1': {}
'@exodus/bytes@1.9.0': {}
'@exodus/bytes@1.9.0':
optional: true
'@exodus/schemasafe@1.3.0': {}
@@ -11412,6 +11434,8 @@ snapshots:
'@types/use-sync-external-store@0.0.6': {}
'@types/whatwg-mimetype@3.0.2': {}
'@types/ws@8.18.1':
dependencies:
'@types/node': 24.10.0
@@ -11797,7 +11821,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
agent-base@7.1.4: {}
agent-base@7.1.4:
optional: true
ajv-draft-04@1.0.0(ajv@8.17.1):
optionalDependencies:
@@ -12036,6 +12061,7 @@ snapshots:
bidi-js@1.0.3:
dependencies:
require-from-string: 2.0.2
optional: true
big.js@5.2.2: {}
@@ -12393,6 +12419,7 @@ snapshots:
dependencies:
mdn-data: 2.12.2
source-map-js: 1.2.1
optional: true
css-what@6.2.2: {}
@@ -12406,6 +12433,7 @@ snapshots:
'@csstools/css-syntax-patches-for-csstree': 1.0.25
css-tree: 3.1.0
lru-cache: 11.2.4
optional: true
csstype@3.2.3: {}
@@ -12479,6 +12507,7 @@ snapshots:
dependencies:
whatwg-mimetype: 5.0.0
whatwg-url: 15.1.0
optional: true
data-view-buffer@1.0.2:
dependencies:
@@ -12512,7 +12541,8 @@ snapshots:
decimal.js-light@2.5.1: {}
decimal.js@10.6.0: {}
decimal.js@10.6.0:
optional: true
decode-named-character-reference@1.2.0:
dependencies:
@@ -12890,8 +12920,8 @@ snapshots:
'@typescript-eslint/parser': 8.52.0(eslint@8.57.1)(typescript@5.9.3)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.5(eslint@8.57.1)
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1)
@@ -12910,7 +12940,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1):
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.3
@@ -12921,22 +12951,22 @@ snapshots:
tinyglobby: 0.2.15
unrs-resolver: 1.11.1
optionalDependencies:
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.52.0(eslint@8.57.1)(typescript@5.9.3)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -12947,7 +12977,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -13406,6 +13436,18 @@ snapshots:
graphql@16.12.0: {}
happy-dom@20.3.4:
dependencies:
'@types/node': 24.10.0
'@types/whatwg-mimetype': 3.0.2
'@types/ws': 8.18.1
entities: 4.5.0
whatwg-mimetype: 3.0.0
ws: 8.19.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
has-bigints@1.1.0: {}
has-flag@4.0.0: {}
@@ -13554,6 +13596,7 @@ snapshots:
'@exodus/bytes': 1.9.0
transitivePeerDependencies:
- '@noble/hashes'
optional: true
html-entities@2.6.0: {}
@@ -13592,6 +13635,7 @@ snapshots:
debug: 4.4.3
transitivePeerDependencies:
- supports-color
optional: true
http2-client@1.3.5: {}
@@ -13610,6 +13654,7 @@ snapshots:
debug: 4.4.3
transitivePeerDependencies:
- supports-color
optional: true
human-signals@2.1.0: {}
@@ -13776,7 +13821,8 @@ snapshots:
is-plain-obj@4.1.0: {}
is-potential-custom-element-name@1.0.1: {}
is-potential-custom-element-name@1.0.1:
optional: true
is-reference@1.2.1:
dependencies:
@@ -13897,6 +13943,7 @@ snapshots:
- bufferutil
- supports-color
- utf-8-validate
optional: true
jsep@1.4.0: {}
@@ -14065,7 +14112,8 @@ snapshots:
lru-cache@10.4.3: {}
lru-cache@11.2.4: {}
lru-cache@11.2.4:
optional: true
lru-cache@5.1.1:
dependencies:
@@ -14279,7 +14327,8 @@ snapshots:
dependencies:
'@types/mdast': 4.0.4
mdn-data@2.12.2: {}
mdn-data@2.12.2:
optional: true
mdurl@2.0.0: {}
@@ -14930,6 +14979,7 @@ snapshots:
parse5@8.0.0:
dependencies:
entities: 6.0.1
optional: true
party-js@2.2.0: {}
@@ -15644,6 +15694,7 @@ snapshots:
saxes@6.0.0:
dependencies:
xmlchars: 2.2.0
optional: true
scheduler@0.23.2:
dependencies:
@@ -16046,7 +16097,8 @@ snapshots:
transitivePeerDependencies:
- encoding
symbol-tree@3.2.4: {}
symbol-tree@3.2.4:
optional: true
tailwind-merge@2.6.0: {}
@@ -16163,6 +16215,7 @@ snapshots:
tr46@6.0.0:
dependencies:
punycode: 2.3.1
optional: true
tree-kill@1.2.2: {}
@@ -16508,7 +16561,7 @@ snapshots:
terser: 5.44.1
yaml: 2.8.2
vitest@4.0.17(@opentelemetry/api@1.9.0)(@types/node@24.10.0)(jiti@2.6.1)(jsdom@27.4.0)(msw@2.11.6(@types/node@24.10.0)(typescript@5.9.3))(terser@5.44.1)(yaml@2.8.2):
vitest@4.0.17(@opentelemetry/api@1.9.0)(@types/node@24.10.0)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.4.0)(msw@2.11.6(@types/node@24.10.0)(typescript@5.9.3))(terser@5.44.1)(yaml@2.8.2):
dependencies:
'@vitest/expect': 4.0.17
'@vitest/mocker': 4.0.17(msw@2.11.6(@types/node@24.10.0)(typescript@5.9.3))(vite@7.3.1(@types/node@24.10.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))
@@ -16533,6 +16586,7 @@ snapshots:
optionalDependencies:
'@opentelemetry/api': 1.9.0
'@types/node': 24.10.0
happy-dom: 20.3.4
jsdom: 27.4.0
transitivePeerDependencies:
- jiti
@@ -16552,6 +16606,7 @@ snapshots:
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0
optional: true
warning@4.0.3:
dependencies:
@@ -16566,7 +16621,8 @@ snapshots:
webidl-conversions@3.0.1: {}
webidl-conversions@8.0.1: {}
webidl-conversions@8.0.1:
optional: true
webpack-dev-middleware@6.1.3(webpack@5.104.1(esbuild@0.25.12)):
dependencies:
@@ -16622,14 +16678,19 @@ snapshots:
- esbuild
- uglify-js
whatwg-mimetype@4.0.0: {}
whatwg-mimetype@3.0.0: {}
whatwg-mimetype@5.0.0: {}
whatwg-mimetype@4.0.0:
optional: true
whatwg-mimetype@5.0.0:
optional: true
whatwg-url@15.1.0:
dependencies:
tr46: 6.0.0
webidl-conversions: 8.0.1
optional: true
whatwg-url@5.0.0:
dependencies:
@@ -16710,11 +16771,13 @@ snapshots:
ws@8.19.0: {}
xml-name-validator@5.0.0: {}
xml-name-validator@5.0.0:
optional: true
xmlbuilder@15.1.1: {}
xmlchars@2.2.0: {}
xmlchars@2.2.0:
optional: true
xtend@4.0.2: {}

View File

@@ -1,8 +0,0 @@
import { expect, test } from "vitest";
import { render, screen } from "@/tests/test-utils";
import { MainMarkeplacePage } from "./MainMarketplacePage";
test("MainMarketplacePage", () => {
render(<MainMarkeplacePage />);
expect(screen.getByText("Featured Agents")).toBeDefined();
});

View File

@@ -6383,6 +6383,11 @@
"title": "Has Human In The Loop",
"readOnly": true
},
"has_sensitive_action": {
"type": "boolean",
"title": "Has Sensitive Action",
"readOnly": true
},
"trigger_setup_info": {
"anyOf": [
{ "$ref": "#/components/schemas/GraphTriggerInfo" },
@@ -6399,6 +6404,7 @@
"output_schema",
"has_external_trigger",
"has_human_in_the_loop",
"has_sensitive_action",
"trigger_setup_info"
],
"title": "BaseGraph"
@@ -7629,6 +7635,11 @@
"title": "Has Human In The Loop",
"readOnly": true
},
"has_sensitive_action": {
"type": "boolean",
"title": "Has Sensitive Action",
"readOnly": true
},
"trigger_setup_info": {
"anyOf": [
{ "$ref": "#/components/schemas/GraphTriggerInfo" },
@@ -7652,6 +7663,7 @@
"output_schema",
"has_external_trigger",
"has_human_in_the_loop",
"has_sensitive_action",
"trigger_setup_info",
"credentials_input_schema"
],
@@ -7730,6 +7742,11 @@
"title": "Has Human In The Loop",
"readOnly": true
},
"has_sensitive_action": {
"type": "boolean",
"title": "Has Sensitive Action",
"readOnly": true
},
"trigger_setup_info": {
"anyOf": [
{ "$ref": "#/components/schemas/GraphTriggerInfo" },
@@ -7754,6 +7771,7 @@
"output_schema",
"has_external_trigger",
"has_human_in_the_loop",
"has_sensitive_action",
"trigger_setup_info",
"credentials_input_schema"
],
@@ -7762,8 +7780,14 @@
"GraphSettings": {
"properties": {
"human_in_the_loop_safe_mode": {
"anyOf": [{ "type": "boolean" }, { "type": "null" }],
"title": "Human In The Loop Safe Mode"
"type": "boolean",
"title": "Human In The Loop Safe Mode",
"default": true
},
"sensitive_action_safe_mode": {
"type": "boolean",
"title": "Sensitive Action Safe Mode",
"default": false
}
},
"type": "object",
@@ -7921,6 +7945,16 @@
"title": "Has External Trigger",
"description": "Whether the agent has an external trigger (e.g. webhook) node"
},
"has_human_in_the_loop": {
"type": "boolean",
"title": "Has Human In The Loop",
"description": "Whether the agent has human-in-the-loop blocks"
},
"has_sensitive_action": {
"type": "boolean",
"title": "Has Sensitive Action",
"description": "Whether the agent has sensitive action blocks"
},
"trigger_setup_info": {
"anyOf": [
{ "$ref": "#/components/schemas/GraphTriggerInfo" },
@@ -7967,6 +8001,8 @@
"output_schema",
"credentials_input_schema",
"has_external_trigger",
"has_human_in_the_loop",
"has_sensitive_action",
"new_output",
"can_access_graph",
"is_latest_version",

View 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

View File

@@ -1,141 +0,0 @@
# 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`
### ✅ 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:** `src/app/**/page.test.tsx` or `src/components/**/Component.test.tsx`
```tsx
// Example: Test page renders data from API
render(<MarketplacePage />, { wrapper: MockProviders });
await screen.findByText('Featured Agents');
expect(screen.getByRole('list')).toHaveLength(3);
```
### ✅ 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/
│ ├── page.tsx
│ └── page.test.tsx # Integration test
├── lib/
│ ├── utils.ts
│ └── utils.test.ts # Unit test
└── tests/
└── *.spec.ts # E2E tests (Playwright)
```
---
## 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.
---
## 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 tests** - Keep `.test.tsx` next to the component
5. **E2E is expensive** - Only for critical journeys; prefer integration tests

View File

@@ -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: { user: null },
error: null,
}),
},
};
export const mockSupabaseRequest = () => {
vi.mock("@/lib/supabase/server/getServerSupabase", () => ({
getServerSupabase: vi.fn().mockResolvedValue(mockSupabaseClient),
}));
};

View File

@@ -0,0 +1,63 @@
import { vi } from "vitest";
export const mockNextjsModules = () => {
vi.mock("next/image", () => ({
__esModule: true,
default: ({
fill,
priority,
quality,
placeholder,
blurDataURL,
loader,
...props
}: any) => {
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>
),
}));
};

View File

@@ -1,3 +1,5 @@
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";
@@ -15,7 +17,11 @@ function createTestQueryClient() {
function TestProviders({ children }: { children: ReactNode }) {
const queryClient = createTestQueryClient();
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
<QueryClientProvider client={queryClient}>
<BackendAPIProvider>
<OnboardingProvider>{children}</OnboardingProvider>
</BackendAPIProvider>
</QueryClientProvider>
);
}

View File

@@ -0,0 +1,12 @@
import { beforeAll, afterAll, afterEach, vi } 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());

View File

@@ -1,6 +0,0 @@
import { beforeAll, afterAll, afterEach } from "vitest";
import { server } from "@/mocks/mock-server";
beforeAll(() => server.listen({ onUnhandledRequest: "bypass" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

View File

@@ -1,12 +1,12 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
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: 'jsdom',
include: ['src/**/*.test.tsx'],
setupFiles: ['./src/tests/vitest.setup.ts'],
},
})
plugins: [tsconfigPaths(), react()],
test: {
environment: "happy-dom",
include: ["src/**/*.test.tsx"],
setupFiles: ["./src/tests/integrations/vitest.setup.tsx"],
},
});