| 1 | import * as BrowserHttpClient from "@effect/platform-browser/BrowserHttpClient" |
| 2 | import { assert, describe, it } from "@effect/vitest" |
| 3 | import * as Effect from "effect/Effect" |
| 4 | import * as Cookies from "effect/http/Cookies" |
| 5 | import * as HttpClient from "effect/http/HttpClient" |
| 6 | import * as Layer from "effect/Layer" |
| 7 | import * as Stream from "effect/Stream" |
| 8 | import * as MXHR from "mock-xmlhttprequest" |
| 9 | |
| 10 | const 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 | |
| 22 | describe("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 | }) |