@@ -8,10 +8,10 @@ import {
8
8
getRedboxSource ,
9
9
} from 'next-test-utils'
10
10
import type { Request , Response } from 'playwright'
11
- import fs from 'fs-extra'
12
- import nodeFs from 'fs'
13
- import { join } from 'path'
11
+ import fs from 'node:fs/promises'
12
+ import { join } from 'node:path'
14
13
import { outdent } from 'outdent'
14
+ import { setTimeout } from 'node:timers/promises'
15
15
16
16
const GENERIC_RSC_ERROR =
17
17
'Error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.'
@@ -540,10 +540,10 @@ describe('app-dir action handling', () => {
540
540
// this triggers a revalidate + redirect in a client component
541
541
await browser . elementById ( 'redirect-revalidate-client' ) . click ( )
542
542
await retry ( async ( ) => {
543
+ expect ( await browser . url ( ) ) . toBe ( `${ next . url } /revalidate?foo=bar` )
544
+
543
545
const newJustPutIt = await browser . elementById ( 'justputit' ) . text ( )
544
546
expect ( newJustPutIt ) . not . toBe ( initialJustPutit )
545
-
546
- expect ( await browser . url ( ) ) . toBe ( `${ next . url } /revalidate?foo=bar` )
547
547
} )
548
548
549
549
// this triggers a revalidate + redirect in a server component
@@ -593,7 +593,8 @@ describe('app-dir action handling', () => {
593
593
beforePageLoad ( page ) {
594
594
page . on ( 'request' , ( request ) => {
595
595
const url = new URL ( request . url ( ) )
596
- if ( url . pathname === '/server' ) {
596
+ // Only count POST requests to /server (form submissions)
597
+ if ( url . pathname === '/server' && request . method ( ) === 'POST' ) {
597
598
requestCount ++
598
599
}
599
600
} )
@@ -772,8 +773,15 @@ describe('app-dir action handling', () => {
772
773
// This verifies the redirect & server response happens in a single roundtrip,
773
774
// if the redirect resource was static. In development, these responses are always
774
775
// dynamically generated, so we only expect a single request for build/deploy.
776
+ // With PPR enabled, there might be an additional RSC request for the redirect.
775
777
if ( ! isNextDev ) {
776
- expect ( requests . length ) . toBe ( 0 )
778
+ // When PPR is enabled (either through __NEXT_EXPERIMENTAL_PPR or with node middleware),
779
+ // we may see 1 RSC request due to different handling of redirects
780
+ const isPPREnabled =
781
+ process . env . __NEXT_EXPERIMENTAL_PPR === 'true' ||
782
+ process . env . TEST_NODE_MIDDLEWARE
783
+ const expectedRequests = isPPREnabled ? 1 : 0
784
+ expect ( requests . length ) . toBe ( expectedRequests )
777
785
}
778
786
} )
779
787
@@ -963,16 +971,21 @@ describe('app-dir action handling', () => {
963
971
if ( isNextStart ) {
964
972
it ( 'should not expose action content in sourcemaps' , async ( ) => {
965
973
// We check all sourcemaps in the `static` folder for sensitive information given that chunking
966
- const sourcemaps = nodeFs
967
- . readdirSync ( join ( next . testDir , '.next' , 'static' ) , {
974
+ const sourcemaps = await fs
975
+ . readdir ( join ( next . testDir , '.next' , 'static' ) , {
968
976
recursive : true ,
969
977
encoding : 'utf8' ,
970
978
} )
971
- . filter ( ( f ) => f . endsWith ( '.js.map' ) )
972
- . map ( ( f ) =>
973
- nodeFs . readFileSync ( join ( next . testDir , '.next' , 'static' , f ) , {
974
- encoding : 'utf8' ,
975
- } )
979
+ . then ( ( files ) =>
980
+ Promise . all (
981
+ files
982
+ . filter ( ( f ) => f . endsWith ( '.js.map' ) )
983
+ . map ( ( f ) =>
984
+ fs . readFile ( join ( next . testDir , '.next' , 'static' , f ) , {
985
+ encoding : 'utf8' ,
986
+ } )
987
+ )
988
+ )
976
989
)
977
990
978
991
expect ( sourcemaps ) . not . toBeEmpty ( )
@@ -1046,23 +1059,22 @@ describe('app-dir action handling', () => {
1046
1059
it ( 'should bundle external libraries if they are on the action layer' , async ( ) => {
1047
1060
await next . fetch ( '/client' )
1048
1061
const pageBundle = await fs . readFile (
1049
- join ( next . testDir , '.next' , 'server' , 'app' , 'client' , 'page.js' )
1062
+ join ( next . testDir , '.next' , 'server' , 'app' , 'client' , 'page.js' ) ,
1063
+ { encoding : 'utf8' }
1050
1064
)
1051
1065
if ( isTurbopack ) {
1052
- const chunkPaths = pageBundle
1053
- . toString ( )
1054
- . matchAll ( / l o a d C h u n k \( " ( [ ^ " ] * ) " \) / g)
1055
- // @ts -ignore
1066
+ const chunkPaths = pageBundle . matchAll ( / l o a d C h u n k \( " ( [ ^ " ] * ) " \) / g)
1056
1067
const reads = [ ...chunkPaths ] . map ( async ( match ) => {
1057
1068
const bundle = await fs . readFile (
1058
- join ( next . testDir , '.next' , ...match [ 1 ] . split ( / [ \\ / ] / g) )
1069
+ join ( next . testDir , '.next' , ...match [ 1 ] . split ( / [ \\ / ] / g) ) ,
1070
+ { encoding : 'utf8' }
1059
1071
)
1060
- return bundle . toString ( ) . includes ( 'node_modules/nanoid/index.js' )
1072
+ return bundle . includes ( 'node_modules/nanoid/index.js' )
1061
1073
} )
1062
1074
1063
1075
expect ( await Promise . all ( reads ) ) . toContain ( true )
1064
1076
} else {
1065
- expect ( pageBundle . toString ( ) ) . toContain ( 'node_modules/nanoid/index.js' )
1077
+ expect ( pageBundle ) . toContain ( 'node_modules/nanoid/index.js' )
1066
1078
}
1067
1079
} )
1068
1080
}
@@ -1526,7 +1538,7 @@ describe('app-dir action handling', () => {
1526
1538
const browser = await next . browser ( '/revalidate' )
1527
1539
await browser . refresh ( )
1528
1540
1529
- const thankYouNext = await browser . elementByCss ( '#thankyounext' ) . text ( )
1541
+ const original = await browser . elementByCss ( '#thankyounext' ) . text ( )
1530
1542
1531
1543
await browser . elementByCss ( '#another' ) . click ( )
1532
1544
await retry ( async ( ) => {
@@ -1535,47 +1547,54 @@ describe('app-dir action handling', () => {
1535
1547
)
1536
1548
} )
1537
1549
1538
- const newThankYouNext = await browser
1539
- . elementByCss ( '#thankyounext' )
1540
- . text ( )
1541
-
1542
- // Should be the same number although in serverless
1543
- // it might be eventually consistent
1550
+ // Should be the same number although in serverless it might be
1551
+ // eventually consistent.
1544
1552
if ( ! isNextDeploy ) {
1545
- expect ( thankYouNext ) . toEqual ( newThankYouNext )
1553
+ await retry ( async ( ) => {
1554
+ const another = await browser . elementByCss ( '#thankyounext' ) . text ( )
1555
+ expect ( another ) . toEqual ( original )
1556
+ } )
1546
1557
}
1547
1558
1548
1559
await browser . elementByCss ( '#back' ) . click ( )
1549
-
1550
- // Should be different
1551
- let revalidatedThankYouNext
1552
1560
await retry ( async ( ) => {
1553
- switch ( type ) {
1554
- case 'tag' :
1555
- await browser . elementByCss ( '#revalidate-thankyounext' ) . click ( )
1556
- break
1557
- case 'path' :
1558
- await browser . elementByCss ( '#revalidate-path' ) . click ( )
1559
- break
1560
- default :
1561
- throw new Error ( `Invalid type: ${ type } ` )
1562
- }
1561
+ expect ( await browser . elementByCss ( '#title' ) . text ( ) ) . toBe ( 'revalidate' )
1562
+ } )
1563
1563
1564
- revalidatedThankYouNext = await browser
1565
- . elementByCss ( '#thankyounext' )
1566
- . text ( )
1564
+ switch ( type ) {
1565
+ case 'tag' :
1566
+ await browser . elementByCss ( '#revalidate-thankyounext' ) . click ( )
1567
+ break
1568
+ case 'path' :
1569
+ await browser . elementByCss ( '#revalidate-path' ) . click ( )
1570
+ break
1571
+ default :
1572
+ throw new Error ( `Invalid type: ${ type } ` )
1573
+ }
1567
1574
1568
- expect ( thankYouNext ) . not . toBe ( revalidatedThankYouNext )
1575
+ // Give some time for it to be revalidated.
1576
+ if ( isNextDeploy ) {
1577
+ await setTimeout ( 5000 )
1578
+ }
1579
+
1580
+ // Should be different
1581
+ let revalidated
1582
+ await retry ( async ( ) => {
1583
+ revalidated = await browser . elementByCss ( '#thankyounext' ) . text ( )
1584
+ expect ( revalidated ) . not . toBe ( original )
1569
1585
} )
1570
1586
1571
1587
await browser . elementByCss ( '#another' ) . click ( )
1588
+ await retry ( async ( ) => {
1589
+ expect ( await browser . elementByCss ( '#title' ) . text ( ) ) . toBe (
1590
+ 'another route'
1591
+ )
1592
+ } )
1572
1593
1573
1594
// The other page should be revalidated too
1574
1595
await retry ( async ( ) => {
1575
- const newThankYouNext = await browser
1576
- . elementByCss ( '#thankyounext' )
1577
- . text ( )
1578
- expect ( revalidatedThankYouNext ) . toBe ( newThankYouNext )
1596
+ const another = await browser . elementByCss ( '#thankyounext' ) . text ( )
1597
+ expect ( another ) . toBe ( revalidated )
1579
1598
} )
1580
1599
}
1581
1600
)
0 commit comments