chore: improve test suites, upgrade jest and playwright, add debug utilities for lexical (#4011)
* feat(richtext-lexical): 'bottom' position value for plugins * feat: TestRecorderFeature * chore: restructuring to seed and clear db before each test * chore: make sure all tests pass * chore: make sure indexes are created in seed.ts - this fixes one erroring test * chore: speed up test runs through db snapshots * chore: support drizzle when resetting db * chore: simplify seeding process, by moving boilerplate db reset / snapshot logic into a wrapper function * chore: add new seeding process to admin test suite * chore(deps): upgrade jest and playwright * chore: make sure mongoose-specific tests are not skipped * chore: fix point test, which was depending on another test (that's bad!) * chore: fix incorrect import * chore: remove unnecessary comments * chore: clearly label lexicalE2E test file as todo * chore: simplify seed logic * chore: move versions test suite to new seed system
This commit is contained in:
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@playwright/test": "1.38.1",
|
||||
"@playwright/test": "1.39.0",
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/jest": "0.2.29",
|
||||
"@swc/register": "0.1.10",
|
||||
@@ -45,7 +45,7 @@
|
||||
"@types/conventional-changelog-core": "^4.2.5",
|
||||
"@types/conventional-changelog-preset-loader": "^2.3.4",
|
||||
"@types/fs-extra": "^11.0.2",
|
||||
"@types/jest": "29.5.4",
|
||||
"@types/jest": "29.5.7",
|
||||
"@types/minimist": "1.2.2",
|
||||
"@types/node": "20.5.7",
|
||||
"@types/prompts": "^2.4.5",
|
||||
@@ -64,6 +64,7 @@
|
||||
"copyfiles": "2.4.1",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "8.6.0",
|
||||
"drizzle-orm": "0.28.5",
|
||||
"express": "4.18.2",
|
||||
"form-data": "3.0.1",
|
||||
"fs-extra": "10.1.0",
|
||||
@@ -73,8 +74,8 @@
|
||||
"graphql-request": "6.1.0",
|
||||
"husky": "^8.0.3",
|
||||
"isomorphic-fetch": "3.0.0",
|
||||
"jest": "29.6.4",
|
||||
"jest-environment-jsdom": "29.6.4",
|
||||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jwt-decode": "3.1.2",
|
||||
"lint-staged": "^14.0.1",
|
||||
"minimist": "1.2.8",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { FeatureProvider } from '../../types'
|
||||
|
||||
import { TestRecorderPlugin } from './plugin'
|
||||
|
||||
export const TestRecorderFeature = (): FeatureProvider => {
|
||||
return {
|
||||
feature: () => {
|
||||
return {
|
||||
plugins: [
|
||||
{
|
||||
Component: TestRecorderPlugin,
|
||||
position: 'bottom',
|
||||
},
|
||||
],
|
||||
props: null,
|
||||
}
|
||||
},
|
||||
key: 'debug-testrecorder',
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
.test-recorder-output {
|
||||
margin: 20px auto 20px auto;
|
||||
width: 100%;
|
||||
}
|
||||
.test-recorder-toolbar {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.test-recorder-button {
|
||||
position: relative;
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
padding: 6px 6px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.4);
|
||||
background-color: #222;
|
||||
color: white;
|
||||
transition: box-shadow 50ms ease-out;
|
||||
}
|
||||
|
||||
.test-recorder-button:active {
|
||||
box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.test-recorder-button + .test-recorder-button {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.test-recorder-button::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
display: block;
|
||||
background-size: contain;
|
||||
filter: invert(1);
|
||||
}
|
||||
#test-recorder-button {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#test-recorder-button-snapshot {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
import type { GridSelection, LexicalEditor, NodeSelection, RangeSelection } from 'lexical'
|
||||
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { $createParagraphNode, $createTextNode, $getRoot } from 'lexical'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
|
||||
import { IS_APPLE } from '../../../../lexical/utils/environment'
|
||||
import './index.scss'
|
||||
|
||||
const copy = (text: null | string) => {
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.value = text || ''
|
||||
textArea.style.position = 'absolute'
|
||||
textArea.style.opacity = '0'
|
||||
document.body?.appendChild(textArea)
|
||||
textArea.focus()
|
||||
textArea.select()
|
||||
try {
|
||||
const result = document.execCommand('copy')
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(result)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
document.body?.removeChild(textArea)
|
||||
}
|
||||
|
||||
const download = (filename: string, text: null | string) => {
|
||||
const a = document.createElement('a')
|
||||
a.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text || ''))
|
||||
a.setAttribute('download', filename)
|
||||
a.style.display = 'none'
|
||||
document.body?.appendChild(a)
|
||||
a.click()
|
||||
document.body?.removeChild(a)
|
||||
}
|
||||
|
||||
const formatStep = (step: Step) => {
|
||||
const formatOneStep = (name: string, value: Step['value']) => {
|
||||
switch (name) {
|
||||
case 'click': {
|
||||
return ` await page.mouse.click(${value.x}, ${value.y});`
|
||||
}
|
||||
case 'press': {
|
||||
return ` await page.keyboard.press('${value}');`
|
||||
}
|
||||
case 'keydown': {
|
||||
return ` await page.keyboard.keydown('${value}');`
|
||||
}
|
||||
case 'keyup': {
|
||||
return ` await page.keyboard.keyup('${value}');`
|
||||
}
|
||||
case 'type': {
|
||||
return ` await page.keyboard.type('${value}');`
|
||||
}
|
||||
case 'selectAll': {
|
||||
return ` await selectAll(page);`
|
||||
}
|
||||
case 'snapshot': {
|
||||
return ` await assertHTMLSnapshot(page);
|
||||
await assertSelection(page, {
|
||||
anchorPath: [${value.anchorPath.toString()}],
|
||||
anchorOffset: ${value.anchorOffset},
|
||||
focusPath: [${value.focusPath.toString()}],
|
||||
focusOffset: ${value.focusOffset},
|
||||
});
|
||||
`
|
||||
}
|
||||
default:
|
||||
return ``
|
||||
}
|
||||
}
|
||||
const formattedStep = formatOneStep(step.name, step.value)
|
||||
switch (step.count) {
|
||||
case 1:
|
||||
return formattedStep
|
||||
case 2:
|
||||
return [formattedStep, formattedStep].join(`\n`)
|
||||
default:
|
||||
return ` await repeat(${step.count}, async () => {
|
||||
${formattedStep}
|
||||
);`
|
||||
}
|
||||
}
|
||||
|
||||
export function isSelectAll(event: KeyboardEvent): boolean {
|
||||
return event.keyCode === 65 && (IS_APPLE ? event.metaKey : event.ctrlKey)
|
||||
}
|
||||
|
||||
// stolen from LexicalSelection-test
|
||||
function sanitizeSelection(selection: Selection) {
|
||||
const { anchorNode, focusNode } = selection
|
||||
let { anchorOffset, focusOffset } = selection
|
||||
if (anchorOffset !== 0) {
|
||||
anchorOffset--
|
||||
}
|
||||
if (focusOffset !== 0) {
|
||||
focusOffset--
|
||||
}
|
||||
return { anchorNode, anchorOffset, focusNode, focusOffset }
|
||||
}
|
||||
|
||||
function getPathFromNodeToEditor(node: Node, rootElement: HTMLElement | null) {
|
||||
let currentNode: Node | null | undefined = node
|
||||
const path = []
|
||||
while (currentNode !== rootElement) {
|
||||
if (currentNode !== null && currentNode !== undefined) {
|
||||
path.unshift(
|
||||
Array.from(currentNode?.parentNode?.childNodes ?? []).indexOf(currentNode as ChildNode),
|
||||
)
|
||||
}
|
||||
currentNode = currentNode?.parentNode
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
const keyPresses = new Set([
|
||||
'Enter',
|
||||
'Backspace',
|
||||
'Delete',
|
||||
'Escape',
|
||||
'ArrowLeft',
|
||||
'ArrowRight',
|
||||
'ArrowUp',
|
||||
'ArrowDown',
|
||||
])
|
||||
|
||||
type Step = {
|
||||
count: number
|
||||
name: string
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any
|
||||
}
|
||||
|
||||
type Steps = Step[]
|
||||
|
||||
function useTestRecorder(editor: LexicalEditor): [JSX.Element, JSX.Element | null] {
|
||||
const [steps, setSteps] = useState<Steps>([])
|
||||
const [isRecording, setIsRecording] = useState(false)
|
||||
const [, setCurrentInnerHTML] = useState('')
|
||||
const [templatedTest, setTemplatedTest] = useState('')
|
||||
const previousSelectionRef = useRef<GridSelection | NodeSelection | RangeSelection | null>(null)
|
||||
const skipNextSelectionChangeRef = useRef(false)
|
||||
const preRef = useRef<HTMLPreElement>(null)
|
||||
|
||||
const getCurrentEditor = useCallback(() => {
|
||||
return editor
|
||||
}, [editor])
|
||||
|
||||
const generateTestContent = useCallback(() => {
|
||||
const rootElement = editor.getRootElement()
|
||||
const browserSelection = window.getSelection()
|
||||
|
||||
if (
|
||||
rootElement == null ||
|
||||
browserSelection == null ||
|
||||
browserSelection.anchorNode == null ||
|
||||
browserSelection.focusNode == null ||
|
||||
!rootElement.contains(browserSelection.anchorNode) ||
|
||||
!rootElement.contains(browserSelection.focusNode)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return `
|
||||
import {
|
||||
initializeE2E,
|
||||
assertHTMLSnapshot,
|
||||
assertSelection,
|
||||
repeat,
|
||||
} from '../utils';
|
||||
import {selectAll} from '../keyboardShortcuts';
|
||||
import { RangeSelection } from 'lexical';
|
||||
import { NodeSelection } from 'lexical';
|
||||
|
||||
describe('Test case', () => {
|
||||
initializeE2E((e2e) => {
|
||||
it('Should pass this test', async () => {
|
||||
const {page} = e2e;
|
||||
|
||||
await page.focus('div[contenteditable="true"]');
|
||||
${steps.map(formatStep).join(`\n`)}
|
||||
});
|
||||
});
|
||||
`
|
||||
}, [editor, steps])
|
||||
|
||||
// just a wrapper around inserting new actions so that we can
|
||||
// coalesce some actions like insertText/moveNativeSelection
|
||||
const pushStep = useCallback(
|
||||
(name: string, value: Step['value']) => {
|
||||
setSteps((currentSteps) => {
|
||||
// trying to group steps
|
||||
const currentIndex = steps.length - 1
|
||||
const lastStep = steps[currentIndex]
|
||||
if (lastStep) {
|
||||
if (lastStep.name === name) {
|
||||
if (name === 'type') {
|
||||
// for typing events we just append the text
|
||||
return [
|
||||
...steps.slice(0, currentIndex),
|
||||
{ ...lastStep, value: lastStep.value + value },
|
||||
]
|
||||
} else {
|
||||
// for other events we bump the counter if their values are the same
|
||||
if (lastStep.value === value) {
|
||||
return [...steps.slice(0, currentIndex), { ...lastStep, count: lastStep.count + 1 }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// could not group, just append a new one
|
||||
return [...currentSteps, { name, count: 1, value }]
|
||||
})
|
||||
},
|
||||
[steps, setSteps],
|
||||
)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (!isRecording) {
|
||||
return
|
||||
}
|
||||
const key = event.key
|
||||
if (isSelectAll(event)) {
|
||||
pushStep('selectAll', '')
|
||||
} else if (keyPresses.has(key)) {
|
||||
pushStep('press', event.key)
|
||||
} else if ([...key].length > 1) {
|
||||
pushStep('keydown', event.key)
|
||||
} else {
|
||||
pushStep('type', event.key)
|
||||
}
|
||||
}
|
||||
|
||||
const onKeyUp = (event: KeyboardEvent) => {
|
||||
if (!isRecording) {
|
||||
return
|
||||
}
|
||||
const key = event.key
|
||||
if (!keyPresses.has(key) && [...key].length > 1) {
|
||||
pushStep('keyup', event.key)
|
||||
}
|
||||
}
|
||||
|
||||
return editor.registerRootListener(
|
||||
(rootElement: HTMLElement | null, prevRootElement: HTMLElement | null) => {
|
||||
if (prevRootElement !== null) {
|
||||
prevRootElement.removeEventListener('keydown', onKeyDown)
|
||||
prevRootElement.removeEventListener('keyup', onKeyUp)
|
||||
}
|
||||
if (rootElement !== null) {
|
||||
rootElement.addEventListener('keydown', onKeyDown)
|
||||
rootElement.addEventListener('keyup', onKeyUp)
|
||||
}
|
||||
},
|
||||
)
|
||||
}, [editor, isRecording, pushStep])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (preRef.current) {
|
||||
preRef.current.scrollTo(0, preRef.current.scrollHeight)
|
||||
}
|
||||
}, [generateTestContent])
|
||||
|
||||
useEffect(() => {
|
||||
if (steps) {
|
||||
const testContent = generateTestContent()
|
||||
if (testContent !== null) {
|
||||
setTemplatedTest(testContent)
|
||||
}
|
||||
if (preRef.current) {
|
||||
preRef.current.scrollTo(0, preRef.current.scrollHeight)
|
||||
}
|
||||
}
|
||||
}, [generateTestContent, steps])
|
||||
|
||||
useEffect(() => {
|
||||
const removeUpdateListener = editor.registerUpdateListener(
|
||||
({ dirtyElements, dirtyLeaves, editorState }) => {
|
||||
if (!isRecording) {
|
||||
return
|
||||
}
|
||||
const currentSelection = editorState._selection
|
||||
const previousSelection = previousSelectionRef.current
|
||||
const skipNextSelectionChange = skipNextSelectionChangeRef.current
|
||||
if (previousSelection !== currentSelection) {
|
||||
if (dirtyLeaves.size === 0 && dirtyElements.size === 0 && !skipNextSelectionChange) {
|
||||
const browserSelection = window.getSelection()
|
||||
if (
|
||||
browserSelection &&
|
||||
(browserSelection.anchorNode == null || browserSelection.focusNode == null)
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
previousSelectionRef.current = currentSelection
|
||||
}
|
||||
skipNextSelectionChangeRef.current = false
|
||||
const testContent = generateTestContent()
|
||||
if (testContent !== null) {
|
||||
setTemplatedTest(testContent)
|
||||
}
|
||||
},
|
||||
)
|
||||
return removeUpdateListener
|
||||
}, [editor, generateTestContent, isRecording, pushStep])
|
||||
|
||||
// save innerHTML
|
||||
useEffect(() => {
|
||||
if (!isRecording) {
|
||||
return
|
||||
}
|
||||
const removeUpdateListener = editor.registerUpdateListener(() => {
|
||||
const rootElement = editor.getRootElement()
|
||||
if (rootElement !== null) {
|
||||
setCurrentInnerHTML(rootElement?.innerHTML)
|
||||
}
|
||||
})
|
||||
return removeUpdateListener
|
||||
}, [editor, isRecording])
|
||||
|
||||
// clear editor and start recording
|
||||
const toggleEditorSelection = useCallback(
|
||||
(currentEditor: LexicalEditor) => {
|
||||
if (!isRecording) {
|
||||
currentEditor.update(() => {
|
||||
const root = $getRoot()
|
||||
root.clear()
|
||||
const text = $createTextNode()
|
||||
root.append($createParagraphNode().append(text))
|
||||
text.select()
|
||||
})
|
||||
setSteps([])
|
||||
}
|
||||
setIsRecording((currentIsRecording) => !currentIsRecording)
|
||||
},
|
||||
[isRecording],
|
||||
)
|
||||
|
||||
const onSnapshotClick = useCallback(() => {
|
||||
if (!isRecording) {
|
||||
return
|
||||
}
|
||||
const browserSelection = window.getSelection()
|
||||
if (
|
||||
browserSelection === null ||
|
||||
browserSelection.anchorNode == null ||
|
||||
browserSelection.focusNode == null
|
||||
) {
|
||||
return
|
||||
}
|
||||
const { anchorNode, anchorOffset, focusNode, focusOffset } = sanitizeSelection(browserSelection)
|
||||
const rootElement = getCurrentEditor().getRootElement()
|
||||
let anchorPath
|
||||
if (anchorNode !== null) {
|
||||
anchorPath = getPathFromNodeToEditor(anchorNode, rootElement)
|
||||
}
|
||||
let focusPath
|
||||
if (focusNode !== null) {
|
||||
focusPath = getPathFromNodeToEditor(focusNode, rootElement)
|
||||
}
|
||||
pushStep('snapshot', {
|
||||
anchorNode,
|
||||
anchorOffset,
|
||||
anchorPath,
|
||||
focusNode,
|
||||
focusOffset,
|
||||
focusPath,
|
||||
})
|
||||
}, [pushStep, isRecording, getCurrentEditor])
|
||||
|
||||
const onCopyClick = useCallback(() => {
|
||||
copy(generateTestContent())
|
||||
}, [generateTestContent])
|
||||
|
||||
const onDownloadClick = useCallback(() => {
|
||||
download('test.js', generateTestContent())
|
||||
}, [generateTestContent])
|
||||
|
||||
const button = (
|
||||
<button
|
||||
className={`editor-dev-button ${isRecording ? 'active' : ''}`}
|
||||
id="test-recorder-button"
|
||||
onClick={(e) => {
|
||||
toggleEditorSelection(getCurrentEditor())
|
||||
e.preventDefault()
|
||||
}}
|
||||
title={isRecording ? 'Disable test recorder' : 'Enable test recorder'}
|
||||
>
|
||||
{isRecording ? 'Disable test recorder' : 'Enable test recorder'}
|
||||
</button>
|
||||
)
|
||||
const output = isRecording ? (
|
||||
<div className="test-recorder-output">
|
||||
<div className="test-recorder-toolbar">
|
||||
<button
|
||||
className="test-recorder-button"
|
||||
id="test-recorder-button-snapshot"
|
||||
onClick={(e) => {
|
||||
onSnapshotClick()
|
||||
e.preventDefault()
|
||||
}}
|
||||
title="Insert snapshot"
|
||||
>
|
||||
Insert Snapshot
|
||||
</button>
|
||||
<button
|
||||
className="test-recorder-button"
|
||||
id="test-recorder-button-copy"
|
||||
onClick={(e) => {
|
||||
onCopyClick()
|
||||
e.preventDefault()
|
||||
}}
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
className="test-recorder-button"
|
||||
id="test-recorder-button-download"
|
||||
onClick={(e) => {
|
||||
onDownloadClick()
|
||||
e.preventDefault()
|
||||
}}
|
||||
title="Download as a file"
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
<pre id="test-recorder" ref={preRef}>
|
||||
{templatedTest}
|
||||
</pre>
|
||||
</div>
|
||||
) : null
|
||||
|
||||
return [button, output]
|
||||
}
|
||||
export const TestRecorderPlugin: React.FC = () => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const [testRecorderButton, testRecorderOutput] = useTestRecorder(editor)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<p>HI</p>
|
||||
{testRecorderButton}
|
||||
{testRecorderOutput}
|
||||
<p>DONE</p>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export const TreeviewFeature = (): FeatureProvider => {
|
||||
plugins: [
|
||||
{
|
||||
Component: TreeViewPlugin,
|
||||
position: 'normal',
|
||||
position: 'bottom',
|
||||
},
|
||||
],
|
||||
props: null,
|
||||
|
||||
@@ -89,6 +89,11 @@ export type Feature = {
|
||||
Component: React.FC
|
||||
position: 'normal' // Determines at which position the Component will be added.
|
||||
}
|
||||
| {
|
||||
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
|
||||
Component: React.FC<{ anchorElem: HTMLElement }>
|
||||
position: 'bottom' // Determines at which position the Component will be added.
|
||||
}
|
||||
| {
|
||||
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
|
||||
Component: React.FC<{ anchorElem: HTMLElement }>
|
||||
@@ -191,6 +196,12 @@ export type SanitizedFeatures = Required<
|
||||
key: string
|
||||
position: 'floatingAnchorElem' // Determines at which position the Component will be added.
|
||||
}
|
||||
| {
|
||||
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
|
||||
Component: React.FC
|
||||
key: string
|
||||
position: 'bottom' // Determines at which position the Component will be added.
|
||||
}
|
||||
>
|
||||
/** The node types mapped to their populationPromises */
|
||||
populationPromises: Map<string, Array<PopulationPromise>>
|
||||
|
||||
@@ -111,6 +111,11 @@ export const LexicalEditor: React.FC<Pick<LexicalProviderProps, 'editorConfig' |
|
||||
return <plugin.Component key={plugin.key} />
|
||||
}
|
||||
})}
|
||||
{editorConfig.features.plugins.map((plugin) => {
|
||||
if (plugin.position === 'bottom') {
|
||||
return <plugin.Component key={plugin.key} />
|
||||
}
|
||||
})}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
import { CAN_USE_DOM } from './canUseDOM'
|
||||
|
||||
declare global {
|
||||
interface Document {
|
||||
documentMode?: unknown
|
||||
}
|
||||
|
||||
interface Window {
|
||||
MSStream?: unknown
|
||||
}
|
||||
}
|
||||
|
||||
const documentMode = CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null
|
||||
|
||||
export const IS_APPLE: boolean = CAN_USE_DOM && /Mac|iPod|iPhone|iPad/.test(navigator.platform)
|
||||
|
||||
export const IS_FIREFOX: boolean =
|
||||
CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent)
|
||||
|
||||
export const CAN_USE_BEFORE_INPUT: boolean =
|
||||
CAN_USE_DOM && 'InputEvent' in window && !documentMode
|
||||
? 'getTargetRanges' in new window.InputEvent('input')
|
||||
: false
|
||||
|
||||
export const IS_SAFARI: boolean = CAN_USE_DOM && /Version\/[\d.].*Safari/.test(navigator.userAgent)
|
||||
|
||||
export const IS_IOS: boolean =
|
||||
CAN_USE_DOM && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream
|
||||
|
||||
export const IS_ANDROID: boolean = CAN_USE_DOM && /Android/.test(navigator.userAgent)
|
||||
|
||||
export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform)
|
||||
export const IS_CHROME: boolean = CAN_USE_DOM && /^(?=.*Chrome).*/i.test(navigator.userAgent)
|
||||
// export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode;
|
||||
|
||||
export const IS_APPLE_WEBKIT =
|
||||
CAN_USE_DOM && /AppleWebKit\/[\d.]+/.test(navigator.userAgent) && !IS_CHROME
|
||||
@@ -185,6 +185,7 @@ export type { HTMLConverter } from './field/features/converters/html/converter/t
|
||||
export { consolidateHTMLConverters } from './field/features/converters/html/field'
|
||||
export { lexicalHTML } from './field/features/converters/html/field'
|
||||
|
||||
export { TestRecorderFeature } from './field/features/debug/TestRecorder'
|
||||
export { TreeviewFeature } from './field/features/debug/TreeView'
|
||||
|
||||
export { BoldTextFeature } from './field/features/format/Bold'
|
||||
|
||||
@@ -10,6 +10,9 @@ const config: PlaywrightTestConfig = {
|
||||
trace: 'retain-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
},
|
||||
workers: 999,
|
||||
expect: {
|
||||
timeout: 10000,
|
||||
},
|
||||
workers: 16,
|
||||
}
|
||||
export default config
|
||||
|
||||
150
pnpm-lock.yaml
generated
150
pnpm-lock.yaml
generated
@@ -22,8 +22,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:packages/eslint-config-payload
|
||||
'@playwright/test':
|
||||
specifier: 1.38.1
|
||||
version: 1.38.1
|
||||
specifier: 1.39.0
|
||||
version: 1.39.0
|
||||
'@swc/cli':
|
||||
specifier: ^0.1.62
|
||||
version: 0.1.62(@swc/core@1.3.76)
|
||||
@@ -55,8 +55,8 @@ importers:
|
||||
specifier: ^11.0.2
|
||||
version: 11.0.2
|
||||
'@types/jest':
|
||||
specifier: 29.5.4
|
||||
version: 29.5.4
|
||||
specifier: 29.5.7
|
||||
version: 29.5.7
|
||||
'@types/minimist':
|
||||
specifier: 1.2.2
|
||||
version: 1.2.2
|
||||
@@ -111,6 +111,9 @@ importers:
|
||||
dotenv:
|
||||
specifier: 8.6.0
|
||||
version: 8.6.0
|
||||
drizzle-orm:
|
||||
specifier: 0.28.5
|
||||
version: 0.28.5(@libsql/client@0.3.4)(@types/pg@8.10.2)(pg@8.11.3)
|
||||
express:
|
||||
specifier: 4.18.2
|
||||
version: 4.18.2
|
||||
@@ -139,11 +142,11 @@ importers:
|
||||
specifier: 3.0.0
|
||||
version: 3.0.0
|
||||
jest:
|
||||
specifier: 29.6.4
|
||||
version: 29.6.4(@types/node@20.5.7)(ts-node@10.9.1)
|
||||
specifier: 29.7.0
|
||||
version: 29.7.0(@types/node@20.5.7)(ts-node@10.9.1)
|
||||
jest-environment-jsdom:
|
||||
specifier: 29.6.4
|
||||
version: 29.6.4
|
||||
specifier: 29.7.0
|
||||
version: 29.7.0
|
||||
jwt-decode:
|
||||
specifier: 3.1.2
|
||||
version: 3.1.2
|
||||
@@ -416,7 +419,7 @@ importers:
|
||||
version: 2.4.5
|
||||
ts-jest:
|
||||
specifier: ^29.1.0
|
||||
version: 29.1.1(@babel/core@7.22.20)(jest@29.6.4)(typescript@5.2.2)
|
||||
version: 29.1.1(@babel/core@7.22.20)(jest@29.7.0)(typescript@5.2.2)
|
||||
|
||||
packages/db-mongodb:
|
||||
dependencies:
|
||||
@@ -517,7 +520,7 @@ importers:
|
||||
version: 2.28.1(@typescript-eslint/parser@6.6.0)(eslint@8.48.0)
|
||||
eslint-plugin-jest:
|
||||
specifier: 27.2.3
|
||||
version: 27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(jest@29.6.4)(typescript@5.2.2)
|
||||
version: 27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(jest@29.7.0)(typescript@5.2.2)
|
||||
eslint-plugin-jest-dom:
|
||||
specifier: 5.1.0
|
||||
version: 5.1.0(eslint@8.48.0)
|
||||
@@ -1072,7 +1075,7 @@ importers:
|
||||
version: link:../payload
|
||||
ts-jest:
|
||||
specifier: ^29.1.0
|
||||
version: 29.1.1(@babel/core@7.22.20)(jest@29.6.4)(typescript@5.2.2)
|
||||
version: 29.1.1(@babel/core@7.22.20)(jest@29.7.0)(typescript@5.2.2)
|
||||
webpack:
|
||||
specifier: ^5.78.0
|
||||
version: 5.88.2(@swc/core@1.3.76)(webpack-cli@4.10.0)
|
||||
@@ -3660,14 +3663,14 @@ packages:
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/transform': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 20.6.2
|
||||
'@types/node': 16.18.58
|
||||
ansi-escapes: 4.3.2
|
||||
chalk: 4.1.2
|
||||
ci-info: 3.8.0
|
||||
exit: 0.1.2
|
||||
graceful-fs: 4.2.11
|
||||
jest-changed-files: 29.7.0
|
||||
jest-config: 29.7.0(@types/node@20.6.2)(ts-node@10.9.1)
|
||||
jest-config: 29.7.0(@types/node@16.18.58)(ts-node@10.9.1)
|
||||
jest-haste-map: 29.7.0
|
||||
jest-message-util: 29.7.0
|
||||
jest-regex-util: 29.6.3
|
||||
@@ -3701,7 +3704,7 @@ packages:
|
||||
dependencies:
|
||||
'@jest/fake-timers': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 20.6.2
|
||||
'@types/node': 16.18.58
|
||||
jest-mock: 29.7.0
|
||||
|
||||
/@jest/expect-utils@29.7.0:
|
||||
@@ -3725,7 +3728,7 @@ packages:
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
'@sinonjs/fake-timers': 10.3.0
|
||||
'@types/node': 20.6.2
|
||||
'@types/node': 16.18.58
|
||||
jest-message-util: 29.7.0
|
||||
jest-mock: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
@@ -4150,7 +4153,6 @@ packages:
|
||||
- bufferutil
|
||||
- encoding
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@libsql/hrana-client@0.5.5:
|
||||
resolution: {integrity: sha512-i+hDBpiV719poqEiHupUUZYKJ9YSbCRFe5Q2PQ0v3mHIftePH6gayLjp2u6TXbqbO/Dv6y8yyvYlBXf/kFfRZA==}
|
||||
@@ -4163,7 +4165,6 @@ packages:
|
||||
- bufferutil
|
||||
- encoding
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@libsql/isomorphic-fetch@0.1.10:
|
||||
resolution: {integrity: sha512-dH0lMk50gKSvEKD78xWMu60SY1sjp1sY//iFLO0XMmBwfVfG136P9KOk06R4maBdlb8KMXOzJ1D28FR5ZKnHTA==}
|
||||
@@ -4172,7 +4173,6 @@ packages:
|
||||
node-fetch: 2.6.12
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@libsql/isomorphic-ws@0.1.5:
|
||||
resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==}
|
||||
@@ -4182,7 +4182,6 @@ packages:
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@mole-inc/bin-wrapper@8.0.1:
|
||||
resolution: {integrity: sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==}
|
||||
@@ -4376,12 +4375,12 @@ packages:
|
||||
engines: {node: '>=8.0.0'}
|
||||
dev: true
|
||||
|
||||
/@playwright/test@1.38.1:
|
||||
resolution: {integrity: sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==}
|
||||
/@playwright/test@1.39.0:
|
||||
resolution: {integrity: sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
playwright: 1.38.1
|
||||
playwright: 1.39.0
|
||||
dev: true
|
||||
|
||||
/@pnpm/config.env-replace@1.1.0:
|
||||
@@ -5832,6 +5831,13 @@ packages:
|
||||
pretty-format: 29.7.0
|
||||
dev: true
|
||||
|
||||
/@types/jest@29.5.7:
|
||||
resolution: {integrity: sha512-HLyetab6KVPSiF+7pFcUyMeLsx25LDNDemw9mGsJBkai/oouwrjTycocSDYopMEwFhN2Y4s9oPyOCZNofgSt2g==}
|
||||
dependencies:
|
||||
expect: 29.7.0
|
||||
pretty-format: 29.7.0
|
||||
dev: true
|
||||
|
||||
/@types/joi@14.3.4:
|
||||
resolution: {integrity: sha512-1TQNDJvIKlgYXGNIABfgFp9y0FziDpuGrd799Q5RcnsDu+krD+eeW/0Fs5PHARvWWFelOhIG2OPCo6KbadBM4A==}
|
||||
dev: true
|
||||
@@ -5839,7 +5845,7 @@ packages:
|
||||
/@types/jsdom@20.0.1:
|
||||
resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
|
||||
dependencies:
|
||||
'@types/node': 20.6.2
|
||||
'@types/node': 16.18.58
|
||||
'@types/tough-cookie': 4.0.3
|
||||
parse5: 7.1.2
|
||||
dev: true
|
||||
@@ -6174,7 +6180,7 @@ packages:
|
||||
/@types/testing-library__jest-dom@5.14.8:
|
||||
resolution: {integrity: sha512-NRfJE9Cgpmu4fx716q9SYmU4jxxhYRU1BQo239Txt/9N3EC745XZX1Yl7h/SBIDlo1ANVOCRB4YDXjaQdoKCHQ==}
|
||||
dependencies:
|
||||
'@types/jest': 29.5.4
|
||||
'@types/jest': 29.5.7
|
||||
dev: true
|
||||
|
||||
/@types/to-snake-case@1.0.0:
|
||||
@@ -6267,7 +6273,6 @@ packages:
|
||||
resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==}
|
||||
dependencies:
|
||||
'@types/node': 16.18.58
|
||||
dev: false
|
||||
|
||||
/@types/yargs-parser@21.0.0:
|
||||
resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
|
||||
@@ -7193,7 +7198,6 @@ packages:
|
||||
dependencies:
|
||||
bindings: 1.5.0
|
||||
prebuild-install: 7.1.1
|
||||
dev: false
|
||||
|
||||
/big-integer@1.6.51:
|
||||
resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
|
||||
@@ -7240,7 +7244,6 @@ packages:
|
||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||
dependencies:
|
||||
file-uri-to-path: 1.0.0
|
||||
dev: false
|
||||
|
||||
/bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
@@ -7391,7 +7394,6 @@ packages:
|
||||
/buffer-writer@2.0.0:
|
||||
resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/buffer@4.9.2:
|
||||
resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==}
|
||||
@@ -8982,7 +8984,6 @@ packages:
|
||||
'@libsql/client': 0.3.4
|
||||
'@types/pg': 8.10.2
|
||||
pg: 8.11.3
|
||||
dev: false
|
||||
|
||||
/duplexer@0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
@@ -9402,7 +9403,7 @@ packages:
|
||||
requireindex: 1.2.0
|
||||
dev: false
|
||||
|
||||
/eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(jest@29.6.4)(typescript@5.2.2):
|
||||
/eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(jest@29.7.0)(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-sRLlSCpICzWuje66Gl9zvdF6mwD5X86I4u55hJyFBsxYOsBCmT5+kSUjf+fkFWVMMgpzNEupjW8WzUqi83hJAQ==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
@@ -9418,7 +9419,7 @@ packages:
|
||||
'@typescript-eslint/eslint-plugin': 6.6.0(@typescript-eslint/parser@6.6.0)(eslint@8.48.0)(typescript@5.2.2)
|
||||
'@typescript-eslint/utils': 5.62.0(eslint@8.48.0)(typescript@5.2.2)
|
||||
eslint: 8.48.0
|
||||
jest: 29.6.4(@types/node@20.5.7)(ts-node@10.9.1)
|
||||
jest: 29.7.0(@types/node@20.5.7)(ts-node@10.9.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
@@ -9501,7 +9502,7 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
eslint: 8.48.0
|
||||
eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(jest@29.6.4)(typescript@5.2.2)
|
||||
eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(jest@29.7.0)(typescript@5.2.2)
|
||||
dev: false
|
||||
|
||||
/eslint-plugin-react-hooks@4.6.0(eslint@8.48.0):
|
||||
@@ -10001,7 +10002,6 @@ packages:
|
||||
|
||||
/file-uri-to-path@1.0.0:
|
||||
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
|
||||
dev: false
|
||||
|
||||
/filename-reserved-regex@3.0.0:
|
||||
resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==}
|
||||
@@ -11730,7 +11730,6 @@ packages:
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/jest-config@29.7.0(@types/node@20.5.7)(ts-node@10.9.1):
|
||||
resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
|
||||
@@ -11772,46 +11771,6 @@ packages:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
|
||||
/jest-config@29.7.0(@types/node@20.6.2)(ts-node@10.9.1):
|
||||
resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
'@types/node': '*'
|
||||
ts-node: '>=9.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
ts-node:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/core': 7.22.20
|
||||
'@jest/test-sequencer': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 20.6.2
|
||||
babel-jest: 29.7.0(@babel/core@7.22.20)
|
||||
chalk: 4.1.2
|
||||
ci-info: 3.8.0
|
||||
deepmerge: 4.3.1
|
||||
glob: 7.2.3
|
||||
graceful-fs: 4.2.11
|
||||
jest-circus: 29.7.0
|
||||
jest-environment-node: 29.7.0
|
||||
jest-get-type: 29.6.3
|
||||
jest-regex-util: 29.6.3
|
||||
jest-resolve: 29.7.0
|
||||
jest-runner: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
jest-validate: 29.7.0
|
||||
micromatch: 4.0.5
|
||||
parse-json: 5.2.0
|
||||
pretty-format: 29.7.0
|
||||
slash: 3.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
ts-node: 10.9.1(@swc/core@1.3.76)(@types/node@20.5.7)(typescript@5.2.2)
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
|
||||
/jest-diff@27.5.1:
|
||||
resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
@@ -11847,8 +11806,8 @@ packages:
|
||||
jest-util: 29.7.0
|
||||
pretty-format: 29.7.0
|
||||
|
||||
/jest-environment-jsdom@29.6.4:
|
||||
resolution: {integrity: sha512-K6wfgUJ16DoMs02JYFid9lOsqfpoVtyJxpRlnTxUHzvZWBnnh2VNGRB9EC1Cro96TQdq5TtSjb3qUjNaJP9IyA==}
|
||||
/jest-environment-jsdom@29.7.0:
|
||||
resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
canvas: ^2.5.0
|
||||
@@ -11860,7 +11819,7 @@ packages:
|
||||
'@jest/fake-timers': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
'@types/jsdom': 20.0.1
|
||||
'@types/node': 20.6.2
|
||||
'@types/node': 16.18.58
|
||||
jest-mock: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
jsdom: 20.0.3
|
||||
@@ -11953,7 +11912,7 @@ packages:
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 20.6.2
|
||||
'@types/node': 16.18.58
|
||||
jest-util: 29.7.0
|
||||
|
||||
/jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
|
||||
@@ -12130,8 +12089,8 @@ packages:
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
|
||||
/jest@29.6.4(@types/node@16.18.58)(ts-node@10.9.1):
|
||||
resolution: {integrity: sha512-tEFhVQFF/bzoYV1YuGyzLPZ6vlPrdfvDmmAxudA1dLEuiztqg2Rkx20vkKY32xiDROcD2KXlgZ7Cu8RPeEHRKw==}
|
||||
/jest@29.7.0(@types/node@16.18.58)(ts-node@10.9.1):
|
||||
resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -12151,8 +12110,8 @@ packages:
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/jest@29.6.4(@types/node@20.5.7)(ts-node@10.9.1):
|
||||
resolution: {integrity: sha512-tEFhVQFF/bzoYV1YuGyzLPZ6vlPrdfvDmmAxudA1dLEuiztqg2Rkx20vkKY32xiDROcD2KXlgZ7Cu8RPeEHRKw==}
|
||||
/jest@29.7.0(@types/node@20.5.7)(ts-node@10.9.1):
|
||||
resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -12186,7 +12145,6 @@ packages:
|
||||
|
||||
/js-base64@3.7.5:
|
||||
resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==}
|
||||
dev: false
|
||||
|
||||
/js-beautify@1.14.9:
|
||||
resolution: {integrity: sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg==}
|
||||
@@ -13330,7 +13288,6 @@ packages:
|
||||
data-uri-to-buffer: 4.0.1
|
||||
fetch-blob: 3.2.0
|
||||
formdata-polyfill: 4.0.10
|
||||
dev: false
|
||||
|
||||
/node-forge@1.3.1:
|
||||
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
|
||||
@@ -13780,7 +13737,6 @@ packages:
|
||||
|
||||
/packet-reader@1.0.0:
|
||||
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
|
||||
dev: false
|
||||
|
||||
/param-case@3.0.4:
|
||||
resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
|
||||
@@ -14127,12 +14083,10 @@ packages:
|
||||
/pg-cloudflare@1.1.1:
|
||||
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/pg-connection-string@2.6.2:
|
||||
resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==}
|
||||
dev: false
|
||||
|
||||
/pg-int8@1.0.1:
|
||||
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
|
||||
@@ -14148,7 +14102,6 @@ packages:
|
||||
pg: '>=8.0'
|
||||
dependencies:
|
||||
pg: 8.11.3
|
||||
dev: false
|
||||
|
||||
/pg-protocol@1.6.0:
|
||||
resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==}
|
||||
@@ -14162,7 +14115,6 @@ packages:
|
||||
postgres-bytea: 1.0.0
|
||||
postgres-date: 1.0.7
|
||||
postgres-interval: 1.2.0
|
||||
dev: false
|
||||
|
||||
/pg-types@4.0.1:
|
||||
resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==}
|
||||
@@ -14194,13 +14146,11 @@ packages:
|
||||
pgpass: 1.0.5
|
||||
optionalDependencies:
|
||||
pg-cloudflare: 1.1.1
|
||||
dev: false
|
||||
|
||||
/pgpass@1.0.5:
|
||||
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
|
||||
dependencies:
|
||||
split2: 4.2.0
|
||||
dev: false
|
||||
|
||||
/picocolors@1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
@@ -14332,18 +14282,18 @@ packages:
|
||||
dependencies:
|
||||
find-up: 3.0.0
|
||||
|
||||
/playwright-core@1.38.1:
|
||||
resolution: {integrity: sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==}
|
||||
/playwright-core@1.39.0:
|
||||
resolution: {integrity: sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/playwright@1.38.1:
|
||||
resolution: {integrity: sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==}
|
||||
/playwright@1.39.0:
|
||||
resolution: {integrity: sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
playwright-core: 1.38.1
|
||||
playwright-core: 1.39.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
@@ -14981,7 +14931,6 @@ packages:
|
||||
/postgres-array@2.0.0:
|
||||
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/postgres-array@3.0.2:
|
||||
resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==}
|
||||
@@ -14990,7 +14939,6 @@ packages:
|
||||
/postgres-bytea@1.0.0:
|
||||
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/postgres-bytea@3.0.0:
|
||||
resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==}
|
||||
@@ -15001,7 +14949,6 @@ packages:
|
||||
/postgres-date@1.0.7:
|
||||
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/postgres-date@2.0.1:
|
||||
resolution: {integrity: sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==}
|
||||
@@ -15012,7 +14959,6 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
xtend: 4.0.2
|
||||
dev: false
|
||||
|
||||
/postgres-interval@3.0.0:
|
||||
resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==}
|
||||
@@ -17194,7 +17140,7 @@ packages:
|
||||
dependencies:
|
||||
typescript: 5.2.2
|
||||
|
||||
/ts-jest@29.1.1(@babel/core@7.22.20)(jest@29.6.4)(typescript@5.2.2):
|
||||
/ts-jest@29.1.1(@babel/core@7.22.20)(jest@29.7.0)(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
@@ -17218,7 +17164,7 @@ packages:
|
||||
'@babel/core': 7.22.20
|
||||
bs-logger: 0.2.6
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
jest: 29.6.4(@types/node@16.18.58)(ts-node@10.9.1)
|
||||
jest: 29.7.0(@types/node@16.18.58)(ts-node@10.9.1)
|
||||
jest-util: 29.7.0
|
||||
json5: 2.2.3
|
||||
lodash.memoize: 4.1.2
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import CustomEditView from '../components/views/CustomEdit'
|
||||
|
||||
export const customViews1Slug = 'custom-views-one'
|
||||
import { customViews1CollectionSlug } from '../slugs'
|
||||
|
||||
export const CustomViews1: CollectionConfig = {
|
||||
slug: customViews1Slug,
|
||||
slug: customViews1CollectionSlug,
|
||||
versions: true,
|
||||
admin: {
|
||||
components: {
|
||||
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
customNestedTabViewPath,
|
||||
customTabLabel,
|
||||
customTabViewPath,
|
||||
customViews2Slug,
|
||||
} from '../shared'
|
||||
import { customViews2CollectionSlug } from '../slugs'
|
||||
|
||||
export const CustomViews2: CollectionConfig = {
|
||||
slug: customViews2Slug,
|
||||
slug: customViews2CollectionSlug,
|
||||
versions: true,
|
||||
admin: {
|
||||
components: {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { geoCollectionSlug } from '../slugs'
|
||||
|
||||
export const Geo: CollectionConfig = {
|
||||
slug: 'geo',
|
||||
slug: geoCollectionSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'point',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { group1Collection1Slug } from '../shared'
|
||||
import { group1Collection1Slug } from '../slugs'
|
||||
|
||||
export const CollectionGroup1A: CollectionConfig = {
|
||||
slug: group1Collection1Slug,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { group1Collection2Slug } from '../shared'
|
||||
import { group1Collection2Slug } from '../slugs'
|
||||
|
||||
export const CollectionGroup1B: CollectionConfig = {
|
||||
slug: group1Collection2Slug,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { group2Collection1Slug } from '../slugs'
|
||||
|
||||
export const CollectionGroup2A: CollectionConfig = {
|
||||
slug: 'group-two-collection-ones',
|
||||
slug: group2Collection1Slug,
|
||||
admin: {
|
||||
group: 'One',
|
||||
},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { group2Collection2Slug } from '../slugs'
|
||||
|
||||
export const CollectionGroup2B: CollectionConfig = {
|
||||
slug: 'group-two-collection-twos',
|
||||
slug: group2Collection2Slug,
|
||||
admin: {
|
||||
group: 'One',
|
||||
},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { hiddenCollectionSlug } from '../slugs'
|
||||
|
||||
export const CollectionHidden: CollectionConfig = {
|
||||
slug: 'hidden-collection',
|
||||
slug: hiddenCollectionSlug,
|
||||
admin: {
|
||||
hidden: () => true,
|
||||
},
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { noApiViewCollection } from '../shared'
|
||||
import { noApiViewCollectionSlug } from '../slugs'
|
||||
|
||||
export const CollectionNoApiView: CollectionConfig = {
|
||||
slug: noApiViewCollection,
|
||||
slug: noApiViewCollectionSlug,
|
||||
admin: {
|
||||
hideAPIURL: true,
|
||||
},
|
||||
|
||||
@@ -3,10 +3,11 @@ import type { CollectionConfig } from '../../../packages/payload/src/collections
|
||||
import { slateEditor } from '../../../packages/richtext-slate/src'
|
||||
import DemoUIFieldCell from '../components/DemoUIField/Cell'
|
||||
import DemoUIFieldField from '../components/DemoUIField/Field'
|
||||
import { postsSlug, slugPluralLabel, slugSingularLabel } from '../shared'
|
||||
import { slugPluralLabel, slugSingularLabel } from '../shared'
|
||||
import { postsCollectionSlug } from '../slugs'
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: postsSlug,
|
||||
slug: postsCollectionSlug,
|
||||
labels: {
|
||||
singular: slugSingularLabel,
|
||||
plural: slugPluralLabel,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { usersCollectionSlug } from '../slugs'
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
slug: usersCollectionSlug,
|
||||
auth: true,
|
||||
admin: {
|
||||
useAsTitle: 'email',
|
||||
|
||||
@@ -27,7 +27,7 @@ import { GlobalGroup1A } from './globals/Group1A'
|
||||
import { GlobalGroup1B } from './globals/Group1B'
|
||||
import { GlobalHidden } from './globals/Hidden'
|
||||
import { GlobalNoApiView } from './globals/NoApiView'
|
||||
import { seed } from './seed'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
import { customNestedViewPath, customViewPath } from './shared'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
@@ -63,6 +63,16 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
},
|
||||
},
|
||||
webpack: (config) => ({
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config?.resolve?.alias,
|
||||
fs: path.resolve(__dirname, './mocks/emptyModule.js'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
i18n: {
|
||||
resources: {
|
||||
@@ -99,5 +109,7 @@ export default buildConfigWithDefaults({
|
||||
GlobalGroup1A,
|
||||
GlobalGroup1B,
|
||||
],
|
||||
onInit: seed,
|
||||
onInit: async (payload) => {
|
||||
await clearAndSeedEverything(payload)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Page } from '@playwright/test'
|
||||
import { expect, test } from '@playwright/test'
|
||||
import qs from 'qs'
|
||||
|
||||
import type { PayloadRequest } from '../../packages/payload/src/express/types'
|
||||
import type { Post } from './payload-types'
|
||||
|
||||
import payload from '../../packages/payload/src'
|
||||
@@ -20,6 +19,7 @@ import {
|
||||
} from '../helpers'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
|
||||
import { initPayloadE2E } from '../helpers/configHelpers'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
import {
|
||||
customEditLabel,
|
||||
customNestedTabViewPath,
|
||||
@@ -31,17 +31,19 @@ import {
|
||||
customTabViewTitle,
|
||||
customViewPath,
|
||||
customViewTitle,
|
||||
customViews2Slug,
|
||||
slugPluralLabel,
|
||||
} from './shared'
|
||||
import {
|
||||
customViews2CollectionSlug,
|
||||
globalSlug,
|
||||
group1Collection1Slug,
|
||||
group1GlobalSlug,
|
||||
noApiViewCollection,
|
||||
noApiViewGlobal,
|
||||
postsSlug,
|
||||
slugPluralLabel,
|
||||
} from './shared'
|
||||
noApiViewCollectionSlug,
|
||||
noApiViewGlobalSlug,
|
||||
postsCollectionSlug,
|
||||
} from './slugs'
|
||||
|
||||
const { afterEach, beforeAll, beforeEach, describe } = test
|
||||
const { beforeAll, beforeEach, describe } = test
|
||||
|
||||
const title = 'Title'
|
||||
const description = 'Description'
|
||||
@@ -54,29 +56,21 @@ describe('admin', () => {
|
||||
|
||||
beforeAll(async ({ browser }) => {
|
||||
serverURL = (await initPayloadE2E(__dirname)).serverURL
|
||||
await clearDocs() // Clear any seeded data from onInit
|
||||
url = new AdminUrlUtil(serverURL, postsSlug)
|
||||
customViewsURL = new AdminUrlUtil(serverURL, customViews2Slug)
|
||||
url = new AdminUrlUtil(serverURL, postsCollectionSlug)
|
||||
customViewsURL = new AdminUrlUtil(serverURL, customViews2CollectionSlug)
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await clearDocs()
|
||||
// clear preferences
|
||||
await payload.db.deleteMany({
|
||||
collection: 'payload-preferences',
|
||||
req: {} as PayloadRequest,
|
||||
where: {},
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await clearAndSeedEverything(payload)
|
||||
})
|
||||
|
||||
describe('Nav', () => {
|
||||
test('should nav to collection - nav', async () => {
|
||||
await page.goto(url.admin)
|
||||
await openNav(page)
|
||||
await page.locator(`#nav-${postsSlug}`).click()
|
||||
await page.locator(`#nav-${postsCollectionSlug}`).click()
|
||||
expect(page.url()).toContain(url.list)
|
||||
})
|
||||
|
||||
@@ -90,7 +84,7 @@ describe('admin', () => {
|
||||
test('should navigate to collection - card', async () => {
|
||||
await page.goto(url.admin)
|
||||
await wait(200)
|
||||
await page.locator(`#card-${postsSlug}`).click()
|
||||
await page.locator(`#card-${postsCollectionSlug}`).click()
|
||||
expect(page.url()).toContain(url.list)
|
||||
})
|
||||
|
||||
@@ -147,7 +141,7 @@ describe('admin', () => {
|
||||
const { id } = await createPost()
|
||||
await page.goto(url.edit(id))
|
||||
const collectionBreadcrumb = page.locator(
|
||||
`.step-nav a[href="/admin/collections/${postsSlug}"]`,
|
||||
`.step-nav a[href="/admin/collections/${postsCollectionSlug}"]`,
|
||||
)
|
||||
await expect(collectionBreadcrumb).toBeVisible()
|
||||
await expect(collectionBreadcrumb).toHaveText(slugPluralLabel)
|
||||
@@ -239,35 +233,35 @@ describe('admin', () => {
|
||||
})
|
||||
|
||||
test('collection - should not show API tab when disabled in config', async () => {
|
||||
await page.goto(url.collection(noApiViewCollection))
|
||||
await page.goto(url.collection(noApiViewCollectionSlug))
|
||||
await page.locator('.collection-list .table a').click()
|
||||
await expect(page.locator('.doc-tabs__tabs-container')).not.toContainText('API')
|
||||
})
|
||||
|
||||
test('collection - should not enable API route when disabled in config', async () => {
|
||||
const collectionItems = await payload.find({
|
||||
collection: noApiViewCollection,
|
||||
collection: noApiViewCollectionSlug,
|
||||
limit: 1,
|
||||
})
|
||||
expect(collectionItems.docs.length).toBe(1)
|
||||
await page.goto(`${url.collection(noApiViewCollection)}/${collectionItems.docs[0].id}/api`)
|
||||
await page.goto(`${url.collection(noApiViewGlobalSlug)}/${collectionItems.docs[0].id}/api`)
|
||||
await expect(page.locator('.not-found')).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('global - should not show API tab when disabled in config', async () => {
|
||||
await page.goto(url.global(noApiViewGlobal))
|
||||
await page.goto(url.global(noApiViewGlobalSlug))
|
||||
await expect(page.locator('.doc-tabs__tabs-container')).not.toContainText('API')
|
||||
})
|
||||
|
||||
test('global - should not enable API route when disabled in config', async () => {
|
||||
await page.goto(`${url.global(noApiViewGlobal)}/api`)
|
||||
await page.goto(`${url.global(noApiViewGlobalSlug)}/api`)
|
||||
await expect(page.locator('.not-found')).toHaveCount(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ui', () => {
|
||||
test('collection - should render preview button when `admin.preview` is set', async () => {
|
||||
const collectionWithPreview = new AdminUrlUtil(serverURL, postsSlug)
|
||||
const collectionWithPreview = new AdminUrlUtil(serverURL, postsCollectionSlug)
|
||||
await page.goto(collectionWithPreview.create)
|
||||
await page.locator('#field-title').fill(title)
|
||||
await saveDocAndAssert(page)
|
||||
@@ -406,9 +400,11 @@ describe('admin', () => {
|
||||
})
|
||||
|
||||
test('should bulk delete', async () => {
|
||||
await createPost()
|
||||
await createPost()
|
||||
await createPost()
|
||||
// First, delete all posts created by the seed
|
||||
await deleteAllPosts()
|
||||
|
||||
await Promise.all([createPost(), createPost(), createPost()])
|
||||
|
||||
await page.goto(url.list)
|
||||
await page.locator('input#select-all').check()
|
||||
await page.locator('.delete-documents__toggle').click()
|
||||
@@ -420,9 +416,10 @@ describe('admin', () => {
|
||||
})
|
||||
|
||||
test('should bulk update', async () => {
|
||||
await createPost()
|
||||
await createPost()
|
||||
await createPost()
|
||||
// First, delete all posts created by the seed
|
||||
await deleteAllPosts()
|
||||
|
||||
await Promise.all([createPost(), createPost(), createPost()])
|
||||
|
||||
const bulkTitle = 'Bulk update title'
|
||||
await page.goto(url.list)
|
||||
@@ -514,6 +511,9 @@ describe('admin', () => {
|
||||
})
|
||||
|
||||
test('search by id', async () => {
|
||||
// delete all posts created by the seed
|
||||
await deleteAllPosts()
|
||||
|
||||
const { id } = await createPost()
|
||||
await page.locator('.search-filter__input').fill(id)
|
||||
const tableItems = page.locator(tableRowLocator)
|
||||
@@ -534,6 +534,9 @@ describe('admin', () => {
|
||||
})
|
||||
|
||||
test('toggle columns', async () => {
|
||||
// delete all posts created by the seed
|
||||
await deleteAllPosts()
|
||||
|
||||
const columnCountLocator = 'table > thead > tr > th'
|
||||
await createPost()
|
||||
|
||||
@@ -592,6 +595,9 @@ describe('admin', () => {
|
||||
})
|
||||
|
||||
test('filter rows', async () => {
|
||||
// delete all posts created by the seed
|
||||
await deleteAllPosts()
|
||||
|
||||
const { id } = await createPost({ title: 'post1' })
|
||||
await createPost({ title: 'post2' })
|
||||
|
||||
@@ -923,6 +929,9 @@ describe('admin', () => {
|
||||
|
||||
describe('multi-select', () => {
|
||||
beforeEach(async () => {
|
||||
// delete all posts created by the seed
|
||||
await deleteAllPosts()
|
||||
|
||||
await mapAsync([...Array(3)], async () => {
|
||||
await createPost()
|
||||
})
|
||||
@@ -962,12 +971,6 @@ describe('admin', () => {
|
||||
})
|
||||
|
||||
describe('pagination', () => {
|
||||
beforeAll(async () => {
|
||||
await mapAsync([...Array(11)], async () => {
|
||||
await createPost()
|
||||
})
|
||||
})
|
||||
|
||||
test('should paginate', async () => {
|
||||
const pageInfo = page.locator('.collection-list__page-info')
|
||||
const perPage = page.locator('.per-page')
|
||||
@@ -999,13 +1002,18 @@ describe('admin', () => {
|
||||
|
||||
// TODO: Troubleshoot flaky suite
|
||||
describe('sorting', () => {
|
||||
beforeAll(async () => {
|
||||
await createPost({
|
||||
number: 1,
|
||||
})
|
||||
await createPost({
|
||||
number: 2,
|
||||
})
|
||||
beforeEach(async () => {
|
||||
// delete all posts created by the seed
|
||||
await deleteAllPosts()
|
||||
|
||||
await Promise.all([
|
||||
createPost({
|
||||
number: 1,
|
||||
}),
|
||||
createPost({
|
||||
number: 2,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test('should sort', async () => {
|
||||
@@ -1082,18 +1090,26 @@ describe('admin', () => {
|
||||
|
||||
async function createPost(overrides?: Partial<Post>): Promise<Post> {
|
||||
return payload.create({
|
||||
collection: postsSlug,
|
||||
collection: postsCollectionSlug,
|
||||
data: {
|
||||
description,
|
||||
title,
|
||||
...overrides,
|
||||
},
|
||||
})
|
||||
}) as unknown as Promise<Post>
|
||||
}
|
||||
|
||||
async function clearDocs(): Promise<void> {
|
||||
await payload.delete({
|
||||
collection: postsSlug,
|
||||
where: { id: { exists: true } },
|
||||
async function deleteAllPosts() {
|
||||
const posts = await payload.find({
|
||||
collection: postsCollectionSlug,
|
||||
limit: 100,
|
||||
})
|
||||
await Promise.all([
|
||||
...posts.docs.map((post) => {
|
||||
return payload.delete({
|
||||
collection: postsCollectionSlug,
|
||||
id: post.id,
|
||||
})
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
|
||||
|
||||
import CustomEditView from '../components/views/CustomEdit'
|
||||
import { customGlobalViews1GlobalSlug } from '../slugs'
|
||||
|
||||
export const CustomGlobalViews1: GlobalConfig = {
|
||||
slug: 'custom-global-views-one',
|
||||
slug: customGlobalViews1GlobalSlug,
|
||||
versions: true,
|
||||
admin: {
|
||||
components: {
|
||||
|
||||
@@ -4,9 +4,10 @@ import CustomTabComponent from '../components/CustomTabComponent'
|
||||
import CustomDefaultEditView from '../components/views/CustomEditDefault'
|
||||
import CustomView from '../components/views/CustomTab'
|
||||
import CustomVersionsView from '../components/views/CustomVersions'
|
||||
import { customGlobalViews2GlobalSlug } from '../slugs'
|
||||
|
||||
export const CustomGlobalViews2: GlobalConfig = {
|
||||
slug: 'custom-global-views-two',
|
||||
slug: customGlobalViews2GlobalSlug,
|
||||
versions: true,
|
||||
admin: {
|
||||
components: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
|
||||
|
||||
import { globalSlug } from '../shared'
|
||||
import { globalSlug } from '../slugs'
|
||||
|
||||
export const Global: GlobalConfig = {
|
||||
slug: globalSlug,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
|
||||
|
||||
import { group1GlobalSlug } from '../shared'
|
||||
import { group1GlobalSlug } from '../slugs'
|
||||
|
||||
export const GlobalGroup1A: GlobalConfig = {
|
||||
slug: group1GlobalSlug,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
|
||||
|
||||
import { group2GlobalSlug } from '../slugs'
|
||||
|
||||
export const GlobalGroup1B: GlobalConfig = {
|
||||
slug: 'group-globals-two',
|
||||
slug: group2GlobalSlug,
|
||||
admin: {
|
||||
group: 'Group',
|
||||
},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
|
||||
|
||||
import { hiddenGlobalSlug } from '../slugs'
|
||||
|
||||
export const GlobalHidden: GlobalConfig = {
|
||||
slug: 'hidden-global',
|
||||
slug: hiddenGlobalSlug,
|
||||
admin: {
|
||||
hidden: () => true,
|
||||
},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
|
||||
import { noApiViewGlobal } from '../shared'
|
||||
|
||||
import { noApiViewGlobalSlug } from '../slugs'
|
||||
|
||||
export const GlobalNoApiView: GlobalConfig = {
|
||||
slug: noApiViewGlobal,
|
||||
slug: noApiViewGlobalSlug,
|
||||
admin: {
|
||||
hideAPIURL: true,
|
||||
},
|
||||
|
||||
1
test/admin/mocks/emptyModule.js
Normal file
1
test/admin/mocks/emptyModule.js
Normal file
@@ -0,0 +1 @@
|
||||
export default {}
|
||||
70
test/admin/seed.ts
Normal file
70
test/admin/seed.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { Payload } from '../../packages/payload/src'
|
||||
|
||||
import { devUser } from '../credentials'
|
||||
import { seedDB } from '../helpers/seed'
|
||||
import {
|
||||
collectionSlugs,
|
||||
customViews1CollectionSlug,
|
||||
customViews2CollectionSlug,
|
||||
geoCollectionSlug,
|
||||
noApiViewCollectionSlug,
|
||||
postsCollectionSlug,
|
||||
usersCollectionSlug,
|
||||
} from './slugs'
|
||||
|
||||
export async function clearAndSeedEverything(_payload: Payload) {
|
||||
return await seedDB({
|
||||
snapshotKey: 'adminTest',
|
||||
shouldResetDB: true,
|
||||
collectionSlugs,
|
||||
_payload,
|
||||
seedFunction: async (_payload) => {
|
||||
await Promise.all([
|
||||
_payload.create({
|
||||
collection: usersCollectionSlug,
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
}),
|
||||
...[...Array(11)].map(() => {
|
||||
_payload.create({
|
||||
collection: postsCollectionSlug,
|
||||
data: {
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
},
|
||||
})
|
||||
}),
|
||||
_payload.create({
|
||||
collection: customViews1CollectionSlug,
|
||||
data: {
|
||||
title: 'Custom View',
|
||||
},
|
||||
}),
|
||||
_payload.create({
|
||||
collection: customViews2CollectionSlug,
|
||||
data: {
|
||||
title: 'Custom View',
|
||||
},
|
||||
}),
|
||||
_payload.create({
|
||||
collection: geoCollectionSlug,
|
||||
data: {
|
||||
point: [7, -7],
|
||||
},
|
||||
}),
|
||||
_payload.create({
|
||||
collection: geoCollectionSlug,
|
||||
data: {
|
||||
point: [5, -5],
|
||||
},
|
||||
}),
|
||||
_payload.create({
|
||||
collection: noApiViewCollectionSlug,
|
||||
data: {},
|
||||
}),
|
||||
])
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import type { Config } from '../../../packages/payload/src/config/types'
|
||||
|
||||
import { mapAsync } from '../../../packages/payload/src/utilities/mapAsync'
|
||||
import { devUser } from '../../credentials'
|
||||
import { customViews1Slug } from '../collections/CustomViews1'
|
||||
import { customViews2Slug, noApiViewCollection, postsSlug } from '../shared'
|
||||
|
||||
export const seed: Config['onInit'] = async (payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
|
||||
await mapAsync([...Array(11)], async () => {
|
||||
await payload.create({
|
||||
collection: postsSlug,
|
||||
data: {
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: customViews1Slug,
|
||||
data: {
|
||||
title: 'Custom View',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: customViews2Slug,
|
||||
data: {
|
||||
title: 'Custom View',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'geo',
|
||||
data: {
|
||||
point: [7, -7],
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'geo',
|
||||
data: {
|
||||
point: [5, -5],
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: noApiViewCollection,
|
||||
data: {},
|
||||
})
|
||||
}
|
||||
@@ -1,21 +1,7 @@
|
||||
export const postsSlug = 'posts'
|
||||
|
||||
export const group1Collection1Slug = 'group-one-collection-ones'
|
||||
|
||||
export const group1Collection2Slug = 'group-one-collection-twos'
|
||||
|
||||
export const slugSingularLabel = 'Post'
|
||||
|
||||
export const slugPluralLabel = 'Posts'
|
||||
|
||||
export const globalSlug = 'global'
|
||||
|
||||
export const group1GlobalSlug = 'group-globals-one'
|
||||
|
||||
export const noApiViewCollection = 'collection-no-api-view'
|
||||
|
||||
export const noApiViewGlobal = 'global-no-api-view'
|
||||
|
||||
export const customViewPath = '/custom-view'
|
||||
|
||||
export const customViewTitle = 'Custom View'
|
||||
@@ -24,8 +10,6 @@ export const customNestedViewPath = `${customViewPath}/nested-view`
|
||||
|
||||
export const customNestedViewTitle = 'Custom Nested View'
|
||||
|
||||
export const customViews2Slug = 'custom-views-two'
|
||||
|
||||
export const customEditLabel = 'Custom Edit Label'
|
||||
|
||||
export const customTabLabel = 'Custom Tab Label'
|
||||
|
||||
41
test/admin/slugs.ts
Normal file
41
test/admin/slugs.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
export const usersCollectionSlug = 'users' as const
|
||||
export const customViews1CollectionSlug = 'custom-views-one' as const
|
||||
export const customViews2CollectionSlug = 'custom-views-two' as const
|
||||
export const geoCollectionSlug = 'geo' as const
|
||||
export const postsCollectionSlug = 'posts' as const
|
||||
export const group1Collection1Slug = 'group-one-collection-ones' as const
|
||||
export const group1Collection2Slug = 'group-one-collection-twos' as const
|
||||
export const group2Collection1Slug = 'group-two-collection-ones' as const
|
||||
export const group2Collection2Slug = 'group-two-collection-twos' as const
|
||||
export const hiddenCollectionSlug = 'hidden-collection' as const
|
||||
export const noApiViewCollectionSlug = 'collection-no-api-view' as const
|
||||
export const collectionSlugs = [
|
||||
usersCollectionSlug,
|
||||
customViews1CollectionSlug,
|
||||
customViews2CollectionSlug,
|
||||
geoCollectionSlug,
|
||||
postsCollectionSlug,
|
||||
group1Collection1Slug,
|
||||
group1Collection2Slug,
|
||||
group2Collection1Slug,
|
||||
group2Collection2Slug,
|
||||
hiddenCollectionSlug,
|
||||
noApiViewCollectionSlug,
|
||||
]
|
||||
|
||||
export const customGlobalViews1GlobalSlug = 'custom-global-views-one' as const
|
||||
export const customGlobalViews2GlobalSlug = 'custom-global-views-two' as const
|
||||
export const globalSlug = 'global' as const
|
||||
export const group1GlobalSlug = 'group-globals-one' as const
|
||||
export const group2GlobalSlug = 'group-globals-two' as const
|
||||
export const hiddenGlobalSlug = 'hidden-global' as const
|
||||
export const noApiViewGlobalSlug = 'global-no-api-view'
|
||||
export const globalSlugs = [
|
||||
customGlobalViews1GlobalSlug,
|
||||
customGlobalViews2GlobalSlug,
|
||||
globalSlug,
|
||||
group1GlobalSlug,
|
||||
group2GlobalSlug,
|
||||
hiddenGlobalSlug,
|
||||
noApiViewGlobalSlug,
|
||||
]
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { arrayFieldsSlug } from '../../slugs'
|
||||
import { ArrayRowLabel } from './LabelComponent'
|
||||
|
||||
export const arrayDefaultValue = [{ text: 'row one' }, { text: 'row two' }]
|
||||
|
||||
export const arrayFieldsSlug = 'array-fields'
|
||||
|
||||
const ArrayFields: CollectionConfig = {
|
||||
slug: arrayFieldsSlug,
|
||||
admin: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
import type { BlockField } from '../../../../packages/payload/src/fields/config/types'
|
||||
|
||||
import { blockFieldsSlug } from '../../slugs'
|
||||
import { AddCustomBlocks } from './components/AddCustomBlocks'
|
||||
|
||||
export const getBlocksFieldSeedData = (prefix?: string): any => [
|
||||
@@ -149,7 +150,7 @@ export const getBlocksField = (prefix?: string): BlockField => ({
|
||||
})
|
||||
|
||||
const BlockFields: CollectionConfig = {
|
||||
slug: 'block-fields',
|
||||
slug: blockFieldsSlug,
|
||||
fields: [
|
||||
getBlocksField(),
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { checkboxFieldsSlug } from '../../slugs'
|
||||
|
||||
const CheckboxFields: CollectionConfig = {
|
||||
slug: 'checkbox-fields',
|
||||
slug: checkboxFieldsSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'checkbox',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
import type { CodeField } from '../../payload-types'
|
||||
|
||||
import { codeFieldsSlug } from '../../slugs'
|
||||
|
||||
const Code: CollectionConfig = {
|
||||
fields: [
|
||||
{
|
||||
@@ -39,7 +41,7 @@ const Code: CollectionConfig = {
|
||||
type: 'code',
|
||||
},
|
||||
],
|
||||
slug: 'code-fields',
|
||||
slug: codeFieldsSlug,
|
||||
}
|
||||
|
||||
export const codeDoc: Partial<CodeField> = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { collapsibleFieldsSlug } from '../../slugs'
|
||||
import { CollapsibleLabelComponent } from './LabelComponent'
|
||||
import { collapsibleFieldsSlug } from './shared'
|
||||
|
||||
const CollapsibleFields: CollectionConfig = {
|
||||
slug: collapsibleFieldsSlug,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const collapsibleFieldsSlug = 'collapsible-fields'
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { conditionalLogicSlug } from '../../slugs'
|
||||
|
||||
const ConditionalLogic: CollectionConfig = {
|
||||
slug: 'conditional-logic',
|
||||
slug: conditionalLogicSlug,
|
||||
admin: {
|
||||
useAsTitle: 'text',
|
||||
},
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { dateFieldsSlug } from '../../slugs'
|
||||
|
||||
export const defaultText = 'default-text'
|
||||
|
||||
const DateFields: CollectionConfig = {
|
||||
slug: 'date-fields',
|
||||
slug: dateFieldsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'default',
|
||||
},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { groupFieldsSlug } from '../../slugs'
|
||||
|
||||
export const groupDefaultValue = 'set from parent'
|
||||
export const groupDefaultChild = 'child takes priority'
|
||||
export const groupFieldsSlug = 'group-fields'
|
||||
|
||||
const GroupFields: CollectionConfig = {
|
||||
slug: groupFieldsSlug,
|
||||
|
||||
@@ -4,6 +4,8 @@ import type {
|
||||
} from '../../../../packages/payload/src/collections/config/types'
|
||||
import type { IndexedField } from '../../payload-types'
|
||||
|
||||
import { indexedFieldsSlug } from '../../slugs'
|
||||
|
||||
const beforeDuplicate: BeforeDuplicate<IndexedField> = ({ data }) => {
|
||||
return {
|
||||
...data,
|
||||
@@ -22,7 +24,7 @@ const beforeDuplicate: BeforeDuplicate<IndexedField> = ({ data }) => {
|
||||
}
|
||||
|
||||
const IndexedFields: CollectionConfig = {
|
||||
slug: 'indexed-fields',
|
||||
slug: indexedFieldsSlug,
|
||||
// used to assert that versions also get indexes
|
||||
admin: {
|
||||
hooks: {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { jsonFieldsSlug } from '../../slugs'
|
||||
|
||||
type JSONField = {
|
||||
createdAt: string
|
||||
id: string
|
||||
@@ -17,7 +19,7 @@ const JSON: CollectionConfig = {
|
||||
type: 'json',
|
||||
},
|
||||
],
|
||||
slug: 'json-fields',
|
||||
slug: jsonFieldsSlug,
|
||||
versions: {
|
||||
maxPerDoc: 1,
|
||||
},
|
||||
|
||||
8
test/fields/collections/Lexical/data.ts
Normal file
8
test/fields/collections/Lexical/data.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { generateLexicalRichText } from './generateLexicalRichText'
|
||||
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData'
|
||||
|
||||
export const lexicalRichTextDoc = {
|
||||
title: 'Rich Text',
|
||||
richTextLexicalCustomFields: generateLexicalRichText(),
|
||||
richTextLexicalWithLexicalPluginData: payloadPluginLexicalData,
|
||||
}
|
||||
@@ -5,10 +5,12 @@ import {
|
||||
HTMLConverterFeature,
|
||||
LexicalPluginToLexicalFeature,
|
||||
LinkFeature,
|
||||
TestRecorderFeature,
|
||||
TreeviewFeature,
|
||||
UploadFeature,
|
||||
lexicalEditor,
|
||||
} from '../../../../packages/richtext-lexical/src'
|
||||
import { lexicalFieldsSlug } from '../../slugs'
|
||||
import {
|
||||
RelationshipBlock,
|
||||
RichTextBlock,
|
||||
@@ -21,7 +23,7 @@ import { generateLexicalRichText } from './generateLexicalRichText'
|
||||
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData'
|
||||
|
||||
export const LexicalFields: CollectionConfig = {
|
||||
slug: 'lexical-fields',
|
||||
slug: lexicalFieldsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
listSearchableFields: ['title', 'richTextLexicalCustomFields'],
|
||||
@@ -35,6 +37,27 @@ export const LexicalFields: CollectionConfig = {
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'richTextLexicalSimple',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
TestRecorderFeature(),
|
||||
TreeviewFeature(),
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
RichTextBlock,
|
||||
TextBlock,
|
||||
UploadAndRichTextBlock,
|
||||
SelectFieldBlock,
|
||||
RelationshipBlock,
|
||||
SubBlockBlock,
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'richTextLexicalCustomFields',
|
||||
type: 'richText',
|
||||
@@ -42,6 +65,7 @@ export const LexicalFields: CollectionConfig = {
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
TestRecorderFeature(),
|
||||
TreeviewFeature(),
|
||||
HTMLConverterFeature(),
|
||||
LinkFeature({
|
||||
@@ -126,9 +150,3 @@ export const LexicalFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const LexicalRichTextDoc = {
|
||||
title: 'Rich Text',
|
||||
richTextLexicalCustomFields: generateLexicalRichText(),
|
||||
richTextLexicalWithLexicalPluginData: payloadPluginLexicalData,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,958 @@
|
||||
export const payloadPluginLexicalData = {
|
||||
words: 49,
|
||||
preview:
|
||||
'paragraph text bold italic underline and all subscript superscript code internal link external link…',
|
||||
comments: [],
|
||||
characters: 493,
|
||||
jsonContent: {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'paragraph text ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 1,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'bold',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 2,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'italic',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 8,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'underline',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' and ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 11,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'all',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 32,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'subscript',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 64,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'superscript',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 16,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'code',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'internal link',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'link',
|
||||
version: 2,
|
||||
attributes: {
|
||||
newTab: true,
|
||||
linkType: 'internal',
|
||||
doc: {
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
relationTo: 'text-fields',
|
||||
data: {}, // populated data
|
||||
},
|
||||
text: 'internal link',
|
||||
},
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'external link',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'link',
|
||||
version: 2,
|
||||
attributes: {
|
||||
newTab: true,
|
||||
nofollow: false,
|
||||
url: 'https://fewfwef.de',
|
||||
linkType: 'custom',
|
||||
text: 'external link',
|
||||
},
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' s. ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 4,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'strikethrough',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'heading 1',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'heading',
|
||||
version: 1,
|
||||
tag: 'h1',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'heading 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'heading',
|
||||
version: 1,
|
||||
tag: 'h2',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'bullet list ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 3',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list',
|
||||
version: 1,
|
||||
listType: 'bullet',
|
||||
start: 1,
|
||||
tag: 'ul',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'ordered list',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 3',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list',
|
||||
version: 1,
|
||||
listType: 'number',
|
||||
start: 1,
|
||||
tag: 'ol',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'check list',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 3',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list',
|
||||
version: 1,
|
||||
listType: 'check',
|
||||
start: 1,
|
||||
tag: 'ul',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'quoteeee',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'quote',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'code block line ',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '1',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
highlightType: 'number',
|
||||
},
|
||||
{
|
||||
type: 'linebreak',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'code block line ',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
highlightType: 'number',
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'code',
|
||||
version: 1,
|
||||
language: 'javascript',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'Upload:',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
type: 'upload',
|
||||
version: 1,
|
||||
rawImagePayload: {
|
||||
value: {
|
||||
id: '{{UPLOAD_DOC_ID}}',
|
||||
},
|
||||
relationTo: 'uploads',
|
||||
},
|
||||
caption: {
|
||||
editorState: {
|
||||
root: {
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'upload caption',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'root',
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
showCaption: true,
|
||||
data: {}, // populated upload data
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table top left',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 3,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table top right',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablerow',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table bottom left',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table bottom right',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 0,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablerow',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'table',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
cells: [
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'kafuj',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'iussu',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
],
|
||||
height: null,
|
||||
id: 'tnied',
|
||||
},
|
||||
{
|
||||
cells: [
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'hpnnv',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'ndteg',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'normal',
|
||||
width: null,
|
||||
},
|
||||
],
|
||||
height: null,
|
||||
id: 'rxyey',
|
||||
},
|
||||
{
|
||||
cells: [
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'rtueq',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'vrzoi',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'normal',
|
||||
width: null,
|
||||
},
|
||||
],
|
||||
height: null,
|
||||
id: 'qzglv',
|
||||
},
|
||||
],
|
||||
type: 'tablesheet',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'youtube:',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'youtube',
|
||||
version: 1,
|
||||
videoID: '3Nwt3qu0_UY',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
equation: '3+3',
|
||||
inline: true,
|
||||
type: 'equation',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'collapsible title',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'collapsible-title',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'collabsible conteent',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'collapsible-content',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'collapsible-container',
|
||||
version: 1,
|
||||
open: true,
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
type: 'horizontalrule',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
},
|
||||
},
|
||||
}
|
||||
73
test/fields/collections/LexicalMigrate/index.ts
Normal file
73
test/fields/collections/LexicalMigrate/index.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import {
|
||||
LexicalPluginToLexicalFeature,
|
||||
LinkFeature,
|
||||
TreeviewFeature,
|
||||
UploadFeature,
|
||||
lexicalEditor,
|
||||
} from '../../../../packages/richtext-lexical/src'
|
||||
import { lexicalMigrateFieldsSlug } from '../../slugs'
|
||||
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData'
|
||||
|
||||
export const LexicalMigrateFields: CollectionConfig = {
|
||||
slug: lexicalMigrateFieldsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
listSearchableFields: ['title', 'richTextLexicalCustomFields'],
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'richTextLexicalWithLexicalPluginData',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
LexicalPluginToLexicalFeature(),
|
||||
TreeviewFeature(),
|
||||
LinkFeature({
|
||||
fields: [
|
||||
{
|
||||
name: 'rel',
|
||||
label: 'Rel Attribute',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
options: ['noopener', 'noreferrer', 'nofollow'],
|
||||
admin: {
|
||||
description:
|
||||
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
UploadFeature({
|
||||
collections: {
|
||||
uploads: {
|
||||
fields: [
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor(),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const LexicalRichTextDoc = {
|
||||
title: 'Rich Text',
|
||||
richTextLexicalWithLexicalPluginData: payloadPluginLexicalData,
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { numberFieldsSlug } from '../../slugs'
|
||||
|
||||
export const defaultNumber = 5
|
||||
|
||||
const NumberFields: CollectionConfig = {
|
||||
slug: 'number-fields',
|
||||
slug: numberFieldsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'number',
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
export const pointFieldsSlug = 'point-fields'
|
||||
import { pointFieldsSlug } from '../../slugs'
|
||||
|
||||
const PointFields: CollectionConfig = {
|
||||
slug: pointFieldsSlug,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { radioFieldsSlug } from '../../slugs'
|
||||
|
||||
const RadioFields: CollectionConfig = {
|
||||
slug: 'radio-fields',
|
||||
slug: radioFieldsSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'radio',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
export const relationshipFieldsSlug = 'relationship-fields'
|
||||
import { relationshipFieldsSlug } from '../../slugs'
|
||||
|
||||
const RelationshipFields: CollectionConfig = {
|
||||
fields: [
|
||||
|
||||
135
test/fields/collections/RichText/data.ts
Normal file
135
test/fields/collections/RichText/data.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { generateLexicalRichText } from './generateLexicalRichText'
|
||||
import { generateSlateRichText } from './generateSlateRichText'
|
||||
|
||||
export const richTextBlocks = [
|
||||
{
|
||||
blockType: 'textBlock',
|
||||
text: 'Regular text',
|
||||
},
|
||||
{
|
||||
blockType: 'richTextBlock',
|
||||
text: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Rich text',
|
||||
},
|
||||
],
|
||||
type: 'h1',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
export const richTextDoc = {
|
||||
title: 'Rich Text',
|
||||
selectHasMany: ['one', 'five'],
|
||||
richText: generateSlateRichText(),
|
||||
richTextReadOnly: generateSlateRichText(),
|
||||
richTextCustomFields: generateSlateRichText(),
|
||||
richTextLexicalCustomFields: generateLexicalRichText(),
|
||||
blocks: richTextBlocks,
|
||||
}
|
||||
|
||||
export const richTextBulletsDoc = {
|
||||
title: 'Bullets and Indentation',
|
||||
richTextLexicalCustomFields: generateLexicalRichText(),
|
||||
richText: [
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'I am semantically connected to my sub-bullets',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'I am sub-bullets that are semantically connected to the parent bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Normal bullet',
|
||||
},
|
||||
],
|
||||
type: 'li',
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'I am the old style of sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'Another normal bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'This text precedes a nested list',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'I am a sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'And I am another sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -10,12 +10,13 @@ import {
|
||||
} from '../../../../packages/richtext-lexical/src'
|
||||
import { lexicalHTML } from '../../../../packages/richtext-lexical/src/field/features/converters/html/field'
|
||||
import { slateEditor } from '../../../../packages/richtext-slate/src'
|
||||
import { richTextFieldsSlug } from '../../slugs'
|
||||
import { RelationshipBlock, SelectFieldBlock, TextBlock, UploadAndRichTextBlock } from './blocks'
|
||||
import { generateLexicalRichText } from './generateLexicalRichText'
|
||||
import { generateSlateRichText } from './generateSlateRichText'
|
||||
|
||||
const RichTextFields: CollectionConfig = {
|
||||
slug: 'rich-text-fields',
|
||||
slug: richTextFieldsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
@@ -317,137 +318,4 @@ const RichTextFields: CollectionConfig = {
|
||||
],
|
||||
}
|
||||
|
||||
export const richTextBulletsDoc = {
|
||||
title: 'Bullets and Indentation',
|
||||
richTextLexicalCustomFields: generateLexicalRichText(),
|
||||
richText: [
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'I am semantically connected to my sub-bullets',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'I am sub-bullets that are semantically connected to the parent bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Normal bullet',
|
||||
},
|
||||
],
|
||||
type: 'li',
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'I am the old style of sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'Another normal bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'This text precedes a nested list',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'I am a sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'And I am another sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const richTextBlocks = [
|
||||
{
|
||||
blockType: 'textBlock',
|
||||
text: 'Regular text',
|
||||
},
|
||||
{
|
||||
blockType: 'richTextBlock',
|
||||
text: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Rich text',
|
||||
},
|
||||
],
|
||||
type: 'h1',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
export const richTextDoc = {
|
||||
title: 'Rich Text',
|
||||
selectHasMany: ['one', 'five'],
|
||||
richText: generateSlateRichText(),
|
||||
richTextReadOnly: generateSlateRichText(),
|
||||
richTextCustomFields: generateSlateRichText(),
|
||||
richTextLexicalCustomFields: generateLexicalRichText(),
|
||||
blocks: richTextBlocks,
|
||||
}
|
||||
|
||||
export default RichTextFields
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
export const rowFieldsSlug = 'row-fields'
|
||||
import { rowFieldsSlug } from '../../slugs'
|
||||
|
||||
const RowFields: CollectionConfig = {
|
||||
slug: rowFieldsSlug,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { selectFieldsSlug } from '../../slugs'
|
||||
|
||||
const SelectFields: CollectionConfig = {
|
||||
slug: 'select-fields',
|
||||
slug: selectFieldsSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'select',
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export const tabsSlug = 'tabs-fields'
|
||||
|
||||
export const namedTabText = 'Some text in a named tab'
|
||||
export const namedTabDefaultValue = 'default text inside of a named tab'
|
||||
export const localizedTextValue = 'localized text'
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { tabsFieldsSlug } from '../../slugs'
|
||||
import { getBlocksField, getBlocksFieldSeedData } from '../Blocks'
|
||||
import { UIField } from './UIField'
|
||||
import { localizedTextValue, namedTabDefaultValue, namedTabText, tabsSlug } from './constants'
|
||||
import { localizedTextValue, namedTabDefaultValue, namedTabText } from './constants'
|
||||
|
||||
const TabsFields: CollectionConfig = {
|
||||
slug: tabsSlug,
|
||||
slug: tabsFieldsSlug,
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { textFieldsSlug } from '../../slugs'
|
||||
|
||||
export const defaultText = 'default-text'
|
||||
export const textFieldsSlug = 'text-fields'
|
||||
|
||||
const TextFields: CollectionConfig = {
|
||||
slug: textFieldsSlug,
|
||||
|
||||
@@ -2,8 +2,10 @@ import path from 'path'
|
||||
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { uploadsSlug } from '../../slugs'
|
||||
|
||||
const Uploads: CollectionConfig = {
|
||||
slug: 'uploads',
|
||||
slug: uploadsSlug,
|
||||
upload: {
|
||||
staticDir: path.resolve(__dirname, './uploads'),
|
||||
},
|
||||
@@ -15,7 +17,7 @@ const Uploads: CollectionConfig = {
|
||||
{
|
||||
type: 'upload',
|
||||
name: 'media',
|
||||
relationTo: 'uploads',
|
||||
relationTo: uploadsSlug,
|
||||
filterOptions: {
|
||||
mimeType: {
|
||||
equals: 'image/png',
|
||||
|
||||
@@ -2,8 +2,10 @@ import path from 'path'
|
||||
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { uploads2Slug } from '../../slugs'
|
||||
|
||||
const Uploads2: CollectionConfig = {
|
||||
slug: 'uploads2',
|
||||
slug: uploads2Slug,
|
||||
upload: {
|
||||
staticDir: path.resolve(__dirname, './uploads2'),
|
||||
},
|
||||
@@ -19,7 +21,7 @@ const Uploads2: CollectionConfig = {
|
||||
{
|
||||
type: 'upload',
|
||||
name: 'media',
|
||||
relationTo: 'uploads2',
|
||||
relationTo: uploads2Slug,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ import path from 'path'
|
||||
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { uploads3Slug } from '../../slugs'
|
||||
|
||||
const Uploads3: CollectionConfig = {
|
||||
slug: 'uploads3',
|
||||
slug: uploads3Slug,
|
||||
upload: {
|
||||
staticDir: path.resolve(__dirname, './uploads3'),
|
||||
},
|
||||
@@ -18,7 +20,7 @@ const Uploads3: CollectionConfig = {
|
||||
{
|
||||
type: 'upload',
|
||||
name: 'media',
|
||||
relationTo: 'uploads3',
|
||||
relationTo: uploads3Slug,
|
||||
},
|
||||
{
|
||||
type: 'richText',
|
||||
|
||||
@@ -1,33 +1,75 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import getFileByPath from '../../packages/payload/src/uploads/getFileByPath'
|
||||
import type { CollectionConfig } from '../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
|
||||
import { devUser } from '../credentials'
|
||||
import ArrayFields, { arrayDoc } from './collections/Array'
|
||||
import BlockFields, { blocksDoc } from './collections/Blocks'
|
||||
import ArrayFields from './collections/Array'
|
||||
import BlockFields from './collections/Blocks'
|
||||
import CheckboxFields from './collections/Checkbox'
|
||||
import CodeFields, { codeDoc } from './collections/Code'
|
||||
import CollapsibleFields, { collapsibleDoc } from './collections/Collapsible'
|
||||
import ConditionalLogic, { conditionalLogicDoc } from './collections/ConditionalLogic'
|
||||
import DateFields, { dateDoc } from './collections/Date'
|
||||
import GroupFields, { groupDoc } from './collections/Group'
|
||||
import CodeFields from './collections/Code'
|
||||
import CollapsibleFields from './collections/Collapsible'
|
||||
import ConditionalLogic from './collections/ConditionalLogic'
|
||||
import DateFields from './collections/Date'
|
||||
import GroupFields from './collections/Group'
|
||||
import IndexedFields from './collections/Indexed'
|
||||
import JSONFields, { jsonDoc } from './collections/JSON'
|
||||
import { LexicalFields, LexicalRichTextDoc } from './collections/Lexical'
|
||||
import NumberFields, { numberDoc } from './collections/Number'
|
||||
import PointFields, { pointDoc } from './collections/Point'
|
||||
import RadioFields, { radiosDoc } from './collections/Radio'
|
||||
import JSONFields from './collections/JSON'
|
||||
import { LexicalFields } from './collections/Lexical'
|
||||
import { LexicalMigrateFields } from './collections/LexicalMigrate'
|
||||
import NumberFields from './collections/Number'
|
||||
import PointFields from './collections/Point'
|
||||
import RadioFields from './collections/Radio'
|
||||
import RelationshipFields from './collections/Relationship'
|
||||
import RichTextFields, { richTextBulletsDoc, richTextDoc } from './collections/RichText'
|
||||
import RichTextFields from './collections/RichText'
|
||||
import RowFields from './collections/Row'
|
||||
import SelectFields, { selectsDoc } from './collections/Select'
|
||||
import TabsFields, { tabsDoc } from './collections/Tabs'
|
||||
import TextFields, { textDoc, textFieldsSlug } from './collections/Text'
|
||||
import Uploads, { uploadsDoc } from './collections/Upload'
|
||||
import SelectFields from './collections/Select'
|
||||
import TabsFields from './collections/Tabs'
|
||||
import TextFields from './collections/Text'
|
||||
import Uploads from './collections/Upload'
|
||||
import Uploads2 from './collections/Upload2'
|
||||
import Uploads3 from './collections/Uploads3'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
|
||||
export const collectionSlugs: CollectionConfig[] = [
|
||||
LexicalFields,
|
||||
LexicalMigrateFields,
|
||||
{
|
||||
admin: {
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
auth: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'canViewConditionalField',
|
||||
defaultValue: true,
|
||||
type: 'checkbox',
|
||||
},
|
||||
],
|
||||
slug: 'users',
|
||||
},
|
||||
ArrayFields,
|
||||
BlockFields,
|
||||
CheckboxFields,
|
||||
CodeFields,
|
||||
CollapsibleFields,
|
||||
ConditionalLogic,
|
||||
DateFields,
|
||||
RadioFields,
|
||||
GroupFields,
|
||||
RowFields,
|
||||
IndexedFields,
|
||||
JSONFields,
|
||||
NumberFields,
|
||||
PointFields,
|
||||
RelationshipFields,
|
||||
RichTextFields,
|
||||
SelectFields,
|
||||
TabsFields,
|
||||
TextFields,
|
||||
Uploads,
|
||||
Uploads2,
|
||||
Uploads3,
|
||||
]
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
admin: {
|
||||
@@ -42,139 +84,13 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
}),
|
||||
},
|
||||
collections: [
|
||||
LexicalFields,
|
||||
{
|
||||
admin: {
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
auth: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'canViewConditionalField',
|
||||
defaultValue: true,
|
||||
type: 'checkbox',
|
||||
},
|
||||
],
|
||||
slug: 'users',
|
||||
},
|
||||
ArrayFields,
|
||||
BlockFields,
|
||||
CheckboxFields,
|
||||
CodeFields,
|
||||
CollapsibleFields,
|
||||
ConditionalLogic,
|
||||
DateFields,
|
||||
RadioFields,
|
||||
GroupFields,
|
||||
RowFields,
|
||||
IndexedFields,
|
||||
JSONFields,
|
||||
NumberFields,
|
||||
PointFields,
|
||||
RelationshipFields,
|
||||
RichTextFields,
|
||||
SelectFields,
|
||||
TabsFields,
|
||||
TextFields,
|
||||
Uploads,
|
||||
Uploads2,
|
||||
Uploads3,
|
||||
],
|
||||
collections: collectionSlugs,
|
||||
localization: {
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
locales: ['en', 'es'],
|
||||
},
|
||||
onInit: async (payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
|
||||
const createdArrayDoc = await payload.create({ collection: 'array-fields', data: arrayDoc })
|
||||
await payload.create({ collection: 'collapsible-fields', data: collapsibleDoc })
|
||||
await payload.create({ collection: 'conditional-logic', data: conditionalLogicDoc })
|
||||
await payload.create({ collection: 'group-fields', data: groupDoc })
|
||||
await payload.create({ collection: 'select-fields', data: selectsDoc })
|
||||
await payload.create({ collection: 'radio-fields', data: radiosDoc })
|
||||
await payload.create({ collection: 'tabs-fields', data: tabsDoc })
|
||||
await payload.create({ collection: 'point-fields', data: pointDoc })
|
||||
await payload.create({ collection: 'date-fields', data: dateDoc })
|
||||
await payload.create({ collection: 'code-fields', data: codeDoc })
|
||||
await payload.create({ collection: 'json-fields', data: jsonDoc })
|
||||
|
||||
const createdTextDoc = await payload.create({ collection: textFieldsSlug, data: textDoc })
|
||||
|
||||
const uploadsDir = path.resolve(__dirname, './collections/Upload/uploads')
|
||||
|
||||
if (fs.existsSync(uploadsDir))
|
||||
fs.readdirSync(uploadsDir).forEach((f) => fs.rmSync(`${uploadsDir}/${f}`))
|
||||
|
||||
const pngPath = path.resolve(__dirname, './uploads/payload.png')
|
||||
const pngFile = await getFileByPath(pngPath)
|
||||
const createdPNGDoc = await payload.create({ collection: 'uploads', data: {}, file: pngFile })
|
||||
|
||||
const jpgPath = path.resolve(__dirname, './collections/Upload/payload.jpg')
|
||||
const jpgFile = await getFileByPath(jpgPath)
|
||||
const createdJPGDoc = await payload.create({
|
||||
collection: 'uploads',
|
||||
data: {
|
||||
...uploadsDoc,
|
||||
media: createdPNGDoc.id,
|
||||
},
|
||||
file: jpgFile,
|
||||
})
|
||||
|
||||
const formattedID =
|
||||
payload.db.defaultIDType === 'number' ? createdArrayDoc.id : `"${createdArrayDoc.id}"`
|
||||
|
||||
const formattedJPGID =
|
||||
payload.db.defaultIDType === 'number' ? createdJPGDoc.id : `"${createdJPGDoc.id}"`
|
||||
|
||||
const formattedTextID =
|
||||
payload.db.defaultIDType === 'number' ? createdTextDoc.id : `"${createdTextDoc.id}"`
|
||||
|
||||
const richTextDocWithRelId = JSON.parse(
|
||||
JSON.stringify(richTextDoc)
|
||||
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, formattedID)
|
||||
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, formattedJPGID)
|
||||
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, formattedTextID),
|
||||
)
|
||||
const richTextBulletsDocWithRelId = JSON.parse(
|
||||
JSON.stringify(richTextBulletsDoc)
|
||||
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, formattedID)
|
||||
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, formattedJPGID)
|
||||
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, formattedTextID),
|
||||
)
|
||||
|
||||
const lexicalRichTextDocWithRelId = JSON.parse(
|
||||
JSON.stringify(LexicalRichTextDoc)
|
||||
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, formattedID)
|
||||
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, formattedJPGID)
|
||||
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, formattedTextID),
|
||||
)
|
||||
await payload.create({ collection: 'lexical-fields', data: lexicalRichTextDocWithRelId })
|
||||
|
||||
const richTextDocWithRelationship = { ...richTextDocWithRelId }
|
||||
|
||||
await payload.create({ collection: 'rich-text-fields', data: richTextBulletsDocWithRelId })
|
||||
await payload.create({ collection: 'rich-text-fields', data: richTextDocWithRelationship })
|
||||
|
||||
await payload.create({ collection: 'number-fields', data: { number: 2 } })
|
||||
await payload.create({ collection: 'number-fields', data: { number: 3 } })
|
||||
await payload.create({ collection: 'number-fields', data: numberDoc })
|
||||
|
||||
const blocksDocWithRichText = { ...blocksDoc }
|
||||
|
||||
// @ts-ignore
|
||||
blocksDocWithRichText.blocks[0].richText = richTextDocWithRelationship.richText
|
||||
// @ts-ignore
|
||||
blocksDocWithRichText.localizedBlocks[0].richText = richTextDocWithRelationship.richText
|
||||
|
||||
await payload.create({ collection: 'block-fields', data: blocksDocWithRichText })
|
||||
await clearAndSeedEverything(payload)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -10,19 +10,25 @@ import { saveDocAndAssert, saveDocHotkeyAndAssert } from '../helpers'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
|
||||
import { initPayloadE2E } from '../helpers/configHelpers'
|
||||
import { RESTClient } from '../helpers/rest'
|
||||
import { collapsibleFieldsSlug } from './collections/Collapsible/shared'
|
||||
import { jsonDoc } from './collections/JSON'
|
||||
import { numberDoc } from './collections/Number'
|
||||
import { pointFieldsSlug } from './collections/Point'
|
||||
import { relationshipFieldsSlug } from './collections/Relationship'
|
||||
import { tabsSlug } from './collections/Tabs/constants'
|
||||
import { textDoc, textFieldsSlug } from './collections/Text'
|
||||
import { textDoc } from './collections/Text'
|
||||
import { lexicalE2E } from './lexicalE2E'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
import {
|
||||
collapsibleFieldsSlug,
|
||||
pointFieldsSlug,
|
||||
relationshipFieldsSlug,
|
||||
tabsFieldsSlug,
|
||||
textFieldsSlug,
|
||||
} from './slugs'
|
||||
|
||||
const { afterEach, beforeAll, describe } = test
|
||||
const { afterEach, beforeAll, describe, beforeEach } = test
|
||||
|
||||
let client: RESTClient
|
||||
let page: Page
|
||||
let serverURL
|
||||
let serverURL: string
|
||||
// If we want to make this run in parallel: test.describe.configure({ mode: 'parallel' })
|
||||
|
||||
describe('fields', () => {
|
||||
beforeAll(async ({ browser }) => {
|
||||
@@ -34,7 +40,12 @@ describe('fields', () => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await clearAndSeedEverything(payload)
|
||||
await client.logout()
|
||||
client = new RESTClient(null, { serverURL, defaultSlug: 'users' })
|
||||
await client.login()
|
||||
})
|
||||
describe('text', () => {
|
||||
let url: AdminUrlUtil
|
||||
beforeAll(() => {
|
||||
@@ -146,7 +157,7 @@ describe('fields', () => {
|
||||
|
||||
describe('indexed', () => {
|
||||
let url: AdminUrlUtil
|
||||
beforeAll(() => {
|
||||
beforeEach(() => {
|
||||
url = new AdminUrlUtil(serverURL, 'indexed-fields')
|
||||
})
|
||||
|
||||
@@ -162,13 +173,14 @@ describe('fields', () => {
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
await page.locator('#field-text').fill('test')
|
||||
await page.locator('#field-uniqueText').fill(uniqueText)
|
||||
|
||||
// attempt to save
|
||||
await page.locator('#action-save').click()
|
||||
await page.click('#action-save', { delay: 100 })
|
||||
|
||||
// toast error
|
||||
await expect(page.locator('.Toastify')).toContainText(
|
||||
@@ -269,7 +281,7 @@ describe('fields', () => {
|
||||
let url: AdminUrlUtil
|
||||
let filledGroupPoint
|
||||
let emptyGroupPoint
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
url = new AdminUrlUtil(serverURL, pointFieldsSlug)
|
||||
filledGroupPoint = await payload.create({
|
||||
collection: pointFieldsSlug,
|
||||
@@ -682,7 +694,7 @@ describe('fields', () => {
|
||||
describe('tabs', () => {
|
||||
let url: AdminUrlUtil
|
||||
beforeAll(() => {
|
||||
url = new AdminUrlUtil(serverURL, tabsSlug)
|
||||
url = new AdminUrlUtil(serverURL, tabsFieldsSlug)
|
||||
})
|
||||
|
||||
test('should fill and retain a new value within a tab while switching tabs', async () => {
|
||||
@@ -750,7 +762,7 @@ describe('fields', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('lexical', lexicalE2E(client, page, serverURL))
|
||||
describe('richText', () => {
|
||||
async function navigateToRichTextFields() {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
|
||||
@@ -1473,7 +1485,7 @@ describe('fields', () => {
|
||||
url = new AdminUrlUtil(serverURL, 'uploads')
|
||||
})
|
||||
|
||||
test('should upload files', async () => {
|
||||
async function uploadImage() {
|
||||
await page.goto(url.create)
|
||||
|
||||
// create a jpg upload
|
||||
@@ -1484,10 +1496,15 @@ describe('fields', () => {
|
||||
await page.locator('#action-save').click()
|
||||
await wait(200)
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
}
|
||||
|
||||
test('should upload files', async () => {
|
||||
await uploadImage()
|
||||
})
|
||||
|
||||
// test that the image renders
|
||||
test('should render uploaded image', async () => {
|
||||
await uploadImage()
|
||||
await expect(page.locator('.file-field .file-details img')).toHaveAttribute(
|
||||
'src',
|
||||
'/uploads/payload-1.jpg',
|
||||
@@ -1495,6 +1512,7 @@ describe('fields', () => {
|
||||
})
|
||||
|
||||
test('should upload using the document drawer', async () => {
|
||||
await uploadImage()
|
||||
// Open the media drawer and create a png upload
|
||||
await page.locator('.field-type.upload .upload__toggler.doc-drawer__toggler').click()
|
||||
await page
|
||||
@@ -1521,10 +1539,20 @@ describe('fields', () => {
|
||||
})
|
||||
|
||||
test('should clear selected upload', async () => {
|
||||
await uploadImage()
|
||||
await page.locator('.field-type.upload .upload__toggler.doc-drawer__toggler').click()
|
||||
await page
|
||||
.locator('[id^=doc-drawer_uploads_1_] .file-field__upload input[type="file"]')
|
||||
.setInputFiles(path.resolve(__dirname, './uploads/payload.png'))
|
||||
await page.locator('[id^=doc-drawer_uploads_1_] #action-save').click()
|
||||
await wait(200)
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
await page.locator('.field-type.upload .file-details__remove').click()
|
||||
})
|
||||
|
||||
test('should select using the list drawer and restrict mimetype based on filterOptions', async () => {
|
||||
await uploadImage()
|
||||
|
||||
await page.locator('.field-type.upload .upload__toggler.list-drawer__toggler').click()
|
||||
await wait(200)
|
||||
const jpgImages = page.locator('[id^=list-drawer_1_] .upload-gallery img[src$=".jpg"]')
|
||||
|
||||
@@ -8,28 +8,24 @@ import type { RichTextField } from './payload-types'
|
||||
|
||||
import payload from '../../packages/payload/src'
|
||||
import { initPayloadTest } from '../helpers/configHelpers'
|
||||
import { isMongoose } from '../helpers/isMongoose'
|
||||
import { RESTClient } from '../helpers/rest'
|
||||
import configPromise from '../uploads/config'
|
||||
import { arrayDefaultValue, arrayFieldsSlug } from './collections/Array'
|
||||
import { arrayDefaultValue } from './collections/Array'
|
||||
import { blocksDoc } from './collections/Blocks'
|
||||
import { dateDoc } from './collections/Date'
|
||||
import {
|
||||
groupDefaultChild,
|
||||
groupDefaultValue,
|
||||
groupDoc,
|
||||
groupFieldsSlug,
|
||||
} from './collections/Group'
|
||||
import { groupDefaultChild, groupDefaultValue, groupDoc } from './collections/Group'
|
||||
import { defaultNumber, numberDoc } from './collections/Number'
|
||||
import { pointDoc } from './collections/Point'
|
||||
import { relationshipFieldsSlug } from './collections/Relationship'
|
||||
import { tabsDoc } from './collections/Tabs'
|
||||
import {
|
||||
localizedTextValue,
|
||||
namedTabDefaultValue,
|
||||
namedTabText,
|
||||
tabsSlug,
|
||||
} from './collections/Tabs/constants'
|
||||
import { defaultText } from './collections/Text'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
import { arrayFieldsSlug, groupFieldsSlug, relationshipFieldsSlug, tabsFieldsSlug } from './slugs'
|
||||
|
||||
let client
|
||||
let graphQLClient: GraphQLClient
|
||||
@@ -48,10 +44,16 @@ describe('Fields', () => {
|
||||
token = await client.login()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await clearAndSeedEverything(payload)
|
||||
client = new RESTClient(config, { defaultSlug: 'point-fields', serverURL })
|
||||
await client.login()
|
||||
})
|
||||
|
||||
describe('text', () => {
|
||||
let doc
|
||||
const text = 'text field'
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
doc = await payload.create({
|
||||
collection: 'text-fields',
|
||||
data: { text },
|
||||
@@ -86,7 +88,7 @@ describe('Fields', () => {
|
||||
const otherTextDocText = 'alt text'
|
||||
const relationshipText = 'relationship text'
|
||||
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
textDoc = await payload.create({
|
||||
collection: 'text-fields',
|
||||
data: {
|
||||
@@ -193,7 +195,7 @@ describe('Fields', () => {
|
||||
describe('timestamps', () => {
|
||||
const tenMinutesAgo = new Date(Date.now() - 1000 * 60 * 10)
|
||||
let doc
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
doc = await payload.create({
|
||||
collection: 'date-fields',
|
||||
data: dateDoc,
|
||||
@@ -231,7 +233,7 @@ describe('Fields', () => {
|
||||
|
||||
describe('select', () => {
|
||||
let doc
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
const { id } = await payload.create({
|
||||
collection: 'select-fields',
|
||||
data: {
|
||||
@@ -273,7 +275,7 @@ describe('Fields', () => {
|
||||
|
||||
describe('number', () => {
|
||||
let doc
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
doc = await payload.create({
|
||||
collection: 'number-fields',
|
||||
data: numberDoc,
|
||||
@@ -375,13 +377,13 @@ describe('Fields', () => {
|
||||
})
|
||||
})
|
||||
|
||||
if (['mongoose'].includes(process.env.PAYLOAD_DATABASE)) {
|
||||
if (isMongoose(payload) || !['postgres'].includes(process.env.PAYLOAD_DATABASE)) {
|
||||
describe('indexes', () => {
|
||||
let indexes
|
||||
const definitions: Record<string, IndexDirection> = {}
|
||||
const options: Record<string, IndexOptions> = {}
|
||||
|
||||
beforeAll(() => {
|
||||
beforeEach(() => {
|
||||
indexes = (payload.db as MongooseAdapter).collections[
|
||||
'indexed-fields'
|
||||
].schema.indexes() as [Record<string, IndexDirection>, IndexOptions]
|
||||
@@ -434,7 +436,7 @@ describe('Fields', () => {
|
||||
const definitions: Record<string, IndexDirection> = {}
|
||||
const options: Record<string, IndexOptions> = {}
|
||||
|
||||
beforeAll(() => {
|
||||
beforeEach(() => {
|
||||
indexes = (payload.db as MongooseAdapter).versions['indexed-fields'].schema.indexes() as [
|
||||
Record<string, IndexDirection>,
|
||||
IndexOptions,
|
||||
@@ -458,7 +460,7 @@ describe('Fields', () => {
|
||||
const localized = [5, -2]
|
||||
const group = { point: [1, 9] }
|
||||
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
const findDoc = await payload.find({
|
||||
collection: 'point-fields',
|
||||
pagination: false,
|
||||
@@ -495,6 +497,17 @@ describe('Fields', () => {
|
||||
})
|
||||
|
||||
it('should not create duplicate point when unique', async () => {
|
||||
// first create the point field
|
||||
doc = await payload.create({
|
||||
collection: 'point-fields',
|
||||
data: {
|
||||
group,
|
||||
localized,
|
||||
point,
|
||||
},
|
||||
})
|
||||
|
||||
// Now make sure we can't create a duplicate (since 'localized' is a unique field)
|
||||
await expect(() =>
|
||||
payload.create({
|
||||
collection: 'point-fields',
|
||||
@@ -546,7 +559,7 @@ describe('Fields', () => {
|
||||
let doc
|
||||
const collection = arrayFieldsSlug
|
||||
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
doc = await payload.create({
|
||||
collection,
|
||||
data: {},
|
||||
@@ -611,7 +624,7 @@ describe('Fields', () => {
|
||||
describe('group', () => {
|
||||
let document
|
||||
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
document = await payload.create({
|
||||
collection: groupFieldsSlug,
|
||||
data: {},
|
||||
@@ -627,9 +640,9 @@ describe('Fields', () => {
|
||||
describe('tabs', () => {
|
||||
let document
|
||||
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
document = await payload.create({
|
||||
collection: tabsSlug,
|
||||
collection: tabsFieldsSlug,
|
||||
data: tabsDoc,
|
||||
})
|
||||
})
|
||||
@@ -649,7 +662,7 @@ describe('Fields', () => {
|
||||
it('should create with localized text inside a named tab', async () => {
|
||||
document = await payload.findByID({
|
||||
id: document.id,
|
||||
collection: tabsSlug,
|
||||
collection: tabsFieldsSlug,
|
||||
locale: 'all',
|
||||
})
|
||||
expect(document.localizedTab.en.text).toStrictEqual(localizedTextValue)
|
||||
@@ -658,7 +671,7 @@ describe('Fields', () => {
|
||||
it('should allow access control on a named tab', async () => {
|
||||
document = await payload.findByID({
|
||||
id: document.id,
|
||||
collection: tabsSlug,
|
||||
collection: tabsFieldsSlug,
|
||||
overrideAccess: false,
|
||||
})
|
||||
expect(document.accessControlTab).toBeUndefined()
|
||||
@@ -666,7 +679,7 @@ describe('Fields', () => {
|
||||
|
||||
it('should allow hooks on a named tab', async () => {
|
||||
const newDocument = await payload.create({
|
||||
collection: tabsSlug,
|
||||
collection: tabsFieldsSlug,
|
||||
data: tabsDoc,
|
||||
})
|
||||
expect(newDocument.hooksTab.beforeValidate).toBe(true)
|
||||
|
||||
27
test/fields/lexicalE2E.ts
Normal file
27
test/fields/lexicalE2E.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
import type { RESTClient } from '../helpers/rest'
|
||||
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
|
||||
|
||||
const { describe } = test
|
||||
|
||||
export const lexicalE2E = (client: RESTClient, page: Page, serverURL: string) => {
|
||||
async function navigateToRichTextFields() {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
|
||||
await page.goto(url.list)
|
||||
await page.locator('.row-1 .cell-title a').click()
|
||||
}
|
||||
|
||||
return () => {
|
||||
describe('todo', () => {
|
||||
test.skip('todo', async () => {
|
||||
await navigateToRichTextFields()
|
||||
|
||||
await page.locator('todo').first().click()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
147
test/fields/seed.ts
Normal file
147
test/fields/seed.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import path from 'path'
|
||||
|
||||
import { type Payload } from '../../packages/payload/src'
|
||||
import getFileByPath from '../../packages/payload/src/uploads/getFileByPath'
|
||||
import { devUser } from '../credentials'
|
||||
import { seedDB } from '../helpers/seed'
|
||||
import { arrayDoc } from './collections/Array'
|
||||
import { blocksDoc } from './collections/Blocks'
|
||||
import { codeDoc } from './collections/Code'
|
||||
import { collapsibleDoc } from './collections/Collapsible'
|
||||
import { conditionalLogicDoc } from './collections/ConditionalLogic'
|
||||
import { dateDoc } from './collections/Date'
|
||||
import { groupDoc } from './collections/Group'
|
||||
import { jsonDoc } from './collections/JSON'
|
||||
import { lexicalRichTextDoc } from './collections/Lexical/data'
|
||||
import { numberDoc } from './collections/Number'
|
||||
import { pointDoc } from './collections/Point'
|
||||
import { radiosDoc } from './collections/Radio'
|
||||
import { richTextBulletsDoc, richTextDoc } from './collections/RichText/data'
|
||||
import { selectsDoc } from './collections/Select'
|
||||
import { tabsDoc } from './collections/Tabs'
|
||||
import { textDoc } from './collections/Text'
|
||||
import { uploadsDoc } from './collections/Upload'
|
||||
import {
|
||||
blockFieldsSlug,
|
||||
codeFieldsSlug,
|
||||
collapsibleFieldsSlug,
|
||||
collectionSlugs,
|
||||
conditionalLogicSlug,
|
||||
dateFieldsSlug,
|
||||
groupFieldsSlug,
|
||||
jsonFieldsSlug,
|
||||
lexicalFieldsSlug,
|
||||
lexicalMigrateFieldsSlug,
|
||||
numberFieldsSlug,
|
||||
pointFieldsSlug,
|
||||
radioFieldsSlug,
|
||||
richTextFieldsSlug,
|
||||
selectFieldsSlug,
|
||||
tabsFieldsSlug,
|
||||
textFieldsSlug,
|
||||
uploadsSlug,
|
||||
usersSlug,
|
||||
} from './slugs'
|
||||
|
||||
export async function clearAndSeedEverything(_payload: Payload) {
|
||||
return await seedDB({
|
||||
snapshotKey: 'fieldsTest',
|
||||
shouldResetDB: true,
|
||||
collectionSlugs,
|
||||
_payload,
|
||||
uploadsDir: path.resolve(__dirname, './collections/Upload/uploads'),
|
||||
seedFunction: async (_payload) => {
|
||||
const jpgPath = path.resolve(__dirname, './collections/Upload/payload.jpg')
|
||||
const pngPath = path.resolve(__dirname, './uploads/payload.png')
|
||||
|
||||
// Get both files in parallel
|
||||
const [jpgFile, pngFile] = await Promise.all([getFileByPath(jpgPath), getFileByPath(pngPath)])
|
||||
|
||||
const [createdArrayDoc, createdTextDoc, createdPNGDoc] = await Promise.all([
|
||||
_payload.create({ collection: 'array-fields', data: arrayDoc }),
|
||||
_payload.create({ collection: textFieldsSlug, data: textDoc }),
|
||||
_payload.create({ collection: uploadsSlug, data: {}, file: pngFile }),
|
||||
])
|
||||
|
||||
const createdJPGDoc = await _payload.create({
|
||||
collection: uploadsSlug,
|
||||
data: {
|
||||
...uploadsDoc,
|
||||
media: createdPNGDoc.id,
|
||||
},
|
||||
file: jpgFile,
|
||||
})
|
||||
|
||||
const formattedID =
|
||||
_payload.db.defaultIDType === 'number' ? createdArrayDoc.id : `"${createdArrayDoc.id}"`
|
||||
|
||||
const formattedJPGID =
|
||||
_payload.db.defaultIDType === 'number' ? createdJPGDoc.id : `"${createdJPGDoc.id}"`
|
||||
|
||||
const formattedTextID =
|
||||
_payload.db.defaultIDType === 'number' ? createdTextDoc.id : `"${createdTextDoc.id}"`
|
||||
|
||||
const richTextDocWithRelId = JSON.parse(
|
||||
JSON.stringify(richTextDoc)
|
||||
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
|
||||
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
|
||||
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`),
|
||||
)
|
||||
const richTextBulletsDocWithRelId = JSON.parse(
|
||||
JSON.stringify(richTextBulletsDoc)
|
||||
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
|
||||
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
|
||||
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`),
|
||||
)
|
||||
|
||||
const richTextDocWithRelationship = { ...richTextDocWithRelId }
|
||||
|
||||
const blocksDocWithRichText = { ...blocksDoc }
|
||||
|
||||
blocksDocWithRichText.blocks[0].richText = richTextDocWithRelationship.richText
|
||||
blocksDocWithRichText.localizedBlocks[0].richText = richTextDocWithRelationship.richText
|
||||
|
||||
const lexicalRichTextDocWithRelId = JSON.parse(
|
||||
JSON.stringify(lexicalRichTextDoc)
|
||||
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
|
||||
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
|
||||
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`),
|
||||
)
|
||||
|
||||
await Promise.all([
|
||||
_payload.create({
|
||||
collection: usersSlug,
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
}),
|
||||
_payload.create({ collection: collapsibleFieldsSlug, data: collapsibleDoc }),
|
||||
_payload.create({ collection: conditionalLogicSlug, data: conditionalLogicDoc }),
|
||||
_payload.create({ collection: groupFieldsSlug, data: groupDoc }),
|
||||
_payload.create({ collection: selectFieldsSlug, data: selectsDoc }),
|
||||
_payload.create({ collection: radioFieldsSlug, data: radiosDoc }),
|
||||
_payload.create({ collection: tabsFieldsSlug, data: tabsDoc }),
|
||||
_payload.create({ collection: pointFieldsSlug, data: pointDoc }),
|
||||
_payload.create({ collection: dateFieldsSlug, data: dateDoc }),
|
||||
_payload.create({ collection: codeFieldsSlug, data: codeDoc }),
|
||||
_payload.create({ collection: jsonFieldsSlug, data: jsonDoc }),
|
||||
|
||||
_payload.create({ collection: blockFieldsSlug, data: blocksDocWithRichText }),
|
||||
|
||||
_payload.create({ collection: lexicalFieldsSlug, data: lexicalRichTextDocWithRelId }),
|
||||
_payload.create({
|
||||
collection: lexicalMigrateFieldsSlug,
|
||||
data: lexicalRichTextDocWithRelId,
|
||||
}),
|
||||
|
||||
_payload.create({ collection: richTextFieldsSlug, data: richTextBulletsDocWithRelId }),
|
||||
_payload.create({ collection: richTextFieldsSlug, data: richTextDocWithRelationship }),
|
||||
|
||||
_payload.create({ collection: numberFieldsSlug, data: { number: 2 } }),
|
||||
_payload.create({ collection: numberFieldsSlug, data: { number: 3 } }),
|
||||
_payload.create({ collection: numberFieldsSlug, data: numberDoc }),
|
||||
])
|
||||
},
|
||||
})
|
||||
}
|
||||
52
test/fields/slugs.ts
Normal file
52
test/fields/slugs.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
export const usersSlug = 'users' as const
|
||||
export const arrayFieldsSlug = 'array-fields' as const
|
||||
export const blockFieldsSlug = 'block-fields' as const
|
||||
export const checkboxFieldsSlug = 'checkbox-fields' as const
|
||||
export const codeFieldsSlug = 'code-fields' as const
|
||||
export const collapsibleFieldsSlug = 'collapsible-fields' as const
|
||||
export const conditionalLogicSlug = 'conditional-logic' as const
|
||||
export const dateFieldsSlug = 'date-fields' as const
|
||||
export const groupFieldsSlug = 'group-fields' as const
|
||||
export const indexedFieldsSlug = 'indexed-fields' as const
|
||||
export const jsonFieldsSlug = 'json-fields' as const
|
||||
export const lexicalFieldsSlug = 'lexical-fields' as const
|
||||
export const lexicalMigrateFieldsSlug = 'lexical-migrate-fields' as const
|
||||
export const numberFieldsSlug = 'number-fields' as const
|
||||
export const pointFieldsSlug = 'point-fields' as const
|
||||
export const radioFieldsSlug = 'radio-fields' as const
|
||||
export const relationshipFieldsSlug = 'relationship-fields' as const
|
||||
export const richTextFieldsSlug = 'rich-text-fields' as const
|
||||
export const rowFieldsSlug = 'row-fields' as const
|
||||
export const selectFieldsSlug = 'select-fields' as const
|
||||
export const tabsFieldsSlug = 'tabs-fields' as const
|
||||
export const textFieldsSlug = 'text-fields' as const
|
||||
export const uploadsSlug = 'uploads' as const
|
||||
export const uploads2Slug = 'uploads2' as const
|
||||
export const uploads3Slug = 'uploads3' as const
|
||||
export const collectionSlugs = [
|
||||
usersSlug,
|
||||
arrayFieldsSlug,
|
||||
blockFieldsSlug,
|
||||
checkboxFieldsSlug,
|
||||
codeFieldsSlug,
|
||||
collapsibleFieldsSlug,
|
||||
conditionalLogicSlug,
|
||||
dateFieldsSlug,
|
||||
groupFieldsSlug,
|
||||
indexedFieldsSlug,
|
||||
jsonFieldsSlug,
|
||||
lexicalFieldsSlug,
|
||||
lexicalMigrateFieldsSlug,
|
||||
numberFieldsSlug,
|
||||
pointFieldsSlug,
|
||||
radioFieldsSlug,
|
||||
relationshipFieldsSlug,
|
||||
richTextFieldsSlug,
|
||||
rowFieldsSlug,
|
||||
selectFieldsSlug,
|
||||
tabsFieldsSlug,
|
||||
textFieldsSlug,
|
||||
uploadsSlug,
|
||||
uploads2Slug,
|
||||
uploads3Slug,
|
||||
]
|
||||
@@ -40,8 +40,6 @@ export async function initPayloadTest(options: Options): Promise<InitializedPayl
|
||||
process.env.NODE_ENV = 'test'
|
||||
process.env.PAYLOAD_CONFIG_PATH = path.resolve(options.__dirname, './config.ts')
|
||||
|
||||
const port = await getPort()
|
||||
|
||||
if (!initOptions?.local) {
|
||||
initOptions.express = express()
|
||||
}
|
||||
@@ -63,6 +61,7 @@ export async function initPayloadTest(options: Options): Promise<InitializedPayl
|
||||
|
||||
await payload.init(initOptions)
|
||||
|
||||
const port = await getPort()
|
||||
if (initOptions.express) {
|
||||
initOptions.express.listen(port)
|
||||
}
|
||||
|
||||
5
test/helpers/isMongoose.ts
Normal file
5
test/helpers/isMongoose.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { Payload } from '../../packages/payload/src'
|
||||
|
||||
export function isMongoose(_payload?: Payload) {
|
||||
return _payload?.db?.name === 'mongoose' || ['mongoose'].includes(process.env.PAYLOAD_DATABASE)
|
||||
}
|
||||
36
test/helpers/reset.ts
Normal file
36
test/helpers/reset.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { PostgresAdapter } from '../../packages/db-postgres/src/types'
|
||||
import type { Payload } from '../../packages/payload/src'
|
||||
|
||||
import { isMongoose } from './isMongoose'
|
||||
|
||||
export async function resetDB(_payload: Payload, collectionSlugs: string[]) {
|
||||
if (isMongoose(_payload)) {
|
||||
await _payload.db.collections[collectionSlugs[0]].db.dropDatabase()
|
||||
} else {
|
||||
const db: PostgresAdapter = _payload.db as unknown as PostgresAdapter
|
||||
|
||||
// Alternative to: await db.drizzle.execute(sql`drop schema public cascade; create schema public;`)
|
||||
|
||||
// Deleting the schema causes issues when restoring the database from a snapshot later on. That's why we only delete the table data here,
|
||||
// To avoid having to re-create any table schemas / indexes / whatever
|
||||
const schema = db.drizzle._.schema
|
||||
if (!schema) {
|
||||
return
|
||||
}
|
||||
const queries = Object.values(schema).map((table: any) => {
|
||||
return sql.raw(`DELETE FROM ${table.dbName}`)
|
||||
})
|
||||
|
||||
await db.drizzle.transaction(async (trx) => {
|
||||
await Promise.all(
|
||||
queries.map(async (query) => {
|
||||
if (query) {
|
||||
await trx.execute(query)
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
95
test/helpers/seed.ts
Normal file
95
test/helpers/seed.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import { type Payload } from '../../packages/payload/src'
|
||||
import { isMongoose } from './isMongoose'
|
||||
import { resetDB } from './reset'
|
||||
import { createSnapshot, dbSnapshot, restoreFromSnapshot } from './snapshot'
|
||||
|
||||
type SeedFunction = (_payload: Payload) => Promise<void>
|
||||
|
||||
export async function seedDB({
|
||||
shouldResetDB,
|
||||
_payload,
|
||||
snapshotKey,
|
||||
seedFunction,
|
||||
uploadsDir,
|
||||
collectionSlugs,
|
||||
}: {
|
||||
_payload: Payload
|
||||
collectionSlugs: string[]
|
||||
seedFunction: SeedFunction
|
||||
shouldResetDB: boolean
|
||||
/**
|
||||
* Key to uniquely identify the kind of snapshot. Each test suite should pass in a unique key
|
||||
*/
|
||||
snapshotKey: string
|
||||
uploadsDir?: string
|
||||
}) {
|
||||
/**
|
||||
* Reset database and delete uploads directory
|
||||
*/
|
||||
if (shouldResetDB) {
|
||||
let clearUploadsDirPromise: any = Promise.resolve()
|
||||
if (uploadsDir) {
|
||||
clearUploadsDirPromise = fs.promises
|
||||
.access(uploadsDir)
|
||||
.then(() => fs.promises.readdir(uploadsDir))
|
||||
.then((files) =>
|
||||
Promise.all(files.map((file) => fs.promises.rm(path.join(uploadsDir, file)))),
|
||||
)
|
||||
.catch((error) => {
|
||||
if (error.code !== 'ENOENT') {
|
||||
// If the error is not because the directory doesn't exist
|
||||
console.error('Error clearing the uploads directory:', error)
|
||||
throw error
|
||||
}
|
||||
// If the directory does not exist, resolve the promise (nothing to clear)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
await Promise.all([resetDB(_payload, collectionSlugs), clearUploadsDirPromise])
|
||||
}
|
||||
|
||||
/**
|
||||
* Mongoose & Postgres: Restore snapshot of old data if available
|
||||
*
|
||||
* Note for postgres: For postgres, this needs to happen AFTER the tables were created.
|
||||
* This does not work if I run payload.db.init or payload.db.connect anywhere. Thus, when resetting the database, we are not dropping the schema, but are instead only deleting the table values
|
||||
*/
|
||||
let restored = false
|
||||
if (dbSnapshot[snapshotKey] && Object.keys(dbSnapshot[snapshotKey]).length) {
|
||||
await restoreFromSnapshot(_payload, snapshotKey, collectionSlugs)
|
||||
restored = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Mongoose: Re-create indexes
|
||||
* Postgres: No need for any action here, since we only delete the table data and no schemas
|
||||
*/
|
||||
// Dropping the db breaks indexes (on mongoose - did not test extensively on postgres yet), so we recreate them here
|
||||
if (shouldResetDB) {
|
||||
if (isMongoose(_payload)) {
|
||||
await Promise.all([
|
||||
...collectionSlugs.map(async (collectionSlug) => {
|
||||
await _payload.db.collections[collectionSlug].createIndexes()
|
||||
}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a snapshot was restored, we don't need to seed the database
|
||||
*/
|
||||
if (restored) {
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed the database with data and save it to a snapshot
|
||||
**/
|
||||
await seedFunction(_payload)
|
||||
|
||||
await createSnapshot(_payload, snapshotKey, collectionSlugs)
|
||||
}
|
||||
118
test/helpers/snapshot.ts
Normal file
118
test/helpers/snapshot.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import type { PgTable } from 'drizzle-orm/pg-core'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { PostgresAdapter } from '../../packages/db-postgres/src/types'
|
||||
import type { Payload } from '../../packages/payload/src'
|
||||
|
||||
import { isMongoose } from './isMongoose'
|
||||
|
||||
export const dbSnapshot = {}
|
||||
|
||||
async function createMongooseSnapshot(collectionsObj, snapshotKey: string) {
|
||||
const snapshot = {}
|
||||
|
||||
// Assuming `collectionsObj` is an object where keys are names and values are collection references
|
||||
for (const collectionName of Object.keys(collectionsObj)) {
|
||||
const collection = collectionsObj[collectionName]
|
||||
const documents = await collection.find({}).toArray() // Get all documents
|
||||
snapshot[collectionName] = documents
|
||||
}
|
||||
|
||||
dbSnapshot[snapshotKey] = snapshot // Save the snapshot in memory
|
||||
}
|
||||
|
||||
async function restoreFromMongooseSnapshot(collectionsObj, snapshotKey: string) {
|
||||
if (!dbSnapshot[snapshotKey]) {
|
||||
throw new Error('No snapshot found to restore from.')
|
||||
}
|
||||
|
||||
// Assuming `collectionsObj` is an object where keys are names and values are collection references
|
||||
for (const [name, documents] of Object.entries(dbSnapshot[snapshotKey])) {
|
||||
const collection = collectionsObj[name]
|
||||
// You would typically clear the collection here, but as per your requirement, you do it manually
|
||||
if ((documents as any[]).length > 0) {
|
||||
await collection.insertMany(documents)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function createDrizzleSnapshot(db: PostgresAdapter, snapshotKey: string) {
|
||||
const snapshot = {}
|
||||
|
||||
const schema: Record<string, PgTable> = db.drizzle._.schema
|
||||
if (!schema) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const tableName in schema) {
|
||||
const table = db.drizzle.query[tableName]['fullSchema'][tableName] //db.drizzle._.schema[tableName]
|
||||
const records = await db.drizzle.select().from(table).execute()
|
||||
snapshot[tableName] = records
|
||||
}
|
||||
|
||||
dbSnapshot[snapshotKey] = snapshot
|
||||
}
|
||||
|
||||
async function restoreFromDrizzleSnapshot(db: PostgresAdapter, snapshotKey: string) {
|
||||
if (!dbSnapshot[snapshotKey]) {
|
||||
throw new Error('No snapshot found to restore from.')
|
||||
}
|
||||
|
||||
const disableFKConstraintChecksQuery = sql.raw('SET session_replication_role = replica;')
|
||||
const enableFKConstraintChecksQuery = sql.raw('SET session_replication_role = DEFAULT;')
|
||||
|
||||
await db.drizzle.transaction(async (trx) => {
|
||||
// Temporarily disable foreign key constraint checks
|
||||
await trx.execute(disableFKConstraintChecksQuery)
|
||||
try {
|
||||
for (const tableName in dbSnapshot[snapshotKey]) {
|
||||
const table = db.drizzle.query[tableName]['fullSchema'][tableName]
|
||||
await trx.delete(table).execute() // This deletes all records from the table. Probably not necessary, as I'm deleting the table before restoring anyways
|
||||
|
||||
const records = dbSnapshot[snapshotKey][tableName]
|
||||
if (records.length > 0) {
|
||||
await trx.insert(table).values(records).execute()
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
// Re-enable foreign key constraint checks
|
||||
await trx.execute(enableFKConstraintChecksQuery)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function createSnapshot(
|
||||
_payload: Payload,
|
||||
snapshotKey: string,
|
||||
collectionSlugs: string[],
|
||||
) {
|
||||
if (isMongoose(_payload)) {
|
||||
const mongooseCollections = _payload.db.collections[collectionSlugs[0]].db.collections
|
||||
|
||||
await createMongooseSnapshot(mongooseCollections, snapshotKey)
|
||||
} else {
|
||||
const db: PostgresAdapter = _payload.db as unknown as PostgresAdapter
|
||||
await createDrizzleSnapshot(db, snapshotKey)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure to delete the db before calling this function
|
||||
* @param _payload
|
||||
*/
|
||||
export async function restoreFromSnapshot(
|
||||
_payload: Payload,
|
||||
snapshotKey: string,
|
||||
collectionSlugs: string[],
|
||||
) {
|
||||
if (isMongoose(_payload)) {
|
||||
const mongooseCollections = _payload.db.collections[collectionSlugs[0]].db.collections
|
||||
await restoreFromMongooseSnapshot(mongooseCollections, snapshotKey)
|
||||
} else {
|
||||
const db: PostgresAdapter = _payload.db as unknown as PostgresAdapter
|
||||
await restoreFromDrizzleSnapshot(db, snapshotKey)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { autosaveSlug } from '../shared'
|
||||
import { autosaveCollectionSlug } from '../slugs'
|
||||
|
||||
const AutosavePosts: CollectionConfig = {
|
||||
slug: autosaveSlug,
|
||||
slug: autosaveCollectionSlug,
|
||||
labels: {
|
||||
singular: 'Autosave Post',
|
||||
plural: 'Autosave Posts',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { CustomPublishButton } from '../elements/CustomSaveButton'
|
||||
import { draftSlug } from '../shared'
|
||||
import { draftCollectionSlug } from '../slugs'
|
||||
|
||||
const DraftPosts: CollectionConfig = {
|
||||
access: {
|
||||
@@ -98,7 +98,7 @@ const DraftPosts: CollectionConfig = {
|
||||
type: 'blocks',
|
||||
},
|
||||
],
|
||||
slug: draftSlug,
|
||||
slug: draftCollectionSlug,
|
||||
versions: {
|
||||
drafts: true,
|
||||
maxPerDoc: 35,
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { draftSlug, postSlug, versionSlug } from '../shared'
|
||||
import { draftCollectionSlug, postCollectionSlug, versionCollectionSlug } from '../slugs'
|
||||
|
||||
const Posts: CollectionConfig = {
|
||||
slug: postSlug,
|
||||
slug: postCollectionSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'relationToVersions',
|
||||
type: 'relationship',
|
||||
relationTo: versionSlug,
|
||||
relationTo: versionCollectionSlug,
|
||||
},
|
||||
{
|
||||
name: 'relationToDrafts',
|
||||
type: 'relationship',
|
||||
relationTo: draftSlug,
|
||||
relationTo: draftCollectionSlug,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { versionSlug } from '../shared'
|
||||
import { versionCollectionSlug } from '../slugs'
|
||||
|
||||
const VersionPosts: CollectionConfig = {
|
||||
slug: versionSlug,
|
||||
slug: versionCollectionSlug,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'description', 'createdAt'],
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import path from 'path'
|
||||
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
|
||||
import AutosavePosts from './collections/Autosave'
|
||||
import DraftPosts from './collections/Drafts'
|
||||
@@ -5,7 +7,7 @@ import Posts from './collections/Posts'
|
||||
import VersionPosts from './collections/Versions'
|
||||
import AutosaveGlobal from './globals/Autosave'
|
||||
import DraftGlobal from './globals/Draft'
|
||||
import { seed } from './seed'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [Posts, AutosavePosts, DraftPosts, VersionPosts],
|
||||
@@ -15,5 +17,19 @@ export default buildConfigWithDefaults({
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'es'],
|
||||
},
|
||||
onInit: seed,
|
||||
admin: {
|
||||
webpack: (config) => ({
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config?.resolve?.alias,
|
||||
fs: path.resolve(__dirname, './mocks/emptyModule.js'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
onInit: async (payload) => {
|
||||
await clearAndSeedEverything(payload)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -27,20 +27,22 @@ import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
import payload from '../../packages/payload/src'
|
||||
import wait from '../../packages/payload/src/utilities/wait'
|
||||
import { globalSlug } from '../admin/shared'
|
||||
import { globalSlug } from '../admin/slugs'
|
||||
import { changeLocale, exactText, findTableCell, selectTableRow } from '../helpers'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
|
||||
import { initPayloadE2E } from '../helpers/configHelpers'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
import { titleToDelete } from './shared'
|
||||
import {
|
||||
autoSaveGlobalSlug,
|
||||
autosaveSlug,
|
||||
autosaveCollectionSlug,
|
||||
draftCollectionSlug,
|
||||
draftGlobalSlug,
|
||||
draftSlug,
|
||||
titleToDelete,
|
||||
} from './shared'
|
||||
} from './slugs'
|
||||
|
||||
const { beforeAll, describe } = test
|
||||
const { beforeAll, beforeEach, describe } = test
|
||||
|
||||
describe('versions', () => {
|
||||
let page: Page
|
||||
@@ -55,10 +57,14 @@ describe('versions', () => {
|
||||
page = await context.newPage()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await clearAndSeedEverything(payload)
|
||||
})
|
||||
|
||||
describe('draft collections', () => {
|
||||
beforeAll(() => {
|
||||
url = new AdminUrlUtil(serverURL, draftSlug)
|
||||
autosaveURL = new AdminUrlUtil(serverURL, autosaveSlug)
|
||||
url = new AdminUrlUtil(serverURL, draftCollectionSlug)
|
||||
autosaveURL = new AdminUrlUtil(serverURL, autosaveCollectionSlug)
|
||||
})
|
||||
|
||||
// This test has to run before bulk updates that will rename the title
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
|
||||
|
||||
import { autoSaveGlobalSlug } from '../shared'
|
||||
import { autoSaveGlobalSlug } from '../slugs'
|
||||
|
||||
const AutosaveGlobal: GlobalConfig = {
|
||||
slug: autoSaveGlobalSlug,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
|
||||
|
||||
import { draftGlobalSlug } from '../shared'
|
||||
import { draftGlobalSlug } from '../slugs'
|
||||
|
||||
const DraftGlobal: GlobalConfig = {
|
||||
slug: draftGlobalSlug,
|
||||
|
||||
@@ -6,7 +6,8 @@ import { initPayloadTest } from '../helpers/configHelpers'
|
||||
import AutosavePosts from './collections/Autosave'
|
||||
import configPromise from './config'
|
||||
import AutosaveGlobal from './globals/Autosave'
|
||||
import { autosaveSlug, draftSlug } from './shared'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
import { autosaveCollectionSlug, draftCollectionSlug } from './slugs'
|
||||
|
||||
let collectionLocalPostID: string
|
||||
let collectionLocalVersionID
|
||||
@@ -54,13 +55,7 @@ describe('Versions', () => {
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
// First: delete potential existing versions from previous tests
|
||||
if (collectionLocalPostID) {
|
||||
await payload.delete({
|
||||
id: collectionLocalPostID,
|
||||
collection,
|
||||
})
|
||||
}
|
||||
await clearAndSeedEverything(payload)
|
||||
|
||||
// now: initialize
|
||||
const autosavePost = await payload.create({
|
||||
@@ -94,7 +89,7 @@ describe('Versions', () => {
|
||||
describe('Create', () => {
|
||||
it('should allow creating a draft with missing required field data', async () => {
|
||||
const draft = await payload.create({
|
||||
collection: autosaveSlug,
|
||||
collection: autosaveCollectionSlug,
|
||||
data: {
|
||||
description: undefined,
|
||||
title: 'i have a title',
|
||||
@@ -213,13 +208,13 @@ describe('Versions', () => {
|
||||
|
||||
it('should query drafts with sort', async () => {
|
||||
const draftsAscending = await payload.find({
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
draft: true,
|
||||
sort: 'title',
|
||||
})
|
||||
|
||||
const draftsDescending = await payload.find({
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
draft: true,
|
||||
sort: '-title',
|
||||
})
|
||||
@@ -233,14 +228,14 @@ describe('Versions', () => {
|
||||
|
||||
it('should `findVersions` with sort', async () => {
|
||||
const draftsAscending = await payload.findVersions({
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
draft: true,
|
||||
sort: 'createdAt',
|
||||
limit: 100,
|
||||
})
|
||||
|
||||
const draftsDescending = await payload.findVersions({
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
draft: true,
|
||||
sort: '-createdAt',
|
||||
limit: 100,
|
||||
@@ -257,7 +252,7 @@ describe('Versions', () => {
|
||||
describe('Restore', () => {
|
||||
it('should return `findVersions` in correct order', async () => {
|
||||
const somePost = await payload.create({
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
description: 'description 1',
|
||||
title: 'first post',
|
||||
@@ -266,14 +261,14 @@ describe('Versions', () => {
|
||||
|
||||
const updatedPost = await payload.update({
|
||||
id: somePost.id,
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
title: 'This should be the latest version',
|
||||
},
|
||||
})
|
||||
|
||||
const versions = await payload.findVersions({
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
where: {
|
||||
parent: { equals: somePost.id },
|
||||
},
|
||||
@@ -286,7 +281,7 @@ describe('Versions', () => {
|
||||
const updated = 'updated'
|
||||
|
||||
const versionedPost = await payload.create({
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
description: 'version description',
|
||||
title: 'version title',
|
||||
@@ -297,7 +292,7 @@ describe('Versions', () => {
|
||||
// @ts-ignore
|
||||
let updatedPost = await payload.update({
|
||||
id: versionedPost.id,
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
blocksField: [
|
||||
{
|
||||
@@ -313,7 +308,7 @@ describe('Versions', () => {
|
||||
// @ts-ignore
|
||||
updatedPost = await payload.update({
|
||||
id: versionedPost.id,
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
blocksField: [
|
||||
{
|
||||
@@ -336,7 +331,7 @@ describe('Versions', () => {
|
||||
// Make sure it was updated correctly
|
||||
const draftFromUpdatedPost = await payload.findByID({
|
||||
id: versionedPost.id,
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
draft: true,
|
||||
})
|
||||
expect(draftFromUpdatedPost.title).toBe(title2)
|
||||
@@ -344,7 +339,7 @@ describe('Versions', () => {
|
||||
expect(draftFromUpdatedPost.blocksField[0].localized).toStrictEqual(updated)
|
||||
|
||||
const versions = await payload.findVersions({
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
where: {
|
||||
parent: {
|
||||
equals: versionedPost.id,
|
||||
@@ -356,7 +351,7 @@ describe('Versions', () => {
|
||||
// restore to previous version
|
||||
const restoredVersion = await payload.restoreVersion({
|
||||
id: versionToRestore.id,
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
})
|
||||
|
||||
expect({ ...restoredVersion }).toMatchObject({
|
||||
@@ -366,7 +361,7 @@ describe('Versions', () => {
|
||||
|
||||
const latestDraft = await payload.findByID({
|
||||
id: versionedPost.id,
|
||||
collection: draftSlug,
|
||||
collection: draftCollectionSlug,
|
||||
draft: true,
|
||||
})
|
||||
|
||||
@@ -435,7 +430,7 @@ describe('Versions', () => {
|
||||
})
|
||||
describe('Delete', () => {
|
||||
let postToDelete
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
postToDelete = await payload.create({
|
||||
collection,
|
||||
data: {
|
||||
@@ -633,7 +628,7 @@ describe('Versions', () => {
|
||||
const updatedTitle2 = 'new title 2'
|
||||
let firstDraft
|
||||
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
// This will be created in the `draft-posts` collection
|
||||
firstDraft = await payload.create({
|
||||
collection: 'draft-posts',
|
||||
@@ -780,14 +775,6 @@ describe('Versions', () => {
|
||||
|
||||
describe('Collections - GraphQL', () => {
|
||||
beforeEach(async () => {
|
||||
// First: delete potential existing versions from previous tests
|
||||
if (collectionGraphQLPostID) {
|
||||
await payload.delete({
|
||||
id: collectionGraphQLPostID,
|
||||
collection,
|
||||
})
|
||||
}
|
||||
|
||||
const description = 'autosave description'
|
||||
|
||||
const query = `mutation {
|
||||
@@ -1182,7 +1169,7 @@ describe('Versions', () => {
|
||||
})
|
||||
|
||||
describe('Globals - GraphQL', () => {
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
// language=graphql
|
||||
const update = `mutation {
|
||||
updateAutosaveGlobal(draft: true, data: {
|
||||
|
||||
1
test/versions/mocks/emptyModule.js
Normal file
1
test/versions/mocks/emptyModule.js
Normal file
@@ -0,0 +1 @@
|
||||
export default {}
|
||||
86
test/versions/seed.ts
Normal file
86
test/versions/seed.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { type Payload } from '../../packages/payload/src'
|
||||
import { devUser } from '../credentials'
|
||||
import { seedDB } from '../helpers/seed'
|
||||
import { titleToDelete } from './shared'
|
||||
import { collectionSlugs, draftCollectionSlug } from './slugs'
|
||||
|
||||
export async function clearAndSeedEverything(_payload: Payload) {
|
||||
return await seedDB({
|
||||
snapshotKey: 'versionsTest',
|
||||
shouldResetDB: true,
|
||||
collectionSlugs,
|
||||
_payload,
|
||||
seedFunction: async (_payload) => {
|
||||
const blocksField = [
|
||||
{
|
||||
blockType: 'block',
|
||||
localized: 'text',
|
||||
text: 'text',
|
||||
},
|
||||
]
|
||||
|
||||
await Promise.all([
|
||||
_payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
}),
|
||||
_payload.create({
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
blocksField,
|
||||
description: 'Description',
|
||||
radio: 'test',
|
||||
title: 'Draft Title',
|
||||
},
|
||||
draft: true,
|
||||
}),
|
||||
])
|
||||
|
||||
const { id: manyDraftsID } = await _payload.create({
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
blocksField,
|
||||
description: 'Description',
|
||||
radio: 'test',
|
||||
title: 'Title With Many Versions',
|
||||
},
|
||||
draft: true,
|
||||
})
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await _payload.update({
|
||||
id: manyDraftsID,
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
title: `Title With Many Versions ${i + 2}`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
await _payload.create({
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
_status: 'published',
|
||||
blocksField,
|
||||
description: 'Description',
|
||||
radio: 'test',
|
||||
title: 'Published Title',
|
||||
},
|
||||
draft: false,
|
||||
})
|
||||
|
||||
await _payload.create({
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
blocksField,
|
||||
description: 'Description',
|
||||
title: titleToDelete,
|
||||
},
|
||||
draft: true,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import type { Config } from '../../../packages/payload/src/config/types'
|
||||
|
||||
import { devUser } from '../../credentials'
|
||||
import { draftSlug, titleToDelete } from '../shared'
|
||||
|
||||
export const seed: Config['onInit'] = async (payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
|
||||
const blocksField = [
|
||||
{
|
||||
blockType: 'block',
|
||||
localized: 'text',
|
||||
text: 'text',
|
||||
},
|
||||
]
|
||||
|
||||
await payload.create({
|
||||
collection: draftSlug,
|
||||
data: {
|
||||
blocksField,
|
||||
description: 'Description',
|
||||
radio: 'test',
|
||||
title: 'Draft Title',
|
||||
},
|
||||
draft: true,
|
||||
})
|
||||
|
||||
const { id: manyDraftsID } = await payload.create({
|
||||
collection: draftSlug,
|
||||
data: {
|
||||
blocksField,
|
||||
description: 'Description',
|
||||
radio: 'test',
|
||||
title: 'Title With Many Versions',
|
||||
},
|
||||
draft: true,
|
||||
})
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await payload.update({
|
||||
id: manyDraftsID,
|
||||
collection: draftSlug,
|
||||
data: {
|
||||
title: `Title With Many Versions ${i + 2}`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
await payload.create({
|
||||
collection: draftSlug,
|
||||
data: {
|
||||
_status: 'published',
|
||||
blocksField,
|
||||
description: 'Description',
|
||||
radio: 'test',
|
||||
title: 'Published Title',
|
||||
},
|
||||
draft: false,
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: draftSlug,
|
||||
data: {
|
||||
blocksField,
|
||||
description: 'Description',
|
||||
title: titleToDelete,
|
||||
},
|
||||
draft: true,
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1 @@
|
||||
export const postSlug = 'posts'
|
||||
export const draftSlug = 'draft-posts'
|
||||
export const autosaveSlug = 'autosave-posts'
|
||||
export const versionSlug = 'version-posts'
|
||||
|
||||
export const autoSaveGlobalSlug = 'autosave-global'
|
||||
export const draftGlobalSlug = 'draft-global'
|
||||
|
||||
export const titleToDelete = 'Title To Delete'
|
||||
|
||||
19
test/versions/slugs.ts
Normal file
19
test/versions/slugs.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export const autosaveCollectionSlug = 'autosave-posts' as const
|
||||
|
||||
export const draftCollectionSlug = 'draft-posts' as const
|
||||
|
||||
export const postCollectionSlug = 'posts' as const
|
||||
|
||||
export const versionCollectionSlug = 'version-posts' as const
|
||||
|
||||
export const collectionSlugs = [
|
||||
autosaveCollectionSlug,
|
||||
draftCollectionSlug,
|
||||
postCollectionSlug,
|
||||
versionCollectionSlug,
|
||||
]
|
||||
|
||||
export const autoSaveGlobalSlug = 'autosave-global' as const
|
||||
export const draftGlobalSlug = 'draft-global' as const
|
||||
|
||||
export const globalSlugs = [autoSaveGlobalSlug, draftGlobalSlug]
|
||||
Reference in New Issue
Block a user