ReviewOS

also looking at this

stacks/lastsoulultra

LSU's own coaching surface, and what auditing the site turned up

#12
Merged glennmichael123 wants to merge claude/last-soul-ultra-web-6e0718 into main
86 files +3943 -1190

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

tests/race-facts.test.tsadded+123-0
Changes to tests/race-facts.test.ts
@@ -0,0 +1,123 @@
1import { describe, expect, it } from 'bun:test'
2import { resolve } from 'node:path'
3import { processDirectives } from '@stacksjs/stx'
4
5/**
6 * The results pages state figures that are derived from the race database in
7 * resources/data/results-<year>.json. The README's one content rule is that
8 * race facts are not replaced with sample content; this is what checks it.
9 *
10 * Everything here is recomputed from the JSON and then looked for in the
11 * rendered page, so a page that drifts from its own source fails, and so
12 * does a page whose numbers were edited by hand.
13 */
14
15interface Row { name: string, yards: number, status: string }
16
17async function season(year: number) {
18 const data = await Bun.file(`resources/data/results-${year}.json`).json()
19 const rows: Row[] = data.results
20 const ordered = [...rows].sort((a, b) => b.yards - a.yards)
21 const loopMetres: number = data.event.loop_metres
22 const winner = ordered[0]!
23
24 return {
25 date: data.event.start_date as string,
26 started: data.totals.started as number,
27 loopMetres,
28 winner,
29 assist: ordered[1]!,
30 third: ordered[2]!,
31 kilometres: Math.round(winner.yards * loopMetres / 100) / 10,
32 /** A yard an hour, so the whole days the race ran for. */
33 days: Math.ceil(winner.yards / 24),
34 }
35}
36
37const page = await (async () => {
38 const path = resolve('resources/views/results.stx')
39 return processDirectives(await Bun.file(path).text(), {}, path, {
40 enabled: true,
41 cache: false,
42 componentsDir: resolve('resources/components'),
43 layoutsDir: resolve('resources/layouts'),
44 partialsDir: resolve('resources/partials'),
45 }, new Set())
46})()
47
48/**
49 * One season's own section, so a figure from the other year cannot satisfy
50 * an assertion about this one. Each is `<section id="<year>" ...>`.
51 */
52function sectionFor(year: number): string {
53 const start = page.indexOf(`<section id="${year}"`)
54 expect(start).toBeGreaterThan(-1)
55 const next = page.indexOf('<section id="', start + 1)
56 return page.slice(start, next === -1 ? undefined : next)
57}
58
59/**
60 * The page's stat blocks are a value element followed by its label. The
61 * source has them on separate lines and the build minifies that away, so the
62 * gap between them is optional.
63 */
64function stat(section: string, label: string): string | undefined {
65 return new RegExp(`<div class="stat-v">([^<]+)</div>\\s*<div class="stat-l">${label}</div>`).exec(section)?.[1]
66}
67
68describe.each([2025, 2026])('the %i results', (year) => {
69 it('names the winner the database names, at the yards it records', async () => {
70 const { winner } = await season(year)
71 const section = sectionFor(year)
72
73 expect(winner.status).toBe('1st')
74 expect(section).toContain(winner.name)
75 expect(stat(section, 'winning yards')).toBe(String(winner.yards))
76 })
77
78 it('counts the field the database counted', async () => {
79 const { started } = await season(year)
80 const section = sectionFor(year)
81
82 expect(section).toContain(`${started} starters`)
83 expect(stat(section, 'athletes on the start line')).toBe(String(started))
84 })
85
86 it('converts yards to kilometres with the loop the database records', async () => {
87 const { kilometres, loopMetres } = await season(year)
88 const section = sectionFor(year)
89
90 expect(stat(section, 'kilometres covered')).toBe(String(kilometres))
91 expect(stat(section, 'km per hourly loop')).toBe(String(loopMetres / 1000))
92 })
93
94 it('counts the days the race actually ran', async () => {
95 const { days } = await season(year)
96 expect(stat(sectionFor(year), 'days of racing')).toBe(String(days))
97 })
98
99 it('names the two behind the winner, in the order they finished', async () => {
100 const { winner, assist, third } = await season(year)
101 const section = sectionFor(year)
102
103 expect(assist.yards).toBeGreaterThanOrEqual(third.yards)
104 expect(section.indexOf(winner.name)).toBeLessThan(section.indexOf(assist.name))
105 expect(section.indexOf(assist.name)).toBeLessThan(section.indexOf(third.name))
106 })
107})
108
109describe('the edition comparison', () => {
110 it('states differences that are the difference between the two seasons', async () => {
111 const a = await season(2025)
112 const b = await season(2026)
113
114 expect(page).toContain(`+${b.winner.yards - a.winner.yards}`)
115 expect(page).toContain(`+${b.started - a.started}`)
116 })
117
118 it('agrees with the loop length the rest of the site uses', async () => {
119 const { race } = await import('../resources/data/race')
120 expect((await season(2026)).loopMetres).toBe(race.loopMetres)
121 expect((await season(2025)).loopMetres).toBe(race.loopMetres)
122 })
123})