1import { assertEquals } from "@std/assert";
2import { test } from "vitest";
3import { getPath, setPath } from "../src/object-path.ts";
4
5// Test interfaces
6interface TestObject {
7 name: string;
8 age: number;
9 address: {
10 street: string;
11 city: string;
12 zip: number;
13 };
14 hobbies: string[];
15 nested: {
16 deep: {
17 value: boolean;
18 };
19 };
20 items: Array<{
21 id: number;
22 label: string;
23 }>;
24}
25
26test("get - should retrieve top-level property", () => {
27 const obj: TestObject = {
28 name: "John",
29 age: 30,
30 address: { street: "Main St", city: "NYC", zip: 10001 },
31 hobbies: ["reading", "gaming"],
32 nested: { deep: { value: true } },
33 items: [{ id: 1, label: "first" }],
34 };
35
36 const result = getPath(obj, ["name"]);
37 assertEquals(result.exists, true);
38 if (result.exists) {
39 assertEquals(result.value, "John");
40 }
41});
42
43test("get - should retrieve nested property", () => {
44 const obj: TestObject = {
45 name: "John",
46 age: 30,
47 address: { street: "Main St", city: "NYC", zip: 10001 },
48 hobbies: ["reading", "gaming"],
49 nested: { deep: { value: true } },
50 items: [{ id: 1, label: "first" }],
51 };
52
53 const result = getPath(obj, ["address", "city"]);
54 assertEquals(result.exists, true);
55 if (result.exists) {
56 assertEquals(result.value, "NYC");
57 }
58});
59
60test("get - should retrieve deeply nested property", () => {
61 const obj: TestObject = {
62 name: "John",
63 age: 30,
64 address: { street: "Main St", city: "NYC", zip: 10001 },
65 hobbies: ["reading", "gaming"],
66 nested: { deep: { value: true } },
67 items: [{ id: 1, label: "first" }],
68 };
69
70 const result = getPath(obj, ["nested", "deep", "value"]);
71 assertEquals(result.exists, true);
72 if (result.exists) {
73 assertEquals(result.value, true);
74 }
75});
76
77test("get - should retrieve array element", () => {
78 const obj: TestObject = {
79 name: "John",
80 age: 30,
81 address: { street: "Main St", city: "NYC", zip: 10001 },
82 hobbies: ["reading", "gaming"],
83 nested: { deep: { value: true } },
84 items: [{ id: 1, label: "first" }],
85 };
86
87 const result = getPath(obj, ["hobbies", 0]);
88 assertEquals(result.exists, true);
89 if (result.exists) {
90 assertEquals(result.value, "reading");
91 }
92});
93
94test("get - should retrieve property from array element", () => {
95 const obj: TestObject = {
96 name: "John",
97 age: 30,
98 address: { street: "Main St", city: "NYC", zip: 10001 },
99 hobbies: ["reading", "gaming"],
100 nested: { deep: { value: true } },
101 items: [{ id: 1, label: "first" }, { id: 2, label: "second" }],
102 };
103
104 const result = getPath(obj, ["items", 1, "label"]);
105 assertEquals(result.exists, true);
106 if (result.exists) {
107 assertEquals(result.value, "second");
108 }
109});
110
111test("get - should return exists: false for non-existent property", () => {
112 const obj: TestObject = {
113 name: "John",
114 age: 30,
115 address: { street: "Main St", city: "NYC", zip: 10001 },
116 hobbies: ["reading", "gaming"],
117 nested: { deep: { value: true } },
118 items: [{ id: 1, label: "first" }],
119 };
120
121 const result = getPath(obj, ["address", "country"] as any);
122 assertEquals(result.exists, false);
123 assertEquals(result.value, undefined);
124});
125
126test("get - should return exists: false for non-existent array index", () => {
127 const obj: TestObject = {
128 name: "John",
129 age: 30,
130 address: { street: "Main St", city: "NYC", zip: 10001 },
131 hobbies: ["reading", "gaming"],
132 nested: { deep: { value: true } },
133 items: [{ id: 1, label: "first" }],
134 };
135
136 const result = getPath(obj, ["hobbies", 10]);
137 assertEquals(result.exists, false);
138 assertEquals(result.value, undefined);
139});
140
141test("get - should return exists: false when traversing through null", () => {
142 const obj: any = {
143 name: "John",
144 data: null,
145 };
146
147 const result = getPath(obj, ["data", "nested"]);
148 assertEquals(result.exists, false);
149 assertEquals(result.value, undefined);
150});
151
152test("get - should return exists: false when traversing through primitive", () => {
153 const obj: any = {
154 name: "John",
155 count: 42,
156 };
157
158 const result = getPath(obj, ["count", "invalid"]);
159 assertEquals(result.exists, false);
160 assertEquals(result.value, undefined);
161});
162
163test("set - should set top-level property", () => {
164 const obj: TestObject = {
165 name: "John",
166 age: 30,
167 address: { street: "Main St", city: "NYC", zip: 10001 },
168 hobbies: ["reading", "gaming"],
169 nested: { deep: { value: true } },
170 items: [{ id: 1, label: "first" }],
171 };
172
173 const result = setPath(obj, ["name"], "Jane");
174 assertEquals(result.name, "Jane");
175 assertEquals(obj.name, "John"); // Original should be unchanged
176});
177
178test("set - should set nested property", () => {
179 const obj: TestObject = {
180 name: "John",
181 age: 30,
182 address: { street: "Main St", city: "NYC", zip: 10001 },
183 hobbies: ["reading", "gaming"],
184 nested: { deep: { value: true } },
185 items: [{ id: 1, label: "first" }],
186 };
187
188 const result = setPath(obj, ["address", "city"], "LA");
189 assertEquals(result.address.city, "LA");
190 assertEquals(obj.address.city, "NYC"); // Original should be unchanged
191 assertEquals(result.address.street, "Main St"); // Other properties preserved
192});
193
194test("set - should set deeply nested property", () => {
195 const obj: TestObject = {
196 name: "John",
197 age: 30,
198 address: { street: "Main St", city: "NYC", zip: 10001 },
199 hobbies: ["reading", "gaming"],
200 nested: { deep: { value: true } },
201 items: [{ id: 1, label: "first" }],
202 };
203
204 const result = setPath(obj, ["nested", "deep", "value"], false);
205 assertEquals(result.nested.deep.value, false);
206 assertEquals(obj.nested.deep.value, true); // Original should be unchanged
207});
208
209test("set - should set array element", () => {
210 const obj: TestObject = {
211 name: "John",
212 age: 30,
213 address: { street: "Main St", city: "NYC", zip: 10001 },
214 hobbies: ["reading", "gaming"],
215 nested: { deep: { value: true } },
216 items: [{ id: 1, label: "first" }],
217 };
218
219 const result = setPath(obj, ["hobbies", 0], "writing");
220 assertEquals(result.hobbies[0], "writing");
221 assertEquals(obj.hobbies[0], "reading"); // Original should be unchanged
222 assertEquals(result.hobbies[1], "gaming"); // Other elements preserved
223});
224
225test("set - should set property in array element", () => {
226 const obj: TestObject = {
227 name: "John",
228 age: 30,
229 address: { street: "Main St", city: "NYC", zip: 10001 },
230 hobbies: ["reading", "gaming"],
231 nested: { deep: { value: true } },
232 items: [{ id: 1, label: "first" }, { id: 2, label: "second" }],
233 };
234
235 const result = setPath(obj, ["items", 0, "label"], "updated");
236 assertEquals(result.items[0].label, "updated");
237 assertEquals(obj.items[0].label, "first"); // Original should be unchanged
238 assertEquals(result.items[0].id, 1); // Other properties preserved
239});
240
241test("set - should not mutate original object", () => {
242 const obj: TestObject = {
243 name: "John",
244 age: 30,
245 address: { street: "Main St", city: "NYC", zip: 10001 },
246 hobbies: ["reading", "gaming"],
247 nested: { deep: { value: true } },
248 items: [{ id: 1, label: "first" }],
249 };
250
251 const originalJson = JSON.stringify(obj);
252 setPath(obj, ["age"], 31);
253 assertEquals(JSON.stringify(obj), originalJson);
254});
255
256test("set - should preserve unrelated properties", () => {
257 const obj: TestObject = {
258 name: "John",
259 age: 30,
260 address: { street: "Main St", city: "NYC", zip: 10001 },
261 hobbies: ["reading", "gaming"],
262 nested: { deep: { value: true } },
263 items: [{ id: 1, label: "first" }],
264 };
265
266 const result = setPath(obj, ["age"], 31);
267 assertEquals(result.name, "John");
268 assertEquals(result.address, obj.address);
269 assertEquals(result.hobbies, obj.hobbies);
270 assertEquals(result.nested, obj.nested);
271 assertEquals(result.items, obj.items);
272});
273
274test("set - should share references for unchanged branches (structural sharing)", () => {
275 const obj: TestObject = {
276 name: "John",
277 age: 30,
278 address: { street: "Main St", city: "NYC", zip: 10001 },
279 hobbies: ["reading", "gaming"],
280 nested: { deep: { value: true } },
281 items: [{ id: 1, label: "first" }],
282 };
283
284 const result = setPath(obj, ["address", "city"], "LA");
285
286 // Changed path should have new references
287 assertEquals(result === obj, false); // Root is new
288 assertEquals(result.address === obj.address, false); // Address is new
289
290 // Unchanged branches should share references
291 assertEquals(result.hobbies === obj.hobbies, true); // Same reference
292 assertEquals(result.nested === obj.nested, true); // Same reference
293 assertEquals(result.items === obj.items, true); // Same reference
294});
295
296test("set - should handle empty path", () => {
297 const obj: TestObject = {
298 name: "John",
299 age: 30,
300 address: { street: "Main St", city: "NYC", zip: 10001 },
301 hobbies: ["reading", "gaming"],
302 nested: { deep: { value: true } },
303 items: [{ id: 1, label: "first" }],
304 };
305
306 const result = setPath(obj, [] as any, obj);
307 assertEquals(result, obj);
308});
309
310test("get and set - should work together", () => {
311 const obj: TestObject = {
312 name: "John",
313 age: 30,
314 address: { street: "Main St", city: "NYC", zip: 10001 },
315 hobbies: ["reading", "gaming"],
316 nested: { deep: { value: true } },
317 items: [{ id: 1, label: "first" }],
318 };
319
320 const getResult1 = getPath(obj, ["address", "zip"]);
321 assertEquals(getResult1.exists, true);
322 if (getResult1.exists) {
323 assertEquals(getResult1.value, 10001);
324 }
325
326 const updated = setPath(obj, ["address", "zip"], 90210);
327
328 const getResult2 = getPath(updated, ["address", "zip"]);
329 assertEquals(getResult2.exists, true);
330 if (getResult2.exists) {
331 assertEquals(getResult2.value, 90210);
332 }
333
334 const getResult3 = getPath(obj, ["address", "zip"]);
335 assertEquals(getResult3.exists, true);
336 if (getResult3.exists) {
337 assertEquals(getResult3.value, 10001);
338 }
339});