526 lines
21 KiB
TypeScript
526 lines
21 KiB
TypeScript
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||
import userEvent from '@testing-library/user-event'
|
||
import PermissionGroupsDashboard from './PermissionGroupsDashboard'
|
||
|
||
jest.mock('@gsap/react', () => ({ useGSAP: jest.fn() }))
|
||
jest.mock('gsap', () => ({ __esModule: true, default: { registerPlugin: jest.fn() } }))
|
||
|
||
jest.mock('next-auth/react', () => ({
|
||
useSession: () => ({
|
||
status: 'authenticated',
|
||
data: { user: { isSuperAdmin: true, name: '本地超级管理员' } },
|
||
}),
|
||
}))
|
||
|
||
jest.mock('./DepartmentShell', () => ({
|
||
__esModule: true,
|
||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||
}))
|
||
|
||
type PermissionFlags = {
|
||
canView: boolean
|
||
canDownload: boolean
|
||
canDownloadSource: boolean
|
||
canShare: boolean
|
||
canEditAssets: boolean
|
||
canAccessPrivate: boolean
|
||
}
|
||
|
||
type Payload = {
|
||
materialTypes: string[]
|
||
tree: Array<{
|
||
id: string
|
||
name: string
|
||
path: string
|
||
level: number
|
||
parentId: string | null
|
||
children: Payload['tree']
|
||
}>
|
||
groups: Array<{
|
||
slug: string
|
||
name: string
|
||
permissions: Record<string, PermissionFlags>
|
||
departments: Array<{ id?: string; path: string; name: string; status?: 'active' | 'invalid' }>
|
||
members: Array<{
|
||
userId: string
|
||
username: string
|
||
displayName: string
|
||
departmentPath: string | null
|
||
departmentName: string | null
|
||
}>
|
||
}>
|
||
}
|
||
|
||
function flags(overrides: Partial<PermissionFlags> = {}): PermissionFlags {
|
||
return {
|
||
canView: true,
|
||
canDownload: true,
|
||
canDownloadSource: false,
|
||
canShare: true,
|
||
canEditAssets: false,
|
||
canAccessPrivate: false,
|
||
...overrides,
|
||
}
|
||
}
|
||
|
||
function createPayload(): Payload {
|
||
return {
|
||
materialTypes: ['3D场景', '任务-详情'],
|
||
tree: [
|
||
{
|
||
id: 'root',
|
||
name: '江苏简尚品牌管理有限公司',
|
||
path: '江苏简尚品牌管理有限公司',
|
||
level: 1,
|
||
parentId: null,
|
||
children: [
|
||
{
|
||
id: 'ops',
|
||
name: '运营中心A',
|
||
path: '江苏简尚品牌管理有限公司/运营中心A',
|
||
level: 2,
|
||
parentId: 'root',
|
||
children: [],
|
||
},
|
||
],
|
||
},
|
||
],
|
||
groups: [
|
||
{
|
||
slug: 'operations',
|
||
name: '运营',
|
||
permissions: {
|
||
'3D场景': flags(),
|
||
'任务-详情': flags({ canDownloadSource: true }),
|
||
},
|
||
departments: [
|
||
{ path: '江苏简尚品牌管理有限公司/运营中心A', name: '运营中心A' },
|
||
],
|
||
members: [],
|
||
},
|
||
{
|
||
slug: 'visual',
|
||
name: '视觉',
|
||
permissions: {
|
||
'3D场景': flags(),
|
||
'任务-详情': flags({ canDownloadSource: true }),
|
||
},
|
||
departments: [],
|
||
members: [],
|
||
},
|
||
],
|
||
}
|
||
}
|
||
|
||
function jsonResponse(body: unknown, ok = true) {
|
||
return Promise.resolve({
|
||
ok,
|
||
json: async () => body,
|
||
})
|
||
}
|
||
|
||
function installFetchSequence(...responses: Array<ReturnType<typeof jsonResponse> | ((init?: RequestInit) => Promise<unknown>)>) {
|
||
const fetchMock = jest.fn()
|
||
fetchMock.mockImplementation((_input: RequestInfo | URL, init?: RequestInit) => {
|
||
const next = responses.shift()
|
||
if (!next) {
|
||
return Promise.reject(new Error('unexpected fetch call'))
|
||
}
|
||
return typeof next === 'function' ? next(init) : next
|
||
})
|
||
global.fetch = fetchMock as unknown as typeof fetch
|
||
return fetchMock
|
||
}
|
||
|
||
function pendingUntilAbort(label: string) {
|
||
return (init?: RequestInit) => {
|
||
const signal = init?.signal
|
||
if (!signal) {
|
||
return Promise.reject(new Error(`${label} missing AbortSignal`))
|
||
}
|
||
return new Promise((_, reject) => {
|
||
const abort = () => {
|
||
const error = new Error(`${label} aborted`)
|
||
error.name = 'AbortError'
|
||
reject(error)
|
||
}
|
||
if (signal.aborted) {
|
||
abort()
|
||
return
|
||
}
|
||
signal.addEventListener('abort', abort, { once: true })
|
||
})
|
||
}
|
||
}
|
||
|
||
async function renderDashboard() {
|
||
render(<PermissionGroupsDashboard />)
|
||
await screen.findByRole('navigation', { name: '权限组' })
|
||
}
|
||
|
||
function getTaskDetailCheckbox(label: string) {
|
||
return within(screen.getByRole('table')).getByRole('checkbox', { name: `任务-详情 ${label}` })
|
||
}
|
||
|
||
async function openDepartments() {
|
||
await userEvent.click(screen.getByRole('tab', { name: /部门/ }))
|
||
}
|
||
|
||
async function advanceAndFlush(ms: number) {
|
||
await act(async () => {
|
||
jest.advanceTimersByTime(ms)
|
||
await Promise.resolve()
|
||
})
|
||
}
|
||
|
||
describe('PermissionGroupsDashboard', () => {
|
||
afterEach(() => {
|
||
jest.useRealTimers()
|
||
jest.clearAllMocks()
|
||
})
|
||
|
||
it('在治理与设置里展示权限组和部门树', async () => {
|
||
installFetchSequence(jsonResponse(createPayload()))
|
||
|
||
await renderDashboard()
|
||
|
||
expect(within(screen.getByRole('navigation', { name: '权限组' })).getByRole('button', { name: /运营/ })).toBeInTheDocument()
|
||
expect(screen.getByRole('columnheader', { name: '可见' })).toBeInTheDocument()
|
||
expect(screen.queryByRole('columnheader', { name: '分享' })).not.toBeInTheDocument()
|
||
expect(getTaskDetailCheckbox('编辑图片')).not.toBeChecked()
|
||
expect(getTaskDetailCheckbox('私密素材查看/下载')).not.toBeChecked()
|
||
await openDepartments()
|
||
expect(await screen.findByRole('navigation', { name: '花名册组织树' })).toBeInTheDocument()
|
||
expect(screen.getByText('已在本组')).toBeInTheDocument()
|
||
expect(screen.getByRole('link', { name: '按人看权限' })).toHaveAttribute(
|
||
'href',
|
||
'/workspaces/governance/permissions?view=people',
|
||
)
|
||
})
|
||
|
||
it('已在别组的部门会标明占用,不能再加入当前组', async () => {
|
||
installFetchSequence(jsonResponse(createPayload()))
|
||
|
||
await renderDashboard()
|
||
await userEvent.click(
|
||
within(screen.getByRole('navigation', { name: '权限组' })).getByRole('button', { name: /视觉/ }),
|
||
)
|
||
await openDepartments()
|
||
|
||
expect(await screen.findByText('已在「运营」')).toBeInTheDocument()
|
||
expect(screen.getByText('占用')).toBeInTheDocument()
|
||
})
|
||
|
||
it('可以把部门从组里拿掉', async () => {
|
||
const fetchMock = installFetchSequence(
|
||
jsonResponse(createPayload()),
|
||
jsonResponse({ ok: true }),
|
||
jsonResponse(createPayload()),
|
||
)
|
||
|
||
await renderDashboard()
|
||
await openDepartments()
|
||
await userEvent.click(await screen.findByRole('button', { name: '移除运营中心A' }))
|
||
|
||
expect(fetchMock).toHaveBeenCalledWith(
|
||
'/api/organization/permission-groups',
|
||
expect.objectContaining({
|
||
method: 'PUT',
|
||
body: JSON.stringify({
|
||
action: 'remove-department',
|
||
slug: 'operations',
|
||
organizationUnitId: 'ops',
|
||
path: '江苏简尚品牌管理有限公司/运营中心A',
|
||
}),
|
||
}),
|
||
)
|
||
})
|
||
|
||
it('失效的旧关联不会当作有效部门,并能按关联 ID 清理', async () => {
|
||
const payload = createPayload()
|
||
payload.groups[0].departments = [{ id: 'stale', path: '公司/旧部门', name: '旧部门', status: 'invalid' }]
|
||
const fetchMock = installFetchSequence(jsonResponse(payload), jsonResponse({ ok: true }), jsonResponse(createPayload()))
|
||
await renderDashboard()
|
||
await openDepartments()
|
||
expect(await screen.findByText('关联已失效')).toBeInTheDocument()
|
||
expect(screen.getAllByText(/0 个部门 · 1 失效/).length).toBeGreaterThan(0)
|
||
await userEvent.click(screen.getByRole('button', { name: '移除已加入的旧部门' }))
|
||
const write = fetchMock.mock.calls.find(([, init]) => init?.method === 'PUT')
|
||
expect(JSON.parse(write?.[1].body)).toMatchObject({ action: 'remove-department', associationId: 'stale', slug: 'operations' })
|
||
})
|
||
|
||
it('save-pack 成功后只发起首屏 GET 和一次 PUT,并直接应用后端返回的标准化权限', async () => {
|
||
const nextPermissions = {
|
||
'3D场景': flags(),
|
||
'任务-详情': flags({
|
||
canDownloadSource: true,
|
||
canEditAssets: true,
|
||
canAccessPrivate: true,
|
||
}),
|
||
}
|
||
const fetchMock = installFetchSequence(
|
||
jsonResponse(createPayload()),
|
||
jsonResponse({ ok: true, permissions: nextPermissions }),
|
||
)
|
||
|
||
await renderDashboard()
|
||
const user = userEvent.setup()
|
||
const editAssets = getTaskDetailCheckbox('编辑图片')
|
||
const privateAccess = getTaskDetailCheckbox('私密素材查看/下载')
|
||
|
||
await user.click(editAssets)
|
||
await user.click(privateAccess)
|
||
await user.click(screen.getByRole('button', { name: '保存这组权限' }))
|
||
|
||
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2))
|
||
await waitFor(() => expect(screen.getByRole('button', { name: '保存这组权限' })).toBeDisabled())
|
||
|
||
expect(fetchMock.mock.calls[1]).toEqual([
|
||
'/api/organization/permission-groups',
|
||
expect.objectContaining({
|
||
method: 'PUT',
|
||
body: expect.stringContaining('"action":"save-pack"'),
|
||
}),
|
||
])
|
||
expect(screen.getByText('已保存')).toBeInTheDocument()
|
||
expect(getTaskDetailCheckbox('编辑图片')).toBeChecked()
|
||
expect(getTaskDetailCheckbox('私密素材查看/下载')).toBeChecked()
|
||
})
|
||
|
||
it('save-pack 超时后退出保存态,保留 draft,且不会自动重发 PUT', async () => {
|
||
jest.useFakeTimers()
|
||
const fetchMock = installFetchSequence(
|
||
jsonResponse(createPayload()),
|
||
pendingUntilAbort('save-pack PUT'),
|
||
)
|
||
|
||
await renderDashboard()
|
||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime })
|
||
await user.click(getTaskDetailCheckbox('私密素材查看/下载'))
|
||
await user.click(screen.getByRole('button', { name: '保存这组权限' }))
|
||
|
||
expect(screen.getByText('保存中…')).toBeInTheDocument()
|
||
|
||
await advanceAndFlush(15_100)
|
||
|
||
await waitFor(() => expect(screen.getByRole('button', { name: '保存这组权限' })).toBeEnabled())
|
||
expect(getTaskDetailCheckbox('私密素材查看/下载')).toBeChecked()
|
||
expect(screen.getByText(/结果尚未确认/)).toBeInTheDocument()
|
||
expect(screen.getByText(/刷新.*列表.*核对/)).toBeInTheDocument()
|
||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||
|
||
await advanceAndFlush(20_000)
|
||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||
})
|
||
|
||
it('其他动作 PUT 成功后即退出保存态,刷新挂起再超时时保留成功提示且不给出再次保存暗示', async () => {
|
||
jest.useFakeTimers()
|
||
const fetchMock = installFetchSequence(
|
||
jsonResponse(createPayload()),
|
||
jsonResponse({ ok: true }),
|
||
pendingUntilAbort('permission-groups refresh GET'),
|
||
)
|
||
|
||
await renderDashboard()
|
||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime })
|
||
await user.click(screen.getByRole('tab', { name: /部门/ }))
|
||
await user.click(await screen.findByRole('button', { name: '移除运营中心A' }))
|
||
|
||
expect(await screen.findByText('已从本组移除 运营中心A')).toBeInTheDocument()
|
||
expect(screen.queryByRole('button', { name: '保存这组权限' })).not.toBeInTheDocument()
|
||
expect(screen.queryByText('保存中…')).not.toBeInTheDocument()
|
||
expect(screen.getByRole('button', { name: '刷新中…' })).toBeDisabled()
|
||
|
||
await advanceAndFlush(15_100)
|
||
|
||
expect(await screen.findByText(/已保存,但列表刷新失败/)).toBeInTheDocument()
|
||
expect(screen.getByText(/不用再次保存/)).toBeInTheDocument()
|
||
expect(screen.getByText('已从本组移除 运营中心A')).toBeInTheDocument()
|
||
expect(fetchMock).toHaveBeenCalledTimes(3)
|
||
expect(fetchMock.mock.calls.filter(([, init]) => init?.method === 'PUT')).toHaveLength(1)
|
||
})
|
||
|
||
it('save-pack 失败后保留当前勾选,不自动刷新列表', async () => {
|
||
const fetchMock = installFetchSequence(
|
||
jsonResponse(createPayload()),
|
||
jsonResponse({ error: '保存权限组失败' }, false),
|
||
)
|
||
|
||
await renderDashboard()
|
||
const user = userEvent.setup()
|
||
await user.click(getTaskDetailCheckbox('编辑图片'))
|
||
await user.click(screen.getByRole('button', { name: '保存这组权限' }))
|
||
|
||
expect(await screen.findByText('保存权限组失败')).toBeInTheDocument()
|
||
expect(getTaskDetailCheckbox('编辑图片')).toBeChecked()
|
||
expect(screen.getByRole('button', { name: '保存这组权限' })).toBeEnabled()
|
||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||
})
|
||
|
||
it('在页签间切换会保留权限草稿,撤销后恢复原始权限', async () => {
|
||
const fetchMock = installFetchSequence(jsonResponse(createPayload()))
|
||
await renderDashboard()
|
||
const user = userEvent.setup()
|
||
await user.click(getTaskDetailCheckbox('编辑图片'))
|
||
await user.click(screen.getByRole('tab', { name: /人员/ }))
|
||
expect(screen.getByText('1 项未保存')).toBeInTheDocument()
|
||
await user.click(screen.getByRole('tab', { name: /素材权限/ }))
|
||
expect(getTaskDetailCheckbox('编辑图片')).toBeChecked()
|
||
await user.click(screen.getByRole('button', { name: '撤销修改' }))
|
||
expect(getTaskDetailCheckbox('编辑图片')).not.toBeChecked()
|
||
expect(screen.getByRole('button', { name: '保存这组权限' })).toBeDisabled()
|
||
expect(fetchMock).toHaveBeenCalledTimes(1)
|
||
})
|
||
|
||
it('改回原始勾选后不再提示未保存,首个修改不影响其他素材类型', async () => {
|
||
installFetchSequence(jsonResponse(createPayload()))
|
||
await renderDashboard()
|
||
const user = userEvent.setup()
|
||
await user.click(getTaskDetailCheckbox('编辑图片'))
|
||
expect(within(screen.getByRole('table')).getByRole('checkbox', { name: '3D场景 可见' })).toBeChecked()
|
||
await user.click(getTaskDetailCheckbox('编辑图片'))
|
||
expect(screen.getByRole('button', { name: '保存这组权限' })).toBeDisabled()
|
||
})
|
||
|
||
it('拒绝切换其他组时保留草稿且不会发出写请求', async () => {
|
||
const fetchMock = installFetchSequence(jsonResponse(createPayload()))
|
||
const confirm = jest.spyOn(window, 'confirm').mockReturnValue(false)
|
||
await renderDashboard()
|
||
const user = userEvent.setup()
|
||
await user.click(getTaskDetailCheckbox('编辑图片'))
|
||
await user.click(within(screen.getByRole('navigation', { name: '权限组' })).getByRole('button', { name: /视觉/ }))
|
||
expect(confirm).toHaveBeenCalledTimes(1)
|
||
expect(getTaskDetailCheckbox('编辑图片')).toBeChecked()
|
||
expect(fetchMock).toHaveBeenCalledTimes(1)
|
||
confirm.mockRestore()
|
||
})
|
||
|
||
it('部门搜索自动显示折叠树内的匹配部门及祖先', async () => {
|
||
const payload = createPayload()
|
||
payload.tree[0].children[0].children = [{ id: 'nested', name: '电商设计部', path: '江苏简尚品牌管理有限公司/运营中心A/电商设计部', parentId: 'ops', level: 3, children: [] }]
|
||
installFetchSequence(jsonResponse(payload))
|
||
await renderDashboard()
|
||
await openDepartments()
|
||
expect(screen.queryByText('电商设计部')).not.toBeInTheDocument()
|
||
await userEvent.type(screen.getByLabelText('查找并加入部门'), '电商设计')
|
||
expect(screen.getByText('电商设计部')).toBeInTheDocument()
|
||
expect(screen.getByText('随 运营中心A · 运营')).toBeInTheDocument()
|
||
})
|
||
|
||
it('组名失焦不写入,明确保存组名后保留素材权限草稿', async () => {
|
||
const renamed = createPayload()
|
||
renamed.groups[0].name = '运营新组'
|
||
const fetchMock = installFetchSequence(jsonResponse(createPayload()), jsonResponse({ ok: true }), jsonResponse(renamed))
|
||
await renderDashboard()
|
||
const user = userEvent.setup()
|
||
await user.click(getTaskDetailCheckbox('编辑图片'))
|
||
await user.click(screen.getByRole('tab', { name: '组设置' }))
|
||
await user.clear(screen.getByLabelText('组名'))
|
||
await user.type(screen.getByLabelText('组名'), '运营新组')
|
||
await user.tab()
|
||
expect(fetchMock).toHaveBeenCalledTimes(1)
|
||
await user.click(screen.getByRole('button', { name: '保存组名' }))
|
||
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(3))
|
||
await user.click(screen.getByRole('tab', { name: /素材权限/ }))
|
||
expect(getTaskDetailCheckbox('编辑图片')).toBeChecked()
|
||
expect(fetchMock.mock.calls[1][1].body).toBe(JSON.stringify({ action: 'rename', slug: 'operations', name: '运营新组' }))
|
||
})
|
||
|
||
it('管理部门后的刷新保留未保存的素材权限', async () => {
|
||
const fetchMock = installFetchSequence(jsonResponse(createPayload()), jsonResponse({ ok: true }), jsonResponse(createPayload()))
|
||
await renderDashboard()
|
||
const user = userEvent.setup()
|
||
await user.click(getTaskDetailCheckbox('私密素材查看/下载'))
|
||
await user.click(screen.getByRole('tab', { name: /部门/ }))
|
||
await user.click(screen.getByRole('button', { name: '移除运营中心A' }))
|
||
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(3))
|
||
await user.click(screen.getByRole('tab', { name: /素材权限/ }))
|
||
expect(getTaskDetailCheckbox('私密素材查看/下载')).toBeChecked()
|
||
expect(screen.getByRole('button', { name: '保存这组权限' })).toBeEnabled()
|
||
})
|
||
|
||
it('页签支持方向键切换并移动焦点', async () => {
|
||
installFetchSequence(jsonResponse(createPayload()))
|
||
await renderDashboard()
|
||
screen.getByRole('tab', { name: /素材权限/ }).focus()
|
||
await userEvent.keyboard('{ArrowRight}')
|
||
expect(screen.getByRole('tab', { name: /部门/ })).toHaveFocus()
|
||
expect(screen.getByRole('tab', { name: /部门/ })).toHaveAttribute('aria-selected', 'true')
|
||
await userEvent.keyboard('{End}')
|
||
expect(screen.getByRole('tab', { name: '组设置' })).toHaveFocus()
|
||
})
|
||
|
||
it('人员搜索失败显示可恢复错误,清空后移除错误状态', async () => {
|
||
installFetchSequence(jsonResponse(createPayload()), jsonResponse({ error: 'unavailable' }, false))
|
||
await renderDashboard()
|
||
const user = userEvent.setup()
|
||
await user.click(screen.getByRole('tab', { name: /人员/ }))
|
||
await user.type(screen.getByLabelText('搜人加入本组'), '测试')
|
||
expect(await screen.findByText('搜索失败,请修改关键词或重试。')).toBeInTheDocument()
|
||
await user.click(screen.getByRole('button', { name: '清空人员搜索' }))
|
||
expect(screen.queryByText('搜索失败,请修改关键词或重试。')).not.toBeInTheDocument()
|
||
})
|
||
|
||
it('较早的人员搜索响应不会覆盖当前搜索结果', async () => {
|
||
jest.useFakeTimers()
|
||
let resolveFirst: (value: unknown) => void = () => {}
|
||
const firstRequest = new Promise(resolve => { resolveFirst = resolve })
|
||
installFetchSequence(jsonResponse(createPayload()), () => firstRequest, jsonResponse({ candidates: [{ userId: 'new', displayName: '当前候选', username: 'new', departmentName: '设计部', departmentPath: null }] }))
|
||
await renderDashboard()
|
||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime })
|
||
await user.click(screen.getByRole('tab', { name: /人员/ }))
|
||
await user.type(screen.getByLabelText('搜人加入本组'), 'first')
|
||
await advanceAndFlush(300)
|
||
await user.clear(screen.getByLabelText('搜人加入本组'))
|
||
await user.type(screen.getByLabelText('搜人加入本组'), 'second')
|
||
await advanceAndFlush(300)
|
||
expect(screen.getByText('当前候选')).toBeInTheDocument()
|
||
await act(async () => { resolveFirst({ ok: true, json: async () => ({ candidates: [{ userId: 'old', displayName: '过期候选', username: 'old' }] }) }); await Promise.resolve() })
|
||
expect(screen.getByText('当前候选')).toBeInTheDocument()
|
||
expect(screen.queryByText('过期候选')).not.toBeInTheDocument()
|
||
})
|
||
|
||
it('本页链接取消离开时保留素材权限草稿', async () => {
|
||
const fetchMock = installFetchSequence(jsonResponse(createPayload()))
|
||
const confirm = jest.spyOn(window, 'confirm').mockReturnValue(false)
|
||
await renderDashboard()
|
||
await userEvent.click(getTaskDetailCheckbox('编辑图片'))
|
||
expect(fireEvent.click(screen.getByRole('link', { name: '按人看权限' }))).toBe(false)
|
||
expect(confirm).toHaveBeenCalledTimes(1)
|
||
expect(getTaskDetailCheckbox('编辑图片')).toBeChecked()
|
||
expect(fetchMock).toHaveBeenCalledTimes(1)
|
||
confirm.mockRestore()
|
||
})
|
||
|
||
it('权限请求处理中阻止本页链接离开', async () => {
|
||
jest.useFakeTimers()
|
||
const fetchMock = installFetchSequence(jsonResponse(createPayload()), pendingUntilAbort('save-pack PUT'))
|
||
await renderDashboard()
|
||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime })
|
||
await user.click(getTaskDetailCheckbox('编辑图片'))
|
||
await user.click(screen.getByRole('button', { name: '保存这组权限' }))
|
||
const link = screen.getByRole('link', { name: '按人看权限' })
|
||
expect(link).toHaveAttribute('aria-disabled', 'true')
|
||
expect(fireEvent.click(link)).toBe(false)
|
||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||
await advanceAndFlush(15_100)
|
||
})
|
||
|
||
it('创建新组与本页链接都保护未保存的组名', async () => {
|
||
const fetchMock = installFetchSequence(jsonResponse(createPayload()))
|
||
const confirm = jest.spyOn(window, 'confirm').mockReturnValue(false)
|
||
await renderDashboard()
|
||
const user = userEvent.setup()
|
||
await user.click(screen.getByRole('tab', { name: '组设置' }))
|
||
await user.type(screen.getByLabelText('组名'), '待保存')
|
||
expect(fireEvent.click(screen.getByRole('link', { name: '按人看权限' }))).toBe(false)
|
||
await user.click(screen.getByRole('button', { name: '新建' }))
|
||
await user.type(screen.getByLabelText('新建权限组'), '新组')
|
||
await user.click(screen.getByRole('button', { name: '创建' }))
|
||
expect(confirm).toHaveBeenCalledTimes(2)
|
||
expect(fetchMock).toHaveBeenCalledTimes(1)
|
||
expect(screen.getByLabelText('组名')).toHaveValue('运营待保存')
|
||
confirm.mockRestore()
|
||
})
|
||
})
|