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.
This commit is contained in:
parent
2f21248c20
commit
71baf10e73
28 changed files with 1766 additions and 300 deletions
89
web/src/components/__tests__/ResultsTable.spec.ts
Normal file
89
web/src/components/__tests__/ResultsTable.spec.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
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')
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue