@@ -1518,6 +1518,80 @@ describe('database', () => {
15181518 } )
15191519 } )
15201520
1521+ it ( 'should return the correct number of docs per page when sorting on an array sub-field' , async ( ) => {
1522+ const createdIds : string [ ] = [ ]
1523+ const TOTAL = 10
1524+ const ITEMS_PER_DOC = 3
1525+ const LIMIT = 5
1526+
1527+ const testPrefix = `SortArraySubField-${ Date . now ( ) } `
1528+
1529+ // Each post has ITEMS_PER_DOC array items with distinct text values so the JOIN
1530+ // produces TOTAL * ITEMS_PER_DOC rows — enough to expose the LIMIT-before-dedup bug.
1531+ for ( let i = 0 ; i < TOTAL ; i ++ ) {
1532+ const doc = await payload . create ( {
1533+ collection : postsSlug ,
1534+ data : {
1535+ arrayWithIDs : Array . from ( { length : ITEMS_PER_DOC } , ( _ , j ) => ( {
1536+ text : `${ testPrefix } -doc${ String ( i ) . padStart ( 2 , '0' ) } -item${ j } ` ,
1537+ } ) ) ,
1538+ title : `${ testPrefix } -${ i } ` ,
1539+ } ,
1540+ } )
1541+
1542+ createdIds . push ( String ( doc . id ) )
1543+ }
1544+
1545+ const page1 = await payload . find ( {
1546+ collection : postsSlug ,
1547+ limit : LIMIT ,
1548+ page : 1 ,
1549+ sort : 'arrayWithIDs.text' ,
1550+ where : { title : { contains : testPrefix } } ,
1551+ } )
1552+
1553+ const page2 = await payload . find ( {
1554+ collection : postsSlug ,
1555+ limit : LIMIT ,
1556+ page : 2 ,
1557+ sort : 'arrayWithIDs.text' ,
1558+ where : { title : { contains : testPrefix } } ,
1559+ } )
1560+
1561+ expect ( page1 . totalDocs ) . toBe ( TOTAL )
1562+ expect ( page1 . totalPages ) . toBe ( TOTAL / LIMIT )
1563+ expect ( page1 . docs ) . toHaveLength ( LIMIT )
1564+ expect ( page2 . docs ) . toHaveLength ( LIMIT )
1565+
1566+ // No document should appear in both pages
1567+ const page1Ids = new Set ( page1 . docs . map ( ( d ) => d . id ) )
1568+ const duplicates = page2 . docs . filter ( ( d ) => page1Ids . has ( d . id ) )
1569+ expect ( duplicates ) . toHaveLength ( 0 )
1570+
1571+ // Verify sort order: each doc's minimum array text is `${testPrefix}-docXX-item0`,
1572+ // so ascending sort should place doc-00 first and doc-09 last.
1573+ // Collect all docs across pages and verify they are in non-decreasing text order.
1574+ const allDocs = [ ...page1 . docs , ...page2 . docs ]
1575+ const minTexts = allDocs . map ( ( d ) => {
1576+ const texts = ( d . arrayWithIDs ?? [ ] ) . map ( ( item ) => item . text ) . filter ( Boolean )
1577+ return texts . length > 0 ? texts . sort ( ) [ 0 ] : ''
1578+ } )
1579+
1580+ for ( let i = 1 ; i < minTexts . length ; i ++ ) {
1581+ expect ( minTexts [ i - 1 ] ! <= minTexts [ i ] ! ) . toBe ( true )
1582+ }
1583+
1584+ // Page 1 docs should all have smaller sort keys than page 2 docs
1585+ const page1MaxText = minTexts . slice ( 0 , LIMIT ) . at ( - 1 ) !
1586+ const page2MinText = minTexts . slice ( LIMIT ) [ 0 ] !
1587+ expect ( page1MaxText <= page2MinText ) . toBe ( true )
1588+
1589+ await payload . delete ( {
1590+ collection : postsSlug ,
1591+ where : { id : { in : createdIds } } ,
1592+ } )
1593+ } )
1594+
15211595 describe ( 'Compound Indexes' , ( ) => {
15221596 beforeEach ( async ( ) => {
15231597 await payload . delete ( { collection : 'compound-indexes' , where : { } } )
0 commit comments