{"_id":"@buddhilive/dsh-client-test-runtime","name":"@buddhilive/dsh-client-test-runtime","dist-tags":{"alpha":"0.1.2-alpha.3","latest":"0.1.2-alpha.3"},"versions":{"0.1.2-alpha.3":{"name":"@buddhilive/dsh-client-test-runtime","description":"jsdom slot test runtime: real Cordis Context + SlotRegistry + UI renderer with test-owned session/workspace doubles for feature specs","version":"0.1.2-alpha.3","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/Buddhilive/buddhi-ai-harness.git","directory":"packages/test-support/client-runtime"},"type":"module","main":"lib/index.js","types":"lib/types/index.d.ts","exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./src/*":"./src/*","./package.json":"./package.json"},"license":"MIT","dependencies":{"@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","vitest":"^4.1.8"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@buddhilive/dsh-api-session-controller":"^0.1.2-alpha.3","@buddhilive/dsh-api-workspace-controller":"^0.1.2-alpha.3","@buddhilive/dsh-client-connection":"^0.1.2-alpha.3","@buddhilive/dsh-attachment":"^0.1.2-alpha.3","@buddhilive/dsh-client-store":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-conversation":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-chat":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-session":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-settings":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-slots":"^0.1.2-alpha.3","@buddhilive/dsh-invariants":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-renderer":"^0.1.2-alpha.3","@buddhilive/dsh-session":"^0.1.2-alpha.3","@buddhilive/dsh-subagent":"^0.1.2-alpha.3","@deepseek-ai/cordis":"^4.0.2","@buddhilive/dsh-typert-protocol":"^0.1.2-alpha.3"},"devDependencies":{"@types/react":"~18.3.1","@types/react-dom":"~18.3.0","react":"^18.2.0","react-dom":"^18.2.0","@buddhilive/dsh-api-session-controller":"^0.1.2-alpha.3","@buddhilive/dsh-client-connection":"^0.1.2-alpha.3","@buddhilive/dsh-client-store":"^0.1.2-alpha.3","@buddhilive/dsh-api-workspace-controller":"^0.1.2-alpha.3","@buddhilive/dsh-attachment":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-conversation":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-settings":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-slots":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-session":"^0.1.2-alpha.3","@buddhilive/dsh-session":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-renderer":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-chat":"^0.1.2-alpha.3","@buddhilive/dsh-subagent":"^0.1.2-alpha.3","@buddhilive/dsh-invariants":"^0.1.2-alpha.3","@buddhilive/dsh-typert-protocol":"^0.1.2-alpha.3","@deepseek-ai/cordis":"^4.0.2"},"_id":"@buddhilive/dsh-client-test-runtime@0.1.2-alpha.3","bugs":{"url":"https://github.com/Buddhilive/buddhi-ai-harness/issues"},"homepage":"https://github.com/Buddhilive/buddhi-ai-harness#readme","_integrity":"sha512-odV/NEvT2dxGZVsM5KvZWj5+LzA/UowLnWsJ44fdKGovgrOoR7IiksiOlGZMRhpoatUFDFoF/o0N7oHcZso1Kw==","_resolved":"C:\\DevDojo\\Buddhi\\buddhi-ai-harness\\dist\\npm\\buddhilive-dsh-client-test-runtime-0.1.2-alpha.3.tgz","_from":"file:C:/DevDojo/Buddhi/buddhi-ai-harness/dist/npm/buddhilive-dsh-client-test-runtime-0.1.2-alpha.3.tgz","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-odV/NEvT2dxGZVsM5KvZWj5+LzA/UowLnWsJ44fdKGovgrOoR7IiksiOlGZMRhpoatUFDFoF/o0N7oHcZso1Kw==","shasum":"e5db933cfacfeeed4d0ba83dee86959c791d6539","tarball":"https://registry.npmjs.org/@buddhilive/dsh-client-test-runtime/-/dsh-client-test-runtime-0.1.2-alpha.3.tgz","fileCount":18,"unpackedSize":108638,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD0BNoAGIY/JhBaFHxt1Pmylz/pDZjmmuYevXIDq6GbJwIgRCoJFvxSNX0NrsR6hSPjvL0uuSAMM7WDZw4P84Ex3NY="}]},"_npmUser":{"name":"buddhilive","email":"visitbudkavin@gmail.com"},"directories":{},"maintainers":[{"name":"buddhilive","email":"visitbudkavin@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-client-test-runtime_0.1.2-alpha.3_1788166067572_0.36495711253366614"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-31T08:47:47.227Z","0.1.2-alpha.3":"2026-08-31T08:47:47.712Z","modified":"2026-08-31T08:47:47.998Z"},"maintainers":[{"name":"buddhilive","email":"visitbudkavin@gmail.com"}],"description":"jsdom slot test runtime: real Cordis Context + SlotRegistry + UI renderer with test-owned session/workspace doubles for feature specs","homepage":"https://github.com/Buddhilive/buddhi-ai-harness#readme","repository":{"type":"git","url":"git+https://github.com/Buddhilive/buddhi-ai-harness.git","directory":"packages/test-support/client-runtime"},"bugs":{"url":"https://github.com/Buddhilive/buddhi-ai-harness/issues"},"license":"MIT","readme":"---\ndescription: \"面向浏览器功能测试的 jsdom slot 测试运行时，供测试作者针对生产机制检验 slot、store 与渲染。\"\nkind: \"package-library\"\n---\n\n# @buddhilive/dsh-client-test-runtime\n\n[English](README.md) | 中文\n\n## 概述\n\n`dsh-client-test-runtime` 让浏览器功能测试拥有真实的 jsdom 测试台：它把 Cordis 上下文、渲染器拥有的 slot 注册表与生产 `UiSession` 适配器组装在带类型的 Session 和 Workspace Controller 替身周围。功能套件无需复制生产渲染器或适配器逻辑，即可检验声明、注册、作用域、store、注入、渲染、更新与销毁。套件通过带类型 fixture 发布 Session 生命周期状态、Workspace 状态、projection 值与 Conversation 事件，再使用局部 DOM 快照根、限定范围的 Testing Library 查询与自明的服务缺失检查。它不属于产品插件图（无 `dsh.client`）；feature 包仅以 `devDependencies` 依赖之。\n\n## 目录\n\n- [使用本包](#use-this-package)\n- [理解实现](#understand-the-implementation)\n- [进一步探索](#further-exploration)\n- [模型体验](#model-experience)\n- [已知限制与延期工作](#known-limitations-and-deferred-work)\n- [开发备注](#dev-note)\n\n-----\n\n<a id=\"use-this-package\"></a>\n## 使用本包\n\n本包让浏览器功能测试拥有可挂载的真实运行时：创建测试台，声明你的功能所占用的 slot，挂载功能插件，渲染一个 slot，在局部视图上断言，然后 dispose（资源释放）——全程不存在生产逻辑的第二份实现。\n\n### 搭建功能测试\n\n`SlotTestRuntime.create()` 组装运行时，`declare(children)` 注册一个自动 frame，其逐 key 的 `<div data-slot>` 包裹层成为快照根，`mount(plugin)` 在真实 fiber 上运行功能，`renderSlot(key, owner)` 返回带限定查询与原位更新的 slot 局部视图：\n\n```text\nconst runtime = await SlotTestRuntime.create()\nawait runtime.declare({ 'feature-slot': {} })\nconst handle = await runtime.mount(FeaturePlugin)\nconst view = runtime.renderSlot('feature-slot', { owner: props })\nexpect(view.container).toMatchSnapshot()\nawait runtime.dispose()\n```\n\n`mount` 会预检必需服务，缺失时自明报错——先用 `provide(name, value)` 提供额外服务。`storeOf(key, scopeKey)` 返回渲染器交给 slot 组件的实时 store 实例，用于身份与动作驱动写入断言。\n\n### 局部 DOM 快照\n\n注册的快照序列化器把 CSS-module 哈希类名折回语义名（`_frame_a1b2c3` → `frame`），使 `.snap` 文件只含结构，并把 `<svg>` 内部折叠为 `data-content` 指纹。需要自定义页面 frame 的套件改用 `root.declare(children, Frame)` 而非自动 frame；`dispose()` 沿单一轴拆除视图、feature fiber、已铸 scope 与持久化 store 状态，且幂等。\n\n### 脚本化 Remote 应答与失败\n\n`TestRemote` 是 `ctx.remote` 面的替身：它把自己连同每个被脚本化的命名空间各注册一个服务，使注入 `remote.<name>` 的插件得以解除挂起；`$on` 订阅由显式的测试事件驱动器推动；`$host` 是普通可变字段，套件直接赋值即可脚本化带 home 或非 loopback 的 Host。UI 套件也在本包取用 `RemoteError` 构造器这个值——`dsh-api-remotes` facade 承载不了它，因为从套件发起的值 import 会拉起该装配尚未构建的 `/remote` 产物链。\n\n按 Host 会答的码来脚本化失败，并以生产代码同样的方式断言——判 `code`，绝不判类：\n\n```text\nimport { RemoteError } from '@buddhilive/dsh-client-test-runtime'\n\nremote.goals.create.mockResolvedValue({\n  ok: false,\n  error: new RemoteError('goal/not-found', 'goal \"g1\" does not exist', { goalId: 'g1' }),\n})\nexpect(view.getByRole('alert')).toHaveTextContent('goal/not-found')\n```\n\n### 何时使用\n\n当功能套件要在真实运行时下检验 slot、store、渲染与销毁时使用本测试台——生产 `SlotRegistry`、渲染器与 provide bundle 物化都会被挂载，绝不重实现。它是浏览器侧测试基础设施：永远不触及模型请求，feature 包仅以 `devDependencies` 依赖之。\n\n### 可能出什么问题\n\n- **已声明服务未提供**——`mount` 自明报错并列出缺失名称；请先用 `provide()` 提供。\n- **在 `declare` 之前尝试渲染**——`renderSlot` 自明报错；请先声明该 key。\n- **测试调用会话行为桩上未打桩的动词**——fixture 桩按设计自明报错，缺失的桩会在调用点浮现，而非静默通过。\n\n-----\n\n<a id=\"understand-the-implementation\"></a>\n## 理解实现\n\n<details>\n<summary>实现细节——点击展开</summary>\n\n本节解释测试台的设计；可观察行为已在[使用本包](#use-this-package)中完整说明。\n\n### 设计\n\n测试台不复制生产逻辑：它挂载生产 `SlotRegistry`、生产渲染器与 `UiSession` 适配器。`TestSessions` 与 `TestWorkspaces` 实现功能通过 Cordis 消费的 owner 接口，每个 fixture Session 实现 `SessionFace`，`stubSettingsScope` 实现 `SettingsScope`。`UiSession` 从这些 Controller binding 派生标准渲染器 source。未 stub 的 `ISession` 行为会携缺失方法名失败。\n\n### 源码地图\n\n| 文件 | 职责 |\n|---|---|\n| [`src/index.ts`](src/index.ts) | `SlotTestRuntime` 组装、`TestRoot`、自动 frame、`mount`/`dispose` |\n| [`src/sessions.ts`](src/sessions.ts) + [`src/workspaces.ts`](src/workspaces.ts) | `ISessions`/`IWorkspaces` 测试替身与 `FixtureSession` 行为桩 |\n| [`src/fixtures.ts`](src/fixtures.ts) | 普通 fixture 构造器：会话快照、workspace 列表状态 |\n| [`src/snapshot.ts`](src/snapshot.ts) | DOM 快照序列化器（类名哈希折叠、`<svg>` 指纹） |\n| [`src/remote.ts`](src/remote.ts) | 用于 host RPC 的 `TestRemote` 替身、`RemoteError` 值转出 |\n| [`src/translate.ts`](src/translate.ts) + [`src/locale-env.ts`](src/locale-env.ts) | 翻译与固定浏览器语言测试辅助 |\n| [`src/settings-scope.ts`](src/settings-scope.ts) | 带测试驱动发布与写入 spy 的 `stubSettingsScope` |\n| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件（无运行时不变式；所挂载的生产包拥有各自的不变式） |\n\n### 生命周期\n\n`create()` 构建全新上下文，挂载 slot 与会话注册表，安装渲染器，并提供 session/workspace 替身。`mount` 在启动 fiber 前对照上下文检查每个已声明注入，使缺失提供方自明报错而非永久挂起。`dispose()` 先卸载 React 树，再 dispose feature fiber、释放根注册、dispose 已铸 session scope 并清除持久化 store 状态；每个公共修改器都包裹在 act 中，因此测试无需自行处理 SlotCore 微任务批处理或 React `act`。\n\n</details>\n\n-----\n\n<a id=\"further-exploration\"></a>\n## 进一步探索\n\n当包级约定不够用时阅读以下页面。它们从测试台逐步进入它所挂载的生产机制以及使用它的测试。\n\n- [ui-session](../../client/ui-session/README.zh.md)——从 Controller 替身派生标准 Slot source 的生产适配器。\n- [UI slots 包](../../client/ui-slots/README.zh.md)——测试台挂载的 `SlotRegistry` 约定。\n- [UI renderer 包](../../client/ui-renderer/README.zh.md)——测试台安装的渲染器。\n- [测试策略](../../../docs/testing.zh.md)——覆盖层级与浏览器快照流水线。\n- [test-support 组地图](../README.zh.md)——兄弟 harness 与支持包。\n\n-----\n\n<a id=\"model-experience\"></a>\n## 模型体验\n\n无；本包是浏览器侧测试基础设施，无一物到达模型请求。\n\n#### KV Cache 影响\n\n无；本包既不组装也不发送提供方请求。\n\n## 已知限制与延期工作\n\n<a id=\"known-limitations-and-deferred-work\"></a>\n\n\n这些限制说明本测试台如何被消费。它们是当前包约束，不是任务积压。\n\n- **仅限 Vitest 与 jsdom**——所有消费方都是仓内、面向浏览器的 Vitest 套件。本包不是产品插件，也不是通用 Node 测试框架。\n- **Session、Conversation 与 Chat fixture 保持分离**——`sessionSnapshot` 只包含 Session Controller 状态，`conversationSnapshot` 包含 target-neutral Conversation 状态，`chatSnapshot` 包含 Chat target 状态。组装测试提供 Session event entry，而不是向 `SessionSnapshot` 添加 Conversation 或 Chat 字段。\n\n<a id=\"dev-note\"></a>\n### 开发备注\n\n<details>\n<summary>维护者的工作上下文——点击展开</summary>\n\n无。\n\n</details>\n","readmeFilename":"README.zh.md","_rev":"1-21091479af954bc35cfe7bbc028e512f"}