Files
payload/test/fields/collections/Relationship/e2e.spec.ts
Sam Wheeler 5bd852c9b5 fix(ui): relationship using list drawer correctly updates when hasMany is true (#12176)
### What?

This fixes an issue raised by @maximseshuk in this PR #11553. Here is
the text of the original comment:

If the field has the property hasMany: true and you select one item, it
shows up in the select field, but any additional selected items won't be
visible in the select field, even though the data is actually there and
can be saved. After refreshing the page, they appear.

In addition I added a fix to an issue where the filterOptions weren't
being passed in to the useListDrawer hook properly in polymorphic
relationships

### How?

Instead of using the push method to update the value state, a new array
is created and directly set using useState. I think the issue was
because using push mutates the original array.
2025-04-28 16:38:50 -04:00

879 lines
34 KiB
TypeScript

import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { addListFilter } from 'helpers/e2e/addListFilter.js'
import { navigateToDoc } from 'helpers/e2e/navigateToDoc.js'
import { openDocControls } from 'helpers/e2e/openDocControls.js'
import { openCreateDocDrawer, openDocDrawer } from 'helpers/e2e/toggleDocDrawer.js'
import path from 'path'
import { wait } from 'payload/shared'
import { fileURLToPath } from 'url'
import type { PayloadTestSDK } from '../../../helpers/sdk/index.js'
import type { Config, RelationshipField, TextField } from '../../payload-types.js'
import {
ensureCompilationIsDone,
exactText,
initPageConsoleErrorCatch,
saveDocAndAssert,
saveDocHotkeyAndAssert,
} from '../../../helpers.js'
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
import { assertToastErrors } from '../../../helpers/assertToastErrors.js'
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
import { reInitializeDB } from '../../../helpers/reInitializeDB.js'
import { RESTClient } from '../../../helpers/rest.js'
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../playwright.config.js'
import { relationshipFieldsSlug, textFieldsSlug } from '../../slugs.js'
const filename = fileURLToPath(import.meta.url)
const currentFolder = path.dirname(filename)
const dirname = path.resolve(currentFolder, '../../')
const { beforeAll, beforeEach, describe } = test
let payload: PayloadTestSDK<Config>
let client: RESTClient
let page: Page
let serverURL: string
// If we want to make this run in parallel: test.describe.configure({ mode: 'parallel' })
describe('relationship', () => {
beforeAll(async ({ browser }, testInfo) => {
testInfo.setTimeout(TEST_TIMEOUT_LONG)
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
dirname,
}))
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
await ensureCompilationIsDone({ page, serverURL })
})
beforeEach(async () => {
await reInitializeDB({
serverURL,
snapshotKey: 'fieldsTest',
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
})
if (client) {
await client.logout()
}
client = new RESTClient({ defaultSlug: 'users', serverURL })
await client.login()
await ensureCompilationIsDone({ page, serverURL })
})
let url: AdminUrlUtil
const tableRowLocator = 'table > tbody > tr'
beforeAll(() => {
url = new AdminUrlUtil(serverURL, 'relationship-fields')
})
test('should create inline relationship within field with many relations', async () => {
await page.goto(url.create)
await openCreateDocDrawer({ page, fieldSelector: '#field-relationship' })
await page
.locator('#field-relationship .relationship-add-new__relation-button--text-fields')
.click()
const textField = page.locator('.drawer__content #field-text')
await expect(textField).toBeEnabled()
const textValue = 'hello'
await textField.fill(textValue)
await page.locator('[id^=doc-drawer_text-fields_1_] #action-save').click()
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
await page.locator('[id^=close-drawer__doc-drawer_text-fields_1_]').click()
await expect(
page.locator('#field-relationship .relationship--single-value__text'),
).toContainText(textValue)
await page.locator('#action-save').click()
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
})
test('should create nested inline relationships', async () => {
await page.goto(url.create)
// Open first modal
await openCreateDocDrawer({ page, fieldSelector: '#field-relationToSelf' })
// Fill first modal's required relationship field
await page.locator('[id^=doc-drawer_relationship-fields_1_] #field-relationship').click()
await page
.locator(
'[id^=doc-drawer_relationship-fields_1_] .rs__option:has-text("Seeded text document")',
)
.click()
const secondModalButton = page.locator(
'[id^=doc-drawer_relationship-fields_1_] #relationToSelf-add-new button',
)
await secondModalButton.click()
// Fill second modal's required relationship field
await page.locator('[id^=doc-drawer_relationship-fields_2_] #field-relationship').click()
await page
.locator(
'[id^=doc-drawer_relationship-fields_2_] .rs__option:has-text("Seeded text document")',
)
.click()
// Save then close the second modal
await page.locator('[id^=doc-drawer_relationship-fields_2_] #action-save').click()
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('create')
await page.locator('[id^=close-drawer__doc-drawer_relationship-fields_2_]').click()
// Assert that the first modal is still open and the value matches
await expect(page.locator('[id^=doc-drawer_relationship-fields_1_]')).toBeVisible()
await expect(
page.locator(
'[id^=doc-drawer_relationship-fields_1_] #field-relationToSelf .relationship--single-value__text',
),
).toBeVisible() // TODO: use '.toContainText('doc_id')' with the doc in the second modal
// Save then close the first modal
await page.locator('[id^=doc-drawer_relationship-fields_1_] #action-save').click()
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('create')
await page.locator('[id^=close-drawer__doc-drawer_relationship-fields_1_]').click()
// Expect the original field to have a value filled
await expect(
page.locator('#field-relationToSelf .relationship--single-value__text'),
).toBeVisible()
// Fill the required field
await page.locator('#field-relationship').click()
await page.locator('.rs__option:has-text("Seeded text document")').click()
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('create')
await page.locator('#action-save').click()
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
})
test('should hide relationship add new button', async () => {
await page.goto(url.create)
const locator1 = page.locator(
'#relationWithAllowEditToFalse-add-new .relationship-add-new__add-button',
)
await expect(locator1).toHaveCount(1)
// expect the button to not exist in the field
const locator2 = page.locator(
'#relationWithAllowCreateToFalse-add-new .relationship-add-new__add-button',
)
await expect(locator2).toHaveCount(0)
})
test('should hide relationship edit button', async () => {
await page.goto(url.create)
const locator1 = page
.locator('#field-relationWithAllowEditToFalse')
.getByLabel('Edit dev@payloadcms.com')
await expect(locator1).toHaveCount(0)
const locator2 = page
.locator('#field-relationWithAllowCreateToFalse')
.getByLabel('Edit dev@payloadcms.com')
await expect(locator2).toHaveCount(1)
// The reason why I check for locator 1 again is that I've noticed that sometimes
// the default value does not appear after the first locator is tested. IDK why.
await expect(locator1).toHaveCount(0)
})
test('should hide edit button in main doc when relationship deleted', async () => {
const createdRelatedDoc = await payload.create({
collection: textFieldsSlug,
data: {
text: 'doc to be deleted',
},
})
const doc = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationship: {
value: createdRelatedDoc.id,
relationTo: textFieldsSlug,
},
},
})
await payload.delete({
collection: textFieldsSlug,
id: createdRelatedDoc.id,
})
await page.goto(url.edit(doc.id))
const editBtn = page.locator(
'#field-relationship button.relationship--single-value__drawer-toggler',
)
await expect(editBtn).toHaveCount(0)
})
// TODO: Flaky test in CI - fix this. https://github.com/payloadcms/payload/actions/runs/8910825395/job/24470963991
test.skip('should clear relationship values', async () => {
await page.goto(url.create)
const field = page.locator('#field-relationship')
// wait for relationship options to load
const textFieldPromise = page.waitForResponse(/api\/text-fields/)
const arrayFieldPromise = page.waitForResponse(/api\/array-fields/)
await field.click()
await textFieldPromise
await arrayFieldPromise
await page.locator('.rs__option:has-text("Seeded text document")').click()
await field.locator('.clear-indicator').click()
await expect(field.locator('.rs__placeholder')).toBeVisible()
})
// TODO: React-Select not loading things sometimes. Fix later
test.skip('should display `hasMany` polymorphic relationships', async () => {
await page.goto(url.create)
const field = page.locator('#field-relationHasManyPolymorphic')
await field.click()
await page
.locator('.rs__option', {
hasText: exactText('Seeded text document'),
})
.click()
await expect(
page
.locator('#field-relationHasManyPolymorphic .relationship--multi-value-label__text', {
hasText: exactText('Seeded text document'),
})
.first(),
).toBeVisible()
// await fill the required fields then save the document and check again
await page.locator('#field-relationship').click()
await page.locator('#field-relationship .rs__option:has-text("Seeded text document")').click()
await saveDocAndAssert(page)
const valueAfterSave = page.locator('#field-relationHasManyPolymorphic .multi-value').first()
await expect(
valueAfterSave
.locator('.relationship--multi-value-label__text', {
hasText: exactText('Seeded text document'),
})
.first(),
).toBeVisible()
})
test('should populate relationship dynamic default value', async () => {
await page.goto(url.create)
await expect(
page.locator('#field-relationWithDynamicDefault .relationship--single-value__text'),
).toContainText('dev@payloadcms.com')
await expect(
page.locator('#field-relationHasManyWithDynamicDefault .relationship--single-value__text'),
).toContainText('dev@payloadcms.com')
})
test('should filter relationship options', async () => {
await page.goto(url.create)
await page.locator('#field-relationship .rs__control').click()
await page.keyboard.type('seeded')
await page.locator('.rs__option:has-text("Seeded text document")').click()
await saveDocAndAssert(page)
})
// Related issue: https://github.com/payloadcms/payload/issues/2815
test('should edit document in relationship drawer', async () => {
await page.goto(url.create)
// First fill out the relationship field, as it's required
await openCreateDocDrawer({ page, fieldSelector: '#field-relationship' })
await page
.locator('#field-relationship .relationship-add-new__relation-button--text-fields')
.click()
await page.locator('.drawer__content #field-text').fill('something')
await page.locator('[id^=doc-drawer_text-fields_1_] #action-save').click()
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
await page.locator('[id^=close-drawer__doc-drawer_text-fields_1_]').click()
await page.locator('#action-save').click()
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
// Create a new doc for the `relationshipHasMany` field
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).not.toContain('create')
await openCreateDocDrawer({ page, fieldSelector: '#field-relationshipHasMany' })
const value = 'Hello, world!'
await page.locator('.drawer__content #field-text').fill(value)
// Save and close the drawer
await page.locator('[id^=doc-drawer_text-fields_1_] #action-save').click()
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
await page.locator('[id^=close-drawer__doc-drawer_text-fields_1_]').click()
// Now open the drawer again to edit the `text` field _using the keyboard_
// Mimic real user behavior by typing into the field with spaces and backspaces
// Explicitly use both `down` and `type` to cover edge cases
await openDocDrawer({
page,
selector: '#field-relationshipHasMany button.relationship--multi-value-label__drawer-toggler',
})
await page.locator('[id^=doc-drawer_text-fields_1_] #field-text').click()
await page.keyboard.down('1')
await page.keyboard.type('23')
await expect(page.locator('[id^=doc-drawer_text-fields_1_] #field-text')).toHaveValue(
`${value}123`,
)
await page.keyboard.type('4567')
await page.keyboard.press('Backspace')
await expect(page.locator('[id^=doc-drawer_text-fields_1_] #field-text')).toHaveValue(
`${value}123456`,
)
// save drawer
await page.locator('[id^=doc-drawer_text-fields_1_] #action-save').click()
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
// close drawer
await page.locator('[id^=close-drawer__doc-drawer_text-fields_1_]').click()
// save document and reload
await page.locator('#action-save').click()
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
await page.reload()
// check if the value is saved
await expect(
page.locator('#field-relationshipHasMany .relationship--multi-value-label__text'),
).toHaveText(`${value}123456`)
})
// Drawers opened through the edit button are prone to issues due to the use of stopPropagation for certain
// events - specifically for drawers opened through the edit button. This test is to ensure that drawers
// opened through the edit button can be saved using the hotkey.
test('should save using hotkey in document drawer', async () => {
await page.goto(url.create)
// First fill out the relationship field, as it's required
await openCreateDocDrawer({ page, fieldSelector: '#field-relationship' })
await page.locator('#field-relationship .value-container').click()
await wait(500)
// Select "Seeded text document" relationship
await page.getByText('Seeded text document', { exact: true }).click()
// Need to wait to properly open drawer - without this the drawer state is flakey and closes before
// the text below can be filled before the save on the drawer
await wait(1000)
// Click edit button which opens drawer
await page.getByRole('button', { name: 'Edit Seeded text document' }).click()
// Fill 'text' field of 'Seeded text document'
await page.locator('.drawer__content #field-text').fill('some updated text value')
// Save drawer (not parent page) with hotkey
await saveDocHotkeyAndAssert(page)
const seededTextDocument = await payload.find({
collection: textFieldsSlug,
where: {
text: {
equals: 'some updated text value',
},
},
})
const relationshipDocuments = await payload.find({
collection: relationshipFieldsSlug,
})
// The Seeded text document should now have a text field with value 'some updated text value',
expect(seededTextDocument.docs.length).toEqual(1)
// but the relationship document should NOT exist, as the hotkey should have saved the drawer and not the parent page
// NOTE: the value here represents the number of documents _before_ the test was run
expect(relationshipDocuments.docs.length).toEqual(2)
})
describe('should create document within document drawer', () => {
test('has one', async () => {
await navigateToDoc(page, url)
const originalValue = await page
.locator('#field-relationship .relationship--single-value')
.textContent()
await openDocDrawer({
page,
selector: '#field-relationship .relationship--single-value__drawer-toggler',
})
const drawer1Content = page.locator('[id^=doc-drawer_text-fields_1_] .drawer__content')
const originalDrawerID = await drawer1Content.locator('.id-label').textContent()
await openDocControls(drawer1Content)
await drawer1Content.locator('#action-create').click()
await wait(1000) // wait for /form-state to return
const title = 'Created from drawer'
await drawer1Content.locator('#field-text').fill(title)
await saveDocAndAssert(page, '[id^=doc-drawer_text-fields_1_] .drawer__content #action-save')
const newDrawerID = drawer1Content.locator('.id-label')
await expect(newDrawerID).not.toHaveText(originalDrawerID)
await page.locator('[id^=doc-drawer_text-fields_1_] .drawer__close').click()
await page.locator('#field-relationship').scrollIntoViewIfNeeded()
await expect(
page.locator('#field-relationship .relationship--single-value__text', {
hasText: exactText(originalValue),
}),
).toBeHidden()
await expect(
page.locator('#field-relationship .relationship--single-value__text', {
hasText: exactText(title),
}),
).toBeVisible()
await page.locator('#field-relationship .rs__control').click()
await expect(
page.locator('.rs__option', {
hasText: exactText(title),
}),
).toBeVisible()
})
})
describe('should duplicate document within document drawer', () => {
test('has one', async () => {
await navigateToDoc(page, url)
await wait(500)
const fieldControl = page.locator('#field-relationship .rs__control')
const originalValue = await page
.locator('#field-relationship .relationship--single-value__text')
.textContent()
await fieldControl.click()
await expect(
page.locator('.rs__option', {
hasText: exactText(originalValue),
}),
).toBeVisible()
await openDocDrawer({
page,
selector: '#field-relationship .relationship--single-value__drawer-toggler',
})
const drawer1Content = page.locator('[id^=doc-drawer_text-fields_1_] .drawer__content')
const originalID = await drawer1Content.locator('.id-label').textContent()
const originalText = 'Text'
await drawer1Content.locator('#field-text').fill(originalText)
await saveDocAndAssert(page, '[id^=doc-drawer_text-fields_1_] .drawer__content #action-save')
await openDocControls(drawer1Content)
await drawer1Content.locator('#action-duplicate').click()
const duplicateID = drawer1Content.locator('.id-label')
await expect(duplicateID).not.toHaveText(originalID)
await page.locator('[id^=doc-drawer_text-fields_1_] .drawer__close').click()
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
await page.locator('#field-relationship').scrollIntoViewIfNeeded()
const newValue = `${originalText} - duplicate` // this is added via a `beforeDuplicate` hook
await expect(
page.locator('#field-relationship .relationship--single-value__text', {
hasText: exactText(originalValue),
}),
).toBeHidden()
await expect(
page.locator('#field-relationship .relationship--single-value__text', {
hasText: exactText(newValue),
}),
).toBeVisible()
await page.locator('#field-relationship .rs__control').click()
await expect(
page.locator('.rs__option', {
hasText: exactText(newValue),
}),
).toBeVisible()
})
})
describe('should delete document within document drawer', () => {
test('has one', async () => {
await navigateToDoc(page, url)
await wait(500)
const originalValue = await page
.locator('#field-relationship .relationship--single-value__text')
.textContent()
await page.locator('#field-relationship .rs__control').click()
await expect(
page.locator('#field-relationship .rs__option', {
hasText: exactText(originalValue),
}),
).toBeVisible()
await openDocDrawer({
page,
selector: '#field-relationship button.relationship--single-value__drawer-toggler',
})
const drawer1Content = page.locator('[id^=doc-drawer_text-fields_1_] .drawer__content')
const originalID = await drawer1Content.locator('.id-label').textContent()
await openDocControls(drawer1Content)
await drawer1Content.locator('#action-delete').click()
await page
.locator('[id^=delete-].payload__modal-item.confirmation-modal[open] button#confirm-action')
.click()
await expect(drawer1Content).toBeHidden()
await expect(
page.locator('#field-relationship .relationship--single-value__text'),
).toBeHidden()
await expect(page.locator('#field-relationship .rs__placeholder')).toBeVisible()
await page.locator('#field-relationship .rs__control').click()
await wait(500)
await expect(
page.locator('#field-relationship .rs__option', {
hasText: exactText(originalValue),
}),
).toBeHidden()
await expect(
page.locator('#field-relationship .rs__option', {
hasText: exactText(`Untitled - ${originalID}`),
}),
).toBeHidden()
})
})
// TODO: Fix this. This test flakes due to react select
test.skip('should bypass min rows validation when no rows present and field is not required', async () => {
await page.goto(url.create)
// First fill out the relationship field, as it's required
await openCreateDocDrawer({ page, fieldSelector: '#field-relationship' })
await page.locator('#field-relationship .value-container').click()
await page.getByText('Seeded text document', { exact: true }).click()
await saveDocAndAssert(page)
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
})
test('should fail min rows validation when rows are present', async () => {
await page.goto(url.create)
// First fill out the relationship field, as it's required
await openCreateDocDrawer({ page, fieldSelector: '#field-relationship' })
await page.locator('#field-relationship .value-container').click()
await page.getByText('Seeded text document', { exact: true }).click()
// Need to wait to allow for field to retrieve documents before the save occurs
await wait(200)
await page.locator('#field-relationshipWithMinRows .value-container').click()
await page
.locator('#field-relationshipWithMinRows .rs__option:has-text("Seeded text document")')
.click()
await page.click('#action-save', { delay: 100 })
await assertToastErrors({
page,
errors: ['Relationship With Min Rows'],
})
})
test('should sort relationship options by sortOptions property (ID in ascending order)', async () => {
await page.goto(url.create)
const field = page.locator('#field-relationship')
await field.click()
await wait(400)
const textDocsGroup = page.locator('.rs__group-heading:has-text("Text Fields")')
const firstTextDocOption = textDocsGroup.locator('+div .rs__option').first()
const firstOptionLabel = await firstTextDocOption.textContent()
expect(firstOptionLabel?.trim()).toBe('Another text document')
})
test('should sort relationHasManyPolymorphic options by sortOptions property: text-fields collection (items in descending order)', async () => {
await page.goto(url.create)
const field = page.locator('#field-relationHasManyPolymorphic')
// wait for relationship options to load
const textFieldPromise = page.waitForResponse(/api\/text-fields/)
const arrayFieldPromise = page.waitForResponse(/api\/array-fields/)
await field.click()
await textFieldPromise
await arrayFieldPromise
const textDocsGroup = page.locator('.rs__group-heading:has-text("Text Fields")')
const firstTextDocOption = textDocsGroup.locator('+div .rs__option').first()
const firstOptionLabel = firstTextDocOption
await expect(firstOptionLabel).toHaveText('Seeded text document')
})
test('should allow filtering by relationship field / equals', async () => {
const textDoc = await createTextFieldDoc()
await createRelationshipFieldDoc({ value: textDoc.id, relationTo: 'text-fields' })
await page.goto(url.list)
await addListFilter({
page,
fieldLabel: 'Relationship',
operatorLabel: 'equals',
value: 'some text',
})
await expect(page.locator(tableRowLocator)).toHaveCount(1)
})
test('should be able to select relationship with drawer appearance', async () => {
await page.goto(url.create)
const relationshipField = page.locator('#field-relationshipDrawer')
await relationshipField.click()
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
await expect(listDrawerContent).toBeVisible()
const firstRow = listDrawerContent.locator('table tbody tr').first()
const button = firstRow.locator('button')
await button.click()
await expect(listDrawerContent).toBeHidden()
const selectedValue = relationshipField.locator('.relationship--single-value__text')
await expect(selectedValue).toBeVisible()
// Fill required field
await page.locator('#field-relationship').click()
await page.locator('.rs__option:has-text("Seeded text document")').click()
await saveDocAndAssert(page)
})
test('should be able to search within relationship list drawer', async () => {
await page.goto(url.create)
const relationshipField = page.locator('#field-relationshipDrawer')
await relationshipField.click()
const searchField = page.locator('.list-drawer .search-filter')
await expect(searchField).toBeVisible()
const searchInput = searchField.locator('input')
await searchInput.fill('seeded')
const rows = page.locator('.list-drawer table tbody tr')
await expect(rows).toHaveCount(1)
const closeButton = page.locator('.list-drawer__header-close')
await closeButton.click()
await expect(page.locator('.list-drawer')).toBeHidden()
})
test('should handle read-only relationship field when `appearance: "drawer"`', async () => {
await page.goto(url.create)
const readOnlyField = page.locator(
'#field-relationshipDrawerReadOnly .rs__control--is-disabled',
)
await expect(readOnlyField).toBeVisible()
})
test('should handle polymorphic relationship when `appearance: "drawer"`', async () => {
await page.goto(url.create)
const relationshipField = page.locator('#field-polymorphicRelationshipDrawer')
await relationshipField.click()
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
await expect(listDrawerContent).toBeVisible()
const relationToSelector = page.locator('.list-header__select-collection')
await expect(relationToSelector).toBeVisible()
await relationToSelector.locator('.rs__control').click()
const option = relationToSelector.locator('.rs__option').nth(1)
await option.click()
const firstRow = listDrawerContent.locator('table tbody tr').first()
const button = firstRow.locator('button')
await button.click()
await expect(listDrawerContent).toBeHidden()
const selectedValue = relationshipField.locator('.relationship--single-value__text')
await expect(selectedValue).toBeVisible()
// Fill required field
await page.locator('#field-relationship').click()
await page.locator('.rs__option:has-text("Seeded text document")').click()
await saveDocAndAssert(page)
})
test('should handle `hasMany` relationship when `appearance: "drawer"`', async () => {
await page.goto(url.create)
const relationshipField = page.locator('#field-relationshipDrawerHasMany')
await relationshipField.click()
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
await expect(listDrawerContent).toBeVisible()
const firstRow = listDrawerContent.locator('table tbody tr').first()
const button = firstRow.locator('button')
await button.click()
await expect(listDrawerContent).toBeHidden()
const selectedValue = relationshipField.locator('.relationship--multi-value-label__text')
await expect(selectedValue).toHaveCount(1)
await relationshipField.click()
await expect(listDrawerContent).toBeVisible()
await button.click()
await expect(listDrawerContent).toBeHidden()
const selectedValues = relationshipField.locator('.relationship--multi-value-label__text')
await expect(selectedValues).toHaveCount(2)
})
test('should handle `hasMany` polymorphic relationship when `appearance: "drawer"`', async () => {
await page.goto(url.create)
const relationshipField = page.locator('#field-relationshipDrawerHasManyPolymorphic')
await relationshipField.click()
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
await expect(listDrawerContent).toBeVisible()
const firstRow = listDrawerContent.locator('table tbody tr').first()
const button = firstRow.locator('button')
await button.click()
await expect(listDrawerContent).toBeHidden()
const selectedValue = relationshipField.locator('.relationship--multi-value-label__text')
await expect(selectedValue).toBeVisible()
})
test('should not be allowed to create in relationship list drawer when `allowCreate` is `false`', async () => {
await page.goto(url.create)
const relationshipField = page.locator('#field-relationshipDrawerWithAllowCreateFalse')
await relationshipField.click()
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
await expect(listDrawerContent).toBeVisible()
const createNewButton = listDrawerContent.locator('list-drawer__create-new-button')
await expect(createNewButton).toBeHidden()
})
test('should respect `filterOptions` in the relationship list drawer for filtered relationship', async () => {
// Create test documents
await createTextFieldDoc({ text: 'list drawer test' })
await createTextFieldDoc({ text: 'not test' })
await page.goto(url.create)
const relationshipField = page.locator('#field-relationshipDrawerWithFilterOptions')
await relationshipField.click()
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
await expect(listDrawerContent).toBeVisible()
const rows = page.locator('.list-drawer table tbody tr')
await expect(rows).toHaveCount(1)
})
test('should filter out existing values from relationship list drawer', async () => {
await page.goto(url.create)
await page.locator('#field-relationshipDrawer').click()
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
await expect(listDrawerContent).toBeVisible()
const rows = listDrawerContent.locator('table tbody tr')
await expect(rows).toHaveCount(2)
await listDrawerContent.getByText('Seeded text document', { exact: true }).click()
const selectedValue = page.locator(
'#field-relationshipDrawer .relationship--single-value__text',
)
await expect(selectedValue).toHaveText('Seeded text document')
await page.locator('#field-relationshipDrawer').click()
const newRows = listDrawerContent.locator('table tbody tr')
await expect(newRows).toHaveCount(1)
await expect(listDrawerContent.getByText('Seeded text document')).toHaveCount(0)
})
test('should filter out existing values from polymorphic relationship list drawer', async () => {
await page.goto(url.create)
const relationshipField = page.locator('#field-polymorphicRelationshipDrawer')
await relationshipField.click()
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
await expect(listDrawerContent).toBeVisible()
const relationToSelector = page.locator('.list-header__select-collection')
await expect(relationToSelector).toBeVisible()
await relationToSelector.locator('.rs__control').click()
const option = relationToSelector.locator('.rs__option').nth(1)
await option.click()
const rows = listDrawerContent.locator('table tbody tr')
await expect(rows).toHaveCount(2)
const firstRow = rows.first()
const button = firstRow.locator('button')
await button.click()
await expect(listDrawerContent).toBeHidden()
const selectedValue = relationshipField.locator('.relationship--single-value__text')
await expect(selectedValue).toBeVisible()
await relationshipField.click()
await expect(listDrawerContent).toBeVisible()
await expect(relationToSelector).toBeVisible()
await relationToSelector.locator('.rs__control').click()
await option.click()
const newRows = listDrawerContent.locator('table tbody tr')
await expect(newRows).toHaveCount(1)
const newFirstRow = newRows.first()
const newButton = newFirstRow.locator('button')
await newButton.click()
await expect(listDrawerContent).toBeHidden()
})
})
async function createTextFieldDoc(overrides?: Partial<TextField>): Promise<TextField> {
return payload.create({
collection: 'text-fields',
data: {
text: 'some text',
localizedText: 'some localized text',
...overrides,
},
}) as unknown as Promise<TextField>
}
async function createRelationshipFieldDoc(
relationship: RelationshipField['relationship'],
overrides?: Partial<RelationshipField>,
): Promise<RelationshipField> {
return payload.create({
collection: 'relationship-fields',
data: {
relationship,
...overrides,
},
}) as unknown as Promise<RelationshipField>
}