@planet-matrix/mobius-model 0.4.0 → 0.6.0
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 +32 -0
- package/README.md +134 -21
- package/dist/index.js +45 -4
- package/dist/index.js.map +186 -11
- package/oxlint.config.ts +6 -0
- package/package.json +16 -10
- package/src/abort/README.md +92 -0
- package/src/abort/abort-manager.ts +278 -0
- package/src/abort/abort-signal-listener-manager.ts +81 -0
- package/src/abort/index.ts +2 -0
- package/src/basic/README.md +69 -117
- package/src/basic/enhance.ts +10 -0
- package/src/basic/function.ts +81 -62
- package/src/basic/index.ts +2 -0
- package/src/basic/is.ts +152 -71
- package/src/basic/object.ts +82 -0
- package/src/basic/promise.ts +29 -8
- package/src/basic/string.ts +2 -33
- package/src/color/README.md +105 -0
- package/src/color/index.ts +3 -0
- package/src/color/internal.ts +42 -0
- package/src/color/rgb/analyze.ts +236 -0
- package/src/color/rgb/construct.ts +130 -0
- package/src/color/rgb/convert.ts +227 -0
- package/src/color/rgb/derive.ts +303 -0
- package/src/color/rgb/index.ts +6 -0
- package/src/color/rgb/internal.ts +208 -0
- package/src/color/rgb/parse.ts +302 -0
- package/src/color/rgb/serialize.ts +144 -0
- package/src/color/types.ts +57 -0
- package/src/color/xyz/analyze.ts +80 -0
- package/src/color/xyz/construct.ts +19 -0
- package/src/color/xyz/convert.ts +71 -0
- package/src/color/xyz/index.ts +3 -0
- package/src/color/xyz/internal.ts +23 -0
- package/src/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/index.ts +1 -0
- package/src/encoding/README.md +92 -0
- package/src/encoding/base64.ts +107 -0
- package/src/encoding/index.ts +1 -0
- package/src/environment/README.md +97 -0
- package/src/environment/basic.ts +26 -0
- package/src/environment/device.ts +311 -0
- package/src/environment/feature.ts +285 -0
- package/src/environment/geo.ts +337 -0
- package/src/environment/index.ts +7 -0
- package/src/environment/runtime.ts +400 -0
- package/src/environment/snapshot.ts +60 -0
- package/src/environment/variable.ts +239 -0
- package/src/event/README.md +90 -0
- package/src/event/class-event-proxy.ts +228 -0
- package/src/event/common.ts +19 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +186 -0
- package/src/event/internal.ts +24 -0
- package/src/exception/README.md +96 -0
- package/src/exception/browser.ts +219 -0
- package/src/exception/index.ts +4 -0
- package/src/exception/nodejs.ts +169 -0
- package/src/exception/normalize.ts +106 -0
- package/src/exception/types.ts +99 -0
- package/src/identifier/README.md +92 -0
- package/src/identifier/id.ts +119 -0
- package/src/identifier/index.ts +2 -0
- package/src/identifier/uuid.ts +187 -0
- package/src/index.ts +18 -1
- package/src/log/README.md +79 -0
- package/src/log/index.ts +5 -0
- package/src/log/log-emitter.ts +72 -0
- package/src/log/log-record.ts +10 -0
- package/src/log/log-scheduler.ts +74 -0
- package/src/log/log-type.ts +8 -0
- package/src/log/logger.ts +543 -0
- package/src/orchestration/README.md +89 -0
- package/src/orchestration/coordination/barrier.ts +214 -0
- package/src/orchestration/coordination/count-down-latch.ts +215 -0
- package/src/orchestration/coordination/errors.ts +98 -0
- package/src/orchestration/coordination/index.ts +16 -0
- package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
- package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
- package/src/orchestration/coordination/keyed-lock.ts +168 -0
- package/src/orchestration/coordination/mutex.ts +257 -0
- package/src/orchestration/coordination/permit.ts +127 -0
- package/src/orchestration/coordination/read-write-lock.ts +444 -0
- package/src/orchestration/coordination/semaphore.ts +280 -0
- package/src/orchestration/index.ts +1 -0
- package/src/random/README.md +78 -0
- package/src/random/index.ts +1 -0
- package/src/random/string.ts +35 -0
- package/src/reactor/README.md +4 -0
- package/src/reactor/reactor-core/primitive.ts +9 -9
- package/src/reactor/reactor-core/reactive-system.ts +5 -5
- package/src/singleton/README.md +79 -0
- package/src/singleton/factory.ts +55 -0
- package/src/singleton/index.ts +2 -0
- package/src/singleton/manager.ts +204 -0
- package/src/storage/README.md +107 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/table.ts +449 -0
- package/src/timer/README.md +86 -0
- package/src/timer/expiration/expiration-manager.ts +594 -0
- package/src/timer/expiration/index.ts +3 -0
- package/src/timer/expiration/min-heap.ts +208 -0
- package/src/timer/expiration/remaining-manager.ts +241 -0
- package/src/timer/index.ts +1 -0
- package/src/type/README.md +54 -307
- package/src/type/class.ts +2 -2
- package/src/type/index.ts +14 -14
- package/src/type/is.ts +265 -2
- package/src/type/object.ts +37 -0
- package/src/type/string.ts +7 -2
- package/src/type/tuple.ts +6 -6
- package/src/type/union.ts +16 -0
- package/src/web/README.md +77 -0
- package/src/web/capture.ts +35 -0
- package/src/web/clipboard.ts +97 -0
- package/src/web/dom.ts +117 -0
- package/src/web/download.ts +16 -0
- package/src/web/event.ts +46 -0
- package/src/web/index.ts +10 -0
- package/src/web/local-storage.ts +113 -0
- package/src/web/location.ts +28 -0
- package/src/web/permission.ts +172 -0
- package/src/web/script-loader.ts +432 -0
- package/tests/unit/abort/abort-manager.spec.ts +225 -0
- package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/object.spec.ts +32 -1
- package/tests/unit/basic/stream.spec.ts +1 -1
- package/tests/unit/basic/string.spec.ts +0 -9
- package/tests/unit/color/rgb/analyze.spec.ts +110 -0
- package/tests/unit/color/rgb/construct.spec.ts +56 -0
- package/tests/unit/color/rgb/convert.spec.ts +60 -0
- package/tests/unit/color/rgb/derive.spec.ts +103 -0
- package/tests/unit/color/rgb/parse.spec.ts +66 -0
- package/tests/unit/color/rgb/serialize.spec.ts +46 -0
- package/tests/unit/color/xyz/analyze.spec.ts +33 -0
- package/tests/unit/color/xyz/construct.spec.ts +10 -0
- package/tests/unit/color/xyz/convert.spec.ts +18 -0
- package/tests/unit/css/class.spec.ts +157 -0
- package/tests/unit/encoding/base64.spec.ts +40 -0
- package/tests/unit/environment/basic.spec.ts +20 -0
- package/tests/unit/environment/device.spec.ts +146 -0
- package/tests/unit/environment/feature.spec.ts +388 -0
- package/tests/unit/environment/geo.spec.ts +111 -0
- package/tests/unit/environment/runtime.spec.ts +364 -0
- package/tests/unit/environment/snapshot.spec.ts +4 -0
- package/tests/unit/environment/variable.spec.ts +190 -0
- package/tests/unit/event/class-event-proxy.spec.ts +225 -0
- package/tests/unit/event/event-manager.spec.ts +246 -0
- package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
- package/tests/unit/exception/browser.spec.ts +213 -0
- package/tests/unit/exception/nodejs.spec.ts +144 -0
- package/tests/unit/exception/normalize.spec.ts +57 -0
- package/tests/unit/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -0
- package/tests/unit/log/log-emitter.spec.ts +33 -0
- package/tests/unit/log/log-scheduler.spec.ts +40 -0
- package/tests/unit/log/log-type.spec.ts +7 -0
- package/tests/unit/log/logger.spec.ts +222 -0
- package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
- package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
- package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
- package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
- package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
- package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
- package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
- package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
- package/tests/unit/random/string.spec.ts +11 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
- package/tests/unit/reactor/preact-signal.spec.ts +1 -2
- package/tests/unit/singleton/singleton.spec.ts +49 -0
- package/tests/unit/storage/table.spec.ts +620 -0
- package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
- package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
- package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
- package/.oxlintrc.json +0 -5
package/src/css/class.ts
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表示以空格分隔的 CSS 类名字串。
|
|
3
|
+
*/
|
|
4
|
+
export type ClassString = string
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 表示按顺序保存的 CSS 类名数组。
|
|
8
|
+
*/
|
|
9
|
+
export type ClassArray = string[]
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 表示以类名为键、以启用状态为值的 CSS 类名对象。
|
|
13
|
+
*
|
|
14
|
+
* 该表示允许保留值为 `false` 的键,以表达已知但当前未启用的类名状态。
|
|
15
|
+
*/
|
|
16
|
+
export type ClassObject = Record<string, boolean>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 表示 CSS 类名的三种公共表达形式:字符串、数组或对象。
|
|
20
|
+
*/
|
|
21
|
+
export type ClassUnion = ClassString | ClassArray | ClassObject
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 规范化类名字串,使其适合作为统一的字符串表示。
|
|
25
|
+
*
|
|
26
|
+
* 该函数会将 `.` 视为分隔符,将连续空白折叠为一个空格,并移除首尾空白。
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```
|
|
30
|
+
* // Expect: 'mobius-base mobius-theme--light'
|
|
31
|
+
* const example1 = neatenClassString('mobius-base mobius-theme--light')
|
|
32
|
+
*
|
|
33
|
+
* // Expect: 'mobius-base mobius-theme--light'
|
|
34
|
+
* const example2 = neatenClassString('.mobius-base.mobius-theme--light')
|
|
35
|
+
*
|
|
36
|
+
* // Expect: 'mobius-base mobius-theme--light'
|
|
37
|
+
* const example3 = neatenClassString(' .mobius-base mobius-theme--light ')
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export const neatenClassString = (str: string): ClassString => {
|
|
41
|
+
const classString = str.replaceAll('.', ' ').replaceAll(/\s+/g, ' ').trim()
|
|
42
|
+
return classString
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 将类名字串转换为类名数组。
|
|
47
|
+
*
|
|
48
|
+
* 输入会先经过 `neatenClassString` 规范化,再按空格拆分,并移除空项。
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```
|
|
52
|
+
* // Expect: ['mobius-base', 'mobius-theme--light']
|
|
53
|
+
* const example1 = classStringToClassArray('mobius-base mobius-theme--light')
|
|
54
|
+
*
|
|
55
|
+
* // Expect: ['mobius-base', 'mobius-theme--light']
|
|
56
|
+
* const example2 = classStringToClassArray('.mobius-base.mobius-theme--light')
|
|
57
|
+
*
|
|
58
|
+
* // Expect: ['mobius-base', 'mobius-theme--light']
|
|
59
|
+
* const example3 = classStringToClassArray(' .mobius-base mobius-theme--light ')
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export const classStringToClassArray = (str: ClassString): ClassArray => {
|
|
63
|
+
const classArray = neatenClassString(str).split(' ').filter(s => s.length !== 0)
|
|
64
|
+
return classArray
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 将类名数组转换为布尔对象表示。
|
|
69
|
+
*
|
|
70
|
+
* 数组中的每个非空类名都会映射为值为 `true` 的对象键。
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```
|
|
74
|
+
* // Expect: { 'mobius-base': true, 'mobius-theme--light': true }
|
|
75
|
+
* const example1 = classArrayToClassObject(['mobius-base', 'mobius-theme--light'])
|
|
76
|
+
*
|
|
77
|
+
* // Expect: { 'mobius-base': true }
|
|
78
|
+
* const example2 = classArrayToClassObject(['mobius-base', ''])
|
|
79
|
+
*
|
|
80
|
+
* // Expect: {}
|
|
81
|
+
* const example3 = classArrayToClassObject([])
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const classArrayToClassObject = (arr: ClassArray): ClassObject => {
|
|
85
|
+
const classObject: ClassObject = {}
|
|
86
|
+
arr.filter(s => s.length !== 0).forEach(s => {
|
|
87
|
+
classObject[s] = true
|
|
88
|
+
})
|
|
89
|
+
return classObject
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 将类名字串直接转换为布尔对象表示。
|
|
94
|
+
*
|
|
95
|
+
* 该函数会先把字符串拆分为类名数组,再将每个类名映射为值为 `true` 的对象键。
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```
|
|
99
|
+
* // Expect: { 'mobius-base': true, 'mobius-theme--light': true }
|
|
100
|
+
* const example1 = classStringToClassObject('mobius-base mobius-theme--light')
|
|
101
|
+
*
|
|
102
|
+
* // Expect: { 'mobius-base': true, 'mobius-theme--light': true }
|
|
103
|
+
* const example2 = classStringToClassObject('.mobius-base.mobius-theme--light')
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export const classStringToClassObject = (str: ClassString): ClassObject => {
|
|
107
|
+
const classArray = classStringToClassArray(str)
|
|
108
|
+
const classObject = classArrayToClassObject(classArray)
|
|
109
|
+
return classObject
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 将布尔对象表示转换为类名数组。
|
|
114
|
+
*
|
|
115
|
+
* 值为 `false` 的类名和空类名会被忽略。
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```
|
|
119
|
+
* // Expect: ['active', 'primary']
|
|
120
|
+
* const example1 = classObjectToClassArray({ active: true, disabled: false, primary: true })
|
|
121
|
+
*
|
|
122
|
+
* // Expect: ['active']
|
|
123
|
+
* const example2 = classObjectToClassArray({ '': true, active: true, disabled: false })
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export const classObjectToClassArray = (obj: ClassObject): ClassArray => {
|
|
127
|
+
const classArray: string[] = []
|
|
128
|
+
Object.entries(obj)
|
|
129
|
+
.filter(([key, value]) => key.length !== 0 && value)
|
|
130
|
+
.forEach(([key, _]) => {
|
|
131
|
+
classArray.push(key)
|
|
132
|
+
})
|
|
133
|
+
return classArray
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 将类名数组按空格连接为类名字串。
|
|
138
|
+
*
|
|
139
|
+
* 空字符串项会被过滤,以避免产生多余空格。
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```
|
|
143
|
+
* // Expect: 'mobius-base mobius-theme--light'
|
|
144
|
+
* const example1 = classArrayToClassString(['mobius-base', 'mobius-theme--light'])
|
|
145
|
+
*
|
|
146
|
+
* // Expect: 'mobius-base mobius-theme--light'
|
|
147
|
+
* const example2 = classArrayToClassString(['mobius-base', '', 'mobius-theme--light'])
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export const classArrayToClassString = (arr: ClassArray): ClassString => {
|
|
151
|
+
const classString = arr.filter(s => s.length !== 0).join(' ')
|
|
152
|
+
return classString
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 将布尔对象表示转换为类名字串。
|
|
157
|
+
*
|
|
158
|
+
* 该函数会忽略值为 `false` 的类名和空类名,并保留其余类名的迭代顺序。
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```
|
|
162
|
+
* // Expect: 'active primary'
|
|
163
|
+
* const example1 = classObjectToClassString({ active: true, disabled: false, primary: true })
|
|
164
|
+
*
|
|
165
|
+
* // Expect: 'active'
|
|
166
|
+
* const example2 = classObjectToClassString({ '': true, active: true, disabled: false })
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export const classObjectToClassString = (obj: ClassObject): ClassString => {
|
|
170
|
+
const classArray = classObjectToClassArray(obj)
|
|
171
|
+
const classString = classArrayToClassString(classArray)
|
|
172
|
+
return classString
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 将任意公共类名表示转换为字符串表示。
|
|
177
|
+
*
|
|
178
|
+
* 如果输入本身已经是字符串,则原样返回;数组和对象会分别经过对应的序列化流程。
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```
|
|
182
|
+
* // Expect: ' .button active '
|
|
183
|
+
* const example1 = toClassString(' .button active ')
|
|
184
|
+
*
|
|
185
|
+
* // Expect: 'button active'
|
|
186
|
+
* const example2 = toClassString(['button', 'active'])
|
|
187
|
+
*
|
|
188
|
+
* // Expect: 'button active'
|
|
189
|
+
* const example3 = toClassString({ button: true, active: true, disabled: false })
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
export const toClassString = (tar: ClassUnion): ClassString => {
|
|
193
|
+
if (typeof tar === 'string') {
|
|
194
|
+
return tar
|
|
195
|
+
} else if (Array.isArray(tar)) {
|
|
196
|
+
return classArrayToClassString(tar)
|
|
197
|
+
} else {
|
|
198
|
+
return classObjectToClassString(tar)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 将任意公共类名表示转换为数组表示。
|
|
204
|
+
*
|
|
205
|
+
* 如果输入本身已经是数组,则会在过滤空类名后返回一个浅拷贝,以避免调用方共享同一数组实例。
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```
|
|
209
|
+
* // Expect: ['button', 'active']
|
|
210
|
+
* const example1 = toClassArray('button active')
|
|
211
|
+
*
|
|
212
|
+
* // Expect: ['button', 'active']
|
|
213
|
+
* const example2 = toClassArray(['button', 'active'])
|
|
214
|
+
*
|
|
215
|
+
* // Expect: ['button', 'active']
|
|
216
|
+
* const example3 = toClassArray({ button: true, disabled: false, active: true })
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
export const toClassArray = (tar: ClassUnion): ClassArray => {
|
|
220
|
+
if (typeof tar === 'string') {
|
|
221
|
+
return classStringToClassArray(tar)
|
|
222
|
+
} else if (Array.isArray(tar)) {
|
|
223
|
+
return tar.filter(s => s.length !== 0)
|
|
224
|
+
} else {
|
|
225
|
+
return classObjectToClassArray(tar)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 将任意公共类名表示转换为对象表示。
|
|
231
|
+
*
|
|
232
|
+
* 如果输入本身已经是对象,则会在过滤空类名后返回一个浅拷贝,以避免外部直接共享内部结果。
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```
|
|
236
|
+
* // Expect: { button: true, active: true }
|
|
237
|
+
* const example1 = toClassObject('button active')
|
|
238
|
+
*
|
|
239
|
+
* // Expect: { button: true, active: true }
|
|
240
|
+
* const example2 = toClassObject(['button', 'active'])
|
|
241
|
+
*
|
|
242
|
+
* // Expect: { button: true, active: false }
|
|
243
|
+
* const example3 = toClassObject({ button: true, active: false, '': true })
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
export const toClassObject = (tar: ClassUnion): ClassObject => {
|
|
247
|
+
if (typeof tar === 'string') {
|
|
248
|
+
return classStringToClassObject(tar)
|
|
249
|
+
} else if (Array.isArray(tar)) {
|
|
250
|
+
return classArrayToClassObject(tar)
|
|
251
|
+
} else {
|
|
252
|
+
const classObject: ClassObject = {}
|
|
253
|
+
Object.entries(tar).forEach(([key, value]) => {
|
|
254
|
+
if (key.length !== 0) {
|
|
255
|
+
classObject[key] = value
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
return classObject
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 按目标值的外部表示,将类名集合格式化为相同形态。
|
|
264
|
+
*
|
|
265
|
+
* 该函数适合在内部统一按对象进行计算后,再把结果还原为调用方原本使用的表示。
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```
|
|
269
|
+
* // Expect: 'button active'
|
|
270
|
+
* const example1 = formatClassToTarget('', ['button', 'active'])
|
|
271
|
+
*
|
|
272
|
+
* // Expect: ['button', 'active']
|
|
273
|
+
* const example2 = formatClassToTarget([], 'button active')
|
|
274
|
+
*
|
|
275
|
+
* // Expect: { button: true, active: true }
|
|
276
|
+
* const example3 = formatClassToTarget({}, ['button', 'active'])
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export const formatClassToTarget = <T extends ClassUnion>(target: T, cls: ClassUnion): T => {
|
|
280
|
+
if (typeof target === 'string') {
|
|
281
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
282
|
+
return toClassString(cls) as T
|
|
283
|
+
} else if (Array.isArray(target)) {
|
|
284
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
285
|
+
return toClassArray(cls) as T
|
|
286
|
+
} else {
|
|
287
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
288
|
+
return toClassObject(cls) as T
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 为类名集合中的每一项补上指定前缀。
|
|
294
|
+
*
|
|
295
|
+
* 已经带有该前缀的类名会保持不变。
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* ```
|
|
299
|
+
* // Expect: 'pm-button pm-active'
|
|
300
|
+
* const example1 = prefixClassWith('pm-', 'button active')
|
|
301
|
+
*
|
|
302
|
+
* // Expect: ['pm-button', 'pm-active']
|
|
303
|
+
* const example2 = prefixClassWith('pm-', ['button', 'pm-active'])
|
|
304
|
+
*
|
|
305
|
+
* // Expect: { 'pm-button': true, 'pm-active': true }
|
|
306
|
+
* const example3 = prefixClassWith('pm-', { button: true, 'pm-active': true })
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
export const prefixClassWith = <T extends ClassUnion>(prefix: string, cls: T): T => {
|
|
310
|
+
if (typeof cls === 'string') {
|
|
311
|
+
const classArray = classStringToClassArray(cls).map(item =>
|
|
312
|
+
item.startsWith(prefix) ? item : `${prefix}${item}`
|
|
313
|
+
)
|
|
314
|
+
const classString = classArrayToClassString(classArray)
|
|
315
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
316
|
+
return classString as T
|
|
317
|
+
} else if (Array.isArray(cls)) {
|
|
318
|
+
const classArray = cls.filter(item => item.length !== 0).map(item =>
|
|
319
|
+
item.startsWith(prefix) ? item : `${prefix}${item}`
|
|
320
|
+
)
|
|
321
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
322
|
+
return classArray as T
|
|
323
|
+
} else {
|
|
324
|
+
const classObject: ClassObject = {}
|
|
325
|
+
Object.entries(cls).forEach(([key, value]) => {
|
|
326
|
+
if (key.length === 0) {
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
const _key = key.startsWith(prefix) ? key : `${prefix}${key}`
|
|
330
|
+
classObject[_key] = value === true
|
|
331
|
+
})
|
|
332
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
333
|
+
return classObject as T
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* 从类名集合中的每一项移除指定前缀。
|
|
339
|
+
*
|
|
340
|
+
* 不带该前缀的类名会保持原样。
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```
|
|
344
|
+
* // Expect: 'button active plain'
|
|
345
|
+
* const example1 = removePrefixOfClass('pm-', 'pm-button pm-active plain')
|
|
346
|
+
*
|
|
347
|
+
* // Expect: ['button', 'plain']
|
|
348
|
+
* const example2 = removePrefixOfClass('pm-', ['pm-button', 'plain'])
|
|
349
|
+
*
|
|
350
|
+
* // Expect: { button: true, plain: false }
|
|
351
|
+
* const example3 = removePrefixOfClass('pm-', { 'pm-button': true, plain: false, 'pm-': true })
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
export const removePrefixOfClass = <T extends ClassUnion>(prefix: string, cls: T): T => {
|
|
355
|
+
if (typeof cls === 'string') {
|
|
356
|
+
const classArray = classStringToClassArray(cls).map(item => {
|
|
357
|
+
if (item.startsWith(prefix)) {
|
|
358
|
+
return item.slice(prefix.length)
|
|
359
|
+
}
|
|
360
|
+
return item
|
|
361
|
+
})
|
|
362
|
+
const classString = classArrayToClassString(classArray)
|
|
363
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
364
|
+
return classString as T
|
|
365
|
+
} else if (Array.isArray(cls)) {
|
|
366
|
+
const classArray = cls.filter(item => item.length !== 0).map(item => {
|
|
367
|
+
if (item.startsWith(prefix)) {
|
|
368
|
+
return item.slice(prefix.length)
|
|
369
|
+
}
|
|
370
|
+
return item
|
|
371
|
+
})
|
|
372
|
+
const filteredClassArray = classArray.filter(item => item.length !== 0)
|
|
373
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
374
|
+
return filteredClassArray as T
|
|
375
|
+
} else {
|
|
376
|
+
const classObject: ClassObject = {}
|
|
377
|
+
Object.entries(cls).forEach(([key, value]) => {
|
|
378
|
+
if (key.length === 0) {
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
const _key = key.startsWith(prefix) ? key.slice(prefix.length) : key
|
|
382
|
+
if (_key.length !== 0) {
|
|
383
|
+
classObject[_key] = value === true
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
387
|
+
return classObject as T
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* 向目标类名集合中加入新的类名。
|
|
393
|
+
*
|
|
394
|
+
* 当目标与新增项都包含同名类时,以新增项转换后的对象表示为准。
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* ```
|
|
398
|
+
* // Expect: 'button active primary'
|
|
399
|
+
* const example1 = addClass('button', 'active primary')
|
|
400
|
+
*
|
|
401
|
+
* // Expect: ['button', 'active', 'primary']
|
|
402
|
+
* const example2 = addClass(['button'], { active: true, primary: true })
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
export const addClass = <T extends ClassUnion>(target: T, added: ClassUnion): T => {
|
|
406
|
+
const targetClassObj = toClassObject(target)
|
|
407
|
+
const addedClassObj = toClassObject(added)
|
|
408
|
+
const resClassObj = { ...targetClassObj, ...addedClassObj }
|
|
409
|
+
const result = formatClassToTarget(target, resClassObj)
|
|
410
|
+
return result
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* 从目标类名集合中移除指定类名。
|
|
415
|
+
*
|
|
416
|
+
* 该函数会把待移除项标记为 `false`,再按目标形态输出结果。
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```
|
|
420
|
+
* // Expect: 'button primary'
|
|
421
|
+
* const example1 = removeClass('button active primary', 'active')
|
|
422
|
+
*
|
|
423
|
+
* // Expect: { button: true, active: false }
|
|
424
|
+
* const example2 = removeClass({ button: true, active: true }, ['active'])
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
export const removeClass = <T extends ClassUnion>(target: T, removed: ClassUnion): T => {
|
|
428
|
+
const targetClassObj = toClassObject(target)
|
|
429
|
+
const removedClassObj = toClassObject(removed)
|
|
430
|
+
Object.keys(removedClassObj).forEach(key => {
|
|
431
|
+
removedClassObj[key] = false
|
|
432
|
+
})
|
|
433
|
+
const resClassObj = { ...targetClassObj, ...removedClassObj }
|
|
434
|
+
const result = formatClassToTarget(target, resClassObj)
|
|
435
|
+
return result
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* 切换目标类名集合中指定类名的启用状态。
|
|
440
|
+
*
|
|
441
|
+
* 已存在的类名会被关闭,不存在的类名会被开启。
|
|
442
|
+
*
|
|
443
|
+
* @example
|
|
444
|
+
* ```
|
|
445
|
+
* // Expect: 'button primary'
|
|
446
|
+
* const example1 = toggleClass('button active', 'active primary')
|
|
447
|
+
*
|
|
448
|
+
* // Expect: { button: true, primary: true }
|
|
449
|
+
* const example2 = toggleClass({ button: true, active: true }, ['active', 'primary'])
|
|
450
|
+
* ```
|
|
451
|
+
*/
|
|
452
|
+
export const toggleClass = <T extends ClassUnion>(target: T, toggled: ClassUnion): T => {
|
|
453
|
+
const targetClassObj = toClassObject(target)
|
|
454
|
+
const toggledClassArr = toClassArray(toggled)
|
|
455
|
+
toggledClassArr.forEach((cls) => {
|
|
456
|
+
// oxlint-disable-next-line strict-boolean-expressions
|
|
457
|
+
targetClassObj[cls] = !targetClassObj[cls]
|
|
458
|
+
})
|
|
459
|
+
const result = formatClassToTarget(target, targetClassObj)
|
|
460
|
+
return result
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* 替换目标类名集合中的类名。
|
|
465
|
+
*
|
|
466
|
+
* `replaced` 支持三种形式:
|
|
467
|
+
* - 字符串:仅移除对应类名。
|
|
468
|
+
* - 字符串数组:逐项移除对应类名。
|
|
469
|
+
* - 元组数组:按 `[from, to]` 的形式逐项替换类名。
|
|
470
|
+
* - 对象:按键值对执行替换,值为空字符串时表示删除。
|
|
471
|
+
*
|
|
472
|
+
* @example
|
|
473
|
+
* ```
|
|
474
|
+
* // Expect: 'button selected'
|
|
475
|
+
* const example1 = replaceClass('button active', [['active', 'selected']])
|
|
476
|
+
*
|
|
477
|
+
* // Expect: ['button', 'selected']
|
|
478
|
+
* const example2 = replaceClass(['button', 'active'], { active: 'selected' })
|
|
479
|
+
*
|
|
480
|
+
* // Expect: { button: true, active: false }
|
|
481
|
+
* const example3 = replaceClass({ button: true, active: true }, 'active')
|
|
482
|
+
* ```
|
|
483
|
+
*/
|
|
484
|
+
export const replaceClass = <T extends ClassUnion>(
|
|
485
|
+
target: T,
|
|
486
|
+
replaced: Record<string, string> | string | string[] | Array<[string, string]>,
|
|
487
|
+
): T => {
|
|
488
|
+
if (typeof replaced === 'string') {
|
|
489
|
+
return removeClass(target, replaced)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const targetClassObj = toClassObject(target)
|
|
493
|
+
|
|
494
|
+
if (Array.isArray(replaced)) {
|
|
495
|
+
for (const item of replaced) {
|
|
496
|
+
if (typeof item === 'string') {
|
|
497
|
+
const fromValue = targetClassObj[item]
|
|
498
|
+
|
|
499
|
+
if (fromValue !== undefined) {
|
|
500
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
501
|
+
delete targetClassObj[item]
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
continue
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const [from, to] = item
|
|
508
|
+
const fromValue = targetClassObj[from]
|
|
509
|
+
|
|
510
|
+
if (fromValue !== undefined) {
|
|
511
|
+
if (to !== '') {
|
|
512
|
+
targetClassObj[to] = fromValue
|
|
513
|
+
}
|
|
514
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
515
|
+
delete targetClassObj[from]
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return formatClassToTarget(target, targetClassObj)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
for (const [from, rawTo] of Object.entries(replaced)) {
|
|
523
|
+
if (typeof rawTo !== 'string') {
|
|
524
|
+
continue
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const fromValue = targetClassObj[from]
|
|
528
|
+
|
|
529
|
+
if (fromValue !== undefined) {
|
|
530
|
+
if (rawTo !== '') {
|
|
531
|
+
targetClassObj[rawTo] = fromValue
|
|
532
|
+
}
|
|
533
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
534
|
+
delete targetClassObj[from]
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return formatClassToTarget(target, targetClassObj)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* 判断目标类名集合是否完整包含另一组类名。
|
|
543
|
+
*
|
|
544
|
+
* 只有当 `contained` 中的每个类名都存在于 `target` 中时,才会返回 `true`。
|
|
545
|
+
*
|
|
546
|
+
* @example
|
|
547
|
+
* ```
|
|
548
|
+
* // Expect: true
|
|
549
|
+
* const example1 = containClass('button active', 'button active primary')
|
|
550
|
+
*
|
|
551
|
+
* // Expect: false
|
|
552
|
+
* const example2 = containClass(['button', 'missing'], { button: true, active: true })
|
|
553
|
+
* ```
|
|
554
|
+
*/
|
|
555
|
+
export const containClass = (contained: ClassUnion, target: ClassUnion): boolean => {
|
|
556
|
+
const containedClassArr = toClassArray(contained)
|
|
557
|
+
const targetClassArr = toClassArray(target)
|
|
558
|
+
return containedClassArr.every(item => targetClassArr.includes(item))
|
|
559
|
+
}
|
package/src/css/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./class.ts"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Encoding
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Encoding 模块提供面向文本编码与解码(encoding and decoding)的基础能力,当前重点覆盖 UTF-8 字符串与 Base64 字符串之间的稳定转换、合法性判断与断言语义。
|
|
6
|
+
|
|
7
|
+
它关注的是“表示如何被明确切换”,而不是更高层的协议字段含义、消息封装规则或业务语义解释。
|
|
8
|
+
|
|
9
|
+
## For Understanding
|
|
10
|
+
|
|
11
|
+
理解 Encoding 模块时,应把它看作 I/O 边界上的表示层模型。它要解决的问题不是“某段数据在业务上代表什么”,而是“某段文本当前以什么编码形式存在,以及如何在不同表示之间明确切换”。
|
|
12
|
+
|
|
13
|
+
当前模块边界相对克制:
|
|
14
|
+
|
|
15
|
+
- 它主要处理 UTF-8 文本与 Base64 字符串之间的转换。
|
|
16
|
+
- 它明确区分“判断输入是否合法”“对非法输入抛错”“执行表示切换”这几类语义。
|
|
17
|
+
- 它允许在不同运行时使用不同底层实现,例如 `Buffer`、`btoa`、`atob`,但这些实现差异不应泄漏成公共语义差异。
|
|
18
|
+
|
|
19
|
+
这意味着 Encoding 不应承载压缩、加密、哈希、二进制协议、业务报文结构或传输层容错逻辑。那些能力可能会用到编码,但并不属于编码语义本身。
|
|
20
|
+
|
|
21
|
+
## For Using
|
|
22
|
+
|
|
23
|
+
当应用需要在纯文本与适合传输、存储或嵌入的编码字符串之间做明确转换时,可以使用这个模块。它尤其适合放在请求边界、文件边界、剪贴板边界、消息边界等“表示切换”明确发生的地方。
|
|
24
|
+
|
|
25
|
+
当前公共能力大致可以按以下几类理解:
|
|
26
|
+
|
|
27
|
+
- 合法性判断:用于判断一个字符串是否为合法 Base64 表示。
|
|
28
|
+
- 断言入口:用于在进入后续流程前显式拒绝非法 Base64 输入。
|
|
29
|
+
- 编码转换:用于把 UTF-8 字符串转换为 Base64。
|
|
30
|
+
- 解码转换:用于把合法 Base64 转回 UTF-8 字符串。
|
|
31
|
+
|
|
32
|
+
如果你的问题已经涉及二进制块处理、协议帧解析、密码学、压缩格式或业务级 payload 建模,应在其它模块中继续分层,而不要把这些问题直接叠加到 Encoding 上。
|
|
33
|
+
|
|
34
|
+
## For Contributing
|
|
35
|
+
|
|
36
|
+
贡献 Encoding 模块时,应优先判断新增能力是否仍然表达“表示切换”这一稳定问题域,而不是借编码名义引入更高层的数据处理逻辑。这个模块应长期保持边界清楚、行为可预期,而不是不断膨胀成杂项数据工具箱。
|
|
37
|
+
|
|
38
|
+
在扩展时,应优先遵守以下边界:
|
|
39
|
+
|
|
40
|
+
- 只承载编码表示本身的判断、断言、编码与解码语义。
|
|
41
|
+
- 不把协议字段解释、业务 payload 处理、压缩、加密或摘要计算直接写入模块公共能力。
|
|
42
|
+
- 不让不同运行时的底层 API 差异泄漏成不一致的公共语义。
|
|
43
|
+
- 对输入合法性的要求应明确,不要把模糊行为留给调用方自行猜测。
|
|
44
|
+
|
|
45
|
+
### JSDoc 注释格式要求
|
|
46
|
+
|
|
47
|
+
- 每个公开导出的目标(类型、函数、变量、类等)都应包含 JSDoc 注释,让人在不跳转实现的情况下就能理解用途。
|
|
48
|
+
- JSDoc 注释第一行应为清晰且简洁的描述,该描述优先使用中文(英文也可以)。
|
|
49
|
+
- 如果描述后还有其他内容,应在描述后加一个空行。
|
|
50
|
+
- 如果有示例,应使用 `@example` 标签,后接三重反引号代码块(不带语言标识)。
|
|
51
|
+
- 如果有示例,应包含多个场景,展示不同用法,尤其要覆盖常见组合方式或边界输入。
|
|
52
|
+
- 如果有示例,应使用注释格式说明每个场景:`// Expect: <result>`。
|
|
53
|
+
- 如果有示例,应将结果赋值给 `example1`、`example2` 之类的变量,以保持示例易读。
|
|
54
|
+
- 如果有示例,`// Expect: <result>` 应该位于 `example1`、`example2` 之前,以保持示例的逻辑清晰。
|
|
55
|
+
- 如果有示例,应优先使用确定性示例;避免断言精确的随机输出。
|
|
56
|
+
- 如果函数返回结构化字符串,应展示其预期格式特征。
|
|
57
|
+
- 如果有参考资料,应将 `@see` 放在 `@example` 代码块之后,并用一个空行分隔。
|
|
58
|
+
|
|
59
|
+
### 实现规范要求
|
|
60
|
+
|
|
61
|
+
- 不同程序元素之间使用一个空行分隔,保持结构清楚。这里的程序元素,通常指函数、类型、常量,以及直接服务于它们的辅助元素。
|
|
62
|
+
- 某程序元素独占的辅助元素与该程序元素本身视为一个整体,不要在它们之间添加空行。
|
|
63
|
+
- 程序元素的辅助元素应该放置在该程序元素的上方,以保持阅读时的逻辑顺序。
|
|
64
|
+
- 若辅助元素被多个程序元素共享,则应将其视为独立的程序元素,放在这些程序元素中第一个相关目标的上方,并与后续程序元素之间保留一个空行。
|
|
65
|
+
- 辅助元素也应该像其它程序元素一样,保持清晰的命名和适当的注释,以便在需要阅读实现细节时能够快速理解它们的作用和使用方式。
|
|
66
|
+
- 辅助元素的命名必须以前缀 `internal` 开头(或 `Internal`,大小写不敏感)。
|
|
67
|
+
- 辅助元素永远不要公开导出。
|
|
68
|
+
- 被模块内多个不同文件中的程序元素共享的辅助元素,应该放在一个单独的文件中,例如 `./src/encoding/internal.ts`;若当前没有必要,不必为了形式强行拆分。
|
|
69
|
+
- 模块内可以包含子模块。只有当某个子目录表达一个稳定、可单独理解、且可能被父模块重导出的子问题域时,才应将其视为子模块。
|
|
70
|
+
- 子模块包含多个文件时,应该为其单独创建子文件夹,并为其创建单独的 Barrel 文件;父模块的 Barrel 文件再重导出子模块的 Barrel 文件。
|
|
71
|
+
- 子模块不需要有自己的 `README.md`。
|
|
72
|
+
- 子模块可以有自己的 `internal.ts` 文件,多个子模块共享的辅助元素应该放在父模块的 `internal.ts` 文件中,单个子模块共享的辅助元素应该放在该子模块的 `internal.ts` 文件中。
|
|
73
|
+
- 对模块依赖关系的要求(通常是不循环依赖或不反向依赖)与对 DRY 的要求可能产生冲突。此时,若复用的代码数量不大,可以适当牺牲 DRY,复制粘贴并保留必要的注释说明;若复用的代码数量较大,则可以将其抽象到新的文件或子模块中,如 `common.ts`,并在需要的地方导入使用。
|
|
74
|
+
- 设计实现时,应优先保证输入表示、输出表示和异常语义都直接可理解,而不是追求某个特定运行时下的最短实现路径。
|
|
75
|
+
|
|
76
|
+
### 导出策略要求
|
|
77
|
+
|
|
78
|
+
- 保持内部辅助项和内部符号为私有,不要让外部接入依赖临时性的内部结构。
|
|
79
|
+
- 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
|
|
80
|
+
- Barrel 文件应命名为 `index.ts`,放在模块目录根部,并且所有公共 API 都应从该文件导出。
|
|
81
|
+
- 新增公共能力时,应优先检查它是否表达稳定、清楚且值得长期维护的编码语义,而不是某段实现细节的便捷暴露;仅在确认需要长期对外承诺时再加入 Barrel 导出。
|
|
82
|
+
|
|
83
|
+
### 测试要求
|
|
84
|
+
|
|
85
|
+
- 若程序元素是函数,则只为该函数编写一个测试,如果该函数需要测试多个用例,应放在同一个测试中。
|
|
86
|
+
- 若程序元素是类,则至少要为该类的每一个方法编写一个测试,如果该方法需要测试多个用例,应放在同一个测试中。
|
|
87
|
+
- 若程序元素是类,除了为该类的每一个方法编写至少一个测试之外,还可以为该类编写任意多个测试,以覆盖该类的不同使用场景或边界情况。
|
|
88
|
+
- 若编写测试时需要用到辅助元素(Mock 或 Spy 等),可以在测试文件中直接定义这些辅助元素。若辅助元素较为简单,则可以直接放在每一个测试内部,优先保证每个测试的独立性,而不是追求极致 DRY;若辅助元素较为复杂或需要在多个测试中复用,则可以放在测试文件顶部,供该测试文件中的所有测试使用。
|
|
89
|
+
- 测试顺序应与源文件中被测试目标的原始顺序保持一致。
|
|
90
|
+
- 若该模块不需要测试,必须在说明文件中明确说明该模块不需要测试,并说明理由。一般来说,只有在该模块没有可执行的公共函数、只承载类型层表达,或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时,才适合这样处理。
|
|
91
|
+
- 模块的单元测试文件目录是 `./tests/unit/encoding`,若模块包含子模块,则子模块的单元测试文件目录为 `./tests/unit/encoding/<sub-module-name>`。
|
|
92
|
+
- 测试应重点覆盖合法 Base64 判断、非法输入断言、UTF-8 文本往返转换,以及不同运行时实现下仍应保持一致的公共语义。
|