From 3753e49361890aa2e4ce7bb56fcc2af087ca54e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lio=20Costa?= Date: Thu, 8 Oct 2026 18:38:38 -0300 Subject: [PATCH] Add Biome lint and format checks to CI CI gains a lint job on Node 22 that runs `pnpm lint` (`biome check .`): formatting, the recommended lint rules with noExplicitAny as an error, and import sorting. Biome is pinned to 2.5.13 and biome.json copies the product repo's settings (2-space indent, line width 100, double quotes, semicolons). `pnpm lint:fix` applies the safe fixes locally. Existing code brought in line: formatter line wrapping in 8 files plus package.json, one sorted type import, and one test assertion rewritten as toHaveLength so it no longer trips noUnsafeOptionalChaining. The emitted JS and type declarations are unchanged apart from that import order. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 14 +++++ biome.json | 29 +++++++++ package.json | 20 +++++- pnpm-lock.yaml | 95 ++++++++++++++++++++++++++++ src/error.ts | 3 +- src/http.ts | 9 +-- src/resources/contacts.ts | 8 ++- src/resources/emails.ts | 10 ++- src/resources/templates.ts | 5 +- src/resources/topics.ts | 6 +- test/client.test.ts | 9 ++- test/resources.test.ts | 124 ++++++++++++++++++++++++++++++------- 12 files changed, 293 insertions(+), 39 deletions(-) create mode 100644 biome.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9735b11..2bc95d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,3 +27,17 @@ jobs: - run: pnpm typecheck - run: pnpm test - run: pnpm build + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4.3.0 + with: + version: 9 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm lint diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..74f85d2 --- /dev/null +++ b/biome.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.5.13/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "linter": { + "enabled": true, + "rules": { + "preset": "recommended", + "suspicious": { + "noExplicitAny": "error" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "always" + } + } +} diff --git a/package.json b/package.json index b80ca74..a5e89ee 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,15 @@ "type": "git", "url": "git+https://github.com/MillionSend/millionsend-node.git" }, - "keywords": ["millionsend", "resend", "email", "ses", "sdk", "transactional", "broadcast"], + "keywords": [ + "millionsend", + "resend", + "email", + "ses", + "sdk", + "transactional", + "broadcast" + ], "exports": { ".": { "types": "./dist/index.d.ts", @@ -20,7 +28,12 @@ "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/index.d.ts", - "files": ["dist", "README.md", "CHANGELOG.md", "LICENSE"], + "files": [ + "dist", + "README.md", + "CHANGELOG.md", + "LICENSE" + ], "engines": { "node": ">=18" }, @@ -28,9 +41,12 @@ "build": "tsc -p tsconfig.build.json", "typecheck": "tsc --noEmit", "test": "vitest run", + "lint": "biome check .", + "lint:fix": "biome check --write .", "prepack": "pnpm build" }, "devDependencies": { + "@biomejs/biome": "2.5.13", "@types/node": "^22.20.1", "typescript": "^5.9.3", "vitest": "^3.2.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2e6bf6..098b350 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@biomejs/biome': + specifier: 2.5.13 + version: 2.5.13 '@types/node': specifier: ^22.20.1 version: 22.20.1 @@ -20,6 +23,63 @@ importers: packages: + '@biomejs/biome@2.5.13': + resolution: {integrity: sha512-+SEC/mFk1a+5mvUANZgbZTaiZXs1nj4iMhL/PHiqDT5TPUEPFIlliEtkKKZB4N862yylHC3UI+/Sj2I0HJEqhA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.5.13': + resolution: {integrity: sha512-nYSuDJ6zgVqZUkAkJkvhXxsF2PYIrUk/g638K4voCXz7foI9f7b6C2/7+oAihsHQlivZNMUrm/y8ywlcHQtZOw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.5.13': + resolution: {integrity: sha512-KVy1ceEDuJ3AzFxjT9kkxbVy+UANw1pjEMUS6lvKfxjJ+fmkRvc7sQn1Xo6ETgseEI7wUQoV03KSga3XfE8YRQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.5.13': + resolution: {integrity: sha512-CH32xpep3dNS5EVJpAHlYchkBYznxyVZQrx0b6YYYlPL9u9ZeD2NtqiK/6s64+HFS2a7hGnryLlqqZAOh8ax5g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-arm64@2.5.13': + resolution: {integrity: sha512-VlNMtoxOqs0dUR6drxxHr18SNUvI7xxAuHZlH4s/dstYSf3g6RaqVgo8JRnhcOhKz9afWrvtJTboNG1JuYlIDQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-linux-x64-musl@2.5.13': + resolution: {integrity: sha512-F3pmwl+VHoUuVJN/tbNLKeLt0SVK3EIyN1jXlMcNyQGYRQQTVqXF+GgkP0/CjGXFN87e/mv0sBfUnWqWza5PKQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-x64@2.5.13': + resolution: {integrity: sha512-Fi6gIxbUaJ3ZCIXeG3ggIBPF76O2DjDN67WrPX/ODRv54GeXPm2gbEPC96Yb0yrJoiH0Eax1JLx1Pi0XbsNFZQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-win32-arm64@2.5.13': + resolution: {integrity: sha512-+WD13qshXrr0Icv4BfsAdzm8Fs3TL+nZ59zEQxsYNd8lcgZJ0A5+OrlwsL1PipVCmWeRpxgIPD6DAcEd7smkCQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.5.13': + resolution: {integrity: sha512-VOofU/nW761XWzUeUNE8zzYNyPrxuMuNFMizL0qz7J75yeV8N7WFheUlk1/K6dOkaFpH9wJ+lDKlwjAbw0346w==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@esbuild/aix-ppc64@0.28.2': resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} engines: {node: '>=18'} @@ -589,6 +649,41 @@ packages: snapshots: + '@biomejs/biome@2.5.13': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.5.13 + '@biomejs/cli-darwin-x64': 2.5.13 + '@biomejs/cli-linux-arm64': 2.5.13 + '@biomejs/cli-linux-arm64-musl': 2.5.13 + '@biomejs/cli-linux-x64': 2.5.13 + '@biomejs/cli-linux-x64-musl': 2.5.13 + '@biomejs/cli-win32-arm64': 2.5.13 + '@biomejs/cli-win32-x64': 2.5.13 + + '@biomejs/cli-darwin-arm64@2.5.13': + optional: true + + '@biomejs/cli-darwin-x64@2.5.13': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.5.13': + optional: true + + '@biomejs/cli-linux-arm64@2.5.13': + optional: true + + '@biomejs/cli-linux-x64-musl@2.5.13': + optional: true + + '@biomejs/cli-linux-x64@2.5.13': + optional: true + + '@biomejs/cli-win32-arm64@2.5.13': + optional: true + + '@biomejs/cli-win32-x64@2.5.13': + optional: true + '@esbuild/aix-ppc64@0.28.2': optional: true diff --git a/src/error.ts b/src/error.ts index 2daeb30..a9b32a5 100644 --- a/src/error.ts +++ b/src/error.ts @@ -20,7 +20,8 @@ export function toErrorResponse(body: unknown, status: number): ErrorResponse { if (body && typeof body === "object") { const b = body as Record; const name = typeof b.name === "string" ? b.name : "application_error"; - const message = typeof b.message === "string" ? b.message : `Request failed with status ${status}`; + const message = + typeof b.message === "string" ? b.message : `Request failed with status ${status}`; const statusCode = typeof b.statusCode === "number" ? b.statusCode : status; return { name, message, statusCode }; } diff --git a/src/http.ts b/src/http.ts index 6e47491..704a42c 100644 --- a/src/http.ts +++ b/src/http.ts @@ -83,10 +83,11 @@ export class HttpClient { constructor(apiKey: string, options: MillionSendOptions = {}) { this.#apiKey = apiKey; - this.baseUrl = (options.baseUrl ?? process.env.MILLIONSEND_BASE_URL ?? DEFAULT_BASE_URL).replace( - /\/+$/, - "", - ); + this.baseUrl = ( + options.baseUrl ?? + process.env.MILLIONSEND_BASE_URL ?? + DEFAULT_BASE_URL + ).replace(/\/+$/, ""); if (!options.allowInsecureHttp && isInsecureHttpUrl(this.baseUrl)) { throw new Error( `Refusing to send the API key over plain http to ${this.baseUrl}. Use https, or set allowInsecureHttp: true.`, diff --git a/src/resources/contacts.ts b/src/resources/contacts.ts index 0eeb0b8..4142a04 100644 --- a/src/resources/contacts.ts +++ b/src/resources/contacts.ts @@ -17,8 +17,8 @@ import type { CreateContactOptions, CreateContactsBatchOptions, List, - ListContactTopicsOptions, ListContactsOptions, + ListContactTopicsOptions, RemoveContactOptions, RemoveContactResponse, RemoveContactSegmentResponse, @@ -162,7 +162,11 @@ export class Contacts { } update(options: UpdateContactOptions): Promise> { - return this.http.request({ method: "PATCH", path: contactPath(options), body: updateBody(options) }); + return this.http.request({ + method: "PATCH", + path: contactPath(options), + body: updateBody(options), + }); } /** DELETE /contacts/:id — `erase` also scrubs the address from email history (`?erase=true`). */ diff --git a/src/resources/emails.ts b/src/resources/emails.ts index 103f058..254590d 100644 --- a/src/resources/emails.ts +++ b/src/resources/emails.ts @@ -110,7 +110,10 @@ export class Batch { constructor(private readonly http: HttpClient) {} /** POST /emails/batch — 1–100 emails in one call; supports an Idempotency-Key and `batchValidation`. */ - send(payload: SendEmailOptions[], options: BatchRequestOptions = {}): Promise> { + send( + payload: SendEmailOptions[], + options: BatchRequestOptions = {}, + ): Promise> { return this.http.request({ method: "POST", path: "/emails/batch", @@ -121,7 +124,10 @@ export class Batch { } /** Alias of {@link send}, mirroring Resend. */ - create(payload: SendEmailOptions[], options: BatchRequestOptions = {}): Promise> { + create( + payload: SendEmailOptions[], + options: BatchRequestOptions = {}, + ): Promise> { return this.send(payload, options); } } diff --git a/src/resources/templates.ts b/src/resources/templates.ts index e11c2c4..7ea546f 100644 --- a/src/resources/templates.ts +++ b/src/resources/templates.ts @@ -41,7 +41,10 @@ export class Templates { } get(idOrAlias: string): Promise> { - return this.http.request({ method: "GET", path: `/templates/${encodeURIComponent(idOrAlias)}` }); + return this.http.request({ + method: "GET", + path: `/templates/${encodeURIComponent(idOrAlias)}`, + }); } /** PATCH /templates/:idOrAlias — `alias`/`subject`/`text: null` clear the field. */ diff --git a/src/resources/topics.ts b/src/resources/topics.ts index d056dda..247febe 100644 --- a/src/resources/topics.ts +++ b/src/resources/topics.ts @@ -39,7 +39,11 @@ export class Topics { return this.http.request({ method: "PATCH", path: `/topics/${encodeURIComponent(id)}`, - body: { name: payload.name, description: payload.description, visibility: payload.visibility }, + body: { + name: payload.name, + description: payload.description, + visibility: payload.visibility, + }, }); } diff --git a/test/client.test.ts b/test/client.test.ts index 58885e9..f66853f 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -67,7 +67,8 @@ describe("construction", () => { expect(() => new MillionSend("ms_test")).toThrow(/allowInsecureHttp/); delete process.env.MILLIONSEND_BASE_URL; expect( - () => new MillionSend("ms_test", { baseUrl: "http://mail.example.com", allowInsecureHttp: true }), + () => + new MillionSend("ms_test", { baseUrl: "http://mail.example.com", allowInsecureHttp: true }), ).not.toThrow(); expect(() => new MillionSend("ms_test", { baseUrl: "http://localhost:3001" })).not.toThrow(); expect(() => new MillionSend("ms_test", { baseUrl: "http://127.0.0.1:3001" })).not.toThrow(); @@ -164,7 +165,11 @@ describe("request wiring", () => { it("passes the all_recipients_suppressed 422 through by name", async () => { const { ms } = makeClient({ status: 422, - body: { statusCode: 422, name: "all_recipients_suppressed", message: "All recipients are suppressed" }, + body: { + statusCode: 422, + name: "all_recipients_suppressed", + message: "All recipients are suppressed", + }, }); const res = await ms.emails.send({ from: "a@x.dev", to: "b@x.dev", subject: "s", text: "t" }); expect(res.data).toBeNull(); diff --git a/test/resources.test.ts b/test/resources.test.ts index edcc3f7..a23be43 100644 --- a/test/resources.test.ts +++ b/test/resources.test.ts @@ -33,8 +33,13 @@ describe("insights", () => { html_size_bytes: 12345, computed_at: "2026-08-31T00:00:00.000Z", checks: [ - { id: "list_unsubscribe", severity: "major", status: "fail", penalty: 1.25, - detail: { header: null, reason: "missing" } }, + { + id: "list_unsubscribe", + severity: "major", + status: "fail", + penalty: 1.25, + detail: { header: null, reason: "missing" }, + }, { id: "plain_text_part", severity: "minor", status: "pass", penalty: 0 }, ], }; @@ -131,7 +136,7 @@ describe("batch", () => { ); expect(pathOf(calls[0])).toBe("/emails/batch"); expect(Array.isArray(calls[0]?.body)).toBe(true); - expect((calls[0]?.body as unknown[]).length).toBe(2); + expect(calls[0]?.body).toHaveLength(2); expect(calls[0]?.headers["Idempotency-Key"]).toBe("batch-1"); expect(res.data?.data.length).toBe(2); }); @@ -163,7 +168,10 @@ describe("contacts", () => { it("remove and list", async () => { const { ms, calls } = makeClient(); await ms.contacts.remove({ email: "c@x.dev" }); - expect(calls[0]).toMatchObject({ method: "DELETE", url: "https://api.test/contacts/c%40x.dev" }); + expect(calls[0]).toMatchObject({ + method: "DELETE", + url: "https://api.test/contacts/c%40x.dev", + }); await ms.contacts.list({ after: "cur" }); expect(pathOf(calls[1])).toBe("/contacts?after=cur"); }); @@ -171,7 +179,10 @@ describe("contacts", () => { it("remove sends ?erase=true only when asked", async () => { const { ms, calls } = makeClient(); await ms.contacts.remove("c1", { erase: true }); - expect(calls[0]).toMatchObject({ method: "DELETE", url: "https://api.test/contacts/c1?erase=true" }); + expect(calls[0]).toMatchObject({ + method: "DELETE", + url: "https://api.test/contacts/c1?erase=true", + }); await ms.contacts.remove("c1", { erase: false }); expect(pathOf(calls[1])).toBe("/contacts/c1"); }); @@ -228,7 +239,9 @@ describe("contacts", () => { it("list passes include as a comma-separated facet list", async () => { const { ms, calls } = makeClient(); await ms.contacts.list({ limit: 100, include: ["properties", "topics"] }); - expect(decodeURIComponent(pathOf(calls[0]))).toBe("/contacts?limit=100&include=properties,topics"); + expect(decodeURIComponent(pathOf(calls[0]))).toBe( + "/contacts?limit=100&include=properties,topics", + ); await ms.contacts.list({ segmentId: "s1", include: ["topics"] }); expect(decodeURIComponent(pathOf(calls[1]))).toBe("/segments/s1/contacts?include=topics"); }); @@ -259,7 +272,10 @@ describe("contacts", () => { body: { data: [{ object: "contact", contact: "c1", deleted: true }] }, }); const res = await ms.contacts.batch.remove({ ids: ["c1", "c2"] }); - expect(calls[0]).toMatchObject({ method: "POST", url: "https://api.test/contacts/batch/remove" }); + expect(calls[0]).toMatchObject({ + method: "POST", + url: "https://api.test/contacts/batch/remove", + }); expect(calls[0]?.body).toEqual({ ids: ["c1", "c2"] }); expect(res.data?.data[0]).toEqual({ object: "contact", contact: "c1", deleted: true }); await ms.contacts.batch.remove({ emails: ["a@x.dev"] }); @@ -272,7 +288,12 @@ describe("contacts", () => { describe("broadcasts", () => { it("covers the full lifecycle", async () => { const { ms, calls } = makeClient(); - await ms.broadcasts.create({ segmentId: "s1", from: "a@x.dev", subject: "News", html: "

hi

" }); + await ms.broadcasts.create({ + segmentId: "s1", + from: "a@x.dev", + subject: "News", + html: "

hi

", + }); expect(pathOf(calls[0])).toBe("/broadcasts"); expect(calls[0]?.body).toEqual({ segment_id: "s1", @@ -408,7 +429,10 @@ describe("emails: every field reaches the wire", () => { it("list, update and remove hit the right paths", async () => { const { ms, calls } = makeClient({ body: { object: "list", data: [], has_more: false } }); await ms.emails.list({ limit: 10, after: "e1" }); - expect(calls[0]).toMatchObject({ method: "GET", url: "https://api.test/emails?limit=10&after=e1" }); + expect(calls[0]).toMatchObject({ + method: "GET", + url: "https://api.test/emails?limit=10&after=e1", + }); await ms.emails.update({ id: "e1", scheduledAt: "2999-01-01T00:00:00Z" }); expect(calls[1]).toMatchObject({ method: "PATCH", url: "https://api.test/emails/e1" }); expect(calls[1]?.body).toEqual({ scheduled_at: "2999-01-01T00:00:00Z" }); @@ -474,7 +498,10 @@ describe("contacts: segments, topics and batch", () => { }; const { ms, calls } = makeClient({ body: response }); const res = await ms.contacts.batch.create( - [{ email: "a@x.dev", firstName: "A" }, { email: "nope", topics: [{ id: "t1", subscription: "opt_out" }] }], + [ + { email: "a@x.dev", firstName: "A" }, + { email: "nope", topics: [{ id: "t1", subscription: "opt_out" }] }, + ], { onConflict: "upsert", batchValidation: "permissive" }, ); expect(calls[0]).toMatchObject({ @@ -500,7 +527,10 @@ describe("contacts: segments, topics and batch", () => { it("segments.add/remove address the contact by email, id or contactId", async () => { const { ms, calls } = makeClient({ body: { id: "c1" } }); await ms.contacts.segments.add({ id: "c1", segmentId: "s1" }); - expect(calls[0]).toMatchObject({ method: "POST", url: "https://api.test/contacts/c1/segments/s1" }); + expect(calls[0]).toMatchObject({ + method: "POST", + url: "https://api.test/contacts/c1/segments/s1", + }); expect(calls[0]?.body).toBeUndefined(); await ms.contacts.segments.remove({ email: "c@x.dev", segmentId: "s1" }); expect(calls[1]).toMatchObject({ @@ -514,7 +544,10 @@ describe("contacts: segments, topics and batch", () => { it("list with segmentId reads /segments/:id/contacts", async () => { const { ms, calls } = makeClient({ body: { object: "list", data: [], has_more: false } }); await ms.contacts.list({ segmentId: "s1", limit: 5 }); - expect(calls[0]).toMatchObject({ method: "GET", url: "https://api.test/segments/s1/contacts?limit=5" }); + expect(calls[0]).toMatchObject({ + method: "GET", + url: "https://api.test/segments/s1/contacts?limit=5", + }); }); it("get returns the typed property objects the API sends", async () => { @@ -577,7 +610,11 @@ describe("broadcasts: full body and null clears", () => { describe("topics: visibility and update", () => { it("create passes visibility; update patches /topics/:id", async () => { const { ms, calls } = makeClient(); - await ms.topics.create({ name: "Product", defaultSubscription: "opt_in", visibility: "public" }); + await ms.topics.create({ + name: "Product", + defaultSubscription: "opt_in", + visibility: "public", + }); expect(calls[0]?.body).toEqual({ name: "Product", default_subscription: "opt_in", @@ -624,10 +661,16 @@ describe("suppressions", () => { it("batch.add and batch.remove post to the batch paths", async () => { const { ms, calls } = makeClient({ body: { data: [] } }); await ms.suppressions.batch.add({ emails: ["a@x.dev", "b@x.dev"], origin: "unsubscribe" }); - expect(calls[0]).toMatchObject({ method: "POST", url: "https://api.test/suppressions/batch/add" }); + expect(calls[0]).toMatchObject({ + method: "POST", + url: "https://api.test/suppressions/batch/add", + }); expect(calls[0]?.body).toEqual({ emails: ["a@x.dev", "b@x.dev"], origin: "unsubscribe" }); await ms.suppressions.batch.remove({ ids: ["sup1"] }); - expect(calls[1]).toMatchObject({ method: "POST", url: "https://api.test/suppressions/batch/remove" }); + expect(calls[1]).toMatchObject({ + method: "POST", + url: "https://api.test/suppressions/batch/remove", + }); expect(calls[1]?.body).toEqual({ ids: ["sup1"] }); await ms.suppressions.batch.remove({ emails: ["a@x.dev"] }); expect(calls[2]?.body).toEqual({ emails: ["a@x.dev"] }); @@ -736,7 +779,11 @@ describe("webhooks", () => { describe("apiKeys", () => { it("create returns the token once; list and remove", async () => { const { ms, calls } = makeClient({ body: { id: "k1", token: "ms_secret" } }); - const res = await ms.apiKeys.create({ name: "ci", permission: "sending_access", domainId: "d1" }); + const res = await ms.apiKeys.create({ + name: "ci", + permission: "sending_access", + domainId: "d1", + }); expect(calls[0]).toMatchObject({ method: "POST", url: "https://api.test/api-keys" }); expect(calls[0]?.body).toEqual({ name: "ci", permission: "sending_access", domain_id: "d1" }); expect(res.data?.token).toBe("ms_secret"); @@ -767,14 +814,25 @@ describe("templates", () => { text: "Hi", alias: "welcome", }); - await ms.templates.update("welcome", { alias: null, subject: null, text: null, html: "

Yo

" }); + await ms.templates.update("welcome", { + alias: null, + subject: null, + text: null, + html: "

Yo

", + }); expect(calls[1]).toMatchObject({ method: "PATCH", url: "https://api.test/templates/welcome" }); expect(calls[1]?.body).toEqual({ html: "

Yo

", subject: null, text: null, alias: null }); }); it("passes from/replyTo/variables through unchanged (the API decides)", async () => { const { ms, calls } = makeClient(); - await ms.templates.create({ name: "n", html: "

", from: "a@x.dev", replyTo: ["r@x.dev"], variables: [] }); + await ms.templates.create({ + name: "n", + html: "

", + from: "a@x.dev", + replyTo: ["r@x.dev"], + variables: [], + }); expect(calls[0]?.body).toEqual({ name: "n", html: "

", @@ -791,9 +849,15 @@ describe("templates", () => { await ms.templates.get("welcome"); expect(calls[1]).toMatchObject({ method: "GET", url: "https://api.test/templates/welcome" }); await ms.templates.publish("tp1"); - expect(calls[2]).toMatchObject({ method: "POST", url: "https://api.test/templates/tp1/publish" }); + expect(calls[2]).toMatchObject({ + method: "POST", + url: "https://api.test/templates/tp1/publish", + }); await ms.templates.duplicate("tp1"); - expect(calls[3]).toMatchObject({ method: "POST", url: "https://api.test/templates/tp1/duplicate" }); + expect(calls[3]).toMatchObject({ + method: "POST", + url: "https://api.test/templates/tp1/duplicate", + }); await ms.templates.remove("tp1"); expect(calls[4]).toMatchObject({ method: "DELETE", url: "https://api.test/templates/tp1" }); }); @@ -806,14 +870,26 @@ describe("contactProperties", () => { expect(calls[0]).toMatchObject({ method: "POST", url: "https://api.test/contact-properties" }); expect(calls[0]?.body).toEqual({ key: "plan", type: "string", fallback_value: "free" }); await ms.contactProperties.list({ limit: 3 }); - expect(calls[1]).toMatchObject({ method: "GET", url: "https://api.test/contact-properties?limit=3" }); + expect(calls[1]).toMatchObject({ + method: "GET", + url: "https://api.test/contact-properties?limit=3", + }); await ms.contactProperties.get("p1"); - expect(calls[2]).toMatchObject({ method: "GET", url: "https://api.test/contact-properties/p1" }); + expect(calls[2]).toMatchObject({ + method: "GET", + url: "https://api.test/contact-properties/p1", + }); await ms.contactProperties.update({ id: "p1", fallbackValue: null }); - expect(calls[3]).toMatchObject({ method: "PATCH", url: "https://api.test/contact-properties/p1" }); + expect(calls[3]).toMatchObject({ + method: "PATCH", + url: "https://api.test/contact-properties/p1", + }); expect(calls[3]?.body).toEqual({ fallback_value: null }); await ms.contactProperties.remove("p1"); - expect(calls[4]).toMatchObject({ method: "DELETE", url: "https://api.test/contact-properties/p1" }); + expect(calls[4]).toMatchObject({ + method: "DELETE", + url: "https://api.test/contact-properties/p1", + }); }); });