Files
deepseek-harness/examples/desktop/test/rubric-fusion-views.test.js
ZiyaZhang 4c47ce55e2 feat(desktop): rubric+growth+runtime three-page fusion
Architectural refactor unifying three previously-independent pages
around a single shared event-log model:

  rubric-fusion-model.js — the shared model. One log of scored
    events keyed by (rubric, runtime, timestamp), plus a small set
    of derivations (recent-scores window, per-rubric aggregates,
    per-runtime rubric grid) that all three views subscribe to.
  rubric-fusion-seed.js  — fixture seed used by tests and the
    isolation-machine screenshots below.

The three page views:

  Rubrics — now leads with a hint card (recent pass/fail streaks,
    stalest rubric) and a stats strip (7-run rolling pass rate,
    latest score, delta vs. previous). Rubric list is unchanged.
  Growth  — rewritten. The old compact-window event feed is gone;
    the page is now a pure SVG time-series curve over the event
    log (score-y × time-x), with per-rubric coloring and a
    hover-time tooltip. -420 lines of the old feed → +630 of the
    curve renderer.
  Runtime — adds a "Rubric grid" tab next to the existing Status
    tab. Grid is one row per rubric × one column per runtime;
    cells are pass/fail/unrun, click-through to the raw event.

Wiring is careful about the five hot zones the parallel lanes are
each touching (nav / chat / context / trace / artifact) — Lane D
stays entirely inside rubric/growth/runtime and their shared model,
so this composes with the C/A lanes without stepping on their
finishTurnContainer or chat-triple regions. style.css conflict
against the C+A combined append was tail-append vs tail-append and
resolved by concatenation (brace balance verified).

  rubric-fusion-model.js       419 +
  rubric-fusion-seed.js        624 +
  rubric-fusion-model.test.js  181 +
  rubric-fusion-views.test.js  189 +
  growth-v2.js                 +631/-420   (SVG time-series rewrite)
  rubrics-page.js              +89 -0      (hint + stats strip)
  runtimes-page.js             +198 -0     (rubric-grid tab)
  index.html                   +7          (three mount points)
  style.css                    +252        (new page sections)
  rubric-fusion-fixture.json   +94         (test/screenshot seed)

Test suite: 1704/1704 pass (+22 over Lane C+A baseline). Isolation-
machine fixture screenshots (rubrics/growth/runtimes,
docs/rubric-fusion-shots/0{1,2,3}-*.png) reproduce from the merged
HEAD.
2026-07-19 01:05:01 -07:00

190 lines
7.4 KiB
JavaScript

// Rubric fusion — cross-view smoke tests.
//
// We can't run the actual DOM controllers under `node --test` (no jsdom),
// but we can:
// 1. Verify the fusion seed JSON parses and drives all three view APIs.
// 2. Verify each of the 3 view scripts loads cleanly with a minimal
// document stub (catching syntax errors early).
// 3. Verify the fusion-model events → view derivations pipeline
// returns the expected shape for each view.
'use strict'
const test = require('node:test')
const assert = require('node:assert/strict')
const path = require('node:path')
const fs = require('node:fs')
// Wire the rubrics-model global before anything requires fusion-model.
global.window = global.window || {}
global.window.__dshRubricsModel = require('../src/renderer/rubrics-model.js')
// Fresh singleton per test.
function freshFusion() {
delete require.cache[require.resolve('../src/renderer/rubric-fusion-model.js')]
const api = require('../src/renderer/rubric-fusion-model.js')
return api.create()
}
const FIXTURE_PATH = path.join(__dirname, '..', 'docs', 'rubric-fusion-fixture.json')
const FIXTURE = JSON.parse(fs.readFileSync(FIXTURE_PATH, 'utf8'))
test('fixture loads with 3 rubrics and > 30 events', () => {
const s = freshFusion()
const res = s.loadFixture(FIXTURE)
assert.equal(res.rubrics, 3)
assert.ok(res.events > 30, 'expected > 30 events, got ' + res.events)
})
test('Rubrics view: recentScoresFor returns per-dim breakdown for each rubric', () => {
const s = freshFusion()
s.loadFixture(FIXTURE)
for (const rubric of s.listRubrics()) {
const stats = s.recentScoresFor(rubric.id)
assert.ok(stats.total > 0, `${rubric.id}: expected events > 0`)
assert.ok(stats.passRate >= 0 && stats.passRate <= 1, `${rubric.id}: passRate out of range`)
for (const dim of rubric.dims) {
const byDim = stats.byDim[dim.id]
assert.ok(byDim, `${rubric.id}/${dim.id}: missing per-dim bucket`)
assert.ok(byDim.n > 0, `${rubric.id}/${dim.id}: expected n > 0`)
}
}
})
test('Growth view: timeSeriesFor by=day produces sorted xAxis and per-dim series', () => {
const s = freshFusion()
s.loadFixture(FIXTURE)
const ts = s.timeSeriesFor({ by: 'day', groupBy: 'dim' })
assert.ok(ts.xAxis.length >= 3, 'expected >= 3 days')
// sorted asc
const sorted = ts.xAxis.slice().sort()
assert.deepEqual(ts.xAxis, sorted)
assert.ok(ts.series.length >= 3, 'expected >= 3 dim series')
for (const s2 of ts.series) {
assert.ok(s2.points.length >= 1, 'each series has points: ' + s2.label)
for (const p of s2.points) {
assert.ok(p.mean01 >= 0 && p.mean01 <= 1, 'mean01 in range for ' + s2.label)
assert.ok(p.passRate >= 0 && p.passRate <= 1, 'passRate in range for ' + s2.label)
}
}
})
test('Growth view: timeSeriesFor by=version buckets by harness version', () => {
const s = freshFusion()
s.loadFixture(FIXTURE)
const ts = s.timeSeriesFor({ by: 'version', groupBy: 'dim' })
const versions = new Set(ts.xAxis)
assert.ok(versions.has('v0.9'), 'v0.9 present')
assert.ok(versions.has('v0.11'), 'v0.11 present')
})
test('Growth view: filter chip harnessVersion narrows the series', () => {
const s = freshFusion()
s.loadFixture(FIXTURE)
const all = s.timeSeriesFor({ by: 'day', groupBy: 'dim' })
const v11 = s.timeSeriesFor({ by: 'day', groupBy: 'dim', filter: { harnessVersion: 'v0.11' } })
const totalAll = all.series.reduce((sum, sr) => sum + sr.points.reduce((a, p) => a + p.n, 0), 0)
const totalV11 = v11.series.reduce((sum, sr) => sum + sr.points.reduce((a, p) => a + p.n, 0), 0)
assert.ok(totalV11 < totalAll, 'filtered total should be strictly smaller')
assert.ok(totalV11 > 0, 'v0.11 filtered total > 0')
})
test('Runtime view: rolloutGridFor produces a matrix with cell pass/fail', () => {
const s = freshFusion()
s.loadFixture(FIXTURE)
const grid = s.rolloutGridFor('svg-gen', 's-svg-live')
assert.equal(grid.rubric.id, 'svg-gen')
assert.ok(grid.rollouts.length >= 3, 'expected >= 3 rollouts for the live session')
assert.ok(grid.dims.length === 3, 'svg-gen has 3 dims')
// Assert we have at least one pass AND at least one fail — the fixture
// deliberately spans both.
const passed = grid.cells.filter(c => c.passed === true).length
const failed = grid.cells.filter(c => c.passed === false).length
assert.ok(passed > 0, 'at least one pass cell')
assert.ok(failed > 0, 'at least one fail cell')
})
test('Runtime view: rolloutGridFor sessionId=null aggregates all rollouts', () => {
const s = freshFusion()
s.loadFixture(FIXTURE)
const grid = s.rolloutGridFor('svg-gen', null)
// The fixture has rollouts 1..8 for svg-gen (5 seed sessions + 3 on
// the live session).
assert.deepEqual(grid.rollouts, [1, 2, 3, 4, 5, 6, 7, 8])
})
test('Rubrics view: similar-sessions hint fires with count >= 3', () => {
const s = freshFusion()
s.loadFixture(FIXTURE)
const classes = s.detectSimilarSessions()
assert.equal(classes.length, 1)
assert.equal(classes[0].id, 'similar-svg-gen')
assert.ok(classes[0].count >= 3)
})
// --- Script-load smoke tests: the 3 view scripts must load without
// throwing under a minimal document stub. Catches syntax errors before
// they hit the browser. ---
function stubDocument() {
return {
addEventListener() {},
readyState: 'complete',
querySelector() { return null },
querySelectorAll() { return [] },
createElement() {
const node = {
style: {},
classList: { add() {}, remove() {}, toggle() {}, contains() { return false } },
dataset: {},
setAttribute() {},
getAttribute() { return null },
appendChild(c) { return c },
replaceChildren() {},
removeChild() {},
addEventListener() {},
set textContent(v) {},
set innerHTML(v) {},
set hidden(v) {},
}
return node
},
createElementNS() { return this.createElement() },
getElementById() { return null },
}
}
test('rubrics-page.js loads under minimal document stub', () => {
global.window = { __dshRubricsModel: require('../src/renderer/rubrics-model.js') }
global.document = stubDocument()
global.requestAnimationFrame = () => {}
delete require.cache[require.resolve('../src/renderer/rubrics-page.js')]
const page = require('../src/renderer/rubrics-page.js')
assert.ok(page._internal, 'exposes _internal')
})
test('growth-v2.js loads under minimal document stub', () => {
global.window = {
__dshRubricsModel: require('../src/renderer/rubrics-model.js'),
__dshRubricFusion: require('../src/renderer/rubric-fusion-model.js'),
}
global.document = stubDocument()
delete require.cache[require.resolve('../src/renderer/growth-v2.js')]
require('../src/renderer/growth-v2.js')
assert.ok(global.window.__dshGrowthV2, 'exposes __dshGrowthV2')
assert.equal(typeof global.window.__dshGrowthV2.show, 'function')
assert.equal(typeof global.window.__dshGrowthV2.render, 'function')
})
test('runtimes-page.js loads under minimal document stub', () => {
global.window = {
__dshRubricsModel: require('../src/renderer/rubrics-model.js'),
__dshRubricFusion: require('../src/renderer/rubric-fusion-model.js'),
}
global.document = stubDocument()
delete require.cache[require.resolve('../src/renderer/runtimes-page.js')]
require('../src/renderer/runtimes-page.js')
assert.ok(global.window.__dshRuntimes, 'exposes __dshRuntimes')
assert.equal(typeof global.window.__dshRuntimes.show, 'function')
})