ai-code-battle/web/public/test-match-list.html
jedarden 724f5162b1 fix(spa): replace r2.aicodebattle.com with b2.aicodebattle.com
- Update docs-api.ts: use B2_BASE, remove R2 references, simplify fetch pattern
- Update docs-data.ts: replace R2 constant with B2
- Update docs-replay-format.ts: update curl example URL
- Update test files: update thumbnail URLs and comments

The SPA was migrated from Cloudflare R2 to Backblaze B2 storage.
All data-fetch URLs now point to b2.aicodebattle.com.
2026-06-16 23:13:08 -04:00

319 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Match List Page Test - Verify Real Matches</title>
<style>
body { margin: 0; padding: 20px; font-family: sans-serif; background: #0f172a; color: #e2e8f0; }
h1 { font-size: 1.2rem; margin-bottom: 10px; }
.test-container { display: flex; gap: 20px; }
.page-section { flex: 1; min-width: 600px; background: #1e293b; border-radius: 8px; padding: 15px; max-height: 90vh; overflow-y: auto; }
.results-section { width: 400px; background: #1e293b; padding: 15px; border-radius: 8px; max-height: 90vh; overflow-y: auto; }
.test-result { padding: 8px; margin: 5px 0; border-radius: 4px; font-size: 12px; }
.pass { background: #22c55e; color: white; }
.fail { background: #ef4444; color: white; }
.info { background: #3b82f6; color: white; }
.warn { background: #f59e0b; color: white; }
.controls { margin-top: 15px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
button { padding: 8px 16px; background: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #2563eb; }
button:disabled { background: #475569; cursor: not-allowed; }
.summary { padding: 10px; margin-top: 15px; border-radius: 8px; background: #334155; }
.summary h3 { margin: 0 0 10px 0; font-size: 14px; }
#match-list-preview { margin-top: 15px; }
#match-list-preview iframe { width: 100%; height: 600px; border: none; border-radius: 8px; }
.thumbnail-test { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.thumbnail-test img { width: 120px; height: 90px; object-fit: cover; border-radius: 6px; background: #334155; }
</style>
</head>
<body>
<h1>Match List Page Test - Verify Real Matches</h1>
<div class="test-container">
<div class="page-section">
<h2>Live Preview</h2>
<div id="match-list-preview">
<p>Loading match list page...</p>
</div>
</div>
<div class="results-section">
<h2>Test Results</h2>
<div id="test-results"></div>
<div class="summary" id="test-summary" style="display: none;">
<h3>Summary</h3>
<div id="summary-content"></div>
</div>
</div>
</div>
<script type="module">
let testsPassed = 0;
let testsFailed = 0;
let testsWarned = 0;
const results = document.getElementById('test-results');
const summary = document.getElementById('test-summary');
const summaryContent = document.getElementById('summary-content');
function addResult(name, status, message) {
const div = document.createElement('div');
let className = 'info';
let icon = '';
if (status === 'pass') { className = 'pass'; icon = '✓'; testsPassed++; }
else if (status === 'fail') { className = 'fail'; icon = '✗'; testsFailed++; }
else if (status === 'warn') { className = 'warn'; icon = '⚠'; testsWarned++; }
div.className = `test-result ${className}`;
div.textContent = `${icon} ${name}: ${message}`;
results.appendChild(div);
results.scrollTop = results.scrollHeight;
updateSummary();
}
function addInfo(message) {
const div = document.createElement('div');
div.className = 'test-result info';
div.textContent = message;
results.appendChild(div);
results.scrollTop = results.scrollHeight;
}
function updateSummary() {
const total = testsPassed + testsFailed + testsWarned;
if (total > 0) {
summary.style.display = 'block';
summaryContent.innerHTML = `
<div>Total: ${total} | Passed: ${testsPassed} | Failed: ${testsFailed} | Warnings: ${testsWarned}</div>
<div style="margin-top: 8px; font-size: 11px; color: #94a3b8;">
Success Rate: ${((testsPassed / total) * 100).toFixed(1)}%
</div>
`;
}
}
async function runTests() {
addInfo('=== Match List Page Verification ===');
addInfo('');
// Test 1: Fetch match index
addInfo('Test 1: Fetching /data/matches/index.json...');
try {
const matchResponse = await fetch('/data/matches/index.json');
if (!matchResponse.ok) throw new Error(`HTTP ${matchResponse.status}`);
const matchData = await matchResponse.json();
addResult('Fetch match index', 'pass', `Loaded with ${matchData.matches?.length || 0} matches`);
addResult('Match index has updated_at', !!matchData.updated_at, matchData.updated_at);
} catch (error) {
addResult('Fetch match index', 'fail', error.message);
return;
}
// Test 2: Fetch playlist index
addInfo('Test 2: Fetching /data/playlists/index.json...');
try {
const playlistResponse = await fetch('/data/playlists/index.json');
if (!playlistResponse.ok) throw new Error(`HTTP ${playlistResponse.status}`);
const playlistData = await playlistResponse.json();
addResult('Fetch playlist index', 'pass', `Loaded with ${playlistData.playlists?.length || 0} playlists`);
} catch (error) {
addResult('Fetch playlist index', 'fail', error.message);
}
// Test 3: Verify match cards have required fields
addInfo('Test 3: Verifying match card required fields...');
try {
const matchResponse = await fetch('/data/matches/index.json');
const matchData = await matchResponse.json();
if (matchData.matches && matchData.matches.length > 0) {
const firstMatch = matchData.matches[0];
// Check bot names
const hasBotNames = firstMatch.participants &&
firstMatch.participants.every((p: any) => p.name && typeof p.name === 'string');
addResult('Match cards have bot names', hasBotNames,
hasBotNames ? firstMatch.participants.map((p: any) => p.name).join(', ') : 'Missing bot names');
// Check turn count
addResult('Match cards have turn count', firstMatch.turns !== undefined,
`${firstMatch.turns ?? 'missing'} turns`);
// Check winner
addResult('Match cards have winner info', firstMatch.winner_id !== undefined,
firstMatch.winner_id ?? 'No winner_id');
// Check map ID
addResult('Match cards have map ID', !!firstMatch.map_id,
firstMatch.map_id || 'No map_id');
// Check participants have scores
const hasScores = firstMatch.participants &&
firstMatch.participants.every((p: any) => p.score !== undefined);
addResult('Match cards have scores', hasScores, 'All participants have scores');
// Check completed_at
addResult('Match cards have completion time', !!firstMatch.completed_at,
firstMatch.completed_at || 'No completed_at');
} else {
addResult('Match cards verification', 'fail', 'No matches in index');
}
} catch (error) {
addResult('Match cards verification', 'fail', error.message);
}
// Test 4: Verify Watch Replay links
addInfo('Test 4: Verifying Watch Replay links...');
try {
const matchResponse = await fetch('/data/matches/index.json');
const matchData = await matchResponse.json();
if (matchData.matches && matchData.matches.length > 0) {
const firstMatch = matchData.matches[0];
const replayUrl = `/replays/${firstMatch.id}.json.gz`;
// Test if replay file exists (or at least the URL is correctly formed)
addResult('Watch Replay link format', 'pass', `Link would be: ${replayUrl}`);
// Try to fetch the replay (may fail if not uploaded yet)
try {
const replayResponse = await fetch(replayUrl);
if (replayResponse.ok) {
addResult('Replay file accessible', 'pass', 'Replay file exists and is accessible');
} else {
addResult('Replay file accessible', 'warn', `Replay returns ${replayResponse.status} (may not be uploaded yet)`);
}
} catch (e) {
addResult('Replay file accessible', 'warn', 'Network error trying to fetch replay');
}
}
} catch (error) {
addResult('Watch Replay links', 'fail', error.message);
}
// Test 5: Verify curated playlist sections
addInfo('Test 5: Verifying curated playlist sections...');
try {
const playlistResponse = await fetch('/data/playlists/index.json');
const playlistData = await playlistResponse.json();
const curatedSlugs = ['best-of-week', 'biggest-upsets', 'closest-finishes'];
const foundPlaylists = playlistData.playlists.filter((p: any) => curatedSlugs.includes(p.slug));
addResult('Curated playlists exist', foundPlaylists.length > 0,
`Found ${foundPlaylists.length} of ${curatedSlugs.length} curated playlists`);
// Check each curated playlist
for (const slug of curatedSlugs) {
const playlist = playlistData.playlists.find((p: any) => p.slug === slug);
if (playlist) {
addResult(`Playlist "${slug}" has data`, playlist.match_count > 0,
`${playlist.match_count} matches`);
} else {
addResult(`Playlist "${slug}" exists`, 'warn', 'Playlist not found (may not have data yet)');
}
}
// Check empty state handling
const emptyPlaylists = playlistData.playlists.filter((p: any) => p.match_count === 0);
addResult('Empty playlists handled gracefully', true,
`${emptyPlaylists.length} empty playlists should show empty state`);
} catch (error) {
addResult('Curated playlists', 'fail', error.message);
}
// Test 6: Check thumbnails
addInfo('Test 6: Checking thumbnail availability...');
try {
const matchResponse = await fetch('/data/matches/index.json');
const matchData = await matchResponse.json();
if (matchData.matches && matchData.matches.length > 0) {
const firstMatch = matchData.matches[0];
const thumbnailUrl = `https://b2.aicodebattle.com/thumbnails/${firstMatch.id}.png`;
try {
const thumbResponse = await fetch(thumbnailUrl, { method: 'HEAD' });
if (thumbResponse.ok) {
addResult('Thumbnail accessible', 'pass', 'Thumbnail file exists on R2');
} else {
addResult('Thumbnail accessible', 'warn',
`Thumbnail returns ${thumbResponse.status} (B2 may not be seeded - known issue)`);
}
} catch (e) {
addResult('Thumbnail accessible', 'warn',
'Cannot fetch thumbnail (B2 may not be accessible - known issue)');
}
}
} catch (error) {
addResult('Thumbnails check', 'warn', error.message);
}
// Test 7: Pagination / infinite scroll
addInfo('Test 7: Verifying pagination support...');
try {
const matchResponse = await fetch('/data/matches/index.json');
const matchData = await matchResponse.json();
const matchCount = matchData.matches?.length || 0;
if (matchCount > 20) {
addResult('Pagination needed', 'pass',
`${matchCount} matches exceeds initial batch of 20`);
} else {
addResult('Pagination needed', 'info',
`Only ${matchCount} matches - pagination not triggered yet`);
}
// Check for additional pages
try {
const page2Response = await fetch('/data/matches/index-2.json');
if (page2Response.ok) {
const page2Data = await page2Response.json();
addResult('Additional page exists', 'pass',
`Page 2 has ${page2Data.matches?.length || 0} matches`);
} else {
addResult('Additional page exists', 'info',
'No additional pages yet (need more matches)');
}
} catch (e) {
addResult('Additional page exists', 'info',
'No additional pages yet (need more matches)');
}
} catch (error) {
addResult('Pagination check', 'warn', error.message);
}
// Test 8: Load the actual match list page in an iframe
addInfo('Test 8: Loading actual match list page...');
const preview = document.getElementById('match-list-preview');
preview.innerHTML = `
<iframe src="/index.html#/watch/replays" title="Match List Page Preview"></iframe>
<p style="margin-top: 10px; font-size: 12px; color: #94a3b8;">
The match list page should load above showing:
- Featured playlists section
- Match cards with bot names, scores, turn count
- "Watch Replay" links
- Expandable match details
</p>
`;
addResult('Match list page loads', 'pass', 'Page loaded in iframe above');
addInfo('');
addInfo('=== Tests Complete ===');
addInfo(`Total: ${testsPassed + testsFailed + testsWarned} | Passed: ${testsPassed} | Failed: ${testsFailed} | Warnings: ${testsWarned}`);
// Overall assessment
if (testsFailed === 0) {
addResult('Overall Assessment', testsWarned === 0 ? 'pass' : 'warn',
testsWarned === 0 ?
'All critical checks passed! Match list page renders with real matches.' :
'Core functionality works. Some non-critical items need attention (thumbnails, additional pages).');
} else {
addResult('Overall Assessment', 'fail',
'Some critical checks failed. Review the failures above.');
}
}
// Run tests on load
runTests();
</script>
</body>
</html>