11import { mount } from '@vue/test-utils' ;
2- import { describe , expect , it , vi } from 'vitest' ;
2+ import type { VueWrapper } from '@vue/test-utils' ;
3+ import { describe , expect , it , vi , beforeEach } from 'vitest' ;
34import {
45 CloseIcon ,
56 AppIcon ,
@@ -8,89 +9,148 @@ import {
89 InfoCircleFilledIcon ,
910} from 'tdesign-icons-vue-next' ;
1011import { Fragment , nextTick } from 'vue' ;
12+ import log from '@tdesign/common-js/log/index' ;
1113import { Alert } from '@tdesign/components/alert' ;
1214
15+ const mock = vi . hoisted ( ( ) => {
16+ const store = { handler : null } as { handler : ( ( e : any ) => void ) | null } ;
17+ const onMock = vi . fn ( ( el : Element , evt : string , handler : ( e : any ) => void ) => {
18+ if ( evt === 'transitionend' ) {
19+ store . handler = handler ;
20+ }
21+ } ) ;
22+ const addClassMock = vi . fn ( ( el : Element | null , cls : string ) => {
23+ if ( el ) {
24+ el . classList . add ( cls ) ;
25+ }
26+ } ) ;
27+ return {
28+ store,
29+ onMock,
30+ addClassMock,
31+ } ;
32+ } ) ;
33+
34+ vi . mock ( '@tdesign/shared-utils' , async ( importOriginal ) => {
35+ const actual = await importOriginal < any > ( ) ;
36+ return {
37+ ...actual ,
38+ on : mock . onMock ,
39+ addClass : mock . addClassMock ,
40+ } ;
41+ } ) ;
42+
43+ function setOffsetHeight ( el : Element , height : number ) {
44+ Object . defineProperty ( el , 'offsetHeight' , {
45+ value : height ,
46+ configurable : true ,
47+ } ) ;
48+ }
49+
1350describe ( 'Alert' , ( ) => {
14- describe ( ':props' , ( ) => {
15- it ( ':default' , ( ) => {
16- const wrapper = mount ( ( ) => < Alert > text</ Alert > ) ;
17- expect ( wrapper . find ( '.t-alert__description' ) . text ( ) ) . toBe ( 'text' ) ;
51+ describe ( 'props' , ( ) => {
52+ let wrapper : VueWrapper | null = null ;
53+
54+ beforeEach ( ( ) => {
55+ wrapper = mount ( ( ) => < Alert > text</ Alert > ) ;
56+ } ) ;
57+
58+ it ( 'default' , ( ) => {
59+ expect ( wrapper . find ( '.t-alert__description' ) . text ( ) ) . eq ( 'text' ) ;
1860 } ) ;
1961
20- it ( ':closeBtn' , ( ) => {
21- const wrapper = mount ( ( ) => < Alert closeBtn > text</ Alert > ) ;
62+ it ( 'closeBtn[boolean]' , async ( ) => {
63+ expect ( wrapper . find ( '.t-alert__close' ) . exists ( ) ) . eq ( false ) ;
64+
65+ const wrapperWithClose = mount ( ( ) => < Alert closeBtn > text</ Alert > ) ;
66+ const close = wrapperWithClose . find ( '.t-alert__close' ) ;
67+ expect ( close . exists ( ) ) . eq ( true ) ;
68+ expect ( wrapperWithClose . findComponent ( CloseIcon ) . exists ( ) ) . eq ( true ) ;
69+ } ) ;
70+
71+ it ( 'closeBtn[string]' , ( ) => {
72+ const wrapper = mount ( ( ) => < Alert closeBtn = "关闭" > text</ Alert > ) ;
73+ const close = wrapper . find ( '.t-alert__close' ) ;
74+ expect ( close . exists ( ) ) . eq ( true ) ;
75+ expect ( close . text ( ) ) . eq ( '关闭' ) ;
76+ } ) ;
77+
78+ it ( 'closeBtn[slot]' , ( ) => {
79+ const slots = {
80+ closeBtn : ( ) => < span > 自定义关闭</ span > ,
81+ } ;
82+ const wrapper = mount ( ( ) => < Alert v-slots = { slots } > text</ Alert > ) ;
2283 const close = wrapper . find ( '.t-alert__close' ) ;
23- expect ( close . exists ( ) ) . toBeTruthy ( ) ;
24- expect ( wrapper . findComponent ( CloseIcon ) . exists ( ) ) . toBeTruthy ( ) ;
84+ expect ( close . exists ( ) ) . eq ( true ) ;
85+ expect ( close . text ( ) ) . eq ( '自定义关闭' ) ;
2586 } ) ;
2687
27- it ( ': icon' , ( ) => {
88+ it ( 'icon[slot] ' , ( ) => {
2889 const slots = {
2990 icon : ( ) => < AppIcon /> ,
3091 } ;
3192 const wrapper = mount ( ( ) => < Alert v-slots = { slots } > text</ Alert > ) ;
3293 const icon = wrapper . find ( '.t-alert__icon' ) ;
33- expect ( icon . exists ( ) ) . toBeTruthy ( ) ;
34- expect ( wrapper . findComponent ( AppIcon ) . exists ( ) ) . toBeTruthy ( ) ;
94+ expect ( icon . exists ( ) ) . eq ( true ) ;
95+ expect ( wrapper . findComponent ( AppIcon ) . exists ( ) ) . eq ( true ) ;
96+ } ) ;
97+
98+ it ( 'icon[function]' , ( ) => {
99+ const wrapper = mount ( ( ) => < Alert icon = { ( ) => < AppIcon /> } > text</ Alert > ) ;
100+ const icon = wrapper . find ( '.t-alert__icon' ) ;
101+ expect ( icon . exists ( ) ) . eq ( true ) ;
102+ expect ( wrapper . findComponent ( AppIcon ) . exists ( ) ) . eq ( true ) ;
35103 } ) ;
36104
37- it ( ': message' , ( ) => {
105+ it ( 'message[string] ' , ( ) => {
38106 const wrapper = mount ( ( ) => < Alert message = "this is message" > </ Alert > ) ;
39107 const description = wrapper . find ( '.t-alert__message .t-alert__description' ) ;
40- expect ( description . exists ( ) ) . toBeTruthy ( ) ;
41- expect ( description . text ( ) ) . toBe ( 'this is message' ) ;
108+ expect ( description . exists ( ) ) . eq ( true ) ;
109+ expect ( description . text ( ) ) . eq ( 'this is message' ) ;
42110 } ) ;
43111
44- it ( ': title' , ( ) => {
112+ it ( 'title[string] ' , ( ) => {
45113 const wrapper = mount ( ( ) => < Alert title = "this is title" > text</ Alert > ) ;
46114 const title = wrapper . find ( '.t-alert__title' ) ;
47- expect ( title . exists ( ) ) . toBeTruthy ( ) ;
48- expect ( title . text ( ) ) . toBe ( 'this is title' ) ;
115+ expect ( title . exists ( ) ) . eq ( true ) ;
116+ expect ( title . text ( ) ) . eq ( 'this is title' ) ;
49117 } ) ;
50118
51- it ( ': operation' , ( ) => {
119+ it ( 'operation[slot] ' , ( ) => {
52120 const slots = {
53121 operation : ( ) => < Fragment > this is operation</ Fragment > ,
54122 } ;
55123 const wrapper = mount ( ( ) => < Alert v-slots = { slots } > text</ Alert > ) ;
56124 const operation = wrapper . find ( '.t-alert__operation' ) ;
57- expect ( operation . exists ( ) ) . toBeTruthy ( ) ;
58- expect ( operation . text ( ) ) . toBe ( 'this is operation' ) ;
59- } ) ;
60-
61- it ( ':theme:info' , ( ) => {
62- const wrapper = mount ( ( ) => < Alert theme = "info" message = "text" /> ) ;
63- const alert = wrapper . find ( '.t-alert' ) ;
64- const icon = wrapper . find ( '.t-alert__icon' ) ;
65- expect ( icon . findComponent ( InfoCircleFilledIcon ) . exists ( ) ) . toBeTruthy ( ) ;
66- expect ( alert . classes ( ) ) . toContain ( 't-alert--info' ) ;
125+ expect ( operation . exists ( ) ) . eq ( true ) ;
126+ expect ( operation . text ( ) ) . eq ( 'this is operation' ) ;
67127 } ) ;
68128
69- it ( ':theme:success' , ( ) => {
70- const wrapper = mount ( ( ) => < Alert theme = "success" message = "text" /> ) ;
71- const alert = wrapper . find ( '.t-alert' ) ;
72- const icon = wrapper . find ( '.t-alert__icon' ) ;
73- expect ( icon . findComponent ( CheckCircleFilledIcon ) . exists ( ) ) . toBeTruthy ( ) ;
74- expect ( alert . classes ( ) ) . toContain ( 't-alert--success' ) ;
129+ it ( 'operation[function]' , ( ) => {
130+ const wrapper = mount ( ( ) => < Alert operation = { ( ) => < Fragment > op-area</ Fragment > } > text</ Alert > ) ;
131+ const operation = wrapper . find ( '.t-alert__operation' ) ;
132+ expect ( operation . exists ( ) ) . eq ( true ) ;
133+ expect ( operation . text ( ) ) . eq ( 'op-area' ) ;
75134 } ) ;
76135
77- it ( ': theme:warning ' , ( ) => {
78- const wrapper = mount ( ( ) => < Alert theme = "warning" message = "text" /> ) ;
79- const alert = wrapper . find ( '.t-alert' ) ;
80- const icon = wrapper . find ( '.t-alert__icon' ) ;
81- expect ( icon . findComponent ( ErrorCircleFilledIcon ) . exists ( ) ) . toBeTruthy ( ) ;
82- expect ( alert . classes ( ) ) . toContain ( 't-alert--warning' ) ;
83- } ) ;
136+ it ( 'theme[string] ' , ( ) => {
137+ const themes = [
138+ { theme : 'info' , icon : InfoCircleFilledIcon } ,
139+ { theme : 'success' , icon : CheckCircleFilledIcon } ,
140+ { theme : 'warning' , icon : ErrorCircleFilledIcon } ,
141+ { theme : 'error' , icon : ErrorCircleFilledIcon } ,
142+ ] as const ;
84143
85- it ( ':theme:error' , ( ) => {
86- const wrapper = mount ( ( ) => < Alert theme = "error" message = "text" /> ) ;
87- const alert = wrapper . find ( '.t-alert' ) ;
88- const icon = wrapper . find ( '.t-alert__icon' ) ;
89- expect ( icon . findComponent ( ErrorCircleFilledIcon ) . exists ( ) ) . toBeTruthy ( ) ;
90- expect ( alert . classes ( ) ) . toContain ( 't-alert--error' ) ;
144+ themes . forEach ( ( { theme, icon } ) => {
145+ const wrapper = mount ( ( ) => < Alert theme = { theme } message = "text" /> ) ;
146+ const alert = wrapper . find ( '.t-alert' ) ;
147+ const iconEl = wrapper . find ( '.t-alert__icon' ) ;
148+ expect ( iconEl . findComponent ( icon ) . exists ( ) ) . eq ( true ) ;
149+ expect ( alert . classes ( ) ) . toContain ( `t-alert--${ theme } ` ) ;
150+ } ) ;
91151 } ) ;
92152
93- it ( ': maxLine' , async ( ) => {
153+ it ( 'maxLine[number] ' , async ( ) => {
94154 const wrapper = mount ( ( ) => (
95155 < Alert title = "this is title" maxLine = { 2 } >
96156 < span > 这是折叠的第一条消息</ span >
@@ -103,17 +163,30 @@ describe('Alert', () => {
103163 ) ) ;
104164 const description = wrapper . find ( '.t-alert__description' ) ;
105165 const collapse = description . find ( '.t-alert__collapse' ) ;
106- expect ( description . element . children . length ) . toBe ( 3 ) ;
107- expect ( collapse . exists ( ) ) . toBeTruthy ( ) ;
108- expect ( collapse . text ( ) ) . toBe ( '展开更多' ) ;
166+ expect ( description . element . children . length ) . eq ( 3 ) ;
167+ expect ( collapse . exists ( ) ) . eq ( true ) ;
168+ expect ( collapse . text ( ) ) . eq ( '展开更多' ) ;
109169 await collapse . trigger ( 'click' ) ;
110- expect ( description . element . children . length ) . toBe ( 7 ) ;
111- expect ( collapse . text ( ) ) . toBe ( '收起' ) ;
170+ expect ( description . element . children . length ) . eq ( 7 ) ;
171+ expect ( collapse . text ( ) ) . eq ( '收起' ) ;
172+ } ) ;
173+
174+ it ( 'maxLine[number] no collapse when maxLine=0' , ( ) => {
175+ const wrapper = mount ( ( ) => (
176+ < Alert maxLine = { 0 } title = "title" >
177+ < span > 1</ span >
178+ < span > 2</ span >
179+ < span > 3</ span >
180+ </ Alert >
181+ ) ) ;
182+ const description = wrapper . find ( '.t-alert__description' ) ;
183+ expect ( description . exists ( ) ) . eq ( true ) ;
184+ expect ( description . find ( '.t-alert__collapse' ) . exists ( ) ) . eq ( false ) ;
112185 } ) ;
113186 } ) ;
114187
115- describe ( ':event ' , ( ) => {
116- it ( ': onClose' , async ( ) => {
188+ describe ( 'events ' , ( ) => {
189+ it ( 'onClose' , async ( ) => {
117190 const fn = vi . fn ( ) ;
118191 const wrapper = mount ( ( ) => (
119192 < Alert closeBtn onClose = { fn } >
@@ -128,4 +201,136 @@ describe('Alert', () => {
128201 expect ( alert . classes ( ) ) . toContain ( 't-alert--closing' ) ;
129202 } ) ;
130203 } ) ;
204+
205+ describe ( 'others' , ( ) => {
206+ it ( 'onClosed not triggered when propertyName is not opacity' , async ( ) => {
207+ const onClosed = vi . fn ( ) ;
208+ const wrapper = mount ( ( ) => (
209+ < Alert closeBtn onClosed = { onClosed } >
210+ text
211+ </ Alert >
212+ ) ) ;
213+ const alertEl = wrapper . find ( '.t-alert' ) ;
214+
215+ mock . store . handler ?.( { propertyName : 'height' , target : alertEl . element } ) ;
216+ await nextTick ( ) ;
217+ expect ( onClosed ) . not . toHaveBeenCalled ( ) ;
218+ expect ( alertEl . classes ( ) ) . not . toContain ( 't-is-hidden' ) ;
219+ } ) ;
220+
221+ it ( 'close (deprecated) triggers warnOnce and renders CloseIcon' , async ( ) => {
222+ const warnSpy = vi . spyOn ( log , 'warnOnce' ) ;
223+ const wrapper = mount ( ( ) => < Alert close > text</ Alert > ) ;
224+ const close = wrapper . find ( '.t-alert__close' ) ;
225+ expect ( close . exists ( ) ) . eq ( true ) ;
226+ expect ( wrapper . findComponent ( CloseIcon ) . exists ( ) ) . eq ( true ) ;
227+ expect ( warnSpy ) . toHaveBeenCalled ( ) ;
228+ warnSpy . mockRestore ( ) ;
229+ } ) ;
230+
231+ it ( 'theme invalid value triggers validator branch' , ( ) => {
232+ const wrapper = mount ( ( ) => < Alert theme = { 'unknown' as any } message = "text" /> ) ;
233+ const alert = wrapper . find ( '.t-alert' ) ;
234+ // 即便 validator 警告,类名仍会跟随传入值,覆盖该分支
235+ expect ( alert . classes ( ) ) . toContain ( 't-alert--unknown' ) ;
236+ } ) ;
237+
238+ it ( 'renderIcon returns null when icon function returns null' , ( ) => {
239+ const wrapper = mount ( ( ) => < Alert icon = { ( ) => null } > text</ Alert > ) ;
240+ // 不应渲染 icon 容器
241+ expect ( wrapper . find ( '.t-alert__icon' ) . exists ( ) ) . eq ( false ) ;
242+ } ) ;
243+
244+ it ( 'renderClose uses "close" slot when provided (isUsingClose = true)' , async ( ) => {
245+ const slots = {
246+ close : ( ) => < span > 关闭插槽</ span > ,
247+ } ;
248+ const wrapper = mount ( ( ) => < Alert v-slots = { slots } > text</ Alert > ) ;
249+ const close = wrapper . find ( '.t-alert__close' ) ;
250+ expect ( close . exists ( ) ) . eq ( true ) ;
251+ expect ( close . text ( ) ) . eq ( '关闭插槽' ) ;
252+ } ) ;
253+
254+ it ( 'renderDescription height short-circuit false branch in collapsed and expanded' , async ( ) => {
255+ const wrapper = mount ( ( ) => (
256+ < Alert title = "desc height test" maxLine = { 2 } >
257+ < span > line1</ span >
258+ < span > line2</ span >
259+ < span > line3</ span >
260+ < span > line4</ span >
261+ </ Alert >
262+ ) ) ;
263+ const description = wrapper . find ( '.t-alert__description' ) ;
264+ // 在 jsdom 中 offsetHeight 通常为 0(falsy),应走 height && ... 的 false 分支,不设置 style.height
265+ expect ( ( description . element as HTMLElement ) . style . height ) . eq ( '' ) ;
266+
267+ // 展开后也应保持未设置 height(覆盖展开路径的 false 分支)
268+ const collapse = description . find ( '.t-alert__collapse' ) ;
269+ expect ( collapse . exists ( ) ) . eq ( true ) ;
270+ await collapse . trigger ( 'click' ) ;
271+ expect ( ( description . element as HTMLElement ) . style . height ) . eq ( '' ) ;
272+ } ) ;
273+
274+ it ( 'props.theme validator falsy branch (val is empty string)' , ( ) => {
275+ const wrapper = mount ( ( ) => < Alert theme = { '' as any } message = "text" /> ) ;
276+ const alert = wrapper . find ( '.t-alert' ) ;
277+ // 传入空字符串使 validator 走 !val 分支;类名将拼接为空后缀
278+ expect ( alert . classes ( ) ) . toContain ( 't-alert--' ) ;
279+ } ) ;
280+
281+ it ( 'renderDescription height truthy branch (collapsed)' , async ( ) => {
282+ const wrapper = mount ( ( ) => (
283+ < Alert title = "height truthy collapsed" maxLine = { 2 } >
284+ < span > line1</ span >
285+ < span > line2</ span >
286+ < span > line3</ span >
287+ < span > line4</ span >
288+ </ Alert >
289+ ) ) ;
290+ const description = wrapper . find ( '.t-alert__description' ) ;
291+ // 设置 description 自身和第一个子元素的 offsetHeight,使 height 判断为真
292+ setOffsetHeight ( description . element , 8 ) ;
293+ const firstChild = description . element . children [ 0 ] as HTMLElement ;
294+ setOffsetHeight ( firstChild , 10 ) ;
295+
296+ // 重新触发渲染以走到高度设置逻辑
297+ await wrapper . vm . $forceUpdate ( ) ;
298+ await new Promise ( ( r ) => setTimeout ( r , 0 ) ) ;
299+
300+ // 折叠态:style.height 应设置为 descHeight(mounted 时记录的 description.offsetHeight)
301+ expect ( ( description . element as HTMLElement ) . style . height ) . eq ( '0px' ) ;
302+ } ) ;
303+
304+ it ( 'renderDescription height truthy branch (expanded)' , async ( ) => {
305+ const wrapper = mount ( ( ) => (
306+ < Alert title = "height truthy expanded" maxLine = { 2 } >
307+ < span > line1</ span >
308+ < span > line2</ span >
309+ < span > line3</ span >
310+ < span > line4</ span >
311+ </ Alert >
312+ ) ) ;
313+ const description = wrapper . find ( '.t-alert__description' ) ;
314+ // 设置 description 自身和第一个子元素的 offsetHeight,使 height 判断为真
315+ setOffsetHeight ( description . element , 8 ) ;
316+ const firstChild = description . element . children [ 0 ] as HTMLElement ;
317+ setOffsetHeight ( firstChild , 10 ) ;
318+
319+ // 点击展开
320+ const collapse = description . find ( '.t-alert__collapse' ) ;
321+ expect ( collapse . exists ( ) ) . toBeTruthy ( ) ;
322+ await collapse . trigger ( 'click' ) ;
323+
324+ // 展开态:style.height = height * (contentLength - maxLine) + descHeight
325+ // contentLength = 4, maxLine = 2, height = 10, descHeight = 8 => 28px
326+ expect ( ( description . element as HTMLElement ) . style . height ) . eq ( '20px' ) ;
327+ } ) ;
328+
329+ it ( 'operation via function still renders (sanity)' , ( ) => {
330+ const wrapper = mount ( ( ) => < Alert operation = { ( ) => < Fragment > op</ Fragment > } > text</ Alert > ) ;
331+ const operation = wrapper . find ( '.t-alert__operation' ) ;
332+ expect ( operation . exists ( ) ) . eq ( true ) ;
333+ expect ( operation . text ( ) ) . eq ( 'op' ) ;
334+ } ) ;
335+ } ) ;
131336} ) ;
0 commit comments