1import * as BrowserHttpClient from "@effect/platform-browser/BrowserHttpClient"
2import { assert, describe, it } from "@effect/vitest"
3import * as Effect from "effect/Effect"
4import * as Cookies from "effect/http/Cookies"
5import * as HttpClient from "effect/http/HttpClient"
6import * as Layer from "effect/Layer"
7import * as Stream from "effect/Stream"
8import * as MXHR from "mock-xmlhttprequest"
9
10const layer = (routes: Parameters<typeof MXHR.newServer>[0], responseURL = "") =>
11 Layer.unwrap(Effect.sync(() => {
12 const server = MXHR.newServer(routes)
13 return BrowserHttpClient.layerXMLHttpRequest.pipe(
14 Layer.provide(Layer.succeed(BrowserHttpClient.XMLHttpRequest, () => {
15 const xhr = server.xhrFactory()
16 xhr.responseURL = responseURL
17 return xhr
18 }))
19 )
20 }))
21
22describe("BrowserHttpClient", () => {
23 it.effect("json", () =>
24 Effect.gen(function*() {
25 const response = yield* HttpClient.get("http://localhost:8080/my/url")
26 assert.strictEqual(response.url, "http://localhost:8080/final?value=1")
27 assert.deepStrictEqual(yield* response.json, { message: "Success!" })
28 }).pipe(Effect.provide(layer({
29 get: ["http://localhost:8080/my/url", {
30 headers: { "Content-Type": "application/json" },
31 body: "{ \"message\": \"Success!\" }"
32 }]
33 }, "http://localhost:8080/final?value=1#fragment"))))
34
35 it.effect("stream", () =>
36 Effect.gen(function*() {
37 const response = yield* HttpClient.get("http://localhost:8080/my/url?existing=1", {
38 urlParams: { value: "a#b" },
39 hash: "fragment"
40 })
41 assert.strictEqual(response.url, "http://localhost:8080/my/url?existing=1&value=a%23b")
42 const body = yield* response.stream.pipe(Stream.decodeText(), Stream.mkString)
43 assert.deepStrictEqual(body, "{ \"message\": \"Success!\" }")
44 }).pipe(Effect.provide(layer({
45 get: ["http://localhost:8080/my/url?existing=1&value=a%23b#fragment", {
46 headers: { "Content-Type": "application/json" },
47 body: "{ \"message\": \"Success!\" }"
48 }]
49 }))))
50
51 it.effect("cookies", () =>
52 Effect.gen(function*() {
53 const cookies = yield* HttpClient.get("http://localhost:8080/my/url").pipe(
54 Effect.map((response) => response.cookies)
55 )
56 assert.deepStrictEqual(Cookies.toRecord(cookies), {
57 foo: "bar"
58 })
59 }).pipe(Effect.provide(layer({
60 get: ["http://localhost:8080/my/url", {
61 headers: { "Content-Type": "application/json", "Set-Cookie": "foo=bar; HttpOnly; Secure" },
62 body: "{ \"message\": \"Success!\" }"
63 }]
64 }))))
65
66 it.effect("arrayBuffer", () =>
67 Effect.gen(function*() {
68 const body = yield* HttpClient.get("http://localhost:8080/my/url").pipe(
69 Effect.flatMap((response) => response.arrayBuffer),
70 BrowserHttpClient.withXHRArrayBuffer
71 )
72 assert.strictEqual(new TextDecoder().decode(body), "{ \"message\": \"Success!\" }")
73 }).pipe(Effect.provide(layer({
74 get: ["http://localhost:8080/my/url", {
75 headers: { "Content-Type": "application/json" },
76 body: "{ \"message\": \"Success!\" }"
77 }]
78 }))))
79
80 it.effect("arrayBuffer without withXHRArrayBuffer", () =>
81 Effect.gen(function*() {
82 const body = yield* HttpClient.get("http://localhost:8080/my/url").pipe(
83 Effect.flatMap((response) => response.arrayBuffer)
84 )
85 assert.strictEqual(new TextDecoder().decode(body), "{ \"message\": \"Success!\" }")
86 }).pipe(Effect.provide(layer({
87 get: ["http://localhost:8080/my/url", {
88 headers: { "Content-Type": "application/json" },
89 body: "{ \"message\": \"Success!\" }"
90 }]
91 }))))
92
93 it.effect("readers in ArrayBuffer mode", () =>
94 Effect.gen(function*() {
95 const response = yield* HttpClient.get("http://localhost:8080/my/url").pipe(
96 BrowserHttpClient.withXHRArrayBuffer
97 )
98 assert.strictEqual(yield* response.text, "{ \"message\": \"café\" }")
99 assert.deepStrictEqual(yield* response.json, { message: "café" })
100 assert.strictEqual(
101 yield* response.stream.pipe(Stream.decodeText(), Stream.mkString),
102 "{ \"message\": \"café\" }"
103 )
104 }).pipe(Effect.provide(layer({
105 get: ["http://localhost:8080/my/url", {
106 headers: { "Content-Type": "application/json" },
107 body: "{ \"message\": \"café\" }"
108 }]
109 }))))
110
111 it.effect("decodes an XHR response as FormData", () =>
112 Effect.gen(function*() {
113 const formData = yield* HttpClient.get("http://localhost/form").pipe(
114 Effect.flatMap((response) => response.formData)
115 )
116 assert.strictEqual(formData.get("value"), "test")
117 }).pipe(Effect.provide(layer({
118 get: ["http://localhost/form", {
119 headers: { "content-type": "multipart/form-data; boundary=x" },
120 body: "--x\r\nContent-Disposition: form-data; name=\"value\"\r\n\r\ntest\r\n--x--\r\n"
121 }]
122 }))))
123})