Public Access
feat(points): unlock items, progress bars and skins
Locked items show a progress bar until the goal is reached. Four CSS-only skins (Pastel, Retro CRT, Cyberpunk, Steam) in skins.css, light and dark. A skin overrides accent and corners; badges, borders, frames, effects and game art stay on top. New Platinum shine and Equalizer effects.
This commit is contained in:
1 parent
8598024bce
commit
b6f3d123fb
22 files changed
+622
-38
No files matched your search
@@ -19,12 +19,17 @@ const wallet = (extra = {}) => ({
|
||||
{code: 'FX_GOLD_SHINE', slot: 'COMPLETED_EFFECT', price: 300},
|
||||
{code: 'BG_GRID', slot: 'PAGE_BACKGROUND', price: 450},
|
||||
{code: 'BN_GRID', slot: 'BANNER_BACKGROUND', price: 300},
|
||||
{code: 'BADGE_FINISH_10', slot: 'BADGE', price: 0, track: 'GAMES_FINISHED', target: 10},
|
||||
{code: 'BADGE_STREAK_4', slot: 'BADGE', price: 0, track: 'FINISH_WEEKS', target: 4},
|
||||
{code: 'SKIN_PASTEL', slot: 'SKIN', price: 600},
|
||||
],
|
||||
progress: {GAMES_FINISHED: 7, FINISH_WEEKS: 9},
|
||||
owned: ['BADGE_PILE_SLAYER'],
|
||||
equipped: {},
|
||||
history: [
|
||||
{kind: 'FINISHED', ref: '620', gameName: 'Portal 2', points: 50, eventAt: '2026-09-30T10:00:00Z'},
|
||||
{kind: 'MONTH', ref: '2026-09', gameName: null, points: 40, eventAt: '2026-09-30T10:00:00Z'},
|
||||
{kind: 'UNLOCK', ref: 'BADGE_PERFECT_5', gameName: null, points: 0, eventAt: '2026-09-29T10:00:00Z'},
|
||||
],
|
||||
...extra,
|
||||
});
|
||||
@@ -60,6 +65,48 @@ describe('PointsPage', () => {
|
||||
expect(card('BANNER_BACKGROUND').querySelector('[data-effect="grid"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('shows a locked unlock item with its progress, a progress bar and no price or Buy button', async () => {
|
||||
await renderPage();
|
||||
|
||||
const item = screen.getByText('POINTS.ITEMS.BADGE_FINISH_10').closest('li');
|
||||
expect(within(item).getByText('POINTS.PROGRESS.GAMES_FINISHED:7')).toBeTruthy();
|
||||
const bar = within(item).getByRole('progressbar');
|
||||
expect(bar.getAttribute('aria-valuenow')).toBe('7');
|
||||
expect(bar.getAttribute('aria-valuemax')).toBe('10');
|
||||
expect(within(item).queryByRole('button')).toBeNull();
|
||||
});
|
||||
|
||||
it('caps the progress at the target', async () => {
|
||||
await renderPage();
|
||||
|
||||
const item = screen.getByText('POINTS.ITEMS.BADGE_STREAK_4').closest('li');
|
||||
expect(within(item).getByText('POINTS.PROGRESS.FINISH_WEEKS:4')).toBeTruthy();
|
||||
expect(within(item).getByRole('progressbar').getAttribute('aria-valuenow')).toBe('4');
|
||||
});
|
||||
|
||||
it('lets the user equip an unlock item once it is owned', async () => {
|
||||
await renderPage({'GET /api/rewards/wallet': json(200, wallet({owned: ['BADGE_FINISH_10']}))});
|
||||
|
||||
const item = screen.getByText('POINTS.ITEMS.BADGE_FINISH_10').closest('li');
|
||||
expect(within(item).getByRole('button', {name: 'POINTS.EQUIP'})).toBeTruthy();
|
||||
expect(within(item).queryByRole('progressbar')).toBeNull();
|
||||
});
|
||||
|
||||
it('previews a skin with its data-skin', async () => {
|
||||
await renderPage();
|
||||
|
||||
const card = screen.getByText('POINTS.SLOTS.SKIN').closest('[data-slot="card"]');
|
||||
expect(card.querySelector('[data-skin="pastel"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('shows an UNLOCK row with the item name and no number', async () => {
|
||||
await renderPage();
|
||||
|
||||
const row = screen.getByText(/POINTS.KINDS.UNLOCK/).closest('li');
|
||||
expect(row.textContent).toContain('POINTS.ITEMS.BADGE_PERFECT_5');
|
||||
expect(row.textContent).not.toMatch(/\+?0$/);
|
||||
});
|
||||
|
||||
it('holds the frame of previews that are off screen', async () => {
|
||||
const callbacks = [];
|
||||
vi.stubGlobal('IntersectionObserver', class {
|
||||
|
||||
Reference in new issue
Block a user