Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
226 lines
9.1 KiB
TypeScript
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())
|
|
})
|