Add Vitest + testing-library/react + jsdom for frontend testing. Tests cover: cn() utility (6), usePermissions hook (27), useTreeValidation hook (22), and userPreferencesStore (6). CI updated to run frontend tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
193 lines
6.2 KiB
TypeScript
193 lines
6.2 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest'
|
|
import { renderHook } from '@testing-library/react'
|
|
import { usePermissions } from './usePermissions'
|
|
import { useAuthStore } from '@/store/authStore'
|
|
import type { User } from '@/types'
|
|
|
|
function makeUser(overrides: Partial<User> = {}): User {
|
|
return {
|
|
id: 'user-1',
|
|
email: 'test@example.com',
|
|
name: 'Test User',
|
|
role: 'engineer',
|
|
is_super_admin: false,
|
|
account_id: 'acct-1',
|
|
account_role: 'engineer',
|
|
created_at: '2026-01-01T00:00:00Z',
|
|
last_login: null,
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
describe('usePermissions', () => {
|
|
beforeEach(() => {
|
|
useAuthStore.setState({ user: null, token: null, isAuthenticated: false })
|
|
})
|
|
|
|
describe('when not authenticated', () => {
|
|
it('defaults to viewer role', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.effectiveRole).toBe('viewer')
|
|
expect(result.current.isViewer).toBe(true)
|
|
})
|
|
|
|
it('cannot create trees', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canCreateTrees).toBe(false)
|
|
})
|
|
|
|
it('cannot create steps', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canCreateSteps).toBe(false)
|
|
})
|
|
|
|
it('cannot delete trees', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canDeleteTree()).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('viewer role', () => {
|
|
beforeEach(() => {
|
|
useAuthStore.setState({ user: makeUser({ role: 'viewer', account_role: 'viewer' }) })
|
|
})
|
|
|
|
it('has viewer effective role', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.effectiveRole).toBe('viewer')
|
|
expect(result.current.isViewer).toBe(true)
|
|
})
|
|
|
|
it('cannot create trees', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canCreateTrees).toBe(false)
|
|
})
|
|
|
|
it('cannot edit any tree', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canEditTree({ author_id: 'user-1' })).toBe(false)
|
|
})
|
|
|
|
it('cannot manage categories', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canManageCategories).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('engineer role', () => {
|
|
beforeEach(() => {
|
|
useAuthStore.setState({ user: makeUser() })
|
|
})
|
|
|
|
it('has engineer effective role', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.effectiveRole).toBe('engineer')
|
|
expect(result.current.isEngineer).toBe(true)
|
|
})
|
|
|
|
it('can create trees', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canCreateTrees).toBe(true)
|
|
})
|
|
|
|
it('can edit own tree', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canEditTree({ author_id: 'user-1' })).toBe(true)
|
|
})
|
|
|
|
it('cannot edit other user tree', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canEditTree({ author_id: 'other-user' })).toBe(false)
|
|
})
|
|
|
|
it('cannot delete trees', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canDeleteTree()).toBe(false)
|
|
})
|
|
|
|
it('can edit own step', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canEditStep({ created_by: 'user-1' })).toBe(true)
|
|
})
|
|
|
|
it('cannot edit other step', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canEditStep({ created_by: 'other' })).toBe(false)
|
|
})
|
|
|
|
it('cannot manage categories', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canManageCategories).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('owner role', () => {
|
|
beforeEach(() => {
|
|
useAuthStore.setState({ user: makeUser({ account_role: 'owner' }) })
|
|
})
|
|
|
|
it('has owner effective role', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.effectiveRole).toBe('owner')
|
|
expect(result.current.isAccountOwner).toBe(true)
|
|
})
|
|
|
|
it('can edit account tree', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canEditTree({ author_id: 'other', account_id: 'acct-1' })).toBe(true)
|
|
})
|
|
|
|
it('cannot edit other account tree', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canEditTree({ author_id: 'other', account_id: 'other-acct' })).toBe(false)
|
|
})
|
|
|
|
it('can manage categories', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canManageCategories).toBe(true)
|
|
})
|
|
|
|
it('cannot manage global categories', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canManageGlobalCategories).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('super admin', () => {
|
|
beforeEach(() => {
|
|
useAuthStore.setState({ user: makeUser({ is_super_admin: true }) })
|
|
})
|
|
|
|
it('has super_admin effective role', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.effectiveRole).toBe('super_admin')
|
|
expect(result.current.isSuperAdmin).toBe(true)
|
|
})
|
|
|
|
it('can edit any tree', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canEditTree({ author_id: 'anyone', account_id: 'any' })).toBe(true)
|
|
})
|
|
|
|
it('can delete trees', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canDeleteTree()).toBe(true)
|
|
})
|
|
|
|
it('can edit any step', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canEditStep({ created_by: 'anyone' })).toBe(true)
|
|
})
|
|
|
|
it('can manage global categories', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canManageGlobalCategories).toBe(true)
|
|
})
|
|
|
|
it('can manage account', () => {
|
|
const { result } = renderHook(() => usePermissions())
|
|
expect(result.current.canManageAccount).toBe(true)
|
|
})
|
|
})
|
|
})
|