Files
Nitro-V3/src/components/fortune-wheel/wheelPrizeTier.test.ts
T
simoleo89 ccebcad8a8 feat(fortune-wheel): celebration reveal, spin animation, prize editor add/remove
Player experience:
- Tiered win celebration overlay (WheelWinReveal): quiet message for the
  "nothing" slice, lighter reveal for common prizes, full confetti +
  jackpot glow for rare ones. Rarity classified client-side by type +
  amount (wheelPrizeTier), shared icon rendering (wheelPrizeIcon).
- Three-phase spin motion (wind-back -> overshoot -> settle) with a
  reduced-motion fast path; responsive wheel scaling via ResizeObserver.

Reveal-timing fix:
- The server pushes the refreshed winners list (which already contains the
  just-won prize) the instant it answers the spin, ~5s before the wheel
  stops. useFortuneWheel now buffers that update mid-spin and flushes it in
  finishSpin so the prize is no longer spoiled in the winners panel.
- handleTransitionEnd only reacts to the wheel's own transform transition,
  so a child icon's bubbling transitionend can't advance the spin phase
  machine early.

Prize editor (admin):
- Add/Remove prize buttons in FortuneWheelSettingsView. New rows carry a
  negative temp id collapsed to 0 on the wire (server inserts); removed rows
  are simply omitted (server soft-disables). Requires the matching emulator
  change to WheelManager.savePrize / WheelAdminSavePrizesEvent.

i18n: wheel.win.* and rarevalues.editor.add/remove in en/it/nl.
2026-05-31 10:47:51 +02:00

56 lines
1.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { CREDITS_RARE_THRESHOLD, getPrizeTier, POINTS_RARE_THRESHOLD } from './wheelPrizeTier';
const makePrize = (overrides: Partial<{ type: string; amount: number }>) => ({
id: 1,
type: 'credits',
spriteId: 0,
badgeCode: '',
amount: 0,
pointsType: -1,
label: '',
...overrides
}) as any;
describe('getPrizeTier', () =>
{
it('returns "common" for a null prize (defensive default)', () =>
{
expect(getPrizeTier(null)).toBe('common');
});
it('classifies the "nothing" slice as "none"', () =>
{
expect(getPrizeTier(makePrize({ type: 'nothing' }))).toBe('none');
});
it('classifies items and badges as "rare"', () =>
{
expect(getPrizeTier(makePrize({ type: 'item' }))).toBe('rare');
expect(getPrizeTier(makePrize({ type: 'badge' }))).toBe('rare');
});
it('classifies a free spin as "common"', () =>
{
expect(getPrizeTier(makePrize({ type: 'spin', amount: 1 }))).toBe('common');
});
it('tiers credits by the threshold', () =>
{
expect(getPrizeTier(makePrize({ type: 'credits', amount: CREDITS_RARE_THRESHOLD - 1 }))).toBe('common');
expect(getPrizeTier(makePrize({ type: 'credits', amount: CREDITS_RARE_THRESHOLD }))).toBe('rare');
expect(getPrizeTier(makePrize({ type: 'credits', amount: CREDITS_RARE_THRESHOLD + 1000 }))).toBe('rare');
});
it('tiers points by the threshold', () =>
{
expect(getPrizeTier(makePrize({ type: 'points', amount: POINTS_RARE_THRESHOLD - 1 }))).toBe('common');
expect(getPrizeTier(makePrize({ type: 'points', amount: POINTS_RARE_THRESHOLD }))).toBe('rare');
});
it('falls back to "common" for unknown prize types', () =>
{
expect(getPrizeTier(makePrize({ type: 'mystery-future-type' }))).toBe('common');
});
});