import { mount, type VueWrapper } from '@vue/test-utils' import { afterEach, describe, expect, it, vi } from 'vitest' import { NButton } from 'naive-ui' import DirectoryItem from '@/components/ui/DirectoryItem.vue' import { readAllStyles } from '@/testing/styles' let wrapper: VueWrapper | undefined afterEach(() => wrapper?.unmount()) describe('统一目录条目', () => { it('主体缩略图使用可选前导槽位,与标题一起保留满行点击区域', () => { wrapper = mount(DirectoryItem, { props: { active: true }, slots: { leading: '母版缩略图', default: '林默', eyebrow: '@CH0001' } }) expect(wrapper.classes()).toContain('has-leading') expect(wrapper.get('.directory-item-leading').text()).toBe('母版缩略图') expect(wrapper.get('.directory-item-title').text()).toBe('林默') expect(wrapper.get('.directory-item-leading').element.closest('button')).toBe(wrapper.element) }) it('编号、长标题和多项状态各占独立层级,沿用 Naive 按钮', () => { const title = '雨夜霓虹街全景与记忆当铺外的人群和闪烁灯牌' wrapper = mount(DirectoryItem, { props: { active: false }, slots: { eyebrow: 'BEAT 2 / SHOT 3', default: title, meta: '设计已保存待状态' } }) expect(wrapper.getComponent(NButton).props()).toMatchObject({ quaternary: true, block: true }) expect(wrapper.get('button').attributes('type')).toBe('button') expect(wrapper.get('.directory-item-eyebrow').text()).toBe('BEAT 2 / SHOT 3') expect(wrapper.get('.directory-item-title').text()).toBe(title) expect(wrapper.get('.directory-item-meta').findAll('.directory-status')).toHaveLength(2) expect(wrapper.find('.truncate').exists()).toBe(false) expect(wrapper.findAll('.directory-item-body > span')).toHaveLength(3) }) it('点击透传给业务层,选中态与无障碍属性保持同步', async () => { const onClick = vi.fn<() => void>() wrapper = mount(DirectoryItem, { props: { active: false }, attrs: { onClick }, slots: { default: '林默' } }) await wrapper.get('button').trigger('click') expect(onClick).toHaveBeenCalledOnce() expect(wrapper.attributes('aria-current')).toBeUndefined() await wrapper.setProps({ active: true }) expect(wrapper.get('button').classes()).toContain('selected') expect(wrapper.attributes('aria-current')).toBe('true') expect(wrapper.find('.directory-item-eyebrow').exists()).toBe(false) expect(wrapper.find('.directory-item-meta').exists()).toBe(false) }) it('显式保护最小高度、留白、换行和窄屏横向条目,避免旧按钮样式重新挤压目录', () => { // DOM 环境不计算视觉几何;这些约束不能替代浏览器验收。 const css = readAllStyles() expect(css).toMatch(/\.n-button\.directory-item\s*\{[^}]*min-height:\s*88px;[^}]*padding:\s*12px 14px/) expect(css).toMatch(/\.directory-item-body\s*\{[^}]*flex-direction:\s*column;[^}]*gap:\s*7px/) expect(css).toMatch(/\.directory-item-title\s*\{[^}]*overflow-wrap:\s*anywhere/) expect(css).toMatch(/\.directory-list-content\s*\{[^}]*flex-direction:\s*row;[^}]*width:\s*max-content/) expect(css).toContain('grid-template-columns: clamp(240px, 22%, 280px) minmax(0, 1fr)') expect(css).toMatch(/\.identity-subject-list-content\s*\{[^}]*gap:\s*2px;[^}]*padding:\s*0 0 8px/) expect(css).toMatch(/\.n-button\.identity-subject-item\s*\{[^}]*min-height:\s*86px;[^}]*padding:\s*10px 14px/) expect(css).not.toMatch(/\.n-button\.identity-subject-item\s*\{[^}]*border-bottom:/) expect(css).toMatch(/\.identity-thumbnail\s*\{[^}]*width:\s*48px;[^}]*height:\s*64px/) expect(css).toMatch(/\.n-button\.identity-subject-item:not\(\.selected\)\s*\{[^}]*var\(--app-surface\)/) }) it('侧栏与主体、镜头、剧集目录统一直角满行,保留文字内边距和移动端滚动', () => { // happy-dom 无法计算伪元素几何,保护实际控制选中背景宽度的 CSS 规则。 const css = readAllStyles() expect(css).toMatch( /\.admin-sider \.n-menu \.n-menu-item-content::before\s*\{[^}]*left:\s*0;[^}]*right:\s*0;[^}]*border-radius:\s*0/ ) expect(css).toMatch(/\.directory-list-content\s*\{[^}]*padding:\s*8px 0 16px/) expect(css).toMatch(/\.reader-directory-content\s*\{[^}]*padding:\s*4px 0 18px/) expect(css).not.toMatch(/\.reader-directory-content\s*\{[^}]*padding-inline:\s*[1-9]/) for (const selector of ['directory-item', 'reader-episode-link']) { expect(css).toMatch(new RegExp(`\\.n-button\\.${selector}\\s*\\{[^}]*width:\\s*100%`)) expect(css).toMatch(new RegExp(`\\.n-button\\.${selector}\\s*\\{[^}]*padding:\\s*12px 14px`)) expect(css).toMatch(new RegExp(`\\.n-button\\.${selector}\\s*\\{[^}]*border-radius:\\s*0`)) } expect(css).toMatch(/\.directory-group-heading\s*\{[^}]*padding:\s*10px 14px/) expect(css).toMatch(/\.directory-list-content\s*\{[^}]*flex-direction:\s*row;[^}]*width:\s*max-content/) }) it('Tabs 工具栏与筛选网格限制最小宽度,不让组件默认宽度挤压内容', () => { const css = readAllStyles() expect(css).toMatch(/\.workspace-tabs\.n-tabs\s*\{[^}]*width:\s*100%;[^}]*min-width:\s*0/) expect(css).toMatch(/\.workspace-tabs \.n-tabs-nav-scroll-wrapper\s*\{[^}]*min-width:\s*0/) expect(css).toMatch( /\.form-image-filters\s*\{[^}]*grid-template-columns:\s*minmax\(200px, 360px\) 152px 160px auto/ ) expect(css).toMatch( /@container \(max-width: 900px\)\s*\{\s*\.form-image-filters\s*\{[^}]*minmax\(0, 1fr\) 152px/ ) }) })