v1.1.0: pooled runtime, 959 tests, production hardening (0 squash)
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
import type { ImhotepId, StateKind, Environment, ExecutionContext } from 'imhotep-core'
|
||||
import { getDefaultContext } from 'imhotep-core'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Snapshot Identity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Stable identifier for a geometry snapshot. */
|
||||
export type SnapshotId = ImhotepId
|
||||
|
||||
// Module-level monotonic counter ensures unique ids even when deterministic
|
||||
// test contexts return identical clock/idGenerator values per call.
|
||||
let _snapshotCounter = 0
|
||||
|
||||
/** Generates a unique snapshot id with an optional prefix.
|
||||
* When no context is provided, uses the global default context.
|
||||
* A module-level counter guarantees uniqueness across consecutive calls.
|
||||
*/
|
||||
export function createSnapshotId(prefix = 'snap', ctx?: ExecutionContext): SnapshotId {
|
||||
const context = ctx ?? getDefaultContext()
|
||||
const counter = ++_snapshotCounter
|
||||
return `${prefix}_${context.clock()}_${context.idGenerator()}_${counter}`
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// State Source Classification
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Describes how a state was materialized.
|
||||
*
|
||||
* - interaction: Real Playwright interaction (hover, focus, mousedown).
|
||||
* - css-pseudo: Synthetic emulation via forced CSS pseudo-class styles.
|
||||
* - synthetic: Computed projection without browser interaction.
|
||||
* - component-hook: Materialized via a component adapter hook.
|
||||
*/
|
||||
export type StateSource = 'interaction' | 'css-pseudo' | 'synthetic' | 'component-hook'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Snapshot Metadata
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Metadata attached to every geometry snapshot so diagnostics can
|
||||
* trace which state, source, and environment produced it.
|
||||
*/
|
||||
export interface SnapshotMetadata {
|
||||
/** Unique id for this snapshot instance. */
|
||||
snapshotId: SnapshotId
|
||||
/** The UI state kind that was materialized (hover, focus, etc). */
|
||||
stateKind: StateKind
|
||||
/** How the state was produced (real interaction vs synthetic). */
|
||||
stateSource: StateSource
|
||||
/** Selector of the element that received the state, if any. */
|
||||
selector?: string
|
||||
/** Milliseconds since epoch when the snapshot was captured. */
|
||||
timestamp: number
|
||||
/** Environment case active when the snapshot was taken. */
|
||||
env?: Environment
|
||||
/** Duration of the transition sample in ms, if applicable. */
|
||||
durationMs?: number
|
||||
/** Approximation note for diagnostics when state is not fully determinate. */
|
||||
approximationNote?: string
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Geometry Snapshot
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A captured geometry world for a specific UI state.
|
||||
*
|
||||
* The `world` property holds the extractor output (geometry world schema).
|
||||
* It is typed as `unknown` here because the exact schema is owned by the
|
||||
* extractor package; imhotep-state materializes and tags snapshots without
|
||||
* needing to inspect world internals.
|
||||
*/
|
||||
export interface GeometrySnapshot {
|
||||
/** Snapshot identifier used by the solver and diagnostics. */
|
||||
id: SnapshotId
|
||||
/** State metadata for traceability and comparison. */
|
||||
metadata: SnapshotMetadata
|
||||
/** The materialized geometry world produced by the extractor. */
|
||||
world: unknown
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Snapshot Comparison
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Result of comparing two geometry snapshots. */
|
||||
export interface SnapshotDiff {
|
||||
/** True when both snapshots have identical world and metadata. */
|
||||
identical: boolean
|
||||
/** Human-readable list of detected differences. */
|
||||
differences: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep-compares two geometry snapshots for equality.
|
||||
*
|
||||
* Used by tests and diagnostics to detect unexpected changes between
|
||||
* states (e.g. hover caused the element to shift).
|
||||
*/
|
||||
export function compareSnapshots(a: GeometrySnapshot, b: GeometrySnapshot): SnapshotDiff {
|
||||
const differences: string[] = []
|
||||
|
||||
if (a.id !== b.id) {
|
||||
differences.push(`id changed from "${a.id}" to "${b.id}"`)
|
||||
}
|
||||
|
||||
if (a.metadata.stateKind !== b.metadata.stateKind) {
|
||||
differences.push(
|
||||
`stateKind changed from "${a.metadata.stateKind}" to "${b.metadata.stateKind}"`
|
||||
)
|
||||
}
|
||||
|
||||
if (a.metadata.stateSource !== b.metadata.stateSource) {
|
||||
differences.push(
|
||||
`stateSource changed from "${a.metadata.stateSource}" to "${b.metadata.stateSource}"`
|
||||
)
|
||||
}
|
||||
|
||||
if (a.metadata.selector !== b.metadata.selector) {
|
||||
differences.push(
|
||||
`selector changed from "${a.metadata.selector}" to "${b.metadata.selector}"`
|
||||
)
|
||||
}
|
||||
|
||||
// Compare world via JSON serialization for a stable deep equality check.
|
||||
// This is sufficient for unit tests and diagnostic diffs.
|
||||
const worldA = JSON.stringify(a.world)
|
||||
const worldB = JSON.stringify(b.world)
|
||||
if (worldA !== worldB) {
|
||||
differences.push('world content differs')
|
||||
}
|
||||
|
||||
return {
|
||||
identical: differences.length === 0,
|
||||
differences,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Snapshot Registry
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* In-memory store for geometry snapshots during a test run.
|
||||
*
|
||||
* The materializer populates this store; the solver reads from it.
|
||||
*/
|
||||
export interface SnapshotStore {
|
||||
/** Add a snapshot to the store. */
|
||||
add(snapshot: GeometrySnapshot): void
|
||||
/** Retrieve a snapshot by id. */
|
||||
get(id: SnapshotId): GeometrySnapshot | undefined
|
||||
/** List all snapshots in insertion order. */
|
||||
list(): GeometrySnapshot[]
|
||||
/** Remove all snapshots. */
|
||||
clear(): void
|
||||
}
|
||||
|
||||
/** Factory for a default Map-backed snapshot store. */
|
||||
export function createSnapshotStore(): SnapshotStore {
|
||||
const store = new Map<SnapshotId, GeometrySnapshot>()
|
||||
return {
|
||||
add(snapshot) {
|
||||
store.set(snapshot.id, snapshot)
|
||||
},
|
||||
get(id) {
|
||||
return store.get(id)
|
||||
},
|
||||
list() {
|
||||
return Array.from(store.values())
|
||||
},
|
||||
clear() {
|
||||
store.clear()
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user