chore: import upstream snapshot with attribution
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { parseSubmitResponse } from "../src/client/index.js";
|
||||
|
||||
describe("parseSubmitResponse", () => {
|
||||
it("unwraps successful plugin API responses from the standard API envelope", () => {
|
||||
expect(
|
||||
parseSubmitResponse({
|
||||
data: {
|
||||
success: true,
|
||||
message: "Thanks",
|
||||
redirect: "/thanks",
|
||||
},
|
||||
}),
|
||||
).toEqual({ success: true, message: "Thanks", redirect: "/thanks" });
|
||||
});
|
||||
|
||||
it("keeps legacy top-level submit responses working", () => {
|
||||
expect(parseSubmitResponse({ success: true, message: "Thanks" })).toEqual({
|
||||
success: true,
|
||||
message: "Thanks",
|
||||
});
|
||||
});
|
||||
|
||||
it("unwraps validation errors returned in the standard API envelope", () => {
|
||||
expect(
|
||||
parseSubmitResponse({
|
||||
data: {
|
||||
errors: [{ field: "email", message: "Email is required" }],
|
||||
},
|
||||
}),
|
||||
).toEqual({ errors: [{ field: "email", message: "Email is required" }] });
|
||||
});
|
||||
|
||||
it("does not recursively unwrap nested envelopes", () => {
|
||||
expect(parseSubmitResponse({ data: { data: { success: true, message: "Nested" } } })).toEqual({
|
||||
data: { success: true, message: "Nested" },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,261 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { PublicFormDefinition } from "../src/public-definition.js";
|
||||
import {
|
||||
loadPublicFormDefinition,
|
||||
parsePublicFormDefinitionPayload,
|
||||
parsePublicFormDefinitionResponse,
|
||||
} from "../src/public-definition.js";
|
||||
|
||||
const activeForm: PublicFormDefinition = {
|
||||
name: "Contact",
|
||||
slug: "contact",
|
||||
pages: [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
id: "email",
|
||||
type: "email",
|
||||
label: "Email",
|
||||
name: "email",
|
||||
required: true,
|
||||
width: "full",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
settings: {
|
||||
spamProtection: "none",
|
||||
submitLabel: "Send",
|
||||
},
|
||||
status: "active",
|
||||
_turnstileSiteKey: null,
|
||||
};
|
||||
|
||||
function jsonResponse(body: unknown, status = 200): Response {
|
||||
return new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
describe("parsePublicFormDefinitionResponse", () => {
|
||||
it("unwraps public plugin route responses from the standard API envelope", async () => {
|
||||
await expect(
|
||||
parsePublicFormDefinitionResponse(jsonResponse({ data: activeForm })),
|
||||
).resolves.toEqual(activeForm);
|
||||
});
|
||||
|
||||
it("returns null for missing form data", async () => {
|
||||
await expect(
|
||||
parsePublicFormDefinitionResponse(jsonResponse({ data: undefined })),
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for inactive forms and failed responses", async () => {
|
||||
await expect(
|
||||
parsePublicFormDefinitionResponse(
|
||||
jsonResponse({ data: { ...activeForm, status: "paused" } }),
|
||||
),
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
parsePublicFormDefinitionResponse(jsonResponse({ error: { message: "Not found" } }, 404)),
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("does not recursively unwrap nested response envelopes", async () => {
|
||||
await expect(
|
||||
parsePublicFormDefinitionResponse(jsonResponse({ data: { data: activeForm } })),
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
parsePublicFormDefinitionResponse(
|
||||
jsonResponse({ data: { success: true, data: activeForm } }),
|
||||
),
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("parsePublicFormDefinitionPayload", () => {
|
||||
it("unwraps successful handler results with an active form", () => {
|
||||
expect(parsePublicFormDefinitionPayload({ success: true, data: activeForm })).toEqual(
|
||||
activeForm,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns null for missing or inactive handler result data", () => {
|
||||
expect(parsePublicFormDefinitionPayload({ success: true, data: undefined })).toBeNull();
|
||||
expect(
|
||||
parsePublicFormDefinitionPayload({
|
||||
success: true,
|
||||
data: { ...activeForm, status: "paused" },
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for failed handler results", () => {
|
||||
expect(
|
||||
parsePublicFormDefinitionPayload({
|
||||
success: false,
|
||||
error: { code: "NOT_FOUND", message: "Form not found" },
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("does not recursively unwrap nested handler result data", () => {
|
||||
expect(
|
||||
parsePublicFormDefinitionPayload({
|
||||
success: true,
|
||||
data: { data: activeForm },
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
parsePublicFormDefinitionPayload({
|
||||
success: true,
|
||||
data: { success: true, data: activeForm },
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for malformed active form data", () => {
|
||||
expect(
|
||||
parsePublicFormDefinitionPayload({
|
||||
success: true,
|
||||
data: { status: "active" },
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
parsePublicFormDefinitionPayload({
|
||||
success: true,
|
||||
data: { ...activeForm, pages: undefined },
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
parsePublicFormDefinitionPayload({
|
||||
success: true,
|
||||
data: { ...activeForm, settings: undefined },
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
parsePublicFormDefinitionPayload({
|
||||
success: true,
|
||||
data: {
|
||||
...activeForm,
|
||||
pages: [{ fields: [{ ...activeForm.pages[0]!.fields[0]!, type: "unknown" }] }],
|
||||
},
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
parsePublicFormDefinitionPayload({
|
||||
success: true,
|
||||
data: {
|
||||
...activeForm,
|
||||
pages: [{ fields: [{ ...activeForm.pages[0]!.fields[0]!, options: {} }] }],
|
||||
},
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadPublicFormDefinition", () => {
|
||||
it("loads active forms through the internal public plugin route handler", async () => {
|
||||
const handlePublicPluginApiRoute = vi.fn(async () => ({ success: true, data: activeForm }));
|
||||
const fetch = vi.fn(async () => jsonResponse({ data: activeForm }));
|
||||
|
||||
await expect(
|
||||
loadPublicFormDefinition({
|
||||
formId: "contact",
|
||||
baseUrl: new URL("https://example.com/contact"),
|
||||
handlePublicPluginApiRoute,
|
||||
fetch,
|
||||
}),
|
||||
).resolves.toEqual(activeForm);
|
||||
|
||||
expect(handlePublicPluginApiRoute).toHaveBeenCalledWith(
|
||||
"emdash-forms",
|
||||
"POST",
|
||||
"/definition",
|
||||
expect.any(Request),
|
||||
);
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("treats internal missing or inactive results as definitive", async () => {
|
||||
const handlePublicPluginApiRoute = vi.fn(async () => ({
|
||||
success: false,
|
||||
error: { code: "NOT_FOUND", message: "Form not found" },
|
||||
}));
|
||||
const fetch = vi.fn(async () => jsonResponse({ data: activeForm }));
|
||||
|
||||
await expect(
|
||||
loadPublicFormDefinition({
|
||||
formId: "missing",
|
||||
baseUrl: new URL("https://example.com/contact"),
|
||||
handlePublicPluginApiRoute,
|
||||
fetch,
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("treats malformed internal results as definitive", async () => {
|
||||
const handlePublicPluginApiRoute = vi.fn(async () => ({
|
||||
success: true,
|
||||
data: { status: "active" },
|
||||
}));
|
||||
const fetch = vi.fn(async () => jsonResponse({ data: activeForm }));
|
||||
|
||||
await expect(
|
||||
loadPublicFormDefinition({
|
||||
formId: "contact",
|
||||
baseUrl: new URL("https://example.com/contact"),
|
||||
handlePublicPluginApiRoute,
|
||||
fetch,
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to fetching the public definition route when no handler is available", async () => {
|
||||
const fetch = vi.fn(async () => jsonResponse({ data: activeForm }));
|
||||
|
||||
await expect(
|
||||
loadPublicFormDefinition({
|
||||
formId: "contact",
|
||||
baseUrl: new URL("https://example.com/contact"),
|
||||
fetch,
|
||||
}),
|
||||
).resolves.toEqual(activeForm);
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not fall back to fetching when the internal handler throws", async () => {
|
||||
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const handlePublicPluginApiRoute = vi.fn(async () => {
|
||||
throw new Error("dispatcher unavailable");
|
||||
});
|
||||
const fetch = vi.fn(async () => jsonResponse({ data: activeForm }));
|
||||
|
||||
try {
|
||||
await expect(
|
||||
loadPublicFormDefinition({
|
||||
formId: "contact",
|
||||
baseUrl: new URL("https://example.com/contact"),
|
||||
handlePublicPluginApiRoute,
|
||||
fetch,
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
"[emdash-forms] public definition dispatcher failed:",
|
||||
expect.any(Error),
|
||||
);
|
||||
} finally {
|
||||
warn.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user