@@ -315,7 +315,6 @@ test.describe("Client Data", () => {
315
315
childClientLoader : false ,
316
316
childClientLoaderHydrate : false ,
317
317
} ) ,
318
- // Blow away parent.child.tsx with our own deferred version
319
318
"app/routes/parent.child.tsx" : js `
320
319
import * as React from 'react';
321
320
import { defer, json } from '@remix-run/node'
@@ -415,7 +414,6 @@ test.describe("Client Data", () => {
415
414
childClientLoader : false ,
416
415
childClientLoaderHydrate : false ,
417
416
} ) ,
418
- // Blow away parent.child.tsx with our own version
419
417
"app/routes/parent.child.tsx" : js `
420
418
import * as React from 'react';
421
419
import { json } from '@remix-run/node';
@@ -470,7 +468,6 @@ test.describe("Client Data", () => {
470
468
childClientLoader : false ,
471
469
childClientLoaderHydrate : false ,
472
470
} ) ,
473
- // Blow away parent.child.tsx with our own version without a server loader
474
471
"app/routes/parent.child.tsx" : js `
475
472
import * as React from 'react';
476
473
import { useLoaderData } from '@remix-run/react';
@@ -514,7 +511,6 @@ test.describe("Client Data", () => {
514
511
childClientLoader : false ,
515
512
childClientLoaderHydrate : false ,
516
513
} ) ,
517
- // Blow away parent.child.tsx with our own version without a server loader
518
514
"app/routes/parent.child.tsx" : js `
519
515
import * as React from 'react';
520
516
import { useLoaderData } from '@remix-run/react';
@@ -545,6 +541,189 @@ test.describe("Client Data", () => {
545
541
html = await app . getHtml ( "main" ) ;
546
542
expect ( html ) . toMatch ( "Loader Data (clientLoader only)" ) ;
547
543
} ) ;
544
+
545
+ test ( "throws a 400 if you call serverLoader without a server loader" , async ( {
546
+ page,
547
+ } ) => {
548
+ appFixture = await createAppFixture (
549
+ await createFixture ( {
550
+ files : {
551
+ ...getFiles ( {
552
+ parentClientLoader : false ,
553
+ parentClientLoaderHydrate : false ,
554
+ childClientLoader : false ,
555
+ childClientLoaderHydrate : false ,
556
+ } ) ,
557
+ "app/routes/parent.child.tsx" : js `
558
+ import * as React from 'react';
559
+ import { useLoaderData, useRouteError } from '@remix-run/react';
560
+ export async function clientLoader({ serverLoader }) {
561
+ return await serverLoader();
562
+ }
563
+ export default function Component() {
564
+ return <p>Child</p>;
565
+ }
566
+ export function HydrateFallback() {
567
+ return <p>Loading...</p>;
568
+ }
569
+ export function ErrorBoundary() {
570
+ let error = useRouteError();
571
+ return <p id="child-error">{error.status} {error.data}</p>;
572
+ }
573
+ ` ,
574
+ } ,
575
+ } )
576
+ ) ;
577
+ let app = new PlaywrightFixture ( appFixture , page ) ;
578
+
579
+ await app . goto ( "/parent/child" ) ;
580
+ await page . waitForSelector ( "#child-error" ) ;
581
+ let html = await app . getHtml ( "#child-error" ) ;
582
+ expect ( html . replace ( / \n / g, " " ) . replace ( / + / g, " " ) ) . toMatch (
583
+ "400 Error: You are trying to call serverLoader() on a route that does " +
584
+ 'not have a server loader (routeId: "routes/parent.child")'
585
+ ) ;
586
+ } ) ;
587
+
588
+ test ( "initial hydration data check functions properly" , async ( {
589
+ page,
590
+ } ) => {
591
+ appFixture = await createAppFixture (
592
+ await createFixture ( {
593
+ files : {
594
+ ...getFiles ( {
595
+ parentClientLoader : false ,
596
+ parentClientLoaderHydrate : false ,
597
+ childClientLoader : false ,
598
+ childClientLoaderHydrate : false ,
599
+ } ) ,
600
+ "app/routes/parent.child.tsx" : js `
601
+ import * as React from 'react';
602
+ import { json } from '@remix-run/node';
603
+ import { useLoaderData, useRevalidator } from '@remix-run/react';
604
+ let isFirstCall = true;
605
+ export async function loader({ serverLoader }) {
606
+ if (isFirstCall) {
607
+ isFirstCall = false
608
+ return json({
609
+ message: "Child Server Loader Data (1)",
610
+ });
611
+ }
612
+ return json({
613
+ message: "Child Server Loader Data (2+)",
614
+ });
615
+ }
616
+ export async function clientLoader({ serverLoader }) {
617
+ await new Promise(r => setTimeout(r, 100));
618
+ let serverData = await serverLoader();
619
+ return {
620
+ message: serverData.message + " (mutated by client)",
621
+ };
622
+ }
623
+ clientLoader.hydrate=true;
624
+ export default function Component() {
625
+ let data = useLoaderData();
626
+ let revalidator = useRevalidator();
627
+ return (
628
+ <>
629
+ <p id="child-data">{data.message}</p>
630
+ <button onClick={() => revalidator.revalidate()}>Revalidate</button>
631
+ </>
632
+ );
633
+ }
634
+ export function HydrateFallback() {
635
+ return <p>Loading...</p>
636
+ }
637
+ ` ,
638
+ } ,
639
+ } )
640
+ ) ;
641
+ let app = new PlaywrightFixture ( appFixture , page ) ;
642
+
643
+ await app . goto ( "/parent/child" ) ;
644
+ await page . waitForSelector ( "#child-data" ) ;
645
+ let html = await app . getHtml ( ) ;
646
+ expect ( html ) . toMatch ( "Child Server Loader Data (1) (mutated by client)" ) ;
647
+ app . clickElement ( "button" ) ;
648
+ await page . waitForSelector ( ':has-text("Child Server Loader Data (2+)")' ) ;
649
+ html = await app . getHtml ( "main" ) ;
650
+ expect ( html ) . toMatch ( "Child Server Loader Data (2+) (mutated by client)" ) ;
651
+ } ) ;
652
+
653
+ test ( "initial hydration data check functions properly even if serverLoader isn't called on hydration" , async ( {
654
+ page,
655
+ } ) => {
656
+ appFixture = await createAppFixture (
657
+ await createFixture ( {
658
+ files : {
659
+ ...getFiles ( {
660
+ parentClientLoader : false ,
661
+ parentClientLoaderHydrate : false ,
662
+ childClientLoader : false ,
663
+ childClientLoaderHydrate : false ,
664
+ } ) ,
665
+ "app/routes/parent.child.tsx" : js `
666
+ import * as React from 'react';
667
+ import { json } from '@remix-run/node';
668
+ import { useLoaderData, useRevalidator } from '@remix-run/react';
669
+ let isFirstCall = true;
670
+ export async function loader({ serverLoader }) {
671
+ if (isFirstCall) {
672
+ isFirstCall = false
673
+ return json({
674
+ message: "Child Server Loader Data (1)",
675
+ });
676
+ }
677
+ return json({
678
+ message: "Child Server Loader Data (2+)",
679
+ });
680
+ }
681
+ let isFirstClientCall = true;
682
+ export async function clientLoader({ serverLoader }) {
683
+ await new Promise(r => setTimeout(r, 100));
684
+ if (isFirstClientCall) {
685
+ isFirstClientCall = false;
686
+ // First time through - don't even call serverLoader
687
+ return {
688
+ message: "Child Client Loader Data",
689
+ };
690
+ }
691
+ // Only call the serverLoader on subsequent calls and this
692
+ // should *not* return us the initialData any longer
693
+ let serverData = await serverLoader();
694
+ return {
695
+ message: serverData.message + " (mutated by client)",
696
+ };
697
+ }
698
+ clientLoader.hydrate=true;
699
+ export default function Component() {
700
+ let data = useLoaderData();
701
+ let revalidator = useRevalidator();
702
+ return (
703
+ <>
704
+ <p id="child-data">{data.message}</p>
705
+ <button onClick={() => revalidator.revalidate()}>Revalidate</button>
706
+ </>
707
+ );
708
+ }
709
+ export function HydrateFallback() {
710
+ return <p>Loading...</p>
711
+ }
712
+ ` ,
713
+ } ,
714
+ } )
715
+ ) ;
716
+ let app = new PlaywrightFixture ( appFixture , page ) ;
717
+
718
+ await app . goto ( "/parent/child" ) ;
719
+ await page . waitForSelector ( "#child-data" ) ;
720
+ let html = await app . getHtml ( ) ;
721
+ expect ( html ) . toMatch ( "Child Client Loader Data" ) ;
722
+ app . clickElement ( "button" ) ;
723
+ await page . waitForSelector ( ':has-text("Child Server Loader Data (2+)")' ) ;
724
+ html = await app . getHtml ( "main" ) ;
725
+ expect ( html ) . toMatch ( "Child Server Loader Data (2+) (mutated by client)" ) ;
726
+ } ) ;
548
727
} ) ;
549
728
550
729
test . describe ( "clientLoader - lazy route module" , ( ) => {
@@ -632,6 +811,50 @@ test.describe("Client Data", () => {
632
811
expect ( html ) . toMatch ( "Parent Server Loader (mutated by client)" ) ;
633
812
expect ( html ) . toMatch ( "Child Server Loader (mutated by client" ) ;
634
813
} ) ;
814
+
815
+ test ( "throws a 400 if you call serverLoader without a server loader" , async ( {
816
+ page,
817
+ } ) => {
818
+ appFixture = await createAppFixture (
819
+ await createFixture ( {
820
+ files : {
821
+ ...getFiles ( {
822
+ parentClientLoader : false ,
823
+ parentClientLoaderHydrate : false ,
824
+ childClientLoader : false ,
825
+ childClientLoaderHydrate : false ,
826
+ } ) ,
827
+ "app/routes/parent.child.tsx" : js `
828
+ import * as React from 'react';
829
+ import { useLoaderData, useRouteError } from '@remix-run/react';
830
+ export async function clientLoader({ serverLoader }) {
831
+ return await serverLoader();
832
+ }
833
+ export default function Component() {
834
+ return <p>Child</p>;
835
+ }
836
+ export function HydrateFallback() {
837
+ return <p>Loading...</p>;
838
+ }
839
+ export function ErrorBoundary() {
840
+ let error = useRouteError();
841
+ return <p id="child-error">{error.status} {error.data}</p>;
842
+ }
843
+ ` ,
844
+ } ,
845
+ } )
846
+ ) ;
847
+ let app = new PlaywrightFixture ( appFixture , page ) ;
848
+
849
+ await app . goto ( "/" ) ;
850
+ await app . clickLink ( "/parent/child" ) ;
851
+ await page . waitForSelector ( "#child-error" ) ;
852
+ let html = await app . getHtml ( "#child-error" ) ;
853
+ expect ( html . replace ( / \n / g, " " ) . replace ( / + / g, " " ) ) . toMatch (
854
+ "400 Error: You are trying to call serverLoader() on a route that does " +
855
+ 'not have a server loader (routeId: "routes/parent.child")'
856
+ ) ;
857
+ } ) ;
635
858
} ) ;
636
859
637
860
test . describe ( "clientAction - critical route module" , ( ) => {
@@ -796,6 +1019,51 @@ test.describe("Client Data", () => {
796
1019
expect ( html ) . toMatch ( "Child Server Loader (mutated by client)" ) ;
797
1020
expect ( html ) . toMatch ( "Child Server Action (mutated by client)" ) ;
798
1021
} ) ;
1022
+
1023
+ test ( "throws a 400 if you call serverAction without a server action" , async ( {
1024
+ page,
1025
+ } ) => {
1026
+ appFixture = await createAppFixture (
1027
+ await createFixture ( {
1028
+ files : {
1029
+ ...getFiles ( {
1030
+ parentClientLoader : false ,
1031
+ parentClientLoaderHydrate : false ,
1032
+ childClientLoader : false ,
1033
+ childClientLoaderHydrate : false ,
1034
+ } ) ,
1035
+ "app/routes/parent.child.tsx" : js `
1036
+ import * as React from 'react';
1037
+ import { json } from '@remix-run/node';
1038
+ import { Form, useRouteError } from '@remix-run/react';
1039
+ export async function clientAction({ serverAction }) {
1040
+ return await serverAction();
1041
+ }
1042
+ export default function Component() {
1043
+ return (
1044
+ <Form method="post">
1045
+ <button type="submit">Submit</button>
1046
+ </Form>
1047
+ );
1048
+ }
1049
+ export function ErrorBoundary() {
1050
+ let error = useRouteError();
1051
+ return <p id="child-error">{error.status} {error.data}</p>;
1052
+ }
1053
+ ` ,
1054
+ } ,
1055
+ } )
1056
+ ) ;
1057
+ let app = new PlaywrightFixture ( appFixture , page ) ;
1058
+ await app . goto ( "/parent/child" ) ;
1059
+ app . clickSubmitButton ( "/parent/child" ) ;
1060
+ await page . waitForSelector ( "#child-error" ) ;
1061
+ let html = await app . getHtml ( "#child-error" ) ;
1062
+ expect ( html . replace ( / \n / g, " " ) . replace ( / + / g, " " ) ) . toMatch (
1063
+ "400 Error: You are trying to call serverAction() on a route that does " +
1064
+ 'not have a server action (routeId: "routes/parent.child")'
1065
+ ) ;
1066
+ } ) ;
799
1067
} ) ;
800
1068
801
1069
test . describe ( "clientAction - lazy route module" , ( ) => {
@@ -968,5 +1236,52 @@ test.describe("Client Data", () => {
968
1236
expect ( html ) . toMatch ( "Child Server Loader (mutated by client)" ) ;
969
1237
expect ( html ) . toMatch ( "Child Server Action (mutated by client)" ) ;
970
1238
} ) ;
1239
+
1240
+ test ( "throws a 400 if you call serverAction without a server action" , async ( {
1241
+ page,
1242
+ } ) => {
1243
+ appFixture = await createAppFixture (
1244
+ await createFixture ( {
1245
+ files : {
1246
+ ...getFiles ( {
1247
+ parentClientLoader : false ,
1248
+ parentClientLoaderHydrate : false ,
1249
+ childClientLoader : false ,
1250
+ childClientLoaderHydrate : false ,
1251
+ } ) ,
1252
+ "app/routes/parent.child.tsx" : js `
1253
+ import * as React from 'react';
1254
+ import { json } from '@remix-run/node';
1255
+ import { Form, useRouteError } from '@remix-run/react';
1256
+ export async function clientAction({ serverAction }) {
1257
+ return await serverAction();
1258
+ }
1259
+ export default function Component() {
1260
+ return (
1261
+ <Form method="post">
1262
+ <button type="submit">Submit</button>
1263
+ </Form>
1264
+ );
1265
+ }
1266
+ export function ErrorBoundary() {
1267
+ let error = useRouteError();
1268
+ return <p id="child-error">{error.status} {error.data}</p>;
1269
+ }
1270
+ ` ,
1271
+ } ,
1272
+ } )
1273
+ ) ;
1274
+ let app = new PlaywrightFixture ( appFixture , page ) ;
1275
+ await app . goto ( "/" ) ;
1276
+ await app . goto ( "/parent/child" ) ;
1277
+ await page . waitForSelector ( "form" ) ;
1278
+ app . clickSubmitButton ( "/parent/child" ) ;
1279
+ await page . waitForSelector ( "#child-error" ) ;
1280
+ let html = await app . getHtml ( "#child-error" ) ;
1281
+ expect ( html . replace ( / \n / g, " " ) . replace ( / + / g, " " ) ) . toMatch (
1282
+ "400 Error: You are trying to call serverAction() on a route that does " +
1283
+ 'not have a server action (routeId: "routes/parent.child")'
1284
+ ) ;
1285
+ } ) ;
971
1286
} ) ;
972
1287
} ) ;
0 commit comments