Skip to content

Commit b15a7e3

Browse files
authored
chore(richtext-lexical): add test converage for internal links (#11075)
Adds e2e test coverage for creating internal links, ensuring they are saved and that depth+population works. This test will prevent regression of #11062
1 parent d56de79 commit b15a7e3

File tree

1 file changed

+145
-16
lines changed

1 file changed

+145
-16
lines changed

test/fields/collections/Lexical/e2e/main/e2e.spec.ts

Lines changed: 145 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
import type { BrowserContext, Locator, Page } from '@playwright/test'
88

99
import { expect, test } from '@playwright/test'
10+
import { except } from 'drizzle-orm/mysql-core'
1011
import path from 'path'
1112
import { wait } from 'payload/shared'
1213
import { fileURLToPath } from 'url'
@@ -28,7 +29,6 @@ import { RESTClient } from '../../../../../helpers/rest.js'
2829
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../../../playwright.config.js'
2930
import { lexicalFieldsSlug } from '../../../../slugs.js'
3031
import { lexicalDocData } from '../../data.js'
31-
import { except } from 'drizzle-orm/mysql-core'
3232

3333
const filename = fileURLToPath(import.meta.url)
3434
const currentFolder = path.dirname(filename)
@@ -937,6 +937,135 @@ describe('lexicalMain', () => {
937937
})
938938
})
939939

940+
test('ensure internal links can be created', async () => {
941+
await navigateToLexicalFields()
942+
const richTextField = page.locator('.rich-text-lexical').first()
943+
await richTextField.scrollIntoViewIfNeeded()
944+
await expect(richTextField).toBeVisible()
945+
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
946+
await expect(page.locator('.rich-text-lexical').nth(2).locator('.lexical-block')).toHaveCount(
947+
10,
948+
)
949+
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
950+
951+
const paragraph = richTextField.locator('.LexicalEditorTheme__paragraph').first()
952+
await paragraph.scrollIntoViewIfNeeded()
953+
await expect(paragraph).toBeVisible()
954+
/**
955+
* Type some text
956+
*/
957+
await paragraph.click()
958+
await page.keyboard.type('Link')
959+
960+
// Select "Link" by pressing shift + arrow left
961+
for (let i = 0; i < 4; i++) {
962+
await page.keyboard.press('Shift+ArrowLeft')
963+
}
964+
// Ensure inline toolbar appeared
965+
const inlineToolbar = page.locator('.inline-toolbar-popup')
966+
await expect(inlineToolbar).toBeVisible()
967+
968+
const linkButton = inlineToolbar.locator('.toolbar-popup__button-link')
969+
await expect(linkButton).toBeVisible()
970+
await linkButton.click()
971+
972+
/**
973+
* Link Drawer
974+
*/
975+
const linkDrawer = page.locator('dialog[id^=drawer_1_lexical-rich-text-link-]').first() // IDs starting with drawer_1_lexical-rich-text-link- (there's some other symbol after the underscore)
976+
await expect(linkDrawer).toBeVisible()
977+
await wait(500)
978+
979+
// Check if has text "Internal Link"
980+
await expect(linkDrawer.locator('.radio-input').nth(1)).toContainText('Internal Link')
981+
982+
// Get radio button for internal link with text "Internal Link"
983+
const radioInternalLink = linkDrawer
984+
.locator('.radio-input')
985+
.nth(1)
986+
.locator('.radio-input__styled-radio')
987+
988+
await radioInternalLink.click()
989+
990+
const internalLinkSelect = linkDrawer
991+
.locator('#field-doc .rs__control .value-container')
992+
.first()
993+
await internalLinkSelect.click()
994+
995+
await expect(linkDrawer.locator('.rs__option').nth(0)).toBeVisible()
996+
await expect(linkDrawer.locator('.rs__option').nth(0)).toContainText('Rich Text') // Link to itself - that way we can also test if depth 0 works
997+
await linkDrawer.locator('.rs__option').nth(0).click()
998+
await expect(internalLinkSelect).toContainText('Rich Text')
999+
1000+
await linkDrawer.locator('button').getByText('Save').first().click()
1001+
await expect(linkDrawer).toBeHidden()
1002+
await wait(1500)
1003+
1004+
await saveDocAndAssert(page)
1005+
1006+
// Check if the text is bold. It's a self-relationship, so no need to follow relationship
1007+
await expect(async () => {
1008+
const lexicalDoc: LexicalField = (
1009+
await payload.find({
1010+
collection: lexicalFieldsSlug,
1011+
depth: 0,
1012+
overrideAccess: true,
1013+
where: {
1014+
title: {
1015+
equals: lexicalDocData.title,
1016+
},
1017+
},
1018+
})
1019+
).docs[0] as never
1020+
1021+
const lexicalField: SerializedEditorState =
1022+
lexicalDoc.lexicalRootEditor as SerializedEditorState
1023+
1024+
const firstParagraph: SerializedParagraphNode = lexicalField.root
1025+
.children[0] as SerializedParagraphNode
1026+
1027+
expect(firstParagraph.children).toHaveLength(1)
1028+
1029+
const linkNode = firstParagraph.children[0] as SerializedLinkNode
1030+
expect(linkNode?.fields?.doc?.relationTo).toBe('lexical-fields')
1031+
// Expect to be string
1032+
expect(typeof linkNode?.fields?.doc?.value).toBe('string')
1033+
}).toPass({
1034+
timeout: POLL_TOPASS_TIMEOUT,
1035+
})
1036+
1037+
// Now check if depth 1 works
1038+
await expect(async () => {
1039+
const lexicalDoc: LexicalField = (
1040+
await payload.find({
1041+
collection: lexicalFieldsSlug,
1042+
depth: 1,
1043+
overrideAccess: true,
1044+
where: {
1045+
title: {
1046+
equals: lexicalDocData.title,
1047+
},
1048+
},
1049+
})
1050+
).docs[0] as never
1051+
1052+
const lexicalField: SerializedEditorState =
1053+
lexicalDoc.lexicalRootEditor as SerializedEditorState
1054+
1055+
const firstParagraph: SerializedParagraphNode = lexicalField.root
1056+
.children[0] as SerializedParagraphNode
1057+
1058+
expect(firstParagraph.children).toHaveLength(1)
1059+
1060+
const linkNode = firstParagraph.children[0] as SerializedLinkNode
1061+
expect(linkNode?.fields?.doc?.relationTo).toBe('lexical-fields')
1062+
expect(typeof linkNode?.fields?.doc?.value).toBe('object')
1063+
expect(typeof (linkNode?.fields?.doc?.value as Record<string, unknown>)?.id).toBe('string')
1064+
}).toPass({
1065+
timeout: POLL_TOPASS_TIMEOUT,
1066+
})
1067+
})
1068+
9401069
test('ensure link drawer displays fields if document does not have `create` permission', async () => {
9411070
await navigateToLexicalFields(true, 'lexical-access-control')
9421071
const richTextField = page.locator('.rich-text-lexical').first()
@@ -1243,20 +1372,20 @@ describe('lexicalMain', () => {
12431372

12441373
const relationshipInput = page.locator('.drawer__content .rs__input').first()
12451374
await expect(relationshipInput).toBeVisible()
1246-
await page.getByRole('heading', { name: 'Lexical Fields' })
1375+
page.getByRole('heading', { name: 'Lexical Fields' })
12471376
await relationshipInput.click()
1248-
const user = await page.getByRole('option', { name: 'User' })
1377+
const user = page.getByRole('option', { name: 'User' })
12491378
await user.click()
12501379

12511380
const userListDrawer = page
12521381
.locator('div')
12531382
.filter({ hasText: /^User$/ })
12541383
.first()
12551384
await expect(userListDrawer).toBeVisible()
1256-
await page.getByRole('heading', { name: 'Users' })
1257-
const button = await page.getByLabel('Add new User')
1385+
page.getByRole('heading', { name: 'Users' })
1386+
const button = page.getByLabel('Add new User')
12581387
await button.click()
1259-
await page.getByText('Creating new User')
1388+
page.getByText('Creating new User')
12601389
})
12611390

12621391
test('ensure links can created from clipboard and deleted', async () => {
@@ -1276,7 +1405,7 @@ describe('lexicalMain', () => {
12761405

12771406
await lastParagraph.click()
12781407

1279-
page.context().grantPermissions(['clipboard-read', 'clipboard-write'])
1408+
await page.context().grantPermissions(['clipboard-read', 'clipboard-write'])
12801409

12811410
// Paste in a link copied from a html page
12821411
const link = '<a href="https://www.google.com">Google</a>'
@@ -1305,17 +1434,17 @@ describe('lexicalMain', () => {
13051434
await expect(linkInput).toBeVisible()
13061435

13071436
const linkInInput = linkInput.locator('a').first()
1308-
expect(linkInInput).toBeVisible()
1437+
await expect(linkInInput).toBeVisible()
13091438

1310-
expect(linkInInput).toContainText('https://www.google.com/')
1439+
await expect(linkInInput).toContainText('https://www.google.com/')
13111440
await expect(linkInInput).toHaveAttribute('href', 'https://www.google.com/')
13121441

13131442
// Click remove button
13141443
const removeButton = linkInput.locator('.link-trash').first()
13151444
await removeButton.click()
13161445

13171446
// Expect link to be removed
1318-
await expect(linkNode).not.toBeVisible()
1447+
await expect(linkNode).toBeHidden()
13191448
})
13201449

13211450
describe('localization', () => {
@@ -1355,39 +1484,39 @@ describe('lexicalMain', () => {
13551484

13561485
const textNode = page.getByText('Upload Node:', { exact: true })
13571486
await textNode.click()
1358-
await expect(decoratorLocator).not.toBeVisible()
1487+
await expect(decoratorLocator).toBeHidden()
13591488

13601489
const closeTagInMultiSelect = page
13611490
.getByRole('button', { name: 'payload.jpg Edit payload.jpg' })
13621491
.getByLabel('Remove')
13631492
await closeTagInMultiSelect.click()
1364-
await expect(decoratorLocator).not.toBeVisible()
1493+
await expect(decoratorLocator).toBeHidden()
13651494

13661495
const labelInsideCollapsableBody = page.locator('label').getByText('Sub Blocks')
13671496
await labelInsideCollapsableBody.click()
13681497
await expectInsideSelectedDecorator(labelInsideCollapsableBody)
13691498

13701499
const textNodeInNestedEditor = page.getByText('Some text below relationship node 1')
13711500
await textNodeInNestedEditor.click()
1372-
await expect(decoratorLocator).not.toBeVisible()
1501+
await expect(decoratorLocator).toBeHidden()
13731502

13741503
await page.getByRole('button', { name: 'Tab2' }).click()
1375-
await expect(decoratorLocator).not.toBeVisible()
1504+
await expect(decoratorLocator).toBeHidden()
13761505

13771506
const labelInsideCollapsableBody2 = page.getByText('Text2')
13781507
await labelInsideCollapsableBody2.click()
13791508
await expectInsideSelectedDecorator(labelInsideCollapsableBody2)
13801509

13811510
// TEST DELETE!
13821511
await page.keyboard.press('Backspace')
1383-
await expect(labelInsideCollapsableBody2).not.toBeVisible()
1512+
await expect(labelInsideCollapsableBody2).toBeHidden()
13841513

13851514
const monacoLabel = page.locator('label').getByText('Code')
13861515
await monacoLabel.click()
13871516
await expectInsideSelectedDecorator(monacoLabel)
13881517

13891518
const monacoCode = page.getByText('Some code')
13901519
await monacoCode.click()
1391-
await expect(decoratorLocator).not.toBeVisible()
1520+
await expect(decoratorLocator).toBeHidden()
13921521
})
13931522
})

0 commit comments

Comments
 (0)