@@ -2,7 +2,7 @@ import type { ArrayField, Payload, RelationshipField } from 'payload'
22
33import path from 'path'
44import { fileURLToPath } from 'url'
5- import { afterAll , beforeAll , describe , expect , it } from 'vitest'
5+ import { afterAll , afterEach , beforeAll , describe , expect , it } from 'vitest'
66
77import type { Page } from './payload-types.js'
88
@@ -192,6 +192,206 @@ describe('@payloadcms/plugin-nested-docs', () => {
192192 } )
193193 } )
194194
195+ describe ( 'versions' , ( ) => {
196+ const createdPageIDs : ( number | string ) [ ] = [ ]
197+
198+ afterEach ( async ( ) => {
199+ // Clean up in reverse order (children before parents)
200+ for ( const id of [ ...createdPageIDs ] . reverse ( ) ) {
201+ await payload . delete ( { collection : 'pages' , id } )
202+ }
203+ createdPageIDs . length = 0
204+ } )
205+
206+ it ( 'should preserve published version of child when parent is saved and child has unpublished draft' , async ( ) => {
207+ // Step 1: Create parent page and publish it
208+ const parentDoc = await payload . create ( {
209+ collection : 'pages' ,
210+ data : {
211+ title : 'Version Parent' ,
212+ slug : 'version-parent' ,
213+ _status : 'published' ,
214+ } ,
215+ } )
216+ createdPageIDs . push ( parentDoc . id )
217+
218+ // Step 2: Create child page and publish it
219+ const childDoc = await payload . create ( {
220+ collection : 'pages' ,
221+ data : {
222+ title : 'Version Child' ,
223+ slug : 'version-child' ,
224+ parent : parentDoc . id ,
225+ _status : 'published' ,
226+ } ,
227+ } )
228+ createdPageIDs . push ( childDoc . id )
229+
230+ // Verify initial published state
231+ const initialPublished = await payload . findByID ( {
232+ id : childDoc . id ,
233+ collection : 'pages' ,
234+ draft : false ,
235+ } )
236+ expect ( initialPublished . _status ) . toBe ( 'published' )
237+ expect ( initialPublished . breadcrumbs ) . toHaveLength ( 2 )
238+
239+ // Step 3: Make unpublished changes to child (creates a draft version)
240+ await payload . update ( {
241+ id : childDoc . id ,
242+ collection : 'pages' ,
243+ data : {
244+ title : 'Version Child Draft Edit' ,
245+ } ,
246+ draft : true ,
247+ } )
248+
249+ // Step 4: Re-publish the parent (triggers resaveChildren)
250+ await payload . update ( {
251+ id : parentDoc . id ,
252+ collection : 'pages' ,
253+ data : {
254+ title : 'Version Parent Updated' ,
255+ slug : 'version-parent-updated' ,
256+ _status : 'published' ,
257+ } ,
258+ } )
259+
260+ // Step 5: Verify the child's published version is still accessible
261+ const publishedChild = await payload . findByID ( {
262+ id : childDoc . id ,
263+ collection : 'pages' ,
264+ draft : false ,
265+ } )
266+
267+ expect ( publishedChild ) . toBeDefined ( )
268+ expect ( publishedChild . _status ) . toBe ( 'published' )
269+ expect ( publishedChild . breadcrumbs ) . toHaveLength ( 2 )
270+ expect ( publishedChild . breadcrumbs ?. [ 0 ] ?. url ) . toBe ( '/version-parent-updated' )
271+
272+ // Step 6: Verify the draft version is also still accessible
273+ const draftChild = await payload . findByID ( {
274+ id : childDoc . id ,
275+ collection : 'pages' ,
276+ draft : true ,
277+ } )
278+
279+ expect ( draftChild ) . toBeDefined ( )
280+ expect ( draftChild . title ) . toBe ( 'Version Child Draft Edit' )
281+ } )
282+
283+ it ( 'should update breadcrumbs for draft-only children when parent is saved' , async ( ) => {
284+ const parentDoc = await payload . create ( {
285+ collection : 'pages' ,
286+ data : {
287+ title : 'Draft Parent' ,
288+ slug : 'draft-parent' ,
289+ _status : 'published' ,
290+ } ,
291+ } )
292+ createdPageIDs . push ( parentDoc . id )
293+
294+ // Create a child that is never published (draft-only)
295+ const draftChild = await payload . create ( {
296+ collection : 'pages' ,
297+ data : {
298+ title : 'Draft Only Child' ,
299+ slug : 'draft-only-child' ,
300+ parent : parentDoc . id ,
301+ _status : 'draft' ,
302+ } ,
303+ } )
304+ createdPageIDs . push ( draftChild . id )
305+
306+ expect ( draftChild . _status ) . toBe ( 'draft' )
307+
308+ // Update the parent
309+ await payload . update ( {
310+ id : parentDoc . id ,
311+ collection : 'pages' ,
312+ data : {
313+ title : 'Draft Parent Updated' ,
314+ slug : 'draft-parent-updated' ,
315+ _status : 'published' ,
316+ } ,
317+ } )
318+
319+ // Draft-only child should have updated breadcrumbs
320+ const updatedDraftChild = await payload . findByID ( {
321+ id : draftChild . id ,
322+ collection : 'pages' ,
323+ draft : true ,
324+ } )
325+
326+ expect ( updatedDraftChild . breadcrumbs ) . toHaveLength ( 2 )
327+ expect ( updatedDraftChild . breadcrumbs ?. [ 0 ] ?. url ) . toBe ( '/draft-parent-updated' )
328+ } )
329+
330+ it ( 'should update breadcrumbs for both published and draft versions when parent changes' , async ( ) => {
331+ const parent = await payload . create ( {
332+ collection : 'pages' ,
333+ data : {
334+ title : 'Breadcrumb Parent' ,
335+ slug : 'breadcrumb-parent' ,
336+ _status : 'published' ,
337+ } ,
338+ } )
339+ createdPageIDs . push ( parent . id )
340+
341+ const child = await payload . create ( {
342+ collection : 'pages' ,
343+ data : {
344+ title : 'Breadcrumb Child' ,
345+ slug : 'breadcrumb-child' ,
346+ parent : parent . id ,
347+ _status : 'published' ,
348+ } ,
349+ } )
350+ createdPageIDs . push ( child . id )
351+
352+ // Create draft edit on child
353+ await payload . update ( {
354+ id : child . id ,
355+ collection : 'pages' ,
356+ data : {
357+ title : 'Breadcrumb Child Draft' ,
358+ } ,
359+ draft : true ,
360+ } )
361+
362+ // Update parent slug
363+ await payload . update ( {
364+ id : parent . id ,
365+ collection : 'pages' ,
366+ data : {
367+ slug : 'breadcrumb-parent-updated' ,
368+ _status : 'published' ,
369+ } ,
370+ } )
371+
372+ // Published child has updated breadcrumbs and is accessible
373+ const published = await payload . findByID ( {
374+ id : child . id ,
375+ collection : 'pages' ,
376+ draft : false ,
377+ } )
378+
379+ expect ( published . _status ) . toBe ( 'published' )
380+ expect ( published . breadcrumbs ?. [ 0 ] ?. url ) . toBe ( '/breadcrumb-parent-updated' )
381+
382+ // Draft child also has updated breadcrumbs
383+ const draft = await payload . findByID ( {
384+ id : child . id ,
385+ collection : 'pages' ,
386+ draft : true ,
387+ } )
388+
389+ expect ( draft . _status ) . toBe ( 'draft' )
390+ expect ( draft . title ) . toBe ( 'Breadcrumb Child Draft' )
391+ expect ( draft . breadcrumbs ?. [ 0 ] ?. url ) . toBe ( '/breadcrumb-parent-updated' )
392+ } )
393+ } )
394+
195395 describe ( 'overrides' , ( ) => {
196396 let collection
197397 beforeAll ( ( ) => {
0 commit comments