Joeri Exelmans 3 лет назад
Родитель
Сommit
f692d8deec
2 измененных файлов с 122 добавлено и 0 удалено
  1. 54 0
      src/onion/composite_delta.test.ts
  2. 68 0
      src/onion/composite_delta.ts

+ 54 - 0
src/onion/composite_delta.test.ts

@@ -0,0 +1,54 @@
+import * as _ from "lodash";
+
+import {
+  NodeCreation,
+  NodeDeletion,
+} from "./micro_op";
+
+import {
+  CompositeLevel,
+  CompositeDelta,
+} from "./composite_delta";
+
+import {
+  getUuidCallback,
+  assert,
+} from "./test_helpers";
+
+
+describe("Composite delta", () => {
+
+  it("Dependency", () => {
+    const getId = getUuidCallback();
+    const level = new CompositeLevel();
+
+    const nodeCreation = new NodeCreation(getId());
+    const composite1 = level.createComposite([nodeCreation]);
+    assert(_.isEqual(composite1.getDependencies(), []), "expected composite1 to have no dependencies");
+
+
+    const nodeDeletion = new NodeDeletion(nodeCreation, []);
+    const nodeCreation2 = new NodeCreation(getId());
+    const composite2 = level.createComposite([nodeDeletion, nodeCreation2]);
+    assert(_.isEqual(composite2.getDependencies(), [composite1]), "expected composite2 to depend on composite 1");
+  });
+
+  it("Conflict", () => {
+    const getId = getUuidCallback();
+    const level = new CompositeLevel();
+
+    const nodeCreation = new NodeCreation(getId());
+    const compositeCreate = level.createComposite([nodeCreation]);
+
+    const nodeDeletion1 = new NodeDeletion(nodeCreation, []);
+    const compositeDelete1 = level.createComposite([nodeDeletion1]);
+    assert(_.isEqual(compositeDelete1.getConflicts(), []), "there should not yet be a conflict")
+
+    const nodeDeletion2 = new NodeDeletion(nodeCreation, []);
+    const compositeDelete2 = level.createComposite([nodeDeletion2]);
+
+    assert(_.isEqual(compositeDelete1.getConflicts(), [compositeDelete2]), "expected compositeDelete1 to conflict with compositeDelete2");
+    assert(_.isEqual(compositeDelete2.getConflicts(), [compositeDelete1]), "expected compositeDelete1 to conflict with compositeDelete2");
+  });
+
+});

+ 68 - 0
src/onion/composite_delta.ts

@@ -0,0 +1,68 @@
+import {Delta} from "./delta";
+
+export class CompositeDelta implements Delta {
+  readonly dependencies: Array<CompositeDelta>;
+  readonly conflicts: Array<CompositeDelta>;
+
+  constructor(dependencies: Array<CompositeDelta>, conflicts: Array<CompositeDelta>) {
+    this.dependencies = dependencies;
+    this.conflicts = conflicts;
+  }
+
+  getDependencies(): Array<CompositeDelta> {
+    return this.dependencies;
+  }
+
+  getConflicts(): Array<CompositeDelta> {
+    return this.conflicts;
+  }
+}
+
+export class CompositeLevel {
+  containedBy: Map<Delta, CompositeDelta> = new Map();
+
+  createComposite(deltas: Array<Delta>) {
+    const dependencies: Array<CompositeDelta> = [];
+    const conflicts: Array<CompositeDelta> = [];
+
+    for (const delta of deltas) {
+      if (this.containedBy.has(delta)) {
+        throw new Error("Assertion failed: delta already part of another composite");
+      }
+      for (const dependency of delta.getDependencies()) {
+        const compositeDependency = this.containedBy.get(dependency);
+        if (compositeDependency === undefined) {
+          throw new Error("Assertion failed: cannot find composite of " + dependency.constructor.name);
+        }
+        if (!dependencies.includes(compositeDependency)) {
+          dependencies.push(compositeDependency);
+        }
+      }
+      for (const conflict of delta.getConflicts()) {
+        const compositeConflict = this.containedBy.get(conflict);
+        if (compositeConflict === undefined) {
+          throw new Error("Assertion failed: cannot find composite of " + conflict.constructor.name);
+        }
+        if (!conflicts.includes(compositeConflict)) {
+          conflicts.push(compositeConflict);
+        }
+      }
+    }
+
+    if (dependencies.some(dependency => conflicts.includes(dependency))) {
+      throw new Error("Assertion failed: overlap between dependencies and conflicts");
+    }
+
+    const composite = new CompositeDelta(dependencies, conflicts);
+
+    for (const delta of deltas) {
+      this.containedBy.set(delta, composite);
+    }
+    for (const compositeConflict of conflicts) {
+      // Conflicts are symmetric, so newly created conflicts are also added to the other:
+      compositeConflict.conflicts.push(composite);
+    }
+
+    return composite;
+  }
+}