admin/e2e/admin/event-management.spec.ts
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

226 lines
9.1 KiB
TypeScript

import { expect, test, type Page } from '@playwright/test'
import { createHostEvent, hostInsights } from '@/e2e/fixtures/hostEvent'
import { authenticateAs } from '@/e2e/fixtures/session'
async function confirmEventScheduleField(page: Page, fieldLabel: string) {
await page.getByRole('button', { name: fieldLabel }).click()
await page.getByRole('dialog', { name: fieldLabel }).getByRole('button', { name: 'تأیید' }).click()
}
const event = createHostEvent({
title: 'رویداد تست مدیریت',
status: 'draft',
settings: {
isDiscoverable: false,
autoCreateGroup: true,
addressVisibility: 'public',
generalArea: null,
waitlistAutoOffer: true,
sendReviewRequestSms: false,
},
organizerId: 'admin-id',
capacity: 20,
bookedCount: 2,
price: 0,
isFree: true,
})
test.beforeEach(async ({ context, page }) => {
await authenticateAs(context, page, 'admin')
await page.route('**/api/v1/**', async (route) => {
const url = new URL(route.request().url())
if (url.pathname.endsWith('/users/me')) {
await route.fulfill({ json: { success: true, data: { id: 'admin-id', role: 'admin', status: 'active' } } })
return
}
if (url.pathname.endsWith('/api/v1/admin/events/event-1')) {
if (route.request().method() === 'GET') {
await route.fulfill({ json: { success: true, data: { ...event, bookmarkCount: 0 } } })
return
}
if (route.request().method() === 'PATCH') {
const payload = route.request().postDataJSON() as { settings?: Record<string, unknown> }
await route.fulfill({
json: { success: true, data: { ...event, settings: { ...event.settings, ...payload.settings } } },
})
return
}
}
if (url.pathname.endsWith('/admin/events/event-1/insights')) {
await route.fulfill({ json: { success: true, data: hostInsights } })
return
}
if (url.pathname.endsWith('/admin/events/event-1/publish') && route.request().method() === 'PATCH') {
await route.fulfill({ json: { success: true, data: { ...event, status: 'published' } } })
return
}
if (url.pathname.endsWith('/event-categories') || url.pathname.includes('/event-categories?')) {
await route.fulfill({ json: { success: true, data: [] } })
return
}
if (url.pathname.endsWith('/geography/cities') || url.pathname.includes('/geography/cities?')) {
await route.fulfill({ json: { success: true, data: [] } })
return
}
if (url.pathname.endsWith('/geography/provinces') || url.pathname.includes('/geography/provinces?')) {
await route.fulfill({ json: { success: true, data: [] } })
return
}
if (url.pathname.endsWith('/events/event-1/media') || url.pathname.endsWith('/events/event-1/faqs')) {
await route.fulfill({ json: { success: true, data: [] } })
return
}
await route.fulfill({ json: { success: true, data: { items: [], meta: { total: 0 } } } })
})
})
test('admin lists events with their main management fields and can open create flow', async ({ page }) => {
const adminEvent = {
id: 'admin-event-1',
title: 'کارگاه مدیریت محصول',
status: 'published',
isDiscoverable: true,
isFree: false,
price: 450_000,
startsAt: '2030-02-01T10:00:00.000Z',
category: { id: 10, name: 'آموزشی' },
province: { id: 1, name: 'تهران' },
city: { id: 2, name: 'تهران' },
organizer: { id: 'organizer-1', firstName: 'سارا', lastName: 'احمدی' },
}
let listRequestUrl: URL | undefined
await page.route('**/api/v1/admin/events**', async (route) => {
listRequestUrl = new URL(route.request().url())
await route.fulfill({
json: {
success: true,
data: {
events: [adminEvent],
response: { page: 1, pageSize: 20, totalItemsCount: 1 },
},
},
})
})
await page.route('**/api/v1/admin/event-categories/flat', (route) =>
route.fulfill({ json: { success: true, data: [{ id: 10, name: 'آموزشی' }] } })
)
await page.route('**/api/v1/geography/cities**', (route) =>
route.fulfill({ json: { success: true, data: [{ id: 2, provinceId: 1, name: 'تهران', slug: 'tehran', lat: 35.7, lng: 51.4 }] } })
)
await page.goto('/manage-events')
await expect(page.getByText('رویدادها', { exact: true }).last()).toBeVisible()
await expect(page.getByRole('grid', { name: 'فهرست صفحه‌بندی‌شده' })).toBeVisible()
const eventRow = page.getByRole('row').filter({ hasText: adminEvent.title })
await expect(eventRow.getByText(adminEvent.title, { exact: true })).toBeVisible()
await expect(eventRow.getByRole('link', { name: 'مشاهده رویداد' })).toHaveAttribute('href', '/manage-events/admin-event-1')
await expect(eventRow.getByText('منتشر‌شده', { exact: true })).toBeVisible()
await expect(eventRow.getByText('آموزشی', { exact: true })).toBeVisible()
await expect(eventRow.getByText('سارا احمدی', { exact: true })).toBeVisible()
await expect(eventRow.getByText('۴۵۰٬۰۰۰ تومان')).toBeVisible()
expect(listRequestUrl?.pathname).toBe('/api/v1/admin/events')
await expect(page.getByRole('link', { name: 'افزودن رویداد' })).toHaveAttribute('href', '/manage-events/new')
})
test('edits discoverability and publishes a draft event', async ({ page }) => {
await page.goto('/manage-events/event-1')
await expect(page.getByText(event.title, { exact: true })).toBeVisible()
await expect(page.getByText('پیش‌نویس', { exact: true })).toBeVisible()
const [updateRequest] = await Promise.all([
page.waitForRequest((request) => request.method() === 'PATCH' && new URL(request.url()).pathname.endsWith('/admin/events/event-1')),
page.getByRole('switch', { name: 'نمایش در جستجو' }).click({ force: true }),
])
expect(updateRequest.postDataJSON()).toEqual({ settings: { isDiscoverable: true } })
const publishRequest = page.waitForRequest(
(request) => request.method() === 'PATCH' && new URL(request.url()).pathname.endsWith('/admin/events/event-1/publish')
)
await page.getByRole('button', { name: 'انتشار فوری' }).click()
await publishRequest
await expect(page.getByText('منتشر‌شده', { exact: true })).toBeVisible()
await expect(page.getByRole('button', { name: 'انتشار فوری' })).toHaveCount(0)
})
test('creates a complete event draft through the multi-step wizard', async ({ page }) => {
let createPayload: Record<string, unknown> | undefined
await page.route('**/api/v1/event-categories**', (route) =>
route.fulfill({
json: {
success: true,
data: [{ id: 10, parentId: null, name: 'ورزشی', slug: 'sports', sortOrder: 1 }],
},
})
)
await page.route('**/api/v1/geography/cities**', (route) =>
route.fulfill({ json: { success: true, data: [{ id: 2, provinceId: 1, name: 'تهران', slug: 'tehran', lat: 35.7, lng: 51.4 }] } })
)
await page.route('**/api/v1/events', async (route) => {
if (route.request().method() !== 'POST') {
await route.fulfill({ json: { success: true, data: { events: [], response: { totalItemsCount: 0 } } } })
return
}
createPayload = route.request().postDataJSON() as Record<string, unknown>
await route.fulfill({ json: { success: true, data: { id: 'created-event', title: createPayload.title, status: 'draft' } } })
})
await page.route('**/api/v1/organizer-guest-lists?**', (route) => route.fulfill({ json: { success: true, data: { items: [] } } }))
await page.goto('/manage-events/new')
await page.getByLabel('عنوان رویداد').fill('دورهمی تست تولید')
await page.getByRole('button', { name: 'دسته‌بندی' }).click()
await page.getByRole('option', { name: 'ورزشی' }).click()
await page.getByRole('button', { name: 'مرحله بعد' }).click()
await confirmEventScheduleField(page, 'تاریخ شروع')
await confirmEventScheduleField(page, 'تاریخ پایان')
await page.getByRole('button', { name: 'شهر' }).click()
await page.getByRole('option', { name: 'تهران' }).click()
await page.getByRole('textbox', { name: 'آدرس *' }).fill('تهران، میدان آزادی')
await page.getByRole('button', { name: 'مرحله بعد' }).click()
await page.getByRole('button', { name: 'رایگان' }).click()
await page.getByRole('textbox', { name: 'ظرفیت *', exact: true }).fill('50')
await page.getByRole('button', { name: 'مرحله بعد' }).click()
await page.getByRole('button', { name: 'ذخیره پیش‌نویس' }).click()
await expect(page).toHaveURL(/\/manage-events$/)
expect(createPayload).toMatchObject({
title: 'دورهمی تست تولید',
slug: expect.stringMatching(/^event-/),
categoryId: 10,
provinceId: 1,
cityId: 2,
address: 'تهران، میدان آزادی',
isFree: true,
price: 0,
capacity: 50,
settings: { isDiscoverable: true, autoCreateGroup: true, waitlistAutoOffer: true, addressVisibility: 'public' },
})
expect(new Date(String(createPayload?.endsAt)).getTime()).toBeGreaterThan(new Date(String(createPayload?.startsAt)).getTime())
})