import { mount, type VueWrapper } from '@vue/test-utils' import { afterEach, describe, expect, it } from 'vitest' import { readFileSync } from 'node:fs' import BeatShotDirectory from './components/BeatShotDirectory.vue' import { designedShot } from './testing/fixtures' let wrapper: VueWrapper | undefined afterEach(() => wrapper?.unmount()) describe('Beat 两级镜头目录', () => { it('按 Beat 和镜号排序,分组只出现一次并显示镜数,不改变原数组', () => { const shots = [ { ...designedShot('b2-s1'), beatNo: 2 }, { ...designedShot('b1-s2'), shotNo: 2 }, designedShot('b1-s1') ] wrapper = mount(BeatShotDirectory, { props: { shots, activeId: 'b1-s1', itemClass: 'shot-link' }, slots: { meta: '设计已保存' } }) const groups = wrapper.findAll('.beat-directory-group') expect(groups).toHaveLength(2) expect(groups[0]!.get('h4').text()).toContain('BEAT 01') expect(groups[0]!.get('.directory-group-count').text()).toBe('2 镜') expect(groups[1]!.get('h4').text()).toContain('BEAT 02') expect(groups[1]!.get('.directory-group-count').text()).toBe('1 镜') expect(wrapper.findAll('.directory-shot-number').map(item => item.text())).toEqual([ '镜头 01', '镜头 02', '镜头 01' ]) expect(wrapper.findAll('.directory-item-meta').every(item => item.text() === '设计已保存')).toBe(true) expect(groups[0]!.attributes('aria-labelledby')).toBe(groups[0]!.get('h4').attributes('id')) expect(shots.map(shot => shot.shotId)).toEqual(['b2-s1', 'b1-s2', 'b1-s1']) }) it('不同 Beat 的同号镜头按正式 ID 选择,数据刷新后选中项保持不变', async () => { const shots = [designedShot('b1-s1'), { ...designedShot('b2-s1'), beatNo: 2 }] wrapper = mount(BeatShotDirectory, { props: { shots, activeId: 'b1-s1', itemClass: 'production-shot-link' } }) await wrapper.findAll('.production-shot-link')[1]!.trigger('click') expect(wrapper.emitted('select')).toEqual([['b2-s1']]) await wrapper.setProps({ activeId: 'b2-s1', shots: [...shots, { ...designedShot('b2-s2'), beatNo: 2, shotNo: 2 }] }) expect(wrapper.get('.selected').attributes('aria-label')).toBe('BEAT 2 · 镜头 1 · 来信') expect(wrapper.findAll('.selected')).toHaveLength(1) expect(wrapper.findAll('.directory-group-count')[1]!.text()).toBe('2 镜') }) it('组内吸顶与移动端归属提示分别保护,不在桌面重复展示 Beat', () => { // happy-dom 不计算吸顶位置,保护 CSS 边界,实际滚动仍需浏览器验收。 const css = readFileSync('src/admin.css', 'utf8') expect(css).toMatch( /\.directory-group-heading\s*\{[^}]*position:\s*sticky;[^}]*top:\s*0;[^}]*background:\s*var\(--app-control\)/ ) expect(css).toMatch(/\.directory-mobile-beat\s*\{\s*display:\s*none/) const mobile = css.slice(css.lastIndexOf('@media (max-width: 760px)')) expect(mobile).toMatch(/\.directory-mobile-beat\s*\{\s*display:\s*inline/) expect(mobile).toMatch(/\.beat-directory-group,\s*\.beat-directory-items\s*\{\s*display:\s*contents/) }) })