a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
at main 5.1 kB view raw
1import { mount } from "$core/binder"; 2import { clearPlugins, registerPlugin } from "$core/plugin"; 3import { signal } from "$core/signal"; 4import { beforeEach, describe, expect, it, vi } from "vitest"; 5 6describe("plugin integration with binder", () => { 7 beforeEach(() => { 8 clearPlugins(); 9 }); 10 11 it("calls registered plugin when binding attribute", () => { 12 const pluginHandler = vi.fn(); 13 registerPlugin("custom", pluginHandler); 14 15 const element = document.createElement("div"); 16 element.dataset.voltCustom = "testValue"; 17 18 const scope = { test: "value" }; 19 mount(element, scope); 20 21 expect(pluginHandler).toHaveBeenCalledOnce(); 22 expect(pluginHandler).toHaveBeenCalledWith( 23 expect.objectContaining({ 24 element, 25 scope, 26 addCleanup: expect.any(Function), 27 findSignal: expect.any(Function), 28 evaluate: expect.any(Function), 29 }), 30 "testValue", 31 ); 32 }); 33 34 it("warns when unknown binding is used without plugin", () => { 35 const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); 36 const element = document.createElement("div"); 37 element.dataset.voltUnknown = "value"; 38 39 mount(element, {}); 40 expect(consoleWarnSpy).toHaveBeenCalledWith("Unknown binding: data-volt-unknown"); 41 consoleWarnSpy.mockRestore(); 42 }); 43 44 it("provides working findSignal utility to plugin", () => { 45 let foundSignal: unknown; 46 registerPlugin("finder", (context) => { 47 foundSignal = context.findSignal("count"); 48 }); 49 50 const element = document.createElement("div"); 51 element.dataset.voltFinder = "test"; 52 53 const count = signal(42); 54 mount(element, { count }); 55 56 expect(foundSignal).toBe(count); 57 }); 58 59 it("provides working evaluate utility to plugin", () => { 60 let evaluatedValue: unknown; 61 registerPlugin("evaluator", (context, value) => { 62 evaluatedValue = context.evaluate(value); 63 }); 64 65 const element = document.createElement("div"); 66 element.dataset.voltEvaluator = "count"; 67 68 const count = signal(100); 69 mount(element, { count }); 70 71 expect(evaluatedValue).toBe(100); 72 }); 73 74 it("registers and calls cleanup functions", () => { 75 const cleanup = vi.fn(); 76 registerPlugin("cleaner", (context) => { 77 context.addCleanup(cleanup); 78 }); 79 80 const element = document.createElement("div"); 81 element.dataset.voltCleaner = "test"; 82 83 const unmount = mount(element, {}); 84 85 expect(cleanup).not.toHaveBeenCalled(); 86 87 unmount(); 88 89 expect(cleanup).toHaveBeenCalledOnce(); 90 }); 91 92 it("handles multiple plugins on same element", () => { 93 const plugin1 = vi.fn(); 94 const plugin2 = vi.fn(); 95 96 registerPlugin("plugin1", plugin1); 97 registerPlugin("plugin2", plugin2); 98 99 const element = document.createElement("div"); 100 element.dataset.voltPlugin1 = "value1"; 101 element.dataset.voltPlugin2 = "value2"; 102 103 mount(element, {}); 104 105 expect(plugin1).toHaveBeenCalledWith(expect.anything(), "value1"); 106 expect(plugin2).toHaveBeenCalledWith(expect.anything(), "value2"); 107 }); 108 109 it("allows plugins to work alongside core bindings", () => { 110 const pluginHandler = vi.fn(); 111 registerPlugin("custom", pluginHandler); 112 113 const element = document.createElement("div"); 114 element.dataset.voltText = "message"; 115 element.dataset.voltCustom = "customValue"; 116 117 const scope = { message: "Hello" }; 118 mount(element, scope); 119 120 expect(element.textContent).toBe("Hello"); 121 expect(pluginHandler).toHaveBeenCalledWith(expect.anything(), "customValue"); 122 }); 123 124 it("handles plugin errors gracefully", () => { 125 const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); 126 const badPlugin = vi.fn(() => { 127 throw new Error("Plugin error"); 128 }); 129 130 registerPlugin("bad", badPlugin); 131 132 const element = document.createElement("div"); 133 element.dataset.voltBad = "value"; 134 135 mount(element, {}); 136 expect(consoleErrorSpy).toHaveBeenCalledTimes(3); 137 expect(consoleErrorSpy).toHaveBeenNthCalledWith(1, expect.stringContaining("[plugin]")); 138 expect(consoleErrorSpy).toHaveBeenNthCalledWith(2, "Caused by:", expect.any(Error)); 139 expect(consoleErrorSpy).toHaveBeenNthCalledWith(3, "Element:", element); 140 consoleErrorSpy.mockRestore(); 141 }); 142 143 it("supports reactive updates from plugins", () => { 144 registerPlugin("reactive", (context, value) => { 145 const sig = context.findSignal(value); 146 if (sig) { 147 const update = () => { 148 (context.element as HTMLElement).dataset.testValue = String(sig.get()); 149 }; 150 update(); 151 const unsubscribe = sig.subscribe(update); 152 context.addCleanup(unsubscribe); 153 } 154 }); 155 156 const element = document.createElement("div"); 157 element.dataset.voltReactive = "count"; 158 159 const count = signal(1); 160 mount(element, { count }); 161 162 expect(element.dataset.testValue).toBe("1"); 163 164 count.set(5); 165 expect(element.dataset.testValue).toBe("5"); 166 167 count.set(10); 168 expect(element.dataset.testValue).toBe("10"); 169 }); 170});