spreadlab/web/src/components/__tests__/ResultsTable.spec.ts
Justin Visser 71baf10e73 web: the dashboard page, slice 3 of M3
The milestone 2 parity page becomes the spec's dashboard: app bar with
wordmark, disclaimer badge and About popover, theme toggle (persisted,
defaults to prefers-color-scheme, applied pre-mount to avoid a flash),
hero with the preset narrative and live toned percentages, scenario
toolbar, panel cards with skeleton loading and dim-while-refreshing,
legend, and footer. ComparisonTable evolves into the collapsed
ResultsTable (test moved along, plus a formatPct rounding case).

NetworkView renders the real topology with the spec's shape encodings.
The layout is seeded d3-force (mulberry32 random source, seeded initial
positions, 300 synchronous ticks, cached per graph hash); anisotropic
forceX/forceY pulls settle the cloud into the wide card shape so the
fit stays uniform-scale and the spacing organic. Justin asked for a
taller network area (380x230) and a reading caption under the legend;
the caption is study copy, so StudyPreset gains a readingCaption field.

Slices still to come keep their controls visibly parked: Export and the
panel kebab render disabled until slices 11 and 7.
2026-06-10 16:55:14 +02:00

89 lines
2.7 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import ResultsTable from '../ResultsTable.vue'
import type { PanelSpec } from '@/presets/types'
import type { Config, Result } from '@/types/engine'
// Hand-made data; the real numbers come from the API and are pinned by the
// Go tests. Here we only care that the table renders them accessibly.
const base: Config = {
numStudents: 10,
edgesPerNode: 2,
triangleProb: 0.4,
forwardProb: 0.5,
numEducated: 3,
origin: 0,
graphSeed: 1,
thresholdSeed: 2,
educationSeed: 3,
}
const panels: PanelSpec[] = [
{ id: 'panel-none', label: 'No program', strategy: 'none', overrides: {} },
{ id: 'panel-random', label: 'Random picks', strategy: 'random', overrides: {} },
{
id: 'panel-big',
label: 'Bigger school',
strategy: 'most-connected',
overrides: { numStudents: 40 },
},
]
const resultsByPanelId: Record<string, Result> = {
'panel-none': {
strategy: 'none',
educated: [],
reachedAtRound: [0, 1, 1, 2, 2, 2, 3, -1, -1, -1],
numReached: 7,
numRounds: 4,
reachedPct: 70,
},
'panel-random': {
strategy: 'random',
educated: [2, 5, 8],
reachedAtRound: [0, 1, 1, 2, -1, -1, -1, -1, -1, -1],
numReached: 4,
numRounds: 3,
reachedPct: 40,
},
}
describe('ResultsTable', () => {
it('renders one row per panel with its reach, honoring overrides', () => {
const wrapper = mount(ResultsTable, { props: { panels, resultsByPanelId, base } })
const rows = wrapper.findAll('tbody tr')
expect(rows).toHaveLength(3)
expect(rows[0]!.text()).toContain('No program')
expect(rows[0]!.text()).toContain('7 / 10')
expect(rows[0]!.text()).toContain('70%')
expect(rows[1]!.text()).toContain('Random')
// The third panel has no result yet and overrides numStudents.
expect(rows[2]!.text()).toContain('Running')
})
it('rounds the share with the shared formatPct', () => {
const halfPctResults = {
'panel-none': { ...resultsByPanelId['panel-none']!, reachedPct: 82.5 },
}
const wrapper = mount(ResultsTable, {
props: { panels: panels.slice(0, 1), resultsByPanelId: halfPctResults, base },
})
expect(wrapper.find('tbody tr').text()).toContain('83%')
})
it('falls back to the raw strategy name for unknown strategies', () => {
const oddPanels: PanelSpec[] = [
{ id: 'panel-odd', label: 'Odd', strategy: 'telepathy', overrides: {} },
]
const wrapper = mount(ResultsTable, {
props: {
panels: oddPanels,
resultsByPanelId: { 'panel-odd': resultsByPanelId['panel-none']! },
base,
},
})
expect(wrapper.findAll('tbody td')[0]!.text()).toBe('telepathy')
})
})