import type { Meta, StoryObj } from "@storybook/vue3-vite";
import { expect, within } from "storybook/test";
import { createRouter, createMemoryHistory } from "vue-router";
import ErrorCard from "./ErrorCard.vue";
// A throwaway router so the correlation chip's RouterLink resolves in the
// iframe instead of rendering as an unresolved element.
const router = createRouter({
history: createMemoryHistory(),
routes: [{ path: "/trace", component: { template: "
" } }],
});
const meta: Meta = {
title: "Errors/ErrorCard",
component: ErrorCard,
tags: ["autodocs"],
parameters: { vueRouter: { router } },
};
export default meta;
type Story = StoryObj;
const env = (corr: string) => ({ messageId: "m-1", causationId: "m-1", correlationId: corr });
export const DeadLettered: Story = {
args: {
record: {
kind: "dlq.recorded",
action: "orders.ship",
attempts: 5,
ts: "2026-01-01T12:30:00.000Z",
error: { name: "TypeError", message: "Cannot read properties of undefined (reading 'sku')" },
envelope: env("c-9f8e21"),
},
},
play: async ({ canvasElement }) => {
const c = within(canvasElement);
await expect(c.getByTestId("error-title")).toHaveTextContent("Dead-lettered");
await expect(c.getByTestId("error-card")).toHaveAttribute("data-severity", "critical");
await expect(c.getByTestId("error-summary")).toHaveTextContent("orders.ship");
},
};
export const Declined: Story = {
args: {
record: {
kind: "action.failed",
action: "billing.charge",
attempt: 1,
maxAttempts: 1,
willRetry: false,
ts: "2026-01-01T12:31:00.000Z",
error: { name: "PaymentDeclined", message: "card declined", code: 402 },
envelope: env("c-aa11bb"),
},
},
play: async ({ canvasElement }) => {
const c = within(canvasElement);
await expect(c.getByTestId("error-card")).toHaveAttribute("data-category", "declined");
await expect(c.getByTestId("error-severity")).toHaveTextContent("Warning");
},
};
export const Retrying: Story = {
args: {
record: {
kind: "action.failed",
action: "inventory.reserve",
attempt: 2,
maxAttempts: 3,
willRetry: true,
ts: "2026-01-01T12:32:00.000Z",
error: { name: "TimeoutError", message: "upstream timed out" },
envelope: env("c-cc22dd"),
},
},
play: async ({ canvasElement }) => {
await expect(within(canvasElement).getByTestId("error-retry")).toHaveTextContent(
"attempt 2 of 3",
);
},
};
export const ExternalCall: Story = {
args: {
record: {
kind: "external.call.failed",
call: "stripe.charge",
target: "https://api.stripe.com/v1/charges",
attempt: 1,
willRetry: false,
ts: "2026-01-01T12:33:00.000Z",
error: { name: "FetchError", message: "ECONNREFUSED" },
},
},
};
export const Gallery: Story = {
render: () => ({
components: { ErrorCard },
setup: () => ({
records: [
{
kind: "dlq.recorded",
action: "orders.ship",
attempts: 5,
error: { name: "TypeError", message: "boom" },
},
{
kind: "action.failed",
action: "billing.charge",
willRetry: false,
error: { name: "PaymentDeclined", message: "declined", code: 402 },
},
{
kind: "action.failed",
action: "inventory.reserve",
attempt: 2,
maxAttempts: 3,
willRetry: true,
error: { name: "TimeoutError", message: "timed out" },
},
{
kind: "projection.failed",
projection: "orders.summary",
event: "OrderPlaced",
error: { name: "RangeError", message: "bad index" },
},
],
}),
template: `
`,
}),
};