Skip to content

Commit 672804c

Browse files
Manishrdyclaude
andcommitted
Tournament create: don't crash for a user with no teams
The no-teams render omits the mode section, so updateAvailableModes() dereferenced a missing #modeGrid on page load. Guard it and add a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 001080f commit 672804c

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

‎templates/tournaments/create.html‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ <h2>Tournament Format</h2>
295295
function updateAvailableModes(numTeams) {
296296
const modeGrid = document.getElementById('modeGrid');
297297
const submitBtn = document.getElementById('submitBtn');
298+
// A user with no teams gets the empty state instead of the mode
299+
// section, so there is no grid, submit button or series options.
300+
if (!modeGrid) return;
298301

299302
if (numTeams < 2) {
300303
modeGrid.innerHTML = `
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
const assert = require('node:assert/strict');
2+
const fs = require('node:fs');
3+
const path = require('node:path');
4+
const test = require('node:test');
5+
const vm = require('node:vm');
6+
7+
const template = fs.readFileSync(
8+
path.resolve(__dirname, '../../templates/tournaments/create.html'),
9+
'utf8'
10+
);
11+
12+
// The ids a render actually contains. With no teams, each `{% if teams %}`
13+
// body is dropped (keeping any `{% else %}` body); with teams, the reverse.
14+
function renderedIds(hasTeams) {
15+
const markup = template.replace(
16+
/\{% if teams %\}([\s\S]*?)(?:\{% else %\}([\s\S]*?))?\{% endif %\}/g,
17+
(_, ifBody, elseBody = '') => (hasTeams ? ifBody : elseBody)
18+
);
19+
return new Set([...markup.matchAll(/\bid="([^"{]+)"/g)].map(m => m[1]));
20+
}
21+
22+
function fakeElement() {
23+
return {
24+
value: '',
25+
textContent: '',
26+
innerHTML: '',
27+
disabled: false,
28+
style: {},
29+
classList: { add() {}, remove() {}, toggle() {} },
30+
};
31+
}
32+
33+
// Runs the page script and its DOMContentLoaded handler against a document
34+
// holding only the elements the given render would produce.
35+
function loadPage(hasTeams, checkedTeams = 0) {
36+
const script = template.match(
37+
/<script>\s*\/\/ Team format availability map[\s\S]*?<\/script>/
38+
)[0]
39+
.replace(/^<script>|<\/script>$/g, '')
40+
.replace('{{ team_formats_json | safe }}', '{}')
41+
.replace('{{ format_labels | tojson }}', '{"T20": "T20"}');
42+
43+
const elements = {};
44+
for (const id of renderedIds(hasTeams)) elements[id] = fakeElement();
45+
const ready = [];
46+
const context = {
47+
window: { addEventListener() {} },
48+
document: {
49+
getElementById: id => elements[id] || null,
50+
querySelector: () => null,
51+
querySelectorAll(selector) {
52+
if (selector.startsWith('input[name="team_ids"]:checked')) {
53+
return { length: checkedTeams };
54+
}
55+
return [];
56+
},
57+
addEventListener(type, fn) {
58+
if (type === 'DOMContentLoaded') ready.push(fn);
59+
},
60+
},
61+
};
62+
vm.runInNewContext(script, context);
63+
ready.forEach(fn => fn());
64+
return elements;
65+
}
66+
67+
test('the no-teams render really omits the mode section', () => {
68+
assert.ok(renderedIds(true).has('modeGrid'));
69+
assert.ok(!renderedIds(false).has('modeGrid'));
70+
assert.ok(renderedIds(false).has('teamCounter'));
71+
});
72+
73+
test('page load does not throw for a user with no teams', () => {
74+
const elements = loadPage(false);
75+
assert.equal(elements.teamCounter.textContent, '0 selected');
76+
});
77+
78+
test('with teams, the mode grid still shows its placeholder and then modes', () => {
79+
const idle = loadPage(true);
80+
assert.match(idle.modeGrid.innerHTML, /Select at least 2 teams/);
81+
assert.equal(idle.submitBtn.disabled, true);
82+
83+
const picked = loadPage(true, 4);
84+
assert.match(picked.modeGrid.innerHTML, /data-mode="round_robin"/);
85+
assert.match(picked.modeGrid.innerHTML, /data-mode="ipl_style"/);
86+
});

0 commit comments

Comments
 (0)