返回 oh-my-ppt
html-utils-animation-sequence.test.ts
根目录 / tests / unit / tools / html-utils-animation-sequence.test.ts
1 import { describe, expect, it } from 'vitest'
2
3 import { validateHtmlContent } from '../../../src/main/presentation/html/html-utils'
4
5 describe('validateHtmlContent declarative animation controls', () => {
6 it('allows declarative data-anim-stagger and data-anim-sequence values', () => {
7 const result = validateHtmlContent(`
8 <div>
9 <div data-anim="fade-up" data-anim-stagger="80">A</div>
10 <div data-anim="fade" data-anim-sequence="with" data-anim-delay="60">B</div>
11 <div data-anim="fade-up" data-anim-sequence="after">C</div>
12 </div>
13 `)
14
15 expect(result.valid).toBe(true)
16 })
17
18 it('rejects invalid data-anim-sequence values', () => {
19 const result = validateHtmlContent(`
20 <div data-anim="fade-up" data-anim-sequence="together">A</div>
21 `)
22
23 expect(result.errors.join('\n')).toContain('data-anim-sequence 仅支持 with/after')
24 })
25
26 it('rejects invalid data-anim-stagger values', () => {
27 const result = validateHtmlContent(`
28 <div data-anim="fade-up" data-anim-stagger="-10">A</div>
29 `)
30
31 expect(result.errors.join('\n')).toContain('data-anim-stagger 必须是大于等于 0 的数字毫秒值')
32 })
33
34 it('allows contiguous click-group usage on click-triggered animations', () => {
35 const result = validateHtmlContent(`
36 <div>
37 <div data-anim="fade-up" data-anim-trigger="click" data-anim-click-group="reveal">A</div>
38 <div data-anim="fade" data-anim-trigger="click" data-anim-click-group="reveal">B</div>
39 <div data-anim="pulse-soft" data-anim-trigger="click">C</div>
40 </div>
41 `)
42
43 expect(result.valid).toBe(true)
44 })
45
46 it('rejects click-group on non-click animations', () => {
47 const result = validateHtmlContent(`
48 <div data-anim="fade-up" data-anim-click-group="reveal">A</div>
49 `)
50
51 expect(result.errors.join('\n')).toContain('data-anim-click-group 只能用于 click 触发动画')
52 })
53
54 it('rejects non-contiguous click-group reuse across click steps', () => {
55 const result = validateHtmlContent(`
56 <div>
57 <div data-anim="fade-up" data-anim-trigger="click" data-anim-click-group="reveal">A</div>
58 <div data-anim="fade-up" data-anim-trigger="click">B</div>
59 <div data-anim="fade-up" data-anim-trigger="click" data-anim-click-group="reveal">C</div>
60 </div>
61 `)
62
63 expect(result.errors.join('\n')).toContain('data-anim-click-group 必须在 click 动画的 DOM 顺序上连续出现')
64 })
65
66 it('rejects data-anim values outside the public editable contract', () => {
67 const result = validateHtmlContent(`
68 <div data-anim="zoom">A</div>
69 <div data-anim="glitch-in">B</div>
70 `)
71
72 expect(result.errors.join('\n')).toContain('data-anim 仅支持当前公开可编辑动画类型')
73 expect(result.errors.join('\n')).toContain('zoom')
74 expect(result.errors.join('\n')).toContain('glitch-in')
75 })
76
77 it('rejects non-canonical trigger and from aliases in generated content', () => {
78 const result = validateHtmlContent(`
79 <div data-anim="fade-up" data-anim-trigger="on-click" data-anim-from="start">A</div>
80 `)
81
82 expect(result.errors.join('\n')).toContain('data-anim-trigger 仅支持 load/with/after/click')
83 expect(result.errors.join('\n')).toContain('on-click')
84 expect(result.errors.join('\n')).toContain('data-anim-from 仅支持 left/right/top/bottom/center')
85 expect(result.errors.join('\n')).toContain('start')
86 })
87
88 it('allows path only with a non-empty data-anim-path', () => {
89 const valid = validateHtmlContent(`
90 <div data-anim="path" data-anim-path="M 0 0 L 120 30">A</div>
91 `)
92 const missing = validateHtmlContent(`
93 <div data-anim="path">A</div>
94 `)
95 const invalid = validateHtmlContent(`
96 <div data-anim="path" data-anim-path="#curve">A</div>
97 `)
98 const unexpected = validateHtmlContent(`
99 <div data-anim="fade-up" data-anim-path="M 0 0 L 120 30">A</div>
100 `)
101
102 expect(valid.valid).toBe(true)
103 expect(missing.errors.join('\n')).toContain('data-anim="path" 必须同时提供可解析为线性位移的 data-anim-path')
104 expect(invalid.errors.join('\n')).toContain('data-anim="path" 必须同时提供可解析为线性位移的 data-anim-path')
105 expect(unexpected.errors.join('\n')).toContain('只有 data-anim="path" 才能使用 data-anim-path')
106
107 const curved = validateHtmlContent(`
108 <div data-anim="path" data-anim-path="M 0 0 C 10 20 30 40 50 60">A</div>
109 `)
110 expect(curved.errors.join('\n')).toContain('data-anim="path" 必须同时提供可解析为线性位移的 data-anim-path')
111 })
112
113 it('allows bounded duration and delay values from the public contract', () => {
114 const result = validateHtmlContent(`
115 <div
116 data-anim="pulse"
117 data-anim-duration="600"
118 data-anim-delay="stagger(80)"
119 >A</div>
120 `)
121
122 expect(result.valid).toBe(true)
123 })
124
125 it('rejects invalid duration and delay values', () => {
126 const result = validateHtmlContent(`
127 <div
128 data-anim="pulse"
129 data-anim-duration="80"
130 data-anim-delay="-10"
131 >A</div>
132 `)
133
134 const joined = result.errors.join('\n')
135 expect(joined).toContain('data-anim-duration 必须是 100-5000 的数字毫秒值')
136 expect(joined).toContain('data-anim-delay 必须是大于等于 0 的数字毫秒值或 stagger(N)')
137 })
138
139 it('rejects runtime-only easing, repeat, and direction knobs in normal editable content', () => {
140 const result = validateHtmlContent(`
141 <div
142 data-anim="pulse"
143 data-anim-easing="easeOutCubic"
144 data-anim-repeat="2"
145 data-anim-direction="alternate"
146 >A</div>
147 `)
148
149 const joined = result.errors.join('\n')
150 expect(joined).toContain('data-anim-easing 当前属于 runtime-only 兼容能力')
151 expect(joined).toContain('data-anim-repeat 当前属于 runtime-only 兼容能力')
152 expect(joined).toContain('data-anim-direction 当前属于 runtime-only 兼容能力')
153 })
154 })
155
155 lines TYPESCRIPT