Skip to content

test(frontend): cover gamification reward deduplication and replay guards - #2203

Merged
K1NGD4VID merged 1 commit into
LabsCrypt:mainfrom
chidii:test/2138-xp-reward-dedup
Oct 26, 2026
Merged

K1NGD4VID merged 1 commit into
LabsCrypt:mainfrom
chidii:test/2138-xp-reward-dedup

Conversation

@chidii

@chidii chidii commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Closes #2138

Adds 21 deterministic tests for useGamificationStore pinning down what stops a reward being paid or shown twice.

Covered:

  • Level-up celebration fires once per level: not replayed after dismiss or re-check; one pending reward; multi-threshold jumps celebrate only the final level; exact-threshold and max-level boundaries; the level never lowers
  • XP is credited per call (callers own idempotency); recentXPGain id behaviour that GlobalXPGain de-duplicates on, including the same-millisecond case
  • Achievements: retried unlocks never add entries, unknown ids are ignored, progress updates never re-stamp an unlocked achievement
  • Persistence: a reload keeps XP/level without replaying the modal or XP animation; repeated rehydration never re-credits XP; reset is persisted

The new file is added to the scoped frontend regression step in ci.yml, so it runs in CI.

Scope note: the store itself has no XP de-duplication. addXP credits every call, and dedup exists only as the guards above plus the id check in GlobalXPGain. These tests document that contract. No production code is changed here. Findings while writing them, as possible follow-ups:

  1. useUserStore.updateUser calls addXP(30, "Profile completion") every time kycVerified is truthy, so a retried or refetched update pays the XP again.
  2. A legacy persisted payload that still contains showLevelUpModal / pendingLevelUp / recentXPGain replays them after rehydrate (partialize only protects what is written, not what is read). Verified with a probe.
  3. unlockAchievement re-stamps unlockedAt on every call (by reading the code; not asserted).
  4. recentXPGain.id is Date.now(), so two rewards in one millisecond share an id and GlobalXPGain shows one animation (asserted as current behaviour).

Verification: CI's exact scoped command passes, 93 tests; prettier clean; no new tsc errors.

Note: submitted after the Oct 22 due date; please let me know if it can still count.

…ards

Adds 21 deterministic tests for useGamificationStore pinning down what stops
a reward being paid or shown twice:

- level-up celebration fires once per level: not replayed after dismiss or
  re-check, one pending reward, multi-threshold jumps, exact-threshold and
  max-level boundaries, level never lowers
- XP: credited per call (callers own idempotency); recentXPGain id behaviour
  that GlobalXPGain de-duplicates on, including the same-millisecond case
- achievements: retried unlocks never add entries, unknown ids are ignored,
  progress updates never re-stamp an unlocked achievement
- persistence: reload keeps XP/level without replaying the modal or XP
  animation, repeated rehydration never re-credits XP, reset is persisted

The new file is added to the scoped frontend regression step in ci.yml so it
runs in CI.

Closes LabsCrypt#2138
@K1NGD4VID
K1NGD4VID merged commit da40c3c into LabsCrypt:main Oct 26, 2026
17 checks passed
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.

[test] Cover frontend XP reward deduplication

2 participants