Files
blackcanyontickets/test-data-setup.cjs
dzinesco aa81eb5adb feat: add advanced analytics and territory management system
- 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>
2025-08-26 09:25:10 -06:00

487 lines
19 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @ts-check
const { test, expect } = require('@playwright/test');
/**
* Test Data Setup and Event Creation
*
* This script helps create test events and data for ticket purchasing tests.
* It can be run independently or as part of the test suite setup.
*/
class TestDataManager {
constructor(page) {
this.page = page;
}
async createTestEvent(eventData) {
// This would typically interact with your admin interface or API
// to create test events. For now, we'll mock the responses.
const eventSlug = eventData.slug || 'test-event-' + Date.now();
// Mock the event page response
await this.page.route(`**/e/${eventSlug}`, async route => {
const mockEventHTML = this.generateMockEventHTML(eventData, eventSlug);
await route.fulfill({
status: 200,
contentType: 'text/html',
body: mockEventHTML
});
});
return { ...eventData, slug: eventSlug };
}
generateMockEventHTML(eventData, slug) {
return `
<!DOCTYPE html>
<html>
<head>
<title>${eventData.title} - Black Canyon Tickets</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
:root {
--ui-text-primary: #1f2937;
--ui-text-secondary: #6b7280;
--ui-text-tertiary: #9ca3af;
--ui-bg-elevated: #ffffff;
--ui-bg-secondary: #f9fafb;
--ui-border-primary: #d1d5db;
--ui-border-secondary: #e5e7eb;
--glass-border-focus: #3b82f6;
--success-color: #10b981;
--success-border: #10b981;
--success-bg: #ecfdf5;
--error-color: #ef4444;
--warning-color: #f59e0b;
--warning-bg: #fffbeb;
--warning-border: #f59e0b;
}
body { font-family: system-ui, -apple-system, sans-serif; margin: 0; padding: 0; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.event-header { background: linear-gradient(to right, #1f2937, #374151); color: white; padding: 24px; border-radius: 16px; margin-bottom: 24px; }
.ticket-section { background: white; border: 2px solid var(--ui-border-primary); border-radius: 16px; padding: 24px; }
.ticket-type { border: 2px solid var(--ui-border-primary); border-radius: 16px; padding: 16px; margin-bottom: 16px; }
.quantity-controls { display: flex; align-items: center; gap: 12px; }
.quantity-btn { width: 48px; height: 48px; border: 2px solid var(--ui-border-primary); border-radius: 12px; background: var(--ui-bg-elevated); font-size: 18px; font-weight: bold; cursor: pointer; }
.quantity-btn:hover { border-color: var(--glass-border-focus); }
.quantity-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.order-summary { background: var(--ui-bg-secondary); border: 2px solid var(--ui-border-primary); border-radius: 16px; padding: 24px; margin-top: 24px; display: none; }
.order-summary.visible { display: block; }
.form-group { margin-bottom: 16px; }
.form-input { width: 100%; padding: 12px; border: 2px solid var(--ui-border-primary); border-radius: 12px; font-size: 16px; }
.form-input:focus { border-color: var(--glass-border-focus); outline: none; }
.purchase-btn { width: 100%; padding: 16px; background: var(--success-color); color: white; border: none; border-radius: 16px; font-size: 18px; font-weight: bold; cursor: pointer; }
.purchase-btn:hover { background: var(--success-border); }
.timer { background: var(--warning-bg); border: 2px solid var(--warning-border); border-radius: 12px; padding: 12px; margin: 16px 0; display: none; }
.timer.visible { display: block; }
.presale-section { background: var(--ui-bg-secondary); border: 2px solid var(--ui-border-primary); border-radius: 16px; padding: 20px; margin-bottom: 20px; }
${eventData.presaleRequired ? '' : '.presale-section { display: none; }'}
</style>
</head>
<body>
<div class="container">
<!-- Event Header -->
<div class="event-header">
<h1 data-test="event-title">${eventData.title}</h1>
<div data-test="event-date">📅 ${eventData.date}</div>
<div data-test="venue">📍 ${eventData.venue}</div>
${eventData.description ? `<p data-test="event-description">${eventData.description}</p>` : ''}
</div>
<!-- Presale Code Section -->
${eventData.presaleRequired ? `
<div class="presale-section" data-test="presale-code-section">
<label for="presale-code">Presale Code Required:</label>
<div style="display: flex; gap: 12px; margin-top: 8px;">
<input
id="presale-code"
type="text"
placeholder="Enter your presale code"
class="form-input"
style="flex: 1;"
/>
<button onclick="validatePresaleCode()" style="padding: 12px 24px; background: var(--success-color); color: white; border: none; border-radius: 12px; cursor: pointer;">
Apply Code
</button>
</div>
<div id="presale-error" data-test="presale-error" style="color: var(--error-color); margin-top: 8px; display: none;"></div>
<div id="presale-success" data-test="presale-success" style="color: var(--success-color); margin-top: 8px; display: none;">✓ Presale access granted</div>
</div>
` : ''}
<!-- Ticket Selection -->
<div class="ticket-section" data-test="ticket-checkout">
<h2>Get Your Tickets</h2>
${eventData.ticketTypes.map((ticket, index) => `
<div class="ticket-type" data-test="ticket-type" data-ticket-index="${index}">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<h3>${ticket.name}</h3>
${ticket.description ? `<p style="color: var(--ui-text-secondary); margin: 8px 0;">${ticket.description}</p>` : ''}
<div style="display: flex; align-items: center; gap: 16px;">
<span style="font-size: 24px; font-weight: bold;">$${ticket.price.toFixed(2)}</span>
<span style="color: var(--ui-text-secondary);" data-test="availability-display">
${ticket.available} available
</span>
</div>
</div>
<div class="quantity-controls">
<button
class="quantity-btn decrease-btn"
onclick="changeQuantity(${index}, -1)"
disabled
aria-label="Decrease quantity"
></button>
<span style="font-size: 18px; font-weight: bold; min-width: 24px; text-align: center;" data-test="quantity-display">0</span>
<button
class="quantity-btn increase-btn"
onclick="changeQuantity(${index}, 1)"
aria-label="Increase quantity"
>+</button>
</div>
</div>
</div>
`).join('')}
<!-- Reservation Timer -->
<div class="timer" data-test="reservation-timer">
<span>⏰ Tickets reserved for <span data-test="time-remaining">15:00</span></span>
</div>
<!-- Order Summary -->
<div class="order-summary" data-test="order-summary">
<h3>Order Summary</h3>
<div id="order-items"></div>
<div style="border-top: 1px solid var(--ui-border-secondary); margin: 16px 0; padding-top: 16px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span>Subtotal:</span>
<span data-test="subtotal">$0.00</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span>Platform fee:</span>
<span data-test="platform-fee">$0.00</span>
</div>
<div style="display: flex; justify-content: space-between; font-size: 20px; font-weight: bold;">
<span>Total:</span>
<span data-test="total">$0.00</span>
</div>
</div>
<!-- Customer Information Form -->
<form id="purchase-form" onsubmit="handlePurchase(event)">
<div class="form-group">
<label for="email">Email Address:</label>
<input type="email" id="email" class="form-input" required />
</div>
<div class="form-group">
<label for="name">Full Name:</label>
<input type="text" id="name" class="form-input" required />
</div>
<button type="submit" class="purchase-btn">Complete Purchase</button>
</form>
</div>
</div>
</div>
<script>
let quantities = ${JSON.stringify(eventData.ticketTypes.map(() => 0))};
let reservationTimer = null;
let timeRemaining = 15 * 60; // 15 minutes in seconds
let presaleValidated = ${!eventData.presaleRequired};
const ticketTypes = ${JSON.stringify(eventData.ticketTypes)};
function changeQuantity(index, delta) {
if (!presaleValidated && ${eventData.presaleRequired}) {
alert('Please enter a valid presale code first.');
return;
}
const newQuantity = Math.max(0, quantities[index] + delta);
const available = ticketTypes[index].available;
if (newQuantity > available) {
alert('Not enough tickets available.');
return;
}
quantities[index] = newQuantity;
updateQuantityDisplay(index);
updateOrderSummary();
if (getTotalQuantity() > 0 && !reservationTimer) {
startReservationTimer();
} else if (getTotalQuantity() === 0 && reservationTimer) {
stopReservationTimer();
}
}
function updateQuantityDisplay(index) {
const ticketElement = document.querySelector(\`[data-ticket-index="\${index}"]\`);
const quantityDisplay = ticketElement.querySelector('[data-test="quantity-display"]');
const decreaseBtn = ticketElement.querySelector('.decrease-btn');
const increaseBtn = ticketElement.querySelector('.increase-btn');
quantityDisplay.textContent = quantities[index];
decreaseBtn.disabled = quantities[index] <= 0;
increaseBtn.disabled = quantities[index] >= ticketTypes[index].available;
}
function updateOrderSummary() {
const orderSummary = document.querySelector('[data-test="order-summary"]');
const totalQuantity = getTotalQuantity();
if (totalQuantity > 0) {
orderSummary.classList.add('visible');
let subtotal = 0;
let orderItemsHTML = '';
quantities.forEach((qty, index) => {
if (qty > 0) {
const ticket = ticketTypes[index];
const itemTotal = qty * ticket.price;
subtotal += itemTotal;
orderItemsHTML += \`
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span>\${qty}x \${ticket.name}</span>
<span>$\${itemTotal.toFixed(2)}</span>
</div>
\`;
}
});
document.getElementById('order-items').innerHTML = orderItemsHTML;
const platformFee = Math.round(subtotal * 0.05 * 100) / 100; // 5% platform fee
const total = subtotal + platformFee;
document.querySelector('[data-test="subtotal"]').textContent = \`$\${subtotal.toFixed(2)}\`;
document.querySelector('[data-test="platform-fee"]').textContent = \`$\${platformFee.toFixed(2)}\`;
document.querySelector('[data-test="total"]').textContent = \`$\${total.toFixed(2)}\`;
} else {
orderSummary.classList.remove('visible');
}
}
function getTotalQuantity() {
return quantities.reduce((sum, qty) => sum + qty, 0);
}
function startReservationTimer() {
const timerElement = document.querySelector('[data-test="reservation-timer"]');
const timeDisplay = document.querySelector('[data-test="time-remaining"]');
timerElement.classList.add('visible');
reservationTimer = setInterval(() => {
timeRemaining--;
if (timeRemaining <= 0) {
alert('Your ticket reservation has expired. Please select your tickets again.');
location.reload();
return;
}
const minutes = Math.floor(timeRemaining / 60);
const seconds = timeRemaining % 60;
timeDisplay.textContent = \`\${minutes}:\${seconds.toString().padStart(2, '0')}\`;
}, 1000);
}
function stopReservationTimer() {
if (reservationTimer) {
clearInterval(reservationTimer);
reservationTimer = null;
document.querySelector('[data-test="reservation-timer"]').classList.remove('visible');
timeRemaining = 15 * 60;
}
}
function validatePresaleCode() {
const code = document.getElementById('presale-code').value.trim().toUpperCase();
const errorDiv = document.getElementById('presale-error');
const successDiv = document.getElementById('presale-success');
if (!code) {
errorDiv.textContent = 'Please enter a presale code';
errorDiv.style.display = 'block';
successDiv.style.display = 'none';
return;
}
// Mock validation - accept any code that contains "VALID"
if (code.includes('VALID')) {
presaleValidated = true;
errorDiv.style.display = 'none';
successDiv.style.display = 'block';
} else {
errorDiv.textContent = 'Invalid presale code';
errorDiv.style.display = 'block';
successDiv.style.display = 'none';
}
}
function handlePurchase(event) {
event.preventDefault();
const email = document.getElementById('email').value;
const name = document.getElementById('name').value;
if (!email || !name) {
alert('Please fill in all required fields.');
return;
}
// Mock purchase completion
alert(\`Purchase completed!\\n\\nCustomer: \${name}\\nEmail: \${email}\\nTickets: \${getTotalQuantity()}\`);
// Reset form
document.getElementById('purchase-form').reset();
quantities = quantities.map(() => 0);
quantities.forEach((_, index) => updateQuantityDisplay(index));
updateOrderSummary();
stopReservationTimer();
}
// Initialize displays
quantities.forEach((_, index) => updateQuantityDisplay(index));
</script>
</body>
</html>
`;
}
// Predefined test events
static getTestEvents() {
return {
basicEvent: {
title: 'Sample Music Concert',
date: 'Friday, December 15, 2024 at 8:00 PM',
venue: 'Grand Theater',
description: 'An amazing evening of live music featuring local artists.',
presaleRequired: false,
ticketTypes: [
{ name: 'General Admission', price: 25.00, available: 50, description: 'Standing room on the main floor' },
{ name: 'VIP Seating', price: 75.00, available: 20, description: 'Reserved seating with complimentary drink' },
{ name: 'Student Discount', price: 15.00, available: 30, description: 'Valid student ID required at entry' }
]
},
presaleEvent: {
title: 'Exclusive Art Gallery Opening',
date: 'Saturday, December 16, 2024 at 6:00 PM',
venue: 'Modern Art Museum',
description: 'Private preview of the new contemporary art exhibition.',
presaleRequired: true,
ticketTypes: [
{ name: 'Early Bird Special', price: 35.00, available: 25, description: 'First access to the exhibition' },
{ name: 'Collector Pass', price: 95.00, available: 10, description: 'Includes exclusive artist meet & greet' }
]
},
soldOutEvent: {
title: 'Popular Dance Performance',
date: 'Sunday, December 17, 2024 at 7:30 PM',
venue: 'City Performance Hall',
description: 'Award-winning dance troupe presents their latest show.',
presaleRequired: false,
ticketTypes: [
{ name: 'Orchestra Seating', price: 65.00, available: 0, description: 'Best view of the performance' },
{ name: 'Balcony Seating', price: 45.00, available: 0, description: 'Elevated view with great acoustics' }
]
},
lowStockEvent: {
title: 'Intimate Jazz Session',
date: 'Monday, December 18, 2024 at 9:00 PM',
venue: 'Blue Note Lounge',
description: 'Close-up performance with renowned jazz musicians.',
presaleRequired: false,
ticketTypes: [
{ name: 'Table for Two', price: 120.00, available: 2, description: 'Prime table with bottle service' },
{ name: 'Bar Seating', price: 45.00, available: 3, description: 'Seats at the jazz bar' },
{ name: 'Standing Room', price: 25.00, available: 8, description: 'Standing area near the stage' }
]
}
};
}
}
// Export for use in other test files
if (typeof module !== 'undefined' && module.exports) {
module.exports = { TestDataManager };
}
// Standalone test for data setup validation
test.describe('Test Data Setup', () => {
test('should create basic test event successfully', async ({ page }) => {
const dataManager = new TestDataManager(page);
const testEvents = TestDataManager.getTestEvents();
const event = await dataManager.createTestEvent(testEvents.basicEvent);
await page.goto(`/e/${event.slug}`);
// Verify event was created correctly
await expect(page.locator('[data-test="event-title"]')).toContainText(event.title);
await expect(page.locator('[data-test="venue"]')).toContainText(event.venue);
// Verify ticket types are present
const ticketTypes = page.locator('[data-test="ticket-type"]');
await expect(ticketTypes).toHaveCount(event.ticketTypes.length);
console.log(`✓ Test event created: ${event.title} (${event.slug})`);
});
test('should create presale event with code validation', async ({ page }) => {
const dataManager = new TestDataManager(page);
const testEvents = TestDataManager.getTestEvents();
const event = await dataManager.createTestEvent(testEvents.presaleEvent);
await page.goto(`/e/${event.slug}`);
// Verify presale section is visible
await expect(page.locator('[data-test="presale-code-section"]')).toBeVisible();
// Test invalid code
await page.fill('#presale-code', 'INVALID123');
await page.click('button:has-text("Apply Code")');
await expect(page.locator('[data-test="presale-error"]')).toBeVisible();
// Test valid code
await page.fill('#presale-code', 'VALID123');
await page.click('button:has-text("Apply Code")');
await expect(page.locator('[data-test="presale-success"]')).toBeVisible();
console.log(`✓ Presale event created with validation: ${event.title}`);
});
test('should create sold out event with disabled controls', async ({ page }) => {
const dataManager = new TestDataManager(page);
const testEvents = TestDataManager.getTestEvents();
const event = await dataManager.createTestEvent(testEvents.soldOutEvent);
await page.goto(`/e/${event.slug}`);
// Verify all increase buttons are disabled
const increaseButtons = page.locator('.increase-btn');
const buttonCount = await increaseButtons.count();
for (let i = 0; i < buttonCount; i++) {
await expect(increaseButtons.nth(i)).toBeDisabled();
}
// Verify availability display shows 0
await expect(page.locator('[data-test="availability-display"]').first()).toContainText('0 available');
console.log(`✓ Sold out event created: ${event.title}`);
});
});