Skip to content

Commit 9669b28

Browse files
authored
Merge branch 'main' into csghub-wl-jlp
2 parents 59aa89a + 085f284 commit 9669b28

File tree

28 files changed

+1355
-236
lines changed

28 files changed

+1355
-236
lines changed
Lines changed: 394 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,394 @@
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

Comments
 (0)