Skip to content

JS testing uplift: coverage + extracted validator + real jQuery + panel tests (#163) - #164

Merged
rpgmem merged 4 commits into
mainfrom
claude/js-test-uplift
May 12, 2026
Merged

JS testing uplift: coverage + extracted validator + real jQuery + panel tests (#163)#164
rpgmem merged 4 commits into
mainfrom
claude/js-test-uplift

Conversation

@rpgmem

@rpgmem rpgmem commented May 12, 2026

Copy link
Copy Markdown
Owner

Closes #163.

Summary

Four sprints implementing the JS testing uplift from #163. Each commit is independently revertable.

Sprint Commit What
S1 27a6f95 @vitest/coverage-v8 + clover-XML floor enforcer mirroring the PHP gate in ci.yml. Also switched loadScript from new Function to vm.runInThisContext (needed for V8 coverage attribution).
S2 88ae8b0 Extracted analyzeDateTimeOrder into a real module assets/js/ffc-geofence-validation.js. Dropped the window.FFCGeofenceAdmin = { ... } export that only existed for tests.
S3 06c4fa8 Real jQuery in the test env (via jquery/factory), replacing the brittle Proxy stub. Prerequisite for the panel tests in S4.
S4 f47f38f 19 new render tests for the certificates / appointments / audience dashboard panels.

Coverage delta

Per npm run test:js:coverage on main vs on this branch:

Before (main) After (this PR)
Total tests 23 42
Overall line coverage 3.78% 7.37%
ffc-geofence-validation.js n/a (didn't exist) 100%
ffc-geofence-frontend.js 39.26% 39.26%
ffc-user-dashboard-certificates.js 0% 76.06%
ffc-user-dashboard-appointments.js 0% 66.16%
ffc-user-dashboard-audience.js 0% 82.82%
ffc-user-dashboard-core.js 0% 50.99%
ffc-user-dashboard-cal-export.js 0% 69.59%

The coverage floor in lint.yml ratchets from 3 → 6 — a small buffer below 7.37% so a real regression fires the gate but a 0.1% slip doesn't.

Notable side-effects

  • Real bug caught by S1's vm.runInThisContext switch: V8 coverage was reporting 0% on every assets/js/ file even though the IIFE scripts were running. The previous new Function(code)(jQuery, $) form stripped the file identity from V8's profiler — runInThisContext with filename preserves it. Without this commit the gate would have been useless.
  • S2 carried a small behavioural footprint beyond pure refactor: the new ffc-geofence-validation.js becomes a separate script tag enqueued before ffc-geofence-admin. WordPress dependency ordering does the rest.
  • jQuery 4 changed its export shape between v3 and v4 — require('jquery') now auto-binds to the ambient window and jquery/factory carries the explicit-factory path. The S3 setup uses the factory so the jQuery instance binds to the SAME jsdom window Vitest set up (otherwise events / data caches the IIFE scripts install would live on a different window than the tests' queries — invisible).

Test plan

  • npm run lint:js — 0 errors, 9 unused-vars warnings unchanged.
  • npm run test:js42 / 42 OK (was 23).
  • npm run test:js:coverage — line coverage 7.37% ≥ floor 6%.
  • vendor/bin/phpunit — runs unchanged (no PHP source touched in this PR).
  • vendor/bin/phpstan analyse — runs unchanged.

https://claude.ai/code/session_01HiExaniSqvNBLpVTszCLNx


Generated by Claude Code

claude added 4 commits May 12, 2026 02:53
Today the Vitest job reports "23 tests pass" but says nothing about
how much of the 28-file `assets/js/` surface those tests cover. This
commit closes that gap — coverage is now measured and a floor is
enforced in CI, mirroring the PHP coverage gate in `ci.yml`.

Three changes:

1. `vitest.config.mjs` — adds a `coverage` block: V8 provider, clover
   + text + html reporters, output to `./coverage-js/`. Includes only
   `assets/js/**/*.js`, excludes minified output.

2. `tests/js/helpers.js` — switches the script-loading path from
   `new Function(code)(jQuery, $)` to `vm.runInThisContext(code,
   { filename: abs })`. The previous form ran code anonymously, so
   V8's coverage profiler couldn't attribute executed lines back to
   the source file — every `assets/js/` file reported 0% even when
   its IIFE was actually running. With `runInThisContext` + filename,
   coverage is real: ffc-geofence-admin.js shows 78.29% lines / 87.5%
   functions after this commit.

3. `.github/workflows/lint.yml` — the `jstest` job now runs
   `npm run test:js:coverage` (new package.json script). A follow-up
   step parses `coverage-js/clover.xml` and fails if line coverage
   drops below `JS_COVERAGE_FLOOR_LINES` (env var, set to `3`).

The 3% floor is deliberately under the current 3.78% baseline so the
gate doesn't fire on day one but does catch a regression. The intent
is to ratchet upward whenever new tests genuinely improve coverage
(S4 of this issue brings dashboard panels online and the floor will
move accordingly).

A small summary block is written to `$GITHUB_STEP_SUMMARY` (lines
pct / floor / raw counts) so reviewers see the delta on the PR
check page without digging through the HTML report.

`.gitignore` gains `/coverage-js/` to keep the local report tree
out of git.

https://claude.ai/code/session_01HiExaniSqvNBLpVTszCLNx
…(S2 of #163)

`analyzeDateTimeOrder` previously lived inside the
`jQuery(document).ready(...)` IIFE of `ffc-geofence-admin.js` and was
exposed on `window.FFCGeofenceAdmin` purely so the unit tests could
reach it. That was production code carrying weight for tests — fine
for one helper, but the pattern would scale poorly as more pure
helpers need coverage.

This commit moves the helper to a real module:

  - New file `assets/js/ffc-geofence-validation.js` — standalone IIFE
    with no jQuery dependency that defines `analyzeDateTimeOrder` and
    publishes it on `window.FFCGeofenceValidation`. Same byte-for-byte
    logic as before; only the home and the namespace changed.

  - `ffc-geofence-admin.js`: drops the inline function definition,
    reads from `window.FFCGeofenceValidation.analyzeDateTimeOrder`
    instead. The trailing `window.FFCGeofenceAdmin = { ... }` export
    that only existed to make tests work is gone.

  - PHP enqueue (`FormEditor::enqueue_scripts`): registers
    `ffc-geofence-validation` first (no deps, no jQuery) and lists it
    as a dependency of `ffc-geofence-admin` so WordPress emits the
    `<script>` tags in the right order.

  - Test (`tests/js/geofence-admin.test.js`): loads the new validation
    module directly instead of the admin IIFE. All 10 existing tests
    pass byte-identical (logic unchanged).

Coverage effect: ffc-geofence-validation.js sits at 87% lines covered
on its own now (the file is essentially just the helper); the admin
file's % drops because its remaining surface is jQuery wiring that
the current stub can't exercise — S3 (real jQuery) and S4 (panel
tests with DOM fixtures) will pull the wider numbers up.

Minified output rebuilt via `npm run build:js`.

https://claude.ai/code/session_01HiExaniSqvNBLpVTszCLNx
`tests/js/setup.js` previously installed a Proxy that returned chainable
no-ops for every jQuery method. That worked for the three pure helpers
under test (validateDateTime, pickHideMode, analyzeDateTimeOrder —
none touch jQuery internally) but lied for anything that genuinely
relied on jQuery semantics: `.val()` always returned `''`, `.is(':checked')`
always returned `false`, `.on(...)` didn't actually register a handler,
etc.

S4 of this issue brings dashboard panels online; those lean heavily on
real jQuery (filter bars, pagination, page-size selectors, AJAX-loading
states, event delegation). Expanding the stub into a half-baked jQuery
clone would have been more code than just installing the real thing.

Changes:

  - `jquery@^4.0.0` added as a devDep (~95KB, no peer deps).
  - `tests/js/setup.js` imports `jQueryFactory` from `jquery/factory`,
    binds it to the jsdom `globalThis.window`, then publishes the
    resulting function on both `globalThis.{jQuery,$}` and
    `globalThis.window.{jQuery,$}` so the IIFE wrappers in
    `assets/js/*.js` resolve `jQuery` / `$` to the real implementation
    regardless of which they reference.

jQuery 4 changed its module shape — `require('jquery')` now auto-binds
to the ambient window (and throws when no window is present), so the
explicit factory path is the one that lets us bind to the SAME jsdom
window Vitest set up. Without that, events / data caches the IIFE
scripts install would live on a different window than the tests'
queries — invisible.

All 23 existing tests still pass byte-identical; jQuery isn't called
from the helpers they cover, so this is a no-op for current behaviour
and a prerequisite for S4.

The `vi.fn()` alert stub is preserved — jsdom throws on `alert()` and
geofence-admin calls it from `validateGeoMethods()`.

https://claude.ai/code/session_01HiExaniSqvNBLpVTszCLNx
19 new tests covering the panels shipped via #142 (the
ffc-user-dashboard.js god-object split). Each panel exposes
`panel.render(state, page)` that writes to a known DOM root —
straightforward to drive in jsdom now that S3 installed real jQuery.

  - dashboard-certificates.test.js (7 tests) — empty state, basic
    row rendering, pagination across 25 items, magic-link PDF
    button visibility, consent-yes/no CSS classes, filter bar always
    rendered, search filter matching.
  - dashboard-appointments.test.js (6 tests) — empty state, three-
    section split (upcoming / past / cancelled), omission of empty
    sections, cancelled-row / past-row CSS classes, receipt-button
    visibility, status CSS classes.
  - dashboard-audience.test.js (6 tests) — empty state, basic row
    rendering, audience-tag chip count per row, three-section split,
    past-row / cancelled-row classes, search filter matching.

Shared infrastructure (`tests/js/dashboard-fixtures.js`):
  - `installDashboardFixtures()` populates `window.ffcDashboard.strings`
    with the i18n keys the panels read (otherwise the rendered HTML
    contains literal "undefined") and writes the `#tab-*` containers
    each panel targets to `document.body`.
  - `loadDashboardCore()` / `loadPanel(name)` are thin wrappers over
    the existing `loadScript` helper.

Coverage effect (per `npm run test:js:coverage`):
  - Overall: 3.78% → 7.37% lines.
  - ffc-user-dashboard-certificates.js: 76% lines / 77% branches.
  - ffc-user-dashboard-appointments.js: 66% lines / 78% branches.
  - ffc-user-dashboard-audience.js: 83% lines / 63% branches.
  - ffc-user-dashboard-core.js: 51% lines / 64% branches (helpers
    + the dispatch path the renderers exercise).
  - ffc-user-dashboard-cal-export.js: 70% lines (incidental — the
    appointments panel composes it on each render).

The JS coverage floor in `lint.yml` ratchets from 3 → 6 (still a
small buffer below the new 7.37% baseline so the gate doesn't fire
on a tiny regression but does on a real one).

https://claude.ai/code/session_01HiExaniSqvNBLpVTszCLNx
@rpgmem
rpgmem marked this pull request as ready for review May 12, 2026 03:24
@rpgmem
rpgmem merged commit 0e6b4de into main May 12, 2026
16 checks passed
@rpgmem
rpgmem deleted the claude/js-test-uplift branch May 12, 2026 03:24
rpgmem pushed a commit that referenced this pull request May 13, 2026
Bumps:
- ffcertificate.php plugin header `Version` → 6.5.3
- FFC_VERSION constant → 6.5.3
- readme.txt Stable tag → 6.5.3

CHANGELOG: collapses the Unreleased block into a 6.5.3 (2026-05-13)
section and adds the items merged since 6.5.2 that the prior diff was
missing:

Changed:
  - thumbmarkjs 1.8.1 → 1.9.0 (this PR)
  - jQuery UI theme 1.14.1 → 1.14.2 (this PR)
  - Recruitment CSV import: CPF/RF normalisation at parse time
    (#172, shipped via #182).

Fixed:
  - Form-editor groups 7/8 toggle-off persistence (already on
    Unreleased; kept).
  - Form-editor public CSV CPF "No" reverting to "Audit" (already on
    Unreleased; kept).
  - Reregistration form: $.trim() TypeError under jQuery 4 (#185).
  - Reregistration form: [name^="fields["] selector rejected under
    jQuery 4, getFields() returned {} (#185).

Internal:
  - JS coverage uplift 7.37% → 72.84% across multiple sprints
    (#162 / #164 / #166 / #168-#171 / #174-#180 / #183-#186),
    floor ratcheted 3 → 70.
  - Coverage job timeout 15 → 20 min (#181).
  - The 9 pre-existing ESLint no-unused-vars warnings cleared (#188).
  - CLAUDE.md added documenting auto-merge convention + CI gates +
    test-infrastructure notes (#187).
rpgmem added a commit that referenced this pull request May 13, 2026
…1 → 1.14.2 (#189)

* chore(deps): bump thumbmarkjs 1.8.1 → 1.9.0 and jQuery UI theme 1.14.1 → 1.14.2

Two upstream patch bumps that keep the API/CSS surface this plugin
relies on:

thumbmarkjs 1.8.1 → 1.9.0:
- New vendored bundle: `libs/js/thumbmark-1.9.0.umd.js` (33 KB).
- Old bundle removed.
- `FFC_THUMBMARK_VERSION` bumped; `Frontend::enqueue_*` resolves the
  filename from the constant so the enqueue call updates automatically.
- Surface used by `assets/js/ffc-device-signals.js`
  (`window.ThumbmarkJS.setOption('logging', false)`,
  `stableStringify`, `getFingerprintData()`) verified present in the
  new bundle and exercised by the existing JS suites
  (device-signals-deep.test.js / device-signals-and-frontend.test.js)
  — both still pass.
- `DeviceSignalsLoggingOffTest::test_vendored_thumbmarkjs_present_at_pinned_path`
  path + redownload-URL hint updated to track the new bundle.

jQuery UI theme 1.14.1 → 1.14.2:
- `libs/css/jquery-ui-smoothness.css` replaced with the 1.14.2 release.
- The CSS payload is byte-identical between the two upstream releases;
  the diff is just the file-header comment moving to
  `v1.14.2 - 2026-01-28`. Visible change for users is the cache-bust
  version string `wp_enqueue_style` emits.
- `FFC_JQUERY_UI_VERSION` bumped to `'1.14.2'`.

Tests: 3893 PHP + 487 JS still green.

* release: 6.5.2 → 6.5.3, consolidate CHANGELOG for the maintenance cut

Bumps:
- ffcertificate.php plugin header `Version` → 6.5.3
- FFC_VERSION constant → 6.5.3
- readme.txt Stable tag → 6.5.3

CHANGELOG: collapses the Unreleased block into a 6.5.3 (2026-05-13)
section and adds the items merged since 6.5.2 that the prior diff was
missing:

Changed:
  - thumbmarkjs 1.8.1 → 1.9.0 (this PR)
  - jQuery UI theme 1.14.1 → 1.14.2 (this PR)
  - Recruitment CSV import: CPF/RF normalisation at parse time
    (#172, shipped via #182).

Fixed:
  - Form-editor groups 7/8 toggle-off persistence (already on
    Unreleased; kept).
  - Form-editor public CSV CPF "No" reverting to "Audit" (already on
    Unreleased; kept).
  - Reregistration form: $.trim() TypeError under jQuery 4 (#185).
  - Reregistration form: [name^="fields["] selector rejected under
    jQuery 4, getFields() returned {} (#185).

Internal:
  - JS coverage uplift 7.37% → 72.84% across multiple sprints
    (#162 / #164 / #166 / #168-#171 / #174-#180 / #183-#186),
    floor ratcheted 3 → 70.
  - Coverage job timeout 15 → 20 min (#181).
  - The 9 pre-existing ESLint no-unused-vars warnings cleared (#188).
  - CLAUDE.md added documenting auto-merge convention + CI gates +
    test-infrastructure notes (#187).

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

[Testes JS] Coverage + extrair validador + real jQuery + panel tests

2 participants