Files
payload/test/joins/e2e.spec.ts
Said Akhrarov a58b9fc230 fix(ui): join table row still shows after deletion (#9783)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR fixes an issue where deleting an entry in a `Join` via the
`Drawer` accessed through the `DrawerLink` would not update the table
until the page was refreshed.

### Why?
For a better, more reactive, deletion experience for end-users. Ideally,
the deletion is reflected in the table right away.

### How?
By passing an `onDrawerDelete` function to the `DrawerLink` which simply
filters out the existing doc according to an id.

Fixes #9580

Before:

[Editing---Post--before-Payload.webm](https://github.com/user-attachments/assets/3dd4df78-bb63-46b1-bf5f-7643935e15ad)

After:

[Editing---Post--after-Payload.webm](https://github.com/user-attachments/assets/97bb604f-41df-4cc9-8c46-9a59a19c72b7)
2024-12-20 21:47:09 -05:00

436 lines
18 KiB
TypeScript

import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { reorderColumns } from 'helpers/e2e/reorderColumns.js'
import * as path from 'path'
import { fileURLToPath } from 'url'
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
import type { Config } from './payload-types.js'
import {
ensureCompilationIsDone,
exactText,
initPageConsoleErrorCatch,
saveDocAndAssert,
} from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { navigateToDoc } from '../helpers/e2e/navigateToDoc.js'
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
import { categoriesJoinRestrictedSlug, categoriesSlug, postsSlug, uploadsSlug } from './shared.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
let payload: PayloadTestSDK<Config>
let serverURL: string
test.describe('Join Field', () => {
let page: Page
let categoriesURL: AdminUrlUtil
let uploadsURL: AdminUrlUtil
let categoriesJoinRestrictedURL: AdminUrlUtil
let categoryID
test.beforeAll(async ({ browser }, testInfo) => {
testInfo.setTimeout(TEST_TIMEOUT_LONG)
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
dirname,
}))
categoriesURL = new AdminUrlUtil(serverURL, categoriesSlug)
uploadsURL = new AdminUrlUtil(serverURL, uploadsSlug)
categoriesJoinRestrictedURL = new AdminUrlUtil(serverURL, categoriesJoinRestrictedSlug)
const { docs } = await payload.find({
collection: categoriesSlug,
where: {
name: {
equals: 'example',
},
},
})
;({ id: categoryID } = docs[0])
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
await ensureCompilationIsDone({ page, serverURL })
})
test('should populate joined relationships in table cells of list view', async () => {
await page.goto(categoriesURL.list)
await expect
.poll(
async () =>
await page
.locator('tbody tr:first-child td.cell-relatedPosts', {
hasText: exactText('Test Post 3, Test Post 2, Test Post 1'),
})
.isVisible(),
)
.toBeTruthy()
})
test('should render initial rows within relationship table', async () => {
await navigateToDoc(page, categoriesURL)
const joinField = page.locator('#field-relatedPosts.field-type.join')
await expect(joinField).toBeVisible()
await expect(joinField.locator('.relationship-table table')).toBeVisible()
const rows = joinField.locator('.relationship-table tbody tr')
await expect(rows).toHaveCount(3)
})
test('should apply defaultLimit and defaultSort on relationship table', async () => {
const result = await payload.find({
collection: categoriesSlug,
limit: 1,
})
const category = result.docs[0]
// seed additional posts to test defaultLimit (5)
await payload.create({
collection: postsSlug,
data: {
title: 'a',
category: category.id,
},
})
await payload.create({
collection: postsSlug,
data: {
title: 'b',
category: category.id,
},
})
await payload.create({
collection: postsSlug,
data: {
title: 'z',
category: category.id,
},
})
await navigateToDoc(page, categoriesURL)
const joinField = page.locator('#field-relatedPosts.field-type.join')
await expect(joinField.locator('.row-1 > .cell-title')).toContainText('z')
await expect(joinField.locator('.paginator > .clickable-arrow--right')).toBeVisible()
const rows = joinField.locator('.relationship-table tbody tr')
await expect(rows).toHaveCount(5)
})
test('should render join field for hidden posts', async () => {
await navigateToDoc(page, categoriesURL)
const joinField = page.locator('#field-hiddenPosts.field-type.join')
await expect(joinField).toBeVisible()
await expect(joinField.locator('.relationship-table table')).toBeVisible()
const columns = joinField.locator('.relationship-table tbody tr')
await expect(columns).toHaveCount(1)
const button = joinField.locator('button.doc-drawer__toggler.relationship-table__add-new')
await expect(button).toBeVisible()
await button.click()
const drawer = page.locator('[id^=doc-drawer_hidden-posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test Hidden Post')
await drawer.locator('button[id="action-save"]').click()
await expect(joinField.locator('.relationship-table tbody tr.row-2')).toBeVisible()
})
test('should render the create page and create doc with the join field', async () => {
await page.goto(categoriesURL.create)
const nameField = page.locator('#field-name')
await expect(nameField).toBeVisible()
// assert that the join field is visible and is not stuck in a loading state
await expect(page.locator('#field-relatedPosts')).toContainText('No Posts found.')
await expect(page.locator('#field-relatedPosts')).not.toContainText('loading')
// assert that the create new button is not visible
await expect(page.locator('#field-relatedPosts > .relationship-table__add-new')).toBeHidden()
// assert that the admin.description is visible
await expect(page.locator('.field-description-hasManyPosts')).toHaveText('Static Description')
//assert that the admin.components.Description is visible
await expect(page.locator('.field-description-relatedPosts')).toHaveText(
'Component description: relatedPosts',
)
await nameField.fill('test category')
await saveDocAndAssert(page)
})
test('should render collection type in first column of relationship table', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-relatedPosts.field-type.join')
await expect(joinField).toBeVisible()
const text = joinField.locator('thead tr th#heading-collection:first-child')
await expect(text).toHaveText('Type')
const cells = joinField.locator('.relationship-table tbody tr td:first-child .pill__label')
const count = await cells.count()
for (let i = 0; i < count; i++) {
const element = cells.nth(i)
// Perform actions on each element
await expect(element).toBeVisible()
await expect(element).toHaveText('Post')
}
})
test('should render drawer toggler without document link in second column of relationship table', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-relatedPosts.field-type.join')
await expect(joinField).toBeVisible()
const actionColumn = joinField.locator('tbody tr td:nth-child(2)').first()
const toggler = actionColumn.locator('button.doc-drawer__toggler')
await expect(toggler).toBeVisible()
const link = actionColumn.locator('a')
await expect(link).toBeHidden()
await reorderColumns(page, {
togglerSelector: '.relationship-table__toggle-columns',
columnContainerSelector: '.relationship-table__columns',
fromColumn: 'Category',
toColumn: 'Title',
})
const newActionColumn = joinField.locator('tbody tr td:nth-child(2)').first()
const newToggler = newActionColumn.locator('button.doc-drawer__toggler')
await expect(newToggler).toBeVisible()
const newLink = newActionColumn.locator('a')
await expect(newLink).toBeHidden()
// put columns back in original order for the next test
await reorderColumns(page, {
togglerSelector: '.relationship-table__toggle-columns',
columnContainerSelector: '.relationship-table__columns',
fromColumn: 'Title',
toColumn: 'Category',
})
})
test('should sort relationship table by clicking on column headers', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-group__relatedPosts.field-type.join')
await expect(joinField).toBeVisible()
const titleColumn = joinField.locator('thead tr th#heading-title')
const titleAscButton = titleColumn.locator('button.sort-column__asc')
await expect(titleAscButton).toBeVisible()
await titleAscButton.click()
await expect(joinField.locator('tbody .row-1')).toContainText('Test Post 1')
const titleDescButton = titleColumn.locator('button.sort-column__desc')
await expect(titleDescButton).toBeVisible()
await titleDescButton.click()
await expect(joinField.locator('tbody .row-1')).toContainText('Test Post 3')
})
test('should display relationship table with columns from admin.defaultColumns', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-group__relatedPosts.field-type.join')
const thead = joinField.locator('.relationship-table thead')
await expect(thead).toContainText('ID')
await expect(thead).toContainText('Created At')
await expect(thead).toContainText('Title')
const innerText = await thead.innerText()
// expect the order of columns to be 'ID', 'Created At', 'Title'
// eslint-disable-next-line payload/no-flaky-assertions
expect(innerText.indexOf('ID')).toBeLessThan(innerText.indexOf('Created At'))
// eslint-disable-next-line payload/no-flaky-assertions
expect(innerText.indexOf('Created At')).toBeLessThan(innerText.indexOf('Title'))
})
test('should update relationship table when new document is created', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-relatedPosts.field-type.join')
await expect(joinField).toBeVisible()
const addButton = joinField.locator('.relationship-table__actions button.doc-drawer__toggler', {
hasText: exactText('Add new'),
})
await expect(addButton).toBeVisible()
await addButton.click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const categoryField = drawer.locator('#field-category')
await expect(categoryField).toBeVisible()
const categoryValue = categoryField.locator('.relationship--single-value__text')
await expect(categoryValue).toHaveText('example')
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test Post 4')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(
joinField.locator('tbody tr td:nth-child(2)', {
hasText: exactText('Test Post 4'),
}),
).toBeVisible()
})
test('should update relationship table when document is updated', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-group__relatedPosts.field-type.join')
await expect(joinField).toBeVisible()
const editButton = joinField.locator(
'tbody tr:first-child td:nth-child(2) button.doc-drawer__toggler',
)
await expect(editButton).toBeVisible()
await editButton.click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test Post 1 Updated')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test Post 1 Updated')
})
test('should update relationship table when document is deleted', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-group__relatedPosts.field-type.join')
await expect(joinField).toBeVisible()
const expectedRows = 3
const rows = joinField.locator('.relationship-table tbody tr')
await expect(rows).toHaveCount(expectedRows)
const editButton = joinField.locator(
'tbody tr:first-child td:nth-child(2) button.doc-drawer__toggler',
)
await expect(editButton).toBeVisible()
await editButton.click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const popupButton = drawer.locator('button.popup-button')
await expect(popupButton).toBeVisible()
await popupButton.click()
const deleteButton = drawer.locator('#action-delete')
await expect(deleteButton).toBeVisible()
await deleteButton.click()
const deleteConfirmModal = page.locator('dialog[id^="delete-"][open]')
await expect(deleteConfirmModal).toBeVisible()
const confirmDeleteButton = deleteConfirmModal.locator('button#confirm-delete')
await expect(confirmDeleteButton).toBeVisible()
await confirmDeleteButton.click()
await expect(drawer).toBeHidden()
// We should have one less row than we started with
await expect(rows).toHaveCount(expectedRows - 1)
})
test('should create join collection from polymorphic relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-polymorphic.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-polymorphic')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should create join collection from polymorphic, hasMany relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-polymorphics.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-polymorphics')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should create join collection from polymorphic localized relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-localizedPolymorphic.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-localizedPolymorphic')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should create join collection from polymorphic, hasMany, localized relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-localizedPolymorphics.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-localizedPolymorphics')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should render empty relationship table when creating new document', async () => {
await page.goto(categoriesURL.create)
const joinField = page.locator('#field-relatedPosts.field-type.join')
await expect(joinField).toBeVisible()
await expect(joinField.locator('.relationship-table tbody tr')).toBeHidden()
})
test('should update relationship table when new upload is created', async () => {
await navigateToDoc(page, uploadsURL)
const joinField = page.locator('#field-relatedPosts.field-type.join')
await expect(joinField).toBeVisible()
// TODO: change this to edit the first row in the join table
const addButton = joinField.locator('.relationship-table__actions button.doc-drawer__toggler', {
hasText: exactText('Add new'),
})
await expect(addButton).toBeVisible()
await addButton.click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const uploadField = drawer.locator('#field-upload')
await expect(uploadField).toBeVisible()
const uploadValue = uploadField.locator('.upload-relationship-details img')
await expect(uploadValue).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Edited title for upload')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(
joinField.locator('tbody tr td:nth-child(2)', {
hasText: exactText('Edited title for upload'),
}),
).toBeVisible()
})
test('should render initial rows within relationship table respecting access control', async () => {
await navigateToDoc(page, categoriesJoinRestrictedURL)
const joinField = page.locator('#field-collectionRestrictedJoin.field-type.join')
await expect(joinField).toBeVisible()
await expect(joinField.locator('.relationship-table table')).toBeVisible()
const rows = joinField.locator('.relationship-table tbody tr')
await expect(rows).toHaveCount(1)
await expect(joinField.locator('.cell-canRead')).not.toContainText('false')
})
})