Skip to content

Commit 61bf477

Browse files
authored
Replace Karma with Vitest and finish the Rollup removal (#42764)
* Add "llms" to the cspell dictionary The llms.txt work fails the spellcheck on a word that is now part of the project vocabulary. Unblocks #42728 * Raise browser coverage and derive the build targets from browserslist The `.browserslistrc` comment claimed about 90.8% coverage, but `npx browserslist --coverage` measured 77.53%. Two things caused the gap. The `>=` lines are joined with OR, so they never acted as floors, and caniuse gives the newest Safari a null release date, which hides roughly 8.2 points of real usage from every range query. Adding `unreleased versions` recovers that Safari share at zero output cost, because it is newer than every floor. Lowering the floors to the true native `light-dark()` boundary reaches about 88% for 1.68 kB of CSS, all of it `-webkit-backdrop-filter`. We stop there on purpose: the next rung up drops below native `light-dark()`, where Lightning CSS falls back to the custom-property polyfill that broke `data-bs-theme`. Dropping and_uc, and_qq and kaios costs 0.718% and removes the only reason Autoprefixer emitted `-webkit-mask-*` and `-moz-column-gap`, so `removeRedundantPrefixes` now only trims the prefixed `transition` declarations that come from the prefixed thumb selectors. With the list reduced to an explainable set, `build/browser-targets.mjs` can resolve it at build time instead of restating the floors by hand. * Remove the dead BrowserStack integration The workflow has been `disabled_manually` with zero runs since about October 2024, so this has not tested anything for well over a year. The browser matrix pinned Chrome 60, Firefox 60, iOS 12 and Android 8, all far below the v6 floors, so re-enabling it would only fail on modern syntax. Dropping the launcher also removes 34 packages from the lockfile, including the legacy tunnel wrapper and its unzip stack. The README credit goes too, since we no longer use the service. `ip` and `karma-jasmine-html-reporter` stay: `npm run js-debug` still needs both. * Port the integration test bundles to Rolldown Both configs bundle the already-built dist, so no TypeScript is involved and the switch is mechanical. `@rollup/plugin-replace` becomes `transform.define`, and node resolution is built in, so the plugin stack disappears. They now share `build/browser-targets.mjs` with the dist build instead of carrying their own Babel targets. Also drops the `js/tests/integration/rollup*.js` glob from the ESLint config. It never matched anything, because the files are `.mjs`. * Replace Karma with Vitest browser mode Vitest 4 ships browser mode as stable, so the specs now run in a real Chromium through Playwright. The suite drops from about 25 seconds to under 6, and the runner needs no Rollup or Babel stack of its own. The specs are written against Jasmine, so `js/tests/vitest-setup.js` maps that API onto Vitest rather than rewriting roughly 22,000 lines. The one behaviour that cannot be codemodded is `spyOn`: Jasmine stubs the method, Vitest calls through, and 92 call sites here rely on the stub. A blind conversion would leave tests that pass while asserting nothing, so the shim stubs by default and restores the real method only for `.and.callThrough()`. hammer-simulator assigns to `event.target`, which throws in the strict mode a module gets, so it runs through a Function constructor to keep the sloppy-mode behaviour Karma gave it. Coverage now instruments all of `js/src` instead of only the files a spec imports. That is a larger and more honest denominator, and it immediately showed `combobox.ts` at 0%, so this adds the missing spec. The branch threshold moves from 89 to 88 to match the new denominator; statements, functions and lines all still clear 90. Vitest 4 also removed the `done` callback, so the four remaining callback-style specs now return a promise. * Remove Rollup and Babel Karma was the last thing using them. With the specs on Vitest and the dist builds and integration bundles on Rolldown, nothing reads `.babelrc.mjs` or the Rollup TypeScript resolver any more, so both files and all nine packages go. Rollup and Babel still appear in the lockfile as transitive dependencies, because Vite bundles Rollup and the Istanbul coverage provider instruments through Babel. Neither is ours to configure now. Also records the deliberate `rolldown` pin. It is exact-pinned while still release-candidate software, so Dependabot should not move it without review. * Replace the aria and datepicker type casts with real types Nine `as unknown as` casts and seven `as any` casts hid two different problems behind the same syntax. Seven of the `as unknown as` casts passed a boolean to `setAttribute`, which takes a string. A `setAriaAttribute` helper does that conversion in one place, so callers no longer reach for a cast. The two that remain are unrelated and stay: one writes to `element.style`, the other is a not-set config sentinel. The datepicker casts were bridging Bootstrap's wide `string` and `number` config types to the narrow literal unions Vanilla Calendar Pro actually accepts. Narrowing `DatepickerConfig` to those unions removes all seven and turns an out-of-range value into a compile error instead of a silent no-op inside the calendar. One assertion survives, where `getMonth()` returns a number TypeScript cannot prove is 0-11, but it now names the type it narrows to. Also deletes the stale `selector-engine.ts` TODO. It claims `next()` is unused, but `prev()` and `next()` have three call sites each across `menu.ts`, `scrollspy.ts` and `combobox.ts`, and both are tested. * Make Tab throw when its element has no tab-panel ancestor The constructor carried a TODO saying it should throw in v6, and this is v6. Until now it returned early and handed back an instance that looked fine but did nothing, so a markup mistake produced tabs that silently refused to switch. This is a breaking change for anyone constructing a Tab outside a `.list-group`, `.nav` or `[role="tablist"]` container. The old test asserted the silent return by name, so it now asserts the throw instead. * Cover the remaining plugins in the type-level API tests `api.ts` and `consumer.ts` only exercised 11 of the 20 plugins, so the public surface of the other nine was never asserted. Both files now cover all of them. The datepicker assertions are the point of this. Four negative cases check that the narrowed Vanilla Calendar Pro unions reject out-of-range values such as `firstWeekday: 7` or `displayMonthsCount: 13`. Extending `consumer.ts` matters more than `api.ts` here, because it reads the emitted declarations and so proves the unions survive the `.d.ts` emit rather than widening on the way out. Also adds a Dialog case for a prevented `show` event, which was an uncovered branch. * Document the Vitest and Rolldown switch `js/tests/README.md` still described Karma and Jasmine, and its paths said `tests/unit/` where the real path is `js/tests/unit/`. It now covers Vitest browser mode and calls out the two things that will catch a contributor out: `spyOn` stubs by default unlike `vi.spyOn`, and Vitest 4 dropped the `done` callback. The coverage section explains why the branch floor sits at 88 and why an untested plugin now reports 0% instead of vanishing from the report. `contribute.mdx` listed Sass, PostCSS and terser but named no JavaScript bundler at all, so it gains a section on Rolldown and on how `build/browser-targets.mjs` keeps both pipelines on one browser list. `migration.mdx` gets the same switch from the consumer's angle, since it only matters to people who build from source. `AGENTS.md` is updated to match. * rebuild * Exclude the type-assertion tests from CodeQL `js/tests/types/` holds compile-time assertions. `tsc` checks them and nothing ever runs or bundles them, so the quality suite's unused-variable query fires on essentially every line: the typed declaration is the assertion, and using the variable afterwards would add nothing. The files were already full of these before this branch. They only became visible because CodeQL annotates changed lines on a pull request, so extending the coverage to all 20 plugins lit up 40 notices for a pattern that was already there. * Update the browser support docs for the new floors This branch lowered the browserslist floors, but the browsers and devices page still reproduced the old ones. It listed Chrome and Edge 130, Firefox 132, and Safari 18, where the real minimums are now Chrome and Edge 123, Firefox 120, and Safari 17.5. The rationale was stale in two more ways. It claimed the floors covered roughly 90% of users, a figure that came from browserslist.dev and its pinned caniuse data; `npx browserslist --coverage` reports 87%. It also said we could not lower the floors, which this branch did. Each floor now sits on the first version of that engine with native `light-dark()`, so the real constraint is the polyfill that breaks `data-bs-theme`. Also note that browserslist ORs its lines, because the page implied `last 2 major versions` was the whole policy, and record that we now exclude UC Browser, QQ Browser, and KaiOS. * Raise the browser floors to Chrome 130, Firefox 132, and Safari 18 A floor applies to a whole major line. We cannot raise it in a minor release, so whatever 6.0 ships caps what every later 6.x feature may use. Earlier in this branch I set the floors on the first versions with native `light-dark()`, which bought 2.18 points of coverage but left v6.x with the oldest baseline we could tolerate rather than the one we want. The low Firefox floor was also wrong on its own terms. `:has()` needs Firefox 121, so Firefox 120 sat inside our support window and could not render our forms, which use `:has()` 28 times in validation alone. The new floors sit on the newest feature v6 already depends on: `:has()` at Firefox 121, `content-visibility` at Firefox 125 and Safari 18, `<details name>` at Firefox 130, `text-wrap: balance` at Chrome 130, and unprefixed `backdrop-filter` at Safari 18. `light-dark()` stays the hard lower bound. Coverage drops from 87.19% to 85.01%. The JavaScript is byte-identical, because every version in both sets handles the syntax we emit. The CSS loses the `-webkit-backdrop-filter` copies on the Dialog and Drawer backdrops, so it fits under the old bundlewatch limit again and this branch no longer needs to raise it. * Record what the browser floors buy us The file already explained which features set each floor. It did not say what the floors give us in return, so the cost was documented and the benefit was not. Anyone reading it could see why we cannot go lower without learning what they may safely use. Add two lists. The first names the features that need no fallback, prefix, or polyfill in any 6.x release. The second names the ones we still cannot use, with the version that blocks each. Firefox is the blocker in almost every case. Relative color syntax is the closest at Firefox 133, and anchor positioning is the one worth waiting for, because it would let Menu, Tooltip, Popover, and Combobox drop Floating UI. Every version here comes from the caniuse-lite copy in this repo, checked against the current floors. Comments only, so the resolved target list and the build output do not change. * Document the raised browser floors for upgraders The migration guide never mentioned browser support. That was the largest gap in it, because v5 supported Chrome and Firefox 60 and Safari 12, while v6 now needs Chrome and Edge 130, Firefox 132, and Safari 18. A reader with an older audience cannot adopt v6 at all, and nothing in the guide said so. Add a warning callout to the upgrade steps rather than a changelog bullet, because this is a prerequisite to check before starting, not a change to apply while working. It links to the browsers and devices page for the reason behind each version. The approach page embeds `.browserslistrc` in full, so the rationale comments added in the last commit now render there ahead of the queries. Point that page at the summary table, so a reader who only wants the versions does not have to scroll the comments. * Move the browserslist commentary into the docs The rationale had grown to roughly 50 comment lines, and the approach page embeds this file in full, so all of it rendered there ahead of the queries it was meant to explain. Prose belongs on a page that can format it. Move the parts the docs did not already cover onto the browsers and devices page: why `last 2 major versions` carries our mobile coverage, why `unreleased versions` is worth eight points, how to measure coverage and why not to trust browserslist.dev, which features the floors do and do not give us, and which prefixes the excluded engines would add. Leave behind a pointer to that page and the two rules a person editing the file must not get wrong. Both pages now read the query lines out of the file through `fileMatch` instead of hand-copying them. That is what drifted in the first place: the page still claimed Chrome 130 and Safari 18 after the floors briefly moved to 123 and 17.5. The regex is anchored on the first and last query, so a restructure fails the docs build rather than shipping stale versions. The resolved target list, the derived oxc targets, and the build output are all unchanged.
1 parent 15cf3dc commit 61bf477

82 files changed

Lines changed: 8866 additions & 11788 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.babelrc.mjs

Lines changed: 0 additions & 27 deletions
This file was deleted.

.browserslistrc

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
# https://github.com/browserslist/browserslist#readme
2+
#
3+
# Read the docs before you change anything here. The support policy, the reason
4+
# behind each floor, the features these floors do and do not give us, and how to
5+
# measure coverage all live in one place:
6+
# https://getbootstrap.com/docs/6.0/getting-started/browsers-devices/
7+
#
8+
# Two rules for editing this file:
9+
# 1. Never set a floor below `light-dark()`: Chrome 123, Firefox 120,
10+
# Safari 17.5. Lightning CSS lowers `light-dark()` to a custom-property
11+
# polyfill below those versions, and the polyfill breaks `data-bs-theme`.
12+
# 2. Run `npx browserslist --coverage` after any change, then update the
13+
# versions and the coverage figure on the docs page above.
214

315
last 2 major versions
416
not dead
5-
# Floors kept on versions with native CSS `light-dark()` (and nesting) so
6-
# Lightning CSS doesn't lower them to the brittle custom-property polyfill that
7-
# broke `data-bs-theme` dark mode. Raised as far as we can while staying above
8-
# 90% global coverage (~90.8%).
17+
unreleased versions
18+
919
Chrome >= 130
1020
Edge >= 130
1121
Firefox >= 132
1222
iOS >= 18.0
1323
Safari >= 18.0
14-
not kaios <= 2.5 # fix floating label issues in Firefox (see https://github.com/postcss/autoprefixer/issues/1533)
15-
not op_mini all # proxy browser we don't target; only source of redundant -o-object-fit prefixes
24+
25+
not and_uc > 0
26+
not and_qq > 0
27+
not kaios > 0
28+
not op_mini all

.cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"lightboxes",
108108
"lightningcss",
109109
"linkinator",
110+
"llms",
110111
"longdesc",
111112
"Lowercased",
112113
"markdownify",

.github/codeql/codeql-config.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
name: "CodeQL config"
22
paths-ignore:
33
- dist
4+
# Compile-time type assertions. `tsc` checks these files; nothing ever runs or
5+
# bundles them. Each assertion is a typed declaration that is unused by design,
6+
# so the quality suite reports every line as an unused variable.
7+
- js/tests/types

.github/dependabot.yml

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,13 @@ updates:
2828
versioning-strategy: increase
2929
rebase-strategy: disabled
3030
ignore:
31-
- dependency-name: "@babel/cli"
32-
update-types:
33-
- "version-update:semver-major"
34-
- dependency-name: "@babel/core"
35-
update-types:
36-
- "version-update:semver-major"
37-
- dependency-name: "@babel/preset-env"
38-
update-types:
39-
- "version-update:semver-major"
40-
- dependency-name: "karma-browserstack-launcher"
41-
update-types:
42-
- "version-update:semver-major"
43-
- "version-update:semver-minor"
44-
- dependency-name: "karma-rollup-preprocessor"
31+
# Rolldown is pinned exactly on purpose while it is still release-candidate
32+
# software. Review each bump by hand.
33+
- dependency-name: "rolldown"
4534
update-types:
4635
- "version-update:semver-major"
4736
- "version-update:semver-minor"
37+
- "version-update:semver-patch"
4838
- dependency-name: "stylelint"
4939
update-types:
5040
- "version-update:semver-major"

.github/workflows/browserstack.yml

Lines changed: 0 additions & 45 deletions
This file was deleted.

.github/workflows/js.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ jobs:
3838
- name: Install npm dependencies
3939
run: npm ci
4040

41+
- name: Install Playwright Chromium
42+
run: npx playwright install --with-deps chromium
43+
4144
- name: Run dist
4245
run: npm run js
4346

.ncurc.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
{
22
"reject": [
3-
"@babel/cli",
4-
"@babel/core",
5-
"@babel/preset-env",
63
"hammer-simulator",
7-
"karma-browserstack-launcher",
8-
"karma-rollup-preprocessor",
4+
"rolldown",
95
"stylelint"
106
]
117
}

AGENTS.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,17 @@ Act as a teacher for JavaScript, TypeScript, React, and all JavaScript-family to
5151
## JavaScript/TypeScript conventions
5252

5353
- Source is TypeScript (`js/src/**/*.ts`, entry `js/src/index.ts`); ESM-only, no semicolons, 2-space indent
54-
- Imports use `.js` extensions (standard TS ESM style); Rolldown resolves them to `.ts` via `resolve.extensionAlias`, Karma via `build/rollup-plugin-ts-resolve.cjs`
55-
- Dist builds use Rolldown (`build/rolldown.config.mjs`, `build/build-plugins.mjs`) with built-in TS transforms; browser floors live in `build/browser-targets.mjs` (keep in sync with `.browserslistrc`). Karma still bundles specs with Rollup + Babel (`@babel/preset-typescript`)
54+
- Imports use `.js` extensions (standard TS ESM style); Rolldown resolves them to `.ts` via `resolve.extensionAlias`, Vitest via the `tsResolve` plugin in `js/tests/vitest.config.mts`
55+
- Dist builds use Rolldown (`build/rolldown.config.mjs`, `build/build-plugins.mjs`) with built-in TS transforms; `build/browser-targets.mjs` derives the browser floors from `.browserslistrc` at build time, so there is nothing to keep in sync. Rollup and Babel are gone from the repo — Vitest transforms the specs itself
5656
- Strict tsconfig with `verbatimModuleSyntax` + `erasableSyntaxOnly`; `tsc` only type-checks and emits `.d.ts` (`npm run js-typecheck`, `npm run js-emit-types`)
5757
- Components extend `BaseComponent` (which extends `Config`); per-component config `type XxxConfig` typed off `Default`, refined on the class via `protected declare _config: XxxConfig`
5858
- Instance fields use `declare` (no runtime emit); constructor `element` param stays optional to keep `typeof Component` assignable to `typeof BaseComponent`
5959
- Every `_`-prefixed instance member is `protected` (never `private`, so subclasses keep access); public API methods and statics stay public. This keeps the internals out of the published `.d.ts` surface
6060
- Constants: `NAME`, `DATA_KEY`, `EVENT_KEY`, `VERSION`
6161
- DOM utilities via `dom/event-handler.ts`, `dom/selector-engine.ts`, `dom/manipulator.ts`
6262
- Floating UI for positioning (dropdown, tooltip, popover)
63-
- Tests: Jasmine + Karma, specs in `js/tests/unit/*.spec.js` (plain JS, bundled against the TS sources); type-level API tests in `js/tests/types/``api.ts` checks the source (`npm run js-typecheck`), `consumer.ts` checks the shipped `js/dist/*.d.ts` through the package `exports` map (`npm run js-typecheck-dist`, after the build)
63+
- Tests: Vitest browser mode (real Chromium via Playwright), config in `js/tests/vitest.config.mts`, specs in `js/tests/unit/*.spec.js` (plain JS, bundled against the TS sources). `js/tests/vitest-setup.js` maps the Jasmine API the specs are written against onto Vitest — note `spyOn` stubs by default, unlike `vi.spyOn`. Async specs return a promise; Vitest 4 dropped `done`. Coverage is Istanbul over all of `js/src`, thresholds 90/88/90/90
64+
- Type-level API tests in `js/tests/types/``api.ts` checks the source (`npm run js-typecheck`), `consumer.ts` checks the shipped `js/dist/*.d.ts` through the package `exports` map (`npm run js-typecheck-dist`, after the build). Keep the two in sync
6465

6566
## Docs conventions
6667

README.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,6 @@ See [the Releases section of our GitHub project](https://github.com/twbs/bootstr
173173

174174
## Thanks
175175

176-
<a href="https://www.browserstack.com/">
177-
<img src="https://live.browserstack.com/images/opensource/browserstack-logo.svg" alt="BrowserStack" width="192" height="42">
178-
</a>
179-
180-
Thanks to [BrowserStack](https://www.browserstack.com/) for providing the infrastructure that allows us to test in real browsers!
181-
182176
<a href="https://www.netlify.com/">
183177
<img src="https://www.netlify.com/v3/img/components/full-logo-light.svg" alt="Netlify" width="147" height="40">
184178
</a>

0 commit comments

Comments
 (0)