@sentientui/react 0.27.0 → 0.29.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/dist/devtools.js +1 -1
- package/dist/devtools.js.map +1 -1
- package/dist/devtools.mjs +1 -1
- package/dist/devtools.mjs.map +1 -1
- package/dist/index.d.cts +32 -2
- package/dist/index.d.ts +32 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/next/adaptive-root.d.ts +10 -1
- package/dist/next/adaptive-root.js +2 -2
- package/dist/next/adaptive-root.js.map +1 -1
- package/dist/server.d.cts +3 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1 -1
- package/dist/server.mjs.map +1 -1
- package/dist/testing/msw.d.cts +3 -0
- package/dist/testing/msw.d.ts +3 -0
- package/dist/testing/msw.js +1 -1
- package/dist/testing/msw.js.map +1 -1
- package/dist/testing/msw.mjs +1 -1
- package/dist/testing/msw.mjs.map +1 -1
- package/dist/testing/node.d.cts +3 -0
- package/dist/testing/node.d.ts +3 -0
- package/dist/testing/node.js +1 -1
- package/dist/testing/node.js.map +1 -1
- package/dist/testing/node.mjs +1 -1
- package/dist/testing/node.mjs.map +1 -1
- package/dist/testing/react.d.cts +3 -0
- package/dist/testing/react.d.ts +3 -0
- package/dist/testing/react.js +1 -1
- package/dist/testing/react.js.map +1 -1
- package/dist/testing/react.mjs +1 -1
- package/dist/testing/react.mjs.map +1 -1
- package/dist/testing.d.cts +3 -0
- package/dist/testing.d.ts +3 -0
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/dist/testing.mjs.map +1 -1
- package/package.json +4 -3
package/dist/testing/msw.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/msw.ts","../../src/testing/handlers.ts","../../src/testing/events.ts","../../src/testing/scenario.ts","../../src/testing/resolve.ts"],"sourcesContent":["// msw-dependent helper behind its own subpath (`@sentientui/react/testing/msw`):\n// msw is an OPTIONAL peer, so handlers.ts's top-level `import { http } from\n// 'msw'` re-exported from the main /testing entry crashed any consumer without\n// msw installed the moment they imported ANY testing helper (applyScenario,\n// the Playwright/Cypress mocks, …) — the same reason msw/node already lives at\n// `/testing/node` instead of the main entry.\nexport { scenarioToHandlers } from './handlers.js';\n","import { http, HttpResponse } from 'msw';\nimport type { RequestHandler } from 'msw';\nimport { resolveScenario } from './resolve.js';\nimport type { SentientScenario } from './scenario.js';\n\n/** Turn a scenario into MSW handlers stubbing every SDK endpoint + capturing events. */\nexport function scenarioToHandlers(scenario: SentientScenario = {}): RequestHandler[] {\n return [\n http.all('*/v1/*', async ({ request }) => {\n const bodyText =\n request.method === 'GET' || request.method === 'HEAD' ? null : await request.text();\n const r = await resolveScenario(scenario, request.method, request.url, bodyText);\n if (!r) return undefined; // not a stubbed route — let MSW handle passthrough\n if (r.json === undefined) return new HttpResponse(null, { status: r.status });\n return HttpResponse.json(r.json as object, { status: r.status });\n }),\n ];\n}\n","export type CapturedEvent = {\n eventType: string;\n goalType?: string;\n componentId?: string;\n variantId?: string;\n [k: string]: unknown;\n};\n\nconst captured: CapturedEvent[] = [];\n\nexport function recordEvent(e: CapturedEvent): void {\n captured.push(e);\n}\n\nexport function getSentientEvents(): CapturedEvent[] {\n return [...captured];\n}\n\nexport function clearSentientEvents(): void {\n captured.length = 0;\n}\n\n/** True if any captured event is a goal (component goal_achieved or named goal) with this name. */\nexport function hasFiredGoal(events: CapturedEvent[], goalName: string): boolean {\n return events.some(\n (e) => (e.eventType === 'goal_achieved' || e.eventType === 'goal') && e.goalType === goalName,\n );\n}\n","import { notifyOverridesChanged } from '../override-events.js';\n\nexport type ScenarioWeight = { variantId: string; pulls: number; avgReward: number };\nexport type ScenarioApiOverride =\n | 'error'\n | number\n | { status?: number; body?: unknown; delayMs?: number };\n\nexport type SentientScenario = {\n variants?: Record<string, string>;\n layout?: string[];\n /** Forced persona (canonical PersonaKey). Also sets the persona html attributes. */\n persona?: string;\n /** Persona confidence 0–1; buckets to low/medium/high for the html attribute. Default 1. */\n confidence?: number;\n /** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */\n slots?: Record<string, string | Record<string, string>>;\n weights?: Record<string, ScenarioWeight[]>;\n api?: Record<string, ScenarioApiOverride>;\n};\n\ntype ScenarioWindow = {\n __sentient_overrides?: Record<string, string>;\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n __sentient_persona_override?: { persona: string; confidence?: number };\n};\n\n/**\n * Confidence → band. Cutoffs pinned to @sentientui/policy `confidenceBand`\n * (<0.3 low, <0.7 medium, else high). Duplicated (not imported) because the\n * Playwright init function is serialized into the page and cannot close\n * over imports — both copies are pinned by tests.\n */\nexport function confidenceBandOf(c: number): 'low' | 'medium' | 'high' {\n return c < 0.3 ? 'low' : c < 0.7 ? 'medium' : 'high';\n}\n\n/** Apply a scenario by setting the client-forcing globals the SDK reads. */\nexport function applyScenario(scenario: SentientScenario = {}): void {\n const w = window as unknown as ScenarioWindow;\n w.__sentient_overrides = { ...(scenario.variants ?? {}) };\n if (scenario.layout) w.__sentient_layout_override = scenario.layout;\n else delete w.__sentient_layout_override;\n if (scenario.slots) w.__sentient_slot_overrides = { ...scenario.slots };\n else delete w.__sentient_slot_overrides;\n if (scenario.persona) {\n w.__sentient_persona_override = {\n persona: scenario.persona,\n confidence: scenario.confidence ?? 1,\n };\n try {\n const d = document.documentElement;\n d.setAttribute('data-sentient-persona', scenario.persona);\n d.setAttribute('data-sentient-confidence', confidenceBandOf(scenario.confidence ?? 1));\n } catch {\n /* no DOM (node env) — the override globals still apply */\n }\n } else {\n delete w.__sentient_persona_override;\n }\n // Ring the override bus so hooks already mounted (useAssignment /\n // useSlotResult / useAdaptivePersona subscribe via useSyncExternalStore)\n // re-render immediately — otherwise forcing state mid-test only takes effect\n // on the next unrelated render.\n notifyOverridesChanged();\n}\n\n/** Clear all forced state. */\nexport function resetScenario(): void {\n const w = window as unknown as ScenarioWindow;\n delete w.__sentient_overrides;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete w.__sentient_persona_override;\n try {\n document.documentElement.removeAttribute('data-sentient-persona');\n document.documentElement.removeAttribute('data-sentient-confidence');\n } catch {\n /* no DOM */\n }\n notifyOverridesChanged();\n}\n","import { recordEvent, type CapturedEvent } from './events.js';\nimport { confidenceBandOf, type SentientScenario, type ScenarioApiOverride } from './scenario.js';\n\n/** A framework-agnostic resolved response. `json` undefined ⇒ empty body. */\nexport type ResolvedResponse = { status: number; json?: unknown };\n\nfunction pathOf(url: string): string {\n try { return new URL(url).pathname; } catch { return url.split('?')[0] ?? url; }\n}\n\nasync function apiOverride(scenario: SentientScenario, route: string): Promise<ResolvedResponse | null> {\n const o: ScenarioApiOverride | undefined = scenario.api?.[route];\n if (o === undefined) return null;\n if (o === 'error') return { status: 500 };\n if (typeof o === 'number') return { status: o };\n if (o.delayMs) await new Promise((r) => setTimeout(r, o.delayMs));\n return { status: o.status ?? 200, json: o.body ?? {} };\n}\n\n/**\n * Resolve a request against a scenario. Returns a response, or null for routes\n * outside `/v1/*` (let the caller pass through). Shared by the MSW handlers and\n * the Playwright/Cypress adapters so behaviour can't drift.\n */\nexport async function resolveScenario(\n scenario: SentientScenario,\n _method: string,\n url: string,\n bodyText: string | null,\n): Promise<ResolvedResponse | null> {\n const path = pathOf(url);\n if (!path.includes('/v1/')) return null;\n\n const route = '/v1/' + (path.split('/v1/')[1] ?? '');\n const override = await apiOverride(scenario, route);\n if (override) return override;\n\n const body = bodyText ? (JSON.parse(bodyText) as unknown) : {};\n\n if (route === '/v1/sessions') return { status: 204 };\n\n if (route === '/v1/events') {\n for (const e of body as CapturedEvent[]) recordEvent(e);\n return { status: 204 };\n }\n\n if (route === '/v1/goals') {\n recordEvent({ eventType: 'goal', goalType: (body as { name?: string }).name });\n return { status: 204 };\n }\n\n if (route === '/v1/assign') {\n const b = body as { componentId: string; variantIds?: string[] };\n const variantId = scenario.variants?.[b.componentId] ?? b.variantIds?.[0] ?? 'control';\n return { status: 200, json: { variantId, assignmentTtlMs: 60_000 } };\n }\n\n if (route === '/v1/decide') {\n const b = body as {\n sections?: { id: string }[];\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona: scenario.persona ?? 'unknown',\n confidence: scenario.confidence ?? 1,\n };\n // Mirror the real server: the slots key exists ONLY when slots were requested.\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/explain') {\n const b = body as {\n sections?: { id: string }[];\n persona?: string;\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const persona = b.persona ?? scenario.persona ?? 'unknown';\n const confidence = scenario.confidence ?? 1;\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona,\n reasons: [],\n personaAttributes: { persona, confidence: confidenceBandOf(confidence) },\n };\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/weights') {\n const components = Object.entries(scenario.weights ?? {}).map(([componentId, variants]) => ({ componentId, updatedAt: 0, variants }));\n return { status: 200, json: { components } };\n }\n\n return null;\n}\n\n/** Declared baseline of a slot: explicit `baseline`, else first arm / first value per dim. */\nfunction defaultSlotResult(decl: {\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n}): string | Record<string, string> {\n if (decl.arms) {\n return typeof decl.baseline === 'string' ? decl.baseline : decl.arms[0] ?? 'baseline';\n }\n const out: Record<string, string> = {};\n for (const [dim, values] of Object.entries(decl.dims ?? {})) {\n const declared =\n typeof decl.baseline === 'object' && decl.baseline !== null ? decl.baseline[dim] : undefined;\n out[dim] = declared ?? values[0] ?? '';\n }\n return out;\n}\n"],"mappings":"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","names":["msw_exports","__export","scenarioToHandlers","__toCommonJS","import_msw","captured","recordEvent","confidenceBandOf","c","pathOf","url","_a","e","apiOverride","scenario","route","_b","_c","o","r","resolveScenario","_method","bodyText","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","_q","_r","_s","_t","_u","path","override","body","recordEvent","b","json","s","slots","decl","defaultSlotResult","layoutOrder","persona","confidence","confidenceBandOf","componentId","variants","out","dim","values","declared","scenarioToHandlers","scenario","request","bodyText","r","resolveScenario"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/msw.ts","../../src/testing/handlers.ts","../../src/testing/events.ts","../../src/testing/scenario.ts","../../src/testing/resolve.ts"],"sourcesContent":["// msw-dependent helper behind its own subpath (`@sentientui/react/testing/msw`):\n// msw is an OPTIONAL peer, so handlers.ts's top-level `import { http } from\n// 'msw'` re-exported from the main /testing entry crashed any consumer without\n// msw installed the moment they imported ANY testing helper (applyScenario,\n// the Playwright/Cypress mocks, …) — the same reason msw/node already lives at\n// `/testing/node` instead of the main entry.\nexport { scenarioToHandlers } from './handlers.js';\n","import { http, HttpResponse } from 'msw';\nimport type { RequestHandler } from 'msw';\nimport { resolveScenario } from './resolve.js';\nimport type { SentientScenario } from './scenario.js';\n\n/** Turn a scenario into MSW handlers stubbing every SDK endpoint + capturing events. */\nexport function scenarioToHandlers(scenario: SentientScenario = {}): RequestHandler[] {\n return [\n http.all('*/v1/*', async ({ request }) => {\n const bodyText =\n request.method === 'GET' || request.method === 'HEAD' ? null : await request.text();\n const r = await resolveScenario(scenario, request.method, request.url, bodyText);\n if (!r) return undefined; // not a stubbed route — let MSW handle passthrough\n if (r.json === undefined) return new HttpResponse(null, { status: r.status });\n return HttpResponse.json(r.json as object, { status: r.status });\n }),\n ];\n}\n","export type CapturedEvent = {\n eventType: string;\n goalType?: string;\n componentId?: string;\n variantId?: string;\n [k: string]: unknown;\n};\n\nconst captured: CapturedEvent[] = [];\n\nexport function recordEvent(e: CapturedEvent): void {\n captured.push(e);\n}\n\nexport function getSentientEvents(): CapturedEvent[] {\n return [...captured];\n}\n\nexport function clearSentientEvents(): void {\n captured.length = 0;\n}\n\n/** True if any captured event is a goal (component goal_achieved or named goal) with this name. */\nexport function hasFiredGoal(events: CapturedEvent[], goalName: string): boolean {\n return events.some(\n (e) => (e.eventType === 'goal_achieved' || e.eventType === 'goal') && e.goalType === goalName,\n );\n}\n","import { notifyOverridesChanged } from '../override-events.js';\n\nexport type ScenarioWeight = { variantId: string; pulls: number; avgReward: number };\nexport type ScenarioApiOverride =\n | 'error'\n | number\n | { status?: number; body?: unknown; delayMs?: number };\n\nexport type SentientScenario = {\n variants?: Record<string, string>;\n layout?: string[];\n /** Forced persona (canonical PersonaKey). Also sets the persona html attributes. */\n persona?: string;\n /** Persona confidence 0–1; buckets to low/medium/high for the html attribute. Default 1. */\n confidence?: number;\n /** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */\n slots?: Record<string, string | Record<string, string>>;\n /** Forced registry slot config (AdaptiveSlot content/blocks): slot id → entry.\n * Pair with `slots` to name which arm is \"served\" (blocks are keyed by arm). */\n slotConfig?: Record<string, unknown>;\n weights?: Record<string, ScenarioWeight[]>;\n api?: Record<string, ScenarioApiOverride>;\n};\n\ntype ScenarioWindow = {\n __sentient_overrides?: Record<string, string>;\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n __sentient_slot_config_overrides?: Record<string, unknown>;\n __sentient_persona_override?: { persona: string; confidence?: number };\n};\n\n/**\n * Confidence → band. Cutoffs pinned to @sentientui/policy `confidenceBand`\n * (<0.3 low, <0.7 medium, else high). Duplicated (not imported) because the\n * Playwright init function is serialized into the page and cannot close\n * over imports — both copies are pinned by tests.\n */\nexport function confidenceBandOf(c: number): 'low' | 'medium' | 'high' {\n return c < 0.3 ? 'low' : c < 0.7 ? 'medium' : 'high';\n}\n\n/** Apply a scenario by setting the client-forcing globals the SDK reads. */\nexport function applyScenario(scenario: SentientScenario = {}): void {\n const w = window as unknown as ScenarioWindow;\n w.__sentient_overrides = { ...(scenario.variants ?? {}) };\n if (scenario.layout) w.__sentient_layout_override = scenario.layout;\n else delete w.__sentient_layout_override;\n if (scenario.slots) w.__sentient_slot_overrides = { ...scenario.slots };\n else delete w.__sentient_slot_overrides;\n if (scenario.slotConfig) w.__sentient_slot_config_overrides = { ...scenario.slotConfig };\n else delete w.__sentient_slot_config_overrides;\n if (scenario.persona) {\n w.__sentient_persona_override = {\n persona: scenario.persona,\n confidence: scenario.confidence ?? 1,\n };\n try {\n const d = document.documentElement;\n d.setAttribute('data-sentient-persona', scenario.persona);\n d.setAttribute('data-sentient-confidence', confidenceBandOf(scenario.confidence ?? 1));\n } catch {\n /* no DOM (node env) — the override globals still apply */\n }\n } else {\n delete w.__sentient_persona_override;\n }\n // Ring the override bus so hooks already mounted (useAssignment /\n // useSlotResult / useAdaptivePersona subscribe via useSyncExternalStore)\n // re-render immediately — otherwise forcing state mid-test only takes effect\n // on the next unrelated render.\n notifyOverridesChanged();\n}\n\n/** Clear all forced state. */\nexport function resetScenario(): void {\n const w = window as unknown as ScenarioWindow;\n delete w.__sentient_overrides;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete w.__sentient_slot_config_overrides;\n delete w.__sentient_persona_override;\n try {\n document.documentElement.removeAttribute('data-sentient-persona');\n document.documentElement.removeAttribute('data-sentient-confidence');\n } catch {\n /* no DOM */\n }\n notifyOverridesChanged();\n}\n","import { recordEvent, type CapturedEvent } from './events.js';\nimport { confidenceBandOf, type SentientScenario, type ScenarioApiOverride } from './scenario.js';\n\n/** A framework-agnostic resolved response. `json` undefined ⇒ empty body. */\nexport type ResolvedResponse = { status: number; json?: unknown };\n\nfunction pathOf(url: string): string {\n try { return new URL(url).pathname; } catch { return url.split('?')[0] ?? url; }\n}\n\nasync function apiOverride(scenario: SentientScenario, route: string): Promise<ResolvedResponse | null> {\n const o: ScenarioApiOverride | undefined = scenario.api?.[route];\n if (o === undefined) return null;\n if (o === 'error') return { status: 500 };\n if (typeof o === 'number') return { status: o };\n if (o.delayMs) await new Promise((r) => setTimeout(r, o.delayMs));\n return { status: o.status ?? 200, json: o.body ?? {} };\n}\n\n/**\n * Resolve a request against a scenario. Returns a response, or null for routes\n * outside `/v1/*` (let the caller pass through). Shared by the MSW handlers and\n * the Playwright/Cypress adapters so behaviour can't drift.\n */\nexport async function resolveScenario(\n scenario: SentientScenario,\n _method: string,\n url: string,\n bodyText: string | null,\n): Promise<ResolvedResponse | null> {\n const path = pathOf(url);\n if (!path.includes('/v1/')) return null;\n\n const route = '/v1/' + (path.split('/v1/')[1] ?? '');\n const override = await apiOverride(scenario, route);\n if (override) return override;\n\n const body = bodyText ? (JSON.parse(bodyText) as unknown) : {};\n\n if (route === '/v1/sessions') return { status: 204 };\n\n if (route === '/v1/events') {\n for (const e of body as CapturedEvent[]) recordEvent(e);\n return { status: 204 };\n }\n\n if (route === '/v1/goals') {\n recordEvent({ eventType: 'goal', goalType: (body as { name?: string }).name });\n return { status: 204 };\n }\n\n if (route === '/v1/assign') {\n const b = body as { componentId: string; variantIds?: string[] };\n const variantId = scenario.variants?.[b.componentId] ?? b.variantIds?.[0] ?? 'control';\n return { status: 200, json: { variantId, assignmentTtlMs: 60_000 } };\n }\n\n if (route === '/v1/decide') {\n const b = body as {\n sections?: { id: string }[];\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona: scenario.persona ?? 'unknown',\n confidence: scenario.confidence ?? 1,\n };\n // Mirror the real server: the slots key exists ONLY when slots were requested.\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/explain') {\n const b = body as {\n sections?: { id: string }[];\n persona?: string;\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const persona = b.persona ?? scenario.persona ?? 'unknown';\n const confidence = scenario.confidence ?? 1;\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona,\n reasons: [],\n personaAttributes: { persona, confidence: confidenceBandOf(confidence) },\n };\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/weights') {\n const components = Object.entries(scenario.weights ?? {}).map(([componentId, variants]) => ({ componentId, updatedAt: 0, variants }));\n return { status: 200, json: { components } };\n }\n\n return null;\n}\n\n/** Declared baseline of a slot: explicit `baseline`, else first arm / first value per dim. */\nfunction defaultSlotResult(decl: {\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n}): string | Record<string, string> {\n if (decl.arms) {\n return typeof decl.baseline === 'string' ? decl.baseline : decl.arms[0] ?? 'baseline';\n }\n const out: Record<string, string> = {};\n for (const [dim, values] of Object.entries(decl.dims ?? {})) {\n const declared =\n typeof decl.baseline === 'object' && decl.baseline !== null ? decl.baseline[dim] : undefined;\n out[dim] = declared ?? values[0] ?? '';\n }\n return out;\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,wBAAAE,IAAA,eAAAC,EAAAH,GCAA,IAAAI,EAAmC,eCQnC,IAAMC,EAA4B,CAAC,EAE5B,SAASC,EAAY,EAAwB,CAClDD,EAAS,KAAK,CAAC,CACjB,CC0BO,SAASE,EAAiBC,EAAsC,CACrE,OAAOA,EAAI,GAAM,MAAQA,EAAI,GAAM,SAAW,MAChD,CClCA,SAASC,EAAOC,EAAqB,CANrC,IAAAC,EAOE,GAAI,CAAE,OAAO,IAAI,IAAID,CAAG,EAAE,QAAU,OAAQE,EAAA,CAAE,OAAOD,EAAAD,EAAI,MAAM,GAAG,EAAE,CAAC,IAAhB,KAAAC,EAAqBD,CAAK,CACjF,CAEA,eAAeG,EAAYC,EAA4BC,EAAiD,CAVxG,IAAAJ,EAAAK,EAAAC,EAWE,IAAMC,GAAqCP,EAAAG,EAAS,MAAT,YAAAH,EAAeI,GAC1D,OAAIG,IAAM,OAAkB,KACxBA,IAAM,QAAgB,CAAE,OAAQ,GAAI,EACpC,OAAOA,GAAM,SAAiB,CAAE,OAAQA,CAAE,GAC1CA,EAAE,SAAS,MAAM,IAAI,QAASC,GAAM,WAAWA,EAAGD,EAAE,OAAO,CAAC,EACzD,CAAE,QAAQF,EAAAE,EAAE,SAAF,KAAAF,EAAY,IAAK,MAAMC,EAAAC,EAAE,OAAF,KAAAD,EAAU,CAAC,CAAE,EACvD,CAOA,eAAsBG,EACpBN,EACAO,EACAX,EACAY,EACkC,CA7BpC,IAAAX,EAAAK,EAAAC,EAAAM,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EA8BE,IAAMC,EAAOhC,EAAOC,CAAG,EACvB,GAAI,CAAC+B,EAAK,SAAS,MAAM,EAAG,OAAO,KAEnC,IAAM1B,EAAQ,SAAUJ,EAAA8B,EAAK,MAAM,MAAM,EAAE,CAAC,IAApB,KAAA9B,EAAyB,IAC3C+B,EAAW,MAAM7B,EAAYC,EAAUC,CAAK,EAClD,GAAI2B,EAAU,OAAOA,EAErB,IAAMC,EAAOrB,EAAY,KAAK,MAAMA,CAAQ,EAAgB,CAAC,EAE7D,GAAIP,IAAU,eAAgB,MAAO,CAAE,OAAQ,GAAI,EAEnD,GAAIA,IAAU,aAAc,CAC1B,QAAWH,KAAK+B,EAAyBC,EAAYhC,CAAC,EACtD,MAAO,CAAE,OAAQ,GAAI,CACvB,CAEA,GAAIG,IAAU,YACZ,OAAA6B,EAAY,CAAE,UAAW,OAAQ,SAAWD,EAA2B,IAAK,CAAC,EACtE,CAAE,OAAQ,GAAI,EAGvB,GAAI5B,IAAU,aAAc,CAC1B,IAAM8B,EAAIF,EAEV,MAAO,CAAE,OAAQ,IAAK,KAAM,CAAE,WADZnB,GAAAD,GAAAP,EAAAF,EAAS,WAAT,YAAAE,EAAoB6B,EAAE,eAAtB,KAAAtB,GAAsCN,EAAA4B,EAAE,aAAF,YAAA5B,EAAe,KAArD,KAAAO,EAA2D,UACpC,gBAAiB,GAAO,CAAE,CACrE,CAEA,GAAIT,IAAU,aAAc,CAC1B,IAAM8B,EAAIF,EAUJG,EAAgC,CACpC,aAFkBpB,EAAAZ,EAAS,SAAT,KAAAY,IAAoBD,EAAAoB,EAAE,WAAF,KAAApB,EAAc,CAAC,GAAG,IAAKsB,GAAMA,EAAE,EAAE,EAGvE,aAAapB,EAAAb,EAAS,WAAT,KAAAa,EAAqB,CAAC,EACnC,SAASC,EAAAd,EAAS,UAAT,KAAAc,EAAoB,UAC7B,YAAYC,EAAAf,EAAS,aAAT,KAAAe,EAAuB,CACrC,EAEA,GAAIgB,EAAE,OAASA,EAAE,MAAM,OAAS,EAAG,CACjC,IAAMG,EAAiC,CAAC,EACxC,QAAWC,KAAQJ,EAAE,MACnBG,EAAMC,EAAK,EAAE,GAAIlB,GAAAD,EAAAhB,EAAS,QAAT,YAAAgB,EAAiBmB,EAAK,MAAtB,KAAAlB,EAA6BmB,EAAkBD,CAAI,EAEtEH,EAAK,MAAQE,CACf,CACA,MAAO,CAAE,OAAQ,IAAK,KAAAF,CAAK,CAC7B,CAEA,GAAI/B,IAAU,cAAe,CAC3B,IAAM8B,EAAIF,EAUJQ,GAAclB,EAAAnB,EAAS,SAAT,KAAAmB,IAAoBD,EAAAa,EAAE,WAAF,KAAAb,EAAc,CAAC,GAAG,IAAKe,GAAMA,EAAE,EAAE,EACnEK,GAAUjB,GAAAD,EAAAW,EAAE,UAAF,KAAAX,EAAapB,EAAS,UAAtB,KAAAqB,EAAiC,UAC3CkB,GAAajB,EAAAtB,EAAS,aAAT,KAAAsB,EAAuB,EACpCU,EAAgC,CACpC,YAAAK,EACA,aAAad,EAAAvB,EAAS,WAAT,KAAAuB,EAAqB,CAAC,EACnC,QAAAe,EACA,QAAS,CAAC,EACV,kBAAmB,CAAE,QAAAA,EAAS,WAAYE,EAAiBD,CAAU,CAAE,CACzE,EACA,GAAIR,EAAE,OAASA,EAAE,MAAM,OAAS,EAAG,CACjC,IAAMG,EAAiC,CAAC,EACxC,QAAWC,KAAQJ,EAAE,MACnBG,EAAMC,EAAK,EAAE,GAAIV,GAAAD,EAAAxB,EAAS,QAAT,YAAAwB,EAAiBW,EAAK,MAAtB,KAAAV,EAA6BW,EAAkBD,CAAI,EAEtEH,EAAK,MAAQE,CACf,CACA,MAAO,CAAE,OAAQ,IAAK,KAAAF,CAAK,CAC7B,CAEA,OAAI/B,IAAU,cAEL,CAAE,OAAQ,IAAK,KAAM,CAAE,WADX,OAAO,SAAQyB,EAAA1B,EAAS,UAAT,KAAA0B,EAAoB,CAAC,CAAC,EAAE,IAAI,CAAC,CAACe,EAAaC,CAAQ,KAAO,CAAE,YAAAD,EAAa,UAAW,EAAG,SAAAC,CAAS,EAAE,CAC3F,CAAE,EAGtC,IACT,CAGA,SAASN,EAAkBD,EAIS,CAjIpC,IAAAtC,EAAAK,EAAAC,EAkIE,GAAIgC,EAAK,KACP,OAAO,OAAOA,EAAK,UAAa,SAAWA,EAAK,UAAWtC,EAAAsC,EAAK,KAAK,CAAC,IAAX,KAAAtC,EAAgB,WAE7E,IAAM8C,EAA8B,CAAC,EACrC,OAAW,CAACC,EAAKC,CAAM,IAAK,OAAO,SAAQ3C,EAAAiC,EAAK,OAAL,KAAAjC,EAAa,CAAC,CAAC,EAAG,CAC3D,IAAM4C,EACJ,OAAOX,EAAK,UAAa,UAAYA,EAAK,WAAa,KAAOA,EAAK,SAASS,CAAG,EAAI,OACrFD,EAAIC,CAAG,GAAIzC,EAAA2C,GAAA,KAAAA,EAAYD,EAAO,CAAC,IAApB,KAAA1C,EAAyB,EACtC,CACA,OAAOwC,CACT,CHtIO,SAASI,EAAmBC,EAA6B,CAAC,EAAqB,CACpF,MAAO,CACL,OAAK,IAAI,SAAU,MAAO,CAAE,QAAAC,CAAQ,IAAM,CACxC,IAAMC,EACJD,EAAQ,SAAW,OAASA,EAAQ,SAAW,OAAS,KAAO,MAAMA,EAAQ,KAAK,EAC9EE,EAAI,MAAMC,EAAgBJ,EAAUC,EAAQ,OAAQA,EAAQ,IAAKC,CAAQ,EAC/E,GAAKC,EACL,OAAIA,EAAE,OAAS,OAAkB,IAAI,eAAa,KAAM,CAAE,OAAQA,EAAE,MAAO,CAAC,EACrE,eAAa,KAAKA,EAAE,KAAgB,CAAE,OAAQA,EAAE,MAAO,CAAC,CACjE,CAAC,CACH,CACF","names":["msw_exports","__export","scenarioToHandlers","__toCommonJS","import_msw","captured","recordEvent","confidenceBandOf","c","pathOf","url","_a","e","apiOverride","scenario","route","_b","_c","o","r","resolveScenario","_method","bodyText","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","_q","_r","_s","_t","_u","path","override","body","recordEvent","b","json","s","slots","decl","defaultSlotResult","layoutOrder","persona","confidence","confidenceBandOf","componentId","variants","out","dim","values","declared","scenarioToHandlers","scenario","request","bodyText","r","resolveScenario"]}
|
package/dist/testing/msw.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{http as U,HttpResponse as F}from"msw";var J=[];function
|
|
1
|
+
import{http as U,HttpResponse as F}from"msw";var J=[];function _(e){J.push(e)}function B(e){return e<.3?"low":e<.7?"medium":"high"}function L(e){var s;try{return new URL(e).pathname}catch(n){return(s=e.split("?")[0])!=null?s:e}}async function N(e,s){var r,a,o;let n=(r=e.api)==null?void 0:r[s];return n===void 0?null:n==="error"?{status:500}:typeof n=="number"?{status:n}:(n.delayMs&&await new Promise(u=>setTimeout(u,n.delayMs)),{status:(a=n.status)!=null?a:200,json:(o=n.body)!=null?o:{}})}async function D(e,s,n,r){var v,m,y,w,b,R,S,h,x,E,k,O,j,A,T,C,I,H,W,M,P;let a=L(n);if(!a.includes("/v1/"))return null;let o="/v1/"+((v=a.split("/v1/")[1])!=null?v:""),u=await N(e,o);if(u)return u;let i=r?JSON.parse(r):{};if(o==="/v1/sessions")return{status:204};if(o==="/v1/events"){for(let t of i)_(t);return{status:204}}if(o==="/v1/goals")return _({eventType:"goal",goalType:i.name}),{status:204};if(o==="/v1/assign"){let t=i;return{status:200,json:{variantId:(b=(w=(m=e.variants)==null?void 0:m[t.componentId])!=null?w:(y=t.variantIds)==null?void 0:y[0])!=null?b:"control",assignmentTtlMs:6e4}}}if(o==="/v1/decide"){let t=i,d={layoutOrder:(S=e.layout)!=null?S:((R=t.sections)!=null?R:[]).map(c=>c.id),assignments:(h=e.variants)!=null?h:{},persona:(x=e.persona)!=null?x:"unknown",confidence:(E=e.confidence)!=null?E:1};if(t.slots&&t.slots.length>0){let c={};for(let l of t.slots)c[l.id]=(O=(k=e.slots)==null?void 0:k[l.id])!=null?O:G(l);d.slots=c}return{status:200,json:d}}if(o==="/v1/explain"){let t=i,p=(A=e.layout)!=null?A:((j=t.sections)!=null?j:[]).map(g=>g.id),d=(C=(T=t.persona)!=null?T:e.persona)!=null?C:"unknown",c=(I=e.confidence)!=null?I:1,l={layoutOrder:p,assignments:(H=e.variants)!=null?H:{},persona:d,reasons:[],personaAttributes:{persona:d,confidence:B(c)}};if(t.slots&&t.slots.length>0){let g={};for(let f of t.slots)g[f.id]=(M=(W=e.slots)==null?void 0:W[f.id])!=null?M:G(f);l.slots=g}return{status:200,json:l}}return o==="/v1/weights"?{status:200,json:{components:Object.entries((P=e.weights)!=null?P:{}).map(([p,d])=>({componentId:p,updatedAt:0,variants:d}))}}:null}function G(e){var n,r,a;if(e.arms)return typeof e.baseline=="string"?e.baseline:(n=e.arms[0])!=null?n:"baseline";let s={};for(let[o,u]of Object.entries((r=e.dims)!=null?r:{})){let i=typeof e.baseline=="object"&&e.baseline!==null?e.baseline[o]:void 0;s[o]=(a=i!=null?i:u[0])!=null?a:""}return s}function z(e={}){return[U.all("*/v1/*",async({request:s})=>{let n=s.method==="GET"||s.method==="HEAD"?null:await s.text(),r=await D(e,s.method,s.url,n);if(r)return r.json===void 0?new F(null,{status:r.status}):F.json(r.json,{status:r.status})})]}export{z as scenarioToHandlers};
|
|
2
2
|
//# sourceMappingURL=msw.mjs.map
|
package/dist/testing/msw.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/handlers.ts","../../src/testing/events.ts","../../src/testing/scenario.ts","../../src/testing/resolve.ts"],"sourcesContent":["import { http, HttpResponse } from 'msw';\nimport type { RequestHandler } from 'msw';\nimport { resolveScenario } from './resolve.js';\nimport type { SentientScenario } from './scenario.js';\n\n/** Turn a scenario into MSW handlers stubbing every SDK endpoint + capturing events. */\nexport function scenarioToHandlers(scenario: SentientScenario = {}): RequestHandler[] {\n return [\n http.all('*/v1/*', async ({ request }) => {\n const bodyText =\n request.method === 'GET' || request.method === 'HEAD' ? null : await request.text();\n const r = await resolveScenario(scenario, request.method, request.url, bodyText);\n if (!r) return undefined; // not a stubbed route — let MSW handle passthrough\n if (r.json === undefined) return new HttpResponse(null, { status: r.status });\n return HttpResponse.json(r.json as object, { status: r.status });\n }),\n ];\n}\n","export type CapturedEvent = {\n eventType: string;\n goalType?: string;\n componentId?: string;\n variantId?: string;\n [k: string]: unknown;\n};\n\nconst captured: CapturedEvent[] = [];\n\nexport function recordEvent(e: CapturedEvent): void {\n captured.push(e);\n}\n\nexport function getSentientEvents(): CapturedEvent[] {\n return [...captured];\n}\n\nexport function clearSentientEvents(): void {\n captured.length = 0;\n}\n\n/** True if any captured event is a goal (component goal_achieved or named goal) with this name. */\nexport function hasFiredGoal(events: CapturedEvent[], goalName: string): boolean {\n return events.some(\n (e) => (e.eventType === 'goal_achieved' || e.eventType === 'goal') && e.goalType === goalName,\n );\n}\n","import { notifyOverridesChanged } from '../override-events.js';\n\nexport type ScenarioWeight = { variantId: string; pulls: number; avgReward: number };\nexport type ScenarioApiOverride =\n | 'error'\n | number\n | { status?: number; body?: unknown; delayMs?: number };\n\nexport type SentientScenario = {\n variants?: Record<string, string>;\n layout?: string[];\n /** Forced persona (canonical PersonaKey). Also sets the persona html attributes. */\n persona?: string;\n /** Persona confidence 0–1; buckets to low/medium/high for the html attribute. Default 1. */\n confidence?: number;\n /** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */\n slots?: Record<string, string | Record<string, string>>;\n weights?: Record<string, ScenarioWeight[]>;\n api?: Record<string, ScenarioApiOverride>;\n};\n\ntype ScenarioWindow = {\n __sentient_overrides?: Record<string, string>;\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n __sentient_persona_override?: { persona: string; confidence?: number };\n};\n\n/**\n * Confidence → band. Cutoffs pinned to @sentientui/policy `confidenceBand`\n * (<0.3 low, <0.7 medium, else high). Duplicated (not imported) because the\n * Playwright init function is serialized into the page and cannot close\n * over imports — both copies are pinned by tests.\n */\nexport function confidenceBandOf(c: number): 'low' | 'medium' | 'high' {\n return c < 0.3 ? 'low' : c < 0.7 ? 'medium' : 'high';\n}\n\n/** Apply a scenario by setting the client-forcing globals the SDK reads. */\nexport function applyScenario(scenario: SentientScenario = {}): void {\n const w = window as unknown as ScenarioWindow;\n w.__sentient_overrides = { ...(scenario.variants ?? {}) };\n if (scenario.layout) w.__sentient_layout_override = scenario.layout;\n else delete w.__sentient_layout_override;\n if (scenario.slots) w.__sentient_slot_overrides = { ...scenario.slots };\n else delete w.__sentient_slot_overrides;\n if (scenario.persona) {\n w.__sentient_persona_override = {\n persona: scenario.persona,\n confidence: scenario.confidence ?? 1,\n };\n try {\n const d = document.documentElement;\n d.setAttribute('data-sentient-persona', scenario.persona);\n d.setAttribute('data-sentient-confidence', confidenceBandOf(scenario.confidence ?? 1));\n } catch {\n /* no DOM (node env) — the override globals still apply */\n }\n } else {\n delete w.__sentient_persona_override;\n }\n // Ring the override bus so hooks already mounted (useAssignment /\n // useSlotResult / useAdaptivePersona subscribe via useSyncExternalStore)\n // re-render immediately — otherwise forcing state mid-test only takes effect\n // on the next unrelated render.\n notifyOverridesChanged();\n}\n\n/** Clear all forced state. */\nexport function resetScenario(): void {\n const w = window as unknown as ScenarioWindow;\n delete w.__sentient_overrides;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete w.__sentient_persona_override;\n try {\n document.documentElement.removeAttribute('data-sentient-persona');\n document.documentElement.removeAttribute('data-sentient-confidence');\n } catch {\n /* no DOM */\n }\n notifyOverridesChanged();\n}\n","import { recordEvent, type CapturedEvent } from './events.js';\nimport { confidenceBandOf, type SentientScenario, type ScenarioApiOverride } from './scenario.js';\n\n/** A framework-agnostic resolved response. `json` undefined ⇒ empty body. */\nexport type ResolvedResponse = { status: number; json?: unknown };\n\nfunction pathOf(url: string): string {\n try { return new URL(url).pathname; } catch { return url.split('?')[0] ?? url; }\n}\n\nasync function apiOverride(scenario: SentientScenario, route: string): Promise<ResolvedResponse | null> {\n const o: ScenarioApiOverride | undefined = scenario.api?.[route];\n if (o === undefined) return null;\n if (o === 'error') return { status: 500 };\n if (typeof o === 'number') return { status: o };\n if (o.delayMs) await new Promise((r) => setTimeout(r, o.delayMs));\n return { status: o.status ?? 200, json: o.body ?? {} };\n}\n\n/**\n * Resolve a request against a scenario. Returns a response, or null for routes\n * outside `/v1/*` (let the caller pass through). Shared by the MSW handlers and\n * the Playwright/Cypress adapters so behaviour can't drift.\n */\nexport async function resolveScenario(\n scenario: SentientScenario,\n _method: string,\n url: string,\n bodyText: string | null,\n): Promise<ResolvedResponse | null> {\n const path = pathOf(url);\n if (!path.includes('/v1/')) return null;\n\n const route = '/v1/' + (path.split('/v1/')[1] ?? '');\n const override = await apiOverride(scenario, route);\n if (override) return override;\n\n const body = bodyText ? (JSON.parse(bodyText) as unknown) : {};\n\n if (route === '/v1/sessions') return { status: 204 };\n\n if (route === '/v1/events') {\n for (const e of body as CapturedEvent[]) recordEvent(e);\n return { status: 204 };\n }\n\n if (route === '/v1/goals') {\n recordEvent({ eventType: 'goal', goalType: (body as { name?: string }).name });\n return { status: 204 };\n }\n\n if (route === '/v1/assign') {\n const b = body as { componentId: string; variantIds?: string[] };\n const variantId = scenario.variants?.[b.componentId] ?? b.variantIds?.[0] ?? 'control';\n return { status: 200, json: { variantId, assignmentTtlMs: 60_000 } };\n }\n\n if (route === '/v1/decide') {\n const b = body as {\n sections?: { id: string }[];\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona: scenario.persona ?? 'unknown',\n confidence: scenario.confidence ?? 1,\n };\n // Mirror the real server: the slots key exists ONLY when slots were requested.\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/explain') {\n const b = body as {\n sections?: { id: string }[];\n persona?: string;\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const persona = b.persona ?? scenario.persona ?? 'unknown';\n const confidence = scenario.confidence ?? 1;\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona,\n reasons: [],\n personaAttributes: { persona, confidence: confidenceBandOf(confidence) },\n };\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/weights') {\n const components = Object.entries(scenario.weights ?? {}).map(([componentId, variants]) => ({ componentId, updatedAt: 0, variants }));\n return { status: 200, json: { components } };\n }\n\n return null;\n}\n\n/** Declared baseline of a slot: explicit `baseline`, else first arm / first value per dim. */\nfunction defaultSlotResult(decl: {\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n}): string | Record<string, string> {\n if (decl.arms) {\n return typeof decl.baseline === 'string' ? decl.baseline : decl.arms[0] ?? 'baseline';\n }\n const out: Record<string, string> = {};\n for (const [dim, values] of Object.entries(decl.dims ?? {})) {\n const declared =\n typeof decl.baseline === 'object' && decl.baseline !== null ? decl.baseline[dim] : undefined;\n out[dim] = declared ?? values[0] ?? '';\n }\n return out;\n}\n"],"mappings":"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","names":["http","HttpResponse","captured","recordEvent","confidenceBandOf","c","pathOf","url","_a","e","apiOverride","scenario","route","_b","_c","o","r","resolveScenario","_method","bodyText","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","_q","_r","_s","_t","_u","path","override","body","recordEvent","b","json","s","slots","decl","defaultSlotResult","layoutOrder","persona","confidence","confidenceBandOf","componentId","variants","out","dim","values","declared","scenarioToHandlers","scenario","http","request","bodyText","resolveScenario","HttpResponse"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/handlers.ts","../../src/testing/events.ts","../../src/testing/scenario.ts","../../src/testing/resolve.ts"],"sourcesContent":["import { http, HttpResponse } from 'msw';\nimport type { RequestHandler } from 'msw';\nimport { resolveScenario } from './resolve.js';\nimport type { SentientScenario } from './scenario.js';\n\n/** Turn a scenario into MSW handlers stubbing every SDK endpoint + capturing events. */\nexport function scenarioToHandlers(scenario: SentientScenario = {}): RequestHandler[] {\n return [\n http.all('*/v1/*', async ({ request }) => {\n const bodyText =\n request.method === 'GET' || request.method === 'HEAD' ? null : await request.text();\n const r = await resolveScenario(scenario, request.method, request.url, bodyText);\n if (!r) return undefined; // not a stubbed route — let MSW handle passthrough\n if (r.json === undefined) return new HttpResponse(null, { status: r.status });\n return HttpResponse.json(r.json as object, { status: r.status });\n }),\n ];\n}\n","export type CapturedEvent = {\n eventType: string;\n goalType?: string;\n componentId?: string;\n variantId?: string;\n [k: string]: unknown;\n};\n\nconst captured: CapturedEvent[] = [];\n\nexport function recordEvent(e: CapturedEvent): void {\n captured.push(e);\n}\n\nexport function getSentientEvents(): CapturedEvent[] {\n return [...captured];\n}\n\nexport function clearSentientEvents(): void {\n captured.length = 0;\n}\n\n/** True if any captured event is a goal (component goal_achieved or named goal) with this name. */\nexport function hasFiredGoal(events: CapturedEvent[], goalName: string): boolean {\n return events.some(\n (e) => (e.eventType === 'goal_achieved' || e.eventType === 'goal') && e.goalType === goalName,\n );\n}\n","import { notifyOverridesChanged } from '../override-events.js';\n\nexport type ScenarioWeight = { variantId: string; pulls: number; avgReward: number };\nexport type ScenarioApiOverride =\n | 'error'\n | number\n | { status?: number; body?: unknown; delayMs?: number };\n\nexport type SentientScenario = {\n variants?: Record<string, string>;\n layout?: string[];\n /** Forced persona (canonical PersonaKey). Also sets the persona html attributes. */\n persona?: string;\n /** Persona confidence 0–1; buckets to low/medium/high for the html attribute. Default 1. */\n confidence?: number;\n /** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */\n slots?: Record<string, string | Record<string, string>>;\n /** Forced registry slot config (AdaptiveSlot content/blocks): slot id → entry.\n * Pair with `slots` to name which arm is \"served\" (blocks are keyed by arm). */\n slotConfig?: Record<string, unknown>;\n weights?: Record<string, ScenarioWeight[]>;\n api?: Record<string, ScenarioApiOverride>;\n};\n\ntype ScenarioWindow = {\n __sentient_overrides?: Record<string, string>;\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n __sentient_slot_config_overrides?: Record<string, unknown>;\n __sentient_persona_override?: { persona: string; confidence?: number };\n};\n\n/**\n * Confidence → band. Cutoffs pinned to @sentientui/policy `confidenceBand`\n * (<0.3 low, <0.7 medium, else high). Duplicated (not imported) because the\n * Playwright init function is serialized into the page and cannot close\n * over imports — both copies are pinned by tests.\n */\nexport function confidenceBandOf(c: number): 'low' | 'medium' | 'high' {\n return c < 0.3 ? 'low' : c < 0.7 ? 'medium' : 'high';\n}\n\n/** Apply a scenario by setting the client-forcing globals the SDK reads. */\nexport function applyScenario(scenario: SentientScenario = {}): void {\n const w = window as unknown as ScenarioWindow;\n w.__sentient_overrides = { ...(scenario.variants ?? {}) };\n if (scenario.layout) w.__sentient_layout_override = scenario.layout;\n else delete w.__sentient_layout_override;\n if (scenario.slots) w.__sentient_slot_overrides = { ...scenario.slots };\n else delete w.__sentient_slot_overrides;\n if (scenario.slotConfig) w.__sentient_slot_config_overrides = { ...scenario.slotConfig };\n else delete w.__sentient_slot_config_overrides;\n if (scenario.persona) {\n w.__sentient_persona_override = {\n persona: scenario.persona,\n confidence: scenario.confidence ?? 1,\n };\n try {\n const d = document.documentElement;\n d.setAttribute('data-sentient-persona', scenario.persona);\n d.setAttribute('data-sentient-confidence', confidenceBandOf(scenario.confidence ?? 1));\n } catch {\n /* no DOM (node env) — the override globals still apply */\n }\n } else {\n delete w.__sentient_persona_override;\n }\n // Ring the override bus so hooks already mounted (useAssignment /\n // useSlotResult / useAdaptivePersona subscribe via useSyncExternalStore)\n // re-render immediately — otherwise forcing state mid-test only takes effect\n // on the next unrelated render.\n notifyOverridesChanged();\n}\n\n/** Clear all forced state. */\nexport function resetScenario(): void {\n const w = window as unknown as ScenarioWindow;\n delete w.__sentient_overrides;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete w.__sentient_slot_config_overrides;\n delete w.__sentient_persona_override;\n try {\n document.documentElement.removeAttribute('data-sentient-persona');\n document.documentElement.removeAttribute('data-sentient-confidence');\n } catch {\n /* no DOM */\n }\n notifyOverridesChanged();\n}\n","import { recordEvent, type CapturedEvent } from './events.js';\nimport { confidenceBandOf, type SentientScenario, type ScenarioApiOverride } from './scenario.js';\n\n/** A framework-agnostic resolved response. `json` undefined ⇒ empty body. */\nexport type ResolvedResponse = { status: number; json?: unknown };\n\nfunction pathOf(url: string): string {\n try { return new URL(url).pathname; } catch { return url.split('?')[0] ?? url; }\n}\n\nasync function apiOverride(scenario: SentientScenario, route: string): Promise<ResolvedResponse | null> {\n const o: ScenarioApiOverride | undefined = scenario.api?.[route];\n if (o === undefined) return null;\n if (o === 'error') return { status: 500 };\n if (typeof o === 'number') return { status: o };\n if (o.delayMs) await new Promise((r) => setTimeout(r, o.delayMs));\n return { status: o.status ?? 200, json: o.body ?? {} };\n}\n\n/**\n * Resolve a request against a scenario. Returns a response, or null for routes\n * outside `/v1/*` (let the caller pass through). Shared by the MSW handlers and\n * the Playwright/Cypress adapters so behaviour can't drift.\n */\nexport async function resolveScenario(\n scenario: SentientScenario,\n _method: string,\n url: string,\n bodyText: string | null,\n): Promise<ResolvedResponse | null> {\n const path = pathOf(url);\n if (!path.includes('/v1/')) return null;\n\n const route = '/v1/' + (path.split('/v1/')[1] ?? '');\n const override = await apiOverride(scenario, route);\n if (override) return override;\n\n const body = bodyText ? (JSON.parse(bodyText) as unknown) : {};\n\n if (route === '/v1/sessions') return { status: 204 };\n\n if (route === '/v1/events') {\n for (const e of body as CapturedEvent[]) recordEvent(e);\n return { status: 204 };\n }\n\n if (route === '/v1/goals') {\n recordEvent({ eventType: 'goal', goalType: (body as { name?: string }).name });\n return { status: 204 };\n }\n\n if (route === '/v1/assign') {\n const b = body as { componentId: string; variantIds?: string[] };\n const variantId = scenario.variants?.[b.componentId] ?? b.variantIds?.[0] ?? 'control';\n return { status: 200, json: { variantId, assignmentTtlMs: 60_000 } };\n }\n\n if (route === '/v1/decide') {\n const b = body as {\n sections?: { id: string }[];\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona: scenario.persona ?? 'unknown',\n confidence: scenario.confidence ?? 1,\n };\n // Mirror the real server: the slots key exists ONLY when slots were requested.\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/explain') {\n const b = body as {\n sections?: { id: string }[];\n persona?: string;\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const persona = b.persona ?? scenario.persona ?? 'unknown';\n const confidence = scenario.confidence ?? 1;\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona,\n reasons: [],\n personaAttributes: { persona, confidence: confidenceBandOf(confidence) },\n };\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/weights') {\n const components = Object.entries(scenario.weights ?? {}).map(([componentId, variants]) => ({ componentId, updatedAt: 0, variants }));\n return { status: 200, json: { components } };\n }\n\n return null;\n}\n\n/** Declared baseline of a slot: explicit `baseline`, else first arm / first value per dim. */\nfunction defaultSlotResult(decl: {\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n}): string | Record<string, string> {\n if (decl.arms) {\n return typeof decl.baseline === 'string' ? decl.baseline : decl.arms[0] ?? 'baseline';\n }\n const out: Record<string, string> = {};\n for (const [dim, values] of Object.entries(decl.dims ?? {})) {\n const declared =\n typeof decl.baseline === 'object' && decl.baseline !== null ? decl.baseline[dim] : undefined;\n out[dim] = declared ?? values[0] ?? '';\n }\n return out;\n}\n"],"mappings":"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","names":["http","HttpResponse","captured","recordEvent","confidenceBandOf","c","pathOf","url","_a","e","apiOverride","scenario","route","_b","_c","o","r","resolveScenario","_method","bodyText","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","_q","_r","_s","_t","_u","path","override","body","recordEvent","b","json","s","slots","decl","defaultSlotResult","layoutOrder","persona","confidence","confidenceBandOf","componentId","variants","out","dim","values","declared","scenarioToHandlers","scenario","http","request","bodyText","resolveScenario","HttpResponse"]}
|
package/dist/testing/node.d.cts
CHANGED
|
@@ -19,6 +19,9 @@ type SentientScenario = {
|
|
|
19
19
|
confidence?: number;
|
|
20
20
|
/** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */
|
|
21
21
|
slots?: Record<string, string | Record<string, string>>;
|
|
22
|
+
/** Forced registry slot config (AdaptiveSlot content/blocks): slot id → entry.
|
|
23
|
+
* Pair with `slots` to name which arm is "served" (blocks are keyed by arm). */
|
|
24
|
+
slotConfig?: Record<string, unknown>;
|
|
22
25
|
weights?: Record<string, ScenarioWeight[]>;
|
|
23
26
|
api?: Record<string, ScenarioApiOverride>;
|
|
24
27
|
};
|
package/dist/testing/node.d.ts
CHANGED
|
@@ -19,6 +19,9 @@ type SentientScenario = {
|
|
|
19
19
|
confidence?: number;
|
|
20
20
|
/** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */
|
|
21
21
|
slots?: Record<string, string | Record<string, string>>;
|
|
22
|
+
/** Forced registry slot config (AdaptiveSlot content/blocks): slot id → entry.
|
|
23
|
+
* Pair with `slots` to name which arm is "served" (blocks are keyed by arm). */
|
|
24
|
+
slotConfig?: Record<string, unknown>;
|
|
22
25
|
weights?: Record<string, ScenarioWeight[]>;
|
|
23
26
|
api?: Record<string, ScenarioApiOverride>;
|
|
24
27
|
};
|
package/dist/testing/node.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var m=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var V=(e,t)=>{for(var n in t)m(e,n,{get:t[n],enumerable:!0})},X=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of K(t))!Q.call(e,o)&&o!==n&&m(e,o,{get:()=>t[o],enumerable:!(r=z(t,o))||r.enumerable});return e};var Y=e=>X(m({},"__esModule",{value:!0}),e);var ee={};V(ee,{setupSentientServer:()=>N});module.exports=Y(ee);var L=require("msw/node");var g=require("msw");var Z=[];function _(e){Z.push(e)}function D(e){return e<.3?"low":e<.7?"medium":"high"}function $(e){var t;try{return new URL(e).pathname}catch(n){return(t=e.split("?")[0])!=null?t:e}}async function q(e,t){var r,o,i;let n=(r=e.api)==null?void 0:r[t];return n===void 0?null:n==="error"?{status:500}:typeof n=="number"?{status:n}:(n.delayMs&&await new Promise(u=>setTimeout(u,n.delayMs)),{status:(o=n.status)!=null?o:200,json:(i=n.body)!=null?i:{}})}async function J(e,t,n,r){var S,b,
|
|
1
|
+
"use strict";var m=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var V=(e,t)=>{for(var n in t)m(e,n,{get:t[n],enumerable:!0})},X=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of K(t))!Q.call(e,o)&&o!==n&&m(e,o,{get:()=>t[o],enumerable:!(r=z(t,o))||r.enumerable});return e};var Y=e=>X(m({},"__esModule",{value:!0}),e);var ee={};V(ee,{setupSentientServer:()=>N});module.exports=Y(ee);var L=require("msw/node");var g=require("msw");var Z=[];function _(e){Z.push(e)}function D(e){return e<.3?"low":e<.7?"medium":"high"}function $(e){var t;try{return new URL(e).pathname}catch(n){return(t=e.split("?")[0])!=null?t:e}}async function q(e,t){var r,o,i;let n=(r=e.api)==null?void 0:r[t];return n===void 0?null:n==="error"?{status:500}:typeof n=="number"?{status:n}:(n.delayMs&&await new Promise(u=>setTimeout(u,n.delayMs)),{status:(o=n.status)!=null?o:200,json:(i=n.body)!=null?i:{}})}async function J(e,t,n,r){var S,w,b,R,h,x,E,j,k,O,A,T,C,I,H,W,M,P,B,G,U;let o=$(n);if(!o.includes("/v1/"))return null;let i="/v1/"+((S=o.split("/v1/")[1])!=null?S:""),u=await q(e,i);if(u)return u;let a=r?JSON.parse(r):{};if(i==="/v1/sessions")return{status:204};if(i==="/v1/events"){for(let s of a)_(s);return{status:204}}if(i==="/v1/goals")return _({eventType:"goal",goalType:a.name}),{status:204};if(i==="/v1/assign"){let s=a;return{status:200,json:{variantId:(h=(R=(w=e.variants)==null?void 0:w[s.componentId])!=null?R:(b=s.variantIds)==null?void 0:b[0])!=null?h:"control",assignmentTtlMs:6e4}}}if(i==="/v1/decide"){let s=a,d={layoutOrder:(E=e.layout)!=null?E:((x=s.sections)!=null?x:[]).map(c=>c.id),assignments:(j=e.variants)!=null?j:{},persona:(k=e.persona)!=null?k:"unknown",confidence:(O=e.confidence)!=null?O:1};if(s.slots&&s.slots.length>0){let c={};for(let l of s.slots)c[l.id]=(T=(A=e.slots)==null?void 0:A[l.id])!=null?T:F(l);d.slots=c}return{status:200,json:d}}if(i==="/v1/explain"){let s=a,p=(I=e.layout)!=null?I:((C=s.sections)!=null?C:[]).map(f=>f.id),d=(W=(H=s.persona)!=null?H:e.persona)!=null?W:"unknown",c=(M=e.confidence)!=null?M:1,l={layoutOrder:p,assignments:(P=e.variants)!=null?P:{},persona:d,reasons:[],personaAttributes:{persona:d,confidence:D(c)}};if(s.slots&&s.slots.length>0){let f={};for(let v of s.slots)f[v.id]=(G=(B=e.slots)==null?void 0:B[v.id])!=null?G:F(v);l.slots=f}return{status:200,json:l}}return i==="/v1/weights"?{status:200,json:{components:Object.entries((U=e.weights)!=null?U:{}).map(([p,d])=>({componentId:p,updatedAt:0,variants:d}))}}:null}function F(e){var n,r,o;if(e.arms)return typeof e.baseline=="string"?e.baseline:(n=e.arms[0])!=null?n:"baseline";let t={};for(let[i,u]of Object.entries((r=e.dims)!=null?r:{})){let a=typeof e.baseline=="object"&&e.baseline!==null?e.baseline[i]:void 0;t[i]=(o=a!=null?a:u[0])!=null?o:""}return t}function y(e={}){return[g.http.all("*/v1/*",async({request:t})=>{let n=t.method==="GET"||t.method==="HEAD"?null:await t.text(),r=await J(e,t.method,t.url,n);if(r)return r.json===void 0?new g.HttpResponse(null,{status:r.status}):g.HttpResponse.json(r.json,{status:r.status})})]}function N(){let e=(0,L.setupServer)(...y());return e.listen({onUnhandledRequest:"bypass"}),{server:e,use:(t={})=>e.use(...y(t))}}0&&(module.exports={setupSentientServer});
|
|
2
2
|
//# sourceMappingURL=node.js.map
|
package/dist/testing/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/node.ts","../../src/testing/server.ts","../../src/testing/handlers.ts","../../src/testing/events.ts","../../src/testing/scenario.ts","../../src/testing/resolve.ts"],"sourcesContent":["// Node-only testing helpers. Kept out of the main `@sentientui/react/testing`\n// entry so that entry stays browser-bundle-safe (Cypress/webpack) — `msw/node`\n// pulls Node built-ins that break a browser bundle.\nexport { setupSentientServer } from './server.js';\n","import { setupServer, type SetupServer } from 'msw/node';\nimport { scenarioToHandlers } from './handlers.js';\nimport type { SentientScenario } from './scenario.js';\n\n/**\n * Create and start an msw/node server pre-loaded with the default (control)\n * scenario. Call `use(scenario)` to swap in a scenario for the current test.\n * Remember to `server.close()` in afterAll and `server.resetHandlers()` between tests.\n */\nexport function setupSentientServer(): {\n server: SetupServer;\n use: (scenario?: SentientScenario) => void;\n} {\n const server = setupServer(...scenarioToHandlers());\n server.listen({ onUnhandledRequest: 'bypass' });\n return {\n server,\n use: (scenario: SentientScenario = {}) => server.use(...scenarioToHandlers(scenario)),\n };\n}\n","import { http, HttpResponse } from 'msw';\nimport type { RequestHandler } from 'msw';\nimport { resolveScenario } from './resolve.js';\nimport type { SentientScenario } from './scenario.js';\n\n/** Turn a scenario into MSW handlers stubbing every SDK endpoint + capturing events. */\nexport function scenarioToHandlers(scenario: SentientScenario = {}): RequestHandler[] {\n return [\n http.all('*/v1/*', async ({ request }) => {\n const bodyText =\n request.method === 'GET' || request.method === 'HEAD' ? null : await request.text();\n const r = await resolveScenario(scenario, request.method, request.url, bodyText);\n if (!r) return undefined; // not a stubbed route — let MSW handle passthrough\n if (r.json === undefined) return new HttpResponse(null, { status: r.status });\n return HttpResponse.json(r.json as object, { status: r.status });\n }),\n ];\n}\n","export type CapturedEvent = {\n eventType: string;\n goalType?: string;\n componentId?: string;\n variantId?: string;\n [k: string]: unknown;\n};\n\nconst captured: CapturedEvent[] = [];\n\nexport function recordEvent(e: CapturedEvent): void {\n captured.push(e);\n}\n\nexport function getSentientEvents(): CapturedEvent[] {\n return [...captured];\n}\n\nexport function clearSentientEvents(): void {\n captured.length = 0;\n}\n\n/** True if any captured event is a goal (component goal_achieved or named goal) with this name. */\nexport function hasFiredGoal(events: CapturedEvent[], goalName: string): boolean {\n return events.some(\n (e) => (e.eventType === 'goal_achieved' || e.eventType === 'goal') && e.goalType === goalName,\n );\n}\n","import { notifyOverridesChanged } from '../override-events.js';\n\nexport type ScenarioWeight = { variantId: string; pulls: number; avgReward: number };\nexport type ScenarioApiOverride =\n | 'error'\n | number\n | { status?: number; body?: unknown; delayMs?: number };\n\nexport type SentientScenario = {\n variants?: Record<string, string>;\n layout?: string[];\n /** Forced persona (canonical PersonaKey). Also sets the persona html attributes. */\n persona?: string;\n /** Persona confidence 0–1; buckets to low/medium/high for the html attribute. Default 1. */\n confidence?: number;\n /** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */\n slots?: Record<string, string | Record<string, string>>;\n weights?: Record<string, ScenarioWeight[]>;\n api?: Record<string, ScenarioApiOverride>;\n};\n\ntype ScenarioWindow = {\n __sentient_overrides?: Record<string, string>;\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n __sentient_persona_override?: { persona: string; confidence?: number };\n};\n\n/**\n * Confidence → band. Cutoffs pinned to @sentientui/policy `confidenceBand`\n * (<0.3 low, <0.7 medium, else high). Duplicated (not imported) because the\n * Playwright init function is serialized into the page and cannot close\n * over imports — both copies are pinned by tests.\n */\nexport function confidenceBandOf(c: number): 'low' | 'medium' | 'high' {\n return c < 0.3 ? 'low' : c < 0.7 ? 'medium' : 'high';\n}\n\n/** Apply a scenario by setting the client-forcing globals the SDK reads. */\nexport function applyScenario(scenario: SentientScenario = {}): void {\n const w = window as unknown as ScenarioWindow;\n w.__sentient_overrides = { ...(scenario.variants ?? {}) };\n if (scenario.layout) w.__sentient_layout_override = scenario.layout;\n else delete w.__sentient_layout_override;\n if (scenario.slots) w.__sentient_slot_overrides = { ...scenario.slots };\n else delete w.__sentient_slot_overrides;\n if (scenario.persona) {\n w.__sentient_persona_override = {\n persona: scenario.persona,\n confidence: scenario.confidence ?? 1,\n };\n try {\n const d = document.documentElement;\n d.setAttribute('data-sentient-persona', scenario.persona);\n d.setAttribute('data-sentient-confidence', confidenceBandOf(scenario.confidence ?? 1));\n } catch {\n /* no DOM (node env) — the override globals still apply */\n }\n } else {\n delete w.__sentient_persona_override;\n }\n // Ring the override bus so hooks already mounted (useAssignment /\n // useSlotResult / useAdaptivePersona subscribe via useSyncExternalStore)\n // re-render immediately — otherwise forcing state mid-test only takes effect\n // on the next unrelated render.\n notifyOverridesChanged();\n}\n\n/** Clear all forced state. */\nexport function resetScenario(): void {\n const w = window as unknown as ScenarioWindow;\n delete w.__sentient_overrides;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete w.__sentient_persona_override;\n try {\n document.documentElement.removeAttribute('data-sentient-persona');\n document.documentElement.removeAttribute('data-sentient-confidence');\n } catch {\n /* no DOM */\n }\n notifyOverridesChanged();\n}\n","import { recordEvent, type CapturedEvent } from './events.js';\nimport { confidenceBandOf, type SentientScenario, type ScenarioApiOverride } from './scenario.js';\n\n/** A framework-agnostic resolved response. `json` undefined ⇒ empty body. */\nexport type ResolvedResponse = { status: number; json?: unknown };\n\nfunction pathOf(url: string): string {\n try { return new URL(url).pathname; } catch { return url.split('?')[0] ?? url; }\n}\n\nasync function apiOverride(scenario: SentientScenario, route: string): Promise<ResolvedResponse | null> {\n const o: ScenarioApiOverride | undefined = scenario.api?.[route];\n if (o === undefined) return null;\n if (o === 'error') return { status: 500 };\n if (typeof o === 'number') return { status: o };\n if (o.delayMs) await new Promise((r) => setTimeout(r, o.delayMs));\n return { status: o.status ?? 200, json: o.body ?? {} };\n}\n\n/**\n * Resolve a request against a scenario. Returns a response, or null for routes\n * outside `/v1/*` (let the caller pass through). Shared by the MSW handlers and\n * the Playwright/Cypress adapters so behaviour can't drift.\n */\nexport async function resolveScenario(\n scenario: SentientScenario,\n _method: string,\n url: string,\n bodyText: string | null,\n): Promise<ResolvedResponse | null> {\n const path = pathOf(url);\n if (!path.includes('/v1/')) return null;\n\n const route = '/v1/' + (path.split('/v1/')[1] ?? '');\n const override = await apiOverride(scenario, route);\n if (override) return override;\n\n const body = bodyText ? (JSON.parse(bodyText) as unknown) : {};\n\n if (route === '/v1/sessions') return { status: 204 };\n\n if (route === '/v1/events') {\n for (const e of body as CapturedEvent[]) recordEvent(e);\n return { status: 204 };\n }\n\n if (route === '/v1/goals') {\n recordEvent({ eventType: 'goal', goalType: (body as { name?: string }).name });\n return { status: 204 };\n }\n\n if (route === '/v1/assign') {\n const b = body as { componentId: string; variantIds?: string[] };\n const variantId = scenario.variants?.[b.componentId] ?? b.variantIds?.[0] ?? 'control';\n return { status: 200, json: { variantId, assignmentTtlMs: 60_000 } };\n }\n\n if (route === '/v1/decide') {\n const b = body as {\n sections?: { id: string }[];\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona: scenario.persona ?? 'unknown',\n confidence: scenario.confidence ?? 1,\n };\n // Mirror the real server: the slots key exists ONLY when slots were requested.\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/explain') {\n const b = body as {\n sections?: { id: string }[];\n persona?: string;\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const persona = b.persona ?? scenario.persona ?? 'unknown';\n const confidence = scenario.confidence ?? 1;\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona,\n reasons: [],\n personaAttributes: { persona, confidence: confidenceBandOf(confidence) },\n };\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/weights') {\n const components = Object.entries(scenario.weights ?? {}).map(([componentId, variants]) => ({ componentId, updatedAt: 0, variants }));\n return { status: 200, json: { components } };\n }\n\n return null;\n}\n\n/** Declared baseline of a slot: explicit `baseline`, else first arm / first value per dim. */\nfunction defaultSlotResult(decl: {\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n}): string | Record<string, string> {\n if (decl.arms) {\n return typeof decl.baseline === 'string' ? decl.baseline : decl.arms[0] ?? 'baseline';\n }\n const out: Record<string, string> = {};\n for (const [dim, values] of Object.entries(decl.dims ?? {})) {\n const declared =\n typeof decl.baseline === 'object' && decl.baseline !== null ? decl.baseline[dim] : undefined;\n out[dim] = declared ?? values[0] ?? '';\n }\n return out;\n}\n"],"mappings":"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","names":["node_exports","__export","setupSentientServer","__toCommonJS","import_node","import_msw","captured","recordEvent","confidenceBandOf","c","pathOf","url","_a","e","apiOverride","scenario","route","_b","_c","o","r","resolveScenario","_method","bodyText","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","_q","_r","_s","_t","_u","path","override","body","recordEvent","b","json","s","slots","decl","defaultSlotResult","layoutOrder","persona","confidence","confidenceBandOf","componentId","variants","out","dim","values","declared","scenarioToHandlers","scenario","request","bodyText","resolveScenario","setupSentientServer","server","scenarioToHandlers","scenario"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/node.ts","../../src/testing/server.ts","../../src/testing/handlers.ts","../../src/testing/events.ts","../../src/testing/scenario.ts","../../src/testing/resolve.ts"],"sourcesContent":["// Node-only testing helpers. Kept out of the main `@sentientui/react/testing`\n// entry so that entry stays browser-bundle-safe (Cypress/webpack) — `msw/node`\n// pulls Node built-ins that break a browser bundle.\nexport { setupSentientServer } from './server.js';\n","import { setupServer, type SetupServer } from 'msw/node';\nimport { scenarioToHandlers } from './handlers.js';\nimport type { SentientScenario } from './scenario.js';\n\n/**\n * Create and start an msw/node server pre-loaded with the default (control)\n * scenario. Call `use(scenario)` to swap in a scenario for the current test.\n * Remember to `server.close()` in afterAll and `server.resetHandlers()` between tests.\n */\nexport function setupSentientServer(): {\n server: SetupServer;\n use: (scenario?: SentientScenario) => void;\n} {\n const server = setupServer(...scenarioToHandlers());\n server.listen({ onUnhandledRequest: 'bypass' });\n return {\n server,\n use: (scenario: SentientScenario = {}) => server.use(...scenarioToHandlers(scenario)),\n };\n}\n","import { http, HttpResponse } from 'msw';\nimport type { RequestHandler } from 'msw';\nimport { resolveScenario } from './resolve.js';\nimport type { SentientScenario } from './scenario.js';\n\n/** Turn a scenario into MSW handlers stubbing every SDK endpoint + capturing events. */\nexport function scenarioToHandlers(scenario: SentientScenario = {}): RequestHandler[] {\n return [\n http.all('*/v1/*', async ({ request }) => {\n const bodyText =\n request.method === 'GET' || request.method === 'HEAD' ? null : await request.text();\n const r = await resolveScenario(scenario, request.method, request.url, bodyText);\n if (!r) return undefined; // not a stubbed route — let MSW handle passthrough\n if (r.json === undefined) return new HttpResponse(null, { status: r.status });\n return HttpResponse.json(r.json as object, { status: r.status });\n }),\n ];\n}\n","export type CapturedEvent = {\n eventType: string;\n goalType?: string;\n componentId?: string;\n variantId?: string;\n [k: string]: unknown;\n};\n\nconst captured: CapturedEvent[] = [];\n\nexport function recordEvent(e: CapturedEvent): void {\n captured.push(e);\n}\n\nexport function getSentientEvents(): CapturedEvent[] {\n return [...captured];\n}\n\nexport function clearSentientEvents(): void {\n captured.length = 0;\n}\n\n/** True if any captured event is a goal (component goal_achieved or named goal) with this name. */\nexport function hasFiredGoal(events: CapturedEvent[], goalName: string): boolean {\n return events.some(\n (e) => (e.eventType === 'goal_achieved' || e.eventType === 'goal') && e.goalType === goalName,\n );\n}\n","import { notifyOverridesChanged } from '../override-events.js';\n\nexport type ScenarioWeight = { variantId: string; pulls: number; avgReward: number };\nexport type ScenarioApiOverride =\n | 'error'\n | number\n | { status?: number; body?: unknown; delayMs?: number };\n\nexport type SentientScenario = {\n variants?: Record<string, string>;\n layout?: string[];\n /** Forced persona (canonical PersonaKey). Also sets the persona html attributes. */\n persona?: string;\n /** Persona confidence 0–1; buckets to low/medium/high for the html attribute. Default 1. */\n confidence?: number;\n /** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */\n slots?: Record<string, string | Record<string, string>>;\n /** Forced registry slot config (AdaptiveSlot content/blocks): slot id → entry.\n * Pair with `slots` to name which arm is \"served\" (blocks are keyed by arm). */\n slotConfig?: Record<string, unknown>;\n weights?: Record<string, ScenarioWeight[]>;\n api?: Record<string, ScenarioApiOverride>;\n};\n\ntype ScenarioWindow = {\n __sentient_overrides?: Record<string, string>;\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n __sentient_slot_config_overrides?: Record<string, unknown>;\n __sentient_persona_override?: { persona: string; confidence?: number };\n};\n\n/**\n * Confidence → band. Cutoffs pinned to @sentientui/policy `confidenceBand`\n * (<0.3 low, <0.7 medium, else high). Duplicated (not imported) because the\n * Playwright init function is serialized into the page and cannot close\n * over imports — both copies are pinned by tests.\n */\nexport function confidenceBandOf(c: number): 'low' | 'medium' | 'high' {\n return c < 0.3 ? 'low' : c < 0.7 ? 'medium' : 'high';\n}\n\n/** Apply a scenario by setting the client-forcing globals the SDK reads. */\nexport function applyScenario(scenario: SentientScenario = {}): void {\n const w = window as unknown as ScenarioWindow;\n w.__sentient_overrides = { ...(scenario.variants ?? {}) };\n if (scenario.layout) w.__sentient_layout_override = scenario.layout;\n else delete w.__sentient_layout_override;\n if (scenario.slots) w.__sentient_slot_overrides = { ...scenario.slots };\n else delete w.__sentient_slot_overrides;\n if (scenario.slotConfig) w.__sentient_slot_config_overrides = { ...scenario.slotConfig };\n else delete w.__sentient_slot_config_overrides;\n if (scenario.persona) {\n w.__sentient_persona_override = {\n persona: scenario.persona,\n confidence: scenario.confidence ?? 1,\n };\n try {\n const d = document.documentElement;\n d.setAttribute('data-sentient-persona', scenario.persona);\n d.setAttribute('data-sentient-confidence', confidenceBandOf(scenario.confidence ?? 1));\n } catch {\n /* no DOM (node env) — the override globals still apply */\n }\n } else {\n delete w.__sentient_persona_override;\n }\n // Ring the override bus so hooks already mounted (useAssignment /\n // useSlotResult / useAdaptivePersona subscribe via useSyncExternalStore)\n // re-render immediately — otherwise forcing state mid-test only takes effect\n // on the next unrelated render.\n notifyOverridesChanged();\n}\n\n/** Clear all forced state. */\nexport function resetScenario(): void {\n const w = window as unknown as ScenarioWindow;\n delete w.__sentient_overrides;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete w.__sentient_slot_config_overrides;\n delete w.__sentient_persona_override;\n try {\n document.documentElement.removeAttribute('data-sentient-persona');\n document.documentElement.removeAttribute('data-sentient-confidence');\n } catch {\n /* no DOM */\n }\n notifyOverridesChanged();\n}\n","import { recordEvent, type CapturedEvent } from './events.js';\nimport { confidenceBandOf, type SentientScenario, type ScenarioApiOverride } from './scenario.js';\n\n/** A framework-agnostic resolved response. `json` undefined ⇒ empty body. */\nexport type ResolvedResponse = { status: number; json?: unknown };\n\nfunction pathOf(url: string): string {\n try { return new URL(url).pathname; } catch { return url.split('?')[0] ?? url; }\n}\n\nasync function apiOverride(scenario: SentientScenario, route: string): Promise<ResolvedResponse | null> {\n const o: ScenarioApiOverride | undefined = scenario.api?.[route];\n if (o === undefined) return null;\n if (o === 'error') return { status: 500 };\n if (typeof o === 'number') return { status: o };\n if (o.delayMs) await new Promise((r) => setTimeout(r, o.delayMs));\n return { status: o.status ?? 200, json: o.body ?? {} };\n}\n\n/**\n * Resolve a request against a scenario. Returns a response, or null for routes\n * outside `/v1/*` (let the caller pass through). Shared by the MSW handlers and\n * the Playwright/Cypress adapters so behaviour can't drift.\n */\nexport async function resolveScenario(\n scenario: SentientScenario,\n _method: string,\n url: string,\n bodyText: string | null,\n): Promise<ResolvedResponse | null> {\n const path = pathOf(url);\n if (!path.includes('/v1/')) return null;\n\n const route = '/v1/' + (path.split('/v1/')[1] ?? '');\n const override = await apiOverride(scenario, route);\n if (override) return override;\n\n const body = bodyText ? (JSON.parse(bodyText) as unknown) : {};\n\n if (route === '/v1/sessions') return { status: 204 };\n\n if (route === '/v1/events') {\n for (const e of body as CapturedEvent[]) recordEvent(e);\n return { status: 204 };\n }\n\n if (route === '/v1/goals') {\n recordEvent({ eventType: 'goal', goalType: (body as { name?: string }).name });\n return { status: 204 };\n }\n\n if (route === '/v1/assign') {\n const b = body as { componentId: string; variantIds?: string[] };\n const variantId = scenario.variants?.[b.componentId] ?? b.variantIds?.[0] ?? 'control';\n return { status: 200, json: { variantId, assignmentTtlMs: 60_000 } };\n }\n\n if (route === '/v1/decide') {\n const b = body as {\n sections?: { id: string }[];\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona: scenario.persona ?? 'unknown',\n confidence: scenario.confidence ?? 1,\n };\n // Mirror the real server: the slots key exists ONLY when slots were requested.\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/explain') {\n const b = body as {\n sections?: { id: string }[];\n persona?: string;\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const persona = b.persona ?? scenario.persona ?? 'unknown';\n const confidence = scenario.confidence ?? 1;\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona,\n reasons: [],\n personaAttributes: { persona, confidence: confidenceBandOf(confidence) },\n };\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/weights') {\n const components = Object.entries(scenario.weights ?? {}).map(([componentId, variants]) => ({ componentId, updatedAt: 0, variants }));\n return { status: 200, json: { components } };\n }\n\n return null;\n}\n\n/** Declared baseline of a slot: explicit `baseline`, else first arm / first value per dim. */\nfunction defaultSlotResult(decl: {\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n}): string | Record<string, string> {\n if (decl.arms) {\n return typeof decl.baseline === 'string' ? decl.baseline : decl.arms[0] ?? 'baseline';\n }\n const out: Record<string, string> = {};\n for (const [dim, values] of Object.entries(decl.dims ?? {})) {\n const declared =\n typeof decl.baseline === 'object' && decl.baseline !== null ? decl.baseline[dim] : undefined;\n out[dim] = declared ?? values[0] ?? '';\n }\n return out;\n}\n"],"mappings":"yaAAA,IAAAA,GAAA,GAAAC,EAAAD,GAAA,yBAAAE,IAAA,eAAAC,EAAAH,ICAA,IAAAI,EAA8C,oBCA9C,IAAAC,EAAmC,eCQnC,IAAMC,EAA4B,CAAC,EAE5B,SAASC,EAAY,EAAwB,CAClDD,EAAS,KAAK,CAAC,CACjB,CC0BO,SAASE,EAAiBC,EAAsC,CACrE,OAAOA,EAAI,GAAM,MAAQA,EAAI,GAAM,SAAW,MAChD,CClCA,SAASC,EAAOC,EAAqB,CANrC,IAAAC,EAOE,GAAI,CAAE,OAAO,IAAI,IAAID,CAAG,EAAE,QAAU,OAAQE,EAAA,CAAE,OAAOD,EAAAD,EAAI,MAAM,GAAG,EAAE,CAAC,IAAhB,KAAAC,EAAqBD,CAAK,CACjF,CAEA,eAAeG,EAAYC,EAA4BC,EAAiD,CAVxG,IAAAJ,EAAAK,EAAAC,EAWE,IAAMC,GAAqCP,EAAAG,EAAS,MAAT,YAAAH,EAAeI,GAC1D,OAAIG,IAAM,OAAkB,KACxBA,IAAM,QAAgB,CAAE,OAAQ,GAAI,EACpC,OAAOA,GAAM,SAAiB,CAAE,OAAQA,CAAE,GAC1CA,EAAE,SAAS,MAAM,IAAI,QAASC,GAAM,WAAWA,EAAGD,EAAE,OAAO,CAAC,EACzD,CAAE,QAAQF,EAAAE,EAAE,SAAF,KAAAF,EAAY,IAAK,MAAMC,EAAAC,EAAE,OAAF,KAAAD,EAAU,CAAC,CAAE,EACvD,CAOA,eAAsBG,EACpBN,EACAO,EACAX,EACAY,EACkC,CA7BpC,IAAAX,EAAAK,EAAAC,EAAAM,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EA8BE,IAAMC,EAAOhC,EAAOC,CAAG,EACvB,GAAI,CAAC+B,EAAK,SAAS,MAAM,EAAG,OAAO,KAEnC,IAAM1B,EAAQ,SAAUJ,EAAA8B,EAAK,MAAM,MAAM,EAAE,CAAC,IAApB,KAAA9B,EAAyB,IAC3C+B,EAAW,MAAM7B,EAAYC,EAAUC,CAAK,EAClD,GAAI2B,EAAU,OAAOA,EAErB,IAAMC,EAAOrB,EAAY,KAAK,MAAMA,CAAQ,EAAgB,CAAC,EAE7D,GAAIP,IAAU,eAAgB,MAAO,CAAE,OAAQ,GAAI,EAEnD,GAAIA,IAAU,aAAc,CAC1B,QAAWH,KAAK+B,EAAyBC,EAAYhC,CAAC,EACtD,MAAO,CAAE,OAAQ,GAAI,CACvB,CAEA,GAAIG,IAAU,YACZ,OAAA6B,EAAY,CAAE,UAAW,OAAQ,SAAWD,EAA2B,IAAK,CAAC,EACtE,CAAE,OAAQ,GAAI,EAGvB,GAAI5B,IAAU,aAAc,CAC1B,IAAM8B,EAAIF,EAEV,MAAO,CAAE,OAAQ,IAAK,KAAM,CAAE,WADZnB,GAAAD,GAAAP,EAAAF,EAAS,WAAT,YAAAE,EAAoB6B,EAAE,eAAtB,KAAAtB,GAAsCN,EAAA4B,EAAE,aAAF,YAAA5B,EAAe,KAArD,KAAAO,EAA2D,UACpC,gBAAiB,GAAO,CAAE,CACrE,CAEA,GAAIT,IAAU,aAAc,CAC1B,IAAM8B,EAAIF,EAUJG,EAAgC,CACpC,aAFkBpB,EAAAZ,EAAS,SAAT,KAAAY,IAAoBD,EAAAoB,EAAE,WAAF,KAAApB,EAAc,CAAC,GAAG,IAAKsB,GAAMA,EAAE,EAAE,EAGvE,aAAapB,EAAAb,EAAS,WAAT,KAAAa,EAAqB,CAAC,EACnC,SAASC,EAAAd,EAAS,UAAT,KAAAc,EAAoB,UAC7B,YAAYC,EAAAf,EAAS,aAAT,KAAAe,EAAuB,CACrC,EAEA,GAAIgB,EAAE,OAASA,EAAE,MAAM,OAAS,EAAG,CACjC,IAAMG,EAAiC,CAAC,EACxC,QAAWC,KAAQJ,EAAE,MACnBG,EAAMC,EAAK,EAAE,GAAIlB,GAAAD,EAAAhB,EAAS,QAAT,YAAAgB,EAAiBmB,EAAK,MAAtB,KAAAlB,EAA6BmB,EAAkBD,CAAI,EAEtEH,EAAK,MAAQE,CACf,CACA,MAAO,CAAE,OAAQ,IAAK,KAAAF,CAAK,CAC7B,CAEA,GAAI/B,IAAU,cAAe,CAC3B,IAAM8B,EAAIF,EAUJQ,GAAclB,EAAAnB,EAAS,SAAT,KAAAmB,IAAoBD,EAAAa,EAAE,WAAF,KAAAb,EAAc,CAAC,GAAG,IAAKe,GAAMA,EAAE,EAAE,EACnEK,GAAUjB,GAAAD,EAAAW,EAAE,UAAF,KAAAX,EAAapB,EAAS,UAAtB,KAAAqB,EAAiC,UAC3CkB,GAAajB,EAAAtB,EAAS,aAAT,KAAAsB,EAAuB,EACpCU,EAAgC,CACpC,YAAAK,EACA,aAAad,EAAAvB,EAAS,WAAT,KAAAuB,EAAqB,CAAC,EACnC,QAAAe,EACA,QAAS,CAAC,EACV,kBAAmB,CAAE,QAAAA,EAAS,WAAYE,EAAiBD,CAAU,CAAE,CACzE,EACA,GAAIR,EAAE,OAASA,EAAE,MAAM,OAAS,EAAG,CACjC,IAAMG,EAAiC,CAAC,EACxC,QAAWC,KAAQJ,EAAE,MACnBG,EAAMC,EAAK,EAAE,GAAIV,GAAAD,EAAAxB,EAAS,QAAT,YAAAwB,EAAiBW,EAAK,MAAtB,KAAAV,EAA6BW,EAAkBD,CAAI,EAEtEH,EAAK,MAAQE,CACf,CACA,MAAO,CAAE,OAAQ,IAAK,KAAAF,CAAK,CAC7B,CAEA,OAAI/B,IAAU,cAEL,CAAE,OAAQ,IAAK,KAAM,CAAE,WADX,OAAO,SAAQyB,EAAA1B,EAAS,UAAT,KAAA0B,EAAoB,CAAC,CAAC,EAAE,IAAI,CAAC,CAACe,EAAaC,CAAQ,KAAO,CAAE,YAAAD,EAAa,UAAW,EAAG,SAAAC,CAAS,EAAE,CAC3F,CAAE,EAGtC,IACT,CAGA,SAASN,EAAkBD,EAIS,CAjIpC,IAAAtC,EAAAK,EAAAC,EAkIE,GAAIgC,EAAK,KACP,OAAO,OAAOA,EAAK,UAAa,SAAWA,EAAK,UAAWtC,EAAAsC,EAAK,KAAK,CAAC,IAAX,KAAAtC,EAAgB,WAE7E,IAAM8C,EAA8B,CAAC,EACrC,OAAW,CAACC,EAAKC,CAAM,IAAK,OAAO,SAAQ3C,EAAAiC,EAAK,OAAL,KAAAjC,EAAa,CAAC,CAAC,EAAG,CAC3D,IAAM4C,EACJ,OAAOX,EAAK,UAAa,UAAYA,EAAK,WAAa,KAAOA,EAAK,SAASS,CAAG,EAAI,OACrFD,EAAIC,CAAG,GAAIzC,EAAA2C,GAAA,KAAAA,EAAYD,EAAO,CAAC,IAApB,KAAA1C,EAAyB,EACtC,CACA,OAAOwC,CACT,CHtIO,SAASI,EAAmBC,EAA6B,CAAC,EAAqB,CACpF,MAAO,CACL,OAAK,IAAI,SAAU,MAAO,CAAE,QAAAC,CAAQ,IAAM,CACxC,IAAMC,EACJD,EAAQ,SAAW,OAASA,EAAQ,SAAW,OAAS,KAAO,MAAMA,EAAQ,KAAK,EAC9E,EAAI,MAAME,EAAgBH,EAAUC,EAAQ,OAAQA,EAAQ,IAAKC,CAAQ,EAC/E,GAAK,EACL,OAAI,EAAE,OAAS,OAAkB,IAAI,eAAa,KAAM,CAAE,OAAQ,EAAE,MAAO,CAAC,EACrE,eAAa,KAAK,EAAE,KAAgB,CAAE,OAAQ,EAAE,MAAO,CAAC,CACjE,CAAC,CACH,CACF,CDRO,SAASE,GAGd,CACA,IAAMC,KAAS,eAAY,GAAGC,EAAmB,CAAC,EAClD,OAAAD,EAAO,OAAO,CAAE,mBAAoB,QAAS,CAAC,EACvC,CACL,OAAAA,EACA,IAAK,CAACE,EAA6B,CAAC,IAAMF,EAAO,IAAI,GAAGC,EAAmBC,CAAQ,CAAC,CACtF,CACF","names":["node_exports","__export","setupSentientServer","__toCommonJS","import_node","import_msw","captured","recordEvent","confidenceBandOf","c","pathOf","url","_a","e","apiOverride","scenario","route","_b","_c","o","r","resolveScenario","_method","bodyText","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","_q","_r","_s","_t","_u","path","override","body","recordEvent","b","json","s","slots","decl","defaultSlotResult","layoutOrder","persona","confidence","confidenceBandOf","componentId","variants","out","dim","values","declared","scenarioToHandlers","scenario","request","bodyText","resolveScenario","setupSentientServer","server","scenarioToHandlers","scenario"]}
|
package/dist/testing/node.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{setupServer as K}from"msw/node";import{http as z,HttpResponse as F}from"msw";var J=[];function
|
|
1
|
+
import{setupServer as K}from"msw/node";import{http as z,HttpResponse as F}from"msw";var J=[];function v(e){J.push(e)}function G(e){return e<.3?"low":e<.7?"medium":"high"}function L(e){var t;try{return new URL(e).pathname}catch(n){return(t=e.split("?")[0])!=null?t:e}}async function N(e,t){var s,a,o;let n=(s=e.api)==null?void 0:s[t];return n===void 0?null:n==="error"?{status:500}:typeof n=="number"?{status:n}:(n.delayMs&&await new Promise(u=>setTimeout(u,n.delayMs)),{status:(a=n.status)!=null?a:200,json:(o=n.body)!=null?o:{}})}async function D(e,t,n,s){var _,y,S,w,b,R,h,x,E,j,k,O,A,T,C,I,H,W,M,P,B;let a=L(n);if(!a.includes("/v1/"))return null;let o="/v1/"+((_=a.split("/v1/")[1])!=null?_:""),u=await N(e,o);if(u)return u;let i=s?JSON.parse(s):{};if(o==="/v1/sessions")return{status:204};if(o==="/v1/events"){for(let r of i)v(r);return{status:204}}if(o==="/v1/goals")return v({eventType:"goal",goalType:i.name}),{status:204};if(o==="/v1/assign"){let r=i;return{status:200,json:{variantId:(b=(w=(y=e.variants)==null?void 0:y[r.componentId])!=null?w:(S=r.variantIds)==null?void 0:S[0])!=null?b:"control",assignmentTtlMs:6e4}}}if(o==="/v1/decide"){let r=i,d={layoutOrder:(h=e.layout)!=null?h:((R=r.sections)!=null?R:[]).map(c=>c.id),assignments:(x=e.variants)!=null?x:{},persona:(E=e.persona)!=null?E:"unknown",confidence:(j=e.confidence)!=null?j:1};if(r.slots&&r.slots.length>0){let c={};for(let l of r.slots)c[l.id]=(O=(k=e.slots)==null?void 0:k[l.id])!=null?O:U(l);d.slots=c}return{status:200,json:d}}if(o==="/v1/explain"){let r=i,p=(T=e.layout)!=null?T:((A=r.sections)!=null?A:[]).map(g=>g.id),d=(I=(C=r.persona)!=null?C:e.persona)!=null?I:"unknown",c=(H=e.confidence)!=null?H:1,l={layoutOrder:p,assignments:(W=e.variants)!=null?W:{},persona:d,reasons:[],personaAttributes:{persona:d,confidence:G(c)}};if(r.slots&&r.slots.length>0){let g={};for(let f of r.slots)g[f.id]=(P=(M=e.slots)==null?void 0:M[f.id])!=null?P:U(f);l.slots=g}return{status:200,json:l}}return o==="/v1/weights"?{status:200,json:{components:Object.entries((B=e.weights)!=null?B:{}).map(([p,d])=>({componentId:p,updatedAt:0,variants:d}))}}:null}function U(e){var n,s,a;if(e.arms)return typeof e.baseline=="string"?e.baseline:(n=e.arms[0])!=null?n:"baseline";let t={};for(let[o,u]of Object.entries((s=e.dims)!=null?s:{})){let i=typeof e.baseline=="object"&&e.baseline!==null?e.baseline[o]:void 0;t[o]=(a=i!=null?i:u[0])!=null?a:""}return t}function m(e={}){return[z.all("*/v1/*",async({request:t})=>{let n=t.method==="GET"||t.method==="HEAD"?null:await t.text(),s=await D(e,t.method,t.url,n);if(s)return s.json===void 0?new F(null,{status:s.status}):F.json(s.json,{status:s.status})})]}function Q(){let e=K(...m());return e.listen({onUnhandledRequest:"bypass"}),{server:e,use:(t={})=>e.use(...m(t))}}export{Q as setupSentientServer};
|
|
2
2
|
//# sourceMappingURL=node.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/server.ts","../../src/testing/handlers.ts","../../src/testing/events.ts","../../src/testing/scenario.ts","../../src/testing/resolve.ts"],"sourcesContent":["import { setupServer, type SetupServer } from 'msw/node';\nimport { scenarioToHandlers } from './handlers.js';\nimport type { SentientScenario } from './scenario.js';\n\n/**\n * Create and start an msw/node server pre-loaded with the default (control)\n * scenario. Call `use(scenario)` to swap in a scenario for the current test.\n * Remember to `server.close()` in afterAll and `server.resetHandlers()` between tests.\n */\nexport function setupSentientServer(): {\n server: SetupServer;\n use: (scenario?: SentientScenario) => void;\n} {\n const server = setupServer(...scenarioToHandlers());\n server.listen({ onUnhandledRequest: 'bypass' });\n return {\n server,\n use: (scenario: SentientScenario = {}) => server.use(...scenarioToHandlers(scenario)),\n };\n}\n","import { http, HttpResponse } from 'msw';\nimport type { RequestHandler } from 'msw';\nimport { resolveScenario } from './resolve.js';\nimport type { SentientScenario } from './scenario.js';\n\n/** Turn a scenario into MSW handlers stubbing every SDK endpoint + capturing events. */\nexport function scenarioToHandlers(scenario: SentientScenario = {}): RequestHandler[] {\n return [\n http.all('*/v1/*', async ({ request }) => {\n const bodyText =\n request.method === 'GET' || request.method === 'HEAD' ? null : await request.text();\n const r = await resolveScenario(scenario, request.method, request.url, bodyText);\n if (!r) return undefined; // not a stubbed route — let MSW handle passthrough\n if (r.json === undefined) return new HttpResponse(null, { status: r.status });\n return HttpResponse.json(r.json as object, { status: r.status });\n }),\n ];\n}\n","export type CapturedEvent = {\n eventType: string;\n goalType?: string;\n componentId?: string;\n variantId?: string;\n [k: string]: unknown;\n};\n\nconst captured: CapturedEvent[] = [];\n\nexport function recordEvent(e: CapturedEvent): void {\n captured.push(e);\n}\n\nexport function getSentientEvents(): CapturedEvent[] {\n return [...captured];\n}\n\nexport function clearSentientEvents(): void {\n captured.length = 0;\n}\n\n/** True if any captured event is a goal (component goal_achieved or named goal) with this name. */\nexport function hasFiredGoal(events: CapturedEvent[], goalName: string): boolean {\n return events.some(\n (e) => (e.eventType === 'goal_achieved' || e.eventType === 'goal') && e.goalType === goalName,\n );\n}\n","import { notifyOverridesChanged } from '../override-events.js';\n\nexport type ScenarioWeight = { variantId: string; pulls: number; avgReward: number };\nexport type ScenarioApiOverride =\n | 'error'\n | number\n | { status?: number; body?: unknown; delayMs?: number };\n\nexport type SentientScenario = {\n variants?: Record<string, string>;\n layout?: string[];\n /** Forced persona (canonical PersonaKey). Also sets the persona html attributes. */\n persona?: string;\n /** Persona confidence 0–1; buckets to low/medium/high for the html attribute. Default 1. */\n confidence?: number;\n /** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */\n slots?: Record<string, string | Record<string, string>>;\n weights?: Record<string, ScenarioWeight[]>;\n api?: Record<string, ScenarioApiOverride>;\n};\n\ntype ScenarioWindow = {\n __sentient_overrides?: Record<string, string>;\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n __sentient_persona_override?: { persona: string; confidence?: number };\n};\n\n/**\n * Confidence → band. Cutoffs pinned to @sentientui/policy `confidenceBand`\n * (<0.3 low, <0.7 medium, else high). Duplicated (not imported) because the\n * Playwright init function is serialized into the page and cannot close\n * over imports — both copies are pinned by tests.\n */\nexport function confidenceBandOf(c: number): 'low' | 'medium' | 'high' {\n return c < 0.3 ? 'low' : c < 0.7 ? 'medium' : 'high';\n}\n\n/** Apply a scenario by setting the client-forcing globals the SDK reads. */\nexport function applyScenario(scenario: SentientScenario = {}): void {\n const w = window as unknown as ScenarioWindow;\n w.__sentient_overrides = { ...(scenario.variants ?? {}) };\n if (scenario.layout) w.__sentient_layout_override = scenario.layout;\n else delete w.__sentient_layout_override;\n if (scenario.slots) w.__sentient_slot_overrides = { ...scenario.slots };\n else delete w.__sentient_slot_overrides;\n if (scenario.persona) {\n w.__sentient_persona_override = {\n persona: scenario.persona,\n confidence: scenario.confidence ?? 1,\n };\n try {\n const d = document.documentElement;\n d.setAttribute('data-sentient-persona', scenario.persona);\n d.setAttribute('data-sentient-confidence', confidenceBandOf(scenario.confidence ?? 1));\n } catch {\n /* no DOM (node env) — the override globals still apply */\n }\n } else {\n delete w.__sentient_persona_override;\n }\n // Ring the override bus so hooks already mounted (useAssignment /\n // useSlotResult / useAdaptivePersona subscribe via useSyncExternalStore)\n // re-render immediately — otherwise forcing state mid-test only takes effect\n // on the next unrelated render.\n notifyOverridesChanged();\n}\n\n/** Clear all forced state. */\nexport function resetScenario(): void {\n const w = window as unknown as ScenarioWindow;\n delete w.__sentient_overrides;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete w.__sentient_persona_override;\n try {\n document.documentElement.removeAttribute('data-sentient-persona');\n document.documentElement.removeAttribute('data-sentient-confidence');\n } catch {\n /* no DOM */\n }\n notifyOverridesChanged();\n}\n","import { recordEvent, type CapturedEvent } from './events.js';\nimport { confidenceBandOf, type SentientScenario, type ScenarioApiOverride } from './scenario.js';\n\n/** A framework-agnostic resolved response. `json` undefined ⇒ empty body. */\nexport type ResolvedResponse = { status: number; json?: unknown };\n\nfunction pathOf(url: string): string {\n try { return new URL(url).pathname; } catch { return url.split('?')[0] ?? url; }\n}\n\nasync function apiOverride(scenario: SentientScenario, route: string): Promise<ResolvedResponse | null> {\n const o: ScenarioApiOverride | undefined = scenario.api?.[route];\n if (o === undefined) return null;\n if (o === 'error') return { status: 500 };\n if (typeof o === 'number') return { status: o };\n if (o.delayMs) await new Promise((r) => setTimeout(r, o.delayMs));\n return { status: o.status ?? 200, json: o.body ?? {} };\n}\n\n/**\n * Resolve a request against a scenario. Returns a response, or null for routes\n * outside `/v1/*` (let the caller pass through). Shared by the MSW handlers and\n * the Playwright/Cypress adapters so behaviour can't drift.\n */\nexport async function resolveScenario(\n scenario: SentientScenario,\n _method: string,\n url: string,\n bodyText: string | null,\n): Promise<ResolvedResponse | null> {\n const path = pathOf(url);\n if (!path.includes('/v1/')) return null;\n\n const route = '/v1/' + (path.split('/v1/')[1] ?? '');\n const override = await apiOverride(scenario, route);\n if (override) return override;\n\n const body = bodyText ? (JSON.parse(bodyText) as unknown) : {};\n\n if (route === '/v1/sessions') return { status: 204 };\n\n if (route === '/v1/events') {\n for (const e of body as CapturedEvent[]) recordEvent(e);\n return { status: 204 };\n }\n\n if (route === '/v1/goals') {\n recordEvent({ eventType: 'goal', goalType: (body as { name?: string }).name });\n return { status: 204 };\n }\n\n if (route === '/v1/assign') {\n const b = body as { componentId: string; variantIds?: string[] };\n const variantId = scenario.variants?.[b.componentId] ?? b.variantIds?.[0] ?? 'control';\n return { status: 200, json: { variantId, assignmentTtlMs: 60_000 } };\n }\n\n if (route === '/v1/decide') {\n const b = body as {\n sections?: { id: string }[];\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona: scenario.persona ?? 'unknown',\n confidence: scenario.confidence ?? 1,\n };\n // Mirror the real server: the slots key exists ONLY when slots were requested.\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/explain') {\n const b = body as {\n sections?: { id: string }[];\n persona?: string;\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const persona = b.persona ?? scenario.persona ?? 'unknown';\n const confidence = scenario.confidence ?? 1;\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona,\n reasons: [],\n personaAttributes: { persona, confidence: confidenceBandOf(confidence) },\n };\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/weights') {\n const components = Object.entries(scenario.weights ?? {}).map(([componentId, variants]) => ({ componentId, updatedAt: 0, variants }));\n return { status: 200, json: { components } };\n }\n\n return null;\n}\n\n/** Declared baseline of a slot: explicit `baseline`, else first arm / first value per dim. */\nfunction defaultSlotResult(decl: {\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n}): string | Record<string, string> {\n if (decl.arms) {\n return typeof decl.baseline === 'string' ? decl.baseline : decl.arms[0] ?? 'baseline';\n }\n const out: Record<string, string> = {};\n for (const [dim, values] of Object.entries(decl.dims ?? {})) {\n const declared =\n typeof decl.baseline === 'object' && decl.baseline !== null ? decl.baseline[dim] : undefined;\n out[dim] = declared ?? values[0] ?? '';\n }\n return out;\n}\n"],"mappings":"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","names":["setupServer","http","HttpResponse","captured","recordEvent","confidenceBandOf","c","pathOf","url","_a","e","apiOverride","scenario","route","_b","_c","o","r","resolveScenario","_method","bodyText","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","_q","_r","_s","_t","_u","path","override","body","recordEvent","b","json","s","slots","decl","defaultSlotResult","layoutOrder","persona","confidence","confidenceBandOf","componentId","variants","out","dim","values","declared","scenarioToHandlers","scenario","http","request","bodyText","r","resolveScenario","HttpResponse","setupSentientServer","server","setupServer","scenarioToHandlers","scenario"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/server.ts","../../src/testing/handlers.ts","../../src/testing/events.ts","../../src/testing/scenario.ts","../../src/testing/resolve.ts"],"sourcesContent":["import { setupServer, type SetupServer } from 'msw/node';\nimport { scenarioToHandlers } from './handlers.js';\nimport type { SentientScenario } from './scenario.js';\n\n/**\n * Create and start an msw/node server pre-loaded with the default (control)\n * scenario. Call `use(scenario)` to swap in a scenario for the current test.\n * Remember to `server.close()` in afterAll and `server.resetHandlers()` between tests.\n */\nexport function setupSentientServer(): {\n server: SetupServer;\n use: (scenario?: SentientScenario) => void;\n} {\n const server = setupServer(...scenarioToHandlers());\n server.listen({ onUnhandledRequest: 'bypass' });\n return {\n server,\n use: (scenario: SentientScenario = {}) => server.use(...scenarioToHandlers(scenario)),\n };\n}\n","import { http, HttpResponse } from 'msw';\nimport type { RequestHandler } from 'msw';\nimport { resolveScenario } from './resolve.js';\nimport type { SentientScenario } from './scenario.js';\n\n/** Turn a scenario into MSW handlers stubbing every SDK endpoint + capturing events. */\nexport function scenarioToHandlers(scenario: SentientScenario = {}): RequestHandler[] {\n return [\n http.all('*/v1/*', async ({ request }) => {\n const bodyText =\n request.method === 'GET' || request.method === 'HEAD' ? null : await request.text();\n const r = await resolveScenario(scenario, request.method, request.url, bodyText);\n if (!r) return undefined; // not a stubbed route — let MSW handle passthrough\n if (r.json === undefined) return new HttpResponse(null, { status: r.status });\n return HttpResponse.json(r.json as object, { status: r.status });\n }),\n ];\n}\n","export type CapturedEvent = {\n eventType: string;\n goalType?: string;\n componentId?: string;\n variantId?: string;\n [k: string]: unknown;\n};\n\nconst captured: CapturedEvent[] = [];\n\nexport function recordEvent(e: CapturedEvent): void {\n captured.push(e);\n}\n\nexport function getSentientEvents(): CapturedEvent[] {\n return [...captured];\n}\n\nexport function clearSentientEvents(): void {\n captured.length = 0;\n}\n\n/** True if any captured event is a goal (component goal_achieved or named goal) with this name. */\nexport function hasFiredGoal(events: CapturedEvent[], goalName: string): boolean {\n return events.some(\n (e) => (e.eventType === 'goal_achieved' || e.eventType === 'goal') && e.goalType === goalName,\n );\n}\n","import { notifyOverridesChanged } from '../override-events.js';\n\nexport type ScenarioWeight = { variantId: string; pulls: number; avgReward: number };\nexport type ScenarioApiOverride =\n | 'error'\n | number\n | { status?: number; body?: unknown; delayMs?: number };\n\nexport type SentientScenario = {\n variants?: Record<string, string>;\n layout?: string[];\n /** Forced persona (canonical PersonaKey). Also sets the persona html attributes. */\n persona?: string;\n /** Persona confidence 0–1; buckets to low/medium/high for the html attribute. Default 1. */\n confidence?: number;\n /** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */\n slots?: Record<string, string | Record<string, string>>;\n /** Forced registry slot config (AdaptiveSlot content/blocks): slot id → entry.\n * Pair with `slots` to name which arm is \"served\" (blocks are keyed by arm). */\n slotConfig?: Record<string, unknown>;\n weights?: Record<string, ScenarioWeight[]>;\n api?: Record<string, ScenarioApiOverride>;\n};\n\ntype ScenarioWindow = {\n __sentient_overrides?: Record<string, string>;\n __sentient_layout_override?: string[];\n __sentient_slot_overrides?: Record<string, string | Record<string, string>>;\n __sentient_slot_config_overrides?: Record<string, unknown>;\n __sentient_persona_override?: { persona: string; confidence?: number };\n};\n\n/**\n * Confidence → band. Cutoffs pinned to @sentientui/policy `confidenceBand`\n * (<0.3 low, <0.7 medium, else high). Duplicated (not imported) because the\n * Playwright init function is serialized into the page and cannot close\n * over imports — both copies are pinned by tests.\n */\nexport function confidenceBandOf(c: number): 'low' | 'medium' | 'high' {\n return c < 0.3 ? 'low' : c < 0.7 ? 'medium' : 'high';\n}\n\n/** Apply a scenario by setting the client-forcing globals the SDK reads. */\nexport function applyScenario(scenario: SentientScenario = {}): void {\n const w = window as unknown as ScenarioWindow;\n w.__sentient_overrides = { ...(scenario.variants ?? {}) };\n if (scenario.layout) w.__sentient_layout_override = scenario.layout;\n else delete w.__sentient_layout_override;\n if (scenario.slots) w.__sentient_slot_overrides = { ...scenario.slots };\n else delete w.__sentient_slot_overrides;\n if (scenario.slotConfig) w.__sentient_slot_config_overrides = { ...scenario.slotConfig };\n else delete w.__sentient_slot_config_overrides;\n if (scenario.persona) {\n w.__sentient_persona_override = {\n persona: scenario.persona,\n confidence: scenario.confidence ?? 1,\n };\n try {\n const d = document.documentElement;\n d.setAttribute('data-sentient-persona', scenario.persona);\n d.setAttribute('data-sentient-confidence', confidenceBandOf(scenario.confidence ?? 1));\n } catch {\n /* no DOM (node env) — the override globals still apply */\n }\n } else {\n delete w.__sentient_persona_override;\n }\n // Ring the override bus so hooks already mounted (useAssignment /\n // useSlotResult / useAdaptivePersona subscribe via useSyncExternalStore)\n // re-render immediately — otherwise forcing state mid-test only takes effect\n // on the next unrelated render.\n notifyOverridesChanged();\n}\n\n/** Clear all forced state. */\nexport function resetScenario(): void {\n const w = window as unknown as ScenarioWindow;\n delete w.__sentient_overrides;\n delete w.__sentient_layout_override;\n delete w.__sentient_slot_overrides;\n delete w.__sentient_slot_config_overrides;\n delete w.__sentient_persona_override;\n try {\n document.documentElement.removeAttribute('data-sentient-persona');\n document.documentElement.removeAttribute('data-sentient-confidence');\n } catch {\n /* no DOM */\n }\n notifyOverridesChanged();\n}\n","import { recordEvent, type CapturedEvent } from './events.js';\nimport { confidenceBandOf, type SentientScenario, type ScenarioApiOverride } from './scenario.js';\n\n/** A framework-agnostic resolved response. `json` undefined ⇒ empty body. */\nexport type ResolvedResponse = { status: number; json?: unknown };\n\nfunction pathOf(url: string): string {\n try { return new URL(url).pathname; } catch { return url.split('?')[0] ?? url; }\n}\n\nasync function apiOverride(scenario: SentientScenario, route: string): Promise<ResolvedResponse | null> {\n const o: ScenarioApiOverride | undefined = scenario.api?.[route];\n if (o === undefined) return null;\n if (o === 'error') return { status: 500 };\n if (typeof o === 'number') return { status: o };\n if (o.delayMs) await new Promise((r) => setTimeout(r, o.delayMs));\n return { status: o.status ?? 200, json: o.body ?? {} };\n}\n\n/**\n * Resolve a request against a scenario. Returns a response, or null for routes\n * outside `/v1/*` (let the caller pass through). Shared by the MSW handlers and\n * the Playwright/Cypress adapters so behaviour can't drift.\n */\nexport async function resolveScenario(\n scenario: SentientScenario,\n _method: string,\n url: string,\n bodyText: string | null,\n): Promise<ResolvedResponse | null> {\n const path = pathOf(url);\n if (!path.includes('/v1/')) return null;\n\n const route = '/v1/' + (path.split('/v1/')[1] ?? '');\n const override = await apiOverride(scenario, route);\n if (override) return override;\n\n const body = bodyText ? (JSON.parse(bodyText) as unknown) : {};\n\n if (route === '/v1/sessions') return { status: 204 };\n\n if (route === '/v1/events') {\n for (const e of body as CapturedEvent[]) recordEvent(e);\n return { status: 204 };\n }\n\n if (route === '/v1/goals') {\n recordEvent({ eventType: 'goal', goalType: (body as { name?: string }).name });\n return { status: 204 };\n }\n\n if (route === '/v1/assign') {\n const b = body as { componentId: string; variantIds?: string[] };\n const variantId = scenario.variants?.[b.componentId] ?? b.variantIds?.[0] ?? 'control';\n return { status: 200, json: { variantId, assignmentTtlMs: 60_000 } };\n }\n\n if (route === '/v1/decide') {\n const b = body as {\n sections?: { id: string }[];\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona: scenario.persona ?? 'unknown',\n confidence: scenario.confidence ?? 1,\n };\n // Mirror the real server: the slots key exists ONLY when slots were requested.\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/explain') {\n const b = body as {\n sections?: { id: string }[];\n persona?: string;\n slots?: Array<{\n id: string;\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n }>;\n };\n const layoutOrder = scenario.layout ?? (b.sections ?? []).map((s) => s.id);\n const persona = b.persona ?? scenario.persona ?? 'unknown';\n const confidence = scenario.confidence ?? 1;\n const json: Record<string, unknown> = {\n layoutOrder,\n assignments: scenario.variants ?? {},\n persona,\n reasons: [],\n personaAttributes: { persona, confidence: confidenceBandOf(confidence) },\n };\n if (b.slots && b.slots.length > 0) {\n const slots: Record<string, unknown> = {};\n for (const decl of b.slots) {\n slots[decl.id] = scenario.slots?.[decl.id] ?? defaultSlotResult(decl);\n }\n json.slots = slots;\n }\n return { status: 200, json };\n }\n\n if (route === '/v1/weights') {\n const components = Object.entries(scenario.weights ?? {}).map(([componentId, variants]) => ({ componentId, updatedAt: 0, variants }));\n return { status: 200, json: { components } };\n }\n\n return null;\n}\n\n/** Declared baseline of a slot: explicit `baseline`, else first arm / first value per dim. */\nfunction defaultSlotResult(decl: {\n arms?: string[];\n dims?: Record<string, string[]>;\n baseline?: string | Record<string, string>;\n}): string | Record<string, string> {\n if (decl.arms) {\n return typeof decl.baseline === 'string' ? decl.baseline : decl.arms[0] ?? 'baseline';\n }\n const out: Record<string, string> = {};\n for (const [dim, values] of Object.entries(decl.dims ?? {})) {\n const declared =\n typeof decl.baseline === 'object' && decl.baseline !== null ? decl.baseline[dim] : undefined;\n out[dim] = declared ?? values[0] ?? '';\n }\n return out;\n}\n"],"mappings":"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","names":["setupServer","http","HttpResponse","captured","recordEvent","confidenceBandOf","c","pathOf","url","_a","e","apiOverride","scenario","route","_b","_c","o","r","resolveScenario","_method","bodyText","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","_q","_r","_s","_t","_u","path","override","body","recordEvent","b","json","s","slots","decl","defaultSlotResult","layoutOrder","persona","confidence","confidenceBandOf","componentId","variants","out","dim","values","declared","scenarioToHandlers","scenario","http","request","bodyText","r","resolveScenario","HttpResponse","setupSentientServer","server","setupServer","scenarioToHandlers","scenario"]}
|
package/dist/testing/react.d.cts
CHANGED
|
@@ -20,6 +20,9 @@ type SentientScenario = {
|
|
|
20
20
|
confidence?: number;
|
|
21
21
|
/** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */
|
|
22
22
|
slots?: Record<string, string | Record<string, string>>;
|
|
23
|
+
/** Forced registry slot config (AdaptiveSlot content/blocks): slot id → entry.
|
|
24
|
+
* Pair with `slots` to name which arm is "served" (blocks are keyed by arm). */
|
|
25
|
+
slotConfig?: Record<string, unknown>;
|
|
23
26
|
weights?: Record<string, ScenarioWeight[]>;
|
|
24
27
|
api?: Record<string, ScenarioApiOverride>;
|
|
25
28
|
};
|
package/dist/testing/react.d.ts
CHANGED
|
@@ -20,6 +20,9 @@ type SentientScenario = {
|
|
|
20
20
|
confidence?: number;
|
|
21
21
|
/** Forced slot results: slot id → arm id (arms slots) or per-dim values (token slots). */
|
|
22
22
|
slots?: Record<string, string | Record<string, string>>;
|
|
23
|
+
/** Forced registry slot config (AdaptiveSlot content/blocks): slot id → entry.
|
|
24
|
+
* Pair with `slots` to name which arm is "served" (blocks are keyed by arm). */
|
|
25
|
+
slotConfig?: Record<string, unknown>;
|
|
23
26
|
weights?: Record<string, ScenarioWeight[]>;
|
|
24
27
|
api?: Record<string, ScenarioApiOverride>;
|
|
25
28
|
};
|
package/dist/testing/react.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
"use strict";var
|
|
2
|
+
"use strict";var Y=Object.create;var h=Object.defineProperty,Z=Object.defineProperties,ee=Object.getOwnPropertyDescriptor,te=Object.getOwnPropertyDescriptors,ne=Object.getOwnPropertyNames,D=Object.getOwnPropertySymbols,ie=Object.getPrototypeOf,W=Object.prototype.hasOwnProperty,re=Object.prototype.propertyIsEnumerable;var I=(e,t,n)=>t in e?h(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,v=(e,t)=>{for(var n in t||(t={}))W.call(t,n)&&I(e,n,t[n]);if(D)for(var n of D(t))re.call(t,n)&&I(e,n,t[n]);return e},F=(e,t)=>Z(e,te(t));var oe=(e,t)=>{for(var n in t)h(e,n,{get:t[n],enumerable:!0})},K=(e,t,n,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let l of ne(t))!W.call(e,l)&&l!==n&&h(e,l,{get:()=>t[l],enumerable:!(a=ee(t,l))||a.enumerable});return e};var M=(e,t,n)=>(n=e!=null?Y(ie(e)):{},K(t||!e||!e.__esModule?h(n,"default",{value:e,enumerable:!0}):n,e)),se=e=>K(h({},"__esModule",{value:!0}),e);var ye={};oe(ye,{renderWithSentient:()=>_e});module.exports=se(ye);var q=require("@testing-library/react");var r=require("react"),R=require("@sentientui/core");var ae=new Map,le=new Map;function T(e,t){ae.set(e,t);let n=le.get(e);if(n)for(let a of n)try{a(t)}catch(l){}}var ce={on:!1,listeners:new Set};function B(){if(typeof window=="undefined")return ce;let e=window;return e.__sentient_preview||(e.__sentient_preview={on:!1,listeners:new Set}),e.__sentient_preview}function k(){return B().on}function N(e){let t=B().listeners;return t.add(e),()=>{t.delete(e)}}function V(e){return{isLocal:e.isLocal,track:()=>{},goal:()=>{},componentGoal:()=>{},identify:()=>{},fetchWeights:()=>Promise.resolve([]),getAssignment:(t,n)=>e.getAssignment(t,n),assign:(t,n,a,l)=>e.assign(t,n,a,l),decide:()=>Promise.resolve(null),getSlotResult:t=>e.getSlotResult(t),getSlotConfig:t=>e.getSlotConfig(t),getSitePalette:()=>e.getSitePalette(),reportSlots:t=>e.reportSlots(t),getPersona:()=>e.getPersona(),getGraph:()=>e.getGraph(),dispose:()=>e.dispose(),destroy:()=>e.destroy()}}var de="sentient:overrides-changed";function U(){var t;if(typeof window=="undefined")return;let e=window;e.__sentient_overrides_version=((t=e.__sentient_overrides_version)!=null?t:0)+1,window.dispatchEvent(new Event(de))}function $(e){typeof window!="undefined"&&(window.__sentient_devtools_config=e)}function x(){var e;return typeof process=="undefined"||((e=process.env)==null?void 0:e.NODE_ENV)!=="production"}var ue={components:new Map,slots:new Map,sections:[],listeners:new Set,version:0};function A(){if(typeof window=="undefined")return ue;let e=window;return e.__sentient_registry||(e.__sentient_registry={components:new Map,slots:new Map,sections:[],listeners:new Set,version:0}),e.__sentient_registry}function fe(){A().version+=1;for(let e of A().listeners)e()}function P(e){x()&&(A().sections=[...e],fe())}var z="0.29.0",O=z!=="0.0.0-dev"?{name:"react",version:z}:void 0;var J=require("react/jsx-runtime");function ge(){var e,t;if(typeof window=="undefined")return"desktop:direct";try{return(0,R.deriveSessionSegment)({userAgent:(e=navigator.userAgent)!=null?e:"",referer:(t=document.referrer)!=null?t:"",appOrigin:window.location.origin})}catch(n){return"desktop:direct"}}var H="https://api.sentient-ui.com/v1",pe=(0,r.createContext)({client:null,apiKey:"",initialAssignments:{},sessionSegment:"desktop:direct",ssrFallback:"first",onAssignment:void 0,initialLayoutOrder:null,initialSlots:{},initialSlotConfig:{},initialPalette:null,initialPersona:null,apiBaseUrl:H,debug:!1});function me(e){let[t,n]=(0,r.useState)(!1),a=(0,r.useRef)(e);a.current=e;let{cookie:l,value:g,event:_}=e!=null?e:{},y=typeof(e==null?void 0:e.check)=="function";return(0,r.useEffect)(()=>{let p=()=>{var w;let d=a.current;if(!d)return!1;if(d.check)return d.check()===!0;if(!d.cookie||typeof document=="undefined")return!1;let b=`${d.cookie}=${(w=d.value)!=null?w:"accepted"}`;return document.cookie.split("; ").some(E=>E.trim()===b)};if(n(p()),!_)return;let S=()=>{n(p())};return window.addEventListener(_,S),()=>window.removeEventListener(_,S)},[l,g,_,y]),t}function j(e){var G,L;let[t,n]=(0,r.useState)(null),[a]=(0,r.useState)(()=>{var i;return(i=e.sessionSegment)!=null?i:ge()}),[l,g]=(0,r.useState)(k());(0,r.useEffect)(()=>N(()=>g(k())),[]);let _=me(e.consentFrom),y=e.consentFrom?e.consent===!0||_:e.consent,p=(0,r.useRef)(null);(0,r.useEffect)(()=>{var m;if(y===!1&&!e.preConsentBehavior){(m=p.current)==null||m.destroy(),p.current=null,n(null);return}let i=v({apiKey:e.apiKey,context:e.context,debug:e.debug,initialAssignments:e.initialAssignments,sessionSegment:a,consent:y,preConsentBehavior:e.preConsentBehavior,respectDoNotTrack:e.respectDoNotTrack,ssrSessionId:e.ssrSessionId,country:e.country,persona:e.persona,localMode:e.localMode,initialSlots:e.initialSlots,initialSlotConfig:e.initialSlotConfig,initialPalette:e.initialPalette,initialPersona:e.initialPersona,ingestUrl:e.apiBaseUrl?`${e.apiBaseUrl.replace(/\/$/,"")}/events`:void 0},O?{sdk:O}:{}),c=!1,o=null,s=null,u=f=>{e.engagement!==!1&&import("@sentientui/core/engagement").then(({startEngagementCapture:C})=>{c||(s=C(f,{apiKey:e.apiKey,apiBase:e.apiBaseUrl?e.apiBaseUrl.replace(/\/$/,""):void 0}))})};return e.enableGraph!==!1?import("@sentientui/core/graph").then(({init:f})=>{c||(o=f(F(v({},i),{graph:!0,captureDomText:e.captureDomText===!0})),p.current=o,n(o),u(o))}):(o=(0,R.init)(i),p.current=o,n(o),u(o)),()=>{c=!0,s==null||s(),o==null||o.dispose()}},[y]),(0,r.useEffect)(()=>{if(!t)return;let i=!1,c=async()=>{if(i)return;let s;try{s=await t.fetchWeights()}catch(u){return}if(!i)for(let u of s){let m={componentId:u.componentId,updatedAt:u.updatedAt,variants:u.variants.map(f=>{var C;return{variantId:f.variantId,pulls:f.pulls,avgReward:(C=f.avgReward)!=null?C:0}})};T(u.componentId,m)}};c();let o=setInterval(()=>{c()},6e4);return()=>{i=!0,clearInterval(o)}},[t]);let S=(G=e.ssrFallback)!=null?G:"first",d=((L=e.apiBaseUrl)!=null?L:H).replace(/\/$/,""),b=(0,r.useRef)(null);(0,r.useEffect)(()=>{var o;if(typeof process!="undefined"&&((o=process.env)==null?void 0:o.NODE_ENV)==="production")return;let i={apiKey:e.apiKey,context:e.context,country:e.country,persona:e.persona,apiBaseUrl:d},c=b.current;if(b.current=i,c!==null)for(let s of["apiKey","context","country","persona","apiBaseUrl"])Object.is(c[s],i[s])||console.warn(`[sentient] AdaptiveProvider: \`${s}\` changed after initialisation, but the SDK client is stable for the session and only re-inits on \`consent\` \u2014 the new value is ignored. Remount the provider (e.g. via a changing \`key\` prop) to apply it.`)},[e.apiKey,e.context,e.country,e.persona,d]),(0,r.useEffect)(()=>{var i;typeof process!="undefined"&&((i=process.env)==null?void 0:i.NODE_ENV)==="production"||$({apiKey:e.apiKey,apiBaseUrl:d,isLocal:(t==null?void 0:t.isLocal)===!0})},[t,e.apiKey,d]),(0,r.useEffect)(()=>{let i=e.initialLayoutOrder;if(i&&i.length>0){P(i);return}e.declaredSections&&e.declaredSections.length>0&&P(e.declaredSections)},[e.initialLayoutOrder,e.declaredSections]),(0,r.useEffect)(()=>{if(!x())return;let i=e.declaredSections;if(!i||i.length===0||typeof document=="undefined")return;let c=new Set(Array.from(document.querySelectorAll("[data-sentient-id]"),s=>s.getAttribute("data-sentient-id"))),o=i.filter(s=>!c.has(s));o.length>0&&console.warn(`[SentientUI] Declared section${o.length>1?"s":""} ${o.map(s=>`"${s}"`).join(", ")} ${o.length>1?"have":"has"} no matching data-sentient-id element, so the layout engine cannot learn what they are and will serve the same order to every persona. Add data-sentient-id="<sectionId>" (plus optional data-sentient-type) to each section's element.`)},[e.declaredSections]);let w=(0,r.useMemo)(()=>t&&l?V(t):t,[t,l]),E=(0,r.useMemo)(()=>{var i,c,o,s,u,m,f;return{client:w,apiKey:e.apiKey,initialAssignments:(i=e.initialAssignments)!=null?i:{},sessionSegment:a,ssrFallback:S,onAssignment:e.onAssignment,initialLayoutOrder:(c=e.initialLayoutOrder)!=null?c:null,initialSlots:(o=e.initialSlots)!=null?o:{},initialSlotConfig:(s=e.initialSlotConfig)!=null?s:{},initialPalette:(u=e.initialPalette)!=null?u:null,initialPersona:(m=e.initialPersona)!=null?m:null,apiBaseUrl:d,debug:(f=e.debug)!=null?f:!1}},[w,e.apiKey,e.initialAssignments,a,S,e.onAssignment,e.initialLayoutOrder,e.initialSlots,e.initialSlotConfig,e.initialPalette,e.initialPersona,d,e.debug]);return(0,J.jsx)(pe.Provider,{value:E,children:e.children})}function ve(e){return e<.3?"low":e<.7?"medium":"high"}function X(e={}){var n,a,l;let t=window;if(t.__sentient_overrides=v({},(n=e.variants)!=null?n:{}),e.layout?t.__sentient_layout_override=e.layout:delete t.__sentient_layout_override,e.slots?t.__sentient_slot_overrides=v({},e.slots):delete t.__sentient_slot_overrides,e.slotConfig?t.__sentient_slot_config_overrides=v({},e.slotConfig):delete t.__sentient_slot_config_overrides,e.persona){t.__sentient_persona_override={persona:e.persona,confidence:(a=e.confidence)!=null?a:1};try{let g=document.documentElement;g.setAttribute("data-sentient-persona",e.persona),g.setAttribute("data-sentient-confidence",ve((l=e.confidence)!=null?l:1))}catch(g){}}else delete t.__sentient_persona_override;U()}var Q=require("react/jsx-runtime");function _e(e,t={},n){return X(t),(0,q.render)((0,Q.jsx)(j,{apiKey:"pk_test",context:"saas",consent:!1,children:e}),n)}0&&(module.exports={renderWithSentient});
|
|
3
3
|
//# sourceMappingURL=react.js.map
|