- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
248 lines
9.7 KiB
TypeScript
248 lines
9.7 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('CreateTicketTypeModal', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Navigate to login and authenticate as admin
|
|
await page.goto('/login');
|
|
await page.fill('[data-testid="email-input"]', 'admin@test.com');
|
|
await page.fill('[data-testid="password-input"]', 'password');
|
|
await page.click('[data-testid="login-button"]');
|
|
|
|
// Wait for redirect to dashboard
|
|
await expect(page).toHaveURL('/dashboard');
|
|
|
|
// Navigate to a specific event detail page
|
|
await page.goto('/events/evt-1');
|
|
await expect(page.getByTestId('event-detail-page')).toBeVisible();
|
|
});
|
|
|
|
test('should open modal when Add Ticket Type button is clicked', async ({ page }) => {
|
|
// Click the Add Ticket Type button
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Check that modal opens
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: 'Create Ticket Type' })).toBeVisible();
|
|
});
|
|
|
|
test('should validate required fields', async ({ page }) => {
|
|
// Open modal
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Try to submit empty form
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Check for validation errors
|
|
await expect(page.getByText('Name must be at least 2 characters')).toBeVisible();
|
|
await expect(page.getByText('Price must be greater than 0')).toBeVisible();
|
|
});
|
|
|
|
test('should validate name length constraints', async ({ page }) => {
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Test minimum length
|
|
await page.fill('input[name="name"]', 'A');
|
|
await page.click('button[type="submit"]');
|
|
await expect(page.getByText('Name must be at least 2 characters')).toBeVisible();
|
|
|
|
// Test maximum length
|
|
const longName = 'A'.repeat(61);
|
|
await page.fill('input[name="name"]', longName);
|
|
await page.click('button[type="submit"]');
|
|
await expect(page.getByText('Name must be 60 characters or less')).toBeVisible();
|
|
});
|
|
|
|
test('should validate price constraints', async ({ page }) => {
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Fill required name field
|
|
await page.fill('input[name="name"]', 'Test Ticket');
|
|
|
|
// Test negative price
|
|
await page.fill('input[type="number"][step="0.01"]', '-10');
|
|
await page.click('button[type="submit"]');
|
|
await expect(page.getByText('Price cannot be negative')).toBeVisible();
|
|
|
|
// Test zero price (should be allowed for free tickets)
|
|
await page.fill('input[type="number"][step="0.01"]', '0');
|
|
await page.fill('input[name="inventory"]', '100');
|
|
// Should not show price error for free tickets
|
|
await expect(page.getByText('Price must be greater than 0')).not.toBeVisible();
|
|
});
|
|
|
|
test('should validate inventory constraints', async ({ page }) => {
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Fill required fields
|
|
await page.fill('input[name="name"]', 'Test Ticket');
|
|
await page.fill('input[type="number"][step="0.01"]', '25.00');
|
|
|
|
// Test negative inventory
|
|
await page.fill('input[name="inventory"]', '-1');
|
|
await page.click('button[type="submit"]');
|
|
await expect(page.getByText('Inventory cannot be negative')).toBeVisible();
|
|
});
|
|
|
|
test('should validate sales date constraints', async ({ page }) => {
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Fill required fields
|
|
await page.fill('input[name="name"]', 'Test Ticket');
|
|
await page.fill('input[type="number"][step="0.01"]', '25.00');
|
|
await page.fill('input[name="inventory"]', '100');
|
|
|
|
// Set sales end before sales start
|
|
await page.fill('input[name="salesStart"]', '2024-12-25T10:00');
|
|
await page.fill('input[name="salesEnd"]', '2024-12-20T10:00');
|
|
|
|
await page.click('button[type="submit"]');
|
|
await expect(page.getByText('Sales end date must be after sales start date')).toBeVisible();
|
|
});
|
|
|
|
test('should convert price from dollars to cents correctly', async ({ page }) => {
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Fill form with valid data
|
|
await page.fill('input[name="name"]', 'General Admission');
|
|
await page.fill('input[type="number"][step="0.01"]', '25.99');
|
|
await page.fill('input[name="inventory"]', '100');
|
|
|
|
// Submit form
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Check for success message
|
|
await expect(page.getByText(/created successfully/)).toBeVisible({ timeout: 10000 });
|
|
|
|
// Modal should close after success
|
|
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 3000 });
|
|
});
|
|
|
|
test('should create active ticket type successfully', async ({ page }) => {
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Fill form completely
|
|
await page.fill('input[name="name"]', 'VIP Access');
|
|
await page.fill('input[type="number"][step="0.01"]', '150.00');
|
|
await page.fill('input[name="inventory"]', '50');
|
|
await page.fill('input[name="salesStart"]', '2024-12-01T09:00');
|
|
await page.fill('input[name="salesEnd"]', '2024-12-31T23:59');
|
|
|
|
// Select active status (should be default)
|
|
await page.selectOption('[role="combobox"]', 'active');
|
|
|
|
// Submit form
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Wait for success
|
|
await expect(page.getByText(/VIP Access.*created successfully/)).toBeVisible({ timeout: 10000 });
|
|
|
|
// Check that new ticket type appears in the list
|
|
await expect(page.getByText('VIP Access')).toBeVisible();
|
|
await expect(page.getByText('$150.00')).toBeVisible();
|
|
await expect(page.getByText('0 / 50 sold')).toBeVisible();
|
|
});
|
|
|
|
test('should create paused ticket type successfully', async ({ page }) => {
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Fill form
|
|
await page.fill('input[name="name"]', 'Early Bird');
|
|
await page.fill('input[type="number"][step="0.01"]', '75.00');
|
|
await page.fill('input[name="inventory"]', '25');
|
|
|
|
// Select paused status
|
|
await page.click('[role="combobox"]');
|
|
await page.click('button[role="option"]:has-text("Paused")');
|
|
|
|
// Submit form
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Wait for success
|
|
await expect(page.getByText(/Early Bird.*created successfully/)).toBeVisible({ timeout: 10000 });
|
|
|
|
// Check that new ticket type appears with paused status
|
|
await expect(page.getByText('Early Bird')).toBeVisible();
|
|
await expect(page.getByText('paused')).toBeVisible();
|
|
});
|
|
|
|
test('should handle form reset when cancelled', async ({ page }) => {
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Fill form partially
|
|
await page.fill('input[name="name"]', 'Test Ticket');
|
|
await page.fill('input[type="number"][step="0.01"]', '50.00');
|
|
|
|
// Cancel modal
|
|
await page.click('button:has-text("Cancel")');
|
|
|
|
// Modal should close
|
|
await expect(page.getByRole('dialog')).not.toBeVisible();
|
|
|
|
// Reopen modal and check form is reset
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
await expect(page.locator('input[name="name"]')).toHaveValue('');
|
|
await expect(page.locator('input[type="number"][step="0.01"]')).toHaveValue('');
|
|
});
|
|
|
|
test('should close modal with escape key', async ({ page }) => {
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
// Press escape
|
|
await page.keyboard.press('Escape');
|
|
|
|
// Modal should close
|
|
await expect(page.getByRole('dialog')).not.toBeVisible();
|
|
});
|
|
|
|
test('should show permission error for unauthorized users', async ({ page }) => {
|
|
// Logout and login as regular user (assuming they don't have tickets:write permission)
|
|
await page.goto('/login');
|
|
await page.fill('[data-testid="email-input"]', 'user@test.com');
|
|
await page.fill('[data-testid="password-input"]', 'password');
|
|
await page.click('[data-testid="login-button"]');
|
|
|
|
// Navigate to event detail page
|
|
await page.goto('/events/evt-1');
|
|
|
|
// Add Ticket Type button should not be visible for regular users
|
|
await expect(page.getByTestId('add-ticket-type-button')).not.toBeVisible();
|
|
});
|
|
|
|
test('should handle loading states correctly', async ({ page }) => {
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Fill form
|
|
await page.fill('input[name="name"]', 'Loading Test');
|
|
await page.fill('input[type="number"][step="0.01"]', '30.00');
|
|
await page.fill('input[name="inventory"]', '75');
|
|
|
|
// Submit form and check loading state
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Submit button should show loading state
|
|
await expect(page.locator('button[type="submit"]')).toBeDisabled();
|
|
|
|
// Wait for completion
|
|
await expect(page.getByText(/created successfully/)).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('should create free ticket (price = 0) successfully', async ({ page }) => {
|
|
await page.click('[data-testid="add-ticket-type-button"]');
|
|
|
|
// Fill form with free ticket
|
|
await page.fill('input[name="name"]', 'Free Community Pass');
|
|
await page.fill('input[type="number"][step="0.01"]', '0.00');
|
|
await page.fill('input[name="inventory"]', '200');
|
|
|
|
// Submit form
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Should succeed without price validation error
|
|
await expect(page.getByText(/Free Community Pass.*created successfully/)).toBeVisible({ timeout: 10000 });
|
|
|
|
// Check that ticket appears with $0.00 price
|
|
await expect(page.getByText('Free Community Pass')).toBeVisible();
|
|
await expect(page.getByText('$0.00')).toBeVisible();
|
|
});
|
|
}); |