/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { assert, expect } from "chai";
import { join } from "path";
import { restore as sinonRestore, spy as sinonSpy } from "sinon";
import { Guid, Id64 } from "@itwin/core-bentley";
import { CodeScopeSpec, CodeSpec, IModel } from "@itwin/core-common";
import { ClassRegistry } from "../../ClassRegistry";
import { EditTxn } from "../../EditTxn";
import { ElementUniqueAspect } from "../../ElementAspect";
import { _nativeDb, ChannelControl, FunctionalBreakdownElement, FunctionalComponentElement, FunctionalModel, FunctionalPartition, FunctionalSchema, InformationPartitionElement, Schemas, StandaloneDb, } from "../../core-backend";
import { ElementOwnsChildElements, ElementOwnsUniqueAspect, SubjectOwnsPartitionElements } from "../../NavigationRelationship";
import { IModelTestUtils } from "../IModelTestUtils";
import { KnownTestLocations } from "../KnownTestLocations";
let iModelDb;
const insertedLabel = "inserted label";
const updatedLabel = "updated label";
/** test schema for supplying element/model/aspect classes */
class TestSchema extends FunctionalSchema {
    static get schemaName() { return "TestFunctional"; }
}
/** partition element for testing `Element.onSubModelXxx` methods */
class TestFuncPartition extends InformationPartitionElement {
    static get className() { return "TestFuncPartition"; }
    static onSubModelInsert(arg) {
        super.onSubModelInsert(arg);
        assert.equal(arg.iModel, iModelDb);
        assert.equal(arg.subModelProps.classFullName, TestFuncModel.classFullName);
    }
    static onSubModelInserted(arg) {
        super.onSubModelInserted(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onSubModelDelete(arg) {
        super.onSubModelDelete(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onSubModelDeleted(arg) {
        super.onSubModelDeleted(arg);
        assert.equal(arg.iModel, iModelDb);
    }
}
/** for testing `Model.onXxx` methods */
class TestFuncModel extends FunctionalModel {
    static get className() { return "TestFuncModel"; }
    static dontDelete = "";
    static onInsert(arg) {
        super.onInsert(arg);
        assert.equal(arg.iModel, iModelDb);
        assert.equal(arg.props.classFullName, this.classFullName);
    }
    static onInserted(arg) {
        super.onInserted(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onUpdate(arg) {
        super.onUpdate(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onUpdated(arg) {
        super.onUpdated(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onDelete(arg) {
        super.onDelete(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onDeleted(arg) {
        super.onDeleted(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onInsertElement(arg) {
        super.onInsertElement(arg);
        assert.equal(arg.iModel, iModelDb);
        if (arg.elementProps.code.value === "badval")
            throw new Error("bad element");
    }
    static onInsertedElement(arg) {
        super.onInsertedElement(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onUpdateElement(arg) {
        super.onUpdateElement(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onUpdatedElement(arg) {
        super.onUpdatedElement(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onDeleteElement(arg) {
        super.onDeleteElement(arg);
        assert.equal(arg.iModel, iModelDb);
        if (arg.elementId === this.dontDelete)
            throw new Error("dont delete my element");
    }
    static onDeletedElement(arg) {
        super.onDeletedElement(arg);
        assert.equal(arg.iModel, iModelDb);
    }
}
/** for testing `Element.onXxx` methods */
class Breakdown extends FunctionalBreakdownElement {
    static get className() { return "Breakdown"; }
    static dontDeleteChild = "";
    static onInsert(arg) {
        arg.props.userLabel = insertedLabel;
        super.onInsert(arg);
        assert.equal(arg.iModel, iModelDb);
        assert.equal(arg.props.classFullName, this.classFullName);
    }
    static onInserted(arg) {
        super.onInserted(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onUpdate(arg) {
        arg.props.userLabel = updatedLabel;
        super.onUpdate(arg);
        assert.equal(arg.iModel, iModelDb);
        assert.equal(arg.props.classFullName, this.classFullName);
    }
    static onUpdated(arg) {
        super.onUpdated(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onDelete(arg) {
        super.onDelete(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onDeleted(arg) {
        super.onDeleted(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onChildDelete(arg) {
        super.onChildDelete(arg);
        assert.equal(arg.iModel, iModelDb);
        if (arg.childId === this.dontDeleteChild)
            throw new Error("dont delete my child");
    }
    static onChildDeleted(arg) {
        super.onChildDeleted(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onChildInsert(arg) {
        super.onChildInsert(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onChildInserted(arg) {
        super.onChildInserted(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onChildUpdate(arg) {
        super.onChildUpdate(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onChildUpdated(arg) {
        super.onChildUpdated(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onChildAdd(arg) {
        super.onChildAdd(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onChildAdded(arg) {
        super.onChildAdded(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onChildDrop(arg) {
        super.onChildDrop(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onChildDropped(arg) {
        super.onChildDropped(arg);
        assert.equal(arg.iModel, iModelDb);
    }
}
/** for testing `ElementAspect.onXxx` methods */
class TestFuncAspect extends ElementUniqueAspect {
    static get className() { return "TestFuncAspect"; }
    static expectedVal = "";
    static onInsert(arg) {
        super.onInsert(arg);
        assert.equal(arg.iModel, iModelDb);
        assert.equal(arg.props.strProp, this.expectedVal);
    }
    static onInserted(arg) {
        super.onInserted(arg);
        assert.equal(arg.iModel, iModelDb);
        assert.equal(arg.props.strProp, this.expectedVal);
    }
    static onUpdate(arg) {
        super.onUpdate(arg);
        assert.equal(arg.iModel, iModelDb);
        assert.equal(arg.props.strProp, this.expectedVal);
    }
    static onUpdated(arg) {
        super.onUpdated(arg);
        assert.equal(arg.iModel, iModelDb);
        assert.equal(arg.props.strProp, this.expectedVal);
    }
    static onDelete(arg) {
        super.onDelete(arg);
        assert.equal(arg.iModel, iModelDb);
    }
    static onDeleted(arg) {
        super.onDeleted(arg);
        assert.equal(arg.iModel, iModelDb);
    }
}
class Component extends FunctionalComponentElement {
    static get className() { return "Component"; }
}
describe("Functional Domain", () => {
    afterEach(() => {
        sinonRestore();
    });
    it("should populate FunctionalModel and test Element, Model, and ElementAspect callbacks", async () => {
        iModelDb = StandaloneDb.createEmpty(IModelTestUtils.prepareOutputFile("FunctionalDomain", "FunctionalTest.bim"), {
            rootSubject: { name: "FunctionalTest", description: "Test of the Functional domain schema." },
            client: "Functional",
            globalOrigin: { x: 0, y: 0 },
            projectExtents: { low: { x: -500, y: -500, z: -50 }, high: { x: 500, y: 500, z: 50 } },
            guid: Guid.createValue(),
        });
        iModelDb[_nativeDb].resetBriefcaseId(100);
        // Import the Functional schema
        FunctionalSchema.registerSchema();
        Schemas.registerSchema(TestSchema);
        // eslint-disable-next-line @typescript-eslint/naming-convention
        ClassRegistry.registerModule({ TestFuncPartition, TestFuncModel, Breakdown, Component, TestFuncAspect }, TestSchema);
        await FunctionalSchema.importSchema(iModelDb);
        let commits = 0;
        let committed = 0;
        const elements = iModelDb.elements;
        const dropCommit = iModelDb.txns.onCommit.addListener(() => commits++);
        const dropCommitted = iModelDb.txns.onCommitted.addListener(() => committed++);
        iModelDb[_nativeDb].saveChanges("Import Functional schema");
        assert.equal(commits, 1);
        assert.equal(committed, 1);
        dropCommit();
        dropCommitted();
        IModelTestUtils.flushTxns(iModelDb); // importSchema below will fail if this is not called to flush local changes
        await iModelDb.importSchemas([join(KnownTestLocations.assetsDir, "TestFunctional.ecschema.xml")]);
        assert.equal(commits, 1);
        assert.equal(committed, 1);
        const txn = new EditTxn(iModelDb, "functional domain test");
        txn.start();
        const testChannelKey1 = "channel 1 for tests";
        const testChannelKey2 = "channel 2 for tests";
        function testChannel(channelKey, fn, spies) {
            iModelDb.channels.removeAllowedChannel(channelKey);
            expect(fn).throws("not allowed");
            iModelDb.channels.addAllowedChannel(channelKey);
            spies.forEach((s) => s.resetHistory());
            return fn();
        }
        const spy = {
            model: {
                onInsert: sinonSpy(TestFuncModel, "onInsert"),
                onInserted: sinonSpy(TestFuncModel, "onInserted"),
                onUpdate: sinonSpy(TestFuncModel, "onUpdate"),
                onUpdated: sinonSpy(TestFuncModel, "onUpdated"),
                onDelete: sinonSpy(TestFuncModel, "onDelete"),
                onDeleted: sinonSpy(TestFuncModel, "onDeleted"),
                onInsertElement: sinonSpy(TestFuncModel, "onInsertElement"),
                onInsertedElement: sinonSpy(TestFuncModel, "onInsertedElement"),
                onUpdateElement: sinonSpy(TestFuncModel, "onUpdateElement"),
                onUpdatedElement: sinonSpy(TestFuncModel, "onUpdatedElement"),
                onDeleteElement: sinonSpy(TestFuncModel, "onDeleteElement"),
                onDeletedElement: sinonSpy(TestFuncModel, "onDeletedElement"),
            },
            partition: {
                onSubModelInsert: sinonSpy(TestFuncPartition, "onSubModelInsert"),
                onSubModelInserted: sinonSpy(TestFuncPartition, "onSubModelInserted"),
                onSubModelDelete: sinonSpy(TestFuncPartition, "onSubModelDelete"),
                onSubModelDeleted: sinonSpy(TestFuncPartition, "onSubModelDeleted"),
            },
            breakdown: {
                onInsert: sinonSpy(Breakdown, "onInsert"),
                onInserted: sinonSpy(Breakdown, "onInserted"),
                onUpdate: sinonSpy(Breakdown, "onUpdate"),
                onUpdated: sinonSpy(Breakdown, "onUpdated"),
                onDelete: sinonSpy(Breakdown, "onDelete"),
                onDeleted: sinonSpy(Breakdown, "onDeleted"),
                onChildDelete: sinonSpy(Breakdown, "onChildDelete"),
                onChildDeleted: sinonSpy(Breakdown, "onChildDeleted"),
                onChildInsert: sinonSpy(Breakdown, "onChildInsert"),
                onChildInserted: sinonSpy(Breakdown, "onChildInserted"),
                onChildUpdate: sinonSpy(Breakdown, "onChildUpdate"),
                onChildUpdated: sinonSpy(Breakdown, "onChildUpdated"),
                onChildAdd: sinonSpy(Breakdown, "onChildAdd"),
                onChildAdded: sinonSpy(Breakdown, "onChildAdded"),
                onChildDrop: sinonSpy(Breakdown, "onChildDrop"),
                onChildDropped: sinonSpy(Breakdown, "onChildDropped"),
            },
            aspect: {
                onInsert: sinonSpy(TestFuncAspect, "onInsert"),
                onInserted: sinonSpy(TestFuncAspect, "onInserted"),
                onUpdate: sinonSpy(TestFuncAspect, "onUpdate"),
                onUpdated: sinonSpy(TestFuncAspect, "onUpdated"),
                onDelete: sinonSpy(TestFuncAspect, "onDelete"),
                onDeleted: sinonSpy(TestFuncAspect, "onDeleted"),
            },
        };
        assert.equal(iModelDb.channels.queryChannelRoot(ChannelControl.sharedChannelName), IModel.rootSubjectId);
        const codeSpec = CodeSpec.create(iModelDb, "Test Functional Elements", CodeScopeSpec.Type.Model);
        iModelDb.codeSpecs.insert(txn, codeSpec);
        assert.isTrue(Id64.isValidId64(codeSpec.id));
        // create a channel for all elements in this test
        assert.isUndefined(iModelDb.channels.queryChannelRoot(testChannelKey1));
        const subject1Id = iModelDb.channels.insertChannelSubject({ subjectName: "Test Functional Subject", channelKey: testChannelKey1, txn });
        assert.equal(iModelDb.channels.queryChannelRoot(testChannelKey1), subject1Id);
        const partitionCode = FunctionalPartition.createCode(iModelDb, subject1Id, "Test Functional Model");
        const partitionProps = {
            classFullName: TestFuncPartition.classFullName, model: IModel.repositoryModelId,
            parent: new SubjectOwnsPartitionElements(subject1Id), code: partitionCode,
        };
        iModelDb.channels.addAllowedChannel(testChannelKey1);
        let partitionId = txn.insertElement(partitionProps);
        const modelId = testChannel(testChannelKey1, () => txn.insertModel({ classFullName: TestFuncModel.classFullName, modeledElement: { id: partitionId } }), [spy.model.onInsert]);
        assert.isTrue(Id64.isValidId64(modelId));
        assert.isTrue(spy.model.onInsert.calledOnce);
        assert.isTrue(spy.model.onInserted.calledOnce);
        assert.equal(spy.model.onInserted.getCall(0).args[0].id, modelId);
        assert.isFalse(spy.model.onUpdate.called, "model insert should not call onUpdate");
        assert.isFalse(spy.model.onUpdated.called, "model insert should not call onUpdated");
        assert.isTrue(spy.partition.onSubModelInsert.calledOnce);
        assert.isTrue(spy.partition.onSubModelInserted.calledOnce);
        assert.equal(spy.partition.onSubModelInserted.getCall(0).args[0].subModelId, modelId, "Element.onSubModelInserted should have correct subModelId");
        expect(() => iModelDb.channels.insertChannelSubject({ subjectName: "Test Functional Subject 2", channelKey: testChannelKey1, txn })).to.throw(`Channel ${testChannelKey1} root already exist`);
        const subject2Id = iModelDb.channels.insertChannelSubject({ subjectName: "Test Functional Subject 2", channelKey: testChannelKey2, txn });
        iModelDb.channels.addAllowedChannel(testChannelKey2);
        expect(() => iModelDb.channels.insertChannelSubject({ subjectName: "Nested Subject", parentSubjectId: subject2Id, channelKey: "nested channel", txn })).to.throw("may not nest");
        partitionProps.code.value = "Test Func 2";
        partitionProps.parent = new SubjectOwnsPartitionElements(subject2Id);
        partitionId = txn.insertElement(partitionProps);
        const modelId2 = txn.insertModel({ classFullName: TestFuncModel.classFullName, modeledElement: { id: partitionId } });
        assert.isTrue(Id64.isValidId64(modelId2));
        assert.equal(spy.model.onInserted.getCall(1).args[0].id, modelId2, "second insert should set new id");
        assert.equal(spy.model.onInsert.callCount, 2);
        assert.equal(spy.model.onInserted.callCount, 2);
        assert.equal(spy.partition.onSubModelInserted.getCall(1).args[0].subModelId, modelId2, "Element.onSubModelInserted should have correct subModelId");
        const model2 = iModelDb.models.getModel(modelId2);
        testChannel(testChannelKey2, () => model2.update(txn), []);
        assert.equal(spy.model.onUpdated.getCall(0).args[0].id, modelId2);
        assert.equal(spy.model.onUpdate.callCount, 2);
        assert.equal(spy.model.onUpdated.callCount, 1);
        testChannel(testChannelKey2, () => model2.delete(txn), [spy.model.onDelete, spy.partition.onSubModelDelete]);
        assert.isTrue(spy.model.onDelete.calledOnce);
        assert.isTrue(spy.model.onDeleted.calledOnce);
        assert.equal(spy.model.onDeleted.getCall(0).args[0].id, modelId2);
        assert.isTrue(spy.partition.onSubModelDelete.calledOnce);
        assert.isTrue(spy.partition.onSubModelDeleted.calledOnce);
        assert.equal(spy.partition.onSubModelDeleted.getCall(0).args[0].subModelId, modelId2);
        const breakdownProps = { classFullName: Breakdown.classFullName, model: modelId, code: { spec: codeSpec.id, scope: modelId, value: "Breakdown1" } };
        const breakdownId = testChannel(testChannelKey1, () => txn.insertElement(breakdownProps), [spy.model.onInsertElement, spy.breakdown.onInsert]);
        assert.isTrue(Id64.isValidId64(breakdownId));
        assert.isTrue(spy.model.onInsertElement.calledOnce);
        assert.isTrue(spy.model.onInsertedElement.calledOnce);
        assert.equal(spy.model.onInsertedElement.getCall(0).args[0].elementId, breakdownId);
        assert.isTrue(spy.breakdown.onInsert.calledOnce);
        assert.isTrue(spy.breakdown.onInserted.calledOnce);
        assert.equal(spy.breakdown.onInserted.getCall(0).args[0].id, breakdownId);
        assert.equal(spy.breakdown.onInsert.getCall(0).args[0].props, breakdownProps);
        const breakdown2Props = { classFullName: Breakdown.classFullName, model: modelId, code: { spec: codeSpec.id, scope: modelId, value: "badval" } };
        // TestFuncModel.onInsertElement throws for this code.value
        expect(() => txn.insertElement(breakdown2Props)).to.throw("bad element");
        breakdown2Props.code.value = "Breakdown2";
        breakdown2Props.userLabel = "start label"; // gets overwritten in `onInsert`
        const bd2 = txn.insertElement(breakdown2Props);
        const aspect = { classFullName: TestFuncAspect.classFullName, element: new ElementOwnsUniqueAspect(bd2), strProp: "prop 1" };
        TestFuncAspect.expectedVal = aspect.strProp;
        testChannel(testChannelKey1, () => txn.insertAspect(aspect), [spy.aspect.onInsert]);
        assert.isTrue(spy.aspect.onInsert.calledOnce);
        assert.isTrue(spy.aspect.onInserted.calledOnce);
        assert.isFalse(spy.aspect.onUpdate.called);
        assert.isFalse(spy.aspect.onUpdated.called);
        assert.equal(spy.aspect.onInserted.getCall(0).args[0].props.element.id, bd2, "elemId from ElementAspect.onInserted");
        aspect.strProp = "prop 2";
        TestFuncAspect.expectedVal = aspect.strProp;
        testChannel(testChannelKey1, () => txn.updateAspect(aspect), [spy.aspect.onUpdate]);
        assert.equal(spy.aspect.onInsert.callCount, 1, "ElementAspect.onInsert should not be called on update");
        assert.equal(spy.aspect.onInserted.callCount, 1, "ElementAspect.onInserted should should not be called on update");
        assert.equal(spy.aspect.onUpdate.callCount, 1);
        assert.equal(spy.aspect.onUpdated.callCount, 1);
        assert.equal(spy.aspect.onUpdated.getCall(0).args[0].props.element.id, bd2, "from ElementAspect.onUpdated");
        const aspects = elements.getAspects(bd2, TestFuncAspect.classFullName);
        assert.equal(aspects.length, 1);
        testChannel(testChannelKey1, () => txn.deleteAspect(aspects[0].id), [spy.aspect.onDelete]);
        assert.equal(spy.aspect.onDelete.callCount, 1);
        assert.equal(spy.aspect.onDeleted.callCount, 1);
        assert.equal(spy.aspect.onDelete.getCall(0).args[0].aspectId, aspects[0].id);
        assert.equal(spy.aspect.onDeleted.getCall(0).args[0].aspectId, aspects[0].id);
        let bd2el = elements.getElement(bd2);
        assert.equal(bd2el.userLabel, insertedLabel, "label was modified by onInsert");
        expect(iModelDb.channels.getChannelKey(bd2)).equals(testChannelKey1);
        bd2el.userLabel = "nothing";
        testChannel(testChannelKey1, () => bd2el.update(txn), [spy.breakdown.onUpdate, spy.breakdown.onUpdated]);
        bd2el = elements.getElement(bd2);
        assert.equal(bd2el.userLabel, updatedLabel, "label was modified in onUpdate");
        assert.equal(spy.breakdown.onUpdate.callCount, 1);
        assert.equal(spy.breakdown.onUpdated.callCount, 1);
        assert.equal(spy.breakdown.onUpdate.getCall(0).args[0].props.id, bd2);
        assert.equal(spy.breakdown.onUpdated.getCall(0).args[0].id, bd2);
        testChannel(testChannelKey1, () => bd2el.delete(txn), [spy.breakdown.onDelete]);
        assert.equal(spy.breakdown.onDelete.callCount, 1);
        assert.equal(spy.breakdown.onDeleted.callCount, 1);
        const deleteArg = spy.breakdown.onDelete.getCall(0).args[0];
        assert.equal(deleteArg.id, bd2);
        assert.equal(deleteArg.model, bd2el.model);
        assert.equal(deleteArg.federationGuid, bd2el.federationGuid);
        assert.equal(spy.breakdown.onDeleted.getCall(0).args[0].id, bd2);
        const breakdown3Props = {
            classFullName: Breakdown.classFullName,
            model: modelId,
            code: { spec: codeSpec.id, scope: modelId, value: "bd3" },
        };
        const bd3 = txn.insertElement(breakdown3Props);
        const componentProps = {
            classFullName: Component.classFullName,
            model: modelId,
            parent: { id: breakdownId, relClassName: ElementOwnsChildElements.classFullName },
            code: { spec: codeSpec.id, scope: modelId, value: "Component1" },
        };
        const componentId = testChannel(testChannelKey1, () => txn.insertElement(componentProps), []);
        assert.isTrue(Id64.isValidId64(componentId));
        assert.equal(spy.breakdown.onChildInserted.callCount, 1);
        assert.equal(spy.breakdown.onChildInserted.getCall(0).args[0].childId, componentId);
        // test model and element callbacks for updateElement
        const component1 = elements.getElement(componentId);
        testChannel(testChannelKey1, () => component1.update(txn), [spy.model.onUpdateElement, spy.model.onUpdatedElement]);
        assert.equal(spy.model.onUpdateElement.callCount, 1);
        assert.equal(spy.model.onUpdatedElement.callCount, 1);
        assert.equal(spy.model.onUpdatedElement.getCall(0).args[0].elementId, componentId);
        assert.equal(spy.breakdown.onChildUpdate.callCount, 1);
        assert.equal(spy.breakdown.onChildUpdated.callCount, 1);
        assert.equal(spy.breakdown.onChildUpdate.getCall(0).args[0].parentId, breakdownId);
        assert.equal(spy.breakdown.onChildUpdated.getCall(0).args[0].childId, componentId);
        componentProps.code.value = "comp2";
        const comp2 = txn.insertElement(componentProps);
        assert.equal(spy.breakdown.onChildInserted.callCount, 2);
        assert.equal(spy.breakdown.onChildInserted.getCall(1).args[0].childId, comp2);
        const el2 = elements.getElement(comp2);
        spy.model.onDeleteElement.resetHistory();
        spy.model.onDeletedElement.resetHistory();
        TestFuncModel.dontDelete = comp2; // block deletion through model
        expect(() => el2.delete(txn)).to.throw("dont delete my element");
        TestFuncModel.dontDelete = ""; // allow deletion through model
        Breakdown.dontDeleteChild = comp2; // but block through parent
        expect(() => el2.delete(txn)).to.throw("dont delete my child"); // nope
        assert.equal(spy.model.onDeleteElement.callCount, 2, "Model.onElementDelete gets called even though element is not really deleted");
        assert.equal(spy.model.onDeletedElement.callCount, 0, "make sure Model.onElementDeleted did not get called");
        Breakdown.dontDeleteChild = ""; // now fully allow delete
        el2.delete(txn);
        assert.equal(spy.model.onDeleteElement.callCount, 3, "Model.onElementDelete should be called again");
        assert.equal(spy.model.onDeletedElement.callCount, 1);
        assert.equal(spy.model.onDeletedElement.getCall(0).args[0].elementId, comp2);
        assert.equal(spy.breakdown.onChildDeleted.callCount, 1);
        assert.equal(spy.breakdown.onChildDeleted.getCall(0).args[0].childId, comp2);
        // next we make sure that changing the parent of an element calls the "onChildAdd/Drop/Added/Dropped" callbacks.
        // To do this we switch a component's parent from "breakDownId" to "bc3"
        componentProps.parent.id = bd3;
        const comp3 = txn.insertElement(componentProps);
        const compEl3 = elements.getElementProps(comp3);
        compEl3.parent.id = breakdownId;
        testChannel(testChannelKey1, () => txn.updateElement(compEl3), []);
        assert.equal(spy.breakdown.onChildAdd.callCount, 1);
        assert.equal(spy.breakdown.onChildAdd.getCall(0).args[0].parentId, breakdownId);
        assert.equal(spy.breakdown.onChildAdd.getCall(0).args[0].childProps.id, comp3);
        assert.equal(spy.breakdown.onChildDrop.callCount, 1);
        assert.equal(spy.breakdown.onChildDrop.getCall(0).args[0].parentId, bd3);
        assert.equal(spy.breakdown.onChildDrop.getCall(0).args[0].childId, comp3);
        assert.equal(spy.breakdown.onChildAdded.callCount, 1);
        assert.equal(spy.breakdown.onChildAdded.getCall(0).args[0].parentId, breakdownId);
        assert.equal(spy.breakdown.onChildAdded.getCall(0).args[0].childId, comp3);
        assert.equal(spy.breakdown.onChildDropped.callCount, 1);
        assert.equal(spy.breakdown.onChildDropped.getCall(0).args[0].parentId, bd3);
        assert.equal(spy.breakdown.onChildDropped.getCall(0).args[0].childId, comp3);
        txn.saveChanges("Insert Functional elements");
        // unregister test schema to make sure it will throw exceptions if it is not present (since it has the "SchemaHasBehavior" custom attribute)
        Schemas.unregisterSchema(TestSchema.schemaName);
        const errMsg = "Schema [TestFunctional] not registered, but is marked with SchemaHasBehavior";
        expect(() => txn.deleteElement(breakdownId)).to.throw(errMsg);
        assert.isDefined(elements.getElement(breakdownId), "should not have been deleted");
        expect(() => txn.updateElement(breakdownProps)).to.throw(errMsg);
        breakdownProps.code.value = "Breakdown 2";
        expect(() => txn.insertElement(breakdownProps)).to.throw(errMsg);
        iModelDb.close();
    });
});
//# sourceMappingURL=FunctionalDomain.test.js.map