Skip to content

Commit 4c67db6

Browse files
committed
test(entity): add tests for dontFragment and cascadeDelete relations
* Implement tests to verify dontFragment behavior in relations. * Ensure cascadeDelete and dontFragment can be set simultaneously. * Validate that entities remain in the same archetype with dontFragment.
1 parent f12b742 commit 4c67db6

File tree

2 files changed

+90
-2
lines changed

2 files changed

+90
-2
lines changed

src/entity.test.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
getComponentOptions,
2222
isExclusiveComponent,
2323
isCascadeDeleteComponent,
24+
isDontFragmentComponent,
2425
getComponentNameById,
2526
getComponentIdByName,
2627
} from "./entity";
@@ -455,11 +456,11 @@ describe("Component Options", () => {
455456

456457
it("should support name in options object", () => {
457458
const namedComp = component({ name: "TestComponent", exclusive: true });
458-
459+
459460
const options = getComponentOptions(namedComp);
460461
expect(options?.name).toBe("TestComponent");
461462
expect(options?.exclusive).toBe(true);
462-
463+
463464
expect(getComponentNameById(namedComp)).toBe("TestComponent");
464465
expect(getComponentIdByName("TestComponent")).toBe(namedComp);
465466
});
@@ -479,4 +480,23 @@ describe("Component Options", () => {
479480
expect(isCascadeDeleteComponent(cascadeComp)).toBe(true);
480481
expect(isCascadeDeleteComponent(normalComp)).toBe(false);
481482
});
483+
484+
it("should check if component is dontFragment", () => {
485+
const dontFragmentComp = component({ dontFragment: true });
486+
const normalComp = component();
487+
488+
expect(isDontFragmentComponent(dontFragmentComp)).toBe(true);
489+
expect(isDontFragmentComponent(normalComp)).toBe(false);
490+
});
491+
492+
it("should support cascadeDelete and dontFragment set simultaneously", () => {
493+
const combinedComp = component({ cascadeDelete: true, dontFragment: true });
494+
495+
const options = getComponentOptions(combinedComp);
496+
expect(options?.cascadeDelete).toBe(true);
497+
expect(options?.dontFragment).toBe(true);
498+
499+
expect(isCascadeDeleteComponent(combinedComp)).toBe(true);
500+
expect(isDontFragmentComponent(combinedComp)).toBe(true);
501+
});
482502
});

src/world.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,74 @@ describe("World", () => {
244244
expect(world.exists(b)).toBe(false);
245245
});
246246

247+
it("should prevent archetype fragmentation with dontFragment relations", () => {
248+
const world = new World();
249+
const entity1 = world.new();
250+
const entity2 = world.new();
251+
const target1 = world.new();
252+
const target2 = world.new();
253+
254+
// Create Follows component with dontFragment option
255+
const Follows = component<{ strength: number }>({ dontFragment: true });
256+
257+
const followsTarget1 = relation(Follows, target1);
258+
const followsTarget2 = relation(Follows, target2);
259+
260+
// Add different relations to different entities
261+
world.set(entity1, followsTarget1, { strength: 1 });
262+
world.set(entity2, followsTarget2, { strength: 2 });
263+
world.sync();
264+
265+
// Both entities should exist and have their relations
266+
expect(world.has(entity1, followsTarget1)).toBe(true);
267+
expect(world.has(entity2, followsTarget2)).toBe(true);
268+
269+
// They should be in the same archetype despite having different relation targets
270+
// (this is the key behavior of dontFragment)
271+
const archetype1 = (world as any).entityToArchetype.get(entity1);
272+
const archetype2 = (world as any).entityToArchetype.get(entity2);
273+
expect(archetype1).toBe(archetype2);
274+
275+
// Verify the wildcard marker is present
276+
const wildcardMarker = relation(Follows, "*");
277+
expect(archetype1.componentTypes).toContain(wildcardMarker);
278+
});
279+
280+
it("should support cascadeDelete and dontFragment simultaneously", () => {
281+
const world = new World();
282+
const parent = world.new();
283+
const child1 = world.new();
284+
const child2 = world.new();
285+
286+
// Create ChildOf component with both cascadeDelete and dontFragment options
287+
const ChildOf = component<{ priority: number }>({ cascadeDelete: true, dontFragment: true });
288+
289+
const childOfParent1 = relation(ChildOf, parent);
290+
const childOfParent2 = relation(ChildOf, parent);
291+
292+
// Add relations to children
293+
world.set(child1, childOfParent1, { priority: 1 });
294+
world.set(child2, childOfParent2, { priority: 2 });
295+
world.sync();
296+
297+
// Verify relations exist
298+
expect(world.has(child1, childOfParent1)).toBe(true);
299+
expect(world.has(child2, childOfParent2)).toBe(true);
300+
301+
// Both children should be in the same archetype (dontFragment behavior)
302+
const archetype1 = (world as any).entityToArchetype.get(child1);
303+
const archetype2 = (world as any).entityToArchetype.get(child2);
304+
expect(archetype1).toBe(archetype2);
305+
306+
// Delete parent - should cascade delete both children (cascadeDelete behavior)
307+
world.delete(parent);
308+
world.sync();
309+
310+
expect(world.exists(parent)).toBe(false);
311+
expect(world.exists(child1)).toBe(false);
312+
expect(world.exists(child2)).toBe(false);
313+
});
314+
247315
it("should handle multiple components", () => {
248316
const world = new World();
249317
const entity = world.new();

0 commit comments

Comments
 (0)