Skip to content

Commit f9986d1

Browse files
committed
✨ added map classes like MultiLevelMap and MapWithKeyFn
1 parent a52ccdc commit f9986d1

File tree

2 files changed

+396
-3
lines changed

2 files changed

+396
-3
lines changed

src/lang.spec.ts

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,228 @@ test("minBy and maxBy functions with edge cases", () => {
217217
expect(Lang.minBy(nanArr, (x) => x.value)).toEqual({ value: NaN });
218218
expect(Lang.maxBy(nanArr, (x) => x.value)).toEqual({ value: NaN });
219219
});
220+
221+
test("MultiLevelMap set and get", () => {
222+
const map = new Lang.MultiLevelMap<number>();
223+
map.set(["a", "b", "c"], 42);
224+
expect(map.get(["a", "b", "c"])).toBe(42);
225+
// Attempt to get a value with an incomplete key path should return undefined.
226+
expect(map.get(["a", "b"])).toBeUndefined();
227+
});
228+
229+
test("MultiLevelMap getAll returns all values under a branch", () => {
230+
const map = new Lang.MultiLevelMap<string>();
231+
map.set(["user", "alice"], "AliceData");
232+
map.set(["user", "bob"], "BobData");
233+
map.set(["user", "charlie"], "CharlieData");
234+
map.set(["system", "config"], "SysConfig");
235+
236+
const userValues = map.getAll(["user"]);
237+
expect(userValues.sort()).toEqual(
238+
["AliceData", "BobData", "CharlieData"].sort(),
239+
);
240+
241+
// For a key path that doesn't exist, getAll should return an empty array.
242+
expect(map.getAll(["nonexistent"])).toEqual([]);
243+
});
244+
245+
test("MultiLevelMap has function", () => {
246+
const map = new Lang.MultiLevelMap<boolean>();
247+
expect(map.has(["settings", "theme"])).toBe(false);
248+
map.set(["settings", "theme"], true);
249+
expect(map.has(["settings", "theme"])).toBe(true);
250+
});
251+
252+
test("MultiLevelMap delete function removes entry and cleans up empty nodes", () => {
253+
const map = new Lang.MultiLevelMap<number>();
254+
map.set(["a", "b", "c"], 100);
255+
map.set(["a", "b", "d"], 200);
256+
expect(map.get(["a", "b", "c"])).toBe(100);
257+
expect(map.get(["a", "b", "d"])).toBe(200);
258+
259+
// Delete one entry
260+
expect(map.delete(["a", "b", "c"])).toBe(true);
261+
expect(map.get(["a", "b", "c"])).toBeUndefined();
262+
// The other entry should still exist
263+
expect(map.get(["a", "b", "d"])).toBe(200);
264+
265+
// Attempt deleting a non-existent key path
266+
expect(map.delete(["a", "b", "c"])).toBe(false);
267+
});
268+
269+
test("MultiLevelMap delete cleans up nodes without values", () => {
270+
const map = new Lang.MultiLevelMap<string>();
271+
map.set(["x", "y", "z"], "deepValue");
272+
// Delete the deep value.
273+
expect(map.delete(["x", "y", "z"])).toBe(true);
274+
// After deletion, getAll from the branch should be empty.
275+
expect(map.getAll(["x"])).toEqual([]);
276+
});
277+
test("MultiLevelMap overwrites value if set with same keys", () => {
278+
const map = new Lang.MultiLevelMap<number>();
279+
map.set(["a", "b"], 1);
280+
expect(map.get(["a", "b"])).toBe(1);
281+
// Overwrite the value at the same key path.
282+
map.set(["a", "b"], 2);
283+
expect(map.get(["a", "b"])).toBe(2);
284+
});
285+
286+
test("MultiLevelMap getAll returns only values under branch", () => {
287+
const map = new Lang.MultiLevelMap<string>();
288+
map.set(["section", "item1"], "value1");
289+
map.set(["section", "subsection", "item2"], "value2");
290+
map.set(["section", "subsection", "item3"], "value3");
291+
map.set(["section", "other"], "value4");
292+
map.set(["otherSection", "item"], "value5");
293+
294+
const results = map.getAll(["section"]);
295+
expect(results.sort()).toEqual(
296+
["value1", "value2", "value3", "value4"].sort(),
297+
);
298+
});
299+
300+
test("MultiLevelMap deleting branch cleans intermediate nodes", () => {
301+
const map = new Lang.MultiLevelMap<number>();
302+
map.set(["a", "b", "c", "d"], 10);
303+
map.set(["a", "b", "c", "e"], 20);
304+
expect(map.get(["a", "b", "c", "d"])).toBe(10);
305+
expect(map.get(["a", "b", "c", "e"])).toBe(20);
306+
307+
// Delete one leaf value and ensure the other remains.
308+
expect(map.delete(["a", "b", "c", "d"])).toBe(true);
309+
let items = map.getAll(["a", "b", "c"]);
310+
expect(items).toContain(20);
311+
expect(items).not.toContain(10);
312+
313+
// Delete the other leaf value; now the branch should be cleaned up.
314+
expect(map.delete(["a", "b", "c", "e"])).toBe(true);
315+
items = map.getAll(["a", "b", "c"]);
316+
expect(items).toEqual([]);
317+
});
318+
319+
test("MultiLevelMap complex nested structure with interleaved keys", () => {
320+
const map = new Lang.MultiLevelMap<string>();
321+
map.set(["level1", "a", "x"], "ax");
322+
map.set(["level1", "a", "y"], "ay");
323+
map.set(["level1", "b"], "b-main");
324+
map.set(["level1", "a", "z", "nested"], "az-nested");
325+
326+
// Direct retrievals.
327+
expect(map.get(["level1", "b"])).toBe("b-main");
328+
expect(map.get(["level1", "a", "x"])).toBe("ax");
329+
expect(map.get(["level1", "a", "z", "nested"])).toBe("az-nested");
330+
331+
// getAll at the top level.
332+
const all = map.getAll(["level1"]);
333+
expect(all.sort()).toEqual(["ax", "ay", "b-main", "az-nested"].sort());
334+
335+
// getAll for a specific branch.
336+
const aBranch = map.getAll(["level1", "a"]);
337+
expect(aBranch.sort()).toEqual(["ax", "ay", "az-nested"].sort());
338+
});
339+
340+
test("MultiLevelMap non-existent keys behavior", () => {
341+
const map = new Lang.MultiLevelMap<number>();
342+
// Retrieval on a key path that doesn't exist returns undefined.
343+
expect(map.get(["non", "existent"])).toBeUndefined();
344+
// getAll should return an empty array when no branch exists.
345+
expect(map.getAll(["non"])).toEqual([]);
346+
// Deleting a non-existent key path returns false.
347+
expect(map.delete(["non", "existent"])).toBe(false);
348+
});
349+
350+
test("MapWithKeyFn basic set and get", () => {
351+
type Item = { id: number; name: string };
352+
const keyFn = (item: Item) => item.id.toString();
353+
const map = new Lang.MapWithKeyFn<Item, string>([], keyFn);
354+
const item1: Item = { id: 1, name: "Alice" };
355+
const item2: Item = { id: 2, name: "Bob" };
356+
357+
// Initially, get should return undefined and has should return false.
358+
expect(map.get(item1)).toBeUndefined();
359+
expect(map.has(item2)).toBe(false);
360+
361+
// Set values.
362+
map.set(item1, "value1");
363+
map.set(item2, "value2");
364+
365+
expect(map.get(item1)).toBe("value1");
366+
expect(map.get(item2)).toBe("value2");
367+
expect(map.has(item1)).toBe(true);
368+
expect(map.has(item2)).toBe(true);
369+
});
370+
371+
test("MapWithKeyFn update value for existing key", () => {
372+
type Item = { id: number; name: string };
373+
const keyFn = (item: Item) => item.id.toString();
374+
const map = new Lang.MapWithKeyFn<Item, string>([], keyFn);
375+
const original: Item = { id: 1, name: "Alice" };
376+
377+
map.set(original, "initial");
378+
expect(map.get(original)).toBe("initial");
379+
380+
// Update using a different instance with the same key.
381+
const updated: Item = { id: 1, name: "Alice Updated" };
382+
map.set(updated, "updated");
383+
expect(map.get(original)).toBe("updated");
384+
expect(map.get(updated)).toBe("updated");
385+
});
386+
387+
test("MapWithKeyFn iteration and clear", () => {
388+
type Item = { id: number; name: string };
389+
const keyFn = (item: Item) => item.id.toString();
390+
const items: Item[] = [
391+
{ id: 1, name: "Alice" },
392+
{ id: 2, name: "Bob" },
393+
{ id: 3, name: "Charlie" },
394+
];
395+
const map = new Lang.MapWithKeyFn<Item, string>([], keyFn);
396+
items.forEach((item, idx) => map.set(item, `value${idx + 1}`));
397+
398+
// Test keys, values, and entries iteration.
399+
const keys = [...map.keys()];
400+
const values = [...map.values()];
401+
const entries = [...map.entries()];
402+
403+
expect(keys).toHaveLength(3);
404+
expect(values).toHaveLength(3);
405+
expect(entries).toHaveLength(3);
406+
407+
// Iterating over the map itself should yield the same entries.
408+
expect([...map]).toEqual(entries);
409+
410+
// Test deletion of a key.
411+
expect(map.delete(items[0])).toBe(true);
412+
expect(map.get(items[0])).toBeUndefined();
413+
414+
// Clear the map.
415+
map.clear();
416+
expect(map.size).toBe(0);
417+
});
418+
419+
test("MapWithKeyFn duplicate keys handling", () => {
420+
type User = { id: number; username: string };
421+
const keyFn = (user: User) => user.id.toString();
422+
423+
// Two separate objects with the same id should be treated as the same key.
424+
const user1: User = { id: 1, username: "alice" };
425+
const userDuplicate: User = { id: 1, username: "aliceClone" };
426+
const user2: User = { id: 2, username: "bob" };
427+
428+
const map = new Lang.MapWithKeyFn<User, string>([], keyFn);
429+
map.set(user1, "first");
430+
// Setting a duplicate should overwrite the previous value.
431+
map.set(userDuplicate, "updated");
432+
433+
expect(map.get(user1)).toBe("updated");
434+
expect(map.get(userDuplicate)).toBe("updated");
435+
expect(map.has(user1)).toBe(true);
436+
437+
// Insert another distinct element.
438+
map.set(user2, "second");
439+
expect(map.get(user2)).toBe("second");
440+
441+
// Confirm iteration reflects unique keys.
442+
const entries = [...map.entries()];
443+
expect(entries.length).toBe(2);
444+
});

0 commit comments

Comments
 (0)