chore: splits out relationship
This commit is contained in:
1
.github/workflows/main.yml
vendored
1
.github/workflows/main.yml
vendored
@@ -258,6 +258,7 @@ jobs:
|
||||
- fields
|
||||
- fields/collections/Blocks
|
||||
- fields/collections/Array
|
||||
- fields/collections/Relationship
|
||||
# - fields/lexical
|
||||
# - live-preview
|
||||
# - localization
|
||||
|
||||
445
test/fields/collections/Relationship/e2e.spec.ts
Normal file
445
test/fields/collections/Relationship/e2e.spec.ts
Normal file
@@ -0,0 +1,445 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import path from 'path'
|
||||
import { wait } from 'payload/utilities'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { PayloadTestSDK } from '../../../helpers/sdk/index.js'
|
||||
import type { Config, RelationshipField, TextField } from '../../payload-types.js'
|
||||
|
||||
import {
|
||||
ensureAutoLoginAndCompilationIsDone,
|
||||
exactText,
|
||||
initPageConsoleErrorCatch,
|
||||
saveDocAndAssert,
|
||||
saveDocHotkeyAndAssert,
|
||||
} from '../../../helpers.js'
|
||||
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
|
||||
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
|
||||
import { reInitializeDB } from '../../../helpers/reInit.js'
|
||||
import { RESTClient } from '../../../helpers/rest.js'
|
||||
import { POLL_TOPASS_TIMEOUT } 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 }) => {
|
||||
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({
|
||||
dirname,
|
||||
}))
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsRelationshipTest',
|
||||
uploadsDir: path.resolve(dirname, '../Upload/uploads'),
|
||||
})
|
||||
|
||||
if (client) {
|
||||
await client.logout()
|
||||
}
|
||||
client = new RESTClient(null, { defaultSlug: 'users', serverURL })
|
||||
await client.login()
|
||||
|
||||
await ensureAutoLoginAndCompilationIsDone({ 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)
|
||||
|
||||
const button = page.locator('#relationship-add-new .relationship-add-new__add-button')
|
||||
await button.click()
|
||||
await page
|
||||
.locator('#field-relationship .relationship-add-new__relation-button--text-fields')
|
||||
.click()
|
||||
|
||||
const textField = page.locator('.drawer__content #field-text')
|
||||
const textValue = 'hello'
|
||||
|
||||
await textField.fill(textValue)
|
||||
|
||||
await page.locator('[id^=doc-drawer_text-fields_1_] #action-save').click()
|
||||
await expect(page.locator('.Toastify')).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('.Toastify')).toContainText('successfully')
|
||||
})
|
||||
|
||||
test('should create nested inline relationships', async () => {
|
||||
await page.goto(url.create)
|
||||
await page.waitForURL(`**/${url.create}`)
|
||||
// Open first modal
|
||||
await page.locator('#relationToSelf-add-new .relationship-add-new__add-button').click()
|
||||
|
||||
// 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()
|
||||
|
||||
// Open second modal
|
||||
await page
|
||||
.locator('[id^=doc-drawer_relationship-fields_1_] #relationToSelf-add-new button')
|
||||
.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('.Toastify')).toContainText('successfully')
|
||||
})
|
||||
|
||||
test('should hide relationship add new button', async () => {
|
||||
await page.goto(url.create)
|
||||
// expect the button to not exist in the field
|
||||
const count = await page
|
||||
.locator('#relationToSelfSelectOnly-add-new .relationship-add-new__add-button')
|
||||
.count()
|
||||
expect(count).toEqual(0)
|
||||
})
|
||||
|
||||
test('should clear relationship values', async () => {
|
||||
await page.goto(url.create)
|
||||
|
||||
const field = page.locator('#field-relationship')
|
||||
await field.click()
|
||||
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 modify fields in relationship drawer', async () => {
|
||||
await page.goto(url.create)
|
||||
await page.waitForURL(`**/${url.create}`)
|
||||
// First fill out the relationship field, as it's required
|
||||
await page.locator('#relationship-add-new .relationship-add-new__add-button').click()
|
||||
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('.Toastify')).toContainText('successfully')
|
||||
await page.locator('[id^=close-drawer__doc-drawer_text-fields_1_]').click()
|
||||
await page.locator('#action-save').click()
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
|
||||
// Create a new doc for the `relationshipHasMany` field
|
||||
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).not.toContain('create')
|
||||
await page.locator('#field-relationshipHasMany .relationship-add-new__add-button').click()
|
||||
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('.Toastify')).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 page
|
||||
.locator('#field-relationshipHasMany button.relationship--multi-value-label__drawer-toggler')
|
||||
.click()
|
||||
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('.Toastify')).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('.Toastify')).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 edit document drawer', async () => {
|
||||
await page.goto(url.create)
|
||||
// First fill out the relationship field, as it's required
|
||||
await page.locator('#relationship-add-new .relationship-add-new__add-button').click()
|
||||
await page.locator('#field-relationship .value-container').click()
|
||||
// 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(200)
|
||||
|
||||
// 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
|
||||
expect(relationshipDocuments.docs.length).toEqual(0)
|
||||
})
|
||||
|
||||
// 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 page.locator('#relationship-add-new .relationship-add-new__add-button').click()
|
||||
await page.locator('#field-relationship .value-container').click()
|
||||
await page.getByText('Seeded text document', { exact: true }).click()
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
await expect(page.locator('.Toastify')).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 page.locator('#relationship-add-new .relationship-add-new__add-button').click()
|
||||
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 expect(page.locator('.Toastify')).toContainText(
|
||||
'The following field is invalid: relationshipWithMinRows',
|
||||
)
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
const firstOption = page.locator('.rs__option').first()
|
||||
await expect(firstOption).toBeVisible()
|
||||
const firstOptionText = await firstOption.textContent()
|
||||
expect(firstOptionText.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')
|
||||
await field.click()
|
||||
|
||||
const firstOption = page.locator('.rs__option').first()
|
||||
await expect(firstOption).toBeVisible()
|
||||
const firstOptionText = await firstOption.textContent()
|
||||
expect(firstOptionText.trim()).toBe('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 page.locator('.list-controls__toggle-columns').click()
|
||||
await page.locator('.list-controls__toggle-where').click()
|
||||
await expect(page.locator('.list-controls__where.rah-static--height-auto')).toBeVisible()
|
||||
await page.locator('.where-builder__add-first-filter').click()
|
||||
|
||||
const conditionField = page.locator('.condition__field')
|
||||
await conditionField.click()
|
||||
|
||||
const dropdownFieldOptions = conditionField.locator('.rs__option')
|
||||
await dropdownFieldOptions.locator('text=Relationship').nth(0).click()
|
||||
|
||||
const operatorField = page.locator('.condition__operator')
|
||||
await operatorField.click()
|
||||
|
||||
const dropdownOperatorOptions = operatorField.locator('.rs__option')
|
||||
await dropdownOperatorOptions.locator('text=equals').click()
|
||||
|
||||
const valueField = page.locator('.condition__value')
|
||||
await valueField.click()
|
||||
const dropdownValueOptions = valueField.locator('.rs__option')
|
||||
await dropdownValueOptions.locator('text=some text').click()
|
||||
|
||||
await expect(page.locator(tableRowLocator)).toHaveCount(1)
|
||||
})
|
||||
})
|
||||
|
||||
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>
|
||||
}
|
||||
@@ -1102,369 +1102,6 @@ describe('fields', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('relationship', () => {
|
||||
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)
|
||||
|
||||
const button = page.locator('#relationship-add-new .relationship-add-new__add-button')
|
||||
await button.click()
|
||||
await page
|
||||
.locator('#field-relationship .relationship-add-new__relation-button--text-fields')
|
||||
.click()
|
||||
|
||||
const textField = page.locator('.drawer__content #field-text')
|
||||
const textValue = 'hello'
|
||||
|
||||
await textField.fill(textValue)
|
||||
|
||||
await page.locator('[id^=doc-drawer_text-fields_1_] #action-save').click()
|
||||
await expect(page.locator('.Toastify')).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('.Toastify')).toContainText('successfully')
|
||||
})
|
||||
|
||||
test('should create nested inline relationships', async () => {
|
||||
await page.goto(url.create)
|
||||
await page.waitForURL(`**/${url.create}`)
|
||||
// Open first modal
|
||||
await page.locator('#relationToSelf-add-new .relationship-add-new__add-button').click()
|
||||
|
||||
// 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()
|
||||
|
||||
// Open second modal
|
||||
await page
|
||||
.locator('[id^=doc-drawer_relationship-fields_1_] #relationToSelf-add-new button')
|
||||
.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('.Toastify')).toContainText('successfully')
|
||||
})
|
||||
|
||||
test('should hide relationship add new button', async () => {
|
||||
await page.goto(url.create)
|
||||
// expect the button to not exist in the field
|
||||
const count = await page
|
||||
.locator('#relationToSelfSelectOnly-add-new .relationship-add-new__add-button')
|
||||
.count()
|
||||
expect(count).toEqual(0)
|
||||
})
|
||||
|
||||
test('should clear relationship values', async () => {
|
||||
await page.goto(url.create)
|
||||
|
||||
const field = page.locator('#field-relationship')
|
||||
await field.click()
|
||||
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 modify fields in relationship drawer', async () => {
|
||||
await page.goto(url.create)
|
||||
await page.waitForURL(`**/${url.create}`)
|
||||
// First fill out the relationship field, as it's required
|
||||
await page.locator('#relationship-add-new .relationship-add-new__add-button').click()
|
||||
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('.Toastify')).toContainText('successfully')
|
||||
await page.locator('[id^=close-drawer__doc-drawer_text-fields_1_]').click()
|
||||
await page.locator('#action-save').click()
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
|
||||
// Create a new doc for the `relationshipHasMany` field
|
||||
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).not.toContain('create')
|
||||
await page.locator('#field-relationshipHasMany .relationship-add-new__add-button').click()
|
||||
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('.Toastify')).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 page
|
||||
.locator(
|
||||
'#field-relationshipHasMany button.relationship--multi-value-label__drawer-toggler',
|
||||
)
|
||||
.click()
|
||||
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('.Toastify')).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('.Toastify')).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 edit document drawer', async () => {
|
||||
await page.goto(url.create)
|
||||
// First fill out the relationship field, as it's required
|
||||
await page.locator('#relationship-add-new .relationship-add-new__add-button').click()
|
||||
await page.locator('#field-relationship .value-container').click()
|
||||
// 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(200)
|
||||
|
||||
// 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
|
||||
expect(relationshipDocuments.docs.length).toEqual(0)
|
||||
})
|
||||
|
||||
// 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 page.locator('#relationship-add-new .relationship-add-new__add-button').click()
|
||||
await page.locator('#field-relationship .value-container').click()
|
||||
await page.getByText('Seeded text document', { exact: true }).click()
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
await expect(page.locator('.Toastify')).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 page.locator('#relationship-add-new .relationship-add-new__add-button').click()
|
||||
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 expect(page.locator('.Toastify')).toContainText(
|
||||
'The following field is invalid: relationshipWithMinRows',
|
||||
)
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
const firstOption = page.locator('.rs__option').first()
|
||||
await expect(firstOption).toBeVisible()
|
||||
const firstOptionText = await firstOption.textContent()
|
||||
expect(firstOptionText.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')
|
||||
await field.click()
|
||||
|
||||
const firstOption = page.locator('.rs__option').first()
|
||||
await expect(firstOption).toBeVisible()
|
||||
const firstOptionText = await firstOption.textContent()
|
||||
expect(firstOptionText.trim()).toBe('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 page.locator('.list-controls__toggle-columns').click()
|
||||
await page.locator('.list-controls__toggle-where').click()
|
||||
await expect(page.locator('.list-controls__where.rah-static--height-auto')).toBeVisible()
|
||||
await page.locator('.where-builder__add-first-filter').click()
|
||||
|
||||
const conditionField = page.locator('.condition__field')
|
||||
await conditionField.click()
|
||||
|
||||
const dropdownFieldOptions = conditionField.locator('.rs__option')
|
||||
await dropdownFieldOptions.locator('text=Relationship').nth(0).click()
|
||||
|
||||
const operatorField = page.locator('.condition__operator')
|
||||
await operatorField.click()
|
||||
|
||||
const dropdownOperatorOptions = operatorField.locator('.rs__option')
|
||||
await dropdownOperatorOptions.locator('text=equals').click()
|
||||
|
||||
const valueField = page.locator('.condition__value')
|
||||
await valueField.click()
|
||||
const dropdownValueOptions = valueField.locator('.rs__option')
|
||||
await dropdownValueOptions.locator('text=some text').click()
|
||||
|
||||
await expect(page.locator(tableRowLocator)).toHaveCount(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('upload', () => {
|
||||
let url: AdminUrlUtil
|
||||
beforeAll(() => {
|
||||
|
||||
@@ -53,14 +53,7 @@ if (!suiteName) {
|
||||
} else {
|
||||
// Run specific suite
|
||||
clearWebpackCache()
|
||||
let suitePath: string
|
||||
if (suiteName.includes('/')) {
|
||||
// Used for fields/lexical.e2e.spec.ts which is run from CI as fields/lexical
|
||||
const suiteNameSplit = suiteName.split('/')
|
||||
suitePath = path.resolve(dirname, suiteNameSplit[0], suiteNameSplit[1] + '.e2e.spec.ts')
|
||||
} else {
|
||||
suitePath = path.resolve(dirname, suiteName, 'e2e.spec.ts')
|
||||
}
|
||||
const suitePath = path.resolve(dirname, suiteName, '.e2e.spec.ts')
|
||||
executePlaywright(suitePath)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user