Skip to content

test(regressions): pin PR #249 Bugbot fixes#259

Closed
aidenybai wants to merge 7 commits into
mainfrom
tests/pr-249-regressions
Closed

test(regressions): pin PR #249 Bugbot fixes#259
aidenybai wants to merge 7 commits into
mainfrom
tests/pr-249-regressions

Conversation

@aidenybai
Copy link
Copy Markdown
Member

@aidenybai aidenybai commented May 15, 2026

Two regression tests covering the Bugbot findings from #249. Both have been validated and rely on existing test helpers.

What's locked in

1. 'score unavailable' message branches on offline vs API failure (#249)

Added to cli-and-output.test.ts. Bugbot caught that noScoreMessage was unconditionally "Score unavailable in offline mode.", so a scan that ran online but couldn't reach the score API would falsely tell the user they were offline. The fix branches the message on options.offline; these two tests pin both wordings:

  • --offline: true → stdout contains "Score unavailable in offline mode" and does not contain "could not reach the score API".
  • --offline: false + mocked fetch throwing → stdout contains "could not reach the score API" and does not contain "in offline mode".

Both go through real inspect() runs via the existing captureScanOutput helper — black-box, no string scrubbing in source.

2. shared constants between core and project-info (#249)

New package-boundary-integrity.test.ts. Bugbot also caught that SOURCE_FILE_PATTERN, GIT_LS_FILES_MAX_BUFFER_BYTES, and IGNORED_DIRECTORIES were independently re-declared in both core/src/constants.ts and project-info/src/constants.ts — pure drift risk. The fix re-exports these from project-info through core's barrel; the test pins it with strict reference-equality:

expect(core.SOURCE_FILE_PATTERN).toBe(projectInfo.SOURCE_FILE_PATTERN);
expect(core.GIT_LS_FILES_MAX_BUFFER_BYTES).toBe(projectInfo.GIT_LS_FILES_MAX_BUFFER_BYTES);
expect(core.IGNORED_DIRECTORIES).toBe(projectInfo.IGNORED_DIRECTORIES);

If anyone re-declares any of these in core/constants.ts (even with the same value), .toBe() fails because the new RegExp/Set is a different reference. Inline HACK: comment points future failures at the correct fix (re-export, not the test).

Convention compliance (vs AGENTS.md)

  • Arrow functions only ✓
  • All non-trivial comments carry the HACK: prefix ✓
  • Test files in kebab-case ✓
  • Descriptive variable names (no shorthands) ✓
  • Updated cli-and-output.test.ts "Covered closed issues" header to add #249
  • No DRY violation — collapsed 3 separate it() blocks into 1 with 3 inline asserts in the dedup file ✓

Test plan

  • pnpm test — 78 files, 713 tests passing
  • pnpm typecheck — clean
  • pnpm lint — 0/0
  • Verified new tests appear in verbose runner output under their named describe blocks

Note

Medium Risk
Mostly adds regression tests, but introduces repo-wide source scanning and strict export/reference checks that could be brittle across environments or future refactors.

Overview
Fixes inspect() output so the "score unavailable" text distinguishes user-requested --offline from score API failures, by centralizing both messages in cli/utils/constants.ts and using them in inspect.ts.

Adds regression coverage: cli-and-output.test.ts now parameterizes offline vs multiple API-failure modes (fetch throw, non-2xx, malformed body) and asserts the correct message, and a new silent-drift-defenses.test.ts enforces "single source of truth" rules (no duplicated score messages/URLs, no re-declared @react-doctor/types types in consumers, and @react-doctor/core shared exports must be true re-exports from @react-doctor/project-info).

Reviewed by Cursor Bugbot for commit fd1efb3. Bugbot is set up for automated code reviews on this repo. Configure here.

@reactreview
Copy link
Copy Markdown

reactreview Bot commented May 15, 2026

🔴 React Review0/100 (unchanged) · 0 ❌ errors · 4 ⚠️ warnings

Copy prompt for agent
Check if these React Review issues are valid. If so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.

Run this before and after your changes to verify the result:
npx react-doctor@latest --verbose --diff

Do not modify the react-doctor configuration unless explicitly asked.
Fix the underlying code issues instead of changing or suppressing the rules.

React Review found 0 errors and 4 warnings. This PR leaves the React health score unchanged.

<file name="packages/react-doctor/tests/regressions/silent-drift-defenses.test.ts">

<violation number="1" location="packages/react-doctor/tests/regressions/silent-drift-defenses.test.ts:120">
Severity: Warning

.filter().map() iterates the array twice — combine into a single loop with .reduce() or for...of

Combine `.map().filter()` (or similar chains) into a single pass with `.reduce()` or a `for...of` loop to avoid iterating the array twice

Rule: `js-combine-iterations`
</violation>

<violation number="2" location="packages/react-doctor/tests/regressions/silent-drift-defenses.test.ts:172">
Severity: Warning

.filter().map() iterates the array twice — combine into a single loop with .reduce() or for...of

Combine `.map().filter()` (or similar chains) into a single pass with `.reduce()` or a `for...of` loop to avoid iterating the array twice

Rule: `js-combine-iterations`
</violation>

<violation number="3" location="packages/react-doctor/tests/regressions/silent-drift-defenses.test.ts:215">
Severity: Warning

.filter().map() iterates the array twice — combine into a single loop with .reduce() or for...of

Combine `.map().filter()` (or similar chains) into a single pass with `.reduce()` or a `for...of` loop to avoid iterating the array twice

Rule: `js-combine-iterations`
</violation>

<violation number="4" location="packages/react-doctor/tests/regressions/silent-drift-defenses.test.ts:233">
Severity: Warning

.map().filter() iterates the array twice — combine into a single loop with .reduce() or for...of

Combine `.map().filter()` (or similar chains) into a single pass with `.reduce()` or a `for...of` loop to avoid iterating the array twice

Rule: `js-combine-iterations`
</violation>

</file>

Reviewed by react-review for commit fd1efb3. Configure here.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-doctor-website Ready Ready Preview, Comment May 15, 2026 10:39am

Two regressions land here:

1. `cli-and-output.test.ts` gains a "'score unavailable' message
   branches on offline vs API failure" describe block. The original
   bug (Bugbot #249) was `noScoreMessage` unconditionally set to
   "Score unavailable in offline mode" — so every null-score scan
   claimed the user was offline even when the user was online but the
   score API simply failed. Two tests cover the two reasons (with the
   API path stubbed via fetch-throws since the message branches on
   `options.offline`, not on the specific API failure mode).

2. New `package-boundary-integrity.test.ts` pins the post-extraction
   contract that `@react-doctor/core` re-exports
   SOURCE_FILE_PATTERN / GIT_LS_FILES_MAX_BUFFER_BYTES /
   IGNORED_DIRECTORIES from `@react-doctor/project-info` rather than
   re-declaring them. A single `.toBe(...)` reference-equality check
   per constant catches the silent-drift bug Bugbot caught on #249
   (duplicate consts in two packages with no link between them).

Both files follow the existing convention: file-header documents
"Covered closed issues", describe blocks read as "<behavior> (#249)",
and HACK: comments carry the historical provenance.
…o-discover shared exports

- Lift the two "Score unavailable" messages to named constants in
  `cli/utils/constants.ts` so tests can import them instead of
  duplicating string literals (rename-safe assertions).
- Parameterize the #249 noScoreMessage test into a scenario table that
  covers four distinct null-score paths, not just one: `--offline`,
  `fetch` throws, `response.ok === false`, and malformed body. All
  three online failure modes must produce the API-failure message —
  previously only the throwing path was pinned.
- Auto-discover shared exports between `@react-doctor/core` and
  `@react-doctor/project-info` rather than hardcoding three names; a
  4th duplicated constant added tomorrow is now caught with zero test
  maintenance. Defensively require both lookups to be `!== undefined`
  before reference-equality, so a `export type { ... }` slip can't
  smuggle drift past `undefined === undefined`.
…tive names, decoupled stubs)

- Drop verbose comment block on the SCORE_UNAVAILABLE_* constants;
  the SCREAMING_SNAKE_CASE names plus values are self-documenting.
- Decouple the console.warn silencer from `setupFetchStub`'s
  existence — pull it into a named helper and call it explicitly from
  each API-failure scenario so a future scenario can opt out.
- Slim the package-boundary-integrity test: strip the file-head HACK
  block that duplicated the JSDoc, inline `collectSharedExportNames`
  at its single call site, rename `failures` -> `boundaryViolations`
  and the loop variable `name` -> `sharedExportName`. Keep only the
  genuinely non-obvious `undefined === undefined` defensive note.
Comment thread packages/react-doctor/tests/regressions/package-boundary-integrity.test.ts Outdated
…can (Bugbot #259)

Bugbot identified a blind spot in the boundary integrity test:
`Object.is` compares references, which works for object-typed shared
constants (RegExp / Set / Map) but is identity-true for primitives —
so a re-declared `GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024` in
core would slip past unnoticed. The same numeric value === itself.

Augment with a source-level pattern scan: read every `.ts` file under
`packages/core/src/` once at module init, then for each shared export
name check whether any file contains a top-of-line
`export\s+const\s+<name>\b`. Re-export lines like
`export { NAME } from "..."` are skipped by the anchor. Verified the
new check fires by simulating the exact regression Bugbot warned
about (re-declaring `GIT_LS_FILES_MAX_BUFFER_BYTES` in core) — the
test now reports `core declares its own copy in constants.ts`.
Rename `package-boundary-integrity.test.ts` -> `silent-drift-defenses.test.ts`
and extend it with three additional drift classes, each empirically
verified by simulating the regression:

1. Type-level boundary integrity (cross-package types).
   Parse the `@react-doctor/types` barrel to enumerate every exported
   type name, then scan every CLI package source for a top-of-line
   `(export\s+)?(interface|type)\s+NAME` declaration. TypeScript would
   happily accept two structurally-similar parallel declarations
   imported through different paths — the test pins the invariant
   that there's exactly one home for each type. Verified by adding
   `interface Diagnostic` to a canary file -> test fires.

2. Magic-string locality for the two `SCORE_UNAVAILABLE_*` messages.
   The literals must appear ONLY in their constants module. Catches
   the exact regression Bugbot flagged on PR #249 at the SOURCE level
   (a future contributor re-inlines `"Score unavailable in offline
   mode."` somewhere, bypassing the imported constant). Verified by
   inlining the literal in a canary file -> test fires with a
   remediation hint.

3. URL constants locality for `SCORE_API_URL` / `SHARE_BASE_URL`.
   Same defense applied to the score-API and share endpoints. Scope
   excludes `packages/website/` since it's deployed separately and
   legitimately re-declares some URLs for SSR. Verified by inlining
   the score-API URL in a canary file -> test fires.

The four checks share one workspace-source scanner (`collectTypeScriptSources`)
that walks the six CLI package source trees once at module init and
caches per-file content. Each describe block consumes the same cached
map, so adding a fifth drift class is a ~30-line addition.

8 tests total in this file (was 2). Full suite: 705 -> 711.
- H1: remove dead `absolutePath` field from `WorkspaceSourceFile` (was
  assigned but never read).
- H2: fix the misleading remediation hints on the magic-string rules.
  Drop the `remediation` field entirely and derive the hint at
  assertion time from `allowedRepoRelativePath` — eliminates the bug
  class of "hardcoded hint drifts from the path it points at".
- M1: unify naming. The two boundary describes now use per-test
  `expect(...).toEqual([])` rather than mutable accumulator arrays
  named differently in each block.
- M2: drop self-narrating comment ("Consumers = every CLI source
  except types itself" restated the filter that follows).
- M3: HACK-prefix the remaining explanatory comments consistently.
  Every kept comment now justifies a non-obvious design choice.
- L1: swap `let blockMatch + exec` for `matchAll` — no mutable loop
  variable, no null guard.
- L2: convert the two boundary describes from "accumulator + single
  assertion" to `it.each` over auto-discovered names. Each shared
  constant / type gets its own test name; each failure has a
  per-name `expect` message. Matches the magic-string describe's
  pattern, so the file reads consistently end-to-end. 8 -> 35 tests
  in this file (per-name granularity); all behaviorally equivalent
  to the previous 8.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d81673d. Configure here.

…an works on Windows (Bugbot #259)

Bugbot identified a false-positive failure on Windows: my filter
`source.repoRelativePath !== allowedRepoRelativePath` compares values
where the left side comes from `path.relative()` (OS-native: `\` on
Windows) and the right side is a forward-slash literal in the rule
table. On Windows the comparison never matches the allowed file, so
the constants file is scanned, its content includes the literal, and
the test fails for the contributor.

Fix: store all repo-relative and package-relative paths in POSIX form
(`split(path.sep).join("/")`). On macOS / Linux this is a no-op; on
Windows it converts `\` -> `/` and the forward-slash literals in the
rule table just work. `packageRoot` is left as OS-native because it's
only ever compared via `endsWith(path.join(...))`, which is already
cross-platform correct on both sides.

Pin the invariant with an explicit regression test that scans every
stored `repoRelativePath` for backslashes — fires on Windows the
moment someone forgets the normalization (e.g. a refactor of the
scanner).
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.

2 participants