fix: passing versions e2e (#5521)
This commit is contained in:
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -230,7 +230,7 @@ jobs:
|
||||
# - plugin-seo
|
||||
# - refresh-permissions
|
||||
# - uploads
|
||||
# - versions
|
||||
- versions
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { Where } from 'payload/types'
|
||||
import { ReactSelect } from '@payloadcms/ui/elements/ReactSelect'
|
||||
import { fieldBaseClass } from '@payloadcms/ui/fields/shared'
|
||||
import { useConfig } from '@payloadcms/ui/providers/Config'
|
||||
import { useDocumentInfo } from '@payloadcms/ui/providers/DocumentInfo'
|
||||
import { useTranslation } from '@payloadcms/ui/providers/Translation'
|
||||
import { formatDate } from '@payloadcms/ui/utilities/formatDate'
|
||||
import qs from 'qs'
|
||||
@@ -28,6 +29,7 @@ export const SelectComparison: React.FC<Props> = (props) => {
|
||||
admin: { dateFormat },
|
||||
} = useConfig()
|
||||
|
||||
const { docConfig } = useDocumentInfo()
|
||||
const [options, setOptions] = useState(baseOptions)
|
||||
const [lastLoadedPage, setLastLoadedPage] = useState(1)
|
||||
const [errorLoading, setErrorLoading] = useState('')
|
||||
@@ -51,15 +53,18 @@ export const SelectComparison: React.FC<Props> = (props) => {
|
||||
not_equals: versionID,
|
||||
},
|
||||
},
|
||||
{
|
||||
latest: {
|
||||
not_equals: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
if (docConfig.versions?.drafts) {
|
||||
query.where.and.push({
|
||||
latest: {
|
||||
not_equals: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (parentID) {
|
||||
query.where.and.push({
|
||||
parent: {
|
||||
@@ -79,7 +84,6 @@ export const SelectComparison: React.FC<Props> = (props) => {
|
||||
|
||||
if (response.ok) {
|
||||
const data: PaginatedDocs = await response.json()
|
||||
|
||||
if (data.docs.length > 0) {
|
||||
setOptions((existingOptions) => [
|
||||
...existingOptions,
|
||||
@@ -98,7 +102,7 @@ export const SelectComparison: React.FC<Props> = (props) => {
|
||||
setErrorLoading(t('error:unspecific'))
|
||||
}
|
||||
},
|
||||
[dateFormat, baseURL, parentID, versionID, t, i18n],
|
||||
[dateFormat, baseURL, parentID, versionID, t, i18n, docConfig.versions?.drafts],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -15,52 +15,62 @@ import { IDCell } from './cells/ID/index.js'
|
||||
|
||||
export const buildVersionColumns = ({
|
||||
collectionConfig,
|
||||
config,
|
||||
docID,
|
||||
globalConfig,
|
||||
i18n: { t },
|
||||
i18n,
|
||||
}: {
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
config: SanitizedConfig
|
||||
docID?: number | string
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
i18n: I18n
|
||||
}): Column[] => [
|
||||
{
|
||||
name: '',
|
||||
Label: '',
|
||||
accessor: 'updatedAt',
|
||||
active: true,
|
||||
components: {
|
||||
Cell: (
|
||||
<CreatedAtCell
|
||||
collectionSlug={collectionConfig?.slug}
|
||||
docID={docID}
|
||||
globalSlug={globalConfig?.slug}
|
||||
/>
|
||||
),
|
||||
Heading: <SortColumn Label={t('general:updatedAt')} name="updatedAt" />,
|
||||
}): Column[] => {
|
||||
const entityConfig = collectionConfig || globalConfig
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: '',
|
||||
Label: '',
|
||||
accessor: 'updatedAt',
|
||||
active: true,
|
||||
components: {
|
||||
Cell: (
|
||||
<CreatedAtCell
|
||||
collectionSlug={collectionConfig?.slug}
|
||||
docID={docID}
|
||||
globalSlug={globalConfig?.slug}
|
||||
/>
|
||||
),
|
||||
Heading: <SortColumn Label={t('general:updatedAt')} name="updatedAt" />,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
Label: '',
|
||||
accessor: 'id',
|
||||
active: true,
|
||||
components: {
|
||||
Cell: <IDCell />,
|
||||
Heading: <SortColumn Label={t('version:versionID')} disable name="id" />,
|
||||
{
|
||||
name: '',
|
||||
Label: '',
|
||||
accessor: 'id',
|
||||
active: true,
|
||||
components: {
|
||||
Cell: <IDCell />,
|
||||
Heading: <SortColumn Label={t('version:versionID')} disable name="id" />,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
Label: '',
|
||||
accessor: 'autosave',
|
||||
active: true,
|
||||
components: {
|
||||
Cell: <AutosaveCell />,
|
||||
Heading: <SortColumn Label={t('version:type')} disable name="autosave" />,
|
||||
},
|
||||
},
|
||||
]
|
||||
]
|
||||
|
||||
if (
|
||||
entityConfig?.versions?.drafts ||
|
||||
(entityConfig?.versions?.drafts && entityConfig.versions.drafts?.autosave)
|
||||
) {
|
||||
columns.push({
|
||||
name: '',
|
||||
Label: '',
|
||||
accessor: '_status',
|
||||
active: true,
|
||||
components: {
|
||||
Cell: <AutosaveCell />,
|
||||
Heading: <SortColumn Label={t('version:type')} disable name="autosave" />,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return columns
|
||||
}
|
||||
|
||||
@@ -8,15 +8,11 @@ export const AutosaveCell: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { rowData } = useTableCell()
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{rowData?.autosave && (
|
||||
<React.Fragment>
|
||||
<Pill>
|
||||
Autosave
|
||||
{t('version:autosave')}
|
||||
</Pill>
|
||||
<Pill>{t('version:autosave')}</Pill>
|
||||
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
@@ -11,6 +11,7 @@ const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.
|
||||
|
||||
export const DocumentTabLink: React.FC<{
|
||||
adminRoute: SanitizedConfig['routes']['admin']
|
||||
ariaLabel?: string
|
||||
baseClass: string
|
||||
children?: React.ReactNode
|
||||
href: string
|
||||
@@ -19,6 +20,7 @@ export const DocumentTabLink: React.FC<{
|
||||
newTab?: boolean
|
||||
}> = ({
|
||||
adminRoute,
|
||||
ariaLabel,
|
||||
baseClass,
|
||||
children,
|
||||
href: hrefFromProps,
|
||||
@@ -53,7 +55,10 @@ export const DocumentTabLink: React.FC<{
|
||||
isActiveFromProps
|
||||
|
||||
return (
|
||||
<li className={[baseClass, isActive && `${baseClass}--active`].filter(Boolean).join(' ')}>
|
||||
<li
|
||||
aria-label={ariaLabel}
|
||||
className={[baseClass, isActive && `${baseClass}--active`].filter(Boolean).join(' ')}
|
||||
>
|
||||
<Link
|
||||
className={`${baseClass}__link`}
|
||||
href={!isActive || href !== pathname ? hrefWithLocale : ''}
|
||||
|
||||
@@ -56,6 +56,7 @@ export const DocumentTab: React.FC<DocumentTabProps & DocumentTabConfig> = (prop
|
||||
return (
|
||||
<DocumentTabLink
|
||||
adminRoute={routes.admin}
|
||||
ariaLabel={labelToRender}
|
||||
baseClass={baseClass}
|
||||
href={href}
|
||||
isActive={isActive}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
||||
import { useLocale } from '../../providers/Locale/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
|
||||
const DefaultPublishButton: React.FC = () => {
|
||||
export const DefaultPublishButton: React.FC<{ label?: string }> = ({ label: labelProp }) => {
|
||||
const { code } = useLocale()
|
||||
const { id, collectionSlug, globalSlug, publishedDoc, unpublishedVersions } = useDocumentInfo()
|
||||
const [hasPublishPermission, setHasPublishPermission] = React.useState(false)
|
||||
@@ -22,7 +22,7 @@ const DefaultPublishButton: React.FC = () => {
|
||||
serverURL,
|
||||
} = useConfig()
|
||||
const { t } = useTranslation()
|
||||
const label = t('version:publishChanges')
|
||||
const label = labelProp || t('version:publishChanges')
|
||||
|
||||
const hasNewerVersions = unpublishedVersions?.totalDocs > 0
|
||||
const canPublish = hasPublishPermission && (modified || hasNewerVersions || !publishedDoc)
|
||||
|
||||
@@ -8,10 +8,10 @@ import { useHotkey } from '../../hooks/useHotkey.js'
|
||||
import { useEditDepth } from '../../providers/EditDepth/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
|
||||
const DefaultSaveButton: React.FC = () => {
|
||||
export const DefaultSaveButton: React.FC<{ label?: string }> = ({ label: labelProp }) => {
|
||||
const { t } = useTranslation()
|
||||
const { submit } = useForm()
|
||||
const label = t('general:save')
|
||||
const label = labelProp || t('general:save')
|
||||
const ref = useRef<HTMLButtonElement>(null)
|
||||
const editDepth = useEditDepth()
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import type { Payload } from 'payload/types'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import path from 'path'
|
||||
import { wait } from 'payload/utilities'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { globalSlug } from '../admin/slugs.js'
|
||||
@@ -42,6 +41,7 @@ import {
|
||||
} from '../helpers.js'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { initPayloadE2E } from '../helpers/initPayloadE2E.js'
|
||||
import { POLL_TOPASS_TIMEOUT } from '../playwright.config.js'
|
||||
import config from './config.js'
|
||||
import { clearAndSeedEverything } from './seed.js'
|
||||
import { titleToDelete } from './shared.js'
|
||||
@@ -62,6 +62,22 @@ const { beforeAll, beforeEach, describe } = test
|
||||
|
||||
let payload: Payload
|
||||
|
||||
const waitForAutoSaveToComplete = async (page: Page) => {
|
||||
await expect(async () => {
|
||||
await expect(page.locator('.autosave:has-text("Saving...")')).toBeVisible()
|
||||
}).toPass({
|
||||
timeout: 45000,
|
||||
})
|
||||
|
||||
await expect(async () => {
|
||||
await expect(
|
||||
page.locator('.autosave:has-text("Last saved less than a minute ago")'),
|
||||
).toBeVisible()
|
||||
}).toPass({
|
||||
timeout: 45000,
|
||||
})
|
||||
}
|
||||
|
||||
describe('versions', () => {
|
||||
let page: Page
|
||||
let url: AdminUrlUtil
|
||||
@@ -294,16 +310,11 @@ describe('versions', () => {
|
||||
const url = new AdminUrlUtil(serverURL, autoSaveGlobalSlug)
|
||||
// fill out global title and wait for autosave
|
||||
await page.goto(url.global(autoSaveGlobalSlug))
|
||||
await wait(500)
|
||||
await page.waitForURL(`**/${autoSaveGlobalSlug}`)
|
||||
const titleField = page.locator('#field-title')
|
||||
await expect(titleField).toBeVisible()
|
||||
|
||||
await titleField.fill('global title')
|
||||
await wait(500)
|
||||
await expect(page.locator('.autosave:has-text("Saving...")')).toBeVisible()
|
||||
await expect(
|
||||
page.locator('.autosave:has-text("Last saved less than a minute ago")'),
|
||||
).toBeVisible()
|
||||
await waitForAutoSaveToComplete(page)
|
||||
await expect(titleField).toHaveValue('global title')
|
||||
|
||||
// refresh the page and ensure value autosaved
|
||||
@@ -314,39 +325,37 @@ describe('versions', () => {
|
||||
test('should retain localized data during autosave', async () => {
|
||||
const en = 'en'
|
||||
const es = 'es'
|
||||
const title = 'english title'
|
||||
const englishTitle = 'english title'
|
||||
const spanishTitle = 'spanish title'
|
||||
const description = 'description'
|
||||
const newDescription = 'new description'
|
||||
|
||||
await page.goto(autosaveURL.create)
|
||||
await expect(page.locator('.id-label')).toBeVisible()
|
||||
await wait(500)
|
||||
await page.waitForURL(`**/${autosaveURL.create}`)
|
||||
await page.waitForURL(/\/(?!create$)[\w-]+$/)
|
||||
const titleField = page.locator('#field-title')
|
||||
const descriptionField = page.locator('#field-description')
|
||||
|
||||
// fill out en doc
|
||||
await titleField.fill(title)
|
||||
await descriptionField.fill(description)
|
||||
await wait(500)
|
||||
await titleField.fill(englishTitle)
|
||||
await descriptionField.fill('description')
|
||||
await waitForAutoSaveToComplete(page)
|
||||
|
||||
// change locale to spanish
|
||||
await changeLocale(page, es)
|
||||
// set localized title field
|
||||
await titleField.fill(spanishTitle)
|
||||
await wait(500)
|
||||
await waitForAutoSaveToComplete(page)
|
||||
|
||||
// change locale back to en
|
||||
await changeLocale(page, en)
|
||||
// verify en loads its own title
|
||||
await expect(titleField).toHaveValue(title)
|
||||
await expect(titleField).toHaveValue(englishTitle)
|
||||
// change non-localized description field
|
||||
await descriptionField.fill(newDescription)
|
||||
await wait(500)
|
||||
await waitForAutoSaveToComplete(page)
|
||||
|
||||
// change locale to spanish
|
||||
await changeLocale(page, es)
|
||||
await wait(500)
|
||||
|
||||
// reload page in spanish
|
||||
// title should not be english title
|
||||
@@ -362,7 +371,7 @@ describe('versions', () => {
|
||||
const englishTitle = 'english title'
|
||||
|
||||
await page.goto(url.create)
|
||||
await wait(500)
|
||||
await page.waitForURL(`**/${url.create}`)
|
||||
|
||||
// fill out doc in english
|
||||
await page.locator('#field-title').fill(englishTitle)
|
||||
@@ -376,6 +385,15 @@ describe('versions', () => {
|
||||
await page.locator('#field-title').fill(spanishTitle)
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
// wait for the page to load with the new version
|
||||
await expect
|
||||
.poll(
|
||||
async () =>
|
||||
await page.locator('.doc-tab[aria-label="Versions"] .doc-tab__count').textContent(),
|
||||
{ timeout: POLL_TOPASS_TIMEOUT },
|
||||
)
|
||||
.toEqual('2')
|
||||
|
||||
// fill out draft content in spanish
|
||||
await page.locator('#field-title').fill(`${spanishTitle}--draft`)
|
||||
await saveDocAndAssert(page, '#action-save-draft')
|
||||
@@ -391,33 +409,40 @@ describe('versions', () => {
|
||||
test('collection - autosave should only update the current document', async () => {
|
||||
// create and save first doc
|
||||
await page.goto(autosaveURL.create)
|
||||
await wait(500)
|
||||
await page.waitForURL(`**/${autosaveURL.create}`)
|
||||
await page.waitForURL(/\/(?!create$)[\w-]+$/)
|
||||
|
||||
await page.locator('#field-title').fill('first post title')
|
||||
await page.locator('#field-description').fill('first post description')
|
||||
await page.locator('#action-save').click()
|
||||
|
||||
// create and save second doc
|
||||
await page.goto(autosaveURL.create)
|
||||
await wait(500)
|
||||
await page.waitForURL(`**/${autosaveURL.create}`)
|
||||
await page.waitForURL(/\/(?!create$)[\w-]+$/)
|
||||
await page.locator('#field-title').fill('second post title')
|
||||
await page.locator('#field-description').fill('second post description')
|
||||
await page.locator('#action-save').click()
|
||||
// publish changes
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
// update second doc and wait for autosave
|
||||
await page.locator('#field-title').fill('updated second post title')
|
||||
await page.locator('#field-description').fill('updated second post description')
|
||||
await wait(500)
|
||||
await waitForAutoSaveToComplete(page)
|
||||
|
||||
// verify that the first doc is unchanged
|
||||
await page.goto(autosaveURL.list)
|
||||
await page.locator('tbody tr .cell-title a').nth(1).click()
|
||||
const secondRowLink = page.locator('tbody tr:nth-child(2) .cell-title a')
|
||||
const docURL = await secondRowLink.getAttribute('href')
|
||||
await secondRowLink.click()
|
||||
await page.waitForURL(`**${docURL}`)
|
||||
await expect(page.locator('#field-title')).toHaveValue('first post title')
|
||||
await expect(page.locator('#field-description')).toHaveValue('first post description')
|
||||
})
|
||||
|
||||
test('should save versions with custom IDs', async () => {
|
||||
await page.goto(customIDURL.create)
|
||||
await wait(500)
|
||||
await page.waitForURL(`**/${customIDURL.create}`)
|
||||
await page.locator('#field-id').fill('custom')
|
||||
await page.locator('#field-title').fill('title')
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import * as React from 'react'
|
||||
|
||||
// In your projects, you can import as follows:
|
||||
// import { CustomPublishButtonProps } from 'payload/types';
|
||||
|
||||
'use client'
|
||||
import type { CustomPublishButtonProps } from 'payload/types'
|
||||
|
||||
import { DefaultPublishButton } from '@payloadcms/ui/elements/Publish'
|
||||
import * as React from 'react'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export const CustomPublishButton: CustomPublishButtonProps = ({ DefaultButton, ...rest }) => {
|
||||
export const CustomPublishButton: CustomPublishButtonProps = () => {
|
||||
return (
|
||||
<div className={classes.customButton}>
|
||||
<DefaultButton {...rest} />
|
||||
<DefaultPublishButton />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": [
|
||||
"./test/_community/config.ts"
|
||||
"./test/versions/config.ts"
|
||||
],
|
||||
"@payloadcms/live-preview": [
|
||||
"./packages/live-preview/src"
|
||||
@@ -160,4 +160,4 @@
|
||||
".next/types/**/*.ts",
|
||||
"scripts/**/*.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user