113 lines
4.5 KiB
JavaScript
113 lines
4.5 KiB
JavaScript
|
|
// Tests for the annotation-panel read interface (#205 Feedback-tab handoff).
|
||
|
|
//
|
||
|
|
// The panel is a script-tag IIFE that installs `window.__dshAnnotation` at
|
||
|
|
// runtime; under node --test we require it, which runs the IIFE with
|
||
|
|
// `typeof window === 'undefined'` — so the store lives on the CommonJS
|
||
|
|
// module.exports `_internal` handle instead.
|
||
|
|
//
|
||
|
|
// We stub just enough of the browser globals to exercise the CustomEvent
|
||
|
|
// dispatch path in write(): `document.dispatchEvent` + a CustomEvent
|
||
|
|
// constructor that records `type` and `detail`.
|
||
|
|
|
||
|
|
'use strict'
|
||
|
|
|
||
|
|
const test = require('node:test')
|
||
|
|
const assert = require('node:assert/strict')
|
||
|
|
|
||
|
|
// Stub browser globals BEFORE requiring the panel — its `write()` gates on
|
||
|
|
// `typeof document !== 'undefined'`, so setting these controls whether the
|
||
|
|
// CustomEvent branch runs.
|
||
|
|
const dispatched = []
|
||
|
|
global.document = {
|
||
|
|
dispatchEvent(ev) { dispatched.push(ev); return true },
|
||
|
|
addEventListener() {},
|
||
|
|
}
|
||
|
|
class FakeCustomEvent {
|
||
|
|
constructor(type, init = {}) {
|
||
|
|
this.type = type
|
||
|
|
this.detail = init.detail
|
||
|
|
}
|
||
|
|
}
|
||
|
|
global.CustomEvent = FakeCustomEvent
|
||
|
|
|
||
|
|
const panel = require('../src/renderer/annotation-panel.js')
|
||
|
|
const model = require('../src/renderer/annotation-model.js')
|
||
|
|
const { write, read, readAll, state } = panel._internal
|
||
|
|
|
||
|
|
test('write() stamps annotator default when the record has none', () => {
|
||
|
|
state.byId.clear()
|
||
|
|
const ann = model.blankAnnotation('sess-alpha')
|
||
|
|
write(ann)
|
||
|
|
const stored = state.byId.get('sess-alpha')
|
||
|
|
assert.equal(stored.annotator, 'local-user')
|
||
|
|
})
|
||
|
|
|
||
|
|
test('write() preserves an existing annotator instead of overwriting it', () => {
|
||
|
|
state.byId.clear()
|
||
|
|
const ann = { ...model.blankAnnotation('sess-beta'), annotator: 'reviewer-42' }
|
||
|
|
write(ann)
|
||
|
|
assert.equal(state.byId.get('sess-beta').annotator, 'reviewer-42')
|
||
|
|
})
|
||
|
|
|
||
|
|
test('write() dispatches dsh:annotation-updated with the stored record', () => {
|
||
|
|
state.byId.clear()
|
||
|
|
dispatched.length = 0
|
||
|
|
const ann = model.blankAnnotation('sess-gamma')
|
||
|
|
write(ann)
|
||
|
|
const ev = dispatched.find(e => e.type === 'dsh:annotation-updated')
|
||
|
|
assert.ok(ev, 'event fired')
|
||
|
|
assert.equal(ev.detail.sessionId, 'sess-gamma')
|
||
|
|
assert.equal(ev.detail.ann.annotator, 'local-user', 'detail carries stamped record')
|
||
|
|
})
|
||
|
|
|
||
|
|
test('read(sessionId) returns null for unknown ids and a snapshot for known ones', () => {
|
||
|
|
state.byId.clear()
|
||
|
|
assert.equal(read('nope'), null)
|
||
|
|
const ann = model.setOverall(model.blankAnnotation('sess-delta'), 'good', 42)
|
||
|
|
write(ann)
|
||
|
|
const got = read('sess-delta')
|
||
|
|
assert.equal(got.overall, 'good')
|
||
|
|
assert.equal(got.sessionId, 'sess-delta')
|
||
|
|
// Mutating the snapshot must not affect the store.
|
||
|
|
got.overall = 'bad'
|
||
|
|
assert.equal(read('sess-delta').overall, 'good', 'snapshot is a copy')
|
||
|
|
})
|
||
|
|
|
||
|
|
test('readAll() returns a fresh Map of snapshots', () => {
|
||
|
|
state.byId.clear()
|
||
|
|
write(model.setOverall(model.blankAnnotation('a'), 'good', 0))
|
||
|
|
write(model.setOverall(model.blankAnnotation('b'), 'bad', 0))
|
||
|
|
const all = readAll()
|
||
|
|
assert.equal(all.size, 2)
|
||
|
|
assert.equal(all.get('a').overall, 'good')
|
||
|
|
assert.equal(all.get('b').overall, 'bad')
|
||
|
|
// Deleting from the snapshot map does not remove from the store.
|
||
|
|
all.delete('a')
|
||
|
|
assert.ok(readAll().has('a'))
|
||
|
|
})
|
||
|
|
|
||
|
|
// ─── Typed rubric primitives ─────────────────────────────────────────
|
||
|
|
|
||
|
|
test('activeDims defaults to the 5 fixed continuous multi-turn dims', () => {
|
||
|
|
const { activeDims } = panel._internal
|
||
|
|
const dims = activeDims()
|
||
|
|
assert.equal(dims.length, 5, '5 fixed dims out of the box')
|
||
|
|
for (const d of dims) assert.equal(d.type, 'continuous')
|
||
|
|
})
|
||
|
|
|
||
|
|
test('valueForKey routes 1-9 keys to the right primitive value', () => {
|
||
|
|
const { valueForKey } = panel._internal
|
||
|
|
// continuous — key = numeric value inside range
|
||
|
|
assert.equal(valueForKey({ id: 'x', type: 'continuous', min: 1, max: 5 }, 3), 3)
|
||
|
|
assert.equal(valueForKey({ id: 'x', type: 'continuous', min: 1, max: 5 }, 6), undefined,
|
||
|
|
'out-of-range digit → undefined (no write)')
|
||
|
|
// categorical — key = 1-based enum index
|
||
|
|
assert.equal(valueForKey({ id: 'x', type: 'categorical', values: ['bad', 'ok', 'good'] }, 1), 'bad')
|
||
|
|
assert.equal(valueForKey({ id: 'x', type: 'categorical', values: ['bad', 'ok', 'good'] }, 3), 'good')
|
||
|
|
assert.equal(valueForKey({ id: 'x', type: 'categorical', values: ['bad', 'ok', 'good'] }, 4), undefined)
|
||
|
|
// boolean — 1=true, 2=false
|
||
|
|
assert.equal(valueForKey({ id: 'x', type: 'boolean' }, 1), true)
|
||
|
|
assert.equal(valueForKey({ id: 'x', type: 'boolean' }, 2), false)
|
||
|
|
assert.equal(valueForKey({ id: 'x', type: 'boolean' }, 3), undefined)
|
||
|
|
})
|