- 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>
427 lines
16 KiB
TypeScript
427 lines
16 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
test.describe('PublishEventModal Component', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Navigate to events page and set up authenticated state
|
|
await page.goto('/events');
|
|
|
|
// Mock authenticated user state
|
|
await page.evaluate(() => {
|
|
const mockUser = {
|
|
id: 'user-1',
|
|
email: 'organizer@example.com',
|
|
name: 'Event Organizer',
|
|
role: 'organizer',
|
|
organization: {
|
|
id: 'org-1',
|
|
name: 'Test Organization',
|
|
planType: 'pro',
|
|
payment: {
|
|
connected: true,
|
|
accountId: 'acct_test123'
|
|
},
|
|
settings: {
|
|
theme: 'dark',
|
|
currency: 'USD',
|
|
timezone: 'America/New_York'
|
|
}
|
|
},
|
|
preferences: {
|
|
theme: 'system',
|
|
notifications: { email: true, push: true, marketing: false },
|
|
dashboard: { defaultView: 'grid', itemsPerPage: 25 }
|
|
},
|
|
metadata: {
|
|
lastLogin: new Date().toISOString(),
|
|
createdAt: '2024-01-01T00:00:00Z',
|
|
emailVerified: true
|
|
}
|
|
};
|
|
|
|
localStorage.setItem('bct_auth_user', JSON.stringify(mockUser));
|
|
localStorage.setItem('bct_auth_remember', 'true');
|
|
});
|
|
|
|
// Reload to apply auth state
|
|
await page.reload();
|
|
await page.waitForLoadState('networkidle');
|
|
});
|
|
|
|
test('should display publish button for draft events', async ({ page }) => {
|
|
// Navigate to a draft event (assuming first event in mock data)
|
|
await page.click('[data-testid="event-card"]:first-child');
|
|
await page.waitForSelector('[data-testid="event-detail-page"]', { timeout: 5000 });
|
|
|
|
// Check for publish button if event is draft
|
|
const eventStatus = await page.textContent('[data-testid="event-status-badge"]');
|
|
if (eventStatus?.toLowerCase().includes('draft')) {
|
|
await expect(page.locator('button:has-text("Publish Event")')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should not display publish button for published events', async ({ page }) => {
|
|
// Set up a published event in mock data
|
|
await page.evaluate(() => {
|
|
const eventStore = JSON.parse(localStorage.getItem('event-store') || '{}');
|
|
if (eventStore.state?.events) {
|
|
eventStore.state.events[0] = {
|
|
...eventStore.state.events[0],
|
|
status: 'published',
|
|
publishedAt: new Date().toISOString()
|
|
};
|
|
localStorage.setItem('event-store', JSON.stringify(eventStore));
|
|
}
|
|
});
|
|
|
|
await page.reload();
|
|
await page.click('[data-testid="event-card"]:first-child');
|
|
await page.waitForSelector('[data-testid="event-detail-page"]', { timeout: 5000 });
|
|
|
|
// Publish button should not be visible for published events
|
|
await expect(page.locator('button:has-text("Publish Event")')).not.toBeVisible();
|
|
|
|
// But should show published status
|
|
await expect(page.locator('[data-testid="event-status-badge"]:has-text("published")')).toBeVisible();
|
|
});
|
|
|
|
test('should open publish modal when publish button is clicked', async ({ page }) => {
|
|
// Navigate to a draft event
|
|
await page.click('[data-testid="event-card"]:first-child');
|
|
await page.waitForSelector('[data-testid="event-detail-page"]', { timeout: 5000 });
|
|
|
|
// Click publish button if it's visible
|
|
const publishButton = page.locator('button:has-text("Publish Event")');
|
|
if (await publishButton.isVisible()) {
|
|
await publishButton.click();
|
|
|
|
// Check that modal opened
|
|
await expect(page.locator('[role="dialog"]')).toBeVisible();
|
|
await expect(page.locator('[role="dialog"] h2:has-text("Publish Event")')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should show validation checklist in publish modal', async ({ page }) => {
|
|
// Navigate to event and open publish modal
|
|
await page.click('[data-testid="event-card"]:first-child');
|
|
await page.waitForSelector('[data-testid="event-detail-page"]', { timeout: 5000 });
|
|
|
|
const publishButton = page.locator('button:has-text("Publish Event")');
|
|
if (await publishButton.isVisible()) {
|
|
await publishButton.click();
|
|
|
|
// Wait for modal to load requirements
|
|
await page.waitForSelector('[role="dialog"]', { timeout: 3000 });
|
|
|
|
// Check for validation items
|
|
await expect(page.locator('text=Active Ticket Types')).toBeVisible();
|
|
await expect(page.locator('text=Valid Event Dates')).toBeVisible();
|
|
await expect(page.locator('text=Payment Processing')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should disable publish button when requirements not met', async ({ page }) => {
|
|
// Set up event with no active ticket types
|
|
await page.evaluate(() => {
|
|
const eventStore = JSON.parse(localStorage.getItem('event-store') || '{}');
|
|
if (eventStore.state?.ticketTypes) {
|
|
// Set all ticket types to inactive
|
|
eventStore.state.ticketTypes.forEach((tt: any) => {
|
|
tt.status = 'paused';
|
|
});
|
|
localStorage.setItem('event-store', JSON.stringify(eventStore));
|
|
}
|
|
});
|
|
|
|
await page.reload();
|
|
await page.click('[data-testid="event-card"]:first-child');
|
|
await page.waitForSelector('[data-testid="event-detail-page"]', { timeout: 5000 });
|
|
|
|
const publishButton = page.locator('button:has-text("Publish Event")');
|
|
if (await publishButton.isVisible()) {
|
|
await publishButton.click();
|
|
|
|
// Wait for requirements check
|
|
await page.waitForSelector('[role="dialog"]', { timeout: 3000 });
|
|
await page.waitForTimeout(1000); // Wait for requirements loading
|
|
|
|
// Publish button in modal should be disabled
|
|
const modalPublishButton = page.locator('[role="dialog"] button:has-text("Publish Event")');
|
|
await expect(modalPublishButton).toBeDisabled();
|
|
}
|
|
});
|
|
|
|
test('should show fix action buttons for failed requirements', async ({ page }) => {
|
|
// Set up event with no active ticket types and disconnect payments
|
|
await page.evaluate(() => {
|
|
const eventStore = JSON.parse(localStorage.getItem('event-store') || '{}');
|
|
if (eventStore.state?.ticketTypes) {
|
|
// Set all ticket types to inactive
|
|
eventStore.state.ticketTypes.forEach((tt: any) => {
|
|
tt.status = 'paused';
|
|
});
|
|
localStorage.setItem('event-store', JSON.stringify(eventStore));
|
|
}
|
|
|
|
// Disconnect payments
|
|
const mockUser = JSON.parse(localStorage.getItem('bct_auth_user') || '{}');
|
|
mockUser.organization.payment.connected = false;
|
|
localStorage.setItem('bct_auth_user', JSON.stringify(mockUser));
|
|
});
|
|
|
|
await page.reload();
|
|
await page.click('[data-testid="event-card"]:first-child');
|
|
await page.waitForSelector('[data-testid="event-detail-page"]', { timeout: 5000 });
|
|
|
|
const publishButton = page.locator('button:has-text("Publish Event")');
|
|
if (await publishButton.isVisible()) {
|
|
await publishButton.click();
|
|
|
|
// Wait for requirements check
|
|
await page.waitForSelector('[role="dialog"]', { timeout: 3000 });
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Should show fix action buttons
|
|
await expect(page.locator('button:has-text("Add Ticket Type")')).toBeVisible();
|
|
await expect(page.locator('button:has-text("Connect Payments")')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should enable publish button when all requirements are met', async ({ page }) => {
|
|
// Ensure we have active ticket types and connected payments
|
|
await page.evaluate(() => {
|
|
const eventStore = JSON.parse(localStorage.getItem('event-store') || '{}');
|
|
if (eventStore.state?.ticketTypes) {
|
|
// Set at least one ticket type to active
|
|
eventStore.state.ticketTypes[0].status = 'active';
|
|
localStorage.setItem('event-store', JSON.stringify(eventStore));
|
|
}
|
|
|
|
// Ensure payments are connected
|
|
const mockUser = JSON.parse(localStorage.getItem('bct_auth_user') || '{}');
|
|
mockUser.organization.payment.connected = true;
|
|
localStorage.setItem('bct_auth_user', JSON.stringify(mockUser));
|
|
});
|
|
|
|
await page.reload();
|
|
await page.click('[data-testid="event-card"]:first-child');
|
|
await page.waitForSelector('[data-testid="event-detail-page"]', { timeout: 5000 });
|
|
|
|
const publishButton = page.locator('button:has-text("Publish Event")');
|
|
if (await publishButton.isVisible()) {
|
|
await publishButton.click();
|
|
|
|
// Wait for requirements check
|
|
await page.waitForSelector('[role="dialog"]', { timeout: 3000 });
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Publish button in modal should be enabled
|
|
const modalPublishButton = page.locator('[role="dialog"] button:has-text("Publish Event")');
|
|
await expect(modalPublishButton).toBeEnabled();
|
|
}
|
|
});
|
|
|
|
test('should close modal when cancel is clicked', async ({ page }) => {
|
|
await page.click('[data-testid="event-card"]:first-child');
|
|
await page.waitForSelector('[data-testid="event-detail-page"]', { timeout: 5000 });
|
|
|
|
const publishButton = page.locator('button:has-text("Publish Event")');
|
|
if (await publishButton.isVisible()) {
|
|
await publishButton.click();
|
|
await page.waitForSelector('[role="dialog"]', { timeout: 3000 });
|
|
|
|
// Click cancel button
|
|
await page.click('[role="dialog"] button:has-text("Cancel")');
|
|
|
|
// Modal should be closed
|
|
await expect(page.locator('[role="dialog"]')).not.toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should close modal with escape key', async ({ page }) => {
|
|
await page.click('[data-testid="event-card"]:first-child');
|
|
await page.waitForSelector('[data-testid="event-detail-page"]', { timeout: 5000 });
|
|
|
|
const publishButton = page.locator('button:has-text("Publish Event")');
|
|
if (await publishButton.isVisible()) {
|
|
await publishButton.click();
|
|
await page.waitForSelector('[role="dialog"]', { timeout: 3000 });
|
|
|
|
// Press escape key
|
|
await page.keyboard.press('Escape');
|
|
|
|
// Modal should be closed
|
|
await expect(page.locator('[role="dialog"]')).not.toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should show success message after successful publish', async ({ page }) => {
|
|
// Set up ideal conditions for publishing
|
|
await page.evaluate(() => {
|
|
const eventStore = JSON.parse(localStorage.getItem('event-store') || '{}');
|
|
if (eventStore.state?.ticketTypes) {
|
|
eventStore.state.ticketTypes[0].status = 'active';
|
|
localStorage.setItem('event-store', JSON.stringify(eventStore));
|
|
}
|
|
|
|
const mockUser = JSON.parse(localStorage.getItem('bct_auth_user') || '{}');
|
|
mockUser.organization.payment.connected = true;
|
|
localStorage.setItem('bct_auth_user', JSON.stringify(mockUser));
|
|
});
|
|
|
|
await page.reload();
|
|
await page.click('[data-testid="event-card"]:first-child');
|
|
await page.waitForSelector('[data-testid="event-detail-page"]', { timeout: 5000 });
|
|
|
|
const publishButton = page.locator('button:has-text("Publish Event")');
|
|
if (await publishButton.isVisible()) {
|
|
await publishButton.click();
|
|
await page.waitForSelector('[role="dialog"]', { timeout: 3000 });
|
|
await page.waitForTimeout(1000);
|
|
|
|
const modalPublishButton = page.locator('[role="dialog"] button:has-text("Publish Event")');
|
|
if (await modalPublishButton.isEnabled()) {
|
|
await modalPublishButton.click();
|
|
|
|
// Should show success message
|
|
await expect(page.locator('text=Event Published Successfully!')).toBeVisible();
|
|
await expect(page.locator('text=Your event is now live!')).toBeVisible();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Publish Event Validation Logic', () => {
|
|
test('should validate ticket types requirement', async ({ page }) => {
|
|
await page.goto('/events');
|
|
|
|
// Mock function to test validation logic
|
|
const validationResult = await page.evaluate(() => {
|
|
// Mock ticket types data
|
|
const ticketTypes = [
|
|
{ id: '1', eventId: 'evt-1', status: 'active' },
|
|
{ id: '2', eventId: 'evt-1', status: 'paused' },
|
|
];
|
|
|
|
// Validation logic: has active ticket types
|
|
const hasActiveTicketTypes = ticketTypes.some(tt => tt.status === 'active');
|
|
return hasActiveTicketTypes;
|
|
});
|
|
|
|
expect(validationResult).toBe(true);
|
|
});
|
|
|
|
test('should validate no active ticket types', async ({ page }) => {
|
|
await page.goto('/events');
|
|
|
|
const validationResult = await page.evaluate(() => {
|
|
// Mock ticket types with no active ones
|
|
const ticketTypes = [
|
|
{ id: '1', eventId: 'evt-1', status: 'paused' },
|
|
{ id: '2', eventId: 'evt-1', status: 'sold_out' },
|
|
];
|
|
|
|
const hasActiveTicketTypes = ticketTypes.some(tt => tt.status === 'active');
|
|
return hasActiveTicketTypes;
|
|
});
|
|
|
|
expect(validationResult).toBe(false);
|
|
});
|
|
|
|
test('should validate event dates requirement', async ({ page }) => {
|
|
await page.goto('/events');
|
|
|
|
const validationResult = await page.evaluate(() => {
|
|
// Mock event with valid date
|
|
const event = {
|
|
id: 'evt-1',
|
|
date: '2024-12-31T19:00:00Z',
|
|
title: 'Test Event'
|
|
};
|
|
|
|
// Validation logic: has valid date
|
|
const hasValidDates = Boolean(event.date);
|
|
return hasValidDates;
|
|
});
|
|
|
|
expect(validationResult).toBe(true);
|
|
});
|
|
|
|
test('should validate payment connection requirement', async ({ page }) => {
|
|
await page.goto('/events');
|
|
|
|
const validationResult = await page.evaluate(() => {
|
|
// Mock organization with connected payment
|
|
const organization = {
|
|
id: 'org-1',
|
|
payment: {
|
|
connected: true,
|
|
accountId: 'acct_test123'
|
|
}
|
|
};
|
|
|
|
// Validation logic: payment connected
|
|
const isPaymentConnected = organization.payment?.connected ?? false;
|
|
return isPaymentConnected;
|
|
});
|
|
|
|
expect(validationResult).toBe(true);
|
|
});
|
|
|
|
test('should validate all requirements together', async ({ page }) => {
|
|
await page.goto('/events');
|
|
|
|
const validationResult = await page.evaluate(() => {
|
|
// Mock complete scenario
|
|
const ticketTypes = [{ id: '1', eventId: 'evt-1', status: 'active' }];
|
|
const event = { id: 'evt-1', date: '2024-12-31T19:00:00Z' };
|
|
const organization = { payment: { connected: true } };
|
|
|
|
const hasActiveTicketTypes = ticketTypes.some(tt => tt.status === 'active');
|
|
const hasValidDates = Boolean(event.date);
|
|
const isPaymentConnected = organization.payment?.connected ?? false;
|
|
|
|
const canPublish = hasActiveTicketTypes && hasValidDates && isPaymentConnected;
|
|
|
|
return {
|
|
hasActiveTicketTypes,
|
|
hasValidDates,
|
|
isPaymentConnected,
|
|
canPublish
|
|
};
|
|
});
|
|
|
|
expect(validationResult.hasActiveTicketTypes).toBe(true);
|
|
expect(validationResult.hasValidDates).toBe(true);
|
|
expect(validationResult.isPaymentConnected).toBe(true);
|
|
expect(validationResult.canPublish).toBe(true);
|
|
});
|
|
|
|
test('should fail validation when any requirement is missing', async ({ page }) => {
|
|
await page.goto('/events');
|
|
|
|
const validationResult = await page.evaluate(() => {
|
|
// Mock scenario with missing payment connection
|
|
const ticketTypes = [{ id: '1', eventId: 'evt-1', status: 'active' }];
|
|
const event = { id: 'evt-1', date: '2024-12-31T19:00:00Z' };
|
|
const organization = { payment: { connected: false } };
|
|
|
|
const hasActiveTicketTypes = ticketTypes.some(tt => tt.status === 'active');
|
|
const hasValidDates = Boolean(event.date);
|
|
const isPaymentConnected = organization.payment?.connected ?? false;
|
|
|
|
const canPublish = hasActiveTicketTypes && hasValidDates && isPaymentConnected;
|
|
|
|
return {
|
|
hasActiveTicketTypes,
|
|
hasValidDates,
|
|
isPaymentConnected,
|
|
canPublish
|
|
};
|
|
});
|
|
|
|
expect(validationResult.hasActiveTicketTypes).toBe(true);
|
|
expect(validationResult.hasValidDates).toBe(true);
|
|
expect(validationResult.isPaymentConnected).toBe(false);
|
|
expect(validationResult.canPublish).toBe(false);
|
|
});
|
|
}); |