|
9 | 9 |
|
10 | 10 | import { describe, expect, it } from "vitest"; |
11 | 11 |
|
12 | | -import { type Node, sortAndSplice } from "@/tree/core"; |
| 12 | +import { |
| 13 | + deepCopy, |
| 14 | + findNode, |
| 15 | + findNodeParent, |
| 16 | + findNodes, |
| 17 | + flatten, |
| 18 | + getAllNodesOfMinDepth, |
| 19 | + getDescendants, |
| 20 | + moveNode, |
| 21 | + type Node, |
| 22 | + type NodeWithPosition, |
| 23 | + removeNode, |
| 24 | + setNode, |
| 25 | + sortAndSplice, |
| 26 | + updateNode, |
| 27 | +} from "@/tree/core"; |
13 | 28 |
|
14 | 29 | describe("Tree", () => { |
15 | 30 | describe("sortAndSplice", () => { |
@@ -40,4 +55,257 @@ describe("Tree", () => { |
40 | 55 | ]); |
41 | 56 | }); |
42 | 57 | }); |
| 58 | + |
| 59 | + describe("flatten", () => { |
| 60 | + it("should correctly flatten a tree structure", () => { |
| 61 | + const nodes: Node[] = [ |
| 62 | + { |
| 63 | + key: "1", |
| 64 | + name: "parent1", |
| 65 | + children: [ |
| 66 | + { key: "1-1", name: "child1" }, |
| 67 | + { key: "1-2", name: "child2" }, |
| 68 | + ], |
| 69 | + }, |
| 70 | + { key: "2", name: "parent2" }, |
| 71 | + ]; |
| 72 | + const expanded = ["1"]; |
| 73 | + const result = flatten({ nodes, expanded }); |
| 74 | + expect(result).toEqual([ |
| 75 | + { |
| 76 | + key: "1", |
| 77 | + name: "parent1", |
| 78 | + depth: 0, |
| 79 | + expanded: true, |
| 80 | + index: 0, |
| 81 | + path: "1/", |
| 82 | + children: [ |
| 83 | + { key: "1-1", name: "child1" }, |
| 84 | + { key: "1-2", name: "child2" }, |
| 85 | + ], |
| 86 | + }, |
| 87 | + { |
| 88 | + key: "1-1", |
| 89 | + name: "child1", |
| 90 | + depth: 1, |
| 91 | + expanded: false, |
| 92 | + index: 0, |
| 93 | + path: "1/1-1/", |
| 94 | + }, |
| 95 | + { |
| 96 | + key: "1-2", |
| 97 | + name: "child2", |
| 98 | + depth: 1, |
| 99 | + expanded: false, |
| 100 | + index: 1, |
| 101 | + path: "1/1-2/", |
| 102 | + }, |
| 103 | + { key: "2", name: "parent2", depth: 0, expanded: false, index: 1, path: "2/" }, |
| 104 | + ]); |
| 105 | + }); |
| 106 | + |
| 107 | + it("should respect sort option", () => { |
| 108 | + const nodes: Node[] = [ |
| 109 | + { key: "2", name: "B" }, |
| 110 | + { key: "1", name: "A" }, |
| 111 | + ]; |
| 112 | + const result = flatten({ nodes, expanded: [], sort: true }); |
| 113 | + expect(result[0].name).toBe("A"); |
| 114 | + expect(result[1].name).toBe("B"); |
| 115 | + }); |
| 116 | + }); |
| 117 | + |
| 118 | + describe("moveNode", () => { |
| 119 | + it("should move nodes to root when destination is null", () => { |
| 120 | + const tree: Node[] = [ |
| 121 | + { |
| 122 | + key: "1", |
| 123 | + name: "parent", |
| 124 | + children: [{ key: "2", name: "child" }], |
| 125 | + }, |
| 126 | + ]; |
| 127 | + const result = moveNode({ tree, destination: null, keys: "2" }); |
| 128 | + expect(result).toHaveLength(2); |
| 129 | + expect(result.find((n) => n.key === "2")).toBeDefined(); |
| 130 | + }); |
| 131 | + |
| 132 | + it("should move node to new parent", () => { |
| 133 | + const tree: Node[] = [ |
| 134 | + { key: "1", name: "parent1" }, |
| 135 | + { key: "2", name: "parent2", children: [] }, |
| 136 | + ]; |
| 137 | + const result = moveNode({ tree, destination: "2", keys: "1" }); |
| 138 | + expect(result[0].children?.[0].key).toBe("1"); |
| 139 | + }); |
| 140 | + }); |
| 141 | + |
| 142 | + describe("removeNode", () => { |
| 143 | + it("should remove node from root level", () => { |
| 144 | + const tree: Node[] = [ |
| 145 | + { key: "1", name: "node1" }, |
| 146 | + { key: "2", name: "node2" }, |
| 147 | + ]; |
| 148 | + const result = removeNode({ tree, keys: "1" }); |
| 149 | + expect(result).toHaveLength(1); |
| 150 | + expect(result[0].key).toBe("2"); |
| 151 | + }); |
| 152 | + |
| 153 | + it("should remove node from nested level", () => { |
| 154 | + const tree: Node[] = [ |
| 155 | + { |
| 156 | + key: "1", |
| 157 | + name: "parent", |
| 158 | + children: [{ key: "2", name: "child" }], |
| 159 | + }, |
| 160 | + ]; |
| 161 | + const result = removeNode({ tree, keys: "2" }); |
| 162 | + expect(result[0].children).toHaveLength(0); |
| 163 | + }); |
| 164 | + }); |
| 165 | + |
| 166 | + describe("setNode", () => { |
| 167 | + it("should add nodes to root when destination is null", () => { |
| 168 | + const tree: Node[] = [{ key: "1", name: "existing" }]; |
| 169 | + const addition: Node = { key: "2", name: "new" }; |
| 170 | + const result = setNode({ tree, destination: null, additions: addition }); |
| 171 | + expect(result).toHaveLength(2); |
| 172 | + expect(result.find((n) => n.key === "2")).toBeDefined(); |
| 173 | + }); |
| 174 | + |
| 175 | + it("should add nodes to specified parent", () => { |
| 176 | + const tree: Node[] = [{ key: "1", name: "parent", children: [] }]; |
| 177 | + const addition: Node = { key: "2", name: "child" }; |
| 178 | + const result = setNode({ tree, destination: "1", additions: addition }); |
| 179 | + expect(result[0].children).toHaveLength(1); |
| 180 | + expect(result[0].children?.[0].key).toBe("2"); |
| 181 | + }); |
| 182 | + }); |
| 183 | + |
| 184 | + describe("updateNode", () => { |
| 185 | + it("should update node properties", () => { |
| 186 | + const tree: Node[] = [{ key: "1", name: "old" }]; |
| 187 | + const result = updateNode({ |
| 188 | + tree, |
| 189 | + key: "1", |
| 190 | + updater: (node) => ({ ...node, name: "new" }), |
| 191 | + }); |
| 192 | + expect(result[0].name).toBe("new"); |
| 193 | + }); |
| 194 | + |
| 195 | + it("should throw on missing node when throwOnMissing is true", () => { |
| 196 | + const tree: Node[] = []; |
| 197 | + expect(() => |
| 198 | + updateNode({ |
| 199 | + tree, |
| 200 | + key: "missing", |
| 201 | + updater: (node) => node, |
| 202 | + }), |
| 203 | + ).toThrow(); |
| 204 | + }); |
| 205 | + }); |
| 206 | + |
| 207 | + describe("findNode", () => { |
| 208 | + it("should find node and return position info", () => { |
| 209 | + const tree: Node[] = [ |
| 210 | + { |
| 211 | + key: "1", |
| 212 | + name: "parent", |
| 213 | + children: [{ key: "2", name: "child" }], |
| 214 | + }, |
| 215 | + ]; |
| 216 | + const result = findNode({ tree, key: "2" }); |
| 217 | + expect(result?.depth).toBe(1); |
| 218 | + expect(result?.position).toBe(0); |
| 219 | + }); |
| 220 | + |
| 221 | + it("should return null for non-existent node", () => { |
| 222 | + const tree: Node[] = [{ key: "1", name: "node" }]; |
| 223 | + const result = findNode({ tree, key: "missing" }); |
| 224 | + expect(result).toBeNull(); |
| 225 | + }); |
| 226 | + }); |
| 227 | + |
| 228 | + describe("findNodes", () => { |
| 229 | + it("should find multiple nodes", () => { |
| 230 | + const tree: Node[] = [ |
| 231 | + { key: "1", name: "node1" }, |
| 232 | + { key: "2", name: "node2" }, |
| 233 | + ]; |
| 234 | + const result = findNodes({ tree, keys: ["1", "2"] }); |
| 235 | + expect(result).toHaveLength(2); |
| 236 | + }); |
| 237 | + }); |
| 238 | + |
| 239 | + describe("findNodeParent", () => { |
| 240 | + it("should find parent of nested node", () => { |
| 241 | + const tree: Node[] = [ |
| 242 | + { |
| 243 | + key: "1", |
| 244 | + name: "parent", |
| 245 | + children: [{ key: "2", name: "child" }], |
| 246 | + }, |
| 247 | + ]; |
| 248 | + const result = findNodeParent({ tree, key: "2" }); |
| 249 | + expect(result?.key).toBe("1"); |
| 250 | + }); |
| 251 | + |
| 252 | + it("should return null for root level node", () => { |
| 253 | + const tree: Node[] = [{ key: "1", name: "root" }]; |
| 254 | + const result = findNodeParent({ tree, key: "1" }); |
| 255 | + expect(result).toBeNull(); |
| 256 | + }); |
| 257 | + }); |
| 258 | + |
| 259 | + describe("deepCopy", () => { |
| 260 | + it("should create deep copy of tree structure", () => { |
| 261 | + const original: Node[] = [ |
| 262 | + { |
| 263 | + key: "1", |
| 264 | + name: "parent", |
| 265 | + children: [{ key: "2", name: "child" }], |
| 266 | + }, |
| 267 | + ]; |
| 268 | + const copy = deepCopy(original); |
| 269 | + copy[0].name = "modified"; |
| 270 | + expect(original[0].name).toBe("parent"); |
| 271 | + }); |
| 272 | + }); |
| 273 | + |
| 274 | + describe("getDescendants", () => { |
| 275 | + it("should return all descendants of nodes", () => { |
| 276 | + const node: Node = { |
| 277 | + key: "1", |
| 278 | + name: "parent", |
| 279 | + children: [ |
| 280 | + { key: "2", name: "child1" }, |
| 281 | + { |
| 282 | + key: "3", |
| 283 | + name: "child2", |
| 284 | + children: [{ key: "4", name: "grandchild" }], |
| 285 | + }, |
| 286 | + ], |
| 287 | + }; |
| 288 | + const result = getDescendants(node); |
| 289 | + expect(result).toHaveLength(4); |
| 290 | + expect(result.map((n) => n.key)).toEqual(["1", "2", "3", "4"]); |
| 291 | + }); |
| 292 | + }); |
| 293 | + |
| 294 | + describe("getAllNodesOfMinDepth", () => { |
| 295 | + it("should return nodes at minimum depth", () => { |
| 296 | + const nodes: NodeWithPosition[] = [ |
| 297 | + { key: "1", name: "root1", depth: 0, position: 0 }, |
| 298 | + { key: "2", name: "root2", depth: 0, position: 1 }, |
| 299 | + { key: "3", name: "child", depth: 1, position: 0 }, |
| 300 | + ]; |
| 301 | + const result = getAllNodesOfMinDepth(nodes); |
| 302 | + expect(result).toHaveLength(2); |
| 303 | + expect(result.every((n) => n.depth === 0)).toBe(true); |
| 304 | + }); |
| 305 | + |
| 306 | + it("should return empty array for empty input", () => { |
| 307 | + const result = getAllNodesOfMinDepth([]); |
| 308 | + expect(result).toHaveLength(0); |
| 309 | + }); |
| 310 | + }); |
43 | 311 | }); |
0 commit comments