Repository navigation
fix: bug fixes for 0.6.1 - #69
Merged
Merged
Conversation
Vitest merges a test's options over its tags' options (`assignDefined` in 5.0.5, a spread in 4.1.12), so `skip: false`, `todo: false`, `fails: false` or `only: false` on a test undoes what its tag sets, and a conditional `skip` replaces the tag's unconditional one. The scan kept the tag's state. Checked with Vitest 5.0.5: all four tests run. Closes TI-21.
…e test Before 4.1 Vitest compares the attempt number with the retry option, so an object makes the comparison false and the test body never runs (checked with Vitest 4.0.18 and 3.2.4: zero attempts, reported as pending). The scan ignored the object and filled in the config's count, which hid that. A retry object in a test's or describe's options below 4.1 now gives `retries: null` from where it is written, with an `unresolved-option` warning. One in the config keeps `null` with source `config`: config objects carry no position for a warning, and Vitest 4.0's types reject it. Closes TI-15.
Vitest 5.0.5 (vitest-dev/vitest#11591) made `skipIf()` and `runIf()` set a test's `skip` in its chain context, and merges options with `assignDefined`, which leaves out `undefined`. So the last of `.skip`, `skipIf()` and `runIf()` decides, `test.skip.skipIf(false)` runs, a `skipIf()` or `runIf()` replaces a tag's skip, and `{ skip: undefined }` no longer undoes `test.skip`. Each case was run with Vitest 5.0.5 and 4.1.12; 4.1 keeps the old behaviour, which the scan still gives for it. The scan keys rules on major and minor only, so it applies these to all of Vitest 5. Chose: apply the 5.0.5 rules to all of 5.0 over adding patch versions to the version checks (about 20 call sites), as the scan already takes each minor's newest rules.
Imports were read as UTF-8 text and line endings normalised, so every byte that isn't UTF-8 became U+FFFD and three different PNGs gave the same dependencyHash. A file that isn't valid UTF-8 now keeps its bytes, which the hash takes as they are, as it already does for screenshots. Its decoded text stays as before, so a code file with a stray Latin-1 byte still parses.
TypeScript tries the files of a matching `paths` pattern and, when none exists, goes on to baseUrl and node_modules. The scan stopped at the alias, so a catch-all such as `"*": ["./src/types/*"]` made 'vitest', 'decimal.js' and every other package unreadable, and every test got hasUnfollowedDependencies, which keeps it in every --related run. When nothing is found after the fallback either, a matched alias still gives "could not be read", so a broken alias keeps its warning.
TypeScript maps a .jsx extension in an import to the .tsx source, as it maps .js to .ts. The resolver only stripped .js, .mjs and .cjs, so './OrderHeader.jsx' with OrderHeader.tsx on disk was unreadable and the test was marked unfollowed.
A Svelte component with JSON-LD in <svelte:head> had that block joined to its script, so the component failed to parse, its imports were dropped from dependencies and its tests were marked unfollowed. Only blocks with no type, type="module" or a JavaScript MIME type are code, as in HTML.
A barrel that only re-exports added no code to the hash, so it was in no
test's dependencies, and --related left out every test that imports
through it. Pointing `export * from './fees'` at './fees-eu' changed the
hash but kept no test, while `vitest related` runs the test.
Each re-export a name passes through now adds a unit for the statement,
as an import already does, since which module a name comes from is code
too. The old test said files that only re-export had nothing to list;
its expectation now includes them. The trading-web golden changes only
in dependencyHash, from its `export { expect } from '@playwright/test'`.
Chose: list re-exporting files over documenting the gap, since the docs
promise --related keeps every test a change to a file can affect.
An import such as `@/pricing`, set up only in Vite's resolve.alias, was taken for an installed package: the aliased file was missing from dependencies, hasUnfollowedDependencies stayed false and no warning said so, so --related left the test out when that file changed. npm allows no package name that starts with `@/` or `~/`, so such an import is a path alias for sure. When no tsconfig paths define it, the test is now marked unfollowed with a dependency-not-followed warning, and the unresolved-test-import message names it as an alias. Three tests that import `test` from '@/fixtures' without a usable tsconfig expected the old wording. Chose: mark these aliases now and leave reading resolve.alias from the config to a follow-up, as that needs the config reader to pass aliases to the resolver; other aliases, such as @app/pricing, still read as packages, which the limits page now says.
…l alias
With "paths": { "*": [...] }, an import with no file under the alias returned 'could not be read' unless it was installed or a workspace package, so 'node:fs', 'path' and packages before npm ci still marked tests unfollowed. TypeScript goes on to its usual lookup for any name, so only an @/ or ~/ import, which can't be a package, keeps that reason now.
Vitest 5.0.5 merges a test's options over those its describe passes down, and skipIf()/runIf() now set the test's own skip, so test.runIf(true) inside describe.only('...', { skip: true }) runs (checked with 5.0.5; 4.1.12 skips it). The scan only let a skip in the options object replace the describe's.
Hashing a Latin-1 CSV by its raw bytes made its dependencyHash differ between a Windows and a Linux checkout, as git rewrites the line endings of any file it takes for text. Bytes of a file git takes for text, with no NUL byte in its first 8,000, now have their line endings read as \n, as UTF-8 text already does; a binary file such as a PNG stays byte for byte.
oxc-parser recurses on the native stack. A file with brackets nested about 5,000 deep overflows the main thread's 8 MB stack (2,400 in a worker thread's 4 MB) and the process dies with SIGSEGV, which no try/catch can stop, so one generated fixture killed the whole scan and no inventory was written. oxc has no depth guard (oxc-project/oxc#24375, #21953) and 0.156.0 is the newest release. parseFile now counts bracket nesting in one pass over the text before parsing, skipping strings, comments, regular expressions and template text, and returns a parse error for a file nested more than 500 deep. The check takes about 6% of parseSync's time on typescript.js (31 ms against 499 ms for 9.1 M characters) and refuses none of the 10,091 files in node_modules, the deepest of which nests 34. Chose: a bracket-depth pre-check over parsing in a worker with a larger stack (a worker only raises the crash depth, a SIGSEGV in it still kills the process, and the AST would have to be copied back), and over counting operator chains too (ternaries, arrows, else-if and unary chains crash only past 10,000 levels on the main thread and are not nesting written by hand). Chose: a limit of 500, five times below the crash depth in a 4 MB worker stack and fifteen times above the deepest real file found.
In Vitest browser mode, `await expect.element(locator).toHaveTextContent()` is the usual assertion, and it is `expect.poll` with a locator. The scan counted only `expect()`, `expect.soft()` and `expect.poll()`, so such tests had `expectCount: 0` and looked like tests without assertions; the chart-browser golden showed it for "shows the prices of the candle under the pointer", whose only assertion is an `expect.element()`. `expect.element` is in @vitest/browser from 2.0.0 (not in 1.6.0), so every Vitest version the scan supports, 3.0 and newer, has it. Chose: count it in Playwright files too, as expectCount counts the calls written and the extractor reads `expect` the same way for both frameworks; Playwright has no `expect.element`, so it only appears there by mistake.
A Vitest project without a test.name got the name "", so a monorepo with
projects: ['packages/*'] listed every test under "". Vitest (3.2, 4.1 and 5)
names such a project after the name in the package.json of its folder, else
the folder, and an inline project after its index among the inline entries.
Vitest 5 also drops test.name from what an inline project inherits, and
names the projects of a config another config lists inside that config's
name, as "web (unit)"; Vitest 5.0.0 already does both, so the rules apply
to all of Vitest 5.
Two existing expectations were wrong for the runner: a folder project
without a package.json is named after the folder ("quotes", as Vitest
3.2.7, 4.1.12 and 5.0.5 name packages/quotes in a real run), and an
unnamed inline browser project at index 3 has its instance named
"3 (firefox)" (resolveTestConfig names instances after the resolved
project name; not run, as browser mode needs a provider).
Chose: keep "" for an unnamed root config's own project over the
package.json name, as Vitest names only the projects a config lists; and
keep the names a nested config has when started there for its configs
entry, over the names the listing config gives, as that entry describes
the config run on its own.
defineConfig(base, config) was read as a shallow spread, so a config that
overrides one project of its base lost the others: with base projects
setup and chromium (dependencies: ['setup']) and an override of chromium
only, the scan listed chromium alone, without its dependency, where
Playwright 1.64.1 lists [setup] and [chromium]. An override's
expect: { timeout } also dropped the base's toHaveScreenshot.pathTemplate,
so tests comparing screenshots lost hasUnfollowedDependencies.
Playwright's defineConfig (lib/common/index.js in 1.64.1) merges expect,
use and build key by key and projects by name, the project's use key by
key, adds the other projects after the base's, keeping the last of each
name, and takes the first config as it is. The scan now does the same.
Chose: leave webServer as replaced, over joining its lists, as the scan
doesn't read it; and take the projects as unknown when a project's name
can't be worked out, over guessing which ones merge.
Playwright 1.64 added testProject.default: without --project, a project with default: false doesn't run, unless a project that runs lists it in dependencies or teardown (filterProjects and buildProjectsClosure in 1.64.1). The scan ignored it and listed such a project in the projects of its tests: with a slow project set to default: false, npx playwright test --list lists no test of it, and the scan gave ["slow"]. The scan now leaves such a project out from 1.64, and keeps the ones that running projects pull in through dependencies or teardown, transitively. Chose: take a project that runs by default as pulling in its dependencies even when none of its tests are scanned, over working out whether it has tests, which Playwright checks but a per-file scan can't; and give projects: null with a config-value-unknown warning for a default the scan can't work out, over taking the project as running.
Playwright (1.57 and later) gives every test the config's top-level tag: loadTestFile starts each file suite with config.tags, so the tag is first in test.tags and in the text grep and grepInvert match, right after the file path. The scan ignored it: with tag: '@nightly', npx playwright test --list in 1.64.1 gives "returns a quote for AAPL" the tags ["@nightly"], where the scan gave [], and a project with grepInvert: /@nightly/ runs no test, where the scan listed it. The tag now comes first in each test's tags and in the grep text. When it can't be worked out, the tests of a project with a grep get projects: null and the config a config-value-unknown warning where it writes tag. src/records.ts takes the tags from FileConfig.configTags, as the config's placement is what knows them. Chose: leave out a tag the scan can't work out, over keeping its known items, as part of a list is no truer than none; and report it only where the config writes tag, over also where unknown code may set it, which the config's projects already report.
The nesting check read the slash of '</ul>' and of '} />' as the start of a regular expression, which then ran over the closing brackets after it, so each component left braces open and a file of 500 small components got a parse-error that oxc doesn't give. A slash after '<', or before '>' right after a '}', now ends a tag. Over the 173 .tsx and .jsx files at hand the deepest nesting is 8.
When a running Playwright project's dependencies or teardown can't be worked out, as in dependencies: process.env.CI ? ['setup'] : [], a project with default: false was taken as not running, so its tests had no projects and a wrong file-not-run warning; Playwright 1.64.1 runs it when the list names it. Such a list now leaves the other projects unknown, with a config-value-unknown warning at the setting.
A test.name such as process.env.DESK fell back to the project's folder or index without a sign, a plausible name that Vitest doesn't give (4.1.12 and 5.0.0 name it after the variable's value). It now gets a config-value-unknown warning that says what the scan names it instead.
Huldoser
added a commit
that referenced
this pull request
Oct 30, 2026
## What changed Release 0.6.1, made with `npm run release`. ### Patch Changes - [#69](#69) [`41ce74d`](41ce74d) - An import that matches a tsconfig `paths` pattern but none of its files, as an installed package does with `"paths": { "*": ["./src/types/*"] }`, now goes on to the usual lookup, as TypeScript does. Such imports were reported as unreadable, so every test that used a package or a built-in module such as `node:fs` got `hasUnfollowedDependencies`. An `@/` or `~/` alias whose files are missing is still reported. - [#69](#69) [`41ce74d`](41ce74d) - A file that only passes names on, as a barrel `index.ts` with `export * from './fees'` or `export { OrderTicket } from './order-ticket'` does, is now in the `dependencies` of the tests that import through it, so `--related` keeps them when the file changes. Which module a re-export points at is now part of `dependencyHash`, as an import's already was, so the hash of a test that imports through a re-export changes once. - [#69](#69) [`41ce74d`](41ce74d) - An imported file that isn't text, such as `import logo from './logo.png'`, is now taken by its bytes in `dependencyHash`. It was read as text, where every byte that isn't UTF-8 reads the same, so two different images could give the same hash. - [#69](#69) [`41ce74d`](41ce74d) - A `<script>` block in a Vue or Svelte component whose `type` isn't JavaScript, such as `<script type="application/ld+json">` in `<svelte:head>`, is no longer read as the component's code. It made the component unreadable, so the modules its real script imports were missing from `dependencies`. - [#68](#68) [`fa9b97a`](fa9b97a) - `dependencyHash` now follows what a Vue or Svelte single-file component imports, also the components and helpers only its template uses, so a change to a child component changes the hash of the tests that mount its parent. A component whose script doesn't parse is still taken by its text, and its tests get `hasUnfollowedDependencies`. - [#69](#69) [`41ce74d`](41ce74d) - A file with brackets nested more than 500 deep, such as a generated fixture, now gets a `parse-error` instead of crashing the scan. oxc-parser overflows the native stack on such a file, about 5,000 deep, and the process dies with a segmentation fault that no error handling can catch, so no inventory was written. - [#69](#69) [`41ce74d`](41ce74d) - `expectCount` now counts `expect.element()`, the assertion of Vitest browser mode, so a browser test that checks the page only with `await expect.element(locator).toBeVisible()` no longer looks like a test without assertions. - [#69](#69) [`41ce74d`](41ce74d) - An import written with a `.jsx` extension now finds the `.tsx` file it refers to, as `.js` already found `.ts`, so `import { OrderHeader } from './OrderHeader.jsx'` is followed instead of marking the test with `hasUnfollowedDependencies`. - [#69](#69) [`41ce74d`](41ce74d) - From Playwright 1.57, the config's `tag` is now read: every test gets it first in its `tags`, and a project's `grep` or `grepInvert` matches it, so a project with `grepInvert: /@nightly/` in a config with `tag: '@nightly'` no longer runs the tests. When the scan can't work out the `tag`, as for `process.env.CI_ENVIRONMENT_NAME`, it gets a `config-value-unknown` warning, and the tests of a project with a `grep` get `projects: null`. - [#69](#69) [`41ce74d`](41ce74d) - From Playwright 1.64, a project with `default: false` is no longer listed in a test's `projects`, as Playwright runs it only with `--project`, unless a project that runs lists it in `dependencies` or `teardown`. A `default` the scan can't work out gets a `config-value-unknown` warning, and the tests it may run get `projects: null`. - [#69](#69) [`41ce74d`](41ce74d) - Playwright's `defineConfig(base, config)` is now merged as Playwright merges it: a project of `config` merges into the project of `base` with the same name instead of replacing all of `base`'s projects, and `expect`, `use` and `build` merge key by key. A setup project from a base config is no longer lost, and an `expect.toHaveScreenshot.pathTemplate` from the base still marks the tests that compare screenshots with `hasUnfollowedDependencies`. - [#69](#69) [`41ce74d`](41ce74d) - A Vitest `retry` object in a test's or describe's options before Vitest 4.1 now gets an `unresolved-option` warning and `retries: null`. Vitest 4.0 and 3.2 never run such a test; the scan used to ignore the object and give the config's retry count instead. - [#69](#69) [`41ce74d`](41ce74d) - A Vitest test's own `skip`, `only`, `todo` or `fails` option now replaces the one its tags set, as Vitest does, so `test('title', { tags: ['flaky'], skip: false }, fn)` is active instead of skipped, and a conditional `skip` replaces the tag's unconditional one. - [#69](#69) [`41ce74d`](41ce74d) - An import that starts with `@/` or `~/`, as a Vite `resolve.alias` allows, now gives the test `hasUnfollowedDependencies` and a `dependency-not-followed` warning when no tsconfig `paths` define it. It was taken for an installed package, so the aliased file was missing from `dependencies` without a sign, and `--related` left the test out when that file changed. - [#69](#69) [`41ce74d`](41ce74d) - Vitest 5 tests follow the option rules of Vitest 5.0.5: the last of `.skip`, `skipIf()` and `runIf()` on a test decides its skip, so `test.skip.skipIf(false)` is active, and a `skipIf()` or `runIf()` replaces the skip of a tag or of the options a describe passes down. An option set to `undefined`, as in `test.skip('title', { skip: undefined }, fn)`, no longer undoes the chain or a tag. - [#69](#69) [`41ce74d`](41ce74d) - In Vitest 5, the projects of a config that another config lists in `projects` are named inside that config's name, as Vitest names them: the `unit` project of `apps/web/vitest.config.ts`, named `web`, is `web (unit)`. - [#69](#69) [`41ce74d`](41ce74d) - A Vitest project without a `test.name` now has the name Vitest gives it, in `projects` and in `configs`: the `name` in the package.json of its folder, else the folder's name, so `projects: ['packages/*']` names its projects `@trading/pricing` or `ledger` instead of `""`. An inline project without a name is named after its place among the inline projects, as `0`, and in Vitest 5 no longer takes the name of the config it extends. ## Checklist - [ ] Tests cover the change, and `npm run coverage` reports 100%. - [ ] The docs describe the change: the guide or reference pages, the README, or the TSDoc of exported functions. - [x] No docs change is needed. - [x] A changeset describes the change for users (`npm run changeset`), or the change does not affect them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug fixes for 0.6.1, found in a round of runner comparisons (Playwright 1.64.1, Vitest 5.0.5, 4.1.12, 4.0.18 and 3.2.x), a review of the 0.5.0 and 0.6.0 code, and the upstream release notes. One commit and one changeset per fix; each fix has a test that failed first.
Test states (Vitest)
skip,only,todoorfailsoption replaces its tag's, also when set tofalse.retryobject before Vitest 4.1 gets anunresolved-optionwarning andretries: null: Vitest 4.0 and 3.2 never run such a test..skip,skipIf()andrunIf()decides, askipIf()orrunIf()replaces the skip of a tag or of a describe's options, and an option set toundefinedchanges nothing.dependencyHash, dependencies and --related
index.tsdoes, are independencies, so--relatedkeeps their tests.\nunless git takes it for binary.pathspattern with no matching file falls back to the usual lookup, as TypeScript does..jsximport finds its.tsxfile.<script>block whosetypeisn't JavaScript, such as JSON-LD, isn't read as code.@/or~/import that no tsconfigpathsdefine marks the test unfollowed, with a warning.Configs
test.nameare named as Vitest names them; Vitest 5 names a listed config's projects inside its name, asweb (unit). An unknowntest.namegets a warning.defineConfig(base, config)mergesprojectsby name anduse,expectandbuildby key.default: falseare left out unless a running project pulls them in; unknowndependenciesleave that open, with a warning.tagis read.Parsing
parse-errorinstead of crashing the scan in oxc-parser (parser: uncatchable SIGSEGV in napiparseSyncon deeply nested expressions — kills the host Node.js process oxc-project/oxc#24375).expect.element()counts inexpectCount.Checks:
npm run coveragepasses, 1,033 tests and 100% for every file insrc/; typecheck, lint and format are clean. Golden changes:dependencyHashin trading-web (itsexport { expect } from '@playwright/test'), a warning in the Vitest 3.0 and 4.0 syntax tours, and oneexpectCountin chart-browser.