@@ -41,6 +41,215 @@ describe('folders', () => {
4141 } )
4242 } )
4343
44+ describe ( 'nested folder population with depth' , ( ) => {
45+ it ( 'should populate all nested subfolders for multiple root folders when queried with depth' , async ( ) => {
46+ const ROOT_FOLDER_COUNT = 8
47+ const NESTED_FOLDER_COUNT = 10
48+
49+ // Create root folders
50+ const rootFolders : { id : number | string ; name : string } [ ] = [ ]
51+ for ( let i = 1 ; i <= ROOT_FOLDER_COUNT ; i ++ ) {
52+ const rootFolder = await payload . create ( {
53+ collection : 'payload-folders' ,
54+ data : {
55+ name : `Root Folder ${ i } ` ,
56+ folderType : [ 'posts' ] ,
57+ } ,
58+ } )
59+ rootFolders . push ( { id : rootFolder . id , name : rootFolder . name } )
60+
61+ // Create nested subfolders for each root folder
62+ for ( let j = 1 ; j <= NESTED_FOLDER_COUNT ; j ++ ) {
63+ await payload . create ( {
64+ collection : 'payload-folders' ,
65+ data : {
66+ name : `Root ${ i } - Subfolder ${ j } ` ,
67+ folder : rootFolder . id ,
68+ folderType : [ 'posts' ] ,
69+ } ,
70+ } )
71+ }
72+ }
73+
74+ // Query root folders (folder: { exists: false }) with depth
75+ const result = await payload . find ( {
76+ collection : 'payload-folders' ,
77+ depth : 3 ,
78+ limit : 10000 ,
79+ where : {
80+ and : [ { folderType : { contains : 'posts' } } , { folder : { exists : false } } ] ,
81+ } ,
82+ } )
83+
84+ // Should return all 8 root folders
85+ expect ( result . docs ) . toHaveLength ( ROOT_FOLDER_COUNT )
86+
87+ // Each root folder should have all 10 nested subfolders populated
88+ for ( const rootFolder of result . docs ) {
89+ const nestedFolders = rootFolder . documentsAndFolders ?. docs ?. filter (
90+ ( doc : any ) => doc . relationTo === 'payload-folders' ,
91+ )
92+
93+ expect ( nestedFolders ) . toHaveLength ( NESTED_FOLDER_COUNT )
94+ }
95+
96+ // Verify total nested folder count
97+ const totalNestedFolders = result . docs . reduce ( ( acc , folder ) => {
98+ const nested =
99+ folder . documentsAndFolders ?. docs ?. filter (
100+ ( doc : any ) => doc . relationTo === 'payload-folders' ,
101+ ) || [ ]
102+ return acc + nested . length
103+ } , 0 )
104+
105+ expect ( totalNestedFolders ) . toBe ( ROOT_FOLDER_COUNT * NESTED_FOLDER_COUNT )
106+ } )
107+
108+ it ( 'should populate nested subfolders consistently regardless of query order' , async ( ) => {
109+ // Create 4 root folders with 5 subfolders each
110+ const rootFolders : { id : number | string } [ ] = [ ]
111+ for ( let i = 1 ; i <= 4 ; i ++ ) {
112+ const rootFolder = await payload . create ( {
113+ collection : 'payload-folders' ,
114+ data : {
115+ name : `Root ${ i } ` ,
116+ folderType : [ 'posts' ] ,
117+ } ,
118+ } )
119+ rootFolders . push ( { id : rootFolder . id } )
120+
121+ for ( let j = 1 ; j <= 5 ; j ++ ) {
122+ await payload . create ( {
123+ collection : 'payload-folders' ,
124+ data : {
125+ name : `Root ${ i } - Sub ${ j } ` ,
126+ folder : rootFolder . id ,
127+ folderType : [ 'posts' ] ,
128+ } ,
129+ } )
130+ }
131+ }
132+
133+ // Query with different sort orders and verify consistent results
134+ const ascResult = await payload . find ( {
135+ collection : 'payload-folders' ,
136+ depth : 2 ,
137+ limit : 100 ,
138+ sort : 'name' ,
139+ where : {
140+ folder : { exists : false } ,
141+ folderType : { contains : 'posts' } ,
142+ } ,
143+ } )
144+
145+ const descResult = await payload . find ( {
146+ collection : 'payload-folders' ,
147+ depth : 2 ,
148+ limit : 100 ,
149+ sort : '-name' ,
150+ where : {
151+ folder : { exists : false } ,
152+ folderType : { contains : 'posts' } ,
153+ } ,
154+ } )
155+
156+ // Both queries should return 4 root folders
157+ expect ( ascResult . docs ) . toHaveLength ( 4 )
158+ expect ( descResult . docs ) . toHaveLength ( 4 )
159+
160+ // Both should have same total nested folders
161+ const ascNestedCount = ascResult . docs . reduce (
162+ ( acc , f ) =>
163+ acc +
164+ ( f . documentsAndFolders ?. docs ?. filter ( ( d : any ) => d . relationTo === 'payload-folders' )
165+ ?. length || 0 ) ,
166+ 0 ,
167+ )
168+ const descNestedCount = descResult . docs . reduce (
169+ ( acc , f ) =>
170+ acc +
171+ ( f . documentsAndFolders ?. docs ?. filter ( ( d : any ) => d . relationTo === 'payload-folders' )
172+ ?. length || 0 ) ,
173+ 0 ,
174+ )
175+
176+ expect ( ascNestedCount ) . toBe ( 20 ) // 4 roots * 5 subfolders
177+ expect ( descNestedCount ) . toBe ( 20 )
178+ } )
179+
180+ it ( 'should correctly paginate nested subfolders within polymorphic joins' , async ( ) => {
181+ // Create a folder with 12 subfolders to test pagination
182+ const parentFolder = await payload . create ( {
183+ collection : 'payload-folders' ,
184+ data : {
185+ name : 'Parent with many subfolders' ,
186+ folderType : [ 'posts' ] ,
187+ } ,
188+ } )
189+
190+ // Create 12 subfolders with zero-padded names for consistent sorting
191+ for ( let i = 1 ; i <= 12 ; i ++ ) {
192+ await payload . create ( {
193+ collection : 'payload-folders' ,
194+ data : {
195+ name : `Subfolder ${ String ( i ) . padStart ( 2 , '0' ) } ` ,
196+ folder : parentFolder . id ,
197+ folderType : [ 'posts' ] ,
198+ } ,
199+ } )
200+ }
201+
202+ // Query with limit of 5 on the join - should get first 5 subfolders
203+ const page1Result = await payload . findByID ( {
204+ id : parentFolder . id ,
205+ collection : 'payload-folders' ,
206+ joins : {
207+ documentsAndFolders : {
208+ limit : 5 ,
209+ sort : 'name' ,
210+ } ,
211+ } ,
212+ } )
213+
214+ expect ( page1Result . documentsAndFolders ?. docs ?. length ) . toBeLessThanOrEqual ( 5 )
215+ expect ( page1Result . documentsAndFolders ?. docs ?. length ) . toBeGreaterThan ( 0 )
216+
217+ // Query page 2 - should get next batch of subfolders
218+ const page2Result = await payload . findByID ( {
219+ id : parentFolder . id ,
220+ collection : 'payload-folders' ,
221+ joins : {
222+ documentsAndFolders : {
223+ limit : 5 ,
224+ page : 2 ,
225+ sort : 'name' ,
226+ } ,
227+ } ,
228+ } )
229+
230+ expect ( page2Result . documentsAndFolders ?. docs ?. length ) . toBeGreaterThan ( 0 )
231+ expect ( page2Result . documentsAndFolders ?. docs ?. length ) . toBeLessThanOrEqual ( 5 )
232+
233+ // Verify no overlap between pages by checking names
234+ const page1Names = page1Result . documentsAndFolders ?. docs ?. map (
235+ ( d : any ) => d . value ?. name ,
236+ ) as string [ ]
237+ const page2Names = page2Result . documentsAndFolders ?. docs ?. map (
238+ ( d : any ) => d . value ?. name ,
239+ ) as string [ ]
240+
241+ // Page 1 and page 2 should have no overlap
242+ const page1Set = new Set ( page1Names )
243+ const hasOverlap = page2Names . some ( ( name ) => page1Set . has ( name ) )
244+ expect ( hasOverlap ) . toBe ( false )
245+
246+ // Page 1 names should come before page 2 names alphabetically
247+ const lastPage1Name = page1Names [ page1Names . length - 1 ]
248+ const firstPage2Name = page2Names [ 0 ]
249+ expect ( lastPage1Name < firstPage2Name ) . toBe ( true )
250+ } )
251+ } )
252+
44253 describe ( 'folder > subfolder querying' , ( ) => {
45254 it ( 'should populate subfolders for folder by ID' , async ( ) => {
46255 const parentFolder = await payload . create ( {
0 commit comments