Skip to content

refactor(v4): migrate the spec suite onto the shipped test module - #864

Merged
titouanmathis merged 6 commits into
mainfrom
refactor/v4-specs-on-test-module
Aug 24, 2026
Merged

refactor(v4): migrate the spec suite onto the shipped test module#864
titouanmathis merged 6 commits into
mainfrom
refactor/v4-specs-on-test-module

Conversation

@titouanmathis

Copy link
Copy Markdown
Contributor

Moves the v4 spec suite onto @studiometa/js-toolkit-v4/test (packages/v4/src/test/index.ts, merged in #862) and deletes the helpers the specs had hand-rolled before it existed.

1558 tests, 114 files, green at every commit — the same count as main, so nothing was dropped.

What was deleted, and what replaced it

Deleted Count Replaced by
settle / frames / resetDom re-exported from src/test-utils.ts 74 importing files the same three from src/test/index.js
identical async function render(html) copies 12 mount()
"loop N times over settle()" helpers (observed, settled, ticked, included) 15 waitFor(), or a renamed quiet() where an absence is asserted
waitForClass() copies 3 waitFor()
vi.spyOn(console, 'warn') sites 34 of 41 captureDiagnostics()
local event recorders 6 recordEvents()
recordDiagnostics() in Track.spec.ts 1 captureDiagnostics()

src/test-utils.ts is down to getInstance and countRequestedFrames. The todo component tree moved to a new src/todo.fixtures.ts, where the registerComponent(TodoList) side effect is visible in the filename.

Three assertions that were passing for the wrong reason

mockRestore() clears call history as well as restoring the implementation, so expect(warn).not.toHaveBeenCalled() after a restore is always true. Three sites in src/decorators.spec.ts had exactly that shape:

  • merges a static config field on the same class instead of dropping onewarn.mockRestore() ran before expect(warn).not.toHaveBeenCalled()
  • unions refs declared on both sides — same
  • merges both declarations on a decorated subclass of a decorated class — same

All three now read expect(log.codes).toEqual([]) with the capture still open, and all three pass for real: declaring the same name on both sides genuinely reports nothing, and refs genuinely union without a conflict. The behaviour they meant to assert is correct — they simply were not asserting it.

The swap and the fix could not be separated into two commits: replacing mockRestore() (clears history) with log.stop() (does not) is the fix, and there is no intermediate state that both compiles and stays vacuous. They are in b362fea, at src/decorators.spec.ts lines 514, 562 and 635 of the pre-change file.

One dead silencer, and the product behaviour behind it

Track.spec.tsfalls back to an empty payload when 'data-option-payload' is invalid JSON spied on console.warn and never asserted on it. Under captureDiagnostics() the channel turns out to be completely silent for that case, unlike its two sibling tests which do report track.invalid-json.

The reason is deliberate and documented in the framework: readJSON() in src/Base.ts swallows a malformed option attribute and hands back the declared default ("Unparsable JSON is not a failure either"), so AbstractTrack.optionPayload's own try/catch never runs on the attribute path. No product change made — the asymmetry is pre-existing and intentional at the Base layer. The test now asserts expect(log.codes).toEqual([]) with a comment naming the cause.

Waits: waitFor where there is a condition, quiet() where there is not

The loop helpers existed because observer delivery is not synchronous. Every positive wait is now a waitFor() poll for the exact state the assertion reads — deterministic, and it fails with a useful message instead of a stale read.

An absence cannot be polled for: the predicate is true before anything has happened. Those sites keep the bounded loop, renamed quiet() with a comment. That is the same asymmetry REPORT.md:900 records for transition classes, and the three waitForClass conversions follow it exactly — the positive assertions became polls, the negative one (AnchorNav, "the class is gone after leaveTransition()") stayed a direct assertion.

Three waits had to move to a later signal than the one they named, because the value lands a scheduler lane before the DOM write it feeds:

  • Draggable — poll the written transform, not props.dampedX
  • Carousel — poll --carousel-progress, not carousel.progress
  • smoothTowaitFor(() => !motion.isMoving) replaces a 300-frame bounded loop

Deliberately left alone

  • getInstance and its 187 call sites. It reads the raw INSTANCES symbol with no $isMounted filter; the public getInstances() filters. Not interchangeable.
  • Seven console.warn spies, because each one asserts about the sink, not about a diagnostic: three in diagnostics.spec.ts, the $warn/$error sink pair in Base.spec.ts, negotiated-events.spec.ts's "cancelling suppresses the sink", and the test module's own spec. captureDiagnostics() cancels every event it sees, which would make all seven vacuous.
  • The three uniqueGroup counters. resetRegistry() is the obvious replacement and the wrong one: the state that survives resetDom() in migration/Data/* is the page-wide DataRegistry provided on the root context, whose group records keep their values after the elements are gone. resetRegistry() clears the component registry and never touches it. Documented in 5011010 so the next reader does not swap one for the other.
  • The four Slider* ready() helpers, which compose the shipped settle/frames with per-file instance lookups rather than re-implement a wait.
  • Three sync render(html) helpers (coexistence, context-subscription, responsive-options) that deliberately do not settle: their call sites inspect the DOM before mount, and context-subscription renders no components at all, so mount() would add 5×10ms per call for nothing.

Where the shipped helper did not fit

  • captureDiagnostics() does not expose the event target. Base.spec.ts keeps one hand-rolled listener for the test that asserts a diagnostic started on the component's element (the property that lets a subtree-scoped listener filter). Everything else in that describe block converted.
  • mount() returns the wrapper. TrackEvent.spec.ts and withTransition.spec.ts assert on the component's own element, so each keeps a one-line wrapper that unwraps .firstElementChild. The duplicated DOM-building and settling is gone from both.

Not done here

Twenty-three spec files still attach a diagnostic listener by hand outside a console.warn spy. Those are the same captureDiagnostics() opportunity and are out of this PR's scope.

Verification

Run in packages/v4 unless noted. check:package needs the main checkout's node_modules symlinked into the worktree root; the symlink was removed afterwards and the tree is clean.

npm run test               114 files, 1558 tests passed, exit 0
npm run lint:types         exit 0
npm run check:diagnostics  sinks are centralized and internal code references are tree-shakeable
npm run build              Building 260 modules... Done building!
npm run check:package      588 files, 293.6 kB packed — node / TypeScript / browser packed consumers all passed
npm run subpaths:check     packages/js-toolkit and packages/v4 up to date
npm run lint:fmt (root)    All matched files use the correct format
npx oxlint --type-aware packages/v4   no new warnings

No build or packaging change was needed, as expected: scripts/build.js already excludes !test-utils.ts and **/*.fixtures.ts, and check-package.js already guards both. Verified rather than assumed — dist/ contains neither test-utils nor todo.fixtures.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NnepYhqjPMcFCSb43PoHRM

titouanmathis and others added 6 commits August 24, 2026 15:52
…module

The specs predate `src/test/index.ts` and imported the same three helpers
from `src/test-utils.ts`. Point all 74 of them at the shipped module
instead, and delete the twelve hand-rolled `render(html)` copies that
`mount()` now covers.

`TrackEvent.spec.ts` and `withTransition.spec.ts` keep a one-line local
wrapper, because both assert on the component's own element rather than
on the wrapper `mount()` returns.

`getInstance` and `countRequestedFrames` still come from `test-utils.ts`:
`getInstance` reads the raw instances map with no `$isMounted` filter, so
it is not the public `getInstances`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnepYhqjPMcFCSb43PoHRM
…ver settle

Fifteen files carried a local `observed()`, `settled()`, `ticked()` or
`included()` — a loop over `settle()` sized by guesswork, because an
observer's delivery and a frame-driven write land on nobody's promise.
Where the spec waits for a *condition*, `waitFor()` now polls for that
exact condition and the wait is deterministic. The three hand-copied
`waitForClass()` bodies go the same way, since a class landing is just
another condition.

Where the assertion is an *absence* — nothing emitted, nothing imported,
nothing mounted, a scroll that never moved — the loop survives under the
name `quiet()`, with a comment saying why. An absence cannot be polled
for: the predicate is true before anything has happened.

Three waits had to move to a later signal than the one they named,
because the value lands a scheduler lane before the DOM write it feeds:
`Draggable`'s transform, `Carousel`'s `--carousel-progress`, and
`smoothTo`'s `isMoving`.

The four `Slider*` `ready()` helpers stay: they compose the shipped
`settle`/`frames` with per-file instance lookups rather than re-implement
a wait.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnepYhqjPMcFCSb43PoHRM
…t writes to

Thirty-four of the forty-one `vi.spyOn(console, 'warn')` sites become
`captureDiagnostics()`. A spy on the sink cannot see the code, the
severity or the reporting component, passes for a diagnostic carrying the
wrong code, and breaks when the sink's wording changes. Where a site only
asserted "something was warned", the assertion is now the code itself.

`Track.spec.ts`'s local `recordDiagnostics()` was `captureDiagnostics()`
written out by hand, and is deleted.

Seven spies stay, all of them assertions about the sink rather than about
a diagnostic: the three in `diagnostics.spec.ts`, the two `$warn`/`$error`
sink tests in `Base.spec.ts`, `negotiated-events.spec.ts`'s
"cancelling suppresses the sink", and the test module's own spec.
`captureDiagnostics()` cancels every event it sees, so using it in those
would make the assertion vacuous.

`Base.spec.ts` keeps one hand-rolled listener: it asserts the element a
diagnostic *started on*, which `captureDiagnostics()` does not expose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnepYhqjPMcFCSb43PoHRM
Six spec files kept their own listener-attaching recorder, in two shapes:
one that pushed the type alone and one that pushed `{ type, detail }`.
`recordEvents()` returns the richer shape, so the four files that only
want the names map down at the call site, and `Fetch` and `Draggable` —
which already asserted on payloads — read it directly.

`Timer.spec.ts` keeps a two-line `record()` over the shipped helper,
because its eight call sites each name a different set of types and the
mapping reads better once than eight times.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnepYhqjPMcFCSb43PoHRM
… is left

`test-utils.ts` was three things at once: a wait helper, an instance
reader, and a registered component tree. The waits now come from
`src/test/index.ts`, so the tree moves to `src/todo.fixtures.ts` — where
its `registerComponent(TodoList)` side effect is visible in the filename
— and `test-utils.ts` keeps only what the shipped module deliberately
does not export.

`getInstance()` stays because it reads the raw instances map with no
`$isMounted` filter, which is what lets a spec look at an instance before
it mounts or after it unmounts. `countRequestedFrames()` stays because it
replaces a global.

No build or packaging change: `scripts/build.js` already excludes
`!test-utils.ts` and `**/*.fixtures.ts`, and `check-package.js` already
guards both. Verified — `npm run build` emits neither, and
`npm run check:package` passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnepYhqjPMcFCSb43PoHRM
`resetRegistry()` is the obvious replacement for a per-test counter, and
it is the wrong one here: the state that survives `resetDom()` in these
three files is the page-wide `DataRegistry` on the root context, whose
group records keep their values after the elements are gone.
`resetRegistry()` clears the component registry and never touches it.

Say so, so the next reader does not swap one for the other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnepYhqjPMcFCSb43PoHRM
@github-actions

Copy link
Copy Markdown

Export size

Bundled per export with peer dependencies left external, dynamic imports excluded and the output minified; sizes are gzipped.

✅ No export size changes.

Unchanged (396)

@studiometa/js-toolkit

Export Size (gzip) Diff
(barrel) 17.48 kB
AbstractService 598 B
Base 9.1 kB
ComponentLoader 2.31 kB
DEFAULT_DIAGNOSTIC_PREFIX 102 B
DragService 2.02 kB
IDLE_TIMEOUT 57 B
KeyService 935 B
LoadService 666 B
MutationService 849 B
PointerService 1.13 kB
RafService 1020 B
ResizeService 1.12 kB
ScrollService 1.36 kB
VISIBLE_ROOT_MARGIN 72 B
autoload 2.4 kB
closestComponent 419 B
composeManifests 119 B
createApp 996 B
defineFeatures 326 B
defineManifest 512 B
fromMetaGlob 228 B
fromWebpackContext 131 B
getClosestParent 197 B
getDirectChildren 202 B
getInstanceFromElement 125 B
getInstances 187 B
getScopedGroups 104 B
importOnInteraction 926 B
importOnMediaQuery 243 B
importWhenIdle 225 B
importWhenPrefersMotion 271 B
importWhenVisible 935 B
isDirectChild 218 B
logTree 551 B
queryComponent 594 B
queryComponentAll 601 B
readEagerTokens 201 B
registerComponent 305 B
registerComponents 356 B
registerManifest 2.87 kB
registerManifests 2.89 kB
useDrag 2.05 kB
useKey 943 B
useLoad 676 B
useMutation 876 B
usePointer 1.15 kB
useRaf 1 kB
useResize 1.13 kB
useScroll 1.36 kB
utils 10.08 kB
utils/Queue 291 B
utils/SmartQueue 473 B
utils/addClass 240 B
utils/addStyle 239 B
utils/animate 3.34 kB
utils/boundingRectToCircle 206 B
utils/cache 208 B
utils/camelCase 405 B
utils/clamp 98 B
utils/clamp01 114 B
utils/collideCircleCircle 129 B
utils/collideCircleRect 192 B
utils/collidePointCircle 128 B
utils/collidePointRect 122 B
utils/collideRectRect 128 B
utils/createEaseInOut 123 B
utils/createEaseOut 91 B
utils/createElement 635 B
utils/createLocalStorage 1.32 kB
utils/createLocalStorageProvider 296 B
utils/createMemoryStorageProvider 174 B
utils/createNoopProvider 128 B
utils/createRange 115 B
utils/createSessionStorage 1.32 kB
utils/createSessionStorageProvider 288 B
utils/createStorage 1.3 kB
utils/createUrlSearchParamsInHashProvider 461 B
utils/createUrlSearchParamsInHashStorage 1.35 kB
utils/createUrlSearchParamsProvider 429 B
utils/createUrlSearchParamsStorage 1.34 kB
utils/damp 106 B
utils/dashCase 404 B
utils/debounce 122 B
utils/domScheduler 310 B
utils/ease 519 B
utils/easeInCirc 285 B
utils/easeInCubic 287 B
utils/easeInExpo 286 B
utils/easeInOutCirc 288 B
utils/easeInOutCubic 289 B
utils/easeInOutExpo 288 B
utils/easeInOutQuad 288 B
utils/easeInOutQuart 289 B
utils/easeInOutQuint 289 B
utils/easeInOutSine 288 B
utils/easeInQuad 285 B
utils/easeInQuart 286 B
utils/easeInQuint 286 B
utils/easeInSine 285 B
utils/easeLinear 77 B
utils/easeOutCirc 286 B
utils/easeOutCubic 288 B
utils/easeOutExpo 286 B
utils/easeOutQuad 286 B
utils/easeOutQuart 286 B
utils/easeOutQuint 286 B
utils/easeOutSine 286 B
utils/endsWith 128 B
utils/fold 168 B
utils/getAncestorWhere 123 B
utils/getAncestorWhereUntil 148 B
utils/getComponentResolver 140 B
utils/getOffsetSizes 194 B
utils/hasWindow 88 B
utils/historyPush 524 B
utils/historyReplace 526 B
utils/inertiaFinalValue 169 B
utils/isArray 63 B
utils/isBoolean 78 B
utils/isDefined 75 B
utils/isDev 78 B
utils/isEmpty 206 B
utils/isEmptyString 108 B
utils/isFunction 79 B
utils/isNull 68 B
utils/isNumber 91 B
utils/isObject 108 B
utils/isString 77 B
utils/keyCodes 122 B
utils/lerp 84 B
utils/loadElement 220 B
utils/loadIframe 241 B
utils/loadImage 241 B
utils/loadLink 237 B
utils/loadScript 251 B
utils/localStorageProvider 839 B
utils/lowerCase 404 B
utils/map 93 B
utils/matrix 136 B
utils/mean 126 B
utils/memo 130 B
utils/memoize 228 B
utils/memoryStorageProvider 843 B
utils/nextFrame 179 B
utils/nextMicrotask 133 B
utils/nextTick 148 B
utils/noop 62 B
utils/noopValue 76 B
utils/objectToURLSearchParams 322 B
utils/pascalCase 407 B
utils/random 93 B
utils/randomInt 113 B
utils/randomItem 234 B
utils/removeClass 242 B
utils/removeStyle 243 B
utils/round 95 B
utils/saveActiveElement 92 B
utils/scrollTo 2.31 kB
utils/sessionStorageProvider 838 B
utils/smoothTo 476 B
utils/snakeCase 406 B
utils/spring 154 B
utils/startsWith 125 B
utils/throttle 125 B
utils/toggleClass 242 B
utils/transform 347 B
utils/transition 1010 B
utils/trapFocus 441 B
utils/tween 1.72 kB
utils/untrapFocus 120 B
utils/upperCase 404 B
utils/urlSearchParamsInHashProvider 845 B
utils/urlSearchParamsProvider 839 B
utils/useScheduler 309 B
utils/wait 103 B
utils/withLeadingCharacters 135 B
utils/withLeadingSlash 142 B
utils/withTrailingCharacters 135 B
utils/withTrailingSlash 142 B
utils/withoutLeadingCharacters 122 B
utils/withoutLeadingCharactersRecursive 165 B
utils/withoutLeadingSlash 133 B
utils/withoutTrailingCharacters 122 B
utils/withoutTrailingCharactersRecursive 165 B
utils/withoutTrailingSlash 133 B
utils/wrap 122 B
version 56 B
withBreakpointManager 1.54 kB
withBreakpointObserver 1.71 kB
withDrag 2.18 kB
withExtraConfig 163 B
withFreezedOptions 187 B
withGroup 455 B
withIntersectionObserver 303 B
withMountOnMediaQuery 393 B
withMountWhenInView 347 B
withMountWhenPrefersMotion 431 B
withMutation 1010 B
withName 109 B
withRelativePointer 1.29 kB
withResponsiveOptions 2.4 kB
withScrolledInView 3.05 kB

@studiometa/js-toolkit-v4

Export Size (gzip) Diff
(barrel) 22.66 kB
BREAKPOINTS 778 B
Base 8.59 kB
DIAGNOSTICS 714 B
DRAG_MODES 162 B
EVENTS 153 B
MOUNT_ATTRIBUTE 69 B
SWAP_MODES 129 B
children 244 B
component 11.66 kB
createContext 472 B
createFallbackProvider 1.33 kB
createGroup 1.07 kB
createLocalStorage 2.33 kB
createMemoryStorageProvider 1.21 kB
createService 640 B
createServiceMixin 1015 B
createSessionStorage 2.33 kB
createStorage 2.3 kB
createUrlSearchParamsInHashProvider 1.21 kB
createUrlSearchParamsInHashStorage 2.34 kB
createUrlSearchParamsProvider 1.21 kB
createUrlSearchParamsStorage 2.34 kB
defaultScheduler 1.5 kB
defineManifest 983 B
domUpdate 1.23 kB
emitExtendable 1.09 kB
fromMetaGlob 203 B
fromWebpackContext 131 B
getBreakpoints 776 B
getInstances 2.86 kB
inject 176 B
injectContext 675 B
injectContextSync 634 B
jsonSerializer 95 B
localStorageProvider 1.21 kB
memoryStorageProvider 1.21 kB
namespaceQualifier 120 B
nextFrame 115 B
on 8.93 kB
perTarget 322 B
provide 182 B
provideContext 704 B
provideRootContext 748 B
read 126 B
registerComponent 11.27 kB
registerComponents 11.27 kB
registerManifest 11.33 kB
reportDiagnostic 329 B
sessionStorageProvider 1.21 kB
setBreakpoints 806 B
signal 925 B
subscribeContext 1.44 kB
swap 2.9 kB
test 11.71 kB
toggle 177 B
until 172 B
urlSearchParamsInHashProvider 1.21 kB
urlSearchParamsProvider 1.21 kB
useBreakpoint 1.44 kB
useDrag 3.36 kB
useInView 1.43 kB
useKey 1.47 kB
useMediaQuery 1.06 kB
useMutation 1.4 kB
usePointer 1.85 kB
usePrefersReducedMotion 1.09 kB
useRaf 1.95 kB
useResize 1.43 kB
useScroll 2.67 kB
useScrollProgress 3.64 kB
useWindowScroll 2.66 kB
useWindowSize 1.43 kB
utils 9.25 kB
utils/DEFAULT_DAMP_FACTOR 109 B
utils/INERTIA_FRAME 97 B
utils/MAX_SPRING_RATIO 100 B
utils/SCROLL_ALIGNMENTS 117 B
utils/SCROLL_AXES 100 B
utils/TRANSFORM_PROPS 137 B
utils/TRANSITION_OPTIONS 132 B
utils/camelCase 449 B
utils/capitalize 119 B
utils/clamp 133 B
utils/clamp01 149 B
utils/clampDampFactor 157 B
utils/createEaseInOut 120 B
utils/createEaseOut 91 B
utils/createElement 638 B
utils/createRange 205 B
utils/damp 211 B
utils/debounce 121 B
utils/decayOver 162 B
utils/deepmerge 312 B
utils/easeInCirc 94 B
utils/easeInCubic 81 B
utils/easeInExpo 97 B
utils/easeInOutCirc 150 B
utils/easeInOutCubic 141 B
utils/easeInOutExpo 150 B
utils/easeInOutQuad 139 B
utils/easeInOutQuart 140 B
utils/easeInOutQuint 140 B
utils/easeInOutSine 156 B
utils/easeInQuad 80 B
utils/easeInQuart 81 B
utils/easeInQuint 81 B
utils/easeInSine 104 B
utils/easeLinear 77 B
utils/easeOutCirc 121 B
utils/easeOutCubic 111 B
utils/easeOutExpo 124 B
utils/easeOutQuad 110 B
utils/easeOutQuart 112 B
utils/easeOutQuint 111 B
utils/easeOutSine 132 B
utils/enterTransition 679 B
utils/fold 200 B
utils/getOffsetSizes 268 B
utils/historyPush 391 B
utils/historyReplace 392 B
utils/inertiaDecay 199 B
utils/inertiaFinalValue 187 B
utils/inertiaStep 232 B
utils/inertiaTimeConstant 178 B
utils/isBoolean 90 B
utils/isDefined 87 B
utils/isFunction 86 B
utils/isNull 78 B
utils/isNumber 103 B
utils/isObject 115 B
utils/isString 89 B
utils/kebabCase 421 B
utils/leaveTransition 679 B
utils/lerp 120 B
utils/loadImage 245 B
utils/loadLink 776 B
utils/loadScript 697 B
utils/lockScroll 565 B
utils/lowerCase 84 B
utils/map 128 B
utils/matrix 150 B
utils/mean 147 B
utils/memo 217 B
utils/noop 62 B
utils/noopValue 76 B
utils/objectToURLSearchParams 266 B
utils/pascalCase 434 B
utils/random 93 B
utils/randomInt 132 B
utils/randomItem 163 B
utils/round 130 B
utils/saveActiveElement 571 B
utils/scrollPosition 857 B
utils/scrollTo 1.86 kB
utils/selectorFor 2.78 kB
utils/setClassesOrStyles 218 B
utils/smoothTo 2.83 kB
utils/snakeCase 421 B
utils/spring 343 B
utils/throttle 151 B
utils/transform 286 B
utils/transition 577 B
utils/trapFocus 717 B
utils/untrapFocus 587 B
utils/upperCase 84 B
utils/wait 103 B
utils/withLeadingCharacters 142 B
utils/withLeadingSlash 152 B
utils/withTrailingCharacters 143 B
utils/withTrailingSlash 153 B
utils/withoutLeadingCharacters 127 B
utils/withoutLeadingCharactersRecursive 144 B
utils/withoutLeadingSlash 138 B
utils/withoutTrailingCharacters 129 B
utils/withoutTrailingCharactersRecursive 147 B
utils/withoutTrailingSlash 140 B
utils/wrap 154 B
viewTransition 1.66 kB
warn 321 B
watchAttributeNamespace 2.55 kB
watchAttributes 2.02 kB
whenDOMSettled 2.35 kB
withDrag 4.02 kB
withInView 2.11 kB
withKey 2.15 kB
withMutation 2.09 kB
withPointer 2.52 kB
withRaf 2.62 kB
withResize 2.1 kB
withScroll 3.33 kB
withScrollProgress 4.33 kB
write 124 B

@github-actions

Copy link
Copy Markdown

Code Review

Risk: Low — No concrete defects were found in the files opened; the refactor is safe to merge aside from the explicitly skipped and unopened diffs.

This change migrates v4 specs from hand-rolled helpers to the shipped test module, replaces guessed scheduler waits with condition-based polling, and moves todo fixtures into a dedicated module. It also changes diagnostic and event assertions to use the framework channels directly. I reviewed packages/v4/src/test/index.ts, .code-review-skipped/packages__v4__src__test-utils.ts.diff, .code-review-skipped/packages__v4__src__todo.fixtures.ts.diff, .code-review-skipped/packages__v4__src__decorators.spec.ts.diff, .code-review-skipped/packages__v4__migration__Track__Track.spec.ts.diff, .code-review-skipped/packages__v4__migration__Draggable__Draggable.spec.ts.diff, .code-review-skipped/packages__v4__migration__Carousel__Carousel.spec.ts.diff, .code-review-skipped/packages__v4__src__utils__smoothTo.spec.ts.diff, .code-review-skipped/packages__v4__migration__InView__InView.spec.ts.diff, and .code-review-skipped/packages__v4__migration__Prefetch__Prefetch.spec.ts.diff. I did not open the remaining files in the supplied skipped-files list, so those diffs were not independently reviewed.


Review usage: 302,234 in (233,834 cached) / 2,023 out tokens — $0.0605 (openrouter/openai/gpt-5.6-luna, thinking: low)

Reviewed by @weareikko/code-review v0.9.5 for commit 5011010.

@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.06%. Comparing base (e897468) to head (5011010).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #864   +/-   ##
=======================================
  Coverage   97.06%   97.06%           
=======================================
  Files         176      176           
  Lines        4561     4561           
  Branches     1331     1331           
=======================================
  Hits         4427     4427           
  Misses        122      122           
  Partials       12       12           
Flag Coverage Δ
eslint-plugin-js-toolkit 94.55% <ø> (ø)
js-toolkit 97.93% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

Copy link
Copy Markdown

v4 mount benchmarks

Base and head measured on this runner, alternating over 3 rounds each; every value is the median of the round medians. Running both sides on one machine is what removes cross-machine noise — a cached baseline from another runner would put it back.

A move under 25%, or on a benchmark under 5 ms, is not reported as a change: it is inside the measured noise of a shared runner.

No benchmark moved beyond the noise floor.

Within noise (18)
Group Benchmark Base Head us / component Change
mount 1000 components, one insertion control — declared but unregistered 1.50 ms 1.60 ms 1.60 +6.7%
mount 1000 components, one insertion flat 13.5 ms 13.4 ms 13.40 -0.7%
mount 1000 components, one insertion in-view — one controller per element 27.0 ms 27.8 ms 27.80 +3.0%
mount 1000 components, one insertion nested 4 deep 11.1 ms 10.4 ms 10.40 -6.3%
mount 1000 components, one insertion realistic — 5 refs, 3 options, 4 handlers 59.5 ms 59.0 ms 59.00 -0.8%
mount 1000 components, one insertion responsive option — breakpoint cascade per mount 12.8 ms 12.8 ms 12.80 0.0%
mount 1000 flat components, 1 vs 10 insertions 1 insertion 8.60 ms 9.90 ms 9.90 +15.1%
mount 1000 flat components, 1 vs 10 insertions 10 insertions 8.90 ms 9.30 ms 9.30 +4.5%
mount 5000 components, one insertion control — declared but unregistered 15.2 ms 13.1 ms 2.62 -13.8%
mount 5000 components, one insertion flat 51.2 ms 52.2 ms 10.44 +2.0%
mount 5000 components, one insertion in-view — one controller per element 137.5 ms 129.9 ms 25.98 -5.5%
mount 5000 components, one insertion nested 4 deep 52.4 ms 51.7 ms 10.34 -1.3%
mount 5000 components, one insertion realistic — 5 refs, 3 options, 4 handlers 277.5 ms 269.8 ms 53.96 -2.8%
mount 5000 components, one insertion responsive option — breakpoint cascade per mount 62.1 ms 67.8 ms 13.56 +9.2%
mount 5000 flat components, 1 vs 10 insertions 1 insertion 51.5 ms 52.5 ms 10.50 +1.9%
mount 5000 flat components, 1 vs 10 insertions 10 insertions 51.1 ms 52.1 ms 10.42 +2.0%
unmount 1000 flat components, one removal flat 2.60 ms 1.80 ms 1.80 -30.8%
unmount 5000 flat components, one removal flat 15.0 ms 12.7 ms 2.54 -15.3%

@titouanmathis
titouanmathis merged commit d122ee5 into main Aug 24, 2026
11 of 12 checks passed
@titouanmathis
titouanmathis deleted the refactor/v4-specs-on-test-module branch August 24, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant