JS testing uplift: coverage + extracted validator + real jQuery + panel tests (#163) - #164
Merged
Conversation
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
3 tasks
rpgmem
marked this pull request as ready for review
May 12, 2026 03:24
3 tasks
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #163.
Summary
Four sprints implementing the JS testing uplift from #163. Each commit is independently revertable.
27a6f95@vitest/coverage-v8+ clover-XML floor enforcer mirroring the PHP gate inci.yml. Also switchedloadScriptfromnew Functiontovm.runInThisContext(needed for V8 coverage attribution).88ae8b0analyzeDateTimeOrderinto a real moduleassets/js/ffc-geofence-validation.js. Dropped thewindow.FFCGeofenceAdmin = { ... }export that only existed for tests.06c4fa8jquery/factory), replacing the brittle Proxy stub. Prerequisite for the panel tests in S4.f47f38fCoverage delta
Per
npm run test:js:coverageonmainvs on this branch:ffc-geofence-validation.jsffc-geofence-frontend.jsffc-user-dashboard-certificates.jsffc-user-dashboard-appointments.jsffc-user-dashboard-audience.jsffc-user-dashboard-core.jsffc-user-dashboard-cal-export.jsThe coverage floor in
lint.ymlratchets 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
vm.runInThisContextswitch: V8 coverage was reporting 0% on everyassets/js/file even though the IIFE scripts were running. The previousnew Function(code)(jQuery, $)form stripped the file identity from V8's profiler —runInThisContextwithfilenamepreserves it. Without this commit the gate would have been useless.ffc-geofence-validation.jsbecomes a separate script tag enqueued beforeffc-geofence-admin. WordPress dependency ordering does the rest.require('jquery')now auto-binds to the ambient window andjquery/factorycarries 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:js— 42 / 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