140 lines
4.5 KiB
TypeScript
140 lines
4.5 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
|
|
/**
|
|
* Regression tests for BoardStore selection behavior.
|
|
*
|
|
* Bug history:
|
|
* - Original: BoardStore auto-selected boards[0] whenever activeBoardId was
|
|
* null. Combined with the parent's session-driven clear, this created an
|
|
* infinite toggle loop (screen flicker).
|
|
* - Previous fix: limited auto-select to boards.length === 1 — but in
|
|
* company mode with exactly 1 session (1 board) this STILL flickered
|
|
* when no session was selected.
|
|
* - Current fix: BoardStore does NOT auto-select at all. Selection is
|
|
* entirely driven by the parent (WorkspacePage) which knows the mode.
|
|
* initFromBackend only clears when the prior selection disappears.
|
|
*/
|
|
|
|
// initFromBackend: only preserve-or-clear, no auto-default
|
|
function resolveActiveBoardAfterInit(
|
|
prev: string | null,
|
|
bds: { id: string }[],
|
|
): string | null {
|
|
return prev && bds.some(b => b.id === prev) ? prev : null
|
|
}
|
|
|
|
// Parent-driven selection (simulates WorkspacePage useEffect)
|
|
function parentChooseBoard(opts: {
|
|
isCompanyMode: boolean
|
|
activeSessionBoardId: string | null
|
|
boards: { id: string }[]
|
|
currentActive: string | null
|
|
}): string | null {
|
|
const { isCompanyMode, activeSessionBoardId, boards, currentActive } = opts
|
|
const hasId = (id: string | null) => !!id && boards.some(b => b.id === id)
|
|
if (isCompanyMode) {
|
|
if (activeSessionBoardId && hasId(activeSessionBoardId)) return activeSessionBoardId
|
|
return null
|
|
}
|
|
if (currentActive && hasId(currentActive)) return currentActive
|
|
return boards.length > 0 ? boards[0].id : null
|
|
}
|
|
|
|
// ── initFromBackend ──────────────────────────────────────────────────────
|
|
|
|
// Single board: do NOT auto-select (parent picks)
|
|
assert.strictEqual(
|
|
resolveActiveBoardAfterInit(null, [{ id: 'project-board' }]),
|
|
null,
|
|
'init with null prev stays null even with 1 board',
|
|
)
|
|
|
|
// Preserve valid prior
|
|
assert.strictEqual(
|
|
resolveActiveBoardAfterInit('session-a', [{ id: 'session-a' }, { id: 'session-b' }]),
|
|
'session-a',
|
|
'preserves existing active when still present',
|
|
)
|
|
|
|
// Clear stale
|
|
assert.strictEqual(
|
|
resolveActiveBoardAfterInit('deleted', [{ id: 'session-a' }]),
|
|
null,
|
|
'clears stale active',
|
|
)
|
|
|
|
// Empty
|
|
assert.strictEqual(
|
|
resolveActiveBoardAfterInit(null, []),
|
|
null,
|
|
'empty boards → null',
|
|
)
|
|
|
|
// ── Parent-driven selection ──────────────────────────────────────────────
|
|
|
|
// Company mode: no session → null (shows empty state)
|
|
assert.strictEqual(
|
|
parentChooseBoard({
|
|
isCompanyMode: true,
|
|
activeSessionBoardId: null,
|
|
boards: [{ id: 'session-a' }],
|
|
currentActive: null,
|
|
}),
|
|
null,
|
|
'company mode + no session → null',
|
|
)
|
|
|
|
// Company mode: session selected, its board exists → select it
|
|
assert.strictEqual(
|
|
parentChooseBoard({
|
|
isCompanyMode: true,
|
|
activeSessionBoardId: 'session-a',
|
|
boards: [{ id: 'session-a' }, { id: 'session-b' }],
|
|
currentActive: null,
|
|
}),
|
|
'session-a',
|
|
'company mode + session with board → select session board',
|
|
)
|
|
|
|
// Company mode: session selected but its board doesn't exist yet → null
|
|
assert.strictEqual(
|
|
parentChooseBoard({
|
|
isCompanyMode: true,
|
|
activeSessionBoardId: 'new-session',
|
|
boards: [{ id: 'session-a' }],
|
|
currentActive: null,
|
|
}),
|
|
null,
|
|
'company mode + new session (no board yet) → null (empty state)',
|
|
)
|
|
|
|
// Non-company mode: auto-select project board
|
|
assert.strictEqual(
|
|
parentChooseBoard({
|
|
isCompanyMode: false,
|
|
activeSessionBoardId: null,
|
|
boards: [{ id: 'project-board' }],
|
|
currentActive: null,
|
|
}),
|
|
'project-board',
|
|
'non-company mode → auto-select project board',
|
|
)
|
|
|
|
// ── Flicker regression: 1-session company mode, no session selected ──────
|
|
// The original bug: boards.length===1 triggered auto-select, parent cleared
|
|
// → loop. With the current contract, BOTH init and parent agree on null.
|
|
{
|
|
const boards = [{ id: 'session-a' }]
|
|
const afterInit = resolveActiveBoardAfterInit(null, boards)
|
|
assert.strictEqual(afterInit, null, 'init: null with 1 board stays null')
|
|
const afterParent = parentChooseBoard({
|
|
isCompanyMode: true,
|
|
activeSessionBoardId: null,
|
|
boards,
|
|
currentActive: afterInit,
|
|
})
|
|
assert.strictEqual(afterParent, null, 'parent: 1-session company + no active → null (stable, no loop)')
|
|
}
|
|
|
|
console.log('BoardStore selection contract passed')
|