Skip to content

JS coverage deep: profile + device-signals + frontend (#173) - #174

Merged
rpgmem merged 4 commits into
mainfrom
claude/js-coverage-deep-profile-signals-frontend
May 12, 2026
Merged

JS coverage deep: profile + device-signals + frontend (#173)#174
rpgmem merged 4 commits into
mainfrom
claude/js-coverage-deep-profile-signals-frontend

Conversation

@rpgmem

@rpgmem rpgmem commented May 12, 2026

Copy link
Copy Markdown
Owner

Closes #173.

Summary

Four sprints (I + J + K + L) from #173. Each commit independently revertable.

Sprint Commit What
I fb243ea 15 tests for profile panel save / password change / LGPD privacy / notifications. Profile 40% → 86%.
J 824b014 10 tests for device-signals fingerprint pipeline end-to-end (mocked SubtleCrypto + ThumbmarkJS). Device-signals 38% → 96%.
K 239d177 11 tests for frontend.js form-submission flow (validation, success, error, rate-limit, refresh-captcha branches). Frontend.js 27% → 56%.
L 47a9886 Floor ratchet 24 → 28.

Coverage delta

Before (main) After (this PR)
Total tests 172 208 (+36)
Overall line coverage 25.78% 30.09%
ffc-user-dashboard-profile.js 40% 86%
ffc-device-signals.js 38% 96%
ffc-frontend.js 27% 56%
JS_COVERAGE_FLOOR_LINES 24 28

Mocking infrastructure introduced (reusable)

  • fakeDigest(algo, data) — deterministic 32-byte hash returning Promise; varies with input so different signals produce different hex strings.
  • installCryptoMock() — replaces window.crypto with a stub { subtle: { digest: fakeDigest }, randomUUID, getRandomValues }.
  • installThumbmarkMock(fingerprintData) — replaces window.ThumbmarkJS with vi-tracked { setOption, stableStringify, getFingerprintData }.
  • mockAjaxSuccess(response) / mockAjaxError(xhr)vi.spyOn(window.$, 'ajax') patterns now used across 4 test files.

Notes

  • $(document).ready(cb) deferral — frontend.js and several panels register their event delegates inside the ready callback. Tests with beforeAll(async () => { ...; await flush(); }) ensure delegates are wired before the first test triggers an event.
  • window.crypto is non-writable in jsdom — tests use Object.defineProperty(window, 'crypto', { value, configurable: true }) to swap and restore.
  • Magic-link verification deferred — runs once via $(document).ready + setTimeout(100), reads window.location. Testing different URL configurations would need either re-loading the script (which duplicates the submit delegate) or exposing the function for direct call. Both have downsides bigger than the test value; tracked as a follow-up.
  • Profile LGPD / notification sections — emitted conditionally by render(). Tests inject the buttons inline when the template omits them, so the click handler is the unit under test regardless of the preference shape.

Test plan

  • npm run test:js208 / 208 OK (was 172).
  • npm run test:js:coverage — line coverage 30.09%, gate (floor 28) passes.
  • npm run lint:js — clean (9 pre-existing unused-vars warnings unchanged).
  • vendor/bin/phpunit — runs unchanged (no PHP touched).

What's deferred

  • Magic-link verification + setupDynamicMaskObserver in frontend.js.
  • ffc-csv-download.js (668 LOC, File API).
  • ffc-reregistration-frontend.js (507 LOC).
  • Admin pages (ffc-admin.js, field-builder, submission-edit, etc.) — next sprint per the roadmap.
  • Calendar subsystem.

https://claude.ai/code/session_01HiExaniSqvNBLpVTszCLNx


Generated by Claude Code

claude added 4 commits May 12, 2026 14:53
…ws (Sprint I of #173)

15 tests for the action-handler paths that #168 S4 deliberately
skipped. The read-view render is already covered by
dashboard-profile.test.js; this file drives the document-level event
delegates installed by `panel.bindEvents()` and mocks `$.ajax` to
exercise the success / error branches.

Coverage:
  - ffc-user-dashboard-profile.js: 40.21% → 86.50% lines.
  - Overall: 25.78% → 27.35%.

What's covered:

  showEditForm (3):
    - replaces read view with edit form, populates fields from state
    - no-op when state is null (defensive)
    - cancel button restores the read view from preserved state

  saveProfile (3):
    - sends PUT user/profile with form values serialised as JSON
    - on success: state replaced, read view re-rendered
    - on error: server message shown, save button re-enabled

  changePassword (5):
    - rejects blank fields (no AJAX fired)
    - rejects mismatched new+confirm
    - rejects <8-char passwords
    - on success: POST user/change-password, password fields cleared
    - on error: server message surfaced

  privacyRequest LGPD (3):
    - Export button POSTs without confirmation prompt
    - Delete button asks confirm; declines cancel the AJAX
    - Delete button proceeds when confirmation accepted

  saveNotificationPreferences (1):
    - .ffc-notif-toggle change → AJAX, response merged into state

Pattern:
  - beforeAll() installs fixtures + invokes `panel.bindEvents()` so
    the document-level delegates land before the first test triggers
    a click. (init() isn't called by the harness; bindEvents is the
    moral equivalent for the panel's local handlers.)
  - Each test calls `panel.render(PROFILE_FIXTURE)` to materialise
    the DOM, then triggers user input via `$(selector).trigger(...)`
    or direct DOM property writes.
  - $.ajax is replaced via vi.spyOn with the canned success/error
    payload.
  - vi.spyOn(window, 'confirm') drives the LGPD-delete branch.

The LGPD + notification sections are emitted conditionally by
`render()`; tests inject the buttons inline when the rendered
template omits them, so the click handler is the unit under test
regardless of the template's preference shape.

https://claude.ai/code/session_01HiExaniSqvNBLpVTszCLNx
…t J of #173)

10 tests for the SubtleCrypto + ThumbmarkJS-bound paths that Sprint G
of #170 deferred. End-to-end exercise: mock SubtleCrypto.digest with
a deterministic 32-byte fake, mock ThumbmarkJS with a fixture
fingerprint payload, render a submission form, load the script,
flush microtasks, and assert against the JSON written into
`<input name="ffc_device_signals">`.

Coverage:
  - ffc-device-signals.js: 38.27% → 95.88% lines (100% functions).
  - Overall: 27.35% → 28.61%.

What's covered:
  - No-form page: pipeline runs but never injects an input.
  - Multi-form page: each form gets its own hidden input populated.
  - Hash output: each enabled signal lands as 64-char hex.
  - Allowlist gate: signals absent from ffc_device_config.signals
    are filtered before hashing.
  - Cookie signal + localStorage roundtrip: fresh UUID written on
    first load, reused on second load (validated against the
    script's `/^[0-9a-f-]{30,40}$/i` shape check).
  - getRandomValues fallback when crypto.randomUUID is unavailable.
  - ThumbmarkJS rejection: collectSignals falls back to cookie-only
    payload (graceful degradation).
  - stableStringify failure: falls back to JSON.stringify on the
    `screen` signal path.
  - UA coarse-graining: two UAs differing only in 3-part patch
    version hash identically once the regex strips them.

Mocking infrastructure introduced (reusable for future sprints):
  - `installCryptoMock()` — replaces window.crypto with a stub
    SubtleCrypto + randomUUID + getRandomValues.
  - `installThumbmarkMock(fixture)` — replaces window.ThumbmarkJS
    with a vi-tracked setOption/stableStringify/getFingerprintData
    triple.
  - `fakeDigest(algo, data)` — deterministic 32-byte hash that
    varies with input so different signals produce different hex
    strings but stable across runs.

Timing:
  - `flush()` awaits two `setTimeout(_, 0)` ticks to settle the
    chained Promises inside collectSignals (getFingerprintData →
    forEach → Promise.all → input.value =).

https://claude.ai/code/session_01HiExaniSqvNBLpVTszCLNx
…173)

11 tests for `handleFormSubmission` in assets/js/ffc-frontend.js —
the form-submit AJAX pipeline that Sprint G of #170 deferred.
Pattern: load FFC core + frontend-helpers + frontend.js in
beforeAll (with an async flush so $(document).ready fires before
the first test), then dispatch synthetic submit events and mock
$.ajax to drive each response branch.

Coverage:
  - ffc-frontend.js: 27% → 55.8% lines (60% functions).
  - Overall: 28.61% → 30.09%.

What's covered:

  Required-field validation (2):
    - blocks submission when a required input is empty; marks the
      input with `.ffc-field-error` + `aria-invalid="true"`.
    - injects an accessible alert with the i18n string above the
      form (role=alert).

  AJAX payload shape (1):
    - sends POST with serialised form data + action=ffc_submit_form
      + nonce=<localized> to the ajax_url.

  Success branch (3):
    - replaces form HTML with response.data.html.
    - adds data-pdf-data attribute on the download button when
      pdf_data is in the response.
    - triggers window.ffcGeneratePDF after 500ms via setTimeout
      (uses vi.useFakeTimers to fast-forward).

  Failure branch (2):
    - response.success=false → FFC.Frontend.UI.showFormError shows
      the server message.
    - refresh_captcha → FFC.Frontend.UI.refreshCaptcha updates the
      captcha label + hash in place.

  Error branches (2):
    - rate_limit responseJSON → FFC.Frontend.RateLimit.show called.
    - generic error → connection-error alert injected.

  showAccessibleAlert helper (1):
    - second alert replaces (not stacks) the first.

Magic-link verification deferred: it runs once via
`$(document).ready` + setTimeout(100), reading window.location
state. Testing different URL configurations would need re-loading
the script per scenario (which duplicates the submit delegate) or
exposing the function for direct call. Both options have downsides
larger than the test value; left for a follow-up that decides on
either an exposure pattern or a Playwright-level integration test.

https://claude.ai/code/session_01HiExaniSqvNBLpVTszCLNx
#173)

Sprints I+J+K of #173 added 36 new tests covering:
  - profile panel save/password/LGPD/notif handlers (Sprint I)
  - device-signals fingerprint pipeline end-to-end (Sprint J)
  - frontend.js form-submission AJAX flow (Sprint K)

Overall line coverage went from 25.78% to 30.09%. The floor moves
from 24 → 28, preserving the ~2% buffer the previous ratchets used.

https://claude.ai/code/session_01HiExaniSqvNBLpVTszCLNx
@rpgmem
rpgmem marked this pull request as ready for review May 12, 2026 15:40
@rpgmem
rpgmem merged commit d362a6e into main May 12, 2026
16 checks passed
@rpgmem
rpgmem deleted the claude/js-coverage-deep-profile-signals-frontend branch May 12, 2026 15:41
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.

[Coverage] Deep — profile panel + device-signals pipeline + frontend AJAX flows

2 participants