@@ -841,6 +841,196 @@ describe("generateBlocks", () => {
841841 expect ( content ) . toContain ( "sidebar_position: 12" ) ;
842842 } ) ;
843843
844+ it ( "should assign missing Order pages after the max existing sidebar_position in filtered runs" , async ( ) => {
845+ const { generateBlocks } = await import ( "./generateBlocks" ) ;
846+ const { PAGE_METADATA_CACHE_PATH } = await import ( "./pageMetadataCache" ) ;
847+ const mockWriteFileSync = fs . writeFileSync as Mock ;
848+
849+ const page = createMockNotionPage ( {
850+ title : "New Incremental Page" ,
851+ elementType : "Page" ,
852+ } ) ;
853+ delete page . properties . Order ;
854+
855+ const generateBlocksPath = fileURLToPath (
856+ new URL ( "./generateBlocks.ts" , import . meta. url )
857+ ) ;
858+ const generateBlocksDir = path . dirname ( generateBlocksPath ) ;
859+ const docsPath = path . join ( generateBlocksDir , "../../docs" ) ;
860+ const existingDocPath = path . join ( docsPath , "existing-order-page.md" ) ;
861+ const newDocPath = path . join ( docsPath , "new-incremental-page.md" ) ;
862+
863+ fs . writeFileSync (
864+ existingDocPath ,
865+ `---\nsidebar_position: "20"\n---\n\n# Existing Ordered Page\n` ,
866+ "utf-8"
867+ ) ;
868+
869+ fs . writeFileSync (
870+ PAGE_METADATA_CACHE_PATH ,
871+ JSON . stringify ( {
872+ version : "1.0" ,
873+ scriptHash : "test-hash" ,
874+ lastSync : "2024-01-01T00:00:00.000Z" ,
875+ pages : {
876+ "existing-page-id" : {
877+ lastEdited : "2024-01-01T00:00:00.000Z" ,
878+ outputPaths : [ "/docs/existing-order-page.md" ] ,
879+ processedAt : "2024-01-01T00:00:00.000Z" ,
880+ } ,
881+ } ,
882+ } ) ,
883+ "utf-8"
884+ ) ;
885+
886+ n2m . pageToMarkdown . mockResolvedValue ( [ ] ) ;
887+ n2m . toMarkdownString . mockReturnValue ( {
888+ parent : "# New Incremental Page\n\nContent here." ,
889+ } ) ;
890+
891+ await generateBlocks ( [ page ] , vi . fn ( ) ) ;
892+
893+ const markdownCalls = mockWriteFileSync . mock . calls . filter (
894+ ( call ) => typeof call [ 0 ] === "string" && call [ 0 ] === newDocPath
895+ ) ;
896+
897+ expect ( markdownCalls . length ) . toBeGreaterThan ( 0 ) ;
898+
899+ const content = markdownCalls [ markdownCalls . length - 1 ] [ 1 ] as string ;
900+ expect ( content ) . toContain ( "sidebar_position: 21" ) ;
901+ } ) ;
902+
903+ it ( "should assign unique sequential fallback positions for multiple missing Order pages" , async ( ) => {
904+ const { generateBlocks } = await import ( "./generateBlocks" ) ;
905+ const { PAGE_METADATA_CACHE_PATH } = await import ( "./pageMetadataCache" ) ;
906+ const mockWriteFileSync = fs . writeFileSync as Mock ;
907+
908+ const pageOne = createMockNotionPage ( {
909+ title : "Missing One" ,
910+ elementType : "Page" ,
911+ } ) ;
912+ const pageTwo = createMockNotionPage ( {
913+ title : "Missing Two" ,
914+ elementType : "Page" ,
915+ } ) ;
916+ delete pageOne . properties . Order ;
917+ delete pageTwo . properties . Order ;
918+
919+ const generateBlocksPath = fileURLToPath (
920+ new URL ( "./generateBlocks.ts" , import . meta. url )
921+ ) ;
922+ const generateBlocksDir = path . dirname ( generateBlocksPath ) ;
923+ const docsPath = path . join ( generateBlocksDir , "../../docs" ) ;
924+ const existingDocPath = path . join ( docsPath , "existing-order-page.md" ) ;
925+ const firstDocPath = path . join ( docsPath , "missing-one.md" ) ;
926+ const secondDocPath = path . join ( docsPath , "missing-two.md" ) ;
927+
928+ fs . writeFileSync (
929+ existingDocPath ,
930+ `---\nsidebar_position: "20"\n---\n\n# Existing Ordered Page\n` ,
931+ "utf-8"
932+ ) ;
933+
934+ fs . writeFileSync (
935+ PAGE_METADATA_CACHE_PATH ,
936+ JSON . stringify ( {
937+ version : "1.0" ,
938+ scriptHash : "test-hash" ,
939+ lastSync : "2024-01-01T00:00:00.000Z" ,
940+ pages : {
941+ "existing-page-id" : {
942+ lastEdited : "2024-01-01T00:00:00.000Z" ,
943+ outputPaths : [ "/docs/existing-order-page.md" ] ,
944+ processedAt : "2024-01-01T00:00:00.000Z" ,
945+ } ,
946+ } ,
947+ } ) ,
948+ "utf-8"
949+ ) ;
950+
951+ n2m . pageToMarkdown . mockResolvedValue ( [ ] ) ;
952+ n2m . toMarkdownString . mockReturnValue ( {
953+ parent : "# Content\n\nBody." ,
954+ } ) ;
955+
956+ await generateBlocks ( [ pageOne , pageTwo ] , vi . fn ( ) ) ;
957+
958+ const firstWrites = mockWriteFileSync . mock . calls . filter (
959+ ( call ) => typeof call [ 0 ] === "string" && call [ 0 ] === firstDocPath
960+ ) ;
961+ const secondWrites = mockWriteFileSync . mock . calls . filter (
962+ ( call ) => typeof call [ 0 ] === "string" && call [ 0 ] === secondDocPath
963+ ) ;
964+
965+ expect ( firstWrites . length ) . toBeGreaterThan ( 0 ) ;
966+ expect ( secondWrites . length ) . toBeGreaterThan ( 0 ) ;
967+
968+ const firstContent = firstWrites [ firstWrites . length - 1 ] [ 1 ] as string ;
969+ const secondContent = secondWrites [ secondWrites . length - 1 ] [ 1 ] as string ;
970+ expect ( firstContent ) . toContain ( "sidebar_position: 21" ) ;
971+ expect ( secondContent ) . toContain ( "sidebar_position: 22" ) ;
972+ } ) ;
973+
974+ it ( "should recover max sidebar_position from existing markdown when cache is missing" , async ( ) => {
975+ const { generateBlocks } = await import ( "./generateBlocks" ) ;
976+ const { PAGE_METADATA_CACHE_PATH } = await import ( "./pageMetadataCache" ) ;
977+ const mockWriteFileSync = fs . writeFileSync as Mock ;
978+ const mockReaddirSync = fs . readdirSync as Mock ;
979+ const mockUnlinkSync = fs . unlinkSync as Mock ;
980+
981+ const page = createMockNotionPage ( {
982+ title : "Cacheless Incremental Page" ,
983+ elementType : "Page" ,
984+ } ) ;
985+ delete page . properties . Order ;
986+
987+ const generateBlocksPath = fileURLToPath (
988+ new URL ( "./generateBlocks.ts" , import . meta. url )
989+ ) ;
990+ const generateBlocksDir = path . dirname ( generateBlocksPath ) ;
991+ const docsPath = path . join ( generateBlocksDir , "../../docs" ) ;
992+ const existingDocPath = path . join ( docsPath , "existing-disk-page.md" ) ;
993+ const newDocPath = path . join ( docsPath , "cacheless-incremental-page.md" ) ;
994+
995+ fs . writeFileSync (
996+ existingDocPath ,
997+ `---\nsidebar_position: "30"\n---\n\n# Existing Disk Page\n` ,
998+ "utf-8"
999+ ) ;
1000+ mockUnlinkSync ( PAGE_METADATA_CACHE_PATH ) ;
1001+
1002+ mockReaddirSync . mockImplementation (
1003+ ( targetPath : string , options ?: any ) => {
1004+ if ( targetPath === docsPath && options ?. withFileTypes ) {
1005+ return [
1006+ {
1007+ name : "existing-disk-page.md" ,
1008+ isDirectory : ( ) => false ,
1009+ isFile : ( ) => true ,
1010+ } ,
1011+ ] ;
1012+ }
1013+ return [ ] ;
1014+ }
1015+ ) ;
1016+
1017+ n2m . pageToMarkdown . mockResolvedValue ( [ ] ) ;
1018+ n2m . toMarkdownString . mockReturnValue ( {
1019+ parent : "# Cacheless Incremental Page\n\nContent here." ,
1020+ } ) ;
1021+
1022+ await generateBlocks ( [ page ] , vi . fn ( ) ) ;
1023+
1024+ const markdownCalls = mockWriteFileSync . mock . calls . filter (
1025+ ( call ) => typeof call [ 0 ] === "string" && call [ 0 ] === newDocPath
1026+ ) ;
1027+
1028+ expect ( markdownCalls . length ) . toBeGreaterThan ( 0 ) ;
1029+
1030+ const content = markdownCalls [ markdownCalls . length - 1 ] [ 1 ] as string ;
1031+ expect ( content ) . toContain ( "sidebar_position: 31" ) ;
1032+ } ) ;
1033+
8441034 it ( "should not reuse existing sidebar_position on full sync when Order is missing" , async ( ) => {
8451035 const { generateBlocks } = await import ( "./generateBlocks" ) ;
8461036 const mockWriteFileSync = fs . writeFileSync as Mock ;
0 commit comments