@@ -4,14 +4,15 @@ import createClient, {
4
4
type BodySerializer ,
5
5
type FetchOptions ,
6
6
type MethodResponse ,
7
+ type HeadersOptions ,
7
8
type Middleware ,
8
9
type MiddlewareCallbackParams ,
9
10
type QuerySerializerOptions ,
10
11
type Client ,
11
12
type PathBasedClient ,
12
13
createPathBasedClient ,
13
14
} from "../src/index.js" ;
14
- import { server , baseUrl , useMockRequestHandler , toAbsoluteURL } from "./fixtures/mock-server.js" ;
15
+ import { baseUrl , server , toAbsoluteURL , useMockRequestHandler } from "./fixtures/mock-server.js" ;
15
16
import type { paths } from "./fixtures/api.js" ;
16
17
17
18
beforeAll ( ( ) => {
@@ -819,12 +820,7 @@ describe("client", () => {
819
820
await client . GET ( "/self" ) ;
820
821
821
822
// assert default headers were passed
822
- expect ( getRequest ( ) . headers ) . toEqual (
823
- new Headers ( {
824
- ...headers , // assert new header got passed
825
- "Content-Type" : "application/json" , // probably doesn’t need to get tested, but this was simpler than writing lots of code to ignore these
826
- } ) ,
827
- ) ;
823
+ expect ( getRequest ( ) . headers ) . toEqual ( new Headers ( headers ) ) ;
828
824
} ) ;
829
825
830
826
it ( "can be overridden" , async ( ) => {
@@ -850,7 +846,6 @@ describe("client", () => {
850
846
expect ( getRequest ( ) . headers ) . toEqual (
851
847
new Headers ( {
852
848
"Cache-Control" : "no-cache" ,
853
- "Content-Type" : "application/json" ,
854
849
} ) ,
855
850
) ;
856
851
} ) ;
@@ -894,6 +889,139 @@ describe("client", () => {
894
889
} ) ;
895
890
} ) ;
896
891
892
+ describe ( "content-type" , ( ) => {
893
+ const BODY_ACCEPTING_METHODS = [ [ "PUT" ] , [ "POST" ] , [ "DELETE" ] , [ "OPTIONS" ] , [ "PATCH" ] ] as const ;
894
+ const ALL_METHODS = [ ...BODY_ACCEPTING_METHODS , [ "GET" ] , [ "HEAD" ] ] as const ;
895
+
896
+ const fireRequestAndGetContentType = async ( options : {
897
+ defaultHeaders ?: HeadersOptions ;
898
+ method : ( typeof ALL_METHODS ) [ number ] [ number ] ;
899
+ fetchOptions : FetchOptions < any > ;
900
+ } ) => {
901
+ const client = createClient < any > ( { baseUrl, headers : options . defaultHeaders } ) ;
902
+ const { getRequest } = useMockRequestHandler ( {
903
+ baseUrl,
904
+ method : "all" ,
905
+ path : "/blogposts-optional" ,
906
+ status : 200 ,
907
+ } ) ;
908
+ await client [ options . method ] ( "/blogposts-optional" , options . fetchOptions as any ) ;
909
+
910
+ const request = getRequest ( ) ;
911
+ return request . headers . get ( "content-type" ) ;
912
+ } ;
913
+
914
+ it . each ( ALL_METHODS ) ( "no content-type for body-less requests - %s" , async ( method ) => {
915
+ const contentType = await fireRequestAndGetContentType ( {
916
+ method,
917
+ fetchOptions : { } ,
918
+ } ) ;
919
+
920
+ expect ( contentType ) . toBe ( null ) ;
921
+ } ) ;
922
+
923
+ it . each ( ALL_METHODS ) ( "no content-type for `undefined` body requests - %s" , async ( method ) => {
924
+ const contentType = await fireRequestAndGetContentType ( {
925
+ method,
926
+ fetchOptions : {
927
+ body : undefined ,
928
+ } ,
929
+ } ) ;
930
+
931
+ expect ( contentType ) . toBe ( null ) ;
932
+ } ) ;
933
+
934
+ const BODIES = [ { prop : "a" } , { } , "" , "str" , null , false , 0 , 1 , new Date ( "2024-08-07T09:52:00.836Z" ) ] as const ;
935
+ const METHOD_BODY_COMBINATIONS = BODY_ACCEPTING_METHODS . flatMap ( ( [ method ] ) =>
936
+ BODIES . map ( ( body ) => [ method , body ] as const ) ,
937
+ ) ;
938
+
939
+ it . each ( METHOD_BODY_COMBINATIONS ) (
940
+ "implicit default content-type for body-full requests - %s, %j" ,
941
+ async ( method , body ) => {
942
+ const contentType = await fireRequestAndGetContentType ( {
943
+ method,
944
+ fetchOptions : {
945
+ body,
946
+ } ,
947
+ } ) ;
948
+
949
+ expect ( contentType ) . toBe ( "application/json" ) ;
950
+ } ,
951
+ ) ;
952
+
953
+ it . each ( METHOD_BODY_COMBINATIONS ) (
954
+ "provided default content-type for body-full requests - %s, %j" ,
955
+ async ( method , body ) => {
956
+ const contentType = await fireRequestAndGetContentType ( {
957
+ defaultHeaders : {
958
+ "content-type" : "application/my-json" ,
959
+ } ,
960
+ method,
961
+ fetchOptions : {
962
+ body,
963
+ } ,
964
+ } ) ;
965
+
966
+ expect ( contentType ) . toBe ( "application/my-json" ) ;
967
+ } ,
968
+ ) ;
969
+
970
+ it . each ( METHOD_BODY_COMBINATIONS ) (
971
+ "native-fetch default content-type for body-full requests, when default is suppressed - %s, %j" ,
972
+ async ( method , body ) => {
973
+ const contentType = await fireRequestAndGetContentType ( {
974
+ defaultHeaders : {
975
+ "content-type" : null ,
976
+ } ,
977
+ method,
978
+ fetchOptions : {
979
+ body,
980
+ } ,
981
+ } ) ;
982
+ // the fetch implementation won't allow sending a body without content-type,
983
+ // and it defaults to `text/plain;charset=UTF-8`, however the actual default value
984
+ // is irrelevant and might be flaky across different fetch implementations
985
+ // for us, it's important that it's not `application/json`
986
+ expect ( contentType ) . not . toBe ( "application/json" ) ;
987
+ } ,
988
+ ) ;
989
+
990
+ it . each ( METHOD_BODY_COMBINATIONS ) (
991
+ "specified content-type for body-full requests - %s, %j" ,
992
+ async ( method , body ) => {
993
+ const contentType = await fireRequestAndGetContentType ( {
994
+ method,
995
+ fetchOptions : {
996
+ body,
997
+ headers : {
998
+ "content-type" : "application/my-json" ,
999
+ } ,
1000
+ } ,
1001
+ } ) ;
1002
+
1003
+ expect ( contentType ) . toBe ( "application/my-json" ) ;
1004
+ } ,
1005
+ ) ;
1006
+
1007
+ it . each ( METHOD_BODY_COMBINATIONS ) (
1008
+ "specified content-type for body-full requests, even when default is suppressed - %s, %j" ,
1009
+ async ( method , body ) => {
1010
+ const contentType = await fireRequestAndGetContentType ( {
1011
+ method,
1012
+ fetchOptions : {
1013
+ body,
1014
+ headers : {
1015
+ "content-type" : "application/my-json" ,
1016
+ } ,
1017
+ } ,
1018
+ } ) ;
1019
+
1020
+ expect ( contentType ) . toBe ( "application/my-json" ) ;
1021
+ } ,
1022
+ ) ;
1023
+ } ) ;
1024
+
897
1025
describe ( "fetch" , ( ) => {
898
1026
it ( "createClient" , async ( ) => {
899
1027
function createCustomFetch ( data : any ) {
0 commit comments