Updates 396-hack catalog data, banner manifest, and Playwright fixtures for full banner coverage, zero gap counts, and Fakemon roster tags in the Generation sidebar.
200 lines
8.3 KiB
TypeScript
200 lines
8.3 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
test('homepage presents balanced navigation and facet summaries', async ({ page }) => {
|
|
await page.goto('/');
|
|
|
|
const primaryNav = page.getByRole('navigation', { name: 'Primary navigation' });
|
|
await expect(primaryNav.getByRole('link', { name: 'Catalog' })).toHaveAttribute('href', '/');
|
|
await expect(primaryNav.getByRole('link', { name: 'Browse' })).toHaveAttribute('href', '/#browse');
|
|
await expect(primaryNav.getByRole('link', { name: 'Play Queue' })).toHaveAttribute('href', '/queue/');
|
|
await expect(primaryNav.getByRole('link', { name: 'GBA' })).toHaveCount(0);
|
|
await expect(primaryNav.getByRole('link', { name: 'Difficulty' })).toHaveCount(0);
|
|
|
|
const heroStats = page.locator('.hero-stats');
|
|
await expect(heroStats).toContainText('396');
|
|
await expect(heroStats).toContainText('hacks');
|
|
await expect(heroStats).toContainText('Platforms');
|
|
await expect(heroStats).toContainText('GBA 201');
|
|
await expect(heroStats).toContainText('PC 77');
|
|
await expect(heroStats).toContainText('NDS 50');
|
|
await expect(heroStats).toContainText('GBC 37');
|
|
await expect(heroStats).toContainText('Types');
|
|
await expect(heroStats).toContainText('Expansion 165');
|
|
await expect(heroStats).toContainText('New Experience 165');
|
|
await expect(heroStats).toContainText('Difficulty 126');
|
|
await expect(heroStats).toContainText('QoL 88');
|
|
await expect(heroStats).toContainText('Status');
|
|
await expect(heroStats).toContainText('Complete 337');
|
|
await expect(heroStats).toContainText('Ongoing 30');
|
|
await expect(heroStats).toContainText('Beta 28');
|
|
});
|
|
|
|
test('homepage stat cards are polished and responsive', async ({ page }) => {
|
|
await page.goto('/');
|
|
|
|
const totalCard = page.locator('.stat-card-total');
|
|
await expect(totalCard).toHaveCSS('text-align', 'center');
|
|
await expect(totalCard.locator('strong')).toHaveCSS('font-size', /^(4[0-9]|5[0-9])px$/);
|
|
|
|
const firstStatItem = page.locator('.stat-list li').first();
|
|
await expect(firstStatItem.locator('.stat-icon')).toBeVisible();
|
|
await expect(firstStatItem.locator('.stat-label')).toHaveCSS('white-space', 'nowrap');
|
|
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
await page.goto('/');
|
|
|
|
const statsPanel = page.locator('.hero-stats-panel');
|
|
await expect(statsPanel.locator('summary')).toBeVisible();
|
|
await expect(statsPanel).toHaveAttribute('open', '');
|
|
|
|
const mobileMetrics = await page.locator('.hero-stats').evaluate((element) => {
|
|
const styles = window.getComputedStyle(element);
|
|
const cards = Array.from(element.querySelectorAll('.stat-card')).map((card) => card.getBoundingClientRect());
|
|
return {
|
|
columns: styles.gridAutoFlow,
|
|
overflowX: styles.overflowX,
|
|
firstTop: cards[0]?.top,
|
|
allSameRow: cards.every((card) => Math.abs(card.top - cards[0].top) < 2),
|
|
scrollWidth: element.scrollWidth,
|
|
clientWidth: element.clientWidth,
|
|
};
|
|
});
|
|
|
|
expect(mobileMetrics.columns).toBe('column');
|
|
expect(mobileMetrics.overflowX).toBe('auto');
|
|
expect(mobileMetrics.allSameRow).toBe(true);
|
|
expect(mobileMetrics.scrollWidth).toBeGreaterThan(mobileMetrics.clientWidth);
|
|
});
|
|
|
|
test('homepage browse summary links to grouped facet pages', async ({ page }) => {
|
|
await page.goto('/');
|
|
|
|
const browse = page.getByRole('region', { name: 'Browse by catalog facets' });
|
|
await expect(browse.getByRole('heading', { name: 'Browse the archive' })).toBeVisible();
|
|
await expect(browse.getByRole('link', { name: /GBA\s+201/ })).toHaveAttribute('href', '/browse/platform/gba/');
|
|
await expect(browse.getByRole('link', { name: /Difficulty\s+126/ })).toHaveAttribute(
|
|
'href',
|
|
'/browse/type/difficulty/',
|
|
);
|
|
await expect(browse.getByRole('link', { name: /Complete\s+337/ })).toHaveAttribute(
|
|
'href',
|
|
'/browse/status/complete/',
|
|
);
|
|
});
|
|
|
|
test('catalog search and filters update visible card count', async ({ page }) => {
|
|
await page.goto('/');
|
|
|
|
await expect(page).toHaveTitle(/ROM Hack Archive/);
|
|
await expect(page.getByRole('heading', { name: 'ROM Hack Archive' })).toBeVisible();
|
|
const catalogCards = page.locator('[data-card-grid] [data-card]:visible');
|
|
await expect(catalogCards).toHaveCount(396);
|
|
|
|
await page.getByPlaceholder('Search hacks').fill('Radical Red');
|
|
await expect(catalogCards).toHaveCount(5);
|
|
await expect(page.getByRole('region', { name: 'Hack catalog' }).getByRole('link', { name: /Open Pokémon Radical Red/ })).toBeVisible();
|
|
|
|
await page.getByPlaceholder('Search hacks').fill('');
|
|
await page.getByLabel('GBA').check();
|
|
await expect(catalogCards).toHaveCount(201);
|
|
|
|
await page.getByLabel('Difficulty').check();
|
|
await expect(catalogCards.first()).toBeVisible();
|
|
await expect(page.locator('[data-result-count]')).toContainText('shown');
|
|
});
|
|
|
|
test('desktop filter rail scrolls independently from the catalog page', async ({ page }) => {
|
|
await page.setViewportSize({ width: 1280, height: 720 });
|
|
await page.goto('/');
|
|
|
|
const rail = page.locator('.filter-rail');
|
|
await expect(rail).toBeVisible();
|
|
|
|
const metrics = await rail.evaluate((element) => {
|
|
const styles = window.getComputedStyle(element);
|
|
const firstList = element.querySelector('.filter-list');
|
|
const listStyles = firstList ? window.getComputedStyle(firstList) : null;
|
|
return {
|
|
clientHeight: element.clientHeight,
|
|
overflowY: styles.overflowY,
|
|
scrollHeight: element.scrollHeight,
|
|
railScrollbarColor: styles.scrollbarColor,
|
|
railScrollbarWidth: styles.scrollbarWidth,
|
|
listScrollbarColor: listStyles?.scrollbarColor,
|
|
listScrollbarWidth: listStyles?.scrollbarWidth,
|
|
viewportHeight: window.innerHeight,
|
|
};
|
|
});
|
|
|
|
expect(metrics.overflowY).toBe('auto');
|
|
expect(metrics.scrollHeight).toBeGreaterThan(metrics.clientHeight);
|
|
expect(metrics.clientHeight).toBeLessThan(metrics.viewportHeight);
|
|
expect(metrics.railScrollbarWidth).toBe('thin');
|
|
expect(metrics.listScrollbarWidth).toBe('thin');
|
|
expect(metrics.railScrollbarColor).not.toBe('auto');
|
|
expect(metrics.listScrollbarColor).not.toBe('auto');
|
|
});
|
|
|
|
test('sidebar filter titles sit inside their panels', async ({ page }) => {
|
|
await page.setViewportSize({ width: 1280, height: 720 });
|
|
await page.goto('/');
|
|
|
|
const panelMetrics = await page.locator('.filter-panel').evaluateAll((panels) =>
|
|
panels
|
|
.map((panel) => {
|
|
const legend = panel.querySelector('legend');
|
|
if (!legend) return null;
|
|
|
|
const panelBox = panel.getBoundingClientRect();
|
|
const legendBox = legend.getBoundingClientRect();
|
|
const styles = window.getComputedStyle(panel);
|
|
|
|
return {
|
|
title: legend.textContent?.trim(),
|
|
panelTop: panelBox.top,
|
|
panelLeft: panelBox.left,
|
|
legendTop: legendBox.top,
|
|
legendLeft: legendBox.left,
|
|
paddingTop: Number.parseFloat(styles.paddingTop),
|
|
paddingLeft: Number.parseFloat(styles.paddingLeft),
|
|
};
|
|
})
|
|
.filter(Boolean),
|
|
);
|
|
|
|
expect(panelMetrics).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ title: 'Platform' }),
|
|
expect.objectContaining({ title: 'Base' }),
|
|
]),
|
|
);
|
|
|
|
for (const metrics of panelMetrics) {
|
|
expect(metrics!.legendTop).toBeGreaterThanOrEqual(metrics!.panelTop + metrics!.paddingTop - 1);
|
|
expect(metrics!.legendLeft).toBeGreaterThanOrEqual(metrics!.panelLeft + metrics!.paddingLeft - 1);
|
|
}
|
|
});
|
|
|
|
test('detail page renders metadata and private file links', async ({ page }) => {
|
|
await page.goto('/hack/radical-red/');
|
|
|
|
await expect(page.getByRole('heading', { name: 'Pokémon Radical Red' })).toBeVisible();
|
|
await expect(page.getByText('Platform').first()).toBeVisible();
|
|
await expect(page.getByText('Roster').first()).toBeVisible();
|
|
await expect(page.getByText('Fakemon').first()).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: 'Files' })).toBeVisible();
|
|
await expect(page.getByRole('link', { name: 'ROM file' })).toHaveAttribute(
|
|
'href',
|
|
/https:\/\/romhacks-files\.ginnoir\.com\/_roms\/gba\/Pokemon%20-%20Radical%20Red/,
|
|
);
|
|
await expect(page.locator('body')).not.toContainText('undefined');
|
|
});
|
|
|
|
test('browse pages render static grouped lists', async ({ page }) => {
|
|
await page.goto('/browse/type/difficulty/');
|
|
|
|
await expect(page.getByRole('heading', { name: 'Difficulty' })).toBeVisible();
|
|
await expect(page.locator('[data-card]:visible')).toHaveCount(126);
|
|
await expect(page.getByRole('link', { name: /Open Pokémon Radical Red/ })).toBeVisible();
|
|
});
|