refactor: 租户切换器useTenantOptions收拢三页
新建 composables/useTenantOptions.ts:会话/配置/统计三页各自的 tenants/tenantsLoading/tenantsError/loadTenants 拷贝收拢为单一 composable。两种形态: - 默认(会话/配置页):loadTenants 成功选中首个租户 + bootstrapTenantContext()(原两页 onMounted 公共体:超管拉列表/ 租户管理员固定自己/无 slug 防御不动),选中后的 watch 仍留各页 (重置逻辑页面各异); - selectFirst: false(统计页):纯下拉数据源,activeSlug 以 tenantFilter 别名解构('' = 全部租户合并视图),模板零改动。 dashboard 控制台是租户卡片墙(非下拉切换),不在此列。 composable 显式 import vue API——vitest 不跑 unplugin-auto-import。 新增 8 个 composable 测试(mock api/auth store,不装 pinia)。 vitest 164 passed(156+8);build 通过;dts 行尾漂移已还原。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* useTenantOptions 单元测试(Phase 4 Task 5E)
|
||||
*
|
||||
* 覆盖三页收拢前的行为契约(会话/配置页的选中引导 + stats 页的纯数据源):
|
||||
* - loadTenants:成功填列表并(默认)选首个租户;失败置错误文案保上一份
|
||||
* - selectFirst: false(stats 页形态):只填列表,activeSlug 保持 ''(合并视图)
|
||||
* - bootstrapTenantContext:超管拉列表 / 租户管理员固定自己 / 无 slug 不动
|
||||
* - isSuper 随角色
|
||||
*
|
||||
* 测试纪律:mock @/api/tenants 与 @/store/modules/auth(不装 pinia——
|
||||
* composable 只读 role/tenantSlug 两个字段)。
|
||||
*/
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
fetchTenantList: vi.fn(),
|
||||
auth: { role: 'super', tenantSlug: null as string | null }
|
||||
}))
|
||||
|
||||
vi.mock('@/api/tenants', () => ({
|
||||
fetchTenantList: mocks.fetchTenantList
|
||||
}))
|
||||
|
||||
vi.mock('@/store/modules/auth', () => ({
|
||||
useAuthStore: () => mocks.auth
|
||||
}))
|
||||
|
||||
import { useTenantOptions } from '@/composables/useTenantOptions'
|
||||
|
||||
const ITEMS = [
|
||||
{ slug: 'jiancai', name: '建材租户' },
|
||||
{ slug: 'auto', name: '汽车租户' }
|
||||
]
|
||||
|
||||
describe('useTenantOptions(src/composables/useTenantOptions.ts)', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mocks.auth.role = 'super'
|
||||
mocks.auth.tenantSlug = null
|
||||
})
|
||||
|
||||
it('loadTenants 默认形态:填列表 + 选中首个租户(会话/配置页行为)', async () => {
|
||||
mocks.fetchTenantList.mockResolvedValue({ items: ITEMS })
|
||||
const t = useTenantOptions()
|
||||
|
||||
expect(t.activeSlug.value).toBe('')
|
||||
await t.loadTenants()
|
||||
|
||||
expect(mocks.fetchTenantList).toHaveBeenCalledTimes(1)
|
||||
expect(t.tenants.value).toEqual(ITEMS)
|
||||
expect(t.activeSlug.value).toBe('jiancai')
|
||||
expect(t.tenantsError.value).toBe('')
|
||||
expect(t.tenantsLoading.value).toBe(false)
|
||||
})
|
||||
|
||||
it('selectFirst: false(stats 页形态):只填列表,activeSlug 保持合并视图', async () => {
|
||||
mocks.fetchTenantList.mockResolvedValue({ items: ITEMS })
|
||||
const t = useTenantOptions({ selectFirst: false })
|
||||
|
||||
await t.loadTenants()
|
||||
|
||||
expect(t.tenants.value).toEqual(ITEMS)
|
||||
expect(t.activeSlug.value).toBe('')
|
||||
})
|
||||
|
||||
it('loadTenants 失败:错误文案进 tenantsError,列表保持上一份', async () => {
|
||||
mocks.fetchTenantList.mockResolvedValueOnce({ items: ITEMS })
|
||||
mocks.fetchTenantList.mockRejectedValueOnce(new Error('网络错误'))
|
||||
const t = useTenantOptions()
|
||||
|
||||
await t.loadTenants()
|
||||
await t.loadTenants()
|
||||
|
||||
expect(t.tenantsError.value).toBe('网络错误')
|
||||
expect(t.tenants.value).toEqual(ITEMS) // 失败不清旧列表
|
||||
expect(t.tenantsLoading.value).toBe(false)
|
||||
})
|
||||
|
||||
it('loadTenants 失败(非 Error 抛出物):兜底文案', async () => {
|
||||
mocks.fetchTenantList.mockRejectedValueOnce('boom')
|
||||
const t = useTenantOptions()
|
||||
|
||||
await t.loadTenants()
|
||||
|
||||
expect(t.tenantsError.value).toBe('租户列表加载失败')
|
||||
})
|
||||
|
||||
it('bootstrapTenantContext 超管:拉租户列表(成功后选中首个)', async () => {
|
||||
mocks.fetchTenantList.mockResolvedValue({ items: ITEMS })
|
||||
const t = useTenantOptions()
|
||||
|
||||
t.bootstrapTenantContext()
|
||||
await vi.waitFor(() => expect(t.activeSlug.value).toBe('jiancai'))
|
||||
})
|
||||
|
||||
it('bootstrapTenantContext 租户管理员:固定自己的 tenantSlug,不发列表请求', () => {
|
||||
mocks.auth.role = 'tenant'
|
||||
mocks.auth.tenantSlug = 'jiancai'
|
||||
const t = useTenantOptions()
|
||||
|
||||
t.bootstrapTenantContext()
|
||||
|
||||
expect(t.activeSlug.value).toBe('jiancai')
|
||||
expect(mocks.fetchTenantList).not.toHaveBeenCalled()
|
||||
expect(t.isSuper.value).toBe(false)
|
||||
})
|
||||
|
||||
it('bootstrapTenantContext 租户管理员无 slug:防御不动(模板出告警)', () => {
|
||||
mocks.auth.role = 'tenant'
|
||||
mocks.auth.tenantSlug = null
|
||||
const t = useTenantOptions()
|
||||
|
||||
t.bootstrapTenantContext()
|
||||
|
||||
expect(t.activeSlug.value).toBe('')
|
||||
expect(mocks.fetchTenantList).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('isSuper 随角色判定', () => {
|
||||
expect(useTenantOptions().isSuper.value).toBe(true)
|
||||
mocks.auth.role = 'tenant'
|
||||
expect(useTenantOptions().isSuper.value).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user