2323 */
2424
2525import type { BrowserContext , Dialog , Page } from '@playwright/test'
26+ import type { TypeWithID } from 'payload'
2627
2728import { expect , test } from '@playwright/test'
28- import { postsCollectionSlug } from 'admin/slugs.js'
2929import { checkFocusIndicators } from '__helpers/e2e/checkFocusIndicators.js'
3030import { runAxeScan } from '__helpers/e2e/runAxeScan.js'
31+ import { postsCollectionSlug } from 'admin/slugs.js'
3132import mongoose from 'mongoose'
3233import path from 'path'
3334import { formatAdminURL , wait } from 'payload/shared'
@@ -36,6 +37,7 @@ import { fileURLToPath } from 'url'
3637import type { PayloadTestSDK } from '../__helpers/shared/sdk/index.js'
3738import type { Config , Diff } from './payload-types.js'
3839
40+ import { assertNetworkRequests } from '../__helpers/e2e/assertNetworkRequests.js'
3941import {
4042 changeLocale ,
4143 ensureCompilationIsDone ,
@@ -47,13 +49,12 @@ import {
4749 waitForFormReady ,
4850 // throttleTest,
4951} from '../__helpers/e2e/helpers.js'
50- import { AdminUrlUtil } from '../__helpers/shared/adminUrlUtil.js'
51- import { assertNetworkRequests } from '../__helpers/e2e/assertNetworkRequests.js'
5252import { navigateToDiffVersionView as _navigateToDiffVersionView } from '../__helpers/e2e/navigateToDiffVersionView.js'
5353import { openDocControls } from '../__helpers/e2e/openDocControls.js'
5454import { waitForAutoSaveToRunAndComplete } from '../__helpers/e2e/waitForAutoSaveToRunAndComplete.js'
55- import { initPayloadE2ENoConfig } from '../__helpers/shared/initPayloadE2ENoConfig .js'
55+ import { AdminUrlUtil } from '../__helpers/shared/adminUrlUtil .js'
5656import { reInitializeDB } from '../__helpers/shared/clearAndSeed/reInitializeDB.js'
57+ import { initPayloadE2ENoConfig } from '../__helpers/shared/initPayloadE2ENoConfig.js'
5758import { POLL_TOPASS_TIMEOUT , TEST_TIMEOUT_LONG } from '../playwright.config.js'
5859import { draftWithCustomUnpublishSlug } from './collections/DraftsWithCustomUnpublish.js'
5960import { BASE_PATH } from './shared.js'
@@ -78,8 +79,8 @@ import {
7879 localizedCollectionSlug ,
7980 localizedGlobalSlug ,
8081 postCollectionSlug ,
81- versionCollectionSlug ,
8282 textCollectionSlug ,
83+ versionCollectionSlug ,
8384} from './slugs.js'
8485
8586const filename = fileURLToPath ( import . meta. url )
@@ -2677,6 +2678,108 @@ describe('Versions', () => {
26772678 const blocks = page . locator ( '[data-field-path="blocks"]' )
26782679 await expect ( blocks ) . toBeVisible ( )
26792680 } )
2681+
2682+ test ( 'correctly renders text fields containing HTML special characters' , async ( ) => {
2683+ // Create a document with HTML special characters in a text field
2684+ const doc = await payload . create ( {
2685+ collection : diffCollectionSlug ,
2686+ data : {
2687+ _status : 'published' ,
2688+ text : '<b>bold</b> & "quotes"' ,
2689+ } ,
2690+ } )
2691+
2692+ // Update to create a version
2693+ await payload . update ( {
2694+ collection : diffCollectionSlug ,
2695+ id : doc . id ,
2696+ data : {
2697+ _status : 'published' ,
2698+ text : '<script>alert(1)</script>' ,
2699+ } ,
2700+ } )
2701+
2702+ const versionDiff = (
2703+ await payload . findVersions ( {
2704+ collection : diffCollectionSlug ,
2705+ depth : 0 ,
2706+ limit : 1 ,
2707+ where : {
2708+ parent : { equals : doc . id } ,
2709+ } ,
2710+ } )
2711+ ) . docs [ 0 ] as unknown as TypeWithID
2712+
2713+ await _navigateToDiffVersionView ( {
2714+ adminRoute,
2715+ serverURL,
2716+ collectionSlug : diffCollectionSlug ,
2717+ docID : doc . id ,
2718+ versionID : versionDiff . id ,
2719+ page,
2720+ } )
2721+
2722+ const text = page . locator ( '[data-field-path="text"]' )
2723+
2724+ // Verify that HTML characters are rendered as literal text, not interpreted as HTML
2725+ await expect ( text . locator ( '.html-diff__diff-old' ) ) . toHaveText ( '<b>bold</b> & "quotes"' )
2726+ await expect ( text . locator ( '.html-diff__diff-new' ) ) . toHaveText ( '<script>alert(1)</script>' )
2727+
2728+ // Cleanup
2729+ await payload . delete ( { collection : diffCollectionSlug , id : doc . id } )
2730+ } )
2731+
2732+ test ( 'correctly renders JSON fields containing HTML special characters' , async ( ) => {
2733+ // Create a document with HTML special characters in a JSON field
2734+ const doc = await payload . create ( {
2735+ collection : diffCollectionSlug ,
2736+ data : {
2737+ _status : 'published' ,
2738+ json : { html : '<div class="test">&</div>' } ,
2739+ } ,
2740+ } )
2741+
2742+ // Update to create a version
2743+ await payload . update ( {
2744+ collection : diffCollectionSlug ,
2745+ id : doc . id ,
2746+ data : {
2747+ _status : 'published' ,
2748+ json : { html : '<span onclick="alert(1)">click</span>' } ,
2749+ } ,
2750+ } )
2751+
2752+ const versionDiff = (
2753+ await payload . findVersions ( {
2754+ collection : diffCollectionSlug ,
2755+ depth : 0 ,
2756+ limit : 1 ,
2757+ where : {
2758+ parent : { equals : doc . id } ,
2759+ } ,
2760+ } )
2761+ ) . docs [ 0 ] as unknown as TypeWithID
2762+
2763+ await _navigateToDiffVersionView ( {
2764+ adminRoute,
2765+ serverURL,
2766+ collectionSlug : diffCollectionSlug ,
2767+ docID : doc . id ,
2768+ versionID : versionDiff . id ,
2769+ page,
2770+ } )
2771+
2772+ const json = page . locator ( '[data-field-path="json"]' )
2773+
2774+ // Verify that JSON with HTML characters renders the literal text
2775+ await expect ( json . locator ( '.html-diff__diff-old' ) ) . toContainText ( '"<div class=\\"test\\">' )
2776+ await expect ( json . locator ( '.html-diff__diff-new' ) ) . toContainText (
2777+ '"<span onclick=\\"alert(1)\\">' ,
2778+ )
2779+
2780+ // Cleanup
2781+ await payload . delete ( { collection : diffCollectionSlug , id : doc . id } )
2782+ } )
26802783 } )
26812784
26822785 describe ( 'Scheduled publish' , ( ) => {
0 commit comments