UNPKG

honkit

Version:

HonKit is building beautiful books using Markdown.

130 lines (129 loc) 7.01 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * Direct unit tests for {@link ../safeObjectPath} — mirrors {@link ./objectPath.contract.test.ts} * to keep object-path compatibility. Documents intentional differences (empty path, trimmed * segments, unsafe segment handling). */ const safeObjectPath_1 = require("../safeObjectPath"); describe("safeObjectPath", () => { describe("getAtPath", () => { test("returns nested value by dot path", () => { const obj = { structure: { readme: "README.md" } }; expect((0, safeObjectPath_1.getAtPath)(obj, "structure.readme")).toBe("README.md"); }); test("accepts array path", () => { const obj = { structure: { readme: "README.md" } }; expect((0, safeObjectPath_1.getAtPath)(obj, ["structure", "readme"])).toBe("README.md"); }); test("array path preserves empty-string segments as literal keys", () => { const obj = { "": { key: 1 }, a: { "": 2 } }; expect((0, safeObjectPath_1.getAtPath)(obj, ["", "key"], "d")).toBe(1); expect((0, safeObjectPath_1.getAtPath)(obj, ["a", ""], "d")).toBe(2); }); test("returns default when path missing", () => { const obj = { a: 1 }; expect((0, safeObjectPath_1.getAtPath)(obj, "b", "fallback")).toBe("fallback"); expect((0, safeObjectPath_1.getAtPath)(obj, "a.c", "fallback")).toBe("fallback"); }); test("returns default for null / undefined root", () => { expect((0, safeObjectPath_1.getAtPath)(null, "a", "d")).toBe("d"); expect((0, safeObjectPath_1.getAtPath)(undefined, "a", "d")).toBe("d"); }); test("empty path returns default (unlike object-path empty-path root)", () => { expect((0, safeObjectPath_1.getAtPath)({ a: 1 }, "", "d")).toBe("d"); expect((0, safeObjectPath_1.getAtPath)({ a: 1 }, [], "d")).toBe("d"); }); test("returns null when key exists with null value", () => { expect((0, safeObjectPath_1.getAtPath)({ x: null }, "x", "d")).toBeNull(); }); test("returns default when traversing into null intermediate", () => { expect((0, safeObjectPath_1.getAtPath)({ x: null }, "x.y", "d")).toBe("d"); }); test("returns default when traversing into non-object", () => { expect((0, safeObjectPath_1.getAtPath)({ x: "str" }, "x.y", "d")).toBe("d"); }); test("undefined leaf with default returns default", () => { expect((0, safeObjectPath_1.getAtPath)({ a: {} }, "a.missing", "d")).toBe("d"); }); test("does not read inherited prototype properties (matches object-path)", () => { const obj = Object.create({ inherited: 1 }); obj.own = 2; expect((0, safeObjectPath_1.getAtPath)(obj, "inherited", "d")).toBe("d"); expect((0, safeObjectPath_1.getAtPath)(obj, "own", "d")).toBe(2); }); test("rejects whole path containing unsafe segments", () => { const obj = { safe: 1, polluted: "real-value" }; expect((0, safeObjectPath_1.getAtPath)(obj, "__proto__.polluted", "d")).toBe("d"); expect((0, safeObjectPath_1.getAtPath)(obj, "constructor.prototype.polluted", "d")).toBe("d"); // path is not rewritten to "polluted" by stripping __proto__ expect((0, safeObjectPath_1.getAtPath)(obj, "__proto__", "d")).toBe("d"); }); test("trims segment whitespace for string paths (differs from object-path)", () => { expect((0, safeObjectPath_1.getAtPath)({ a: { b: 2 } }, " a . b ", "d")).toBe(2); }); }); describe("setAtPath", () => { test("sets nested value by dot path", () => { const obj = {}; (0, safeObjectPath_1.setAtPath)(obj, "structure.readme", "INTRO.md"); expect(obj).toEqual({ structure: { readme: "INTRO.md" } }); }); test("accepts array path", () => { const obj = {}; (0, safeObjectPath_1.setAtPath)(obj, ["structure", "readme"], "INTRO.md"); expect(obj).toEqual({ structure: { readme: "INTRO.md" } }); }); test("array path preserves empty-string segments when writing", () => { const obj = {}; (0, safeObjectPath_1.setAtPath)(obj, ["", "key"], "v"); expect(obj).toEqual({ "": { key: "v" } }); }); test("overwrites existing nested value", () => { const obj = { structure: { readme: "A.md" } }; (0, safeObjectPath_1.setAtPath)(obj, "structure.readme", "B.md"); expect(obj.structure.readme).toBe("B.md"); }); test("uses array index segments for numeric keys", () => { const obj = {}; (0, safeObjectPath_1.setAtPath)(obj, "items.0.name", "first"); expect(obj.items).toEqual([{ name: "first" }]); }); test("throws when intermediate is a non-object scalar (matches object-path)", () => { const obj = { a: "was-string" }; expect(() => (0, safeObjectPath_1.setAtPath)(obj, "a.b", 1)).toThrow(); }); test("throws when intermediate is null (matches object-path / native JS)", () => { const obj = { a: null }; expect(() => (0, safeObjectPath_1.setAtPath)(obj, "a.b", 1)).toThrow(); // original null is left untouched expect(obj.a).toBeNull(); }); test("creates missing intermediate when the existing key is undefined", () => { const obj = {}; (0, safeObjectPath_1.setAtPath)(obj, "a.b", 1); expect(obj).toEqual({ a: { b: 1 } }); }); test("set on path containing __proto__ is a no-op (does not write top-level)", () => { const obj = { ok: true }; expect((0, safeObjectPath_1.setAtPath)(obj, "__proto__", "ignored")).toBeUndefined(); expect((0, safeObjectPath_1.setAtPath)(obj, "__proto__.polluted", "ignored")).toBeUndefined(); expect(obj).toEqual({ ok: true }); expect(obj.polluted).toBeUndefined(); }); test("does not descend into inherited objects when setting", () => { const proto = { nested: { shared: 1 } }; const obj = Object.create(proto); (0, safeObjectPath_1.setAtPath)(obj, "nested.shared", 2); // own "nested" was created on obj, not mutated on proto expect(Object.prototype.hasOwnProperty.call(obj, "nested")).toBe(true); expect(proto.nested).toEqual({ shared: 1 }); expect(obj.nested.shared).toBe(2); }); test("returns undefined (matches object-path)", () => { const obj = {}; expect((0, safeObjectPath_1.setAtPath)(obj, "x", 42)).toBeUndefined(); expect(obj.x).toBe(42); }); }); });