dsh-structured 0.7.4 → 0.8.2
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/CHANGELOG.md +18 -0
- package/README.md +10 -4
- package/cordis.patch.yml +2 -0
- package/docs/ACCEPTANCE-0.8.1.md +3 -0
- package/docs/ACCEPTANCE-0.8.2.md +3 -0
- package/docs/CARD-SYSTEM.md +1 -0
- package/docs/METRICS.md +2 -1
- package/docs/TROUBLESHOOT-VISUAL.md +78 -78
- package/docs/VISUAL-ACCEPTANCE.md +4 -4
- package/lib/client.js +153 -110
- package/lib/index.js +34 -12
- package/package.json +12 -2
- package/scripts/desktop-check.mjs +6 -2
- package/src/client.js +153 -110
- package/src/index.js +34 -12
- package/test/client-cards.test.mjs +140 -0
- package/test/client-visual.test.mjs +163 -0
- package/test/structured.test.mjs +37 -2
|
@@ -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
|
+
})
|
package/test/structured.test.mjs
CHANGED
|
@@ -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,
|
|
@@ -76,12 +77,21 @@ function fakeCtx() {
|
|
|
76
77
|
return { ctx, sections, commands, warnings }
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
test('
|
|
80
|
+
test('内置规则覆盖首句结论、组件、清晰度三件事', () => {
|
|
80
81
|
assert.match(BUILTIN_STYLE_TEXT, /首句给结论/)
|
|
81
82
|
assert.match(BUILTIN_STYLE_TEXT, /清单或表格/)
|
|
82
|
-
assert.match(BUILTIN_STYLE_TEXT,
|
|
83
|
+
assert.match(BUILTIN_STYLE_TEXT, /标题即结论/)
|
|
84
|
+
assert.match(BUILTIN_STYLE_TEXT, /表格优先 ≤5 行/)
|
|
85
|
+
assert.match(BUILTIN_STYLE_TEXT, /不设硬性篇幅上限/)
|
|
86
|
+
assert.match(BUILTIN_STYLE_TEXT, /不为了字数删信息/)
|
|
87
|
+
assert.match(BUILTIN_STYLE_TEXT, /过程叙事/)
|
|
88
|
+
assert.match(BUILTIN_STYLE_TEXT, /推断/)
|
|
89
|
+
assert.match(BUILTIN_STYLE_TEXT, /短答不套卡/)
|
|
90
|
+
assert.doesNotMatch(BUILTIN_STYLE_TEXT, /500\s*字|≤\s*500|字数上限/)
|
|
83
91
|
assert.match(BUILTIN_STYLE_TEXT, /可复制内容用代码块/)
|
|
84
92
|
assert.match(TAIL_REMINDER_TEXT, /加粗标题/)
|
|
93
|
+
assert.match(TAIL_REMINDER_TEXT, /清晰优先/)
|
|
94
|
+
assert.doesNotMatch(TAIL_REMINDER_TEXT, /500\s*字|≤\s*500|字数上限/)
|
|
85
95
|
assert.match(BUILTIN_STYLE_TEXT, /判断/)
|
|
86
96
|
assert.match(BUILTIN_STYLE_TEXT, /指标/)
|
|
87
97
|
assert.match(BUILTIN_STYLE_TEXT, /风险/)
|
|
@@ -138,6 +148,22 @@ test('statusText 显示 GenUI 联动状态', () => {
|
|
|
138
148
|
}
|
|
139
149
|
})
|
|
140
150
|
|
|
151
|
+
test('Config:visual 默认开启,且可显式关闭', () => {
|
|
152
|
+
const value = (x) => (x && typeof x.get === 'function' ? x.get() : x)
|
|
153
|
+
const defaults = Config({})
|
|
154
|
+
assert.equal(value(defaults.visual), true)
|
|
155
|
+
assert.equal(value(defaults.defaultEnabled), true)
|
|
156
|
+
assert.equal(value(defaults.tailReminder), true)
|
|
157
|
+
assert.equal(value(defaults.includeSubagents), false)
|
|
158
|
+
assert.equal(value(defaults.genui), 'auto')
|
|
159
|
+
assert.equal(value(Config({ visual: false }).visual), false)
|
|
160
|
+
assert.equal(value(Config({ defaultEnabled: false }).defaultEnabled), false)
|
|
161
|
+
assert.match(
|
|
162
|
+
statusText({ sessionId: 'session-a', sessions: {}, defaultEnabled: true, visual: false }),
|
|
163
|
+
/视觉层:已关闭/,
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
|
|
141
167
|
test('无状态文件时按默认值开启', () => {
|
|
142
168
|
const box = isolate()
|
|
143
169
|
try {
|
|
@@ -498,6 +524,15 @@ test('client:卡片类型与归属标记存在', () => {
|
|
|
498
524
|
}
|
|
499
525
|
})
|
|
500
526
|
|
|
527
|
+
test('client:视觉层可由 configForms 的 visual 设置关闭', () => {
|
|
528
|
+
const client = readFileSync('src/client.js', 'utf8')
|
|
529
|
+
assert.match(client, /configForms/)
|
|
530
|
+
assert.match(client, /getSnapshot/)
|
|
531
|
+
assert.match(client, /visual !== false/)
|
|
532
|
+
assert.match(client, /applyVisual/)
|
|
533
|
+
assert.doesNotMatch(client, /state\.json/)
|
|
534
|
+
})
|
|
535
|
+
|
|
501
536
|
test('构建产物 lib/index.js 与 lib/client.js 均 = banner + src', () => {
|
|
502
537
|
const hostSrc = readFileSync('src/index.js', 'utf8')
|
|
503
538
|
const hostLib = readFileSync('lib/index.js', 'utf8')
|