a reactive (signals based) hypermedia web framework (wip)
stormlightlabs.github.io/volt/
hypermedia
frontend
signals
1import {
2 createScopeMetadata,
3 getPin,
4 getPins,
5 getScopeMetadata,
6 incrementUidCounter,
7 registerPin,
8} from "$core/scope-metadata";
9import type { Scope } from "$types/volt";
10import { beforeEach, describe, expect, it } from "vitest";
11
12describe("Scope Metadata", () => {
13 let testElement: HTMLDivElement;
14 let testScope: Scope;
15
16 beforeEach(() => {
17 testElement = document.createElement("div");
18 testScope = {};
19 });
20
21 describe("createScopeMetadata", () => {
22 it("creates metadata for a scope", () => {
23 const metadata = createScopeMetadata(testScope, testElement);
24
25 expect(metadata).toBeDefined();
26 expect(metadata.origin).toBe(testElement);
27 expect(metadata.pins).toBeInstanceOf(Map);
28 expect(metadata.uidCounter).toBe(0);
29 });
30
31 it("stores metadata in WeakMap", () => {
32 createScopeMetadata(testScope, testElement);
33
34 const retrieved = getScopeMetadata(testScope);
35 expect(retrieved).toBeDefined();
36 expect(retrieved?.origin).toBe(testElement);
37 });
38
39 it("supports optional parent scope", () => {
40 const parentScope: Scope = {};
41 const metadata = createScopeMetadata(testScope, testElement, parentScope);
42
43 expect(metadata.parent).toBe(parentScope);
44 });
45
46 it("initializes empty pin registry", () => {
47 createScopeMetadata(testScope, testElement);
48
49 const metadata = getScopeMetadata(testScope);
50 expect(metadata?.pins.size).toBe(0);
51 });
52 });
53
54 describe("getScopeMetadata", () => {
55 it("returns undefined for scope without metadata", () => {
56 const emptyScope: Scope = {};
57 expect(getScopeMetadata(emptyScope)).toBeUndefined();
58 });
59
60 it("returns metadata for scope with metadata", () => {
61 createScopeMetadata(testScope, testElement);
62
63 const metadata = getScopeMetadata(testScope);
64 expect(metadata).toBeDefined();
65 });
66 });
67
68 describe("registerPin", () => {
69 beforeEach(() => {
70 createScopeMetadata(testScope, testElement);
71 });
72
73 it("registers an element with a name", () => {
74 const input = document.createElement("input");
75 registerPin(testScope, "username", input);
76
77 const metadata = getScopeMetadata(testScope);
78 expect(metadata?.pins.get("username")).toBe(input);
79 });
80
81 it("allows multiple pins with different names", () => {
82 const input1 = document.createElement("input");
83 const input2 = document.createElement("input");
84
85 registerPin(testScope, "username", input1);
86 registerPin(testScope, "email", input2);
87
88 const metadata = getScopeMetadata(testScope);
89 expect(metadata?.pins.size).toBe(2);
90 expect(metadata?.pins.get("username")).toBe(input1);
91 expect(metadata?.pins.get("email")).toBe(input2);
92 });
93
94 it("overwrites existing pin with same name", () => {
95 const input1 = document.createElement("input");
96 const input2 = document.createElement("input");
97
98 registerPin(testScope, "username", input1);
99 registerPin(testScope, "username", input2);
100
101 const metadata = getScopeMetadata(testScope);
102 expect(metadata?.pins.get("username")).toBe(input2);
103 });
104
105 it("does nothing if scope has no metadata", () => {
106 const emptyScope: Scope = {};
107 const input = document.createElement("input");
108
109 // Should not throw
110 registerPin(emptyScope, "username", input);
111
112 expect(getScopeMetadata(emptyScope)).toBeUndefined();
113 });
114 });
115
116 describe("getPin", () => {
117 beforeEach(() => {
118 createScopeMetadata(testScope, testElement);
119 });
120
121 it("returns registered pin by name", () => {
122 const input = document.createElement("input");
123 registerPin(testScope, "username", input);
124
125 expect(getPin(testScope, "username")).toBe(input);
126 });
127
128 it("returns undefined for unregistered pin", () => {
129 expect(getPin(testScope, "unknown")).toBeUndefined();
130 });
131
132 it("returns undefined if scope has no metadata", () => {
133 const emptyScope: Scope = {};
134 expect(getPin(emptyScope, "username")).toBeUndefined();
135 });
136 });
137
138 describe("getPins", () => {
139 beforeEach(() => {
140 createScopeMetadata(testScope, testElement);
141 });
142
143 it("returns empty object for scope with no pins", () => {
144 const pins = getPins(testScope);
145
146 expect(pins).toEqual({});
147 });
148
149 it("returns all pins as record object", () => {
150 const input1 = document.createElement("input");
151 const input2 = document.createElement("input");
152
153 registerPin(testScope, "username", input1);
154 registerPin(testScope, "email", input2);
155
156 const pins = getPins(testScope);
157
158 expect(pins.username).toBe(input1);
159 expect(pins.email).toBe(input2);
160 expect(Object.keys(pins)).toHaveLength(2);
161 });
162
163 it("returns empty object if scope has no metadata", () => {
164 const emptyScope: Scope = {};
165 const pins = getPins(emptyScope);
166
167 expect(pins).toEqual({});
168 });
169 });
170
171 describe("incrementUidCounter", () => {
172 beforeEach(() => {
173 createScopeMetadata(testScope, testElement);
174 });
175
176 it("increments counter and returns new value", () => {
177 const id1 = incrementUidCounter(testScope);
178 const id2 = incrementUidCounter(testScope);
179 const id3 = incrementUidCounter(testScope);
180
181 expect(id1).toBe(1);
182 expect(id2).toBe(2);
183 expect(id3).toBe(3);
184 });
185
186 it("returns 0 for scope without metadata", () => {
187 const emptyScope: Scope = {};
188 expect(incrementUidCounter(emptyScope)).toBe(0);
189 });
190
191 it("maintains separate counters per scope", () => {
192 const scope1: Scope = {};
193 const scope2: Scope = {};
194 const elem1 = document.createElement("div");
195 const elem2 = document.createElement("div");
196
197 createScopeMetadata(scope1, elem1);
198 createScopeMetadata(scope2, elem2);
199
200 expect(incrementUidCounter(scope1)).toBe(1);
201 expect(incrementUidCounter(scope2)).toBe(1);
202 expect(incrementUidCounter(scope1)).toBe(2);
203 expect(incrementUidCounter(scope2)).toBe(2);
204 });
205 });
206
207 describe("Memory Management", () => {
208 it("allows garbage collection of scope", () => {
209 // This test verifies WeakMap behavior
210 let scope: Scope | null = {};
211 const element = document.createElement("div");
212
213 createScopeMetadata(scope, element);
214 expect(getScopeMetadata(scope)).toBeDefined();
215
216 // Clear reference - metadata should be GC-able
217 scope = null;
218
219 // Cannot directly test GC, but at least verify no errors
220 expect(true).toBe(true);
221 });
222 });
223});