dsh-structured 0.8.1 → 0.8.3

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.
@@ -0,0 +1,163 @@
1
+ import assert from 'node:assert/strict'
2
+ import { readFileSync } from 'node:fs'
3
+ import test from 'node:test'
4
+ import vm from 'node:vm'
5
+
6
+ function makeDom() {
7
+ const styles = new Map()
8
+ const attrs = new Map()
9
+ const element = () => ({
10
+ children: [],
11
+ appendChild(child) {
12
+ this.children.push(child)
13
+ },
14
+ remove() {},
15
+ setAttribute(name, value) {
16
+ this[name] = value
17
+ },
18
+ removeAttribute(name) {
19
+ delete this[name]
20
+ },
21
+ querySelectorAll() {
22
+ return []
23
+ },
24
+ closest() {
25
+ return null
26
+ },
27
+ tagName: 'P',
28
+ })
29
+ const document = {
30
+ body: element(),
31
+ documentElement: {
32
+ setAttribute(name, value) {
33
+ attrs.set(name, value)
34
+ },
35
+ removeAttribute(name) {
36
+ attrs.delete(name)
37
+ },
38
+ getAttribute(name) {
39
+ return attrs.get(name)
40
+ },
41
+ },
42
+ head: {
43
+ children: [],
44
+ appendChild(style) {
45
+ this.children.push(style)
46
+ styles.set(style.id, style)
47
+ },
48
+ },
49
+ createElement() {
50
+ const style = element()
51
+ style.textContent = ''
52
+ style.id = ''
53
+ style.remove = () => {
54
+ styles.delete(style.id)
55
+ }
56
+ return style
57
+ },
58
+ getElementById(id) {
59
+ return styles.get(id) ?? null
60
+ },
61
+ querySelectorAll() {
62
+ return []
63
+ },
64
+ }
65
+ return { document, styles, attrs }
66
+ }
67
+
68
+ function loadClient(dom) {
69
+ const code = readFileSync(new URL('../src/client.js', import.meta.url), 'utf8')
70
+ let module = null
71
+ const window = {
72
+ __ModuleLoader__: {
73
+ load(definition) {
74
+ module = definition.factory()
75
+ },
76
+ },
77
+ }
78
+ const context = {
79
+ window,
80
+ document: dom.document,
81
+ MutationObserver: class {
82
+ observe() {}
83
+ disconnect() {}
84
+ },
85
+ requestAnimationFrame: (fn) => fn(),
86
+ cancelAnimationFrame: () => {},
87
+ setTimeout: (fn) => fn(),
88
+ clearTimeout: () => {},
89
+ console: { info() {}, warn() {}, error() {} },
90
+ }
91
+ vm.runInNewContext(code, context, { filename: 'src/client.js' })
92
+ assert.ok(module, 'client module should register')
93
+ return module
94
+ }
95
+
96
+ function makeContext(form) {
97
+ return {
98
+ inject(services, callback) {
99
+ callback({
100
+ configForms: {
101
+ get(id) {
102
+ return id === 'dsh-structured' ? form : undefined
103
+ },
104
+ },
105
+ effect(fn) {
106
+ return fn()
107
+ },
108
+ })
109
+ },
110
+ effect(fn) {
111
+ return fn()
112
+ },
113
+ }
114
+ }
115
+
116
+ test('client:visual=false 停用视觉层,visual=true 恢复', () => {
117
+ const dom = makeDom()
118
+ const form = {
119
+ value: { visual: false },
120
+ listeners: new Set(),
121
+ getSnapshot() {
122
+ return { value: this.value }
123
+ },
124
+ subscribe(listener) {
125
+ this.listeners.add(listener)
126
+ return () => this.listeners.delete(listener)
127
+ },
128
+ }
129
+ const module = loadClient(dom)
130
+ const dispose = module.apply(makeContext(form))
131
+
132
+ assert.equal(dom.styles.has('dsh-structured-skin-style'), false)
133
+ assert.equal(dom.attrs.has('data-dsh-structured'), false)
134
+
135
+ form.value = { visual: true }
136
+ for (const listener of form.listeners) listener()
137
+ assert.equal(dom.styles.has('dsh-structured-skin-style'), true)
138
+ assert.equal(dom.attrs.get('data-dsh-structured'), 'active')
139
+
140
+ form.value = { visual: false }
141
+ for (const listener of form.listeners) listener()
142
+ assert.equal(dom.styles.has('dsh-structured-skin-style'), false)
143
+ assert.equal(dom.attrs.has('data-dsh-structured'), false)
144
+
145
+ dispose()
146
+ })
147
+
148
+ test('client:configForms 不可用时视觉层保持默认开启', () => {
149
+ const dom = makeDom()
150
+ const module = loadClient(dom)
151
+ const ctx = {
152
+ inject(services, callback) {
153
+ // 模拟服务缺失:不调用 callback
154
+ },
155
+ effect(fn) {
156
+ return fn()
157
+ },
158
+ }
159
+ const dispose = module.apply(ctx)
160
+ assert.equal(dom.styles.has('dsh-structured-skin-style'), true)
161
+ assert.equal(dom.attrs.get('data-dsh-structured'), 'active')
162
+ dispose()
163
+ })
@@ -6,6 +6,7 @@ import test from 'node:test'
6
6
 
7
7
  import {
8
8
  BUILTIN_STYLE_TEXT,
9
+ Config,
9
10
  GENUI_ROUTING_TEXT,
10
11
  MAX_SESSION_ENTRIES,
11
12
  TAIL_REMINDER_TEXT,
@@ -97,6 +98,10 @@ test('内置规则覆盖首句结论、组件、清晰度三件事', () => {
97
98
  assert.match(BUILTIN_STYLE_TEXT, /下一步/)
98
99
  assert.match(BUILTIN_STYLE_TEXT, /✅ 完成/)
99
100
  assert.match(GENUI_ROUTING_TEXT, /dsh-ui/)
101
+ assert.match(TAIL_REMINDER_TEXT, /即使后文要用组件也一样/)
102
+ assert.match(TAIL_REMINDER_TEXT, /不得用组件充当首卡/)
103
+ assert.match(GENUI_ROUTING_TEXT, /不得用它的 title 充当首卡/)
104
+ assert.match(GENUI_ROUTING_TEXT, /以本节为准/)
100
105
  })
101
106
 
102
107
  test('detectGenui:profile 清单不再作为 auto 依据,config 可强制', () => {
@@ -147,6 +152,22 @@ test('statusText 显示 GenUI 联动状态', () => {
147
152
  }
148
153
  })
149
154
 
155
+ test('Config:visual 默认开启,且可显式关闭', () => {
156
+ const value = (x) => (x && typeof x.get === 'function' ? x.get() : x)
157
+ const defaults = Config({})
158
+ assert.equal(value(defaults.visual), true)
159
+ assert.equal(value(defaults.defaultEnabled), true)
160
+ assert.equal(value(defaults.tailReminder), true)
161
+ assert.equal(value(defaults.includeSubagents), false)
162
+ assert.equal(value(defaults.genui), 'auto')
163
+ assert.equal(value(Config({ visual: false }).visual), false)
164
+ assert.equal(value(Config({ defaultEnabled: false }).defaultEnabled), false)
165
+ assert.match(
166
+ statusText({ sessionId: 'session-a', sessions: {}, defaultEnabled: true, visual: false }),
167
+ /视觉层:已关闭/,
168
+ )
169
+ })
170
+
150
171
  test('无状态文件时按默认值开启', () => {
151
172
  const box = isolate()
152
173
  try {
@@ -507,6 +528,15 @@ test('client:卡片类型与归属标记存在', () => {
507
528
  }
508
529
  })
509
530
 
531
+ test('client:视觉层可由 configForms 的 visual 设置关闭', () => {
532
+ const client = readFileSync('src/client.js', 'utf8')
533
+ assert.match(client, /configForms/)
534
+ assert.match(client, /getSnapshot/)
535
+ assert.match(client, /visual !== false/)
536
+ assert.match(client, /applyVisual/)
537
+ assert.doesNotMatch(client, /state\.json/)
538
+ })
539
+
510
540
  test('构建产物 lib/index.js 与 lib/client.js 均 = banner + src', () => {
511
541
  const hostSrc = readFileSync('src/index.js', 'utf8')
512
542
  const hostLib = readFileSync('lib/index.js', 'utf8')