Improves the admin e2e test splitting by grouping them by type with semantic names as opposed to numerically. This will provide much needed clarity to exactly _where_ new admin tests should be written and help to quickly distinguish the areas of failure within the CI overview.
1325 lines
37 KiB
TypeScript
1325 lines
37 KiB
TypeScript
import type { Page } from '@playwright/test'
|
|
import type { TypeWithID } from 'payload'
|
|
|
|
import { expect, test } from '@playwright/test'
|
|
import * as path from 'path'
|
|
import { mapAsync } from 'payload'
|
|
import { wait } from 'payload/shared'
|
|
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 { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
|
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
|
import { postsSlug } from './collections/Posts/index.js'
|
|
|
|
const filename = fileURLToPath(import.meta.url)
|
|
const dirname = path.dirname(filename)
|
|
|
|
const { beforeAll, afterAll, describe } = test
|
|
|
|
const lockedDocumentCollection = 'payload-locked-documents'
|
|
|
|
let page: Page
|
|
let globalUrl: AdminUrlUtil
|
|
let postsUrl: AdminUrlUtil
|
|
let pagesUrl: AdminUrlUtil
|
|
let testsUrl: AdminUrlUtil
|
|
let payload: PayloadTestSDK<Config>
|
|
let serverURL: string
|
|
|
|
describe('Locked Documents', () => {
|
|
beforeAll(async ({ browser }, testInfo) => {
|
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
|
;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname }))
|
|
|
|
globalUrl = new AdminUrlUtil(serverURL, 'menu')
|
|
postsUrl = new AdminUrlUtil(serverURL, 'posts')
|
|
pagesUrl = new AdminUrlUtil(serverURL, 'pages')
|
|
testsUrl = new AdminUrlUtil(serverURL, 'tests')
|
|
|
|
const context = await browser.newContext()
|
|
page = await context.newPage()
|
|
|
|
initPageConsoleErrorCatch(page)
|
|
|
|
await ensureCompilationIsDone({ page, serverURL })
|
|
})
|
|
|
|
describe('disabled locking', () => {
|
|
test('should prevent locking of documents if lockDocuments is false', async () => {
|
|
const { id } = await createPageDoc({})
|
|
|
|
await page.goto(pagesUrl.edit(id))
|
|
|
|
const textInput = page.locator('#field-text')
|
|
await textInput.fill('hello world')
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
const lockedDocs = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: id },
|
|
},
|
|
})
|
|
|
|
expect(lockedDocs.docs.length).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('list view - collections', () => {
|
|
let postDoc
|
|
let anotherPostDoc
|
|
let user2
|
|
let lockedDoc
|
|
let testDoc
|
|
let testLockedDoc
|
|
|
|
beforeAll(async () => {
|
|
postDoc = await createPostDoc({
|
|
text: 'hello',
|
|
})
|
|
|
|
anotherPostDoc = await createPostDoc({
|
|
text: 'another post',
|
|
})
|
|
|
|
testDoc = await createTestDoc({
|
|
text: 'test doc',
|
|
})
|
|
|
|
user2 = await payload.create({
|
|
collection: 'users',
|
|
data: {
|
|
email: 'user2@payloadcms.com',
|
|
password: '1234',
|
|
roles: ['is_user'],
|
|
},
|
|
})
|
|
|
|
lockedDoc = await payload.create({
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
document: {
|
|
relationTo: 'posts',
|
|
value: postDoc.id,
|
|
},
|
|
globalSlug: undefined,
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
|
|
testLockedDoc = await payload.create({
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
document: {
|
|
relationTo: 'tests',
|
|
value: testDoc.id,
|
|
},
|
|
globalSlug: undefined,
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await payload.delete({
|
|
collection: 'users',
|
|
id: user2.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: lockedDoc.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: testLockedDoc.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'posts',
|
|
id: postDoc.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'posts',
|
|
id: anotherPostDoc.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'tests',
|
|
id: testDoc.id,
|
|
})
|
|
})
|
|
|
|
test('should show lock icon on document row if locked', async () => {
|
|
await page.goto(postsUrl.list)
|
|
await page.waitForURL(new RegExp(postsUrl.list))
|
|
|
|
await expect(page.locator('.table .row-2 .locked svg')).toBeVisible()
|
|
})
|
|
|
|
test('should not show lock icon on document row if unlocked', async () => {
|
|
await page.goto(postsUrl.list)
|
|
await page.waitForURL(new RegExp(postsUrl.list))
|
|
|
|
await expect(page.locator('.table .row-3 .checkbox-input__input')).toBeVisible()
|
|
})
|
|
|
|
test('should not show lock icon on document if expired', async () => {
|
|
await page.goto(testsUrl.list)
|
|
await page.waitForURL(new RegExp(testsUrl.list))
|
|
|
|
// Need to wait for lock duration to expire (lockDuration: 5 seconds)
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(5000)
|
|
|
|
await page.reload()
|
|
|
|
await expect(page.locator('.table .row-1 .checkbox-input__input')).toBeVisible()
|
|
})
|
|
|
|
test('should not show lock icon on document row if locked by current user', async () => {
|
|
await page.goto(postsUrl.edit(anotherPostDoc.id))
|
|
await page.waitForURL(postsUrl.edit(anotherPostDoc.id))
|
|
|
|
const textInput = page.locator('#field-text')
|
|
await textInput.fill('testing')
|
|
|
|
await page.reload()
|
|
|
|
await page.goto(postsUrl.list)
|
|
await page.waitForURL(new RegExp(postsUrl.list))
|
|
|
|
await expect(page.locator('.table .row-1 .checkbox-input__input')).toBeVisible()
|
|
})
|
|
|
|
test('should only allow bulk delete on unlocked documents on current page', async () => {
|
|
await page.goto(postsUrl.list)
|
|
await page.locator('input#select-all').check()
|
|
await page.locator('.delete-documents__toggle').click()
|
|
await expect(page.locator('.delete-documents__content p')).toHaveText(
|
|
'You are about to delete 2 Posts',
|
|
)
|
|
})
|
|
|
|
test('should only allow bulk delete on unlocked documents on all pages', async () => {
|
|
await mapAsync([...Array(9)], async () => {
|
|
await createPostDoc({
|
|
text: 'Ready for delete',
|
|
})
|
|
})
|
|
|
|
await page.reload()
|
|
|
|
await page.goto(postsUrl.list)
|
|
await page.waitForURL(new RegExp(postsUrl.list))
|
|
|
|
await page.locator('input#select-all').check()
|
|
await page.locator('.list-selection .list-selection__button').click()
|
|
await page.locator('.delete-documents__toggle').click()
|
|
await page.locator('#confirm-delete').click()
|
|
await expect(page.locator('.cell-_select')).toHaveCount(1)
|
|
})
|
|
|
|
test('should only allow bulk publish on unlocked documents on all pages', async () => {
|
|
await mapAsync([...Array(10)], async () => {
|
|
await createPostDoc({
|
|
text: 'Ready for delete',
|
|
})
|
|
})
|
|
|
|
await page.reload()
|
|
|
|
await page.goto(postsUrl.list)
|
|
await page.waitForURL(new RegExp(postsUrl.list))
|
|
|
|
await page.locator('input#select-all').check()
|
|
await page.locator('.list-selection .list-selection__button').click()
|
|
await page.locator('.publish-many__toggle').click()
|
|
await page.locator('#confirm-publish').click()
|
|
|
|
const paginator = page.locator('.paginator')
|
|
|
|
await paginator.locator('button').nth(1).click()
|
|
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('page=2')
|
|
await expect(page.locator('.row-1 .cell-_status')).toContainText('Draft')
|
|
})
|
|
|
|
test('should only allow bulk unpublish on unlocked documents on all pages', async () => {
|
|
await page.goto(postsUrl.list)
|
|
await page.waitForURL(new RegExp(postsUrl.list))
|
|
|
|
await page.locator('input#select-all').check()
|
|
await page.locator('.list-selection .list-selection__button').click()
|
|
await page.locator('.unpublish-many__toggle').click()
|
|
await page.locator('#confirm-unpublish').click()
|
|
await expect(page.locator('.payload-toast-container .toast-success')).toHaveText(
|
|
'Updated 10 Posts successfully.',
|
|
)
|
|
})
|
|
|
|
test('should only allow bulk edit on unlocked documents on all pages', async () => {
|
|
await page.goto(postsUrl.list)
|
|
await page.waitForURL(new RegExp(postsUrl.list))
|
|
|
|
const bulkText = 'Bulk update title'
|
|
|
|
await page.locator('input#select-all').check()
|
|
await page.locator('.list-selection .list-selection__button').click()
|
|
await page.locator('.edit-many__toggle').click()
|
|
|
|
await page.locator('.field-select .rs__control').click()
|
|
|
|
const textOption = page.locator('.field-select .rs__option', {
|
|
hasText: exactText('Text'),
|
|
})
|
|
|
|
await expect(textOption).toBeVisible()
|
|
|
|
await textOption.click()
|
|
|
|
const textInput = page.locator('#field-text')
|
|
|
|
await expect(textInput).toBeVisible()
|
|
|
|
await textInput.fill(bulkText)
|
|
|
|
await page.locator('.form-submit button[type="submit"].edit-many__publish').click()
|
|
await expect(page.locator('.payload-toast-container .toast-error')).toContainText(
|
|
'Unable to update 1 out of 11 Posts.',
|
|
)
|
|
|
|
await page.locator('.edit-many__header__close').click()
|
|
|
|
await page.reload()
|
|
|
|
await expect(page.locator('.row-1 .cell-text')).toContainText(bulkText)
|
|
await expect(page.locator('.row-2 .cell-text')).toContainText(bulkText)
|
|
|
|
const paginator = page.locator('.paginator')
|
|
|
|
await paginator.locator('button').nth(1).click()
|
|
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('page=2')
|
|
await expect(page.locator('.row-1 .cell-text')).toContainText('hello')
|
|
})
|
|
})
|
|
|
|
describe('document locking / unlocking - one user', () => {
|
|
let postDoc
|
|
let postDocTwo
|
|
let expiredDocOne
|
|
let expiredLockedDocOne
|
|
let expiredDocTwo
|
|
let expiredLockedDocTwo
|
|
let testDoc
|
|
let user2
|
|
|
|
beforeAll(async () => {
|
|
postDoc = await createPostDoc({
|
|
text: 'hello',
|
|
})
|
|
|
|
postDocTwo = await createPostDoc({
|
|
text: 'post doc two',
|
|
})
|
|
|
|
user2 = await payload.create({
|
|
collection: 'users',
|
|
data: {
|
|
email: 'user2@payloadcms.com',
|
|
password: '1234',
|
|
roles: ['is_user'],
|
|
},
|
|
})
|
|
|
|
expiredDocOne = await createTestDoc({
|
|
text: 'expired doc one',
|
|
})
|
|
|
|
expiredLockedDocOne = await payload.create({
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
document: {
|
|
relationTo: 'tests',
|
|
value: expiredDocOne.id,
|
|
},
|
|
globalSlug: undefined,
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
|
|
expiredDocTwo = await createTestDoc({
|
|
text: 'expired doc two',
|
|
})
|
|
|
|
expiredLockedDocTwo = await payload.create({
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
document: {
|
|
relationTo: 'tests',
|
|
value: expiredDocTwo.id,
|
|
},
|
|
globalSlug: undefined,
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
|
|
testDoc = await createTestDoc({ text: 'hello' })
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await payload.delete({
|
|
collection: 'users',
|
|
id: user2.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: expiredLockedDocOne.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: expiredLockedDocTwo.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'posts',
|
|
id: postDoc.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'posts',
|
|
id: postDocTwo.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'tests',
|
|
id: expiredDocOne.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'tests',
|
|
id: expiredDocTwo.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'tests',
|
|
id: testDoc.id,
|
|
})
|
|
})
|
|
|
|
test('should delete all expired locked documents upon initial editing of unlocked document', async () => {
|
|
await page.goto(testsUrl.list)
|
|
await page.waitForURL(new RegExp(testsUrl.list))
|
|
|
|
await expect(page.locator('.table .row-2 .locked svg')).toBeVisible()
|
|
await expect(page.locator('.table .row-3 .locked svg')).toBeVisible()
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(5000)
|
|
|
|
await page.reload()
|
|
|
|
await expect(page.locator('.table .row-2 .checkbox-input__input')).toBeVisible()
|
|
await expect(page.locator('.table .row-3 .checkbox-input__input')).toBeVisible()
|
|
|
|
const lockedTestDocs = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
pagination: false,
|
|
})
|
|
|
|
expect(lockedTestDocs.docs.length).toBe(2)
|
|
|
|
await page.goto(testsUrl.edit(testDoc.id))
|
|
await page.waitForURL(testsUrl.edit(testDoc.id))
|
|
|
|
const textInput = page.locator('#field-text')
|
|
await textInput.fill('some test doc')
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
const lockedDocs = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
pagination: false,
|
|
})
|
|
|
|
expect(lockedDocs.docs.length).toBe(1)
|
|
})
|
|
|
|
test('should lock document upon initial editing of unlocked document', async () => {
|
|
await page.goto(postsUrl.edit(postDoc.id))
|
|
await page.waitForURL(postsUrl.edit(postDoc.id))
|
|
|
|
const textInput = page.locator('#field-text')
|
|
await textInput.fill('hello world')
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
const lockedDocs = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
|
|
expect(lockedDocs.docs.length).toBe(1)
|
|
})
|
|
|
|
test('should unlock document on save / publish', async () => {
|
|
await page.goto(postsUrl.edit(postDoc.id))
|
|
await page.waitForURL(postsUrl.edit(postDoc.id))
|
|
|
|
const textInput = page.locator('#field-text')
|
|
await textInput.fill('hello world')
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
const lockedDocs = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
|
|
expect(lockedDocs.docs.length).toBe(1)
|
|
|
|
await saveDocAndAssert(page)
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
const unlockedDocs = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
|
|
expect(unlockedDocs.docs.length).toBe(0)
|
|
})
|
|
|
|
test('should keep document locked when navigating to other tabs i.e. api', async () => {
|
|
await page.goto(postsUrl.edit(postDoc.id))
|
|
await page.waitForURL(postsUrl.edit(postDoc.id))
|
|
|
|
const textInput = page.locator('#field-text')
|
|
await textInput.fill('testing tab navigation...')
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
const lockedDocs = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
|
|
expect(lockedDocs.docs.length).toBe(1)
|
|
|
|
await page.locator('li[aria-label="API"] a').click()
|
|
|
|
// Locate the modal container
|
|
const modalContainer = page.locator('.payload__modal-container')
|
|
await expect(modalContainer).toBeVisible()
|
|
|
|
// Click the "Leave anyway" button
|
|
await page.locator('.leave-without-saving__controls .btn--style-primary').click()
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
const unlockedDocs = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
|
|
expect(unlockedDocs.docs.length).toBe(1)
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
})
|
|
|
|
test('should unlock document on navigate away', async () => {
|
|
await page.goto(postsUrl.edit(postDocTwo.id))
|
|
await page.waitForURL(postsUrl.edit(postDocTwo.id))
|
|
|
|
const textInput = page.locator('#field-text')
|
|
await textInput.fill('hello world')
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(1000)
|
|
|
|
const lockedDocs = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDocTwo.id },
|
|
},
|
|
})
|
|
|
|
expect(lockedDocs.docs.length).toBe(1)
|
|
|
|
await page.locator('header.app-header a[href="/admin/collections/posts"]').click()
|
|
|
|
// Locate the modal container
|
|
const modalContainer = page.locator('.payload__modal-container')
|
|
await expect(modalContainer).toBeVisible()
|
|
|
|
// Click the "Leave anyway" button
|
|
await page.locator('.leave-without-saving__controls .btn--style-primary').click()
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
expect(page.url()).toContain(postsUrl.list)
|
|
|
|
const unlockedDocs = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
|
|
expect(unlockedDocs.docs.length).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('document locking - incoming user', () => {
|
|
let postDoc
|
|
let user2
|
|
let lockedDoc
|
|
let expiredTestDoc
|
|
let expiredTestLockedDoc
|
|
|
|
beforeAll(async () => {
|
|
postDoc = await createPostDoc({
|
|
text: 'new post doc',
|
|
})
|
|
|
|
expiredTestDoc = await createTestDoc({
|
|
text: 'expired doc',
|
|
})
|
|
|
|
user2 = await payload.create({
|
|
collection: 'users',
|
|
data: {
|
|
email: 'user2@payloadcms.com',
|
|
password: '1234',
|
|
roles: ['is_user'],
|
|
},
|
|
})
|
|
|
|
lockedDoc = await payload.create({
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
document: {
|
|
relationTo: 'posts',
|
|
value: postDoc.id,
|
|
},
|
|
globalSlug: undefined,
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
|
|
expiredTestLockedDoc = await payload.create({
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
document: {
|
|
relationTo: 'tests',
|
|
value: expiredTestDoc.id,
|
|
},
|
|
globalSlug: undefined,
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await payload.delete({
|
|
collection: 'users',
|
|
id: user2.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: lockedDoc.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: expiredTestLockedDoc.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'posts',
|
|
id: postDoc.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'tests',
|
|
id: expiredTestDoc.id,
|
|
})
|
|
})
|
|
|
|
test('should show Document Locked modal for incoming user when entering locked document', async () => {
|
|
await page.goto(postsUrl.list)
|
|
await page.waitForURL(new RegExp(postsUrl.list))
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
await page.goto(postsUrl.edit(postDoc.id))
|
|
await page.waitForURL(postsUrl.edit(postDoc.id))
|
|
|
|
const modalContainer = page.locator('.payload__modal-container')
|
|
await expect(modalContainer).toBeVisible()
|
|
|
|
await page.locator('#document-locked-go-back').click()
|
|
|
|
// should go back to collection list view
|
|
expect(page.url()).toContain(postsUrl.list)
|
|
})
|
|
|
|
test('should not show Document Locked modal for incoming user when entering expired locked document', async () => {
|
|
await page.goto(testsUrl.list)
|
|
await page.waitForURL(new RegExp(testsUrl.list))
|
|
|
|
// Need to wait for lock duration to expire (lockDuration: 5 seconds)
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(5000)
|
|
|
|
await page.reload()
|
|
|
|
await page.goto(testsUrl.edit(expiredTestDoc.id))
|
|
await page.waitForURL(testsUrl.edit(expiredTestDoc.id))
|
|
|
|
const modalContainer = page.locator('.payload__modal-container')
|
|
await expect(modalContainer).toBeHidden()
|
|
})
|
|
|
|
test('should show fields in read-only if incoming user views locked doc in read-only mode', async () => {
|
|
await page.goto(postsUrl.edit(postDoc.id))
|
|
await page.waitForURL(postsUrl.edit(postDoc.id))
|
|
|
|
const modalContainer = page.locator('.payload__modal-container')
|
|
await expect(modalContainer).toBeVisible()
|
|
|
|
// Click read-only button to view doc in read-only mode
|
|
await page.locator('#document-locked-view-read-only').click()
|
|
|
|
// save buttons should be readOnly / disabled
|
|
await expect(page.locator('#action-save-draft')).toBeDisabled()
|
|
await expect(page.locator('#action-save')).toBeDisabled()
|
|
await expect(page.locator('.doc-controls__dots')).toBeHidden()
|
|
|
|
// fields should be readOnly / disabled
|
|
await expect(page.locator('#field-text')).toBeDisabled()
|
|
})
|
|
})
|
|
|
|
describe('document take over - modal - incoming user', () => {
|
|
let postDoc
|
|
let user2
|
|
let lockedDoc
|
|
|
|
beforeAll(async () => {
|
|
postDoc = await createPostDoc({
|
|
text: 'hello',
|
|
})
|
|
|
|
user2 = await payload.create({
|
|
collection: 'users',
|
|
data: {
|
|
email: 'user2@payloadcms.com',
|
|
password: '1234',
|
|
roles: ['is_user'],
|
|
},
|
|
})
|
|
|
|
lockedDoc = await payload.create({
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
document: {
|
|
relationTo: 'posts',
|
|
value: postDoc.id,
|
|
},
|
|
globalSlug: undefined,
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await payload.delete({
|
|
collection: 'users',
|
|
id: user2.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: lockedDoc.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'posts',
|
|
id: postDoc.id,
|
|
})
|
|
})
|
|
|
|
test('should update user data if incoming user takes over from document modal', async () => {
|
|
await page.goto(postsUrl.edit(postDoc.id))
|
|
await page.waitForURL(postsUrl.edit(postDoc.id))
|
|
|
|
const modalContainer = page.locator('.payload__modal-container')
|
|
await expect(modalContainer).toBeVisible()
|
|
|
|
// Click take-over button to take over editing rights of locked doc
|
|
await page.locator('#document-locked-take-over').click()
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(1000)
|
|
|
|
const lockedDoc = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
expect(lockedDoc.docs.length).toBe(1)
|
|
|
|
const userEmail =
|
|
// eslint-disable-next-line playwright/no-conditional-in-test
|
|
lockedDoc.docs[0].user.value &&
|
|
typeof lockedDoc.docs[0].user.value === 'object' &&
|
|
'email' in lockedDoc.docs[0].user.value &&
|
|
lockedDoc.docs[0].user.value.email
|
|
|
|
expect(userEmail).toEqual('dev@payloadcms.com')
|
|
})
|
|
})
|
|
|
|
describe('document take over - doc - incoming user', () => {
|
|
let postDoc
|
|
let user2
|
|
let lockedDoc
|
|
|
|
beforeAll(async () => {
|
|
postDoc = await createPostDoc({
|
|
text: 'hello',
|
|
})
|
|
|
|
user2 = await payload.create({
|
|
collection: 'users',
|
|
data: {
|
|
email: 'user2@payloadcms.com',
|
|
password: '1234',
|
|
roles: ['is_user'],
|
|
},
|
|
})
|
|
|
|
lockedDoc = await payload.create({
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
document: {
|
|
relationTo: 'posts',
|
|
value: postDoc.id,
|
|
},
|
|
globalSlug: undefined,
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await payload.delete({
|
|
collection: 'users',
|
|
id: user2.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: lockedDoc.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'posts',
|
|
id: postDoc.id,
|
|
})
|
|
})
|
|
|
|
test('should update user data if incoming user takes over from within document', async () => {
|
|
await page.goto(postsUrl.edit(postDoc.id))
|
|
await page.waitForURL(postsUrl.edit(postDoc.id))
|
|
|
|
const modalContainer = page.locator('.payload__modal-container')
|
|
await expect(modalContainer).toBeVisible()
|
|
|
|
// Click read-only button to view doc in read-only mode
|
|
await page.locator('#document-locked-view-read-only').click()
|
|
|
|
await page.locator('#take-over').click()
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
const lockedDoc = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
expect(lockedDoc.docs.length).toBe(1)
|
|
|
|
const userEmail =
|
|
// eslint-disable-next-line playwright/no-conditional-in-test
|
|
lockedDoc.docs[0].user.value &&
|
|
typeof lockedDoc.docs[0].user.value === 'object' &&
|
|
'email' in lockedDoc.docs[0].user.value &&
|
|
lockedDoc.docs[0].user.value.email
|
|
|
|
expect(userEmail).toEqual('dev@payloadcms.com')
|
|
})
|
|
})
|
|
|
|
describe('document locking - previous user', () => {
|
|
let postDoc
|
|
let user2
|
|
|
|
beforeAll(async () => {
|
|
postDoc = await createPostDoc({
|
|
text: 'hello',
|
|
})
|
|
|
|
user2 = await payload.create({
|
|
collection: 'users',
|
|
data: {
|
|
email: 'user2@payloadcms.com',
|
|
password: '1234',
|
|
roles: ['is_user'],
|
|
},
|
|
})
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await payload.delete({
|
|
collection: 'users',
|
|
id: user2.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: 'posts',
|
|
id: postDoc.id,
|
|
})
|
|
})
|
|
test('should show Document Take Over modal for previous user if taken over', async () => {
|
|
await page.goto(postsUrl.edit(postDoc.id))
|
|
await page.waitForURL(postsUrl.edit(postDoc.id))
|
|
|
|
const textInput = page.locator('#field-text')
|
|
await textInput.fill('hello world')
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
// Retrieve document id from payload locks collection
|
|
const lockedDoc = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
// Update payload-locks collection document with different user
|
|
await payload.update({
|
|
id: lockedDoc.docs[0].id,
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(1000)
|
|
|
|
// Try to edit the document again as the "old" user
|
|
await textInput.fill('goodbye')
|
|
|
|
// Wait for Take Over modal to appear
|
|
const modalContainer = page.locator('.payload__modal-container')
|
|
await expect(modalContainer).toBeVisible()
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: lockedDoc.docs[0].id,
|
|
})
|
|
})
|
|
|
|
test('should take previous user back to dashboard on dashboard button click', async () => {
|
|
await page.goto(postsUrl.edit(postDoc.id))
|
|
await page.waitForURL(postsUrl.edit(postDoc.id))
|
|
|
|
const textInput = page.locator('#field-text')
|
|
await textInput.fill('hello world')
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
// Retrieve document id from payload locks collection
|
|
const lockedDoc = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
// Update payload-locks collection document with different user
|
|
await payload.update({
|
|
id: lockedDoc.docs[0].id,
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(1000)
|
|
|
|
// Try to edit the document again as the "old" user
|
|
await textInput.fill('goodbye')
|
|
|
|
// Wait for Take Over modal to appear
|
|
const modalContainer = page.locator('.payload__modal-container')
|
|
await expect(modalContainer).toBeVisible()
|
|
|
|
// Click read-only button to view doc in read-only mode
|
|
await page.locator('#document-take-over-back-to-dashboard').click()
|
|
|
|
expect(page.url()).toContain(postsUrl.admin)
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: lockedDoc.docs[0].id,
|
|
})
|
|
})
|
|
|
|
test('should show fields in read-only if previous user views doc in read-only mode', async () => {
|
|
await page.goto(postsUrl.edit(postDoc.id))
|
|
await page.waitForURL(postsUrl.edit(postDoc.id))
|
|
|
|
const textInput = page.locator('#field-text')
|
|
await textInput.fill('hello world')
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
// Retrieve document id from payload locks collection
|
|
const lockedDoc = await payload.find({
|
|
collection: lockedDocumentCollection,
|
|
limit: 1,
|
|
pagination: false,
|
|
where: {
|
|
'document.value': { equals: postDoc.id },
|
|
},
|
|
})
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
// Update payload-locks collection document with different user
|
|
await payload.update({
|
|
id: lockedDoc.docs[0].id,
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
// Try to edit the document again as the "old" user
|
|
await textInput.fill('goodbye')
|
|
|
|
// Wait for Take Over modal to appear
|
|
const modalContainer = page.locator('.payload__modal-container')
|
|
await expect(modalContainer).toBeVisible()
|
|
|
|
// Click read-only button to view doc in read-only mode
|
|
await page.locator('#document-take-over-view-read-only').click()
|
|
|
|
// save buttons should be readOnly / disabled
|
|
await expect(page.locator('#action-save-draft')).toBeDisabled()
|
|
await expect(page.locator('#action-save')).toBeDisabled()
|
|
|
|
// fields should be readOnly / disabled
|
|
await expect(page.locator('#field-text')).toBeDisabled()
|
|
})
|
|
})
|
|
|
|
describe('dashboard - globals', () => {
|
|
let user2
|
|
let lockedMenuGlobal
|
|
let lockedAdminGlobal
|
|
|
|
beforeAll(async () => {
|
|
user2 = await payload.create({
|
|
collection: 'users',
|
|
data: {
|
|
email: 'user2@payloadcms.com',
|
|
password: '1234',
|
|
roles: ['is_user'],
|
|
},
|
|
})
|
|
|
|
lockedAdminGlobal = await payload.create({
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
document: undefined,
|
|
globalSlug: 'admin',
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
|
|
lockedMenuGlobal = await payload.create({
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
document: undefined,
|
|
globalSlug: 'menu',
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await payload.delete({
|
|
collection: 'users',
|
|
id: user2.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: lockedAdminGlobal.id,
|
|
})
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: lockedMenuGlobal.id,
|
|
})
|
|
})
|
|
|
|
test('should show lock on document card in dashboard view if locked', async () => {
|
|
await page.goto(postsUrl.admin)
|
|
await page.waitForURL(new RegExp(postsUrl.admin))
|
|
|
|
await expect(page.locator('.dashboard__card-list #card-menu .locked svg')).toBeVisible()
|
|
})
|
|
|
|
test('should not show lock on document card in dashboard view if unlocked', async () => {
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: lockedMenuGlobal.id,
|
|
})
|
|
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(500)
|
|
|
|
await page.goto(postsUrl.admin)
|
|
await page.waitForURL(new RegExp(postsUrl.admin))
|
|
|
|
await expect(page.locator('.dashboard__card-list #card-menu .locked')).toBeHidden()
|
|
})
|
|
|
|
test('should not show lock on document card in dashboard view if locked by current user', async () => {
|
|
await page.goto(globalUrl.global('menu'))
|
|
await page.waitForURL(globalUrl.global('menu'))
|
|
|
|
const textInput = page.locator('#field-globalText')
|
|
await textInput.fill('this is a global menu text field')
|
|
|
|
await page.reload()
|
|
|
|
await page.goto(postsUrl.admin)
|
|
await page.waitForURL(new RegExp(postsUrl.admin))
|
|
|
|
await expect(page.locator('.dashboard__card-list #card-menu .locked')).toBeHidden()
|
|
})
|
|
|
|
test('should not show lock on document card in dashboard view if lock expired', async () => {
|
|
await page.goto(postsUrl.admin)
|
|
await page.waitForURL(new RegExp(postsUrl.admin))
|
|
|
|
await expect(page.locator('.dashboard__card-list #card-admin .locked svg')).toBeVisible()
|
|
|
|
// Need to wait for lock duration to expire (lockDuration: 10 seconds)
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(10000)
|
|
|
|
await page.reload()
|
|
|
|
await expect(page.locator('.dashboard__card-list #card-admin .locked')).toBeHidden()
|
|
|
|
await payload.delete({
|
|
collection: lockedDocumentCollection,
|
|
id: lockedAdminGlobal.id,
|
|
})
|
|
})
|
|
|
|
test('should not show Document Locked modal when entering global with an expired lock', async () => {
|
|
await payload.create({
|
|
collection: lockedDocumentCollection,
|
|
data: {
|
|
document: undefined,
|
|
globalSlug: 'admin',
|
|
user: {
|
|
relationTo: 'users',
|
|
value: user2.id,
|
|
},
|
|
},
|
|
})
|
|
|
|
await page.goto(postsUrl.admin)
|
|
await page.waitForURL(new RegExp(postsUrl.admin))
|
|
|
|
await expect(page.locator('.dashboard__card-list #card-admin .locked svg')).toBeVisible()
|
|
|
|
// Need to wait for lock duration to expire (lockDuration: 10 seconds)
|
|
// eslint-disable-next-line payload/no-wait-function
|
|
await wait(10000)
|
|
|
|
await page.reload()
|
|
|
|
await expect(page.locator('.dashboard__card-list #card-admin .locked')).toBeHidden()
|
|
|
|
await page.locator('.card-admin a').click()
|
|
|
|
const modalContainer = page.locator('.payload__modal-container')
|
|
await expect(modalContainer).toBeHidden()
|
|
})
|
|
})
|
|
})
|
|
|
|
async function createPageDoc(data: any): Promise<Record<string, unknown> & TypeWithID> {
|
|
return payload.create({
|
|
collection: 'pages',
|
|
data,
|
|
}) as unknown as Promise<Record<string, unknown> & TypeWithID>
|
|
}
|
|
|
|
async function createPostDoc(data: any): Promise<Record<string, unknown> & TypeWithID> {
|
|
return payload.create({
|
|
collection: 'posts',
|
|
data,
|
|
}) as unknown as Promise<Record<string, unknown> & TypeWithID>
|
|
}
|
|
|
|
async function createTestDoc(data: any): Promise<Record<string, unknown> & TypeWithID> {
|
|
return payload.create({
|
|
collection: 'tests',
|
|
data,
|
|
}) as unknown as Promise<Record<string, unknown> & TypeWithID>
|
|
}
|
|
|
|
async function deleteAllPosts() {
|
|
await payload.delete({ collection: postsSlug, where: { id: { exists: true } } })
|
|
}
|