Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3e4a0e3
added enum with deletion modes
Noah1891 Feb 23, 2026
3a01d50
added isRequired flag to ReContainer
Noah1891 Feb 23, 2026
ce38944
remove isRequired flag from ReContainer
Noah1891 Feb 24, 2026
89672e3
added mode arguments to destruct, destructAllFromChangingList and delete
Noah1891 Feb 24, 2026
1fd5913
added mode arguments to remove() calls
Noah1891 Feb 24, 2026
6b369cb
added isRequired to ReContainer and the classes that inherit it
Noah1891 Feb 25, 2026
d454ea4
check for isRequired flag removeFromReferencableContainer() method an…
Noah1891 Feb 25, 2026
3d44b80
updated test files to be compatible with new isRequired flag
Noah1891 Mar 3, 2026
370b45d
adapted removeFromReferenceableContainer, destructAllFromChangingList…
Noah1891 Mar 3, 2026
a75705d
added minimal keml for testing deletion modes
Noah1891 Mar 3, 2026
4509e84
Updated tests and potential bug fix in removeFromInverse()
Noah1891 Mar 10, 2026
a16c889
Finished tests for new deletion modes
Noah1891 Mar 10, 2026
0342d96
updated test name to fix inconsistency
Noah1891 Mar 10, 2026
9d9a121
renamed test files
Noah1891 Mar 17, 2026
7d8c3e7
Merge branch 'main' into 14-offer-different-strategies-for-model-cons…
Noah1891 Mar 18, 2026
5bc1be0
fixed small mistakes after resolving merge conflicts
Noah1891 Mar 18, 2026
57f0529
removed isRequired from constructors and set it depending on meta.min…
Noah1891 Mar 23, 2026
fabc8be
Merge branch 'main' into 14-offer-different-strategies-for-model-cons…
Noah1891 Mar 23, 2026
3cfd611
deleted old incompatible tests
Noah1891 Mar 23, 2026
1abcb3f
added deleted tests again in incompatible state
Noah1891 Mar 23, 2026
1a9dcc9
added deleted classes for tests again in incompatible state
Noah1891 Mar 23, 2026
972a8e8
deleted conversation test classes for future replacement and updated …
Noah1891 Mar 29, 2026
a75ba6d
-added removeCascade to MetaAwareModelList interface
Noah1891 Mar 31, 2026
f6f2585
-added tests for removing elements in single containers
Noah1891 Apr 9, 2026
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Referencable} from "../../referenceable";
import {ModelList} from "./model-list";
import {ReListInterface} from "../re-list-interface";
import {DeletionMode} from "../../../../utils/deletion-mode";

export function createListProxy<
T extends Referencable<any>,
Expand Down Expand Up @@ -103,32 +104,37 @@ export function createListProxy<
items.some(item => container.remove(item));
}

if (prop === "removeCascade") {
return (...items: T[]) =>
items.some(item => container.remove(item, DeletionMode.CASCADE))
}

if(prop === "delete") {
return () => {
container.delete();
return (mode?: DeletionMode) => {
container.delete(mode);
}
}

// The pop() method removes the last element from an array and returns that value to the caller.
// If you call pop() on an empty array, it returns undefined.
if (prop === "pop") {
return () => {
return (mode?: DeletionMode) => {
const arr = container.get();
if (arr.length === 0) return undefined;
const last = arr[arr.length - 1];
container.remove(last);
container.remove(last, mode);
return last;
};
}

// The shift() method of Array instances removes the first element from an array and returns that removed element.
// If you call shift() on an empty array, it returns undefined.
if (prop === "shift") {
return () => {
return (mode?: DeletionMode) => {
const arr = container.get();
if (arr.length === 0) return undefined;
const first = arr[0];
container.remove(first);
container.remove(first, mode);
return first;
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface MetaAwareModelList<T, Kind extends RefKind>
move(from: number, to: number): void;
swap(from: number, to: number): void;
remove(...items: T[]): boolean;
removeCascade(...items: T[]): boolean;
delete(): void;

readonly __item: T;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import {Referencable} from "../../referenceable";
import { ReContainer } from "../re-container";
import {SerializationContext} from "../../../../serialization/serialization-context";
import {Ref} from "../../../ref/ref";
import { DeletionMode } from "../../../../utils/deletion-mode";

export interface ReLinkContainer<
T extends Referencable<any>,
P extends Referencable<any>
> extends ReContainer<T,P> {

/*protected constructor(parent: P, referenceName: string, inverseName?: string) {
super(parent, referenceName, inverseName);
/*protected constructor(parent: P, referenceName: string, isRequired: boolean, inverseName?: string) {
super(parent, referenceName, isRequired, inverseName);
this._parent.$otherReferences.push(this)
}*/

removeFromInverse(item: T): boolean;
removeFromInverse(item: T, mode?: DeletionMode): boolean;

toJson(ctx: SerializationContext): Ref[] | Ref | undefined
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,91 @@
import { ReLinkListContainer } from './re-link-list-container';
import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester";
import {RootWithChildren, ReChild3} from "../../../test/referencables-with-children";
import {RootWithChildren, ReChild3, ReChild4, Middle2WithChildren} from "../../../test/referencables-with-children";

describe('ReLinkListContainer', () => {
it('should create an instance', () => {
let tester = new ReferencableTester()
expect(new ReLinkListContainer(tester, 'refName', refTesterRef.references.test)).toBeTruthy();
});

it("should give true if the remove and remove inverse chain triggered an element removal", () => {
it("should give true if the remove and remove inverse chain triggered an element removal without removing element from different container", () => {
let tester = new RootWithChildren()
let elem2 = new ReChild3()
expect(tester.link3.remove(elem2)).toBeFalse()
tester.link3.push(elem2)
let middle = new Middle2WithChildren()
let elem1 = new ReChild3()
let elem2 = new ReChild4()
expect(tester.link3.remove(elem1)).toBeFalse()
expect(tester.link4.remove(elem2)).toBeFalse()
tester.link3.push(elem1)
tester.link4.push(elem2)
middle.child3.push(elem1)
middle.child4.push(elem2)
expect(tester.link3.length).toBe(1)
expect(tester.link3).toContain(elem2)
expect(tester.link3.remove(elem2)).toBeTrue()
expect(tester.link4.length).toBe(1)
expect(tester.link3).toContain(elem1)
expect(tester.link4).toContain(elem2)
expect(middle.child3.length).toBe(1)
expect(middle.child4.length).toBe(1)
expect(middle.child3).toContain(elem1)
expect(middle.child4).toContain(elem2)
expect(elem1.link1.length).toBe(1)
expect(elem2.link1.length).toBe(1)
expect(elem1.link1).toContain(tester)
expect(elem2.link1).toContain(tester)
expect(elem1.parentPointer).toBeDefined()
expect(elem2.parentPointer).toBeDefined()
expect(elem1.parentPointer).toEqual(middle)
expect(elem2.parentPointer).toEqual(middle)
expect(tester.link3.remove(elem1)).toBeTrue()
expect(tester.link4.remove(elem2)).toBeTrue()
expect(tester.link3.length).toBe(0)
expect(tester.link4.length).toBe(0)
expect(middle.child3.length).toBe(1)
expect(middle.child4.length).toBe(1)
expect(elem1.link1.length).toBe(0)
expect(elem2.link1.length).toBe(0)
expect(elem1.parentPointer).toBeDefined()
expect(elem2.parentPointer).toBeDefined()
expect(elem1.parentPointer).toEqual(middle)
expect(elem2.parentPointer).toEqual(middle)
});

it("should give true if the removeCascade and remove inverse chain triggered an element removal, also removing it from other containers if required reference is deleted", () => {
let tester = new RootWithChildren()
let middle = new Middle2WithChildren()
let elem1 = new ReChild3()
let elem2 = new ReChild4()
expect(tester.link3.removeCascade(elem1)).toBeFalse()
expect(tester.link4.removeCascade(elem2)).toBeFalse()
tester.link3.push(elem1)
tester.link4.push(elem2)
middle.child3.push(elem1)
middle.child4.push(elem2)
expect(tester.link3.length).toBe(1)
expect(tester.link4.length).toBe(1)
expect(tester.link3).toContain(elem1)
expect(tester.link4).toContain(elem2)
expect(middle.child3.length).toBe(1)
expect(middle.child4.length).toBe(1)
expect(middle.child3).toContain(elem1)
expect(middle.child4).toContain(elem2)
expect(elem1.link1.length).toBe(1)
expect(elem2.link1.length).toBe(1)
expect(elem1.link1).toContain(tester)
expect(elem2.link1).toContain(tester)
expect(elem1.parentPointer).toBeDefined()
expect(elem2.parentPointer).toBeDefined()
expect(elem1.parentPointer).toEqual(middle)
expect(elem2.parentPointer).toEqual(middle)
expect(tester.link3.removeCascade(elem1)).toBeTrue()
expect(tester.link4.removeCascade(elem2)).toBeTrue()
expect(tester.link3.length).toBe(0)
expect(tester.link4.length).toBe(0)
expect(middle.child3.length).toBe(1)
expect(middle.child4.length).toBe(0)
expect(elem1.link1.length).toBe(0)
expect(elem2.link1.length).toBe(0)
expect(elem1.parentPointer).toBeDefined()
expect(elem2.parentPointer).toBeUndefined()
expect(elem1.parentPointer).toEqual(middle)
})
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {ReLinkContainer} from "./re-link-container";
import {ListUpdater} from "../../../../utils/list-updater";
import {ReListContainer} from "../re-list-container";
import {ReferenceMeta} from "../../../../binding/model-definition";
import {DeletionMode} from "../../../../utils/deletion-mode";

export class ReLinkListContainer<
T extends Referencable<any>,
Expand Down Expand Up @@ -33,24 +34,24 @@ implements ReLinkContainer<T, P> {
return this._instance.map(i => ctx.get(i))
}

override remove(item: T): boolean {
override remove(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean {
const res = ListUpdater.removeFromList(item, this._instance)
if (res) {
if(this.inverseName !== undefined) {
item.removeFromReferencableContainer(this.inverseName, this._parent)
item.removeFromReferencableContainer(this.inverseName, this._parent, mode)
}
}
return res; //todo behaviour of flag different to add??
}

override delete() {
ListUpdater.destructAllFromChangingList(this._instance)
override delete(mode: DeletionMode = DeletionMode.RELAXED) {
ListUpdater.destructAllFromChangingList(this._instance, mode)
}

removeFromInverse(item: T): boolean {
removeFromInverse(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean {
if(this.inverseName !== undefined) {
for (const child of [...this._instance]) {
child.removeFromReferencableContainer(this.inverseName, item)
child.removeFromReferencableContainer(this.inverseName, item, mode)
}
return true; // todo - refine?
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,100 @@
import { ReLinkSingleContainer } from './re-link-single-container';
import {ReLinkSingleContainer} from './re-link-single-container';
import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester";
import {
ReContainersWithSingleChild,
ReContainersWithSingleChild2,
ReSingleChildExample,
ReSingleChildExample2
} from "../../../test/re-containers-with-single-child";
import {DeletionMode} from "../../../../utils/deletion-mode";

describe('ReLinkSingleContainer', () => {
it('should create an instance', () => {
let tester = new ReferencableTester()
expect(new ReLinkSingleContainer(tester, 'refName', refTesterRef.references.test)).toBeTruthy();
expect(new ReLinkSingleContainer(tester, 'refName', refTesterRef.references.test)).toBeTruthy();
});

it('should remove child from container without it getting removed from other containers, even if required reference is removed', () => {
let tester1 = new ReContainersWithSingleChild();
let middle1 = new ReSingleChildExample();
let elem1 = new ReContainersWithSingleChild();
let tester2 = new ReContainersWithSingleChild2();
let middle2 = new ReSingleChildExample2();
let elem2 = new ReContainersWithSingleChild2();
tester1.child = middle1;
middle1.otherLink = elem1;
tester2.child = middle2;
middle2.otherLink = elem2;
expect(tester1.child).toBeDefined();
expect(tester1.child).toEqual(middle1);
expect(tester2.child).toBeDefined();
expect(tester2.child).toEqual(middle2);
expect(middle1.myParent).toBeDefined();
expect(middle1.myParent).toEqual(tester1);
expect(middle2.myParent).toBeDefined();
expect(middle2.myParent).toEqual(tester2);
expect(middle1.otherLink).toBeDefined();
expect(middle1.otherLink).toEqual(elem1);
expect(middle2.otherLink).toBeDefined();
expect(middle2.otherLink).toEqual(elem2);
expect(elem1.link).toBeDefined();
expect(elem1.link).toEqual(middle1);
expect(elem2.link).toBeDefined();
expect(elem2.link).toEqual(middle2);
expect(elem1.$otherReferences[0].remove(middle1)).toBeTrue();
expect(elem2.$otherReferences[0].remove(middle2)).toBeTrue();
expect(tester1.child).toBeDefined();
expect(tester1.child).toEqual(middle1);
expect(tester2.child).toBeDefined();
expect(tester2.child).toEqual(middle2);
expect(middle1.myParent).toBeDefined();
expect(middle1.myParent).toEqual(tester1);
expect(middle2.myParent).toBeDefined();
expect(middle2.myParent).toEqual(tester2);
expect(middle1.otherLink).toBeUndefined();
expect(middle2.otherLink).toBeUndefined();
expect(elem1.link).toBeUndefined();
expect(elem2.link).toBeUndefined();
});

it('should remove reference from container, triggering its deletion in case of required reference getting removed', () => {
let tester1 = new ReContainersWithSingleChild();
let middle1 = new ReSingleChildExample();
let elem1 = new ReContainersWithSingleChild();
let tester2 = new ReContainersWithSingleChild2();
let middle2 = new ReSingleChildExample2();
let elem2 = new ReContainersWithSingleChild2();
tester1.child = middle1;
middle1.otherLink = elem1;
tester2.child = middle2;
middle2.otherLink = elem2;
expect(tester1.child).toBeDefined();
expect(tester1.child).toEqual(middle1);
expect(tester2.child).toBeDefined();
expect(tester2.child).toEqual(middle2);
expect(middle1.myParent).toBeDefined();
expect(middle1.myParent).toEqual(tester1);
expect(middle2.myParent).toBeDefined();
expect(middle2.myParent).toEqual(tester2);
expect(middle1.otherLink).toBeDefined();
expect(middle1.otherLink).toEqual(elem1);
expect(middle2.otherLink).toBeDefined();
expect(middle2.otherLink).toEqual(elem2);
expect(elem1.link).toBeDefined();
expect(elem1.link).toEqual(middle1);
expect(elem2.link).toBeDefined();
expect(elem2.link).toEqual(middle2);
expect(elem1.$otherReferences[0].remove(middle1, DeletionMode.CASCADE)).toBeTrue();
expect(elem2.$otherReferences[0].remove(middle2, DeletionMode.CASCADE)).toBeTrue();
expect(tester1.child).toBeDefined();
expect(tester1.child).toEqual(middle1);
expect(tester2.child).toBeUndefined();
expect(middle1.myParent).toBeDefined();
expect(middle1.myParent).toEqual(tester1);
expect(middle2.myParent).toBeUndefined();
expect(middle1.otherLink).toBeUndefined();
expect(middle2.otherLink).toBeUndefined();
expect(elem1.link).toBeUndefined();
expect(elem2.link).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {SerializationContext} from "../../../../serialization/serialization-cont
import {ReLinkContainer} from "./re-link-container";
import {ReSingleContainer} from "../re-single-container";
import {ReferenceMeta} from "../../../../binding/model-definition";
import { DeletionMode } from "../../../../utils/deletion-mode";

export class ReLinkSingleContainer<
T extends Referencable<any>,
Expand All @@ -18,7 +19,7 @@ implements ReLinkContainer<T, P> {

protected set(instance: T): void {
if(this.inverseName !== undefined) {
this._instance?.removeFromReferencableContainer(this.inverseName, this._parent)
this._instance?.removeFromReferencableContainer(this.inverseName, this._parent, DeletionMode.RELAXED)
this._instance = instance;
instance.addToReferencableContainer(this.inverseName, this._parent)
} else {
Expand All @@ -35,25 +36,25 @@ implements ReLinkContainer<T, P> {
}
}

remove(item: T): boolean {
remove(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean {
if(this._instance == item) {
this._instance = undefined;
if (this.inverseName != undefined) {
item.removeFromReferencableContainer(this.inverseName, this._parent)
item.removeFromReferencableContainer(this.inverseName, this._parent, mode)
}
this._instance = undefined;
return true;
} else {
return false;
}
}

override delete() {
this._instance?.destruct()
override delete(mode: DeletionMode = DeletionMode.RELAXED) {
this._instance?.destruct(mode)
}

removeFromInverse(item: T): boolean {
removeFromInverse(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean {
if(this.inverseName !== undefined) {
this._instance?.removeFromReferencableContainer(this.inverseName, item)
this._instance?.removeFromReferencableContainer(this.inverseName, item, mode)
return true; // todo refine?
}
return false;
Expand Down
Loading