@remediator/core
Version:
Remix/React Router 7 Mediator
227 lines (226 loc) • 9.53 kB
JavaScript
import { describe, it, expect, beforeEach, vi } from "vitest";
import { reMediator as ReMediatorClass, reMediatorInstance, } from "../reMediator";
// Test request classes
class TestRequest {
constructor(name = "test", value = 42) {
this.name = name;
this.value = value;
}
}
class TestRequest2 {
constructor(id = "test-id", count = 10) {
this.id = id;
this.count = count;
}
}
class AuthRequest {
constructor(userId = "user123", action = "read") {
this.userId = userId;
this.action = action;
}
}
// Test handlers
class TestHandler {
async handle(request, context) {
return `Hello ${request.name}, value: ${request.value}`;
}
}
class TestHandler2 {
async handle(request, context) {
return request.count * 2;
}
}
class AuthHandler {
async handle(request, context) {
return request.userId === "user123" && request.action === "read";
}
}
// Test middleware
const testMiddleware = async (request, context, next) => {
const result = await next();
return `[${result}]`;
};
const authMiddleware = async (request, context, next) => {
if (request instanceof AuthRequest && request.userId !== "user123") {
throw new Error("Unauthorized");
}
return await next();
};
const loggingMiddleware = async (request, context, next) => {
const result = await next();
return result;
};
describe("reMediator", () => {
let mediator;
let mockRequest;
beforeEach(() => {
// Create a fresh instance for each test to ensure isolation
mediator = new ReMediatorClass();
mockRequest = new Request("http://localhost:3000/test");
});
describe("register", () => {
it("should register a handler for a request type", () => {
const handler = new TestHandler();
expect(() => mediator.register(TestRequest, handler)).not.toThrow();
});
it("should allow registering multiple handlers for different request types", () => {
const handler1 = new TestHandler();
const handler2 = new TestHandler2();
expect(() => {
mediator.register(TestRequest, handler1);
mediator.register(TestRequest2, handler2);
}).not.toThrow();
});
});
describe("use", () => {
it("should add middleware to the pipeline", () => {
expect(() => mediator.use(testMiddleware)).not.toThrow();
});
it("should allow adding multiple middleware", () => {
expect(() => {
mediator.use(testMiddleware);
mediator.use(authMiddleware);
mediator.use(loggingMiddleware);
}).not.toThrow();
});
});
describe("send - instance overload", () => {
beforeEach(() => {
mediator.register(TestRequest, new TestHandler());
});
it("should handle instance with raw request", async () => {
const request = new TestRequest("Alice", 100);
const result = await mediator.send(request, mockRequest);
expect(result).toBe("Hello Alice, value: 100");
});
it("should handle instance with raw request and middleware", async () => {
mediator.use(testMiddleware);
const request = new TestRequest("Bob", 50);
const result = await mediator.send(request, mockRequest);
expect(result).toBe("[Hello Bob, value: 50]");
});
it("should throw error for unregistered request type", async () => {
const request = new TestRequest2("test", 5);
await expect(mediator.send(request, mockRequest)).rejects.toThrow("No handler registered for 'TestRequest2'");
});
});
describe("send - constructor overload", () => {
beforeEach(() => {
mediator.register(TestRequest, new TestHandler());
});
it("should handle constructor with data and raw request", async () => {
const data = { name: "Charlie", value: 75 };
const result = await mediator.send(TestRequest, data, mockRequest);
expect(result).toBe("Hello Charlie, value: 75");
});
it("should handle constructor with data, raw request, and middleware", async () => {
mediator.use(testMiddleware);
const data = { name: "David", value: 25 };
const result = await mediator.send(TestRequest, data, mockRequest);
expect(result).toBe("[Hello David, value: 25]");
});
it("should merge data with constructor defaults", async () => {
const data = { name: "Eve" }; // value should default to 42
const result = await mediator.send(TestRequest, data, mockRequest);
expect(result).toBe("Hello Eve, value: 42");
});
});
describe("middleware functionality", () => {
beforeEach(() => {
mediator.register(AuthRequest, new AuthHandler());
});
it("should execute middleware in correct order", async () => {
mediator.use(loggingMiddleware);
mediator.use(authMiddleware);
mediator.use(testMiddleware);
const request = new AuthRequest("user123", "read");
const result = await mediator.send(request, mockRequest);
expect(result).toBe("[true]");
});
it("should pass middleware names to context", async () => {
mediator.use(loggingMiddleware);
mediator.use(authMiddleware);
const request = new AuthRequest("user123", "read");
const handler = new AuthHandler();
const spy = vi.spyOn(handler, "handle");
mediator.register(AuthRequest, handler);
await mediator.send(request, mockRequest);
expect(spy).toHaveBeenCalledWith(request, expect.objectContaining({
middlewareNames: ["loggingMiddleware", "authMiddleware"],
}));
});
it("should handle middleware that throws errors", async () => {
mediator.use(authMiddleware);
const request = new AuthRequest("unauthorized", "read");
await expect(mediator.send(request, mockRequest)).rejects.toThrow("Unauthorized");
});
it("should handle custom middleware array", async () => {
mediator.use(loggingMiddleware);
mediator.use(authMiddleware);
const request = new AuthRequest("user123", "read");
const result = await mediator.send(request, mockRequest, [
loggingMiddleware,
]);
expect(result).toBe(true);
});
});
describe("error handling", () => {
it("should throw error for invalid arguments", async () => {
await expect(mediator.send(undefined, undefined)).rejects.toThrow("Invalid arguments for reMediator.send()");
});
it("should throw error for invalid first argument", async () => {
await expect(mediator.send(null, mockRequest)).rejects.toThrow("Invalid arguments for reMediator.send()");
});
it("should warn when handler returns undefined", async () => {
const undefinedHandler = {
async handle() {
return undefined;
},
};
mediator.register(TestRequest, undefinedHandler);
const request = new TestRequest();
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
await mediator.send(request, mockRequest);
expect(consoleSpy).toHaveBeenCalledWith("Handler 'Object' returned undefined.");
consoleSpy.mockRestore();
});
});
describe("context handling", () => {
beforeEach(() => {
mediator.register(TestRequest, new TestHandler());
});
it("should pass correct context to handler", async () => {
const handler = new TestHandler();
const spy = vi.spyOn(handler, "handle");
mediator.register(TestRequest, handler);
const request = new TestRequest("test", 42);
await mediator.send(request, mockRequest);
expect(spy).toHaveBeenCalledWith(request, expect.objectContaining({
rawRequest: mockRequest,
handlerName: "TestRequest",
middlewareNames: [],
}));
});
it("should include middleware names in context", async () => {
mediator.use(testMiddleware);
mediator.use(authMiddleware);
const handler = new TestHandler();
const spy = vi.spyOn(handler, "handle");
mediator.register(TestRequest, handler);
const request = new TestRequest("test", 42);
await mediator.send(request, mockRequest);
expect(spy).toHaveBeenCalledWith(request, expect.objectContaining({
middlewareNames: ["testMiddleware", "authMiddleware"],
}));
});
});
describe("default export", () => {
it("should export a singleton instance", () => {
expect(reMediatorInstance).toBeInstanceOf(ReMediatorClass);
});
it("should be the same instance across imports", () => {
// In ES modules, we can't use require, so we'll test the singleton differently
expect(reMediatorInstance).toBeInstanceOf(ReMediatorClass);
});
});
});