@stylewx/validator 0.1.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.
@@ -0,0 +1,3 @@
1
+ export * from './report.js';
2
+ export * from './validate.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './report.js';
2
+ export * from './validate.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ export declare const VALIDATION_SEVERITY: readonly ["error", "warning", "info"];
3
+ export type ValidationSeverity = (typeof VALIDATION_SEVERITY)[number];
4
+ export declare const validationIssueSchema: z.ZodObject<{
5
+ rule: z.ZodString;
6
+ severity: z.ZodEnum<{
7
+ error: "error";
8
+ warning: "warning";
9
+ info: "info";
10
+ }>;
11
+ message: z.ZodString;
12
+ suggestion: z.ZodString;
13
+ location: z.ZodString;
14
+ }, z.core.$strip>;
15
+ export declare const validationReportSchema: z.ZodObject<{
16
+ pass: z.ZodBoolean;
17
+ issues: z.ZodArray<z.ZodObject<{
18
+ rule: z.ZodString;
19
+ severity: z.ZodEnum<{
20
+ error: "error";
21
+ warning: "warning";
22
+ info: "info";
23
+ }>;
24
+ message: z.ZodString;
25
+ suggestion: z.ZodString;
26
+ location: z.ZodString;
27
+ }, z.core.$strip>>;
28
+ }, z.core.$strip>;
29
+ export type ValidationIssue = z.infer<typeof validationIssueSchema>;
30
+ export type ValidationReport = z.infer<typeof validationReportSchema>;
31
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,mBAAmB,uCAAwC,CAAA;AACxE,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AAErE,eAAO,MAAM,qBAAqB;;;;;;;;;;iBAWhC,CAAA;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;iBAIjC,CAAA;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AACnE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA"}
package/dist/report.js ADDED
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export const VALIDATION_SEVERITY = ['error', 'warning', 'info'];
3
+ export const validationIssueSchema = z.object({
4
+ /** 规则标识,例如 no-forbidden-tag、css-property-not-whitelisted。 */
5
+ rule: z.string(),
6
+ /** 严重程度:error 必须修复;warning 建议处理;info 仅供参考。 */
7
+ severity: z.enum(VALIDATION_SEVERITY),
8
+ /** 面向 Agent 的中文说明:发生了什么、为什么。 */
9
+ message: z.string(),
10
+ /** 可直接执行的修复建议(面向 LLM 优化措辞)。 */
11
+ suggestion: z.string(),
12
+ /** 问题位置,形如 `img#2` 或 `body > p:nth-of-type(3)` 或属性名。 */
13
+ location: z.string(),
14
+ });
15
+ export const validationReportSchema = z.object({
16
+ /** 是否全部通过(无 error 级别问题)。 */
17
+ pass: z.boolean(),
18
+ issues: z.array(validationIssueSchema),
19
+ });
20
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,CAAU,CAAA;AAGxE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,6DAA6D;IAC7D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,8CAA8C;IAC9C,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC;IACrC,gCAAgC;IAChC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,+BAA+B;IAC/B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,wDAAwD;IACxD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;CACrB,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,4BAA4B;IAC5B,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;IACjB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC;CACvC,CAAC,CAAA"}
@@ -0,0 +1,16 @@
1
+ import type { ValidationReport } from './report.js';
2
+ interface HastNode {
3
+ type: string;
4
+ tagName?: string;
5
+ value?: string;
6
+ properties?: Record<string, unknown>;
7
+ children?: HastNode[];
8
+ }
9
+ /** 把 HTML 字符串解析为 hast 根节点。 */
10
+ export declare function parseHtml(html: string): HastNode;
11
+ /**
12
+ * 校验一段 HTML 是否可安全发布到微信公众号,返回结构化报告(恒不抛错,失败信息写入 issues)。
13
+ */
14
+ export declare function validateHtml(html: string): ValidationReport;
15
+ export {};
16
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAmB,gBAAgB,EAAE,MAAM,aAAa,CAAA;AA8CpE,UAAU,QAAQ;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAA;CACtB;AAoKD,8BAA8B;AAC9B,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAIhD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CA4D3D"}
@@ -0,0 +1,265 @@
1
+ /**
2
+ * 微信兼容性校验器:对渲染后的 HTML 做三类检查(CSS 白名单 / HTML 结构 / 内容)。
3
+ * 输出结构化报告 { pass, issues: [{ rule, severity, message, suggestion, location }] }。
4
+ * 同构:不依赖 DOM / Node 独有 API。
5
+ */
6
+ import { unified } from 'unified';
7
+ import rehypeParse from 'rehype-parse';
8
+ import { parseStyleDeclarations, classifyCssProperty, findUnsafeCssValue, } from '@stylewx/theme';
9
+ import { validationReportSchema } from './report.js';
10
+ /** 微信编辑器会过滤/不支持的标签。 */
11
+ const FORBIDDEN_TAGS = new Set([
12
+ 'script',
13
+ 'style',
14
+ 'iframe',
15
+ 'object',
16
+ 'embed',
17
+ 'applet',
18
+ 'canvas',
19
+ 'form',
20
+ 'input',
21
+ 'select',
22
+ 'textarea',
23
+ 'button',
24
+ 'link',
25
+ 'meta',
26
+ 'base',
27
+ 'frame',
28
+ 'frameset',
29
+ 'audio',
30
+ 'video',
31
+ ]);
32
+ /** 微信允许的图片域名(上传素材库后即此类域名)。 */
33
+ const ALLOWED_IMAGE_HOSTS = ['mmbiz.qpic.cn', 'wx.qlogo.cn', 'mp.weixin.qq.com'];
34
+ /** 正文文本长度上限的软告警阈值(字符数)。 */
35
+ const WORD_COUNT_WARNING = 30000;
36
+ const FORBIDDEN_TAG_SUGGESTION = (tag) => `「${tag}」标签微信编辑器会过滤,无法保留。请删除,或改用更基础的排版结构(如引用、图片、段落)表达。`;
37
+ const EVENT_HANDLER_SUGGESTION = '事件属性(on*) 会被微信过滤且存在安全风险,请删除该属性。';
38
+ const JS_URL_SUGGESTION = 'javascript: 链接在微信内会被拦截,请改为正常的 http(s) 链接或删除。';
39
+ const STYLE_TAG_SUGGESTION = '<style> 标签微信编辑器会丢弃,请在渲染阶段使用 juice 把样式内联到元素上,不要输出 <style>。';
40
+ const CSS_PROP_SUGGESTION = (prop) => `属性「${prop}」已被真实微信实测会过滤(如 position / filter 等),请从主题中移除,或改用微信保留的基础属性表达。`;
41
+ const IMAGE_SUGGESTION = '外链图片微信会被拦截/无法显示。请先上传到微信公众号素材库(或使用图床得到 mmbiz.qpic.cn 链接)后再发布。';
42
+ function isElement(node) {
43
+ return node.type === 'element' && typeof node.tagName === 'string';
44
+ }
45
+ function textOf(node) {
46
+ if (node.type === 'text')
47
+ return node.value ?? '';
48
+ if (node.type === 'comment')
49
+ return '';
50
+ let out = '';
51
+ if (node.children)
52
+ for (const c of node.children)
53
+ out += textOf(c);
54
+ return out;
55
+ }
56
+ function countWords(text) {
57
+ const cjk = text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g)?.length ?? 0;
58
+ const latin = text.replace(/[\u4e00-\u9fff\u3400-\u4dbf]/g, ' ').match(/[a-zA-Z0-9]+/g)?.length ?? 0;
59
+ return cjk + latin;
60
+ }
61
+ function isAllowedImageUrl(src) {
62
+ const s = src.trim();
63
+ if (s.startsWith('data:image/'))
64
+ return true;
65
+ if (s.startsWith('//')) {
66
+ const host = s.slice(2).split('/')[0]?.toLowerCase() ?? '';
67
+ return ALLOWED_IMAGE_HOSTS.includes(host);
68
+ }
69
+ try {
70
+ const url = new URL(s);
71
+ const host = url.hostname.toLowerCase();
72
+ return (host === 'mmbiz.qpic.cn' ||
73
+ host.endsWith('.qpic.cn') ||
74
+ ALLOWED_IMAGE_HOSTS.includes(host));
75
+ }
76
+ catch {
77
+ // 非 URL(相对路径等)按外链处理,提示上传素材库。
78
+ return false;
79
+ }
80
+ }
81
+ function walk(node, location, state) {
82
+ if (node.type === 'text') {
83
+ state.text += node.value ?? '';
84
+ return;
85
+ }
86
+ if (!isElement(node))
87
+ return;
88
+ const tag = node.tagName.toLowerCase();
89
+ const tagLocation = `${location} > ${tag}`;
90
+ // 1. 禁止标签
91
+ if (FORBIDDEN_TAGS.has(tag)) {
92
+ state.issues.push({
93
+ rule: 'no-forbidden-tag',
94
+ severity: 'error',
95
+ message: `检测到微信不支持的标签「<${tag}>」。`,
96
+ suggestion: FORBIDDEN_TAG_SUGGESTION(tag),
97
+ location: tagLocation,
98
+ });
99
+ }
100
+ // 2. 事件属性 & 危险属性
101
+ for (const [attr, raw] of Object.entries(node.properties)) {
102
+ const key = attr.toLowerCase();
103
+ if (key.startsWith('on')) {
104
+ state.issues.push({
105
+ rule: 'no-event-handler-attr',
106
+ severity: 'error',
107
+ message: `属性「${attr}」是事件处理函数,微信会过滤且存在安全风险。`,
108
+ suggestion: EVENT_HANDLER_SUGGESTION,
109
+ location: `${tagLocation}[${attr}]`,
110
+ });
111
+ }
112
+ if (key === 'srcdoc') {
113
+ state.issues.push({
114
+ rule: 'no-dangerous-attr',
115
+ severity: 'error',
116
+ message: `属性「${attr}」可能导致 HTML 注入。`,
117
+ suggestion: '请删除该属性。',
118
+ location: `${tagLocation}[${attr}]`,
119
+ });
120
+ }
121
+ if (typeof raw === 'string' && /^javascript:/i.test(raw.trim())) {
122
+ state.issues.push({
123
+ rule: 'no-dangerous-url',
124
+ severity: 'error',
125
+ message: `发现了 javascript: 链接,微信内会被拦截。`,
126
+ suggestion: JS_URL_SUGGESTION,
127
+ location: `${tagLocation}[${attr}]`,
128
+ });
129
+ }
130
+ }
131
+ // 3. 内联样式 -> CSS 三档校验(safe 放行 / gray、unknown 警告 / banned 错误)
132
+ const styleValue = node.properties.style;
133
+ if (typeof styleValue === 'string' && styleValue.trim()) {
134
+ for (const decl of parseStyleDeclarations(styleValue)) {
135
+ const tier = classifyCssProperty(decl.property);
136
+ if (tier === 'banned') {
137
+ state.issues.push({
138
+ rule: 'css-property-banned',
139
+ severity: 'error',
140
+ message: `内联样式使用了真实微信实测会过滤的属性「${decl.property}」。`,
141
+ suggestion: CSS_PROP_SUGGESTION(decl.property),
142
+ location: `${tagLocation}@${decl.property}`,
143
+ });
144
+ }
145
+ else if (tier === 'gray') {
146
+ state.issues.push({
147
+ rule: 'css-property-gray',
148
+ severity: 'warning',
149
+ message: `内联样式使用了灰色属性「${decl.property}」:微信草稿 API 实测会保留,但编辑器粘贴/读者渲染仍不确定。`,
150
+ suggestion: '若需在微信里稳定呈现,建议改用基础排版属性表达;否则可保留(如有问题可在编辑器里再调整)。',
151
+ location: `${tagLocation}@${decl.property}`,
152
+ });
153
+ }
154
+ else if (tier === 'unknown') {
155
+ state.issues.push({
156
+ rule: 'css-property-unknown',
157
+ severity: 'warning',
158
+ message: `内联样式使用了未纳入已知白名单的属性「${decl.property}」,微信兼容性未确认。`,
159
+ suggestion: '建议确认该属性在微信端的表现;若不放心,用已验证的基础属性表达。',
160
+ location: `${tagLocation}@${decl.property}`,
161
+ });
162
+ }
163
+ const unsafe = findUnsafeCssValue(decl.value);
164
+ if (unsafe) {
165
+ state.issues.push({
166
+ rule: 'css-value-unsafe',
167
+ severity: 'warning',
168
+ message: `属性「${decl.property}」的值包含微信表现不稳定的内容:${unsafe}`,
169
+ suggestion: unsafe,
170
+ location: `${tagLocation}@${decl.property}`,
171
+ });
172
+ }
173
+ }
174
+ }
175
+ // 4. 图片检查
176
+ if (tag === 'img') {
177
+ state.imageCount += 1;
178
+ const src = node.properties.src;
179
+ if (typeof src === 'string' && src.trim() && !isAllowedImageUrl(src)) {
180
+ state.issues.push({
181
+ rule: 'external-image',
182
+ severity: 'warning',
183
+ message: '检测到外链图片(非微信素材库域名)。',
184
+ suggestion: IMAGE_SUGGESTION,
185
+ location: `${tagLocation}#${src.slice(0, 60)}`,
186
+ });
187
+ }
188
+ }
189
+ // 递归
190
+ if (node.children) {
191
+ for (const child of node.children)
192
+ walk(child, tagLocation, state);
193
+ }
194
+ }
195
+ /** 把 HTML 字符串解析为 hast 根节点。 */
196
+ export function parseHtml(html) {
197
+ const processor = unified().use(rehypeParse, { fragment: true });
198
+ const root = processor.parse(html);
199
+ return root;
200
+ }
201
+ /**
202
+ * 校验一段 HTML 是否可安全发布到微信公众号,返回结构化报告(恒不抛错,失败信息写入 issues)。
203
+ */
204
+ export function validateHtml(html) {
205
+ const state = { issues: [], imageCount: 0, text: '' };
206
+ let root;
207
+ try {
208
+ root = parseHtml(html);
209
+ }
210
+ catch (error) {
211
+ return {
212
+ pass: false,
213
+ issues: [
214
+ {
215
+ rule: 'unparseable-html',
216
+ severity: 'error',
217
+ message: `HTML 无法解析:${error instanceof Error ? error.message : String(error)}`,
218
+ suggestion: '请检查并修正 HTML 语法后再校验。',
219
+ location: '(document)',
220
+ },
221
+ ],
222
+ };
223
+ }
224
+ for (const child of root.children ?? [])
225
+ walk(child, '(document)', state);
226
+ // 5. 图片数量
227
+ if (state.imageCount > 15) {
228
+ state.issues.push({
229
+ rule: 'image-count-high',
230
+ severity: 'warning',
231
+ message: `图片数量较多(${state.imageCount} 张),可能影响加载与阅读体验。`,
232
+ suggestion: '建议精简配图(如压缩体积、删除冗余图),或将长图拆分为多张。',
233
+ location: '(document)',
234
+ });
235
+ }
236
+ if (state.imageCount === 0) {
237
+ state.issues.push({
238
+ rule: 'image-count-zero',
239
+ severity: 'info',
240
+ message: '正文中未检测到图片。',
241
+ suggestion: '若内容需要配图,请补充;纯文字文章可忽略此提示。',
242
+ location: '(document)',
243
+ });
244
+ }
245
+ // 6. 正文字数上限提示
246
+ const words = countWords(state.text);
247
+ if (words > WORD_COUNT_WARNING) {
248
+ state.issues.push({
249
+ rule: 'word-count-limit',
250
+ severity: 'warning',
251
+ message: `正文字数约 ${words} 字,已超过 ${WORD_COUNT_WARNING} 字的软上限。`,
252
+ suggestion: '考虑压缩内容或拆分为多篇发布;若必需,请确认编辑器可完整承载。',
253
+ location: '(document)',
254
+ });
255
+ }
256
+ const pass = state.issues.every((issue) => issue.severity !== 'error');
257
+ const report = { pass, issues: state.issues };
258
+ try {
259
+ return validationReportSchema.parse(report);
260
+ }
261
+ catch {
262
+ return report;
263
+ }
264
+ }
265
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.test.d.ts","sourceRoot":"","sources":["../src/validator.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateHtml, parseHtml } from './index.js';
3
+ function issuesForRule(report, rule) {
4
+ return report.issues.filter((i) => i.rule === rule);
5
+ }
6
+ describe('HTML 结构校验', () => {
7
+ it('禁用的标签被识别(script/style/iframe 等)', () => {
8
+ const html = '<section><script>alert(1)</script><style>a{}</style><iframe src="x"></iframe></section>';
9
+ const r = validateHtml(html);
10
+ expect(issuesForRule(r, 'no-forbidden-tag').length).toBe(3);
11
+ expect(r.pass).toBe(false);
12
+ });
13
+ it('事件属性(on*)被拒绝', () => {
14
+ const html = '<section><p onclick="alert(1)">text</p></section>';
15
+ const r = validateHtml(html);
16
+ const issues = issuesForRule(r, 'no-event-handler-attr');
17
+ expect(issues).toHaveLength(1);
18
+ expect(issues[0]?.suggestion).toContain('删除');
19
+ });
20
+ it('javascript: 链接被拒绝', () => {
21
+ const html = '<section><a href="javascript:void(0)">x</a></section>';
22
+ const r = validateHtml(html);
23
+ expect(issuesForRule(r, 'no-dangerous-url').length).toBe(1);
24
+ });
25
+ });
26
+ describe('CSS 白名单校验(内联样式)', () => {
27
+ it('真实微信会过滤的属性(banned)被拒绝为 error', () => {
28
+ const html = '<section><p style="position: absolute; color: red;">x</p></section>';
29
+ const r = validateHtml(html);
30
+ const issues = issuesForRule(r, 'css-property-banned');
31
+ expect(issues).toHaveLength(1);
32
+ expect(issues[0]?.severity).toBe('error');
33
+ expect(issues[0]?.message).toContain('position');
34
+ expect(issues[0]?.suggestion).toContain('移除');
35
+ expect(r.pass).toBe(false);
36
+ });
37
+ it('灰色属性(transform)降级为 warning,不阻断 pass', () => {
38
+ const html = '<section><p style="transform: rotate(3deg); color: red;">x</p></section>';
39
+ const r = validateHtml(html);
40
+ const issues = issuesForRule(r, 'css-property-gray');
41
+ expect(issues).toHaveLength(1);
42
+ expect(issues[0]?.severity).toBe('warning');
43
+ expect(r.pass).toBe(true);
44
+ });
45
+ it('白名单内属性通过', () => {
46
+ const html = '<section><p style="color: #333; font-size: 15px; margin: 0;">x</p></section>';
47
+ const r = validateHtml(html);
48
+ expect(issuesForRule(r, 'css-property-not-whitelisted')).toHaveLength(0);
49
+ });
50
+ it('!important 与外部 url() 被标记为 warning(css-value-unsafe)', () => {
51
+ const html = '<section><p style="color: red !important; background-image: url(https://evil/a.png);">x</p></section>';
52
+ const r = validateHtml(html);
53
+ const issues = issuesForRule(r, 'css-value-unsafe');
54
+ expect(issues.length).toBeGreaterThanOrEqual(2);
55
+ expect(issues.every((i) => i.severity === 'warning')).toBe(true);
56
+ expect(r.pass).toBe(true);
57
+ });
58
+ });
59
+ describe('内容校验', () => {
60
+ it('外链图片被标记为需要上传素材库', () => {
61
+ const html = '<section><img src="https://example.com/a.jpg"></section>';
62
+ const r = validateHtml(html);
63
+ const issues = issuesForRule(r, 'external-image');
64
+ expect(issues).toHaveLength(1);
65
+ expect(issues[0]?.suggestion).toContain('素材库');
66
+ });
67
+ it('微信素材库图片域名通过', () => {
68
+ const html = '<section><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/abc/0?wx_fmt=png"></section>';
69
+ const r = validateHtml(html);
70
+ expect(issuesForRule(r, 'external-image')).toHaveLength(0);
71
+ });
72
+ it('data URI 图片通过', () => {
73
+ const html = '<section><img src="data:image/png;base64,xxxx"></section>';
74
+ const r = validateHtml(html);
75
+ expect(issuesForRule(r, 'external-image')).toHaveLength(0);
76
+ });
77
+ it('图片数量过多时告警', () => {
78
+ const imgs = '<img src="https://mmbiz.qpic.cn/a">'.repeat(16);
79
+ const r = validateHtml(`<section>${imgs}</section>`);
80
+ expect(issuesForRule(r, 'image-count-high')).toHaveLength(1);
81
+ });
82
+ it('无图片时给出 info 提示', () => {
83
+ const r = validateHtml('<section><p>纯文字</p></section>');
84
+ expect(issuesForRule(r, 'image-count-zero')).toHaveLength(1);
85
+ });
86
+ it('超长正文给出软上限提示', () => {
87
+ const longText = '字'.repeat(32000);
88
+ const r = validateHtml(`<section><p>${longText}</p></section>`);
89
+ expect(issuesForRule(r, 'word-count-limit')).toHaveLength(1);
90
+ });
91
+ });
92
+ describe('报告格式', () => {
93
+ it('无错误时 pass 为 true', () => {
94
+ const r = validateHtml('<section><p style="color:#333">ok</p></section>');
95
+ expect(r.pass).toBe(true);
96
+ // 仅无图片 info 提示,不应导致 pass = false
97
+ expect(r.issues.every((i) => i.severity !== 'error')).toBe(true);
98
+ });
99
+ it('含有 error 时 pass 为 false', () => {
100
+ const r = validateHtml('<section><script>x</script></section>');
101
+ expect(r.pass).toBe(false);
102
+ });
103
+ it('每条 issue 都包含 rule/severity/message/suggestion/location', () => {
104
+ const r = validateHtml('<section><img src="https://evil.com/a.png"></section>');
105
+ for (const issue of r.issues) {
106
+ expect(issue.rule).toBeTruthy();
107
+ expect(['error', 'warning', 'info']).toContain(issue.severity);
108
+ expect(issue.message).toBeTruthy();
109
+ expect(issue.suggestion).toBeTruthy();
110
+ expect(issue.location).toBeTruthy();
111
+ }
112
+ });
113
+ it('超长正文的软上限提示', () => {
114
+ const r = validateHtml(`<section><p>${'字'.repeat(32000)}</p></section>`);
115
+ expect(r.issues.some((i) => i.location === '(document)' && i.rule === 'word-count-limit')).toBe(true);
116
+ });
117
+ it('损坏 HTML 不抛错,返回 unparseable 错误', () => {
118
+ const r = validateHtml('<section><p');
119
+ // 解析容错,通常能作为文本恢复;若非则回退到 unparseable
120
+ expect(typeof r.pass).toBe('boolean');
121
+ });
122
+ });
123
+ describe('parseHtml', () => {
124
+ it('返回 hast 根节点', () => {
125
+ const root = parseHtml('<section><p>hi</p></section>');
126
+ expect(root.type).toBe('root');
127
+ expect(root.children?.[0]?.type).toBe('element');
128
+ });
129
+ });
130
+ //# sourceMappingURL=validator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.test.js","sourceRoot":"","sources":["../src/validator.test.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@stylewx/validator",
3
+ "version": "0.1.0",
4
+ "description": "stylewx validator: WeChat compatibility checker (CSS whitelist, HTML structure, content limits). Outputs a structured report. Isomorphic (no DOM / no Node-only APIs).",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "sideEffects": false,
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "type-check": "tsc -p tsconfig.json --noEmit",
23
+ "prepublishOnly": "pnpm build"
24
+ },
25
+ "dependencies": {
26
+ "@stylewx/theme": "workspace:*",
27
+ "rehype-parse": "^9.0.1",
28
+ "unified": "^11.0.5",
29
+ "zod": "^4.0.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }