feat: build compact BO2 grind companion with cumulative ranks
Some checks failed
Test and deploy staging / staging (push) Failing after 2m50s

This commit is contained in:
2026-10-08 21:06:19 +00:00
parent c2ba8febb4
commit c2bc9050c2
39 changed files with 1499 additions and 447 deletions

View File

@@ -227,7 +227,8 @@ test('weapon/player ranks, challenge progress and osu goals/scores/training pers
await page.getByLabel('Prestige', { exact: true }).fill('3');
await page.getByRole('button', { name: 'Save rank' }).click();
await expect(page.getByRole('dialog')).toHaveCount(0);
await expect(page.getByText('Level 42 · Prestige 3')).toBeVisible();
await expect(page.getByLabel('Player level and prestige')).toContainText('LEVEL42');
await expect(page.getByLabel('Player level and prestige')).toContainText('PRESTIGE3');
expect(
(
await action(page.request, cod + '?/saveRank', {
@@ -250,8 +251,10 @@ test('weapon/player ranks, challenge progress and osu goals/scores/training pers
await expect(
weaponCard.getByRole('button', { name: `Edit ${weapon.name} level and prestige` })
).toContainText('17');
await expect(weaponCard).toContainText('Max level reached');
await page.getByRole('tab', { name: 'challenges', exact: true }).click();
await weaponCard.getByRole('button', { name: `${weapon.name} camos and milestones` }).click();
await expect(page.getByRole('dialog')).toContainText('Max level reached');
await page.keyboard.press('Escape');
await page.getByRole('tab', { name: 'Challenges', exact: true }).click();
await expect(page.locator('article')).toHaveCount(36);
const goal = {
kind: 'goal',
@@ -526,7 +529,8 @@ test('live weapon controls save immediately, infer max level from prestige, and
const cod = '/projects/game-tracker/call-of-duty/black-ops-3';
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto(cod);
const card = page.getByRole('article', { name: weapon.name, exact: true });
await page.getByRole('button', { name: `${weapon.name} camos and milestones` }).click();
const card = page.getByRole('dialog', { name: weapon.name, exact: true });
const level = card.getByRole('button', { name: `Edit ${weapon.name} level and prestige` });
await expect(card.getByRole('button', { name: `Level down ${weapon.name}` })).toBeDisabled();
await card.getByRole('button', { name: `Level up ${weapon.name}` }).click();
@@ -552,6 +556,7 @@ test('live weapon controls save immediately, infer max level from prestige, and
);
expect(concurrent.every((r) => r.status === 200)).toBe(true);
await page.reload();
await page.getByRole('button', { name: `${weapon.name} camos and milestones` }).click();
await expect(level).toHaveText(/LEVEL5/);
await page.route('**/*adjustWeaponRank', (route) => route.abort());
await card.getByRole('button', { name: `Level up ${weapon.name}` }).click();
@@ -589,6 +594,7 @@ test('live weapon controls save immediately, infer max level from prestige, and
).status
).toBe(401);
await anonymous.dispose();
await page.keyboard.press('Escape');
const cards = await page
.locator('.weapon-card')
.evaluateAll((cards) => cards.map((c) => c.getBoundingClientRect().bottom));
@@ -748,7 +754,7 @@ test('visible progress reacts to sightings, catches and completed stories withou
await expect(
page.getByRole('progressbar', { name: 'Badges & trials', exact: true })
).toHaveAttribute('aria-valuenow', '12.5');
await page.getByRole('button', { name: 'All progress ↗', exact: true }).click();
await page.getByRole('button', { name: 'Full breakdown', exact: true }).click();
await expect(
page.getByRole('dialog', { name: 'Your Pokémon progress' }).getByRole('progressbar')
).toHaveCount(16);
@@ -799,7 +805,7 @@ test('partial COD challenges and osu goals visibly advance completion meters', a
await page.goto(cod);
const overall = page.getByRole('progressbar', { name: 'Game completion', exact: true });
await expect(overall).toHaveAttribute('aria-valuenow', '0');
await page.getByRole('tab', { name: 'challenges', exact: true }).click();
await page.getByRole('tab', { name: 'Challenges', exact: true }).click();
await page.getByLabel('Search challenges').fill(challenge.name);
const card = page
.getByRole('article')
@@ -815,7 +821,7 @@ test('partial COD challenges and osu goals visibly advance completion meters', a
await expect
.poll(async () => Number(await overall.getAttribute('aria-valuenow')))
.toBeGreaterThan(partial);
await page.getByRole('tab', { name: 'weapons', exact: true }).click();
await page.getByRole('tab', { name: 'Weapons', exact: true }).click();
await page.screenshot({ path: '/tmp/tracker-progress-cod.png' });
await action(page.request, '/projects/game-tracker/osu?/save', {
kind: 'goal',
@@ -839,3 +845,155 @@ test('partial COD challenges and osu goals visibly advance completion meters', a
page.getByRole('progressbar', { name: 'Full combo goals', exact: true })
).toHaveAttribute('aria-valuenow', '100');
});
test('BO2 armoury fits every gun, enforces caps and advances all rank progress with each level', async ({
page
}) => {
const userId = await register(page.request);
const cod = '/projects/game-tracker/call-of-duty/black-ops-2';
const [m27] =
await sql`select i.id from tracker_items i join tracker_games g on g.id=i.game_id where g.slug='black-ops-2' and i.name='M27'`;
await page.setViewportSize({ width: 1280, height: 720 });
await page.goto(cod);
await expect(page.locator('.weapon-card')).toHaveCount(40);
for (const [width, height] of [
[1280, 720],
[1920, 1080]
]) {
await page.setViewportSize({ width, height });
const bounds = await page.locator('.weapon-card').evaluateAll((cards) =>
cards.map((c) => {
const b = c.getBoundingClientRect();
const section = c.closest('section')!.getBoundingClientRect();
return {
name: c.getAttribute('aria-label'),
fits:
b.bottom <= section.bottom + 1 &&
b.bottom <= innerHeight &&
b.right <= innerWidth &&
b.height >= 29
};
})
);
expect(bounds.filter((b) => !b.fits)).toEqual([]);
expect(await page.evaluate(() => document.documentElement.scrollHeight)).toBeLessThanOrEqual(
height
);
await expect
.poll(() =>
page
.locator('.weapon-card img')
.evaluateAll((images) =>
images.every(
(i) => (i as HTMLImageElement).complete && (i as HTMLImageElement).naturalWidth > 0
)
)
)
.toBe(true);
}
for (const name of [
'M27',
'AN-94',
'DSR 50',
'Peacekeeper',
'MP7',
'Vector K10',
'Skorpion EVO'
]) {
await expect(
page
.getByRole('article', { name, exact: true })
.getByRole('button', { name: `View ${name} artwork` })
).toBeVisible();
}
const card = page.getByRole('article', { name: 'M27', exact: true });
const levels = page.getByRole('progressbar', { name: 'Weapon levels', exact: true });
const overall = page.getByRole('progressbar', { name: 'Game completion', exact: true });
await expect(levels).toHaveAttribute('aria-valuenow', '0');
await card.getByRole('button', { name: 'Level up M27', exact: true }).click();
await expect(card.getByRole('button', { name: 'Edit M27 level and prestige' })).toHaveText(
'LEVEL2/17'
);
const first = Number(await levels.getAttribute('aria-valuenow'));
expect(first).toBeGreaterThan(0);
const before = Number(await overall.getAttribute('aria-valuenow'));
await card.getByRole('button', { name: 'Level up M27', exact: true }).click();
await expect
.poll(async () => Number(await levels.getAttribute('aria-valuenow')))
.toBeGreaterThan(first);
await expect
.poll(async () => Number(await overall.getAttribute('aria-valuenow')))
.toBeGreaterThan(before);
expect(
(
await action(page.request, cod + '?/saveRank', {
itemId: String(m27.id),
level: '18',
prestige: '2'
})
).status
).toBe(400);
expect(
(
await action(page.request, cod + '?/saveRank', {
itemId: String(m27.id),
level: '17',
prestige: '3'
})
).status
).toBe(400);
expect(
(
await action(page.request, cod + '?/adjustWeaponRank', {
itemId: String(m27.id),
field: 'prestige',
delta: '1'
})
).status
).toBe(400);
await action(page.request, cod + '?/saveRank', {
itemId: String(m27.id),
level: '17',
prestige: '0'
});
const simultaneous = await Promise.all(
[1, 2].map(() =>
action(page.request, cod + '?/adjustWeaponRank', {
itemId: String(m27.id),
field: 'prestige',
delta: '1'
})
)
);
expect(simultaneous.filter((r) => r.status === 200)).toHaveLength(1);
expect(
(
await sql`select level,prestige from tracker_weapon_ranks where user_id=${userId} and item_id=${m27.id}`
)[0]
).toMatchObject({ level: 1, prestige: 1 });
await page.reload();
await expect(card).toContainText('18/51');
await action(page.request, cod + '?/saveRank', {
itemId: String(m27.id),
level: '17',
prestige: '2'
});
await action(page.request, cod + '?/saveRank', { level: '42', prestige: '3' });
await page.reload();
await expect(card).toContainText('51/51');
await expect(card.getByRole('button', { name: 'Level up M27' })).toBeDisabled();
await expect(page.getByRole('img', { name: 'Prestige 3 emblem' })).toBeVisible();
const progress = await overall.getAttribute('aria-valuenow');
await page.goto('/projects/game-tracker');
await expect(
page.getByRole('progressbar', { name: 'Black Ops II progress', exact: true })
).toHaveAttribute('aria-valuenow', progress!);
await page.goto(cod);
await page.setViewportSize({ width: 1280, height: 720 });
await page.screenshot({ path: '/tmp/bo2-verified-desktop.png' });
await page.setViewportSize({ width: 390, height: 844 });
await page.screenshot({ path: '/tmp/bo2-verified-mobile.png', fullPage: true });
await expect
.poll(() => page.evaluate(() => document.documentElement.scrollWidth))
.toBeLessThanOrEqual(390);
});