v1.1.0: pooled runtime, 959 tests, production hardening (0 squash)
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
/**
|
||||
* Mock helpers for cross-package integration tests.
|
||||
*
|
||||
* Provides dependency-injected builders for geometry worlds,
|
||||
* extraction results, solver results, and complete mock scenes.
|
||||
*/
|
||||
|
||||
import type {
|
||||
GeometryWorld,
|
||||
ClauseDescriptor,
|
||||
ClauseResult,
|
||||
} from 'imhotep-solver'
|
||||
import type { EvaluationOutput } from 'imhotep-solver'
|
||||
import type { GeometrySnapshot, SnapshotMetadata } from 'imhotep-state'
|
||||
import type { Diagnostic as CoreDiagnostic } from './diagnostics.js'
|
||||
import type { ImhotepId } from './types.js'
|
||||
import type { SemanticIr } from './ir.js'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Geometry World Builder
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface MockSubject {
|
||||
id: number
|
||||
selector: string
|
||||
box: {
|
||||
top: number
|
||||
bottom: number
|
||||
left: number
|
||||
right: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface MockFrame {
|
||||
id: number
|
||||
kind: string
|
||||
subjectId: number
|
||||
}
|
||||
|
||||
export interface MockWorldOptions {
|
||||
sceneId?: string
|
||||
snapshotId?: string
|
||||
subjects: MockSubject[]
|
||||
frames?: MockFrame[]
|
||||
topology?: {
|
||||
nearestPositionedAncestorOf?: number[]
|
||||
scrollContainerOf?: number[]
|
||||
stackingContextOf?: number[]
|
||||
containingBlockOf?: number[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a solver-compatible geometry world from a declarative description.
|
||||
*/
|
||||
export function buildMockGeometryWorld(opts: MockWorldOptions): GeometryWorld {
|
||||
const subjectCount = opts.subjects.length
|
||||
|
||||
const world: GeometryWorld = {
|
||||
sceneId: opts.sceneId ?? 'scene_test',
|
||||
snapshotId: opts.snapshotId ?? 'snap_test',
|
||||
env: {
|
||||
viewportWidth: 1280,
|
||||
viewportHeight: 720,
|
||||
deviceScaleFactor: 1,
|
||||
colorScheme: 'light',
|
||||
pointer: 'fine',
|
||||
hover: false,
|
||||
reducedMotion: false,
|
||||
locale: 'en',
|
||||
writingMode: 'horizontal-tb',
|
||||
},
|
||||
strings: { values: [] },
|
||||
subjects: {
|
||||
ids: opts.subjects.map((s) => s.id),
|
||||
domNodeId: new Array(subjectCount).fill(0),
|
||||
subjectKind: new Array(subjectCount).fill(0),
|
||||
primaryBoxId: opts.subjects.map((_, i) => i),
|
||||
firstFragmentId: new Array(subjectCount).fill(0),
|
||||
fragmentCount: new Array(subjectCount).fill(0),
|
||||
},
|
||||
dom: {
|
||||
nodeId: opts.subjects.map((s) => s.id),
|
||||
parentNodeId: new Array(subjectCount).fill(0),
|
||||
childCount: new Array(subjectCount).fill(0),
|
||||
tagNameStringId: new Array(subjectCount).fill(0),
|
||||
},
|
||||
boxes: {
|
||||
boxId: opts.subjects.map((_, i) => i),
|
||||
subjectId: opts.subjects.map((s) => s.id),
|
||||
frameId: new Array(subjectCount).fill(0),
|
||||
borderLeft: opts.subjects.map((s) => s.box.left),
|
||||
borderTop: opts.subjects.map((s) => s.box.top),
|
||||
borderRight: opts.subjects.map((s) => s.box.right),
|
||||
borderBottom: opts.subjects.map((s) => s.box.bottom),
|
||||
paddingLeft: opts.subjects.map((s) => s.box.left),
|
||||
paddingTop: opts.subjects.map((s) => s.box.top),
|
||||
paddingRight: opts.subjects.map((s) => s.box.right),
|
||||
paddingBottom: opts.subjects.map((s) => s.box.bottom),
|
||||
contentLeft: opts.subjects.map((s) => s.box.left),
|
||||
contentTop: opts.subjects.map((s) => s.box.top),
|
||||
contentRight: opts.subjects.map((s) => s.box.right),
|
||||
contentBottom: opts.subjects.map((s) => s.box.bottom),
|
||||
},
|
||||
rects: {
|
||||
rectId: opts.subjects.map((_, i) => i),
|
||||
left: opts.subjects.map((s) => s.box.left),
|
||||
top: opts.subjects.map((s) => s.box.top),
|
||||
right: opts.subjects.map((s) => s.box.right),
|
||||
bottom: opts.subjects.map((s) => s.box.bottom),
|
||||
},
|
||||
topology: {
|
||||
containingBlockOf: opts.topology?.containingBlockOf ?? [],
|
||||
nearestPositionedAncestorOf:
|
||||
opts.topology?.nearestPositionedAncestorOf ?? [],
|
||||
scrollContainerOf: opts.topology?.scrollContainerOf ?? [],
|
||||
stackingContextOf: opts.topology?.stackingContextOf ?? [],
|
||||
formattingContextOf: [],
|
||||
clippingRootOf: [],
|
||||
paintOrderBucket: [],
|
||||
paintOrderIndex: [],
|
||||
},
|
||||
scroll: {
|
||||
containerId: [],
|
||||
scrollLeft: [],
|
||||
scrollTop: [],
|
||||
scrollWidth: [],
|
||||
scrollHeight: [],
|
||||
clientWidth: [],
|
||||
clientHeight: [],
|
||||
},
|
||||
visualBoxes: {
|
||||
boxId: opts.subjects.map((_, i) => i),
|
||||
subjectId: opts.subjects.map((s) => s.id),
|
||||
frameId: new Array(subjectCount).fill(0),
|
||||
borderLeft: opts.subjects.map((s) => s.box.left),
|
||||
borderTop: opts.subjects.map((s) => s.box.top),
|
||||
borderRight: opts.subjects.map((s) => s.box.right),
|
||||
borderBottom: opts.subjects.map((s) => s.box.bottom),
|
||||
paddingLeft: opts.subjects.map((s) => s.box.left),
|
||||
paddingTop: opts.subjects.map((s) => s.box.top),
|
||||
paddingRight: opts.subjects.map((s) => s.box.right),
|
||||
paddingBottom: opts.subjects.map((s) => s.box.bottom),
|
||||
contentLeft: opts.subjects.map((s) => s.box.left),
|
||||
contentTop: opts.subjects.map((s) => s.box.top),
|
||||
contentRight: opts.subjects.map((s) => s.box.right),
|
||||
contentBottom: opts.subjects.map((s) => s.box.bottom),
|
||||
},
|
||||
transforms: {
|
||||
transformId: [],
|
||||
subjectId: [],
|
||||
matrixStart: [],
|
||||
matrixLength: [],
|
||||
originX: [],
|
||||
originY: [],
|
||||
},
|
||||
matrices: {
|
||||
values: [],
|
||||
},
|
||||
clipping: {
|
||||
clipNodeId: [],
|
||||
subjectId: [],
|
||||
clipKind: [],
|
||||
clipLeft: [],
|
||||
clipTop: [],
|
||||
clipRight: [],
|
||||
clipBottom: [],
|
||||
parentClipNodeId: [],
|
||||
},
|
||||
visibility: {
|
||||
subjectId: [],
|
||||
isRendered: [],
|
||||
isVisible: [],
|
||||
visibleArea: [],
|
||||
clippedArea: [],
|
||||
},
|
||||
}
|
||||
|
||||
return world
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Clause Descriptor Builder
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function buildClauseDescriptor(opts: {
|
||||
clauseId: string
|
||||
clauseKind: string
|
||||
subjectRef: number
|
||||
referenceRef?: number
|
||||
frameRef?: number
|
||||
bounds?: { minGap?: number; maxGap?: number; tolerance?: number; min?: number; max?: number }
|
||||
options?: Record<string, unknown>
|
||||
}): ClauseDescriptor {
|
||||
return {
|
||||
clauseId: opts.clauseId,
|
||||
clauseKind: opts.clauseKind,
|
||||
version: 1,
|
||||
subjectRef: opts.subjectRef,
|
||||
referenceRef: opts.referenceRef,
|
||||
frameRef: opts.frameRef,
|
||||
bounds: opts.bounds ?? {},
|
||||
options: opts.options ?? {},
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Solver Result Builder
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function buildMockSolverResult(opts: {
|
||||
clauseResults?: ClauseResult[]
|
||||
diagnostics?: Array<{
|
||||
code: string
|
||||
severity: 'error' | 'warning' | 'info'
|
||||
category: string
|
||||
message: string
|
||||
clauseId?: string
|
||||
}>
|
||||
}): EvaluationOutput {
|
||||
return {
|
||||
clauseResults: opts.clauseResults ?? [],
|
||||
groupResults: [],
|
||||
proofs: [],
|
||||
diagnostics: opts.diagnostics ?? [],
|
||||
trace: [],
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Extraction Result Builder
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function buildMockExtractionResult(opts: {
|
||||
requestId?: string
|
||||
worlds: GeometryWorld[]
|
||||
diagnostics?: CoreDiagnostic[]
|
||||
}): {
|
||||
requestId: string
|
||||
status: 'ok' | 'partial' | 'error'
|
||||
worlds: GeometryWorld[]
|
||||
diagnostics: CoreDiagnostic[]
|
||||
} {
|
||||
return {
|
||||
requestId: opts.requestId ?? 'req_1',
|
||||
status: 'ok',
|
||||
worlds: opts.worlds,
|
||||
diagnostics: opts.diagnostics ?? [],
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scene Builder
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface MockScene {
|
||||
world: GeometryWorld
|
||||
clauses: ClauseDescriptor[]
|
||||
}
|
||||
|
||||
export function createMockScene(opts: {
|
||||
subjects: MockSubject[]
|
||||
relations: Array<{
|
||||
kind: string
|
||||
subjectId: number
|
||||
referenceId: number
|
||||
bounds?: { minGap?: number; maxGap?: number }
|
||||
}>
|
||||
}): MockScene {
|
||||
const world = buildMockGeometryWorld({ subjects: opts.subjects })
|
||||
const clauses = opts.relations.map((r, i) =>
|
||||
buildClauseDescriptor({
|
||||
clauseId: `clause_${i + 1}`,
|
||||
clauseKind: r.kind,
|
||||
subjectRef: r.subjectId,
|
||||
referenceRef: r.referenceId,
|
||||
bounds: r.bounds,
|
||||
}),
|
||||
)
|
||||
return { world, clauses }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Semantic IR to Solver Clause Mapping
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Map a compiled Semantic IR to solver ClauseDescriptors.
|
||||
*
|
||||
* This bridges the DSL compiler output to the solver evaluation input.
|
||||
* The `subjectIdMap` translates semantic IR subject ids to world subject ids.
|
||||
*/
|
||||
export function mapSemanticIrToClauses(
|
||||
semanticIr: SemanticIr,
|
||||
subjectIdMap: Map<ImhotepId, number>,
|
||||
): ClauseDescriptor[] {
|
||||
const clauses: ClauseDescriptor[] = []
|
||||
|
||||
for (const [, clause] of semanticIr.clauses) {
|
||||
const subjectRef = subjectIdMap.get(clause.subjectRef)
|
||||
const referenceRef = clause.referenceRef
|
||||
? subjectIdMap.get(clause.referenceRef)
|
||||
: undefined
|
||||
|
||||
if (subjectRef === undefined) continue
|
||||
|
||||
const tolerance = semanticIr.tolerances.get(clause.toleranceRef)
|
||||
const bounds: Record<string, number> = {}
|
||||
|
||||
if (clause.bounds.minGap) {
|
||||
bounds.minGap = clause.bounds.minGap.value
|
||||
}
|
||||
if (clause.bounds.maxGap) {
|
||||
bounds.maxGap = clause.bounds.maxGap.value
|
||||
}
|
||||
if (tolerance) {
|
||||
bounds.tolerance = tolerance.value
|
||||
}
|
||||
|
||||
clauses.push({
|
||||
clauseId: clause.id,
|
||||
clauseKind: `relation.${clause.relation}`,
|
||||
version: 1,
|
||||
subjectRef,
|
||||
referenceRef,
|
||||
bounds,
|
||||
})
|
||||
}
|
||||
|
||||
return clauses
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Snapshot Builder
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function buildMockSnapshot(opts: {
|
||||
id?: string
|
||||
stateKind?: string
|
||||
stateSource?: string
|
||||
selector?: string
|
||||
world: GeometryWorld
|
||||
}): GeometrySnapshot {
|
||||
const metadata: SnapshotMetadata = {
|
||||
snapshotId: opts.id ?? 'snap_1',
|
||||
stateKind: (opts.stateKind as any) ?? 'default',
|
||||
stateSource: (opts.stateSource as any) ?? 'synthetic',
|
||||
selector: opts.selector,
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
|
||||
return {
|
||||
id: opts.id ?? 'snap_1',
|
||||
metadata,
|
||||
world: opts.world,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user