import { reactive } from 'vue' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { InitTable } from '../../../hooks/InitTable' import * as scriptIndex from '../../../script/index' describe('InitTable', () => { let modelData: { [key: string]: any } let dialogConfig: { [key: string]: any } let emits: ReturnType let confirm: ReturnType let delHandle: ReturnType let queryCallback: ReturnType let initTable: InitTable afterEach(() => { vi.restoreAllMocks() }) beforeEach(() => { modelData = reactive({ id: '', name: '' }) dialogConfig = reactive({ isShowDialog: true }) emits = vi.fn() confirm = vi.fn() delHandle = vi.fn() queryCallback = vi.fn() initTable = new InitTable({ modelData, dialogConfig, emits, confirm, delHandle, queryCallback, add: 'addApi', edit: 'editApi', del: 'delApi', query: 'queryApi' }) }) it('handleSelectionChange 会深拷贝选中行并提取 id', () => { const rows = [ { id: '1', name: 'first', nested: { enabled: true } }, { id: '2', name: 'second', nested: { enabled: false } } ] initTable.handleSelectionChange(rows as []) rows[0].nested.enabled = false expect(initTable.selectData).toEqual(['1', '2']) expect(initTable.selectRowData).toEqual([ { id: '1', name: 'first', nested: { enabled: true } }, { id: '2', name: 'second', nested: { enabled: false } } ]) }) it('addOrEditTable 在新增态会调用 add 接口并关闭弹窗', () => { const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.({ id: 'new-id' }) }) modelData.name = 'new row' initTable.addOrEditTable() expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ interface: 'addApi', tips: true })) expect(dialogConfig.isShowDialog).toBe(false) expect(confirm).toHaveBeenCalledTimes(1) expect(emits).toHaveBeenCalledWith('queryTableData', { id: 'new-id' }) }) it('addOrEditTable 在编辑态会调用 edit 接口', () => { const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.({ id: '1' }) }) modelData.id = '1' initTable.addOrEditTable() expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ interface: 'editApi' })) }) it('delTable 会优先使用传入 ids 并在成功后刷新列表', () => { const queryTableDataSpy = vi.spyOn(initTable, 'queryTableData').mockImplementation(() => undefined) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.({}) }) initTable.selectData.push('fallback-id') initTable.delTable(['custom-id']) expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ params: ['custom-id'], interface: 'delApi', tips: true })) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) expect(delHandle).toHaveBeenCalledTimes(1) }) it('delTable 在未传 ids 时会使用选中数据', () => { const table = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }), del: 'delApi' }) const queryTableDataSpy = vi.spyOn(table, 'queryTableData').mockImplementation(() => undefined) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.({}) }) table.selectData.push('selected-id') table.delTable() expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ params: ['selected-id'], interface: 'delApi' })) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('queryTableData 会使用 requestPayload 的结果请求并回填列表数据', () => { initTable.selectData.push('selected-id') initTable.selectRowData.push({ id: 'selected-id' }) initTable.list.push({ id: 'old' }) initTable.sortObject = { createTime: 'ASC' } const payload = { filters: ['payload'] } vi.spyOn(scriptIndex, 'requestPayload').mockReturnValue(payload as any) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.({ totalCount: 2, list: [{ id: '1' }, { id: '2' }] }) }) initTable.queryTableData() expect(scriptIndex.requestPayload).toHaveBeenCalledWith(initTable.parameter, { createTime: 'ASC' }) expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ params: payload, interface: 'queryApi' })) expect(initTable.selectData).toEqual([]) expect(initTable.selectRowData).toEqual([]) expect(initTable.totalCount.value).toBe(2) expect(initTable.list).toEqual([{ id: '1' }, { id: '2' }]) expect(queryCallback).toHaveBeenCalledWith({ totalCount: 2, list: [{ id: '1' }, { id: '2' }] }) }) it('queryTableData 在存在外部 queryTableData 时直接调用外部逻辑', () => { const customQuery = vi.fn() const tableWithCustomQuery = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }), queryTableData: customQuery }) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi') tableWithCustomQuery.queryTableData() expect(customQuery).toHaveBeenCalledTimes(1) expect(serverApiSpy).not.toHaveBeenCalled() }) it('queryTableData 在缺少 query 接口时直接返回', () => { const tableWithoutQuery = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }) }) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi') tableWithoutQuery.queryTableData() expect(serverApiSpy).not.toHaveBeenCalled() }) it('detailTable 在缺少 detail 或 id 时直接返回', () => { const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi') const tableWithoutDetail = new InitTable({ modelData: reactive({ id: '1' }), dialogConfig: reactive({ isShowDialog: false }) }) const tableWithoutId = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }), detail: 'detailApi' }) tableWithoutDetail.detailTable() tableWithoutId.detailTable() expect(serverApiSpy).not.toHaveBeenCalled() }) it('detailTable 会优先使用传入 id 并回填 modelData', () => { const detailHandle = vi.fn() const table = new InitTable({ modelData: reactive({ id: 'fallback-id', name: '' }), dialogConfig: reactive({ isShowDialog: false }), detail: 'detailApi', detailHandle }) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.({ id: 'detail-id', name: 'detail-name', extra: 'value' }) }) table.detailTable('custom-id') expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ params: 'custom-id', interface: 'detailApi' })) expect(detailHandle).toHaveBeenCalledWith({ id: 'detail-id', name: 'detail-name', extra: 'value' }) expect(table.param.modelData).toEqual({ id: 'detail-id', name: 'detail-name', extra: 'value' }) }) it('queryTreeData 在有 tree 接口时会优先使用传入参数并覆盖 treeData', () => { const table = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }), tree: 'treeApi', treeParams: { source: 'default' } }) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.([{ id: 'node-1' }, { id: 'node-2' }]) }) table.treeData.push({ id: 'old-node' }) table.queryTreeData({ source: 'custom' }) expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ interface: 'treeApi', params: { source: 'custom' } })) expect(table.treeData).toEqual([{ id: 'node-1' }, { id: 'node-2' }]) }) it('queryTreeData 不传参数时会使用默认 treeParams', () => { const table = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }), tree: 'treeApi', treeParams: { source: 'default', root: true } }) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.([]) }) table.queryTreeData() expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ interface: 'treeApi', params: { source: 'default', root: true } })) }) it('queryTreeData 在缺少 tree 接口时会回退到列表查询', () => { const queryTableDataSpy = vi.spyOn(initTable, 'queryTableData').mockImplementation(() => undefined) initTable.queryTreeData({ keyword: 'fallback' }) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('enableTable 在未传 ids 时会使用选中数据并刷新列表', () => { const table = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }), enabled: 'enabledApi' }) const queryTableDataSpy = vi.spyOn(table, 'queryTableData').mockImplementation(() => undefined) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.() }) table.selectData.push('row-1') table.enableTable() expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ interface: 'enabledApi', params: { enabled: 1, ids: ['row-1'] } })) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('disableTable 会优先使用传入 ids 并刷新列表', () => { const table = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }), enabled: 'enabledApi' }) const queryTableDataSpy = vi.spyOn(table, 'queryTableData').mockImplementation(() => undefined) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.() }) table.selectData.push('fallback-id') table.disableTable(['custom-id']) expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ interface: 'enabledApi', params: { enabled: 0, ids: ['custom-id'] } })) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('approval 会携带状态并支持 selectData 回退', () => { const table = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }), approval: 'approvalApi' }) const queryTableDataSpy = vi.spyOn(table, 'queryTableData').mockImplementation(() => undefined) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.() }) table.selectData.push('approve-id') table.approval('PASS') expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ interface: 'approvalApi', tips: true, params: { status: 'PASS', ids: ['approve-id'] } })) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('cancelApproval 会优先使用传入 ids 并刷新列表', () => { const table = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }), cancelApproval: 'cancelApprovalApi' }) const queryTableDataSpy = vi.spyOn(table, 'queryTableData').mockImplementation(() => undefined) const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation((options: any) => { options.success?.() }) table.selectData.push('fallback-id') table.cancelApproval(['custom-id']) expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ interface: 'cancelApprovalApi', tips: true, params: ['custom-id'] })) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('handleTree 在存在 treeHandle 时交给外部处理', () => { const treeHandle = vi.fn() const table = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }), treeHandle, treeKey: 'customParentId' }) const queryTableDataSpy = vi.spyOn(table, 'queryTableData').mockImplementation(() => undefined) const node = { id: 'node-1', name: '节点1' } table.handleTree(node) expect(treeHandle).toHaveBeenCalledWith(node, table.parameter) expect(table.parameter.customParentId).toBeUndefined() expect(queryTableDataSpy).not.toHaveBeenCalled() }) it('handleTree 在默认分支会写入 treeKey、拷贝当前节点并刷新列表', () => { const table = new InitTable({ modelData: reactive({}), dialogConfig: reactive({ isShowDialog: false }), treeKey: 'customParentId' }) const queryTableDataSpy = vi.spyOn(table, 'queryTableData').mockImplementation(() => undefined) const node = { id: 'node-2', nested: { enabled: true } } table.handleTree(node) node.nested.enabled = false expect(table.parameter.customParentId).toBe('node-2') expect(table.currentTree).toEqual({ id: 'node-2', nested: { enabled: true } }) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('changeTableData 仅在点击翻页时刷新列表', () => { const queryTableDataSpy = vi.spyOn(initTable, 'queryTableData').mockImplementation(() => undefined) initTable.changeTableData({}, false) initTable.changeTableData({}, true) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('handleSortChange 会更新升序排序并触发查询', () => { const queryTableDataSpy = vi.spyOn(initTable, 'queryTableData').mockImplementation(() => undefined) initTable.handleSortChange({ prop: 'createTime', order: 'ascending' }) expect(initTable.sortObject).toEqual({ createTime: 'ASC' }) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('handleSortChange 会清空旧排序并写入降序排序', () => { const queryTableDataSpy = vi.spyOn(initTable, 'queryTableData').mockImplementation(() => undefined) initTable.sortObject = { name: 'ASC' } initTable.handleSortChange({ prop: 'createTime', order: 'descending' }) expect(initTable.sortObject).toEqual({ createTime: 'DESC' }) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('handleSortChange 在 prop 存在但 order 为异常值时会回落到 DESC', () => { const queryTableDataSpy = vi.spyOn(initTable, 'queryTableData').mockImplementation(() => undefined) initTable.handleSortChange({ prop: 'createTime', order: 'unexpected-order' }) expect(initTable.sortObject).toEqual({ createTime: 'DESC' }) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('handleSortChange 在缺少 prop 时仍会触发查询且保留原排序', () => { const queryTableDataSpy = vi.spyOn(initTable, 'queryTableData').mockImplementation(() => undefined) initTable.sortObject = { name: 'ASC' } initTable.handleSortChange({ order: 'ascending' }) expect(initTable.sortObject).toEqual({ name: 'ASC' }) expect(queryTableDataSpy).toHaveBeenCalledTimes(1) }) it('非弹窗态初始化会自动触发列表查询和详情查询', () => { const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation(() => undefined) new InitTable({ modelData: reactive({ id: '1' }), query: 'queryApi', detail: 'detailApi' }) expect(serverApiSpy).toHaveBeenCalledTimes(2) expect(serverApiSpy).toHaveBeenNthCalledWith(1, expect.objectContaining({ interface: 'queryApi' })) expect(serverApiSpy).toHaveBeenNthCalledWith(2, expect.objectContaining({ params: '1', interface: 'detailApi' })) }) it('非弹窗态且存在 tree 接口时初始化只会请求树数据', () => { const serverApiSpy = vi.spyOn(scriptIndex, 'serverApi').mockImplementation(() => undefined) new InitTable({ modelData: reactive({}), tree: 'treeApi', treeParams: { root: true }, detail: 'detailApi' }) expect(serverApiSpy).toHaveBeenCalledTimes(1) expect(serverApiSpy).toHaveBeenCalledWith(expect.objectContaining({ interface: 'treeApi', params: { root: true } })) }) })