bolts/frontend/tests/entity-configuration.spec.ts

158 lines
6.8 KiB
TypeScript

import { expect, type Page, type Route, test } from '@playwright/test'
interface EntityField {
id: number
name: string
type: string
targetEntityId?: number
relationshipType?: string
}
interface EntityDefinition {
id: number
name: string
fields: EntityField[]
}
async function useAdminSession (page: Page) {
await page.addInitScript(() => {
sessionStorage.setItem('bolts-session', JSON.stringify({
username: 'admin',
role: 'admin',
token: 'admin-token',
}))
})
}
function formValues (route: Route) {
return Object.fromEntries(new URLSearchParams(route.request().postData() ?? ''))
}
test('only admins can access entity configuration', async ({ page }) => {
await page.addInitScript(() => {
sessionStorage.setItem('bolts-session', JSON.stringify({
username: 'member',
role: 'user',
token: 'user-token',
}))
})
await page.goto('/admin/entities')
await expect(page).toHaveURL('/')
await expect(page.getByRole('heading', { name: 'Entity Configuration' })).not.toBeVisible()
})
test('admin can add, edit, and remove an entity and its fields', async ({ page }) => {
const entities: EntityDefinition[] = []
let nextEntityId = 1
let nextFieldId = 1
await useAdminSession(page)
await page.route('**/api/entity-definitions**', async route => {
const request = route.request()
const url = new URL(request.url())
const segments = url.pathname.split('/').filter(Boolean)
const entityId = Number(segments[2])
const fieldId = Number(segments[4])
const entity = entities.find(item => item.id === entityId)
if (request.method() === 'GET') {
await route.fulfill({ json: entities })
} else if (request.method() === 'POST' && segments.at(-1) === 'entity-definitions') {
const created = { id: nextEntityId++, name: formValues(route).name, fields: [] }
entities.push(created)
await route.fulfill({ status: 201, json: created })
} else if (request.method() === 'PATCH' && segments.length === 3 && entity) {
entity.name = formValues(route).name
await route.fulfill({ json: entity })
} else if (request.method() === 'DELETE' && segments.length === 3 && entity) {
entities.splice(entities.indexOf(entity), 1)
await route.fulfill({ status: 204 })
} else if (request.method() === 'POST' && segments.at(-1) === 'fields' && entity) {
const values = formValues(route)
const created = {
id: nextFieldId++,
name: values.name,
type: values.type,
...(values.targetEntityId ? { targetEntityId: Number(values.targetEntityId) } : {}),
...(values.relationshipType ? { relationshipType: values.relationshipType } : {}),
}
entity.fields.push(created)
await route.fulfill({ status: 201, json: created })
} else if (request.method() === 'PATCH' && entity) {
const field = entity.fields.find(item => item.id === fieldId)!
Object.assign(field, formValues(route))
await route.fulfill({ json: field })
} else if (request.method() === 'DELETE' && entity) {
const index = entity.fields.findIndex(item => item.id === fieldId)
entity.fields.splice(index, 1)
await route.fulfill({ status: 204 })
} else {
await route.fulfill({ status: 404 })
}
})
await page.goto('/admin/entities')
await expect(page.getByRole('heading', { name: 'Entity Configuration' })).toBeVisible()
await expect(page.getByRole('link', { name: 'Entity Configuration' })).toBeVisible()
await page.getByLabel('Entity name', { exact: true }).fill('Company')
await page.getByRole('button', { name: 'Add entity' }).click()
await expect(page.getByRole('heading', { name: 'Company' })).toBeVisible()
await page.getByRole('button', { name: 'Edit entity' }).click()
const entityEditForm = page.getByRole('button', { name: 'Save entity' }).locator('..')
await entityEditForm.getByLabel('Entity name', { exact: true }).fill('Organization')
await entityEditForm.getByRole('button', { name: 'Save entity' }).click()
await expect(page.getByRole('heading', { name: 'Organization' })).toBeVisible()
await page.getByLabel('New field').fill('Website')
await page.getByLabel('Field type').selectOption('EMAIL')
await page.getByRole('button', { name: 'Add field' }).click()
await expect(page.getByText('Website')).toBeVisible()
await expect(page.locator('.type-badge')).toHaveText('Email')
await page.getByRole('button', { name: 'Edit field' }).click()
const fieldEditForm = page.getByRole('button', { name: 'Save field' }).locator('..')
await fieldEditForm.getByLabel('Field name').fill('Annual revenue')
await fieldEditForm.getByLabel('Field type').selectOption('NUMBER')
await fieldEditForm.getByRole('button', { name: 'Save field' }).click()
await expect(page.getByText('Annual revenue')).toBeVisible()
await expect(page.locator('.type-badge')).toHaveText('Number')
page.on('dialog', dialog => dialog.accept())
await page.getByRole('button', { name: 'Remove field' }).click()
await expect(page.getByText('Annual revenue')).not.toBeVisible()
await page.getByLabel('Entity name', { exact: true }).fill('Contact')
await page.getByRole('button', { name: 'Add entity' }).click()
const organization = page.locator('.entity-card').filter({
has: page.getByRole('heading', { name: 'Organization' }),
})
const organizationFieldForm = organization.getByRole('form', { name: 'Add field to Organization' })
await organizationFieldForm.getByLabel('New field').fill('Primary contact')
await organizationFieldForm.getByLabel('Field type').selectOption('RELATIONSHIP')
await organizationFieldForm.getByLabel('Related entity').selectOption({ label: 'Contact' })
await organizationFieldForm.getByLabel('Relationship type').selectOption('ONE_TO_ONE')
await organizationFieldForm.getByRole('button', { name: 'Add field' }).click()
await expect(organization.locator('.type-badge').filter({ hasText: 'Relationship' })).toBeVisible()
await expect(organization.getByText('Contact · One to one')).toBeVisible()
await organizationFieldForm.getByLabel('New field').fill('Account owner')
await organizationFieldForm.getByLabel('Field type').selectOption('USER')
await organizationFieldForm.getByRole('button', { name: 'Add field' }).click()
await expect(organization.locator('.type-badge').filter({ hasText: 'User reference' })).toBeVisible()
await expect(organization.getByText('Built-in User entity')).toBeVisible()
await organization.getByRole('button', { name: 'Remove entity' }).click()
await expect(page.getByRole('heading', { name: 'Organization' })).not.toBeVisible()
const contact = page.locator('.entity-card').filter({
has: page.getByRole('heading', { name: 'Contact' }),
})
await contact.getByRole('button', { name: 'Remove entity' }).click()
await expect(page.getByText('No entities yet.')).toBeVisible()
})