add editable menu items for companies
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('../../lib/prisma', () => ({
|
||||
prisma: {
|
||||
menuItem: {
|
||||
findMany: vi.fn(),
|
||||
},
|
||||
employee: {
|
||||
findUniqueOrThrow: vi.fn(),
|
||||
},
|
||||
company: {
|
||||
findUniqueOrThrow: vi.fn(),
|
||||
},
|
||||
auditLog: {
|
||||
create: vi.fn(),
|
||||
},
|
||||
$transaction: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
import { prisma } from '../../lib/prisma'
|
||||
import { getEmployeeMenu, previewCompanyMenu } from './menu.service'
|
||||
|
||||
describe('menu.service', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('builds the employee menu from subscription defaults and company overrides', async () => {
|
||||
vi.mocked(prisma.employee.findUniqueOrThrow).mockResolvedValue({
|
||||
id: 'employee_1',
|
||||
role: 'MANAGER',
|
||||
isActive: true,
|
||||
companyId: 'company_1',
|
||||
} as never)
|
||||
|
||||
vi.mocked(prisma.company.findUniqueOrThrow).mockResolvedValue({
|
||||
id: 'company_1',
|
||||
name: 'Atlas Cars',
|
||||
status: 'ACTIVE',
|
||||
subscription: { plan: 'GROWTH', status: 'ACTIVE' },
|
||||
} as never)
|
||||
|
||||
vi.mocked(prisma.menuItem.findMany).mockResolvedValue([
|
||||
{
|
||||
id: 'item_dashboard',
|
||||
systemKey: 'dashboard',
|
||||
label: 'Dashboard',
|
||||
itemType: 'INTERNAL_PAGE',
|
||||
routeOrUrl: '/',
|
||||
icon: 'LayoutDashboard',
|
||||
parentId: null,
|
||||
openInNewTab: false,
|
||||
isRequired: true,
|
||||
isActive: true,
|
||||
displayOrder: 1,
|
||||
roleVisibilities: [{ role: 'MANAGER' }],
|
||||
subscriptionAssignments: [],
|
||||
companyAssignments: [],
|
||||
},
|
||||
{
|
||||
id: 'item_reports',
|
||||
systemKey: 'reports',
|
||||
label: 'Reports',
|
||||
itemType: 'INTERNAL_PAGE',
|
||||
routeOrUrl: '/reports',
|
||||
icon: 'BarChart2',
|
||||
parentId: null,
|
||||
openInNewTab: false,
|
||||
isRequired: false,
|
||||
isActive: true,
|
||||
displayOrder: 80,
|
||||
roleVisibilities: [{ role: 'MANAGER' }],
|
||||
subscriptionAssignments: [{ plan: 'GROWTH', displayOrder: 80, isActive: true }],
|
||||
companyAssignments: [],
|
||||
},
|
||||
{
|
||||
id: 'item_finance',
|
||||
systemKey: null,
|
||||
label: 'Finance Portal',
|
||||
itemType: 'EXTERNAL_LINK',
|
||||
routeOrUrl: 'https://finance.example.com',
|
||||
icon: null,
|
||||
parentId: null,
|
||||
openInNewTab: true,
|
||||
isRequired: false,
|
||||
isActive: true,
|
||||
displayOrder: 200,
|
||||
roleVisibilities: [{ role: 'MANAGER' }],
|
||||
subscriptionAssignments: [],
|
||||
companyAssignments: [{ companyId: 'company_1', displayOrder: 5, isActive: true }],
|
||||
},
|
||||
{
|
||||
id: 'item_owner_only',
|
||||
systemKey: 'settings',
|
||||
label: 'Settings',
|
||||
itemType: 'INTERNAL_PAGE',
|
||||
routeOrUrl: '/settings',
|
||||
icon: 'Settings',
|
||||
parentId: null,
|
||||
openInNewTab: false,
|
||||
isRequired: false,
|
||||
isActive: true,
|
||||
displayOrder: 150,
|
||||
roleVisibilities: [{ role: 'OWNER' }],
|
||||
subscriptionAssignments: [{ plan: 'GROWTH', displayOrder: 150, isActive: true }],
|
||||
companyAssignments: [],
|
||||
},
|
||||
] as never)
|
||||
|
||||
const result = await getEmployeeMenu('employee_1')
|
||||
|
||||
expect(result.items).toHaveLength(3)
|
||||
expect(result.items.map((item) => item.label)).toEqual(['Dashboard', 'Finance Portal', 'Reports'])
|
||||
expect(result.items[0]).toMatchObject({
|
||||
label: 'Dashboard',
|
||||
itemType: 'INTERNAL_PAGE',
|
||||
})
|
||||
expect(result.items[1]).toMatchObject({
|
||||
label: 'Finance Portal',
|
||||
itemType: 'EXTERNAL_LINK',
|
||||
openInNewTab: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('explains hidden items in preview results', async () => {
|
||||
vi.mocked(prisma.company.findUniqueOrThrow).mockResolvedValue({
|
||||
id: 'company_1',
|
||||
name: 'Atlas Cars',
|
||||
status: 'ACTIVE',
|
||||
subscription: { plan: 'STARTER', status: 'ACTIVE' },
|
||||
} as never)
|
||||
|
||||
vi.mocked(prisma.menuItem.findMany).mockResolvedValue([
|
||||
{
|
||||
id: 'item_dashboard',
|
||||
systemKey: 'dashboard',
|
||||
label: 'Dashboard',
|
||||
itemType: 'INTERNAL_PAGE',
|
||||
routeOrUrl: '/',
|
||||
icon: 'LayoutDashboard',
|
||||
parentId: null,
|
||||
openInNewTab: false,
|
||||
isRequired: true,
|
||||
isActive: true,
|
||||
displayOrder: 1,
|
||||
roleVisibilities: [{ role: 'MANAGER' }],
|
||||
subscriptionAssignments: [],
|
||||
companyAssignments: [],
|
||||
},
|
||||
{
|
||||
id: 'item_reports',
|
||||
systemKey: 'reports',
|
||||
label: 'Reports',
|
||||
itemType: 'INTERNAL_PAGE',
|
||||
routeOrUrl: '/reports',
|
||||
icon: 'BarChart2',
|
||||
parentId: null,
|
||||
openInNewTab: false,
|
||||
isRequired: false,
|
||||
isActive: true,
|
||||
displayOrder: 80,
|
||||
roleVisibilities: [{ role: 'MANAGER' }],
|
||||
subscriptionAssignments: [{ plan: 'GROWTH', displayOrder: 80, isActive: true }],
|
||||
companyAssignments: [],
|
||||
},
|
||||
{
|
||||
id: 'item_direct',
|
||||
systemKey: null,
|
||||
label: 'Finance Portal',
|
||||
itemType: 'EXTERNAL_LINK',
|
||||
routeOrUrl: 'https://finance.example.com',
|
||||
icon: null,
|
||||
parentId: null,
|
||||
openInNewTab: true,
|
||||
isRequired: false,
|
||||
isActive: true,
|
||||
displayOrder: 20,
|
||||
roleVisibilities: [{ role: 'MANAGER' }],
|
||||
subscriptionAssignments: [],
|
||||
companyAssignments: [{ companyId: 'company_1', displayOrder: 10, isActive: true }],
|
||||
},
|
||||
] as never)
|
||||
|
||||
const result = await previewCompanyMenu({ companyId: 'company_1', role: 'MANAGER' })
|
||||
|
||||
expect(result.items.find((item) => item.label === 'Dashboard')).toMatchObject({
|
||||
visible: true,
|
||||
source: 'subscription',
|
||||
})
|
||||
expect(result.items.find((item) => item.label === 'Dashboard')?.reasons.join(' ')).toContain('defaults to all subscription plans')
|
||||
expect(result.items.find((item) => item.label === 'Finance Portal')).toMatchObject({
|
||||
visible: true,
|
||||
source: 'company',
|
||||
})
|
||||
expect(result.items.find((item) => item.label === 'Reports')).toMatchObject({
|
||||
visible: false,
|
||||
})
|
||||
expect(result.items.find((item) => item.label === 'Reports')?.reasons.join(' ')).toContain('STARTER does not include this menu item')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user