Add staff permission controls for editing staff and customers
- Add can_edit_staff and can_edit_customers dangerous permissions - Move Site Builder, Services, Locations, Time Blocks, Payments to Settings permissions - Link Edit Others' Schedules and Edit Own Schedule permissions - Add permission checks to StaffViewSet (partial_update, toggle_active, verify_email) - Add permission checks to CustomerViewSet (update, partial_update, verify_email) - Fix CustomerViewSet permission key mismatch (can_access_customers) - Hide Edit/Verify buttons on Staff and Customers pages without permission - Make dangerous permissions section more visually distinct (darker red) - Fix StaffDashboard links to use correct paths (/dashboard/my-schedule) - Disable settings sub-permissions when Access Settings is unchecked 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
210
frontend/src/data/__tests__/helpSearchIndex.test.ts
Normal file
210
frontend/src/data/__tests__/helpSearchIndex.test.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Tests for helpSearchIndex data and utility functions
|
||||
*/
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
helpSearchIndex,
|
||||
getHelpContextForAI,
|
||||
HelpPage,
|
||||
} from '../helpSearchIndex';
|
||||
|
||||
describe('helpSearchIndex', () => {
|
||||
it('contains help pages', () => {
|
||||
expect(helpSearchIndex.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('all pages have required properties', () => {
|
||||
helpSearchIndex.forEach((page) => {
|
||||
expect(page.path).toBeTruthy();
|
||||
expect(page.title).toBeTruthy();
|
||||
expect(page.description).toBeTruthy();
|
||||
expect(Array.isArray(page.topics)).toBe(true);
|
||||
expect(page.topics.length).toBeGreaterThan(0);
|
||||
expect(page.category).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('all paths start with /dashboard/help', () => {
|
||||
helpSearchIndex.forEach((page) => {
|
||||
expect(page.path.startsWith('/dashboard/help')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('paths are unique', () => {
|
||||
const paths = helpSearchIndex.map((p) => p.path);
|
||||
const uniquePaths = new Set(paths);
|
||||
expect(uniquePaths.size).toBe(paths.length);
|
||||
});
|
||||
|
||||
describe('categories', () => {
|
||||
it('contains Core Features category', () => {
|
||||
const corePages = helpSearchIndex.filter((p) => p.category === 'Core Features');
|
||||
expect(corePages.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('contains Management category', () => {
|
||||
const managementPages = helpSearchIndex.filter((p) => p.category === 'Management');
|
||||
expect(managementPages.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('contains Communication category', () => {
|
||||
const communicationPages = helpSearchIndex.filter((p) => p.category === 'Communication');
|
||||
expect(communicationPages.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('contains Payments category', () => {
|
||||
const paymentPages = helpSearchIndex.filter((p) => p.category === 'Payments');
|
||||
expect(paymentPages.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('contains Automations category', () => {
|
||||
const automationPages = helpSearchIndex.filter((p) => p.category === 'Automations');
|
||||
expect(automationPages.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('contains API category', () => {
|
||||
const apiPages = helpSearchIndex.filter((p) => p.category === 'API');
|
||||
expect(apiPages.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('contains Settings category', () => {
|
||||
const settingsPages = helpSearchIndex.filter((p) => p.category === 'Settings');
|
||||
expect(settingsPages.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('specific pages', () => {
|
||||
it('includes Dashboard page', () => {
|
||||
const dashboard = helpSearchIndex.find((p) => p.path === '/dashboard/help/dashboard');
|
||||
expect(dashboard).toBeDefined();
|
||||
expect(dashboard?.title).toBe('Dashboard');
|
||||
expect(dashboard?.category).toBe('Core Features');
|
||||
});
|
||||
|
||||
it('includes Scheduler page', () => {
|
||||
const scheduler = helpSearchIndex.find((p) => p.path === '/dashboard/help/scheduler');
|
||||
expect(scheduler).toBeDefined();
|
||||
expect(scheduler?.title).toBe('Scheduler');
|
||||
expect(scheduler?.topics).toContain('calendar');
|
||||
});
|
||||
|
||||
it('includes Customers page', () => {
|
||||
const customers = helpSearchIndex.find((p) => p.path === '/dashboard/help/customers');
|
||||
expect(customers).toBeDefined();
|
||||
expect(customers?.title).toBe('Customers');
|
||||
expect(customers?.category).toBe('Management');
|
||||
});
|
||||
|
||||
it('includes Services page', () => {
|
||||
const services = helpSearchIndex.find((p) => p.path === '/dashboard/help/services');
|
||||
expect(services).toBeDefined();
|
||||
expect(services?.topics).toContain('pricing');
|
||||
});
|
||||
|
||||
it('includes API Overview page', () => {
|
||||
const api = helpSearchIndex.find((p) => p.path === '/dashboard/help/api');
|
||||
expect(api).toBeDefined();
|
||||
expect(api?.title).toBe('API Overview');
|
||||
expect(api?.category).toBe('API');
|
||||
});
|
||||
|
||||
it('includes Booking Settings page', () => {
|
||||
const booking = helpSearchIndex.find((p) => p.path === '/dashboard/help/settings/booking');
|
||||
expect(booking).toBeDefined();
|
||||
expect(booking?.topics).toContain('cancellation');
|
||||
expect(booking?.topics).toContain('reschedule');
|
||||
});
|
||||
});
|
||||
|
||||
describe('topics', () => {
|
||||
it('pages have relevant topics', () => {
|
||||
const scheduler = helpSearchIndex.find((p) => p.path === '/dashboard/help/scheduler');
|
||||
expect(scheduler?.topics).toContain('appointments');
|
||||
expect(scheduler?.topics).toContain('bookings');
|
||||
expect(scheduler?.topics).toContain('calendar');
|
||||
});
|
||||
|
||||
it('payments page has payment-related topics', () => {
|
||||
const payments = helpSearchIndex.find((p) => p.path === '/dashboard/help/payments');
|
||||
expect(payments?.topics).toContain('payments');
|
||||
expect(payments?.topics).toContain('stripe');
|
||||
expect(payments?.topics).toContain('credit card');
|
||||
});
|
||||
|
||||
it('staff page has staff-related topics', () => {
|
||||
const staff = helpSearchIndex.find((p) => p.path === '/dashboard/help/staff');
|
||||
expect(staff?.topics).toContain('permissions');
|
||||
expect(staff?.topics).toContain('roles');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHelpContextForAI', () => {
|
||||
it('returns a string', () => {
|
||||
const context = getHelpContextForAI();
|
||||
expect(typeof context).toBe('string');
|
||||
});
|
||||
|
||||
it('contains all page titles', () => {
|
||||
const context = getHelpContextForAI();
|
||||
helpSearchIndex.forEach((page) => {
|
||||
expect(context).toContain(`Page: ${page.title}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('contains all page paths', () => {
|
||||
const context = getHelpContextForAI();
|
||||
helpSearchIndex.forEach((page) => {
|
||||
expect(context).toContain(`Path: ${page.path}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('contains all page categories', () => {
|
||||
const context = getHelpContextForAI();
|
||||
helpSearchIndex.forEach((page) => {
|
||||
expect(context).toContain(`Category: ${page.category}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('contains all page descriptions', () => {
|
||||
const context = getHelpContextForAI();
|
||||
helpSearchIndex.forEach((page) => {
|
||||
expect(context).toContain(`Description: ${page.description}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('contains topics for each page', () => {
|
||||
const context = getHelpContextForAI();
|
||||
helpSearchIndex.forEach((page) => {
|
||||
expect(context).toContain(`Topics: ${page.topics.join(', ')}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses separator between pages', () => {
|
||||
const context = getHelpContextForAI();
|
||||
expect(context).toContain('---');
|
||||
});
|
||||
|
||||
it('is non-empty', () => {
|
||||
const context = getHelpContextForAI();
|
||||
expect(context.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HelpPage type', () => {
|
||||
it('can create a valid HelpPage object', () => {
|
||||
const page: HelpPage = {
|
||||
path: '/test',
|
||||
title: 'Test Page',
|
||||
description: 'A test page',
|
||||
topics: ['test', 'example'],
|
||||
category: 'Test Category',
|
||||
};
|
||||
|
||||
expect(page.path).toBe('/test');
|
||||
expect(page.title).toBe('Test Page');
|
||||
expect(page.description).toBe('A test page');
|
||||
expect(page.topics).toContain('test');
|
||||
expect(page.category).toBe('Test Category');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user