dsh-structured 0.9.4 → 0.9.9

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.
@@ -1,140 +1,145 @@
1
- import test from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { readFileSync } from 'node:fs'
4
- import vm from 'node:vm'
5
- import { parseHTML } from 'linkedom'
6
-
7
- const clientSource = readFileSync('src/client.js', 'utf8')
8
-
9
- function render(html) {
10
- const { window, document } = parseHTML(`<!doctype html><html><head></head><body>${html}</body></html>`)
11
- let definition
12
- const sandbox = {
13
- window: {
14
- __ModuleLoader__: {
15
- load(value) {
16
- definition = value
17
- },
18
- },
19
- },
20
- document,
21
- MutationObserver: class {
22
- observe() {}
23
- disconnect() {}
24
- },
25
- requestAnimationFrame(fn) {
26
- fn()
27
- return 1
28
- },
29
- cancelAnimationFrame() {},
30
- setTimeout,
31
- clearTimeout,
32
- console,
33
- }
34
- sandbox.globalThis = sandbox
35
- vm.runInNewContext(clientSource, sandbox)
36
- assert.ok(definition, 'client module definition captured')
37
- const plugin = definition.factory()
38
- plugin.apply({
39
- inject(_services, callback) {
40
- callback({ configForms: { get: () => null } })
41
- },
42
- effect() {},
43
- })
44
- return document
45
- }
46
-
47
- function block(document, id) {
48
- const element = document.querySelector(`#${id}`)
49
- assert.ok(element, `missing #${id}`)
50
- return element
51
- }
52
-
53
- function assertPlain(document, id) {
54
- const element = block(document, id)
55
- assert.equal(element.getAttribute('data-dsh-structured-card'), null, `${id} card`)
56
- assert.equal(element.getAttribute('data-dsh-structured-card-type'), null, `${id} type`)
57
- assert.equal(element.getAttribute('data-dsh-structured-tone'), null, `${id} tone`)
58
- assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
59
- }
60
-
61
- function assertCard(document, id, type, tone) {
62
- const element = block(document, id)
63
- assert.equal(element.getAttribute('data-dsh-structured-card'), '1', `${id} card`)
64
- assert.equal(element.getAttribute('data-dsh-structured-card-type'), type, `${id} type`)
65
- assert.equal(element.getAttribute('data-dsh-structured-tone'), tone, `${id} tone`)
66
- assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
67
- }
68
-
69
- test('严格卡片:复合标签、普通引用不升级,完整标签才成卡', () => {
70
- const document = render(`
71
- <div class="_markdown_q2"><blockquote id="q2"><p><strong>风险提示</strong><br>影响:宿主升级后可能失效。</p></blockquote></div>
72
- <div class="_markdown_test">
73
- <blockquote id="q1"><p>风险提示:宿主升级后可能失效。</p></blockquote>
74
- <blockquote id="q3"><p>结论:这是用户引用的原文。</p></blockquote>
75
- <blockquote id="q4"><p>这是普通引用,没有加粗标题。</p></blockquote>
76
- <blockquote id="q9"><p><strong>判断依据</strong><br>不应成卡。</p></blockquote>
77
- </div>
78
- <div class="_markdown_q10"><blockquote id="q10"><p>前缀 <strong>标题</strong></p></blockquote></div>
79
- <div class="_markdown_q11"><blockquote id="q11"><p> <strong>标题</strong></p></blockquote></div>
80
- <div class="_markdown_q18"><blockquote id="q18"><p>前缀 <strong>标题</strong>尾正文</p></blockquote></div>
81
- <div class="_markdown_q16"><blockquote id="q16"><p><strong>结论性说明</strong><br>不应成卡。</p></blockquote></div>
82
- <div class="_markdown_q17"><blockquote id="q17"><p><strong>指标数据</strong><br>不应成卡。</p></blockquote></div>
83
- <div class="_markdown_test2">
84
- <blockquote id="q5"><p><strong>Phase 1 完成</strong><br>28/28 通过。</p></blockquote>
85
- <blockquote id="q5b"><p><strong>Phase 2 完成</strong><br>不应升级为 statement。</p></blockquote>
86
- <blockquote id="q6"><p><strong>风险</strong><br>影响:宿主升级后可能失效。</p></blockquote>
87
- <blockquote id="q7"><p><strong>下一步</strong><br>1. 安装测试 profile。</p></blockquote>
88
- <blockquote id="q8"><p><strong>验收</strong><br>✅ 3/3</p></blockquote>
89
- <blockquote id="q12"><p><strong>结论</strong><br>结论卡。</p></blockquote>
90
- <blockquote id="q13"><p><strong>决策</strong><br>决策卡。</p></blockquote>
91
- <blockquote id="q14"><p><strong>指标</strong><br>指标卡。</p></blockquote>
92
- <blockquote id="q15"><p><strong>重点</strong><br>重点卡。</p></blockquote>
93
- <div data-genui="1">
94
- <blockquote id="gen"><p><strong>风险</strong><br>GenUI 内不处理。</p></blockquote>
95
- </div>
96
- </div>
97
- <div class="_markdown_test5">
98
- <div data-genui="1">
99
- <blockquote id="genfirst"><p><strong>风险</strong><br>GenUI 内不处理,也不应阻断真实首块。</p></blockquote>
100
- </div>
101
- <blockquote id="real"><p><strong>Phase 9 完成</strong><br>真实首块仍应升级 statement。</p></blockquote>
102
- </div>
103
- `)
104
-
105
- // 反例:前缀/复合词、普通引用、strong 前有正文或空白
106
- assertPlain(document, 'q1')
107
- assertPlain(document, 'q2')
108
- assertPlain(document, 'q3')
109
- assertPlain(document, 'q4')
110
- assertPlain(document, 'q9')
111
- assertPlain(document, 'q10')
112
- assertPlain(document, 'q11')
113
- assertPlain(document, 'q18')
114
- assertPlain(document, 'q16')
115
- assertPlain(document, 'q17')
116
-
117
- // 正例:全部卡型 + 仅第一块可升级 + GenUI 前置不阻断真实首块
118
- assertCard(document, 'q5', 'statement', 'info')
119
- assertCard(document, 'q6', 'risk', 'warn')
120
- assertCard(document, 'q7', 'next', 'info')
121
- assertCard(document, 'q8', 'accept', 'success')
122
- assertCard(document, 'q12', 'conclusion', 'info')
123
- assertCard(document, 'q13', 'decision', 'info')
124
- assertCard(document, 'q14', 'metric', 'info')
125
- assertCard(document, 'q15', 'key', 'info')
126
- assertCard(document, 'real', 'statement', 'info')
127
- assertPlain(document, 'q5b')
128
-
129
- // GenUI 区域跳过
130
- assertPlain(document, 'gen')
131
- assertPlain(document, 'genfirst')
132
- })
133
-
134
- test('严格卡片:不再有普通首引用 lead 与文本前缀 tone 逻辑', () => {
135
- assert.doesNotMatch(clientSource, /isFirstBlockquote/)
136
- assert.doesNotMatch(clientSource, /toneFor/)
137
- assert.doesNotMatch(clientSource, /setAttribute\(LEAD_ATTR,\s*'lead'\)/)
138
- assert.match(clientSource, /CARD_TYPES = new Map/)
139
- assert.match(clientSource, /isStatementCard/)
140
- })
1
+ import test from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { readFileSync } from 'node:fs'
4
+ import vm from 'node:vm'
5
+ import { parseHTML } from 'linkedom'
6
+
7
+ const clientSource = readFileSync('src/client.js', 'utf8')
8
+
9
+ function render(html) {
10
+ const { window, document } = parseHTML(`<!doctype html><html><head></head><body>${html}</body></html>`)
11
+ let definition
12
+ const sandbox = {
13
+ window: {
14
+ __ModuleLoader__: {
15
+ load(value) {
16
+ definition = value
17
+ },
18
+ },
19
+ },
20
+ document,
21
+ MutationObserver: class {
22
+ observe() {}
23
+ disconnect() {}
24
+ },
25
+ requestAnimationFrame(fn) {
26
+ fn()
27
+ return 1
28
+ },
29
+ cancelAnimationFrame() {},
30
+ setTimeout,
31
+ clearTimeout,
32
+ console,
33
+ }
34
+ sandbox.globalThis = sandbox
35
+ vm.runInNewContext(clientSource, sandbox)
36
+ assert.ok(definition, 'client module definition captured')
37
+ const plugin = definition.factory()
38
+ plugin.apply({
39
+ inject(_services, callback) {
40
+ callback({ configForms: { get: () => null } })
41
+ },
42
+ effect() {},
43
+ })
44
+ return document
45
+ }
46
+
47
+ function block(document, id) {
48
+ const element = document.querySelector(`#${id}`)
49
+ assert.ok(element, `missing #${id}`)
50
+ return element
51
+ }
52
+
53
+ function assertPlain(document, id) {
54
+ const element = block(document, id)
55
+ assert.equal(element.getAttribute('data-dsh-structured-card'), null, `${id} card`)
56
+ assert.equal(element.getAttribute('data-dsh-structured-card-type'), null, `${id} type`)
57
+ assert.equal(element.getAttribute('data-dsh-structured-tone'), null, `${id} tone`)
58
+ assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
59
+ }
60
+
61
+ function assertCard(document, id, type, tone) {
62
+ const element = block(document, id)
63
+ assert.equal(element.getAttribute('data-dsh-structured-card'), '1', `${id} card`)
64
+ assert.equal(element.getAttribute('data-dsh-structured-card-type'), type, `${id} type`)
65
+ assert.equal(element.getAttribute('data-dsh-structured-tone'), tone, `${id} tone`)
66
+ assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
67
+ }
68
+
69
+ test('0.9.7 client 不改写消息里的 $...$', () => {
70
+ const document = render('<p id="dollar">成本 $5,公式 $x^2$</p>')
71
+ assert.equal(document.getElementById('dollar').textContent, '成本 $5,公式 $x^2$')
72
+ })
73
+
74
+ test('严格卡片:复合标签、普通引用不升级,完整标签才成卡', () => {
75
+ const document = render(`
76
+ <div class="_markdown_q2"><blockquote id="q2"><p><strong>风险提示</strong><br>影响:宿主升级后可能失效。</p></blockquote></div>
77
+ <div class="_markdown_test">
78
+ <blockquote id="q1"><p>风险提示:宿主升级后可能失效。</p></blockquote>
79
+ <blockquote id="q3"><p>结论:这是用户引用的原文。</p></blockquote>
80
+ <blockquote id="q4"><p>这是普通引用,没有加粗标题。</p></blockquote>
81
+ <blockquote id="q9"><p><strong>判断依据</strong><br>不应成卡。</p></blockquote>
82
+ </div>
83
+ <div class="_markdown_q10"><blockquote id="q10"><p>前缀 <strong>标题</strong></p></blockquote></div>
84
+ <div class="_markdown_q11"><blockquote id="q11"><p> <strong>标题</strong></p></blockquote></div>
85
+ <div class="_markdown_q18"><blockquote id="q18"><p>前缀 <strong>标题</strong>尾正文</p></blockquote></div>
86
+ <div class="_markdown_q16"><blockquote id="q16"><p><strong>结论性说明</strong><br>不应成卡。</p></blockquote></div>
87
+ <div class="_markdown_q17"><blockquote id="q17"><p><strong>指标数据</strong><br>不应成卡。</p></blockquote></div>
88
+ <div class="_markdown_test2">
89
+ <blockquote id="q5"><p><strong>Phase 1 完成</strong><br>28/28 通过。</p></blockquote>
90
+ <blockquote id="q5b"><p><strong>Phase 2 完成</strong><br>不应升级为 statement。</p></blockquote>
91
+ <blockquote id="q6"><p><strong>风险</strong><br>影响:宿主升级后可能失效。</p></blockquote>
92
+ <blockquote id="q7"><p><strong>下一步</strong><br>1. 安装测试 profile。</p></blockquote>
93
+ <blockquote id="q8"><p><strong>验收</strong><br>✅ 3/3</p></blockquote>
94
+ <blockquote id="q12"><p><strong>结论</strong><br>结论卡。</p></blockquote>
95
+ <blockquote id="q13"><p><strong>决策</strong><br>决策卡。</p></blockquote>
96
+ <blockquote id="q14"><p><strong>指标</strong><br>指标卡。</p></blockquote>
97
+ <blockquote id="q15"><p><strong>重点</strong><br>重点卡。</p></blockquote>
98
+ <div data-genui="1">
99
+ <blockquote id="gen"><p><strong>风险</strong><br>GenUI 内不处理。</p></blockquote>
100
+ </div>
101
+ </div>
102
+ <div class="_markdown_test5">
103
+ <div data-genui="1">
104
+ <blockquote id="genfirst"><p><strong>风险</strong><br>GenUI 内不处理,也不应阻断真实首块。</p></blockquote>
105
+ </div>
106
+ <blockquote id="real"><p><strong>Phase 9 完成</strong><br>真实首块仍应升级 statement。</p></blockquote>
107
+ </div>
108
+ `)
109
+
110
+ // 反例:前缀/复合词、普通引用、strong 前有正文或空白
111
+ assertPlain(document, 'q1')
112
+ assertPlain(document, 'q2')
113
+ assertPlain(document, 'q3')
114
+ assertPlain(document, 'q4')
115
+ assertPlain(document, 'q9')
116
+ assertPlain(document, 'q10')
117
+ assertPlain(document, 'q11')
118
+ assertPlain(document, 'q18')
119
+ assertPlain(document, 'q16')
120
+ assertPlain(document, 'q17')
121
+
122
+ // 正例:全部卡型 + 仅第一块可升级 + GenUI 前置不阻断真实首块
123
+ assertCard(document, 'q5', 'statement', 'info')
124
+ assertCard(document, 'q6', 'risk', 'warn')
125
+ assertCard(document, 'q7', 'next', 'info')
126
+ assertCard(document, 'q8', 'accept', 'success')
127
+ assertCard(document, 'q12', 'conclusion', 'info')
128
+ assertCard(document, 'q13', 'decision', 'info')
129
+ assertCard(document, 'q14', 'metric', 'info')
130
+ assertCard(document, 'q15', 'key', 'info')
131
+ assertCard(document, 'real', 'statement', 'info')
132
+ assertPlain(document, 'q5b')
133
+
134
+ // GenUI 区域跳过
135
+ assertPlain(document, 'gen')
136
+ assertPlain(document, 'genfirst')
137
+ })
138
+
139
+ test('严格卡片:不再有普通首引用 lead 与文本前缀 tone 逻辑', () => {
140
+ assert.doesNotMatch(clientSource, /isFirstBlockquote/)
141
+ assert.doesNotMatch(clientSource, /toneFor/)
142
+ assert.doesNotMatch(clientSource, /setAttribute\(LEAD_ATTR,\s*'lead'\)/)
143
+ assert.match(clientSource, /CARD_TYPES = new Map/)
144
+ assert.match(clientSource, /isStatementCard/)
145
+ })
@@ -0,0 +1,105 @@
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
+ import { parseHTML } from 'linkedom'
6
+
7
+ const source = readFileSync('src/client.js', 'utf8')
8
+
9
+ function boot(html) {
10
+ const { window, document } = parseHTML(`<!doctype html><html><head></head><body>${html}</body></html>`)
11
+ const observers = []
12
+ let definition = null
13
+ class Observer {
14
+ constructor(callback) {
15
+ this.callback = callback
16
+ observers.push(this)
17
+ }
18
+ observe() {}
19
+ disconnect() {}
20
+ }
21
+ const sandbox = {
22
+ window: { __ModuleLoader__: { load(value) { definition = value } } },
23
+ document,
24
+ MutationObserver: Observer,
25
+ requestAnimationFrame: (fn) => { fn(); return 1 },
26
+ cancelAnimationFrame: () => {},
27
+ setTimeout,
28
+ clearTimeout,
29
+ console,
30
+ }
31
+ sandbox.globalThis = sandbox
32
+ vm.runInNewContext(source, sandbox)
33
+ const plugin = definition.factory()
34
+ plugin.apply({
35
+ inject(_services, callback) {
36
+ callback({ configForms: { get: () => null }, effect: (fn) => fn() })
37
+ },
38
+ effect: (fn) => fn(),
39
+ })
40
+ return { document, observers, plugin }
41
+ }
42
+
43
+ const markdown = (id, inner) => `<div class="_markdown_${id}">${inner}</div>`
44
+ const riskCard = (id, body = '正文') => `<blockquote id="${id}"><p><strong>风险</strong><br>${body}</p></blockquote>`
45
+
46
+ test('增量扫描:mutation 只重扫所在 markdown 容器,不触发整页扫描', () => {
47
+ const { document, observers } = boot(
48
+ markdown('a', riskCard('a1')) + markdown('b', riskCard('b1')),
49
+ )
50
+ const a1 = document.getElementById('a1')
51
+ const b1 = document.getElementById('b1')
52
+ assert.equal(a1.getAttribute('data-dsh-structured-card'), '1')
53
+ assert.equal(b1.getAttribute('data-dsh-structured-card'), '1')
54
+
55
+ // 人为制造 B 的“陈旧状态”;若发生整页重扫,它会被重新装饰成卡片。
56
+ for (const attr of ['data-dsh-structured-card', 'data-dsh-structured-card-type', 'data-dsh-structured-tone']) {
57
+ b1.removeAttribute(attr)
58
+ }
59
+
60
+ const wrapper = document.createElement('blockquote')
61
+ wrapper.id = 'a2'
62
+ wrapper.innerHTML = '<p><strong>下一步</strong><br>新内容</p>'
63
+ document.querySelector('._markdown_a').appendChild(wrapper)
64
+ observers[observers.length - 1].callback([
65
+ { type: 'childList', target: document.querySelector('._markdown_a'), addedNodes: [wrapper] },
66
+ ])
67
+
68
+ assert.equal(wrapper.getAttribute('data-dsh-structured-card'), '1', '新增块应被装饰')
69
+ assert.equal(wrapper.getAttribute('data-dsh-structured-card-type'), 'next')
70
+ assert.equal(b1.getAttribute('data-dsh-structured-card'), null, '未变更的容器不应被整页重扫')
71
+ })
72
+
73
+ test('增量扫描:文本变化会重扫所在容器并清掉陈旧属性', () => {
74
+ const { document, observers } = boot(markdown('c', '<blockquote id="c1"><p>普通引用</p></blockquote>'))
75
+ const block = document.getElementById('c1')
76
+ // 人为制造陈旧卡片属性;文本变化后应被清掉。
77
+ block.setAttribute('data-dsh-structured-card', '1')
78
+ block.setAttribute('data-dsh-structured-card-type', 'risk')
79
+ block.setAttribute('data-dsh-structured-tone', 'warn')
80
+
81
+ const text = document.createTextNode(',追加')
82
+ block.querySelector('p').appendChild(text)
83
+ observers[observers.length - 1].callback([
84
+ { type: 'characterData', target: text, addedNodes: [] },
85
+ ])
86
+
87
+ assert.equal(block.getAttribute('data-dsh-structured-card'), null)
88
+ assert.equal(block.getAttribute('data-dsh-structured-card-type'), null)
89
+ assert.equal(block.getAttribute('data-dsh-structured-tone'), null)
90
+ })
91
+
92
+ test('增量扫描:新增消息容器(body 级)会被装饰', () => {
93
+ const { document, observers } = boot(markdown('d', riskCard('d1')))
94
+ const fresh = document.createElement('div')
95
+ fresh.className = '_markdown_e'
96
+ fresh.innerHTML = riskCard('e1')
97
+ document.body.appendChild(fresh)
98
+ observers[observers.length - 1].callback([
99
+ { type: 'childList', target: document.body, addedNodes: [fresh] },
100
+ ])
101
+
102
+ const e1 = document.getElementById('e1')
103
+ assert.equal(e1.getAttribute('data-dsh-structured-card'), '1')
104
+ assert.equal(e1.getAttribute('data-dsh-structured-card-type'), 'risk')
105
+ })