1+ import { describe , it , expect , vi , beforeEach } from 'vitest'
2+ import { mount } from '@vue/test-utils'
3+ import HeaderTags from '@/components/shared/HeaderTags.vue'
4+
5+ // Mock child components
6+ vi . mock ( '@/components/tags/frameworks/PyTorch.vue' , ( ) => ( {
7+ default : { name : 'PyTorch' , template : '<div>PyTorch</div>' }
8+ } ) )
9+ vi . mock ( '@/components/tags/frameworks/TensorFlow.vue' , ( ) => ( {
10+ default : { name : 'TensorFlow' , template : '<div>TensorFlow</div>' }
11+ } ) )
12+ vi . mock ( '@/components/tags/frameworks/Safetensors.vue' , ( ) => ( {
13+ default : { name : 'Safetensors' , template : '<div>Safetensors</div>' }
14+ } ) )
15+ vi . mock ( '@/components/tags/frameworks/JAX.vue' , ( ) => ( {
16+ default : { name : 'JAX' , template : '<div>JAX</div>' }
17+ } ) )
18+ vi . mock ( '@/components/tags/frameworks/ONNX.vue' , ( ) => ( {
19+ default : { name : 'ONNX' , template : '<div>ONNX</div>' }
20+ } ) )
21+ vi . mock ( '@/components/tags/frameworks/PaddlePaddle.vue' , ( ) => ( {
22+ default : { name : 'PaddlePaddle' , template : '<div>PaddlePaddle</div>' }
23+ } ) )
24+ vi . mock ( '@/components/tags/frameworks/GGUF.vue' , ( ) => ( {
25+ default : { name : 'GGUF' , template : '<div>GGUF</div>' }
26+ } ) )
27+ vi . mock ( '@/components/tags/frameworks/Joblib.vue' , ( ) => ( {
28+ default : { name : 'Joblib' , template : '<div>Joblib</div>' }
29+ } ) )
30+
31+ // Mock window.location
32+ Object . defineProperty ( window , 'location' , {
33+ value : {
34+ origin : 'http://localhost:3000' ,
35+ href : 'http://localhost:3000'
36+ } ,
37+ writable : true
38+ } )
39+
40+ const createWrapper = ( props = { } ) => {
41+ return mount ( HeaderTags , {
42+ props : {
43+ tags : {
44+ task_tags : [ ] ,
45+ framework_tags : [ ] ,
46+ language_tags : [ ] ,
47+ license_tags : [ ] ,
48+ industry_tags : [ ] ,
49+ other_tags : [ ] ,
50+ program_language : [ ] ,
51+ runmode_tags : [ ] ,
52+ scene_tags : [ ] ,
53+ sdk_tags : [ ] ,
54+ hardware_tags : [ ] ,
55+ lwftest_tags : [ ] ,
56+ hhcate01_tags : [ ]
57+ } ,
58+ prefix : '' ,
59+ reportUrl : null ,
60+ ...props
61+ } ,
62+ global : {
63+ components : {
64+ 'SvgIcon' : { template : '<div></div>' } ,
65+ 'TagItem' : {
66+ template : '<span>{{ tag.name }}</span>' ,
67+ props : [ 'tag' ]
68+ } ,
69+ 'MoreTags' : {
70+ template : '<div>+ {{ num }} more</div>' ,
71+ props : [ 'num' , 'target' ]
72+ }
73+ } ,
74+ mocks : {
75+ $t : ( key ) => key
76+ }
77+ }
78+ } )
79+ }
80+
81+ describe ( 'HeaderTags' , ( ) => {
82+ beforeEach ( ( ) => {
83+ vi . clearAllMocks ( )
84+ } )
85+
86+ it ( 'mounts correctly' , ( ) => {
87+ const wrapper = createWrapper ( )
88+ expect ( wrapper . vm ) . toBeDefined ( )
89+ } )
90+
91+ it ( 'renders task tags correctly' , ( ) => {
92+ const taskTags = [
93+ { name : 'computer-vision' , show_name : '计算机视觉' } ,
94+ { name : 'nlp' , show_name : '自然语言处理' }
95+ ]
96+
97+ const wrapper = createWrapper ( {
98+ tags : { task_tags : taskTags }
99+ } )
100+
101+ expect ( wrapper . text ( ) ) . toContain ( 'all.task' )
102+ // Check that task tags are rendered - tag names are displayed (hyphens replaced with spaces)
103+ expect ( wrapper . text ( ) ) . toContain ( 'computer vision' )
104+ expect ( wrapper . text ( ) ) . toContain ( 'nlp' )
105+ } )
106+
107+ it ( 'renders framework tags with correct icons' , ( ) => {
108+ const frameworkTags = [
109+ { name : 'pytorch' } ,
110+ { name : 'tensorflow' } ,
111+ { name : 'onnx' }
112+ ]
113+
114+ const wrapper = createWrapper ( {
115+ tags : { framework_tags : frameworkTags }
116+ } )
117+
118+ expect ( wrapper . text ( ) ) . toContain ( 'all.framework' )
119+ expect ( wrapper . findAll ( 'a' ) . length ) . toBe ( 3 )
120+ } )
121+
122+ it ( 'renders language tags with correct styling' , ( ) => {
123+ const languageTags = [
124+ { name : 'en' , show_name : 'English' , color : '#007bff' , label : 'english' } ,
125+ { name : 'zh' , show_name : '中文' , color : '#28a745' , label : 'chinese' }
126+ ]
127+
128+ const wrapper = createWrapper ( {
129+ tags : { language_tags : languageTags }
130+ } )
131+
132+ expect ( wrapper . text ( ) ) . toContain ( 'all.language' )
133+ const languageLinks = wrapper . findAll ( 'a' )
134+ expect ( languageLinks . length ) . toBe ( 2 )
135+ } )
136+
137+ it ( 'renders license tags with license icon' , ( ) => {
138+ const licenseTags = [
139+ { name : 'MIT' } ,
140+ { name : 'Apache-2.0' }
141+ ]
142+
143+ const wrapper = createWrapper ( {
144+ tags : { license_tags : licenseTags }
145+ } )
146+
147+ expect ( wrapper . text ( ) ) . toContain ( 'License:' )
148+ expect ( wrapper . findAllComponents ( { name : 'SvgIcon' } ) . length ) . toBeGreaterThanOrEqual ( 2 )
149+ } )
150+
151+ it ( 'renders hardware tags with hardware icon' , ( ) => {
152+ const hardwareTags = [
153+ { name : 'GPU' } ,
154+ { name : 'CPU' }
155+ ]
156+
157+ const wrapper = createWrapper ( {
158+ tags : { hardware_tags : hardwareTags }
159+ } )
160+
161+ expect ( wrapper . text ( ) ) . toContain ( 'all.hardware' )
162+ expect ( wrapper . findAllComponents ( { name : 'SvgIcon' } ) . length ) . toBeGreaterThanOrEqual ( 2 )
163+ } )
164+
165+ it ( 'renders SDK tags with MCP icon for mcp_server' , ( ) => {
166+ const sdkTags = [
167+ { name : 'mcp_server' } ,
168+ { name : 'other_sdk' }
169+ ]
170+
171+ const wrapper = createWrapper ( {
172+ tags : { sdk_tags : sdkTags }
173+ } )
174+
175+ expect ( wrapper . text ( ) ) . toContain ( 'all.sdk' )
176+ expect ( wrapper . findAllComponents ( { name : 'SvgIcon' } ) . length ) . toBeGreaterThanOrEqual ( 1 )
177+ } )
178+
179+ it ( 'shows "more tags" component when tags exceed 3' , ( ) => {
180+ const taskTags = [
181+ { name : 'tag1' } ,
182+ { name : 'tag2' } ,
183+ { name : 'tag3' } ,
184+ { name : 'tag4' } ,
185+ { name : 'tag5' }
186+ ]
187+
188+ const wrapper = createWrapper ( {
189+ tags : { task_tags : taskTags }
190+ } )
191+
192+ expect ( wrapper . findComponent ( { name : 'MoreTags' } ) . exists ( ) ) . toBe ( true )
193+ } )
194+
195+ it ( 'displays only first 3 tags initially' , ( ) => {
196+ const taskTags = [
197+ { name : 'tag1' } ,
198+ { name : 'tag2' } ,
199+ { name : 'tag3' } ,
200+ { name : 'tag4' } ,
201+ { name : 'tag5' }
202+ ]
203+
204+ const wrapper = createWrapper ( {
205+ tags : { task_tags : taskTags }
206+ } )
207+
208+ // Initially should show only 3 tags
209+ // theTaskTags is a ref containing { moreTags: ref, theTags: ref }
210+ expect ( wrapper . vm . theTaskTags . theTags . length ) . toBe ( 3 )
211+ expect ( wrapper . vm . theTaskTags . moreTags ) . toBe ( true )
212+ } )
213+
214+ it ( 'shows all tags when viewMoreTargets is called' , async ( ) => {
215+ const taskTags = [
216+ { name : 'tag1' } ,
217+ { name : 'tag2' } ,
218+ { name : 'tag3' } ,
219+ { name : 'tag4' } ,
220+ { name : 'tag5' }
221+ ]
222+
223+ const wrapper = createWrapper ( {
224+ tags : { task_tags : taskTags }
225+ } )
226+
227+ // Call viewMoreTargets
228+ wrapper . vm . viewMoreTargets ( 'task' )
229+ await wrapper . vm . $nextTick ( )
230+
231+ expect ( wrapper . vm . theTaskTags . theTags . length ) . toBe ( 5 )
232+ expect ( wrapper . vm . theTaskTags . moreTags ) . toBe ( false )
233+ } )
234+
235+ it ( 'generates correct tag URLs for different categories' , ( ) => {
236+ const wrapper = createWrapper ( { prefix : 'models/' } )
237+
238+ const taskUrl = wrapper . vm . getTagUrl ( 'task' , 'computer-vision' )
239+ expect ( taskUrl ) . toContain ( '/models/' )
240+ expect ( taskUrl ) . toContain ( 'task=computer-vision' )
241+ expect ( taskUrl ) . toContain ( 'page=1' )
242+
243+ const frameworkUrl = wrapper . vm . getTagUrl ( 'framework' , 'PyTorch' )
244+ expect ( frameworkUrl ) . toContain ( 'framework=pytorch' )
245+ } )
246+
247+ it ( 'generates correct MCP server URLs' , ( ) => {
248+ const wrapper = createWrapper ( { prefix : 'mcps/' } )
249+
250+ const url = wrapper . vm . getTagUrl ( 'sdk' , 'mcp_server' )
251+ expect ( url ) . toContain ( '/mcp/servers' )
252+ expect ( url ) . toContain ( 'sdk=mcp_server' )
253+ } )
254+
255+ it ( 'handles searchByTag correctly' , ( ) => {
256+ const wrapper = createWrapper ( )
257+
258+ // Mock window.location.href setter
259+ const mockHref = vi . fn ( )
260+ Object . defineProperty ( window . location , 'href' , {
261+ set : mockHref
262+ } )
263+
264+ const tag = { name : 'computer-vision' }
265+ wrapper . vm . searchByTag ( tag )
266+
267+ expect ( mockHref ) . toHaveBeenCalledWith (
268+ expect . stringContaining ( 'task=computer-vision' )
269+ )
270+ } )
271+
272+ it ( 'displays report section when reportUrl is provided' , ( ) => {
273+ const wrapper = createWrapper ( {
274+ reportUrl : 'http://example.com/report'
275+ } )
276+
277+ expect ( wrapper . text ( ) ) . toContain ( 'models.report' )
278+ expect ( wrapper . text ( ) ) . toContain ( 'models.reportName' )
279+ expect ( wrapper . findComponent ( { name : 'SvgIcon' } ) . exists ( ) ) . toBe ( true )
280+ } )
281+
282+ it ( 'uses correct locale for tag display names' , ( ) => {
283+ const taskTags = [
284+ { name : 'computer-vision' , show_name : '计算机视觉' }
285+ ]
286+
287+ const wrapper = createWrapper ( {
288+ tags : { task_tags : taskTags }
289+ } )
290+
291+ // Mock locale
292+ wrapper . vm . locale = 'zh'
293+ expect ( wrapper . vm . locale ) . toBe ( 'zh' )
294+ } )
295+
296+ it ( 'watches props.tags changes and updates tag refs' , async ( ) => {
297+ const wrapper = createWrapper ( {
298+ tags : { task_tags : [ { name : 'tag1' } ] }
299+ } )
300+
301+ expect ( wrapper . vm . theTaskTags . theTags . length ) . toBe ( 1 )
302+
303+ // Update props
304+ await wrapper . setProps ( {
305+ tags : {
306+ task_tags : [
307+ { name : 'tag1' } ,
308+ { name : 'tag2' } ,
309+ { name : 'tag3' } ,
310+ { name : 'tag4' }
311+ ]
312+ }
313+ } )
314+
315+ expect ( wrapper . vm . theTaskTags . theTags . length ) . toBe ( 3 )
316+ expect ( wrapper . vm . theTaskTags . moreTags ) . toBe ( true )
317+ } )
318+
319+ it ( 'handles empty tag arrays correctly' , ( ) => {
320+ const wrapper = createWrapper ( {
321+ tags : {
322+ task_tags : [ ] ,
323+ framework_tags : [ ] ,
324+ language_tags : [ ]
325+ }
326+ } )
327+
328+ expect ( wrapper . vm . theTaskTags . theTags ) . toEqual ( [ ] )
329+ expect ( wrapper . vm . theFrameworkTags . theTags ) . toEqual ( [ ] )
330+ expect ( wrapper . vm . theLanguageTags . theTags ) . toEqual ( [ ] )
331+ } )
332+
333+ it ( 'handles null/undefined tag values' , ( ) => {
334+ const wrapper = createWrapper ( {
335+ tags : {
336+ task_tags : null ,
337+ framework_tags : undefined
338+ }
339+ } )
340+
341+ // The createTagRefs function should handle null/undefined values gracefully
342+ // When tags are null/undefined, createTagRefs returns { moreTags: ref(false), theTags: ref(undefined) }
343+ expect ( wrapper . vm . theTaskTags ) . toBeDefined ( )
344+ expect ( wrapper . vm . theTaskTags . moreTags ) . toBe ( false )
345+ expect ( wrapper . vm . theFrameworkTags ) . toBeDefined ( )
346+ expect ( wrapper . vm . theFrameworkTags . moreTags ) . toBe ( false )
347+
348+ // The component should not crash and should not display these sections
349+ expect ( wrapper . text ( ) ) . not . toContain ( 'all.task' )
350+ expect ( wrapper . text ( ) ) . not . toContain ( 'all.framework' )
351+ } )
352+
353+ it ( 'returns # for invalid tag URLs' , ( ) => {
354+ const wrapper = createWrapper ( )
355+
356+ expect ( wrapper . vm . getTagUrl ( '' , 'test' ) ) . toBe ( '#' )
357+ expect ( wrapper . vm . getTagUrl ( 'task' , '' ) ) . toBe ( '#' )
358+ expect ( wrapper . vm . getTagUrl ( null , 'test' ) ) . toBe ( '#' )
359+ } )
360+
361+ it ( 'renders all tag categories when provided' , ( ) => {
362+ const allTags = {
363+ task_tags : [ { name : 'task1' } ] ,
364+ framework_tags : [ { name : 'pytorch' } ] ,
365+ language_tags : [ { name : 'en' , color : '#007bff' } ] ,
366+ license_tags : [ { name : 'MIT' } ] ,
367+ industry_tags : [ { name : 'finance' } ] ,
368+ other_tags : [ { name : 'other1' } ] ,
369+ program_language : [ { name : 'python' } ] ,
370+ runmode_tags : [ { name : 'inference' } ] ,
371+ scene_tags : [ { name : 'chatbot' } ] ,
372+ sdk_tags : [ { name : 'transformers' } ] ,
373+ hardware_tags : [ { name : 'GPU' } ] ,
374+ lwftest_tags : [ { name : 'lwftest1' } ] ,
375+ hhcate01_tags : [ { name : 'hhcate1' } ]
376+ }
377+
378+ const wrapper = createWrapper ( { tags : allTags } )
379+
380+ expect ( wrapper . text ( ) ) . toContain ( 'all.task' )
381+ expect ( wrapper . text ( ) ) . toContain ( 'all.framework' )
382+ expect ( wrapper . text ( ) ) . toContain ( 'all.language' )
383+ expect ( wrapper . text ( ) ) . toContain ( 'License:' )
384+ expect ( wrapper . text ( ) ) . toContain ( 'all.industry' )
385+ expect ( wrapper . text ( ) ) . toContain ( 'all.others' )
386+ expect ( wrapper . text ( ) ) . toContain ( 'all.programLanguage' )
387+ expect ( wrapper . text ( ) ) . toContain ( 'all.runmode' )
388+ expect ( wrapper . text ( ) ) . toContain ( 'all.scene' )
389+ expect ( wrapper . text ( ) ) . toContain ( 'all.sdk' )
390+ expect ( wrapper . text ( ) ) . toContain ( 'all.hardware' )
391+ expect ( wrapper . text ( ) ) . toContain ( 'lwftest:' )
392+ expect ( wrapper . text ( ) ) . toContain ( 'hhcate01:' )
393+ } )
394+ } )
0 commit comments