@deepseek-ai/dsh-client-test-runtime 0.0.1-rc.1 → 0.0.1-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +3 -3
- package/README.md +2 -2
- package/README.zh.md +2 -2
- package/lib/index.js +101 -22
- package/lib/invariant.js +1 -1
- package/lib/types/fixtures.d.ts +1 -1
- package/lib/types/index.d.ts +17 -15
- package/lib/types/remote.d.ts +49 -0
- package/lib/types/sessions.d.ts +4 -4
- package/lib/types/settings-scope.d.ts +2 -0
- package/lib/types/translate.d.ts +1 -1
- package/lib/types/workspaces.d.ts +6 -0
- package/package.json +15 -15
package/README.i18n.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
|
-
# pnpm run verify-translation-pairing --write packages/
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/test-support/client-runtime/README.md
|
|
5
|
+
README.md: 15646d05daae0a39a36639f335a71d6d208da13b
|
|
6
|
+
README.zh.md: 9624bc8ffc997ba786f3cb1bba670344d44ac60d
|
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
English | [中文](README.zh.md)
|
|
4
4
|
|
|
5
|
-
jsdom slot test runtime for client feature specs: a real Cordis `Context`, the production `
|
|
5
|
+
jsdom slot test runtime for client feature specs: a real Cordis `Context`, the production `SlotRegistry` and web-react renderer, assembled around typed session/workspace doubles. Feature suites exercise declaration, registration, scope, store, inject, rendering, updates, and disposal without hand-building the machinery per suite — and without a second implementation of any production logic.
|
|
6
6
|
|
|
7
|
-
The doubles implement the same outward faces features receive through ctx (`TestSessions implements ISessions`, `TestWorkspaces implements IWorkspaces`; each fixture session is a `FixtureSession implements SessionFace`; `stubSettingsScope` is a `SettingsScope` with test-driven publications and a write spy), so a production face change breaks the bench at compile time instead of silently drifting. Provide-bundle materialization runs the production `SessionProvideChannel` — the one implementation shared with `
|
|
7
|
+
The doubles implement the same outward faces features receive through ctx (`TestSessions implements ISessions`, `TestWorkspaces implements IWorkspaces`; each fixture session is a `FixtureSession implements SessionFace`; `stubSettingsScope` is a `SettingsScope` with test-driven publications and a write spy), so a production face change breaks the bench at compile time instead of silently drifting. Provide-bundle materialization runs the production `SessionProvideChannel` — the one implementation shared with `SessionRuntime`. Fixtures feed plain data: list rows, conversation snapshots (immer-patched via `updateSnapshot`), projection values, and `ISession`-typed behavior stubs that fail loud when a spec calls an unstubbed verb. The typed `provide()` constrains fakes for declared service names to `Partial` of that service's outward face.
|
|
8
8
|
|
|
9
9
|
Local DOM snapshots: `declare(children)` registers an auto frame whose per-key `<div data-slot>` wrappers are snapshot roots; `renderSlot(key, owner)` returns the slot-local view (container, scoped Testing Library queries, in-place `update(owner)`); a registered snapshot serializer folds CSS-module class hashes (`_frame_a1b2c3` → `frame`) to keep `.snap` files structural and collapses `<svg>` internals to a `data-content` fingerprint. Suites needing a custom page frame use `root.declare(children, Frame)` instead; `mount(plugin)` runs a real fiber with fail-loud service prechecks, and `dispose()` tears down views, feature fibers, minted scopes, and persisted store state on one axis.
|
|
10
10
|
|
package/README.zh.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 中文
|
|
4
4
|
|
|
5
|
-
面向客户端功能测试的 jsdom slot 测试运行时:真实 Cordis `Context`、生产 `
|
|
5
|
+
面向客户端功能测试的 jsdom slot 测试运行时:真实 Cordis `Context`、生产 `SlotRegistry` 与 web-react 渲染器,围绕带类型的 session/workspace 测试替身组装。功能套件无需逐套件手搭机器即可测遍声明、注册、scope、store、inject、渲染、更新与销毁——且不存在任何生产逻辑的第二份实现。
|
|
6
6
|
|
|
7
|
-
替身实现的正是功能通过 ctx 获得的对外接口(`TestSessions implements ISessions`、`TestWorkspaces implements IWorkspaces`;每个 fixture session 是 `FixtureSession implements SessionFace`;`stubSettingsScope` 是发布由测试驱动、带写入 spy 的 `SettingsScope`),生产面一旦改形,测试台在编译期即断,而非静默漂移。provide bundle 材料化直接运行生产 `SessionProvideChannel`——与 `
|
|
7
|
+
替身实现的正是功能通过 ctx 获得的对外接口(`TestSessions implements ISessions`、`TestWorkspaces implements IWorkspaces`;每个 fixture session 是 `FixtureSession implements SessionFace`;`stubSettingsScope` 是发布由测试驱动、带写入 spy 的 `SettingsScope`),生产面一旦改形,测试台在编译期即断,而非静默漂移。provide bundle 材料化直接运行生产 `SessionProvideChannel`——与 `SessionRuntime` 共用同一份实现。fixture 灌入的是普通数据:列表行、会话快照(经 `updateSnapshot` 以 immer 补丁改写)、projection 值,以及按 `ISession` 取型的行为桩——spec 调用未打桩的动词时报错自明。带类型的 `provide()` 将已声明服务名的 fake 约束为该服务对外面的 `Partial` 子集。
|
|
8
8
|
|
|
9
9
|
局部 DOM 快照:`declare(children)` 注册自动 frame,逐 key 的 `<div data-slot>` 包裹层即快照根;`renderSlot(key, owner)` 返回该 slot 的局部视图(container、限定范围的 Testing Library 查询、原位 `update(owner)`);注册的快照序列化器把 CSS-module 哈希类名折回语义名(`_frame_a1b2c3` → `frame`)保持 `.snap` 只含结构,并把 `<svg>` 内部折叠为 `data-content` 指纹。需要自定义页面 frame 的套件改用 `root.declare(children, Frame)`;`mount(plugin)` 在真实 fiber 上运行并对缺失服务先行报错;`dispose()` 沿单一轴拆除视图、feature fiber、已铸 scope 与持久化 store 状态。
|
|
10
10
|
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Context, Inject } from "@deepseek-ai/cordis";
|
|
2
2
|
import { Fragment, createElement, useSyncExternalStore } from "react";
|
|
3
3
|
import { act, render, within } from "@testing-library/react";
|
|
4
|
-
import { ConversationEventRegistry, ConversationViewRegistry, EMPTY_CHAT_SNAPSHOT, SessionProvideChannel,
|
|
4
|
+
import { ConversationEventRegistry, ConversationViewRegistry, EMPTY_CHAT_SNAPSHOT, EMPTY_CONVERSATION_VIEWS, SessionProvideChannel, SlotRegistry, createScope, createSnapshotStore, scopeOf } from "@deepseek-ai/dsh-client-runtime/client";
|
|
5
5
|
import { createSlotRenderer } from "@deepseek-ai/dsh-client-web-react";
|
|
6
6
|
import { afterEach, beforeEach, expect, vi } from "vitest";
|
|
7
7
|
import { SESSION_SEARCH_RESULT_LIMIT } from "@deepseek-ai/dsh-host-apiproxy/api";
|
|
@@ -90,6 +90,7 @@ function registerDomSnapshotSerializer() {
|
|
|
90
90
|
function conversationSnapshot(sessionId) {
|
|
91
91
|
return {
|
|
92
92
|
sessionId,
|
|
93
|
+
views: EMPTY_CONVERSATION_VIEWS,
|
|
93
94
|
chat: EMPTY_CHAT_SNAPSHOT,
|
|
94
95
|
nodes: [],
|
|
95
96
|
turnTimings: /* @__PURE__ */ new Map(),
|
|
@@ -112,7 +113,7 @@ function conversationSnapshot(sessionId) {
|
|
|
112
113
|
};
|
|
113
114
|
}
|
|
114
115
|
/**
|
|
115
|
-
* A ready workspace list with no workspaces (the shape
|
|
116
|
+
* A ready workspace list with no workspaces (the shape WorkspaceRuntime
|
|
116
117
|
* projects after both baselines land).
|
|
117
118
|
* @returns the initial state of the test workspaces store.
|
|
118
119
|
*/
|
|
@@ -261,9 +262,9 @@ var TestSessions = class {
|
|
|
261
262
|
/** The useSessions standard feed (list rows + current selection). */
|
|
262
263
|
list;
|
|
263
264
|
/**
|
|
264
|
-
* Atomic current-session provide projection (production
|
|
265
|
+
* Atomic current-session provide projection (production SessionRuntime
|
|
265
266
|
* mirror): selection changes and provider-roster changes publish through
|
|
266
|
-
* this one source — the member the
|
|
267
|
+
* this one source — the member the SlotRegistry host face hands the
|
|
267
268
|
* renderer's SessionProvider.
|
|
268
269
|
*/
|
|
269
270
|
currentProvideInfo;
|
|
@@ -289,6 +290,7 @@ var TestSessions = class {
|
|
|
289
290
|
current: void 0,
|
|
290
291
|
phase: "ready",
|
|
291
292
|
subagentsByParent: {},
|
|
293
|
+
jobsBySession: {},
|
|
292
294
|
currentAddress: void 0
|
|
293
295
|
});
|
|
294
296
|
this.channel = new SessionProvideChannel({
|
|
@@ -385,7 +387,7 @@ var TestSessions = class {
|
|
|
385
387
|
/**
|
|
386
388
|
* Remove a session: list row, scope fiber, and per-session store instances
|
|
387
389
|
* (with persisted state) die together — the same single lifecycle axis the
|
|
388
|
-
* production
|
|
390
|
+
* production SessionRuntime drives on session death, minus staging.
|
|
389
391
|
* @param id - session id.
|
|
390
392
|
*/
|
|
391
393
|
async remove(id) {
|
|
@@ -820,6 +822,18 @@ var TestWorkspaces = class {
|
|
|
820
822
|
await this.stubs.get("delete")?.(workspaceId);
|
|
821
823
|
}
|
|
822
824
|
/**
|
|
825
|
+
* Move a Workspace in display order (recorded; default no-op).
|
|
826
|
+
* @param workspaceId - Workspace to move.
|
|
827
|
+
* @param beforeWorkspaceId - Anchor; omitted appends.
|
|
828
|
+
*/
|
|
829
|
+
async insertBefore(workspaceId, beforeWorkspaceId) {
|
|
830
|
+
this.calls.push({
|
|
831
|
+
method: "insertBefore",
|
|
832
|
+
args: [workspaceId, beforeWorkspaceId]
|
|
833
|
+
});
|
|
834
|
+
await this.stubs.get("insertBefore")?.(workspaceId, beforeWorkspaceId);
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
823
837
|
* Move an accounted session (recorded). The default echoes a minimal view.
|
|
824
838
|
* @param workspaceId - target workspace.
|
|
825
839
|
* @param sessionId - session to move.
|
|
@@ -876,12 +890,15 @@ function stubSettingsScope() {
|
|
|
876
890
|
let snapshot = {
|
|
877
891
|
status: "loading",
|
|
878
892
|
value: void 0,
|
|
893
|
+
base: void 0,
|
|
894
|
+
user: void 0,
|
|
879
895
|
revision: void 0,
|
|
880
896
|
writable: false,
|
|
881
897
|
mode: "host"
|
|
882
898
|
};
|
|
883
899
|
const listeners = /* @__PURE__ */ new Set();
|
|
884
900
|
const set = vi.fn(() => Promise.resolve());
|
|
901
|
+
const unset = vi.fn(() => Promise.resolve());
|
|
885
902
|
return {
|
|
886
903
|
scope: {
|
|
887
904
|
getSnapshot: () => snapshot,
|
|
@@ -891,9 +908,11 @@ function stubSettingsScope() {
|
|
|
891
908
|
listeners.delete(listener);
|
|
892
909
|
};
|
|
893
910
|
},
|
|
894
|
-
set
|
|
911
|
+
set,
|
|
912
|
+
unset
|
|
895
913
|
},
|
|
896
914
|
set,
|
|
915
|
+
unset,
|
|
897
916
|
listenerCount: () => listeners.size,
|
|
898
917
|
publish: (next) => {
|
|
899
918
|
snapshot = {
|
|
@@ -905,10 +924,73 @@ function stubSettingsScope() {
|
|
|
905
924
|
};
|
|
906
925
|
}
|
|
907
926
|
//#endregion
|
|
927
|
+
//#region lib/types/remote.js
|
|
928
|
+
/**
|
|
929
|
+
* Remote service test double for the forwarded-event path. Feature specs need
|
|
930
|
+
* `ctx.remote.$on` to exist (their plugins inject `remote`) and need forwarded
|
|
931
|
+
* host events to reach those subscribers, but not the generated namespaces or
|
|
932
|
+
* the wire — so this double implements subscription and dispatch only.
|
|
933
|
+
*
|
|
934
|
+
* Dispatch is driven the same way production drives it: `client/runtime` owns the
|
|
935
|
+
* host frame sink and hands each decoded `host/remote-event` frame to
|
|
936
|
+
* `$dispatch`. A spec therefore exercises its refresh chains by calling
|
|
937
|
+
* `$dispatch(name, args)` on this double.
|
|
938
|
+
*
|
|
939
|
+
* `$mount` rejects: a spec that reaches a generated namespace through this
|
|
940
|
+
* double has outgrown it and needs the real Client Remote service.
|
|
941
|
+
*
|
|
942
|
+
* One deliberate asymmetry with production: a throwing listener propagates out
|
|
943
|
+
* of the emit instead of being contained and logged, so a spec cannot lean on
|
|
944
|
+
* this double for the containment guarantee `$on` documents — assert that
|
|
945
|
+
* against the real service.
|
|
946
|
+
*/
|
|
947
|
+
var TestRemote = class {
|
|
948
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
949
|
+
/**
|
|
950
|
+
* Register the double as `ctx.remote`.
|
|
951
|
+
* @param ctx - the spec's root Context.
|
|
952
|
+
*/
|
|
953
|
+
constructor(ctx) {
|
|
954
|
+
ctx.provide("remote", this);
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Deliver one forwarded host event to its subscribers, standing in for the
|
|
958
|
+
* carrier that owns the frame sink.
|
|
959
|
+
* @param event - forwarded host event name.
|
|
960
|
+
* @param args - the Host argument list, verbatim.
|
|
961
|
+
*/
|
|
962
|
+
$dispatch(event, args) {
|
|
963
|
+
const listeners = this.subscriptions.get(event);
|
|
964
|
+
if (listeners === void 0) return;
|
|
965
|
+
for (const listener of [...listeners]) listener(...args);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Subscribe to one forwarded host event.
|
|
969
|
+
* @param event - forwarded host event name.
|
|
970
|
+
* @param listener - receives the Host argument list verbatim.
|
|
971
|
+
* @returns disposer removing this subscription.
|
|
972
|
+
*/
|
|
973
|
+
$on(event, listener) {
|
|
974
|
+
const listeners = this.subscriptions.get(event) ?? /* @__PURE__ */ new Set();
|
|
975
|
+
this.subscriptions.set(event, listeners);
|
|
976
|
+
listeners.add(listener);
|
|
977
|
+
return () => {
|
|
978
|
+
listeners.delete(listener);
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Generated-namespace mount, unsupported by this double.
|
|
983
|
+
* @returns never; always rejects.
|
|
984
|
+
*/
|
|
985
|
+
$mount() {
|
|
986
|
+
return Promise.reject(/* @__PURE__ */ new Error("TestRemote: $mount needs the real Client Remote service"));
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
//#endregion
|
|
908
990
|
//#region lib/types/translate.js
|
|
909
991
|
/**
|
|
910
992
|
* Test double of the locale lookup chain: a translate stub over plain
|
|
911
|
-
* dictionaries, mirroring
|
|
993
|
+
* dictionaries, mirroring LocaleRuntime's resolution order (first dictionary
|
|
912
994
|
* that owns the key wins, then the key itself stays visible) and its
|
|
913
995
|
* `{name}` template interpolation. Specs stub the framework-injected `t`
|
|
914
996
|
* seat with `makeTranslate(zh, commonZh)` instead of re-implementing the
|
|
@@ -938,7 +1020,7 @@ function makeTranslate(...dicts) {
|
|
|
938
1020
|
//#region lib/types/locale-env.js
|
|
939
1021
|
/**
|
|
940
1022
|
* Browser-language pin for specs that assert localized copy. A fresh
|
|
941
|
-
*
|
|
1023
|
+
* LocaleRuntime with no stored preference opens in the language `navigator`
|
|
942
1024
|
* asks for, and jsdom reports the runner's own (`en-US`) — so a spec asserting
|
|
943
1025
|
* the product's Chinese copy states the browser it assumes instead of
|
|
944
1026
|
* inheriting the machine's.
|
|
@@ -971,7 +1053,7 @@ function usePinnedBrowserLanguages(primary, ...rest) {
|
|
|
971
1053
|
//#region lib/types/index.js
|
|
972
1054
|
/**
|
|
973
1055
|
* jsdom slot test runtime: a real small runtime — Cordis `Context`, the
|
|
974
|
-
* runtime `
|
|
1056
|
+
* runtime `SlotRegistry`, and the web-react renderer — assembled around
|
|
975
1057
|
* test-owned session/workspace doubles, so feature specs exercise
|
|
976
1058
|
* declaration, registration, scope, store, inject, rendering, updates, and
|
|
977
1059
|
* disposal without hand-building the machinery per suite.
|
|
@@ -1029,7 +1111,7 @@ var TestRoot = class {
|
|
|
1029
1111
|
stabilize;
|
|
1030
1112
|
disposeEntry;
|
|
1031
1113
|
/**
|
|
1032
|
-
* @param slots - the runtime
|
|
1114
|
+
* @param slots - the runtime SlotRegistry.
|
|
1033
1115
|
* @param stabilize - the owning runtime's act wrapper.
|
|
1034
1116
|
*/
|
|
1035
1117
|
constructor(slots, stabilize) {
|
|
@@ -1067,7 +1149,7 @@ var TestRoot = class {
|
|
|
1067
1149
|
var SlotTestRuntime = class SlotTestRuntime {
|
|
1068
1150
|
/** The runtime's Cordis root (escape hatch: extra services via `ctx.provide`, raw `ctx.plugin` mounts). */
|
|
1069
1151
|
ctx;
|
|
1070
|
-
/** The production
|
|
1152
|
+
/** The production SlotRegistry mounted on {@link SlotTestRuntime.ctx}. */
|
|
1071
1153
|
slots;
|
|
1072
1154
|
/** The test-owned 'root' occupant. */
|
|
1073
1155
|
root;
|
|
@@ -1103,14 +1185,14 @@ var SlotTestRuntime = class SlotTestRuntime {
|
|
|
1103
1185
|
} });
|
|
1104
1186
|
}
|
|
1105
1187
|
/**
|
|
1106
|
-
* Assemble a runtime: real Context, mounted
|
|
1188
|
+
* Assemble a runtime: real Context, mounted SlotRegistry, installed
|
|
1107
1189
|
* renderer, and the session/workspace doubles provided as services.
|
|
1108
1190
|
* @returns the ready runtime.
|
|
1109
1191
|
*/
|
|
1110
1192
|
static async create() {
|
|
1111
1193
|
registerDomSnapshotSerializer();
|
|
1112
1194
|
const ctx = new Context();
|
|
1113
|
-
await ctx.plugin(
|
|
1195
|
+
await ctx.plugin(SlotRegistry).await();
|
|
1114
1196
|
await ctx.plugin(ConversationEventRegistry).await();
|
|
1115
1197
|
await ctx.plugin(ConversationViewRegistry).await();
|
|
1116
1198
|
return new SlotTestRuntime(ctx, ctx.get("slots"));
|
|
@@ -1167,10 +1249,10 @@ var SlotTestRuntime = class SlotTestRuntime {
|
|
|
1167
1249
|
/**
|
|
1168
1250
|
* Declare child slots under an auto-generated root frame — the single-slot
|
|
1169
1251
|
* mounting path for local DOM snapshots. Each key later supplied through
|
|
1170
|
-
* {@link SlotTestRuntime.renderSlot} renders inside
|
|
1171
|
-
* `<div data-slot="<key>">`
|
|
1172
|
-
*
|
|
1173
|
-
* runtime.
|
|
1252
|
+
* {@link SlotTestRuntime.renderSlot} renders inside the renderer's own
|
|
1253
|
+
* `<div data-slot="<key>">` outlet anchor (the snapshot root — the frame
|
|
1254
|
+
* adds no wrapper of its own). Mutually exclusive with
|
|
1255
|
+
* {@link TestRoot.declare} ('root' is a single slot); one call per runtime.
|
|
1174
1256
|
* @param children - child-slot declaration table (same contract as TestRoot.declare).
|
|
1175
1257
|
* @returns completion of the act-wrapped registration.
|
|
1176
1258
|
*/
|
|
@@ -1179,10 +1261,7 @@ var SlotTestRuntime = class SlotTestRuntime {
|
|
|
1179
1261
|
const cell = this.ownerCell;
|
|
1180
1262
|
const AutoFrame = (props) => {
|
|
1181
1263
|
useSyncExternalStore(cell.subscribe, cell.getVersion);
|
|
1182
|
-
return createElement(Fragment, null, cell.entries().map(([key, owner]) => createElement(
|
|
1183
|
-
"data-slot": key,
|
|
1184
|
-
key
|
|
1185
|
-
}, props.renderSlot(key, owner))));
|
|
1264
|
+
return createElement(Fragment, null, cell.entries().map(([key, owner]) => createElement(Fragment, { key }, props.renderSlot(key, owner))));
|
|
1186
1265
|
};
|
|
1187
1266
|
await this.root.declare(children, AutoFrame);
|
|
1188
1267
|
}
|
|
@@ -1256,4 +1335,4 @@ var SlotTestRuntime = class SlotTestRuntime {
|
|
|
1256
1335
|
}
|
|
1257
1336
|
};
|
|
1258
1337
|
//#endregion
|
|
1259
|
-
export { FixtureSession, SlotTestRuntime, TestRoot, TestSessions, TestWorkspaces, conversationSnapshot, domSnapshotSerializer, makeTranslate, registerDomSnapshotSerializer, stubSettingsScope, usePinnedBrowserLanguages, workspaceListState };
|
|
1338
|
+
export { FixtureSession, SlotTestRuntime, TestRemote, TestRoot, TestSessions, TestWorkspaces, conversationSnapshot, domSnapshotSerializer, makeTranslate, registerDomSnapshotSerializer, stubSettingsScope, usePinnedBrowserLanguages, workspaceListState };
|
package/lib/invariant.js
CHANGED
|
@@ -10,7 +10,7 @@ const name = "client-test-runtime-invariant";
|
|
|
10
10
|
const inject = ["invariants"];
|
|
11
11
|
/**
|
|
12
12
|
* No runtime invariant: this test-support package owns no production event
|
|
13
|
-
* stream or mutable data — it assembles the runtime
|
|
13
|
+
* stream or mutable data — it assembles the runtime SlotRegistry and renderer
|
|
14
14
|
* (whose packages own their invariants) around test doubles; its own behavior
|
|
15
15
|
* is exercised by its package tests.
|
|
16
16
|
*/
|
package/lib/types/fixtures.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface SessionFixture {
|
|
|
38
38
|
*/
|
|
39
39
|
export declare function conversationSnapshot(sessionId: SessionId): ConversationSnapshot;
|
|
40
40
|
/**
|
|
41
|
-
* A ready workspace list with no workspaces (the shape
|
|
41
|
+
* A ready workspace list with no workspaces (the shape WorkspaceRuntime
|
|
42
42
|
* projects after both baselines land).
|
|
43
43
|
* @returns the initial state of the test workspaces store.
|
|
44
44
|
*/
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* jsdom slot test runtime: a real small runtime — Cordis `Context`, the
|
|
3
|
-
* runtime `
|
|
3
|
+
* runtime `SlotRegistry`, and the web-react renderer — assembled around
|
|
4
4
|
* test-owned session/workspace doubles, so feature specs exercise
|
|
5
5
|
* declaration, registration, scope, store, inject, rendering, updates, and
|
|
6
6
|
* disposal without hand-building the machinery per suite.
|
|
@@ -15,7 +15,7 @@ import type { Fiber, Plugin } from '@deepseek-ai/cordis';
|
|
|
15
15
|
import type { RenderResult } from '@testing-library/react';
|
|
16
16
|
import type { queries } from '@testing-library/dom';
|
|
17
17
|
import type { BoundFunctions } from '@testing-library/dom';
|
|
18
|
-
import {
|
|
18
|
+
import { SlotRegistry } from '@deepseek-ai/dsh-client-runtime/client';
|
|
19
19
|
import type { ChildrenDecl, ComposedProps, OwnerOf, SlotComponent, SlotMap, StoreInstanceLike } from '@deepseek-ai/dsh-client-ui-slots';
|
|
20
20
|
import { TestSessions } from './sessions.ts';
|
|
21
21
|
import { TestWorkspaces } from './workspaces.ts';
|
|
@@ -25,18 +25,20 @@ export { FixtureSession, TestSessions } from './sessions.ts';
|
|
|
25
25
|
export { stubSettingsScope } from './settings-scope.ts';
|
|
26
26
|
export type { StubSettingsScope } from './settings-scope.ts';
|
|
27
27
|
export { TestWorkspaces } from './workspaces.ts';
|
|
28
|
+
export { TestRemote } from './remote.ts';
|
|
28
29
|
export { conversationSnapshot, workspaceListState } from './fixtures.ts';
|
|
29
30
|
export type { SessionBehaviorOverrides, SessionFixture, Stabilizer } from './fixtures.ts';
|
|
30
31
|
export { makeTranslate } from './translate.ts';
|
|
31
32
|
export { usePinnedBrowserLanguages } from './locale-env.ts';
|
|
32
33
|
/**
|
|
33
34
|
* One rendered slot's local view, from {@link SlotTestRuntime.renderSlot}:
|
|
34
|
-
* the `data-slot`
|
|
35
|
-
* .toMatchSnapshot()` captures exactly this slot's
|
|
36
|
-
* queries are bound inside it, and `update`
|
|
35
|
+
* the renderer's own `[data-slot]` outlet anchor is the snapshot root
|
|
36
|
+
* (`expect(view.container).toMatchSnapshot()` captures exactly this slot's
|
|
37
|
+
* output), Testing Library queries are bound inside it, and `update`
|
|
38
|
+
* re-renders with new owner props.
|
|
37
39
|
*/
|
|
38
40
|
export interface SlotView<K extends keyof SlotMap & string> {
|
|
39
|
-
/** The `<div data-slot="<key>">`
|
|
41
|
+
/** The renderer's `<div data-slot="<key>">` anchor around the slot's rendered output. */
|
|
40
42
|
readonly container: HTMLElement;
|
|
41
43
|
/** Testing Library queries scoped to {@link SlotView.container}. */
|
|
42
44
|
readonly view: BoundFunctions<typeof queries>;
|
|
@@ -71,10 +73,10 @@ export declare class TestRoot {
|
|
|
71
73
|
private readonly stabilize;
|
|
72
74
|
private disposeEntry;
|
|
73
75
|
/**
|
|
74
|
-
* @param slots - the runtime
|
|
76
|
+
* @param slots - the runtime SlotRegistry.
|
|
75
77
|
* @param stabilize - the owning runtime's act wrapper.
|
|
76
78
|
*/
|
|
77
|
-
constructor(slots:
|
|
79
|
+
constructor(slots: SlotRegistry, stabilize: Stabilizer);
|
|
78
80
|
/**
|
|
79
81
|
* Register the root frame, declaring (and thereby claiming) the child
|
|
80
82
|
* slots. One declaration per runtime — a second call fails loud in the
|
|
@@ -96,8 +98,8 @@ export declare class TestRoot {
|
|
|
96
98
|
export declare class SlotTestRuntime {
|
|
97
99
|
/** The runtime's Cordis root (escape hatch: extra services via `ctx.provide`, raw `ctx.plugin` mounts). */
|
|
98
100
|
readonly ctx: Context;
|
|
99
|
-
/** The production
|
|
100
|
-
readonly slots:
|
|
101
|
+
/** The production SlotRegistry mounted on {@link SlotTestRuntime.ctx}. */
|
|
102
|
+
readonly slots: SlotRegistry;
|
|
101
103
|
/** The test-owned 'root' occupant. */
|
|
102
104
|
readonly root: TestRoot;
|
|
103
105
|
/** Sessions double (list/current observable, cells, scopes, behavior faces). */
|
|
@@ -115,7 +117,7 @@ export declare class SlotTestRuntime {
|
|
|
115
117
|
private autoRootView;
|
|
116
118
|
private constructor();
|
|
117
119
|
/**
|
|
118
|
-
* Assemble a runtime: real Context, mounted
|
|
120
|
+
* Assemble a runtime: real Context, mounted SlotRegistry, installed
|
|
119
121
|
* renderer, and the session/workspace doubles provided as services.
|
|
120
122
|
* @returns the ready runtime.
|
|
121
123
|
*/
|
|
@@ -148,10 +150,10 @@ export declare class SlotTestRuntime {
|
|
|
148
150
|
/**
|
|
149
151
|
* Declare child slots under an auto-generated root frame — the single-slot
|
|
150
152
|
* mounting path for local DOM snapshots. Each key later supplied through
|
|
151
|
-
* {@link SlotTestRuntime.renderSlot} renders inside
|
|
152
|
-
* `<div data-slot="<key>">`
|
|
153
|
-
*
|
|
154
|
-
* runtime.
|
|
153
|
+
* {@link SlotTestRuntime.renderSlot} renders inside the renderer's own
|
|
154
|
+
* `<div data-slot="<key>">` outlet anchor (the snapshot root — the frame
|
|
155
|
+
* adds no wrapper of its own). Mutually exclusive with
|
|
156
|
+
* {@link TestRoot.declare} ('root' is a single slot); one call per runtime.
|
|
155
157
|
* @param children - child-slot declaration table (same contract as TestRoot.declare).
|
|
156
158
|
* @returns completion of the act-wrapped registration.
|
|
157
159
|
*/
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Test-owned Remote face: `$on` subscriptions driven by the internal forwarded-event plumbing. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
/**
|
|
4
|
+
* Remote service test double for the forwarded-event path. Feature specs need
|
|
5
|
+
* `ctx.remote.$on` to exist (their plugins inject `remote`) and need forwarded
|
|
6
|
+
* host events to reach those subscribers, but not the generated namespaces or
|
|
7
|
+
* the wire — so this double implements subscription and dispatch only.
|
|
8
|
+
*
|
|
9
|
+
* Dispatch is driven the same way production drives it: `client/runtime` owns the
|
|
10
|
+
* host frame sink and hands each decoded `host/remote-event` frame to
|
|
11
|
+
* `$dispatch`. A spec therefore exercises its refresh chains by calling
|
|
12
|
+
* `$dispatch(name, args)` on this double.
|
|
13
|
+
*
|
|
14
|
+
* `$mount` rejects: a spec that reaches a generated namespace through this
|
|
15
|
+
* double has outgrown it and needs the real Client Remote service.
|
|
16
|
+
*
|
|
17
|
+
* One deliberate asymmetry with production: a throwing listener propagates out
|
|
18
|
+
* of the emit instead of being contained and logged, so a spec cannot lean on
|
|
19
|
+
* this double for the containment guarantee `$on` documents — assert that
|
|
20
|
+
* against the real service.
|
|
21
|
+
*/
|
|
22
|
+
export declare class TestRemote {
|
|
23
|
+
private readonly subscriptions;
|
|
24
|
+
/**
|
|
25
|
+
* Register the double as `ctx.remote`.
|
|
26
|
+
* @param ctx - the spec's root Context.
|
|
27
|
+
*/
|
|
28
|
+
constructor(ctx: Context);
|
|
29
|
+
/**
|
|
30
|
+
* Deliver one forwarded host event to its subscribers, standing in for the
|
|
31
|
+
* carrier that owns the frame sink.
|
|
32
|
+
* @param event - forwarded host event name.
|
|
33
|
+
* @param args - the Host argument list, verbatim.
|
|
34
|
+
*/
|
|
35
|
+
$dispatch(event: string, args: readonly unknown[]): void;
|
|
36
|
+
/**
|
|
37
|
+
* Subscribe to one forwarded host event.
|
|
38
|
+
* @param event - forwarded host event name.
|
|
39
|
+
* @param listener - receives the Host argument list verbatim.
|
|
40
|
+
* @returns disposer removing this subscription.
|
|
41
|
+
*/
|
|
42
|
+
$on(event: string, listener: (...args: never[]) => void): () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Generated-namespace mount, unsupported by this double.
|
|
45
|
+
* @returns never; always rejects.
|
|
46
|
+
*/
|
|
47
|
+
$mount(): Promise<() => Promise<void>>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=remote.d.ts.map
|
package/lib/types/sessions.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Test-owned sessions face: the
|
|
1
|
+
/** Test-owned sessions face: the SlotRegistry host contract over declarative fixtures. */
|
|
2
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
3
3
|
import type { AttachmentIdType } from '@deepseek-ai/dsh-attachment';
|
|
4
4
|
import type { AgentContext, ConversationSnapshot, ISessions, ProjectionsFace, SessionFace, SessionId, SessionListState, SessionProvideDescriptor, SessionSearchResultItem, SessionSummary, SnapshotStore, SubagentAddress } from '@deepseek-ai/dsh-client-runtime/client';
|
|
@@ -95,9 +95,9 @@ export declare class TestSessions implements ISessions {
|
|
|
95
95
|
/** The useSessions standard feed (list rows + current selection). */
|
|
96
96
|
readonly list: SnapshotStore<SessionListState>;
|
|
97
97
|
/**
|
|
98
|
-
* Atomic current-session provide projection (production
|
|
98
|
+
* Atomic current-session provide projection (production SessionRuntime
|
|
99
99
|
* mirror): selection changes and provider-roster changes publish through
|
|
100
|
-
* this one source — the member the
|
|
100
|
+
* this one source — the member the SlotRegistry host face hands the
|
|
101
101
|
* renderer's SessionProvider.
|
|
102
102
|
*/
|
|
103
103
|
readonly currentProvideInfo: HostObservable<SessionMaybeProvideInfo>;
|
|
@@ -149,7 +149,7 @@ export declare class TestSessions implements ISessions {
|
|
|
149
149
|
/**
|
|
150
150
|
* Remove a session: list row, scope fiber, and per-session store instances
|
|
151
151
|
* (with persisted state) die together — the same single lifecycle axis the
|
|
152
|
-
* production
|
|
152
|
+
* production SessionRuntime drives on session death, minus staging.
|
|
153
153
|
* @param id - session id.
|
|
154
154
|
*/
|
|
155
155
|
remove(id: string): Promise<void>;
|
|
@@ -7,6 +7,8 @@ export interface StubSettingsScope<T> {
|
|
|
7
7
|
scope: SettingsScope<T>;
|
|
8
8
|
/** Spy behind `scope.set`; resolves immediately. */
|
|
9
9
|
set: ReturnType<typeof vi.fn>;
|
|
10
|
+
/** Spy behind `scope.unset`; resolves immediately. */
|
|
11
|
+
unset: ReturnType<typeof vi.fn>;
|
|
10
12
|
/** @returns how many listeners are currently subscribed (disposal assertions). */
|
|
11
13
|
listenerCount(): number;
|
|
12
14
|
/**
|
package/lib/types/translate.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Test double of the locale lookup chain: a translate stub over plain
|
|
3
|
-
* dictionaries, mirroring
|
|
3
|
+
* dictionaries, mirroring LocaleRuntime's resolution order (first dictionary
|
|
4
4
|
* that owns the key wins, then the key itself stays visible) and its
|
|
5
5
|
* `{name}` template interpolation. Specs stub the framework-injected `t`
|
|
6
6
|
* seat with `makeTranslate(zh, commonZh)` instead of re-implementing the
|
|
@@ -91,6 +91,12 @@ export declare class TestWorkspaces implements IWorkspaces {
|
|
|
91
91
|
* @param workspaceId - target workspace.
|
|
92
92
|
*/
|
|
93
93
|
delete(workspaceId: WorkspaceId): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Move a Workspace in display order (recorded; default no-op).
|
|
96
|
+
* @param workspaceId - Workspace to move.
|
|
97
|
+
* @param beforeWorkspaceId - Anchor; omitted appends.
|
|
98
|
+
*/
|
|
99
|
+
insertBefore(workspaceId: WorkspaceId, beforeWorkspaceId?: WorkspaceId): Promise<void>;
|
|
94
100
|
/**
|
|
95
101
|
* Move an accounted session (recorded). The default echoes a minimal view.
|
|
96
102
|
* @param workspaceId - target workspace.
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-test-runtime",
|
|
3
|
-
"description": "jsdom slot test runtime: real Cordis Context +
|
|
4
|
-
"version": "0.0.1-rc.
|
|
3
|
+
"description": "jsdom slot test runtime: real Cordis Context + SlotRegistry + web-react renderer with test-owned session/workspace doubles for feature specs",
|
|
4
|
+
"version": "0.0.1-rc.5",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "restricted"
|
|
7
7
|
},
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
-
"directory": "packages/
|
|
11
|
+
"directory": "packages/test-support/client-runtime"
|
|
12
12
|
},
|
|
13
13
|
"type": "module",
|
|
14
14
|
"main": "lib/index.js",
|
|
@@ -34,24 +34,24 @@
|
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^18.2.0",
|
|
36
36
|
"react-dom": "^18.2.0",
|
|
37
|
-
"@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.
|
|
38
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.
|
|
39
|
-
"@deepseek-ai/dsh-client-web-react": "^0.0.1-rc.
|
|
40
|
-
"@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.
|
|
41
|
-
"@deepseek-ai/cordis": "^4.0.1-rc.
|
|
42
|
-
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.
|
|
37
|
+
"@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.5",
|
|
38
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.5",
|
|
39
|
+
"@deepseek-ai/dsh-client-web-react": "^0.0.1-rc.5",
|
|
40
|
+
"@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.5",
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.4",
|
|
42
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.5"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/react": "~18.3.1",
|
|
46
46
|
"@types/react-dom": "~18.3.0",
|
|
47
47
|
"react": "^18.2.0",
|
|
48
48
|
"react-dom": "^18.2.0",
|
|
49
|
-
"@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.
|
|
50
|
-
"@deepseek-ai/dsh-client-
|
|
51
|
-
"@deepseek-ai/dsh-
|
|
52
|
-
"@deepseek-ai/dsh-
|
|
53
|
-
"@deepseek-ai/
|
|
54
|
-
"@deepseek-ai/
|
|
49
|
+
"@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.5",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.5",
|
|
51
|
+
"@deepseek-ai/dsh-client-web-react": "^0.0.1-rc.5",
|
|
52
|
+
"@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.5",
|
|
53
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.5",
|
|
54
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.4"
|
|
55
55
|
},
|
|
56
56
|
"files": [
|
|
57
57
|
"lib/index.js",
|