Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/busy-paws-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@webiny/stdlib": patch
---

refactor: add array index support to immutableDelete and mutableDelete
2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.14.1.cjs
approvedGitRepositories: []
enableScripts: false
npmMinimalAgeGate: 24h
npmMinimalAgeGate: 48h
167 changes: 0 additions & 167 deletions __tests__/dotProp.test.ts

This file was deleted.

71 changes: 71 additions & 0 deletions __tests__/dotProp/immutableDelete.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, expect, it } from "vitest";
import { immutableDelete } from "../../src/common/utils/dotProp.js";

describe("immutableDelete", () => {
it("returns a new object reference", () => {
const obj = { a: 1 };
const result = immutableDelete(obj, "a");
expect(result).not.toBe(obj);
});

it("removes a top-level property", () => {
expect(immutableDelete({ a: 1, b: 2 }, "a")).toEqual({ b: 2 });
});

it("removes a nested property by dot path", () => {
expect(immutableDelete({ a: { b: 1, c: 2 } }, "a.b")).toEqual({ a: { c: 2 } });
});

it("does not mutate the original object", () => {
const obj = { a: 1, b: 2 };
immutableDelete(obj, "a");
expect(obj).toEqual({ a: 1, b: 2 });
});

it("is a no-op when the path does not exist", () => {
const obj = { a: 1 };
expect(immutableDelete(obj, "b")).toEqual({ a: 1 });
});

it("splices an element from a cloned array by numeric index", () => {
const arr = ["a", "b", "c"];
const result = immutableDelete(arr, 1);
expect(result).toEqual(["a", "c"]);
});

it("splices the first element from a cloned array", () => {
const arr = [10, 20, 30];
expect(immutableDelete(arr, 0)).toEqual([20, 30]);
});

it("splices the last element from a cloned array", () => {
const arr = [10, 20, 30];
expect(immutableDelete(arr, 2)).toEqual([10, 20]);
});

it("does not mutate the original array", () => {
const arr = ["a", "b", "c"];
immutableDelete(arr, 1);
expect(arr).toEqual(["a", "b", "c"]);
});

it("returns a new array reference", () => {
const arr = [1, 2, 3];
const result = immutableDelete(arr, 0);
expect(result).not.toBe(arr);
});

it("returns a clone when the index is out of bounds", () => {
const arr = ["x"];
const result = immutableDelete(arr, 5);
expect(result).toEqual(["x"]);
expect(result).not.toBe(arr);
});

it("returns a clone for a negative index", () => {
const arr = [1, 2, 3];
const result = immutableDelete(arr, -1);
expect(result).toEqual([1, 2, 3]);
expect(result).not.toBe(arr);
});
});
36 changes: 36 additions & 0 deletions __tests__/dotProp/immutableGet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import { immutableGet } from "../../src/common/utils/dotProp.js";

describe("immutableGet", () => {
it("gets a top-level property", () => {
expect(immutableGet({ a: 1 }, "a")).toBe(1);
});

it("gets a nested property by dot path", () => {
expect(immutableGet({ a: { b: { c: 42 } } }, "a.b.c")).toBe(42);
});

it("returns undefined when path does not exist and no default is given", () => {
expect(immutableGet({ a: 1 }, "b")).toBeUndefined();
});

it("returns the default value when path does not exist", () => {
expect(immutableGet({ a: 1 }, "b", "fallback")).toBe("fallback");
});

it("returns the default value when object is null", () => {
expect(immutableGet(null, "a.b", 99)).toBe(99);
});

it("returns the default value when object is undefined", () => {
expect(immutableGet(undefined, "a.b", "x")).toBe("x");
});

it("does not mutate the source object", () => {
const obj = { a: { b: 1 } };
const newObj = immutableGet<any>(obj, "a");
newObj.b = 2;
expect(newObj).toEqual({ b: 2 });
expect(obj).toEqual({ a: { b: 1 } });
});
});
48 changes: 48 additions & 0 deletions __tests__/dotProp/immutableSet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, expect, it } from "vitest";
import { immutableSet } from "../../src/common/utils/dotProp.js";

describe("immutableSet", () => {
it("returns a new object reference", () => {
const obj = { a: 1 };
const result = immutableSet(obj, "a", 2);
expect(result).not.toBe(obj);
});

it("sets a top-level property", () => {
expect(immutableSet({ a: 1 }, "a", 2)).toEqual({ a: 2 });
});

it("sets a nested property by dot path", () => {
expect(immutableSet({ a: { b: 1 } }, "a.b", 99)).toEqual({ a: { b: 99 } });
});

it("creates intermediate objects for missing paths", () => {
expect(immutableSet({} as Record<string, any>, "a.b.c", 7)).toEqual({ a: { b: { c: 7 } } });
});

it("does not mutate the original object", () => {
const obj = { a: { b: 1 } };
immutableSet(obj, "a.b", 99);
expect(obj.a.b).toBe(1);
});

it("deep-clones nested objects so the result shares no references with the original", () => {
const obj = { a: { b: 1 } };
const result = immutableSet(obj, "a.b", 2);
expect(result.a).not.toBe(obj.a);
});

it("accepts a functional updater that receives the current value", () => {
const result = immutableSet({ count: 5 }, "count", (n: number) => n + 1);
expect(result).toEqual({ count: 6 });
});

it("functional updater receives undefined for a missing path", () => {
let received: unknown = "sentinel";
immutableSet({} as Record<string, any>, "missing", (v: unknown) => {
received = v;
return 0;
});
expect(received).toBeUndefined();
});
});
Loading
Loading