Skip to content

Commit 37fb3a3

Browse files
csandmanclaude
andauthored
feat(tests): add vitest test suite (#420)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1f99261 commit 37fb3a3

22 files changed

Lines changed: 6583 additions & 660 deletions

.github/workflows/lint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ jobs:
3737
- name: Lint types
3838
run: pnpm lint:types
3939

40-
- name: Build and lint exports
41-
run: pnpm build && pnpm lint:exports
40+
- name: Build, lint exports, and lint publish
41+
run: pnpm build && pnpm lint:exports && pnpm lint:publish

.github/workflows/publish.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ jobs:
4343
- name: Lint publish (publint)
4444
run: pnpm lint:publish
4545

46+
- name: Run tests
47+
run: pnpm test
48+
4649
build:
4750
runs-on: ubuntu-latest
4851
permissions:

.github/workflows/test.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
branches: [v5]
6+
pull_request:
7+
8+
permissions: {}
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
node-version: [22, 24]
19+
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
23+
with:
24+
persist-credentials: false
25+
26+
- name: Install pnpm
27+
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
28+
29+
- name: Use Node.js ${{ matrix.node-version }}
30+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
31+
with:
32+
node-version: ${{ matrix.node-version }}
33+
cache: pnpm
34+
35+
- name: Install Dependencies
36+
run: pnpm install
37+
38+
- name: Run tests
39+
run: pnpm test

.lintstagedrc.mjs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
1+
// lint-staged config — moved here from package.json so we can use a
2+
// function to filter file lists before invoking oxlint. The default glob
3+
// picks up files under src/tests/, but oxlint excludes that directory via
4+
// ignorePatterns in .oxlintrc.json; passing it nothing-but-ignored files
5+
// makes oxlint exit non-zero with "No files found to lint", which breaks
6+
// the pre-commit hook for test-only changes. The filter drops src/tests/
7+
// paths before invoking oxlint; oxfmt still formats them.
8+
9+
import path from "node:path";
10+
11+
const IGNORED = ["src/tests/"];
12+
13+
const filterLintable = (files) =>
14+
files.filter((file) => {
15+
const rel = path.relative(import.meta.dirname, file);
16+
return !IGNORED.some((prefix) => rel.startsWith(prefix));
17+
});
18+
119
const config = {
220
"demo/**/*.{js,jsx,ts,tsx}":
321
"oxlint -c demo/.oxlintrc.json --disable-nested-config --fix",
4-
"!(demo|codemod)/**/*.{js,jsx,ts,tsx}":
5-
"oxlint -c .oxlintrc.json --disable-nested-config --fix",
6-
// Exclude codemod test fixtures from oxfmt — they must exactly match the
7-
// transform output, which jscodeshift formats independently of oxfmt.
8-
"!(codemod/tests/fixtures/**)": "oxfmt",
22+
"!(demo|codemod)/**/*.{js,jsx,ts,tsx}": (files) => {
23+
const lintable = filterLintable(files);
24+
if (lintable.length === 0) {
25+
return [];
26+
}
27+
return `oxlint -c .oxlintrc.json --disable-nested-config --fix ${lintable
28+
.map((f) => JSON.stringify(f))
29+
.join(" ")}`;
30+
},
31+
"*": "oxfmt",
932
};
1033

1134
export default config;

.oxfmtrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"ignorePatterns": ["codemod/tests/fixtures/"],
23
"printWidth": 80,
34
"tabWidth": 2,
45
"useTabs": false,

.oxlintrc.json

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"jsdoc",
1212
"jsx-a11y",
1313
"node",
14-
"promise"
14+
"promise",
15+
"vitest"
1516
],
1617
"categories": {
1718
"correctness": "error",
@@ -58,7 +59,15 @@
5859
"no-proto": "error",
5960
"no-regex-spaces": "warn",
6061
"no-sequences": "error",
61-
"no-unused-vars": ["error", { "ignoreRestSiblings": true }],
62+
"no-unused-vars": [
63+
"error",
64+
{
65+
"ignoreRestSiblings": true,
66+
"argsIgnorePattern": "^_",
67+
"varsIgnorePattern": "^_",
68+
"caughtErrorsIgnorePattern": "^_"
69+
}
70+
],
6271
"no-var": "error",
6372
"no-void": "warn",
6473
"prefer-const": "error",
@@ -94,6 +103,7 @@
94103
"@typescript-eslint/strict-boolean-expressions": "off",
95104
"capitalized-comments": "off",
96105
"id-length": "off",
106+
"init-declarations": "off",
97107
"import/exports-last": "off",
98108
"import/group-exports": "off",
99109
"import/no-named-export": "off",
@@ -122,7 +132,27 @@
122132
"react-perf/jsx-no-new-function-as-prop": "off",
123133
"sort-imports": "off",
124134
"sort-keys": "off",
125-
"unicorn/no-null": "off"
135+
"unicorn/no-null": "off",
136+
137+
"vitest/consistent-test-it": [
138+
"error",
139+
{ "fn": "test", "withinDescribe": "test" }
140+
],
141+
"vitest/max-expects": "off",
142+
"vitest/no-conditional-in-test": "off",
143+
"vitest/no-hooks": "off",
144+
"vitest/no-importing-vitest-globals": "off",
145+
"vitest/no-standalone-expect": "off",
146+
"vitest/prefer-called-times": "off",
147+
"vitest/prefer-called-with": "off",
148+
"vitest/prefer-expect-assertions": "off",
149+
"vitest/prefer-lowercase-title": "off",
150+
"vitest/prefer-strict-boolean-matchers": "off",
151+
"vitest/prefer-to-be": "off",
152+
"vitest/prefer-to-be-falsy": "off",
153+
"vitest/prefer-to-be-truthy": "off",
154+
"vitest/require-hook": "off",
155+
"vitest/require-mock-type-parameters": "off"
126156
},
127157
"overrides": [
128158
{
@@ -133,6 +163,29 @@
133163
"@typescript-eslint/no-unsafe-call": "off",
134164
"@typescript-eslint/no-unsafe-return": "off"
135165
}
166+
},
167+
{
168+
"files": ["src/tests/**/*.{ts,tsx}"],
169+
"rules": {
170+
// ─── TS strictness relaxed for spy/mock-style test code ───
171+
"@typescript-eslint/no-non-null-assertion": "off",
172+
"@typescript-eslint/no-useless-default-assignment": "off",
173+
174+
// ─── General style/correctness rules tests legitimately break ───
175+
"no-console": "off",
176+
"no-param-reassign": "off",
177+
"no-underscore-dangle": "off",
178+
179+
// ─── JSX/a11y/perf rules not applicable to test fixtures ───
180+
"jsx-a11y/no-autofocus": "off",
181+
"react-perf/jsx-no-new-array-as-prop": "off",
182+
"react-perf/jsx-no-new-object-as-prop": "off",
183+
184+
// ─── Unicorn opinions that don't fit test patterns ───
185+
"unicorn/consistent-function-scoping": "off",
186+
"unicorn/prefer-at": "off",
187+
"unicorn/prefer-global-this": "off"
188+
}
136189
}
137190
]
138191
}

CONTRIBUTING.md

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ Before you open a PR:
1212
- Run `pnpm dev` to build (and watch) the package source, as well as run the
1313
demo project which can be viewed at http://localhost:5152.
1414
- Please ensure all the examples work correctly after your change.
15-
- Also run `pnpm lint` to ensure that the change meets the projects code
16-
style setup.
15+
- Also run `pnpm lint` to ensure that the change meets the projects code style
16+
setup.
17+
- Run `pnpm test` to verify the test suite still passes. The suite covers both
18+
behavior ported from `react-select` and chakra-specific extensions — see the
19+
[Tests](#tests) section below.
1720
- Make sure there's an issue open for any work you take on and intend to submit
1821
as a pull request - it helps core members review your concept and direction
1922
early and is a good way to discuss what you're planning to do.
@@ -22,3 +25,45 @@ Before you open a PR:
2225
your hard work.
2326
- All new features and changes need documentation. If you don't have time to
2427
write any, leave a note in your PR.
28+
29+
## Tests
30+
31+
The test suite lives in [`src/tests/`](./src/tests/) and runs with
32+
[Vitest](https://vitest.dev) against a `jsdom` environment plus
33+
`@testing-library/react`:
34+
35+
- `select.test.tsx`, `async-select.test.tsx`, `creatable-select.test.tsx`,
36+
`async-creatable-select.test.tsx`, `state-managed-select.test.tsx` — ported
37+
from `react-select`'s own `__tests__` directory. Each file's header
38+
comment carries a permalink to the upstream source at the pinned version.
39+
- `chakra-specific.test.tsx` — exercises the props this package adds on
40+
top of `react-select` (`size`, `variant`, `isInvalid`, `chakraStyles`,
41+
`tagColorScheme`, `selectedOptionStyle`, etc.).
42+
- `constants.ts` — option fixtures, copied verbatim from upstream.
43+
- `render.tsx`, `setup.ts`, `cases.ts` — local helpers (Chakra-wrapped
44+
render, jsdom polyfills, `jest-in-case` shim).
45+
46+
Useful commands:
47+
48+
- `pnpm test` — run the suite once
49+
- `pnpm test:watch` — re-run on file changes
50+
51+
### When bumping the `react-select` dependency
52+
53+
If a `react-select` bump introduces test changes, port them in as a manual
54+
step rather than rewriting the ported files from scratch:
55+
56+
1. Diff the upstream
57+
[`__tests__/` folder](https://github.com/JedWatson/react-select/tree/master/packages/react-select/src/__tests__)
58+
between the old and new tag — the header comment in each ported file links
59+
to the source at the currently pinned tag.
60+
2. For each upstream test that's new or whose body changed, port it
61+
following the same adaptations already in use:
62+
- imports from `../index` (not `../Select` etc.)
63+
- `render` from `./render` (provides Chakra context)
64+
- `userEvent.setup()` + `await user.click/type` (v14 async API)
65+
- `vi.fn` instead of `jest.fn`
66+
- skip snapshot tests
67+
3. Update each ported file's header permalink to point at the new tag.
68+
4. Leave chakra-specific tests in `chakra-specific.test.tsx` — don't fold
69+
chakra-only assertions into the ported files.

demo/package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@
1515
"@emotion/react": "^11.14.0",
1616
"@emotion/styled": "^11.14.1",
1717
"chakra-react-select": "workspace:*",
18-
"framer-motion": "^12.38.0",
19-
"react": "^19.2.4",
20-
"react-dom": "^19.2.4"
18+
"framer-motion": "^12.40.0",
19+
"react": "^19.2.6",
20+
"react-dom": "^19.2.6"
2121
},
2222
"devDependencies": {
23-
"@types/react": "^19.2.14",
23+
"@types/react": "^19.2.15",
2424
"@types/react-dom": "^19.2.3",
25-
"@vitejs/plugin-react": "^6.0.1",
26-
"oxlint": "^1",
25+
"@vitejs/plugin-react": "^6.0.2",
26+
"oxlint": "^1.67.0",
2727
"oxlint-tsgolint": "latest",
28-
"typescript": "^6.0.2",
29-
"vite": "^8.0.12"
28+
"typescript": "^6.0.3",
29+
"vite": "^8.0.14"
3030
}
3131
}

package.json

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,24 +63,32 @@
6363
"lint:src": "oxlint -c .oxlintrc.json",
6464
"lint:types": "tsc",
6565
"lint-fix": "oxlint -c .oxlintrc.json --fix",
66-
"prepare": "husky"
66+
"prepare": "husky",
67+
"test": "vitest run",
68+
"test:watch": "vitest"
6769
},
6870
"dependencies": {
6971
"react-select": "^5.10.2"
7072
},
7173
"devDependencies": {
7274
"@arethetypeswrong/cli": "^0.18.2",
7375
"@chakra-ui/react": "^2.10.9",
74-
"@types/react": "^19.2.14",
76+
"@testing-library/jest-dom": "^6.9.1",
77+
"@testing-library/react": "^16.3.2",
78+
"@testing-library/user-event": "^14.6.1",
79+
"@types/react": "^19.2.15",
80+
"@types/react-dom": "^19.2.3",
7581
"concurrently": "^9.2.1",
7682
"husky": "^9.1.7",
77-
"lint-staged": "^16.4.0",
78-
"oxfmt": "^0.49.0",
79-
"oxlint": "^1.64.0",
80-
"oxlint-tsgolint": "^0.22.1",
83+
"jsdom": "^29.1.1",
84+
"lint-staged": "^17.0.5",
85+
"oxfmt": "^0.52.0",
86+
"oxlint": "^1.67.0",
87+
"oxlint-tsgolint": "^0.23.0",
8188
"publint": "^0.3.21",
8289
"tsup": "^8.5.1",
83-
"typescript": "^6.0.3"
90+
"typescript": "^6.0.3",
91+
"vitest": "^4.1.7"
8492
},
8593
"peerDependencies": {
8694
"@chakra-ui/react": "2.x",

0 commit comments

Comments
 (0)