obix-test 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # obix-test
2
+
3
+ **Headless verification for the OBIX suite.** Depends on `obix-core`.
4
+
5
+ ```bash
6
+ npm install --save-dev obix-test
7
+ ```
8
+
9
+ ## `mountForTest(def, opts?)`
10
+
11
+ Runs a component's `obix-core` store on a virtual clock — no DOM.
12
+
13
+ ```ts
14
+ import test from "node:test";
15
+ import assert from "node:assert/strict";
16
+ import { mountForTest } from "obix-test";
17
+ import { Timer } from "./Timer.ts";
18
+
19
+ test("timer quiesces at the limit", () => {
20
+ const t = mountForTest(Timer, { props: { limitSeconds: 5 } });
21
+ t.dispatch("Start");
22
+ t.advance(8000); // fire the 1s `tick` effect eight times
23
+ assert.equal(t.state.seconds, 5);
24
+ assert.equal(t.state.running, false);
25
+ assert.equal(t.select("finished"), true);
26
+ assert.match(t.renderText(), /Finished/);
27
+ });
28
+ ```
29
+
30
+ `renderText()` performs the same `{marker}` text substitution the DOM binder
31
+ does, so rendered output is assertable without a browser. `advance(ms)` drives
32
+ declared `{ every, while, dispatch }` effects deterministically.
33
+
34
+ ## `checkEquivalence(def, { trace, from?, props? })`
35
+
36
+ Folds `trace` three ways — a **direct reference evaluator**, `toFunc().replay`,
37
+ and `toClass()` method calls — and reports any step where state or a derived
38
+ value diverges. The reference evaluator shares no code with the projections.
39
+
40
+ ```ts
41
+ const report = checkEquivalence(Timer, {
42
+ trace: [["Start"], ["Tick"], ["Tick"], ["Stop"]],
43
+ props: { limitSeconds: 5 },
44
+ });
45
+ assert.ok(report.equivalent, report.divergences.join("\n"));
46
+ // report.history[report.steps] === { seconds: 2, running: false }
47
+ ```
48
+
49
+ ## `createVirtualClock()`
50
+
51
+ The deterministic `Scheduler` used by `mountForTest`, exported for direct use
52
+ with `obix-core.createStore({ scheduler })`.
53
+
54
+ ## Status
55
+
56
+ Level 0. DOM-level mount / hydration tests are deferred (the DOM binder itself
57
+ is unit-tested inside `obix-core`).
@@ -0,0 +1,13 @@
1
+ import { type ComponentDef, type Trace } from "obix-core";
2
+ export interface EquivalenceReport {
3
+ equivalent: boolean;
4
+ steps: number;
5
+ history: unknown[];
6
+ divergences: string[];
7
+ }
8
+ export declare function checkEquivalence<S extends object, P extends object>(def: ComponentDef<S, P>, options?: {
9
+ trace: Trace;
10
+ from?: S;
11
+ props?: Partial<P>;
12
+ }): EquivalenceReport;
13
+ //# sourceMappingURL=equivalence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"equivalence.d.ts","sourceRoot":"","sources":["../src/equivalence.ts"],"names":[],"mappings":"AAaA,OAAO,EAGL,KAAK,YAAY,EAGjB,KAAK,KAAK,EACX,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IAEd,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAiCD,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EACjE,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EACvB,OAAO,GAAE;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAAkB,GACtE,iBAAiB,CAoCnB"}
@@ -0,0 +1,54 @@
1
+ import { toClass, toFunc, } from "obix-core";
2
+ const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
3
+ function directFold(def, trace, from, props) {
4
+ const states = [from];
5
+ const deriveds = [computeDerived(def, from, props)];
6
+ let cur = from;
7
+ for (const [name, payload] of trace) {
8
+ const fn = def.actions?.[name];
9
+ if (!fn)
10
+ throw new Error(`equivalence: unknown action "${name}"`);
11
+ cur = fn(cur, payload, props);
12
+ states.push(cur);
13
+ deriveds.push(computeDerived(def, cur, props));
14
+ }
15
+ return { states, deriveds };
16
+ }
17
+ function computeDerived(def, state, props) {
18
+ const out = {};
19
+ for (const [k, fn] of Object.entries(def.derived ?? {}))
20
+ out[k] = fn(state, props);
21
+ return out;
22
+ }
23
+ export function checkEquivalence(def, options = { trace: [] }) {
24
+ const from = (options.from ?? def.state);
25
+ const props = { ...(def.props ?? {}), ...(options.props ?? {}) };
26
+ const trace = options.trace;
27
+ const divergences = [];
28
+ const ref = directFold(def, trace, from, props);
29
+ const F = toFunc(def);
30
+ for (let i = 1; i <= trace.length; i++) {
31
+ const state = F.replay(trace.slice(0, i), from, props);
32
+ if (!eq(state, ref.states[i])) {
33
+ divergences.push(`step ${i}: toFunc ${JSON.stringify(state)} ≠ reference ${JSON.stringify(ref.states[i])}`);
34
+ }
35
+ }
36
+ const instance = new (toClass(def))({ state: from, props });
37
+ for (let i = 0; i < trace.length; i++) {
38
+ const [name, payload] = trace[i];
39
+ instance[name](payload);
40
+ if (!eq(instance.state, ref.states[i + 1])) {
41
+ divergences.push(`step ${i + 1}: toClass ${JSON.stringify(instance.state)} ≠ reference ${JSON.stringify(ref.states[i + 1])}`);
42
+ }
43
+ if (!eq(instance.derived(), ref.deriveds[i + 1])) {
44
+ divergences.push(`step ${i + 1}: toClass derived ${JSON.stringify(instance.derived())} ≠ reference ${JSON.stringify(ref.deriveds[i + 1])}`);
45
+ }
46
+ }
47
+ return {
48
+ equivalent: divergences.length === 0,
49
+ steps: trace.length,
50
+ history: ref.states,
51
+ divergences,
52
+ };
53
+ }
54
+ //# sourceMappingURL=equivalence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"equivalence.js","sourceRoot":"","sources":["../src/equivalence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export { createVirtualClock } from "./virtual-clock.js";
2
+ export type { VirtualClock } from "./virtual-clock.js";
3
+ export { mountForTest } from "./mount.js";
4
+ export type { MountOptions, MountedComponent } from "./mount.js";
5
+ export { checkEquivalence } from "./equivalence.js";
6
+ export type { EquivalenceReport } from "./equivalence.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { createVirtualClock } from "./virtual-clock.js";
2
+ export { mountForTest } from "./mount.js";
3
+ export { checkEquivalence } from "./equivalence.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGxD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { type ComponentDef, type Store } from "obix-core";
2
+ import { type VirtualClock } from "./virtual-clock.js";
3
+ export interface MountOptions<S extends object, P extends object> {
4
+ state?: S;
5
+ props?: Partial<P>;
6
+ template?: string;
7
+ }
8
+ export interface MountedComponent<S extends object, P extends object> {
9
+ readonly store: Store<S, P>;
10
+ readonly state: S;
11
+ readonly clock: VirtualClock;
12
+ dispatch(action: string, payload?: unknown): S;
13
+ advance(ms: number): void;
14
+ derived(): Record<string, unknown>;
15
+ select(name: string): unknown;
16
+ readonly transitions: number;
17
+ renderText(): string;
18
+ stop(): void;
19
+ }
20
+ export declare function mountForTest<S extends object, P extends object>(def: ComponentDef<S, P>, opts?: MountOptions<S, P>): MountedComponent<S, P>;
21
+ //# sourceMappingURL=mount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAUA,OAAO,EAIL,KAAK,YAAY,EAGjB,KAAK,KAAK,EACX,MAAM,WAAW,CAAC;AACnB,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAqB3E,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM;IAC9D,KAAK,CAAC,EAAE,CAAC,CAAC;IACV,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAEnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM;IAClE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC;IAE/C,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,UAAU,IAAI,MAAM,CAAC;IACrB,IAAI,IAAI,IAAI,CAAC;CACd;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAC7D,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EACvB,IAAI,GAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAM,GAC5B,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CA4BxB"}
package/dist/mount.js ADDED
@@ -0,0 +1,54 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import { buildScope, createStore, resolvePath, } from "obix-core";
4
+ import { createVirtualClock } from "./virtual-clock.js";
5
+ const MARKER = /\{([^{}]+)\}/g;
6
+ function stringify(v) {
7
+ if (v == null)
8
+ return "";
9
+ if (typeof v === "string")
10
+ return v;
11
+ if (typeof v === "number" || typeof v === "boolean")
12
+ return String(v);
13
+ return JSON.stringify(v);
14
+ }
15
+ function templateSource(def, override) {
16
+ if (override != null)
17
+ return override;
18
+ const t = def.template;
19
+ if (typeof t === "string")
20
+ return t;
21
+ if (t instanceof URL) {
22
+ return t.protocol === "file:" ? readFileSync(fileURLToPath(t), "utf8") : "";
23
+ }
24
+ return "";
25
+ }
26
+ export function mountForTest(def, opts = {}) {
27
+ const clock = createVirtualClock();
28
+ const template = templateSource(def, opts.template);
29
+ const store = createStore(def, {
30
+ state: opts.state,
31
+ props: opts.props,
32
+ scheduler: clock.scheduler,
33
+ });
34
+ return {
35
+ store,
36
+ get state() {
37
+ return store.state;
38
+ },
39
+ clock,
40
+ dispatch: (action, payload) => store.dispatch(action, payload),
41
+ advance: (ms) => clock.advance(ms),
42
+ derived: () => store.derived(),
43
+ select: (name) => store.select(name),
44
+ get transitions() {
45
+ return store.revision;
46
+ },
47
+ renderText() {
48
+ const scope = buildScope(store.state, store.props, store.derived());
49
+ return template.replace(MARKER, (_m, path) => stringify(resolvePath(scope, path)));
50
+ },
51
+ stop: () => store.destroy(),
52
+ };
53
+ }
54
+ //# sourceMappingURL=mount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount.js","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACL,UAAU,EACV,WAAW,EACX,WAAW,GAKZ,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,kBAAkB,EAAqB,MAAM,oBAAoB,CAAC;AAE3E,MAAM,MAAM,GAAG,eAAe,CAAC;AAE/B,SAAS,SAAS,CAAC,CAAU;IAC3B,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IACzB,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACpC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,cAAc,CAAC,GAAiB,EAAE,QAAiB;IAC1D,IAAI,QAAQ,IAAI,IAAI;QAAE,OAAO,QAAQ,CAAC;IACtC,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC;IACvB,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;QACrB,OAAO,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAwBD,MAAM,UAAU,YAAY,CAC1B,GAAuB,EACvB,OAA2B,EAAE;IAE7B,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAmB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpE,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,SAAS,EAAE,KAAK,CAAC,SAAS;KAC3B,CAAC,CAAC;IAEH,OAAO;QACL,KAAK;QACL,IAAI,KAAK;YACP,OAAO,KAAK,CAAC,KAAK,CAAC;QACrB,CAAC;QACD,KAAK;QACL,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;QAC9D,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE;QAC9B,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;QACpC,IAAI,WAAW;YACb,OAAO,KAAK,CAAC,QAAQ,CAAC;QACxB,CAAC;QACD,UAAU;YACR,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACpE,OAAO,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7F,CAAC;QACD,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Scheduler } from "obix-core";
2
+ export interface VirtualClock {
3
+ readonly scheduler: Scheduler;
4
+ readonly now: number;
5
+ advance(ms: number): void;
6
+ }
7
+ export declare function createVirtualClock(): VirtualClock;
8
+ //# sourceMappingURL=virtual-clock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-clock.d.ts","sourceRoot":"","sources":["../src/virtual-clock.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,kBAAkB,IAAI,YAAY,CA0CjD"}
@@ -0,0 +1,44 @@
1
+ export function createVirtualClock() {
2
+ let now = 0;
3
+ let seq = 1;
4
+ const timers = new Map();
5
+ const scheduler = {
6
+ setInterval(fn, ms) {
7
+ const id = seq++;
8
+ timers.set(id, { at: now + ms, every: Math.max(1, ms), fn });
9
+ return id;
10
+ },
11
+ clearInterval(handle) {
12
+ timers.delete(handle);
13
+ },
14
+ };
15
+ return {
16
+ scheduler,
17
+ get now() {
18
+ return now;
19
+ },
20
+ advance(ms) {
21
+ const target = now + ms;
22
+ let guard = 0;
23
+ while (guard++ < 1_000_000) {
24
+ let dueId = -1;
25
+ let dueAt = Infinity;
26
+ for (const [id, t] of timers) {
27
+ if (t.at <= target && t.at < dueAt) {
28
+ dueAt = t.at;
29
+ dueId = id;
30
+ }
31
+ }
32
+ if (dueId === -1)
33
+ break;
34
+ const t = timers.get(dueId);
35
+ now = t.at;
36
+ t.fn();
37
+ if (timers.has(dueId))
38
+ t.at += t.every;
39
+ }
40
+ now = target;
41
+ },
42
+ };
43
+ }
44
+ //# sourceMappingURL=virtual-clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-clock.js","sourceRoot":"","sources":["../src/virtual-clock.ts"],"names":[],"mappings":"AAcA,MAAM,UAAU,kBAAkB;IAChC,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyD,CAAC;IAEhF,MAAM,SAAS,GAAc;QAC3B,WAAW,CAAC,EAAE,EAAE,EAAE;YAChB,MAAM,EAAE,GAAG,GAAG,EAAE,CAAC;YACjB,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC7D,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,aAAa,CAAC,MAAM;YAClB,MAAM,CAAC,MAAM,CAAC,MAAgB,CAAC,CAAC;QAClC,CAAC;KACF,CAAC;IAEF,OAAO;QACL,SAAS;QACT,IAAI,GAAG;YACL,OAAO,GAAG,CAAC;QACb,CAAC;QACD,OAAO,CAAC,EAAE;YACR,MAAM,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC;YACxB,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,OAAO,KAAK,EAAE,GAAG,SAAS,EAAE,CAAC;gBAC3B,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;gBACf,IAAI,KAAK,GAAG,QAAQ,CAAC;gBACrB,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC;oBAC7B,IAAI,CAAC,CAAC,EAAE,IAAI,MAAM,IAAI,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC;wBACnC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;wBACb,KAAK,GAAG,EAAE,CAAC;oBACb,CAAC;gBACH,CAAC;gBACD,IAAI,KAAK,KAAK,CAAC,CAAC;oBAAE,MAAM;gBACxB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC;gBAC7B,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;gBACX,CAAC,CAAC,EAAE,EAAE,CAAC;gBACP,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;oBAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC;YACzC,CAAC;YACD,GAAG,GAAG,MAAM,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "obix-test",
3
+ "version": "0.3.0",
4
+ "description": "Headless verification for the OBIX suite — virtual-clock component harness and function/class projection equivalence checking.",
5
+ "keywords": ["obix", "testing", "web-components", "equivalence", "virtual-time"],
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "private": false,
9
+ "sideEffects": false,
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": ["dist", "README.md"],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "test": "node --test test/"
21
+ },
22
+ "dependencies": {
23
+ "obix-core": "0.3.0"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "engines": {
29
+ "node": ">=20.11.0"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/obinexusmk2/obix-monorepo-2026.git",
34
+ "directory": "suite/obix-test"
35
+ }
36
+ }