| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- import { describe, it, expect, vi } from "vitest";
- import {
- resolveAction,
- executeAction,
- interpolateString,
- actionBinding,
- } from "./actions";
- describe("interpolateString", () => {
- it("interpolates ${path} expressions", () => {
- const data = { user: { name: "Alice" }, count: 5 };
- expect(interpolateString("Hello ${/user/name}!", data)).toBe(
- "Hello Alice!",
- );
- expect(interpolateString("${/user/name} has ${/count} items", data)).toBe(
- "Alice has 5 items",
- );
- });
- it("returns string unchanged when no variables", () => {
- expect(interpolateString("No vars here", {})).toBe("No vars here");
- });
- it("replaces missing values with empty string", () => {
- expect(interpolateString("Hello ${/missing}!", {})).toBe("Hello !");
- });
- it("handles multiple occurrences of same variable", () => {
- const data = { name: "Bob" };
- expect(interpolateString("${/name} says ${/name}", data)).toBe(
- "Bob says Bob",
- );
- });
- });
- describe("resolveAction", () => {
- it("resolves literal params", () => {
- const resolved = resolveAction(
- {
- action: "navigate",
- params: { url: "/home", count: 5 },
- },
- {},
- );
- expect(resolved.action).toBe("navigate");
- expect(resolved.params.url).toBe("/home");
- expect(resolved.params.count).toBe(5);
- });
- it("resolves dynamic $state params", () => {
- const data = { userId: 123, settings: { theme: "dark" } };
- const resolved = resolveAction(
- {
- action: "updateUser",
- params: {
- id: { $state: "/userId" },
- theme: { $state: "/settings/theme" },
- },
- },
- data,
- );
- expect(resolved.params.id).toBe(123);
- expect(resolved.params.theme).toBe("dark");
- });
- it("interpolates confirmation messages", () => {
- const data = { user: { name: "Alice" } };
- const resolved = resolveAction(
- {
- action: "delete",
- confirm: {
- title: "Delete ${/user/name}",
- message: "Are you sure you want to delete ${/user/name}?",
- },
- },
- data,
- );
- expect(resolved.confirm?.title).toBe("Delete Alice");
- expect(resolved.confirm?.message).toBe(
- "Are you sure you want to delete Alice?",
- );
- });
- it("preserves onSuccess and onError handlers", () => {
- const resolved = resolveAction(
- {
- action: "save",
- onSuccess: { navigate: "/success" },
- onError: { set: { error: "$error.message" } },
- },
- {},
- );
- expect(resolved.onSuccess).toEqual({ navigate: "/success" });
- expect(resolved.onError).toEqual({ set: { error: "$error.message" } });
- });
- });
- describe("executeAction", () => {
- it("calls the handler with resolved params", async () => {
- const handler = vi.fn().mockResolvedValue(undefined);
- await executeAction({
- action: { action: "test", params: { value: 42 } },
- handler,
- setState: vi.fn(),
- });
- expect(handler).toHaveBeenCalledWith({ value: 42 });
- });
- it("handles onSuccess with navigate", async () => {
- const navigate = vi.fn();
- await executeAction({
- action: {
- action: "test",
- params: {},
- onSuccess: { navigate: "/success" },
- },
- handler: vi.fn().mockResolvedValue(undefined),
- setState: vi.fn(),
- navigate,
- });
- expect(navigate).toHaveBeenCalledWith("/success");
- });
- it("handles onSuccess with set", async () => {
- const setState = vi.fn();
- await executeAction({
- action: {
- action: "test",
- params: {},
- onSuccess: { set: { saved: true, message: "Done" } },
- },
- handler: vi.fn().mockResolvedValue(undefined),
- setState,
- });
- expect(setState).toHaveBeenCalledWith("saved", true);
- expect(setState).toHaveBeenCalledWith("message", "Done");
- });
- it("handles onSuccess with action", async () => {
- const executeActionFn = vi.fn();
- await executeAction({
- action: {
- action: "test",
- params: {},
- onSuccess: { action: "followUp" },
- },
- handler: vi.fn().mockResolvedValue(undefined),
- setState: vi.fn(),
- executeAction: executeActionFn,
- });
- expect(executeActionFn).toHaveBeenCalledWith("followUp");
- });
- it("handles onError with set", async () => {
- const setState = vi.fn();
- const error = new Error("Something went wrong");
- await executeAction({
- action: {
- action: "test",
- params: {},
- onError: { set: { error: "$error.message" } },
- },
- handler: vi.fn().mockRejectedValue(error),
- setState,
- });
- expect(setState).toHaveBeenCalledWith("error", "Something went wrong");
- });
- it("handles onError with action", async () => {
- const executeActionFn = vi.fn();
- const error = new Error("Failed");
- await executeAction({
- action: {
- action: "test",
- params: {},
- onError: { action: "handleError" },
- },
- handler: vi.fn().mockRejectedValue(error),
- setState: vi.fn(),
- executeAction: executeActionFn,
- });
- expect(executeActionFn).toHaveBeenCalledWith("handleError");
- });
- it("re-throws error when no onError handler", async () => {
- const error = new Error("Unhandled");
- await expect(
- executeAction({
- action: { action: "test", params: {} },
- handler: vi.fn().mockRejectedValue(error),
- setState: vi.fn(),
- }),
- ).rejects.toThrow("Unhandled");
- });
- });
- describe("actionBinding helper", () => {
- describe("simple", () => {
- it("creates binding with action only", () => {
- const a = actionBinding.simple("navigate");
- expect(a.action).toBe("navigate");
- expect(a.params).toBeUndefined();
- });
- it("creates binding with params", () => {
- const a = actionBinding.simple("navigate", { url: "/home" });
- expect(a.action).toBe("navigate");
- expect(a.params).toEqual({ url: "/home" });
- });
- });
- describe("withConfirm", () => {
- it("creates binding with confirmation", () => {
- const a = actionBinding.withConfirm("delete", {
- title: "Confirm",
- message: "Are you sure?",
- });
- expect(a.action).toBe("delete");
- expect(a.confirm).toEqual({
- title: "Confirm",
- message: "Are you sure?",
- });
- });
- it("creates binding with confirmation and params", () => {
- const a = actionBinding.withConfirm(
- "delete",
- { title: "Delete", message: "Delete item?" },
- { id: 123 },
- );
- expect(a.action).toBe("delete");
- expect(a.params).toEqual({ id: 123 });
- expect(a.confirm?.title).toBe("Delete");
- });
- });
- describe("withSuccess", () => {
- it("creates binding with navigate success handler", () => {
- const a = actionBinding.withSuccess("save", { navigate: "/success" });
- expect(a.action).toBe("save");
- expect(a.onSuccess).toEqual({ navigate: "/success" });
- });
- it("creates binding with set success handler", () => {
- const a = actionBinding.withSuccess("save", { set: { saved: true } });
- expect(a.onSuccess).toEqual({ set: { saved: true } });
- });
- it("creates binding with success handler and params", () => {
- const a = actionBinding.withSuccess(
- "save",
- { navigate: "/done" },
- { data: "test" },
- );
- expect(a.params).toEqual({ data: "test" });
- expect(a.onSuccess).toEqual({ navigate: "/done" });
- });
- });
- });
|