merge.test.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import { describe, it, expect } from "vitest";
  2. import { deepMergeSpec } from "./merge";
  3. describe("deepMergeSpec", () => {
  4. it("adds new keys", () => {
  5. const result = deepMergeSpec({ a: 1 }, { b: 2 });
  6. expect(result).toEqual({ a: 1, b: 2 });
  7. });
  8. it("replaces scalar values", () => {
  9. const result = deepMergeSpec({ a: 1 }, { a: 2 });
  10. expect(result).toEqual({ a: 2 });
  11. });
  12. it("deletes keys set to null", () => {
  13. const result = deepMergeSpec({ a: 1, b: 2 }, { b: null });
  14. expect(result).toEqual({ a: 1 });
  15. });
  16. it("deep-merges nested objects", () => {
  17. const base = { user: { name: "Alice", age: 30 } };
  18. const patch = { user: { age: 31 } };
  19. const result = deepMergeSpec(base, patch);
  20. expect(result).toEqual({ user: { name: "Alice", age: 31 } });
  21. });
  22. it("replaces arrays (does not concat)", () => {
  23. const base = { items: [1, 2, 3] };
  24. const patch = { items: [4, 5] };
  25. const result = deepMergeSpec(base, patch);
  26. expect(result).toEqual({ items: [4, 5] });
  27. });
  28. it("does not mutate base or patch", () => {
  29. const base = { a: { b: 1 } };
  30. const patch = { a: { c: 2 } };
  31. const baseCopy = JSON.parse(JSON.stringify(base));
  32. const patchCopy = JSON.parse(JSON.stringify(patch));
  33. deepMergeSpec(base, patch);
  34. expect(base).toEqual(baseCopy);
  35. expect(patch).toEqual(patchCopy);
  36. });
  37. it("handles a spec-like edit merge", () => {
  38. const base = {
  39. root: "main",
  40. elements: {
  41. main: {
  42. type: "Card",
  43. props: { title: "Dashboard" },
  44. children: ["metric-1"],
  45. },
  46. "metric-1": {
  47. type: "Metric",
  48. props: { label: "Revenue", value: "$1M" },
  49. children: [],
  50. },
  51. },
  52. };
  53. const patch = {
  54. elements: {
  55. main: {
  56. props: { title: "Updated Dashboard" },
  57. children: ["metric-1", "chart-1"],
  58. },
  59. "chart-1": {
  60. type: "Chart",
  61. props: { data: "revenue" },
  62. children: [],
  63. },
  64. },
  65. };
  66. const result = deepMergeSpec(base, patch);
  67. expect(result.root).toBe("main");
  68. expect(
  69. (result.elements as Record<string, Record<string, unknown>>)["main"],
  70. ).toEqual({
  71. type: "Card",
  72. props: { title: "Updated Dashboard" },
  73. children: ["metric-1", "chart-1"],
  74. });
  75. expect(
  76. (result.elements as Record<string, Record<string, unknown>>)["metric-1"],
  77. ).toEqual({
  78. type: "Metric",
  79. props: { label: "Revenue", value: "$1M" },
  80. children: [],
  81. });
  82. expect(
  83. (result.elements as Record<string, Record<string, unknown>>)["chart-1"],
  84. ).toEqual({
  85. type: "Chart",
  86. props: { data: "revenue" },
  87. children: [],
  88. });
  89. });
  90. it("deletes an element via null", () => {
  91. const base = {
  92. elements: {
  93. main: { type: "Card" },
  94. old: { type: "Widget" },
  95. },
  96. };
  97. const patch = { elements: { old: null } };
  98. const result = deepMergeSpec(base, patch);
  99. expect(result.elements).toEqual({ main: { type: "Card" } });
  100. });
  101. });