import { App, createApp, defineComponent, h, nextTick, ref } from 'vue' import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest' let InfoScreening: any interface RenderResult { app: App el: HTMLDivElement events: { select: ReturnType reset: ReturnType } screeningRef: { value: any } } const renderedApps: RenderResult[] = [] beforeAll(async () => { InfoScreening = (await import('../../../components/infoScreening/index.vue')).default }) const createListConfig = () => ([ { title: '类型', ref: 'type', lists: [ { name: '全部', id: '' }, { name: '类型A', id: 'a' }, { name: '类型B', id: 'b' } ] }, { title: '状态', ref: 'status', isMultiple: true, lists: [ { name: '全部', id: '' }, { name: '启用', id: 1 }, { name: '停用', id: 2 } ] } ]) const renderScreening = async (props: Record = {}): Promise => { const events = { select: vi.fn(), reset: vi.fn() } const screeningRef = ref(null) const el = document.createElement('div') document.body.appendChild(el) const app = createApp(defineComponent({ setup () { return () => h(InfoScreening, { ref: (instance: any) => { screeningRef.value = instance }, title: '筛选条件', listConfig: [], adaptive: false, ...props, onSelect: events.select, onReset: events.reset }) } })) app.mount(el) await nextTick() const result = { app, el, events, screeningRef } renderedApps.push(result) return result } const getRoot = () => document.body const getRows = () => Array.from(getRoot().querySelectorAll('.screening-row')) as HTMLElement[] const getRowItems = (rowIndex: number) => { const row = getRows()[rowIndex] expect(row).toBeTruthy() return row.querySelectorAll('.row-item') } const clickRowItem = async (rowIndex: number, itemIndex: number) => { const items = getRowItems(rowIndex) const target = items[itemIndex] as HTMLElement | undefined expect(target).toBeTruthy() target!.click() await nextTick() } const click = async (selector: string, index = 0) => { const targets = getRoot().querySelectorAll(selector) const target = targets[index] as HTMLElement | undefined expect(target).toBeTruthy() target!.click() await nextTick() } const initByExpose = async (screeningRef: { value: any }, data: any[]) => { expect(screeningRef.value).toBeTruthy() screeningRef.value.init(data) await nextTick() } afterEach(() => { while (renderedApps.length) { const current = renderedApps.pop()! current.app.unmount() current.el.remove() } document.body.innerHTML = '' vi.useRealTimers() }) describe('components/infoScreening', () => { it('通过暴露的 init 初始化后渲染标题、分组和默认选中项', async () => { const { screeningRef } = await renderScreening() await initByExpose(screeningRef, createListConfig()) expect(getRoot().querySelector('.title-text')?.textContent).toBe('筛选条件:') expect(getRoot().querySelector('.row-item-title')?.textContent?.trim()).toBe('类型:') expect(getRoot().querySelectorAll('.screening-row')).toHaveLength(2) expect(getRoot().querySelectorAll('.row-item.active')).toHaveLength(2) expect(getRoot().querySelector('.reset-btn')).toBeNull() }) it('单选时点击选项会更新选中状态并抛出 select 事件', async () => { const { screeningRef, events } = await renderScreening() await initByExpose(screeningRef, createListConfig()) await clickRowItem(0, 1) const firstRowItems = getRowItems(0) expect(firstRowItems[0]?.className.includes('active')).toBe(false) expect(firstRowItems[1]?.className.includes('active')).toBe(true) expect(getRoot().querySelector('.reset-btn')?.textContent).toBe('重置') expect(events.select).toHaveBeenCalledTimes(1) const payload = events.select.mock.calls[0][0] expect(payload.activeList).toEqual([1]) expect(payload.value).toBe('a') }) it('多选时支持累加选择,全部取消后会回退到默认全选', async () => { const { screeningRef, events } = await renderScreening() await initByExpose(screeningRef, createListConfig()) await clickRowItem(1, 1) await clickRowItem(1, 2) let secondRowItems = getRowItems(1) expect(secondRowItems[1]?.className.includes('active')).toBe(true) expect(secondRowItems[2]?.className.includes('active')).toBe(true) const secondPayload = events.select.mock.calls[1][0] expect(secondPayload.activeList).toEqual([1, 2]) expect(secondPayload.valueList).toEqual([1, 2]) await clickRowItem(1, 1) await clickRowItem(1, 2) secondRowItems = getRowItems(1) const lastPayload = events.select.mock.calls[3][0] expect(lastPayload.activeList).toEqual([0]) expect(lastPayload.valueList).toEqual([]) expect(secondRowItems[0]?.className.includes('active')).toBe(true) }) it('点击重置按钮或调用 resetAll 会恢复默认状态并抛出 reset 事件', async () => { const { screeningRef, events } = await renderScreening() await initByExpose(screeningRef, createListConfig()) await clickRowItem(0, 2) expect(getRoot().querySelector('.reset-btn')).not.toBeNull() await click('.reset-btn') const activeItemsAfterClick = getRoot().querySelectorAll('.row-item.active') expect(activeItemsAfterClick).toHaveLength(2) expect(events.reset).toHaveBeenCalledTimes(1) expect(getRoot().querySelector('.reset-btn')).toBeNull() await clickRowItem(0, 1) screeningRef.value.resetAll() await nextTick() expect(events.reset).toHaveBeenCalledTimes(2) expect(getRoot().querySelector('.reset-btn')).toBeNull() expect(getRoot().querySelectorAll('.row-item.active')).toHaveLength(2) }) it('adaptive 模式下会根据高度决定是否显示更多按钮,并支持展开收起', async () => { vi.useFakeTimers() vi.spyOn(HTMLElement.prototype, 'clientHeight', 'get').mockImplementation(function (this: HTMLElement) { if (this.classList.contains('row-item-box')) { return 60 } return 0 }) const { screeningRef } = await renderScreening({ adaptive: true }) await initByExpose(screeningRef, [createListConfig()[0]]) vi.runAllTimers() await nextTick() const row = getRoot().querySelector('.screening-row') const moreBtn = getRoot().querySelector('.row-more') as HTMLElement | null expect(row?.className.includes('hidden')).toBe(true) expect(moreBtn?.textContent).toContain('更多') moreBtn?.click() await nextTick() expect(row?.className.includes('show')).toBe(true) expect(getRoot().querySelector('.row-more')?.textContent).toContain('收起') }) })