JavaScript / TypeScript catalog¶
The codes implemented by falsegreen-js: a static scan via the
TypeScript compiler API, runner-agnostic across Jest, Vitest, Mocha+Chai, Jasmine, AVA,
node:test, Cypress, Playwright, and Testing Library. Covers .js, .jsx, .ts, .tsx,
.mjs, .cjs, .mts, .cts.
Codes share an id with Python where the concept matches; JS* codes are
ecosystem-specific. Confidence: HIGH blocks, LOW warns. Judgments are
J1-J6.
Every emitted code has its own entry below. The index links straight to each one.
Index¶
| Code | Conf | J | One-liner |
|---|---|---|---|
| C2 | HIGH | J1 | empty test body |
| C2b | LOW | J1 | calls the unit but never asserts |
| C5 | HIGH | J2 | always-true (expect(true).toBe(true)) |
| C6 | LOW | J4 | weak check (toBeTruthy, .length > 0) |
| C7 | HIGH | J2 | self-compare (expect(x).toBe(x)) |
| C8 | LOW | J4 | exact equality on a float |
| C8b | LOW | J4 | toBeCloseTo with no precision argument |
| C9 | LOW | J4 | toThrow() with no error type or message |
| C11a | LOW | J2 | self-confirming literal bound from the call under test |
| C16 | LOW | J1 | depends on time, randomness, or a fixed timer |
| C18 | LOW | J2 | stringified equality (String(x)/JSON.stringify) |
| C20 | HIGH | J1 | dead assertion after return/throw |
| C21 | LOW | J1 | no assertion runs unconditionally |
| C23 | LOW | J6 | reads a real file at a literal path / hard-coded URL |
| C37 | LOW | J4 | duplicate it.each/test.each case |
| C44 | HIGH | J2 | numeric tautology on a length (.length >= 0) |
| C48 | LOW | J1 | dark patch: flips a test-mode flag then asserts |
| CC | LOW | J1 | commented-out assertion |
| JS1 | HIGH | J1 | focused test skips the rest of the suite |
| JS2 | HIGH | J1 | expect with no matcher |
| JS3 | LOW | J2 | snapshot is the only assertion |
| JS4 | LOW | J1 | skipped test (it.skip/xit/it.todo) |
| JS5 | LOW | J1 | async query/event not awaited |
| JS6 | HIGH | J1 | empty describe/suite |
| JS7 | LOW | J1 | assertion in a non-awaited setTimeout/then |
| JS8 | LOW | J3 | mocks the unit under test and asserts it directly |
| JS9 | HIGH | J1 | assertion in a dead literal branch |
| JS11 | LOW | J1 | try/catch swallows the assertion |
| JS13 | LOW | J4 | query used as a loose statement, never asserted |
| JS15 | LOW | J4 | comparison wrapped in a boolean |
| JS17 | LOW | J1 | commented-out test block |
| JS18 | LOW | J1 | done callback instead of async/await |
| JS21 | HIGH | J1 | matcher referenced but never called |
| JS22 | HIGH | J1 | empty it.each/test.each table |
| JS23 | HIGH | J1 | expect.assertions(N) the body cannot satisfy |
| JS24 | LOW | J4 | Cypress query with no assertion |
| JS25 | HIGH | J1 | the only assertion is inside an array-iterator callback |
| JS26 | LOW | J1 | fake timers installed but never advanced |
| JS27 | LOW | J3 | toHaveBeenCalled* is the sole oracle on a local double |
| JS29 | LOW | J6 | resolves/rejects chain is a bare statement |
| JS30 | HIGH | J2 | literal-vs-literal assertion |
| JS31 | LOW | J1 | try/catch swallows a SUT throw, no assertion on the error |
| D1 | OFF | J4 | assertion roulette |
| D3 | OFF | J4 | duplicate assert |
| D4 | OFF | J4 | untitled it.each cases |
| D6 | OFF | J4 | console.* in a test body |
| D7 | OFF | J4 | anonymous test |
| D8 | OFF | J4 | magic number in an assertion |
| M2 | OFF | J5 | over-long test body |
| PL7 | LOW | J5 | no coverage gate |
| PL8 | LOW | J5 | bail stops the run early |
| PL10 | LOW | J1 | passWithNoTests lets an empty suite pass |
A code keeps its id across languages where the smell is the same; the signal below is the JavaScript form. Two shared ids differ in meaning per language, see the Python page: C31 does not exist here; C44 is the numeric-tautology length check here and in Python, widened in Robot to any vacuous library assertion.
Shared codes (same concept as Python)¶
These mirror the Python catalog entries; the signal is the JavaScript form.
C2 - empty test body¶
J1 · HIGH · F1
A test with no matcher, no expect, no assertion of any kind. Always green.
C2b - calls the unit but never asserts¶
J1 · LOW · F1
The test calls production code but no matcher follows. The check is simply missing.
C5 - always-true assertion¶
J2 · HIGH · F3
expect(true).toBe(true), assert(1): structurally guaranteed to pass, so it protects nothing.
C6 - weak check¶
J4 · LOW · F4
toBeTruthy() / toBeDefined() or a .length > 0 check: only that something came back, not the
value.
C7 - self-compare¶
J2 · HIGH · F3
expect(x).toBe(x): both sides are the same expression, always equal.
C8 - exact equality on a float¶
J4 · LOW · F4
expect(compute()).toBe(3.14159): floating-point arithmetic makes exact equality unreliable. Use
toBeCloseTo.
C8b - toBeCloseTo with no precision argument¶
J4 · LOW · F4
toBeCloseTo(x) defaults to 2-digit tolerance, which may be too loose for the value under test.
Pass an explicit precision.
C9 - toThrow() with no error type or message¶
J4 · LOW · F4
expect(fn).toThrow() with no argument accepts any error, including one from a typo inside the
test.
C11a - self-confirming literal¶
J2 · LOW · F3
The expected value is bound from the same call under test: const e = foo(); expect(foo()).toBe(e).
The oracle confirms itself. Bind the expected value independently.
C16 - depends on time, randomness, or a fixed timer¶
J1 · LOW · F6
Date.now / new Date() / Math.random / crypto.randomUUID / getRandomValues, or a fixed
timer. Freeze time and seed randomness.
C18 - stringified equality¶
J2 · LOW · F4
Compares String(x) / JSON.stringify(x) / a template literal to a string literal. The format is
an implementation detail; assert the value.
C20 - dead assertion after a terminator¶
J1 · HIGH · F2
An assertion after return / throw / process.exit / an exhaustive switch. Structured
block-level reachability (cfg.ts) proves it never runs.
C21 - no assertion runs unconditionally¶
J1 · LOW · F2
Every assertion is inside a branch; none sits on the test's guaranteed spine. The same cfg.ts reachability decides this, and a dead assertion does not mask it.
C23 - reads a real file at a literal path or hard-coded URL¶
J6 · LOW · F6
readFileSync("/etc/...") or a hard-coded URL: mystery guest. Use a fixture or temp file.
C37 - duplicate it.each / test.each case¶
J4 · LOW · F8
The same argument row appears twice in the table; the duplicate runs the same scenario and adds no coverage.
C44 - numeric tautology on a length¶
J2 · HIGH · F3
expect(x.length).toBeGreaterThanOrEqual(0): a length is never negative, so the comparison is
always true.
C48 - dark patch: flips a test-mode flag then asserts¶
J1 · LOW · F2
The test flips a test-mode flag (process.env.NODE_ENV = "test", process.env.TESTING = "1",
settings.TESTING = true) then asserts, so it exercises the product's test-only branch instead of
real behaviour. Parity with Python C48.
CC - commented-out assertion¶
J1 · LOW · F2
A line in the body is a commented-out expect(...): the check was switched off and left.
JavaScript-specific codes¶
JS1 - focused test skips the rest of the suite¶
J1 · HIGH · F5
it.only / fit / describe.only silently excludes every other test in the file from the run.
JS2 - expect with no matcher¶
J1 · HIGH · F1
expect(value) with no matcher call. Nothing is asserted; the line is a no-op.
JS3 - snapshot is the only assertion¶
J4 · LOW · F3
A test whose only check is toMatchSnapshot() / toMatchInlineSnapshot(). The baseline is
generated from the output, so it detects change, not correctness.
JS4 - skipped test¶
J1 · LOW · F5
it.skip / xit / it.todo left in place. Silently excluded from the run.
JS5 - async query or event not awaited¶
J1 · LOW · F2
A promise- or value-only-kind call (findBy*, waitFor, userEvent, a floating
expect().resolves) is dropped as a bare statement, so the following assertion reads a stale
moment. Detection routes through the oracle registry.
JS6 - empty describe / suite¶
J1 · HIGH · F1
A describe/suite block with no test inside. It contributes nothing and can read as coverage.
JS7 - assertion in a non-awaited setTimeout / then callback¶
J1 · LOW · F2
The assertion lives in a setTimeout/setInterval callback (timer arm) or a floating
.then/.catch/.finally (promise arm), so it may run after the test reported green.
JS8 - mocks the unit under test and asserts it directly¶
J3 · LOW · F4
The test mocks the very function it claims to test, then asserts the mock's value. It tests the
mock configuration, not the code. (The semantic, deeper form is case 10 / S12.)
JS9 - assertion in a dead literal branch¶
J1 · HIGH · F2
if (false) { expect(...) } or the else of if (true). The assertion is unreachable.
JS11 - try/catch swallows the assertion¶
J1 · LOW · F2
The assertion sits in a try whose catch absorbs the thrown error (logs or ignores), so a
failure never propagates.
JS13 - query used as a loose statement, never asserted¶
J4 · LOW · F1
getBy* / queryBy* called as a bare statement. The query runs but nothing is checked on it.
JS15 - comparison wrapped in a boolean¶
J4 · LOW · F4
expect(a === b).toBe(true). The boolean collapses the diff: on failure the message is
false !== true, with no values. Assert the values directly.
JS17 - commented-out test block¶
J1 · LOW · F2
// it('...', ...) left in the file. The test does not run.
JS18 - done callback instead of async/await¶
J1 · LOW · F2
A done-callback test where done() precedes or sits at the same level as the assertion, so the
runner completes before the assertion throws.
JS21 - matcher referenced but never called¶
J1 · HIGH · F2
expect(x).toBe with no (). The matcher is a property access; the check never runs. Sibling of
JS2.
JS22 - empty it.each / test.each table¶
J1 · HIGH · F5
it.each([])(...). Zero cases are generated, so the test never runs. Parallel of C45.
JS23 - expect.assertions(N) the body cannot satisfy¶
J1 · HIGH · F5
expect.assertions(N) with a numeric N higher than the unconditional, reachable, non-nested
expect() calls that can run. The guard the author wrote to defend against a silently-dropped
async assertion is dead on arrival. Fires only on a provable literal-N shortfall: an expect
in a loop, a branch, a .then/callback, or a called helper makes the count indeterminate and
suppresses the finding. expect.hasAssertions() carries no count and is skipped.
JS24 - Cypress query with no assertion¶
J4 · LOW · F4
A Cypress query chain (cy.get/cy.find/cy.contains) used as a statement with no terminating
.should/.and and no expect inside a .then callback. The query produces a subject that is
never asserted, the cy.* analogue of JS13. Action commands (click/type/visit/...) do work
rather than query, so a chain ending in one stays clean, as does one ending in .should/.and.
JS25 - the only assertion is inside an array-iterator callback¶
J1 · HIGH · F2
The sole expect sits inside a forEach / map / filter / some / every / flatMap
callback. On an empty collection the callback never runs, so the test passes having asserted
nothing. Assert the length first, or pull at least one check outside the iterator.
JS26 - fake timers installed but never advanced¶
J1 · LOW · F2
jest.useFakeTimers() / vi.useFakeTimers() (or sinon fake timers) is set up, but the clock is
never advanced (runAllTimers, advanceTimersByTime, tick). The scheduled callback never fires,
so the assertion reads un-mutated state and passes for the wrong reason.
JS27 - toHaveBeenCalled* is the sole oracle on a locally-created double¶
J3 · LOW · F4
The only assertion is toHaveBeenCalled / toHaveBeenCalledWith / toHaveBeenCalledTimes on a
mock created in the test (jest.fn() / vi.fn()). It verifies the test's own wiring, that the
double was invoked, not that the unit produced the right result.
JS29 - resolves / rejects chain is a bare statement¶
J1 · LOW · F2
expect(p).resolves.toBe(...) / .rejects... written as a statement that is neither awaited
nor returned. The matcher returns a promise; the test finishes green before it settles, so a
later rejection is lost.
JS30 - literal-vs-literal assertion¶
J2 · HIGH · F3
Both operands are fixed at parse time: expect(2).toBe(3), chai expect(1).to.equal(1). The
assertion does not touch the unit under test, so it is either always-true or always-false by
construction, never a check on real behaviour.
JS31 - try/catch swallows a possible throw with no assertion on the exception¶
J1 · LOW · F2
A try calls the unit and the catch neither re-throws nor asserts anything on the error. A unit
that stops throwing still passes green. Sibling of JS11, where the swallowed thing is an expect;
here there is no assertion at all on the caught path.
High-value traps with evidence¶
The scanner also catches a set of idiom-specific false-greens documented in the JS/TS empirical studies:
- HTTP header case trap (
J4): supertest lowercases response headers, sores.headers['Content-Type']is alwaysundefined. Use the lowercase key. - Bitwise NOT coercion (
J4):~~res.header['content-length']turns an absent header into0, passing silently when the expected value is0. - Self-referential field oracle (
J2):expect(result).toEqual([{ createdAt: result[0].createdAt }])compares a field to itself. Use a fixed known value. - Sign-then-verify tautology (
J2): signing a JWT and verifying it with the same key passes even ifverify()skips the check, unless paired with a wrong-key negative test.
Project layer - config audit (PL)¶
Emitted by --config-audit, not the per-file scan. The suite goes green by runner configuration,
not by a smell inside any one test file.
PL7 - no coverage gate¶
J5 · LOW · F8
No coverageThreshold / coverage.thresholds is set, so coverage can fall to zero and the suite
still passes. Add a coverage threshold.
PL8 - bail stops the run early¶
J5 · LOW · F5
bail is set, so the run stops on the first failures and the reported test count is incomplete.
PL10 - passWithNoTests lets an empty suite pass¶
J1 · LOW · F5
passWithNoTests lets an empty or fully-filtered suite report green, so a misconfigured glob or a
deleted file goes unnoticed.
Diagnostic codes (opt-in, OFF by default)¶
Family F8: not false-green (the test still protects), shown only on a diagnostic pass.
| Code | What it flags |
|---|---|
| D1 | assertion roulette: many assertions in one test, none identifying which failed |
| D3 | duplicate assert: the same assertion appears more than once |
| D4 | untitled it.each / test.each cases: a failing case is named only by index |
| D6 | console.* in a test body: a debug artifact that bypasses the oracle |
| D7 | anonymous test: empty or missing description |
| D8 | magic number in an assertion: a bare numeric literal instead of a named constant |
| M2 | over-long test body: exceeds the line-count threshold |
Look-alikes: do NOT flag¶
expect(fn).not.toThrow()after a setup call: the absence of an exception is the meaningful assertion.expect(emitter).toHaveProperty('on'): duck-typing check; interface presence is the contract.expectTypeOf(v).toEqualTypeOf<T>(): a compile-time type assertion; fails attsc, not C5.expect(result).toMatchObject({ kind: 'error' })on a discriminated union: the discriminant determines the branch; not C6.vi.mocked(fn)/jest.mocked(fn): typed mock wrappers, the TS equivalent ofautospec; not C13b.done()after a series of real assertions: the smell isdone()before the assertion, not after.