- Refactored navigation with grouped sections (Operations, Cultivation, Analytics, etc.) - Added RBAC-based navigation filtering by user role - Created DevTools panel for quick user switching during testing - Added collapsible sidebar sections on desktop - Mobile: bottom nav bar (4 items + More) with slide-up sheet - Enhanced seed data with [DEMO] prefix markers - Added multiple demo users: Owner, Manager, Cultivator, Worker - Fixed domain to runfoo.run - Added Audit Log and SOP Library pages to navigation - Created usePermissions hook and RoleBadge component
128 lines
4.2 KiB
TypeScript
128 lines
4.2 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { render, screen } from '@testing-library/react';
|
|
import { BrowserRouter } from 'react-router-dom';
|
|
|
|
// Mock auth context
|
|
vi.mock('../context/AuthContext', () => ({
|
|
useAuth: () => ({
|
|
user: { id: '1', name: 'Test User', email: 'test@test.com', role: 'ADMIN' },
|
|
isAuthenticated: true,
|
|
login: vi.fn(),
|
|
logout: vi.fn(),
|
|
}),
|
|
AuthProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
}));
|
|
|
|
describe('App', () => {
|
|
it('renders without crashing', () => {
|
|
// Basic smoke test
|
|
expect(true).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Utility Functions', () => {
|
|
describe('formatFileSize', () => {
|
|
it('formats bytes correctly', async () => {
|
|
const { formatFileSize } = await import('../lib/photoCompression');
|
|
|
|
expect(formatFileSize(500)).toBe('500 B');
|
|
expect(formatFileSize(1024)).toBe('1.0 KB');
|
|
expect(formatFileSize(1536)).toBe('1.5 KB');
|
|
expect(formatFileSize(1048576)).toBe('1.0 MB');
|
|
});
|
|
});
|
|
|
|
describe('isValidImageType', () => {
|
|
it('validates image types', async () => {
|
|
const { isValidImageType } = await import('../lib/photoCompression');
|
|
|
|
const jpegFile = new File([''], 'test.jpg', { type: 'image/jpeg' });
|
|
const pngFile = new File([''], 'test.png', { type: 'image/png' });
|
|
const textFile = new File([''], 'test.txt', { type: 'text/plain' });
|
|
|
|
expect(isValidImageType(jpegFile)).toBe(true);
|
|
expect(isValidImageType(pngFile)).toBe(true);
|
|
expect(isValidImageType(textFile)).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('QR Code Utils', () => {
|
|
it('generates batch QR data', async () => {
|
|
const { generateBatchQRData } = await import('../lib/qrcode');
|
|
|
|
const data = generateBatchQRData('batch-123', 'Test Batch');
|
|
const parsed = JSON.parse(data);
|
|
|
|
expect(parsed.type).toBe('batch');
|
|
expect(parsed.id).toBe('batch-123');
|
|
expect(parsed.name).toBe('Test Batch');
|
|
});
|
|
|
|
it('parses QR data', async () => {
|
|
const { parseQRData } = await import('../lib/qrcode');
|
|
|
|
const data = JSON.stringify({ type: 'plant', id: 'plant-456' });
|
|
const parsed = parseQRData(data);
|
|
|
|
expect(parsed?.type).toBe('plant');
|
|
expect(parsed?.id).toBe('plant-456');
|
|
});
|
|
|
|
it('handles invalid QR data', async () => {
|
|
const { parseQRData } = await import('../lib/qrcode');
|
|
|
|
const result = parseQRData('not-json');
|
|
expect(result.type).toBe('unknown');
|
|
});
|
|
});
|
|
|
|
describe('Accessibility Utils', () => {
|
|
it('generates ARIA IDs', async () => {
|
|
const { generateAriaId } = await import('../lib/accessibility');
|
|
|
|
const id1 = generateAriaId('button');
|
|
const id2 = generateAriaId('button');
|
|
|
|
expect(id1).toContain('button');
|
|
expect(id1).not.toBe(id2); // Should be unique
|
|
});
|
|
});
|
|
|
|
describe('API Clients', () => {
|
|
describe('Batches API', () => {
|
|
it('exports required functions', async () => {
|
|
const { batchesApi } = await import('../lib/batchesApi');
|
|
|
|
expect(typeof batchesApi.getAll).toBe('function');
|
|
expect(typeof batchesApi.create).toBe('function');
|
|
});
|
|
});
|
|
|
|
describe('Rooms API', () => {
|
|
it('exports required functions', async () => {
|
|
const { roomsApi } = await import('../lib/roomsApi');
|
|
|
|
expect(typeof roomsApi.getAll).toBe('function');
|
|
});
|
|
});
|
|
|
|
describe('Visitors API', () => {
|
|
it('exports required functions', async () => {
|
|
const { visitorsApi } = await import('../lib/visitorsApi');
|
|
|
|
expect(typeof visitorsApi.getAll).toBe('function');
|
|
expect(typeof visitorsApi.checkIn).toBe('function');
|
|
expect(typeof visitorsApi.checkOut).toBe('function');
|
|
});
|
|
});
|
|
|
|
describe('Messaging API', () => {
|
|
it('exports required functions', async () => {
|
|
const { messagingApi } = await import('../lib/messagingApi');
|
|
|
|
expect(typeof messagingApi.getAnnouncements).toBe('function');
|
|
expect(typeof messagingApi.acknowledge).toBe('function');
|
|
});
|
|
});
|
|
});
|