Skip to content

refactor(test): share test setup across the suites - #1069

Draft
lukecotter wants to merge 10 commits into
certinia:refactor-drop-dead-codefrom
lukecotter:refactor-share-test-setup
Draft

lukecotter wants to merge 10 commits into
certinia:refactor-drop-dead-codefrom
lukecotter:refactor-share-test-setup

Conversation

@lukecotter

Copy link
Copy Markdown
Collaborator

📝 PR Overview

Gives the test suites shared setup. Second of two reduction PRs, and stacked on
#1066
— its base is refactor-drop-dead-code, so review that one first and this diff
stays readable.

The suites are 43,933 lines against 77,031 of source. Most of the excess is the same
setup written again in each file: 28 local mount helpers, four near-identical
createEvent builders, the same EXECUTION_STARTED fixture line pasted across 22 files.
This PR gives each of those one home.

Nothing here changes shipped code. The one production-file edit in the first commit is a
comment.

Draft: in progress. More commits are coming on this branch; each lands with
pnpm lint and pnpm test green, and I check the passing-test count is unchanged so a
shared helper cannot quietly stop a case running.

🛠️ Changes made

Landed so far:

  • Mock #vscode-elements through the jest config. 21 suites carried 35 copies of
    jest.mock('#vscode-elements/…', () => ({})). jsdom's ElementInternals has no
    setFormValue, so a form-associated element fails on its first update, and
    vscode-icon warns on every connect about the missing codicon stylesheet — a
    library-wide fact, not a per-suite decision. TimelineKey and DockLayout each
    stubbed a whole component to dodge the same problem and now exercise the real
    OverflowList and DetailDock.

Still to come on this branch:

  • A shared lit mount/settle/cleanupDom (28 local copies today).
  • Shared log event builders (four near-identical createEvent clones, ~250 lines).
  • Shared apex-log fixtures (the same header lines pasted across 22 files).
  • Tabulator, pixi and viewport doubles, plus 55 lines of an unreachable __mocks__ file.
  • The lana asContext cast (67 occurrences) and lastRegisteredCommand.
  • it.each collapses, and one governor-tier policy currently asserted twice.

🧩 Type of change (check all applicable)

  • 🐛 Bug fix - something not working as expected
  • ✨ New feature – adds new functionality
  • ♻️ Refactor - internal changes with no user impact
  • ⚡ Performance Improvement
  • 📝 Documentation - README or documentation site changes
  • 🔧 Chore - dev tooling, CI, config
  • 💥 Breaking change

📷 Screenshots / gifs / video [optional]

No UI change.

🔗 Related Issues

Stacked on #1066.

✅ Tests added?

  • 🙅 no, not needed

This PR changes how tests are set up, not what they cover. The bar for every commit is
that the passing-test count does not move: 2,567 before and after.

Two behaviour widenings are worth a reviewer's attention rather than being buried:

  • The #vscode-elements rule went from opt-in to blanket. 38 suites reach such an import
    transitively and 21 already mocked, so 17 now get the stub where they previously loaded
    the real module. A control run with the mapper removed fails 18 suites and 260 tests,
    which is what proves the stub is load-bearing and that no passing assertion was
    weakened.
  • TimelineKey and DockLayout no longer stub out OverflowList and DetailDock, so
    those two suites now cover more than they did.

vscode-single-select is exempt from the mapper: VsSelect extends VscodeSingleSelect
and spreads its static styles, so a stub cannot be extended. That exemption currently
holds only because no suite mounts a <vs-select> — the real element is form-associated
and would hit the missing setFormValue. VsSelect carries a comment saying so. The
clean fix is an ElementInternals no-op polyfill beside the existing ResizeObserver one
in setup.ts; filed as a follow-up rather than smuggled in here.

📚 Docs updated?

  • 🙅 not needed

Test-only change, so no CHANGELOG entry.

Anything else we need to know? [optional]

Verified per commit with pnpm lint and pnpm test, and by comparing the passing-test
count before and after.

Out of scope by agreement: apex-log-parser, and the legacy timeline behind
lana.timeline.legacy.

21 suites carried 35 copies of jest.mock('#vscode-elements/…', () => ({})).
jsdom's ElementInternals has no setFormValue, so a form-associated
element fails on its first update, and vscode-icon warns on every
connect about the missing codicon stylesheet. That is a library-wide
fact, not a per-suite decision, so it moves to moduleNameMapper.

This widens the rule from opt-in to blanket. 38 suites reach a
#vscode-elements import transitively and 21 already mocked, so 17 now
get the stub where they previously loaded the real module. None assert
vendor-element behaviour and the test count is unchanged.

TimelineKey and DockLayout stubbed a whole component apiece to dodge
the same jsdom problem. The mapper covers it, so both now exercise the
real OverflowList and DetailDock.

vscode-single-select is exempt: VsSelect extends VscodeSingleSelect and
spreads its static styles, so a stub cannot be extended. VsSelect says
so, and warns that mounting a <vs-select> under jsdom needs its own
setFormValue shim.
34 suites each wrote their own create, configure, append and await. They now call
mountElement, which owns that sequence and throws a named error when a tag is not a
rendered Lit element — an unregistered tag otherwise awaits an undefined
updateComplete and fails later on a null shadowRoot.

Three suites drop an `as unknown as { logStore }` cast: the cast was there only
because the mount was typed LitElement, not the component's own exported class.

Two suites settle one update more than before, because the helper always awaits the
first render where their old helper went straight to a longer settle:
LogInspector.test.ts and NamespaceTimeBar.test.ts. Both pass; extra settling cannot
cause a false failure, but it can mask a render-timing regression.

Left alone: skeletonStatus.test.ts mounts into a ContextProvider host rather than the
body, and AnalysisView.test.ts constructs the view without ever adding it to the DOM.
Four suites each wrote the same createEvent, differing only in which optional
fields they exposed. One builder now serves all four.

It carries the fields these passes actually read and no others, typed
Partial<LogEvent> rather than cast blind, so a renamed or reshaped field on the
real class fails the typecheck here. Fourteen fields the old builders set and
nothing reads are gone.

The per-suite timestamp counter and its eight beforeEach resets are gone too:
no module these suites drive reads event.timestamp, so the resets did nothing.

eventText defaulted exitStamp to 1000, the others to null. null is the class
default and models a frame that never exited, so it wins, and the two cases that
read a duration now say exitStamp where they assert on it.

Left alone: ExecutionHighlights derives exitStamp from a running stamp and needs
eventIndex, and the timeline builders are positional and shape their duration
differently. Roughly nine local event builders remain across the repo.
The glob was unquoted, so the shell expanded it before prettier saw it. Without
globstar that reaches 5 files; the repo holds 537 TypeScript files alone. Every
nested path went unchecked.

It stayed invisible because lint-staged writes prettier over staged files on the
way into a commit, so the repo is formatted and the gate that was meant to prove
it was reading almost nothing.

Quoted, prettier expands the glob itself, as prettier-format already does.
pnpm puts its store in .pnpm-store here when the usual location is not writable,
which leaves 947MB untracked and one git add -A away from the history.
A mounted component stayed connected once its test ended, and the listeners its
connectedCallback registered stayed live with it. A suite that emitted on the
eventBus after several mounts reached every one of them.

Cleanup was per suite and ad hoc: sixteen hooks cleared the body, four of them
doing nothing else, and the rest of the jsdom suites cleared nothing. setup.ts
now does it once for all of them, guarded because the suites run under node
unless a file asks for jsdom.

replaceChildren rather than innerHTML, so disconnectedCallback runs and the
subscriptions release.
core/utility/FrameBudget.ts exports waitForNextFrame, and six sites in three
suites wrote it out again. The tests now yield through the same helper the code
under test yields through.

The setTimeout(0) waits stay as they are: waitForNextTask posts through a
MessageChannel, which does not order the same way against a timer.
Three suites each wrapped a body in the same header, execution and code unit,
then parsed it. The envelope, the settings headers and the two eventsById
lookups now have one home.

The bodies stay in the tests. A body is what its test is about, and the whole
input should read where it is asserted on; only the constant wrapper moved.

The settings header takes its level from LOG_LEVEL, the enum recordsVariables
branches on, so the fixture and the code under test cannot drift apart.

VariablesDetail held a third and fourth copy of the isParent lookup and a local
indexOf taking a store where the sibling suite's took a log. One name, one
signature now: call sites pass store.log.
Four test files each built their own ViewportState factory and two built
the same FlameChart internals stub. makeViewport and stubChartInternals
give both one home.

stubChartInternals starts every renderDirty flag false, unlike the real
init(), so a case proves the render it asked for. HoverRehit passes
{ culling: true } where it depends on one.

internalsOf names the cast past the public API once, in place of the
eight copies the two chart suites carried.
asContext replaces 67 hand-written casts of a MockContext to Context, and
lastRegisteredCommand replaces three copies of the expression that digs the
handler out of registerCommand's last call. Both were already written once,
privately, in WhatsNewNotification's suite.

Fifteen beforeEach blocks cleared the mocks again. setup.ts clears them in a
global beforeEach, which runs first, so the second clear did nothing. Three
of the fifteen had nothing else to do.
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