"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const node_assert_1 = require("node:assert");
const localReference_js_1 = require("../localReference.js");
const mergeTree_js_1 = require("../mergeTree.js");
const mergeTreeTracking_js_1 = require("../mergeTreeTracking.js");
const ops_js_1 = require("../ops.js");
const referencePositions_js_1 = require("../referencePositions.js");
const segmentInfos_js_1 = require("../segmentInfos.js");
const sequencePlace_js_1 = require("../sequencePlace.js");
const textSegment_js_1 = require("../textSegment.js");
const clientTestHelper_js_1 = require("./clientTestHelper.js");
const testClient_js_1 = require("./testClient.js");
const testClientLogger_js_1 = require("./testClientLogger.js");
const testUtils_js_1 = require("./testUtils.js");
function getSlideOnRemoveReferencePosition(client, pos, op) {
    let segoff = client.getContainingSegment(pos, {
        referenceSequenceNumber: op.referenceSequenceNumber,
        clientId: op.clientId,
    });
    segoff = (0, mergeTree_js_1.getSlideToSegoff)(segoff);
    return segoff;
}
describe("MergeTree.Client", () => {
    beforeEach(() => {
        (0, localReference_js_1.setValidateRefCount)(testUtils_js_1.validateRefCount);
    });
    afterEach(() => {
        (0, localReference_js_1.setValidateRefCount)(undefined);
    });
    it("Remove segment of non-sliding local reference", () => {
        const client1 = new testClient_js_1.TestClient();
        const client2 = new testClient_js_1.TestClient();
        client1.startOrUpdateCollaboration("1");
        client2.startOrUpdateCollaboration("2");
        let seq = 0;
        for (let i = 0; i < 5; i++) {
            const insert = client1.makeOpMessage(client1.insertTextLocal(client1.getLength(), i.toString()), ++seq);
            insert.minimumSequenceNumber = seq - 1;
            client1.applyMsg(insert);
            client2.applyMsg(insert);
        }
        const segInfo = client1.getContainingSegment(2);
        const c1LocalRef = client1.createLocalReferencePosition(segInfo.segment, segInfo.offset, ops_js_1.ReferenceType.Simple, undefined);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), 2, "create position");
        const remove = client2.makeOpMessage(client2.removeRangeLocal(2, 3), ++seq);
        remove.minimumSequenceNumber = seq - 1;
        client1.applyMsg(remove);
        client2.applyMsg(remove);
        // this only works because zamboni hasn't run yet
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), referencePositions_js_1.DetachedReferencePosition, "after remove");
        // this will force Zamboni to run
        for (let i = 0; i < 5; i++) {
            const insert = client1.makeOpMessage(client1.insertTextLocal(client1.getLength(), i.toString()), ++seq);
            insert.minimumSequenceNumber = seq - 1;
            client1.applyMsg(insert);
            client2.applyMsg(insert);
        }
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), referencePositions_js_1.DetachedReferencePosition, "after zamboni");
    });
    it("Remove segment of sliding local reference", () => {
        const client1 = new testClient_js_1.TestClient();
        const client2 = new testClient_js_1.TestClient();
        client1.startOrUpdateCollaboration("1");
        client2.startOrUpdateCollaboration("2");
        let seq = 0;
        for (let i = 0; i < 5; i++) {
            const insert = client1.makeOpMessage(client1.insertTextLocal(client1.getLength(), i.toString()), ++seq);
            insert.minimumSequenceNumber = seq - 1;
            client1.applyMsg(insert);
            client2.applyMsg(insert);
        }
        const segInfo = client1.getContainingSegment(2);
        const c1LocalRef = client1.createLocalReferencePosition(segInfo.segment, segInfo.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), 2);
        const remove = client2.makeOpMessage(client2.removeRangeLocal(2, 3), ++seq);
        remove.minimumSequenceNumber = seq - 1;
        client1.applyMsg(remove);
        client2.applyMsg(remove);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), 2);
        for (let i = 0; i < 5; i++) {
            const insert = client1.makeOpMessage(client1.insertTextLocal(client1.getLength(), i.toString()), ++seq);
            insert.minimumSequenceNumber = seq - 1;
            client1.applyMsg(insert);
            client2.applyMsg(insert);
        }
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), 2);
    });
    it("Remove segments to end with sliding local reference", () => {
        const client1 = new testClient_js_1.TestClient();
        const client2 = new testClient_js_1.TestClient();
        client1.startOrUpdateCollaboration("1");
        client2.startOrUpdateCollaboration("2");
        let seq = 0;
        for (let i = 0; i < 5; i++) {
            const insert = client1.makeOpMessage(client1.insertTextLocal(client1.getLength(), i.toString()), ++seq);
            insert.minimumSequenceNumber = seq - 1;
            client1.applyMsg(insert);
            client2.applyMsg(insert);
        }
        const segInfo = client1.getContainingSegment(2);
        const c1LocalRef = client1.createLocalReferencePosition(segInfo.segment, segInfo.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), 2);
        const remove = client2.makeOpMessage(client2.removeRangeLocal(2, client2.getLength()), ++seq);
        remove.minimumSequenceNumber = seq - 1;
        client1.applyMsg(remove);
        client2.applyMsg(remove);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), client2.getLength() - 1);
    });
    describe("Slides on first ack of remove/obliterate when both are present", () => {
        // This suite demonstrates that even when we decide we don't need to emit events for segments going away (since
        // they have already been removed locally), we still need to slide local references that are on those segments.
        // Failure to do so can break eventual consistency in features relying on SlideOnRemove references (such as
        // IntervalCollection).
        it("Remove then obliterate", () => {
            const helper = new clientTestHelper_js_1.ClientTestHelper({
                mergeTreeEnableSidedObliterate: true,
            });
            const { A, B } = helper.clients;
            helper.insertText("A", 0, "AxxC");
            helper.processAllOps();
            const [localRefA, localRefB] = [A, B].map((client) => {
                const { segment, offset } = client.getContainingSegment(2);
                (0, node_assert_1.strict)(segment !== undefined && offset !== undefined);
                return client.createLocalReferencePosition(segment, offset, ops_js_1.ReferenceType.SlideOnRemove, undefined, localReference_js_1.SlidingPreference.BACKWARD);
            });
            helper.removeRange("A", 1, 3);
            helper.insertText("A", 1, "should not be placed here");
            helper.obliterateRange("B", { pos: 1, side: sequencePlace_js_1.Side.Before }, { pos: 3, side: sequencePlace_js_1.Side.Before });
            helper.processAllOps();
            node_assert_1.strict.equal(A.localReferencePositionToPosition(localRefA), 0);
            node_assert_1.strict.equal(B.localReferencePositionToPosition(localRefB), 0);
            helper.logger.validate({ baseText: "Ashould not be placed hereC" });
        });
        it("Obliterate then remove", () => {
            const helper = new clientTestHelper_js_1.ClientTestHelper({
                mergeTreeEnableSidedObliterate: true,
            });
            const { A, B } = helper.clients;
            helper.insertText("A", 0, "AxxC");
            helper.processAllOps();
            const [localRefA, localRefB] = [A, B].map((client) => {
                const { segment, offset } = client.getContainingSegment(2);
                (0, node_assert_1.strict)(segment !== undefined && offset !== undefined);
                return client.createLocalReferencePosition(segment, offset, ops_js_1.ReferenceType.SlideOnRemove, undefined, localReference_js_1.SlidingPreference.BACKWARD);
            });
            helper.obliterateRange("B", { pos: 1, side: sequencePlace_js_1.Side.Before }, { pos: 3, side: sequencePlace_js_1.Side.Before });
            helper.insertText("B", 1, "should not be placed here");
            helper.removeRange("A", 1, 3);
            helper.processAllOps();
            node_assert_1.strict.equal(A.localReferencePositionToPosition(localRefA), 0);
            node_assert_1.strict.equal(B.localReferencePositionToPosition(localRefB), 0);
            helper.logger.validate({ baseText: "Ashould not be placed hereC" });
        });
    });
    it("Remove segments from end with sliding local reference", () => {
        const client1 = new testClient_js_1.TestClient();
        client1.startOrUpdateCollaboration("1");
        let seq = 0;
        const insert = client1.makeOpMessage(client1.insertTextLocal(0, "ABCD"), ++seq);
        insert.minimumSequenceNumber = seq - 1;
        client1.applyMsg(insert);
        const segInfo = client1.getContainingSegment(3);
        const c1LocalRef = client1.createLocalReferencePosition(segInfo.segment, segInfo.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), 3, "ref created");
        const remove1 = client1.makeOpMessage(client1.removeRangeLocal(3, 4), ++seq);
        remove1.minimumSequenceNumber = seq - 1;
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), 3, "after remove");
        const remove2 = client1.makeOpMessage(client1.removeRangeLocal(1, 3), ++seq);
        remove2.minimumSequenceNumber = seq - 1;
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), 1, "after second remove");
        client1.applyMsg(remove1);
        client1.applyMsg(remove2);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), 0, "ops applied");
    });
    it("getSlideOnRemoveReferencePosition", () => {
        const client1 = new testClient_js_1.TestClient();
        const client2 = new testClient_js_1.TestClient();
        client1.startOrUpdateCollaboration("1");
        client2.startOrUpdateCollaboration("2");
        let seq = 0;
        const insert1 = client1.makeOpMessage(client1.insertTextLocal(0, "XYZ"), ++seq);
        client1.applyMsg(insert1);
        const insert2 = client1.makeOpMessage(client1.insertTextLocal(0, "ABC"), ++seq);
        client1.applyMsg(insert2);
        // Position depends on op
        const createReference1 = client2.makeOpMessage({ type: ops_js_1.MergeTreeDeltaType.INSERT }, ++seq, insert1.sequenceNumber);
        let segoff = getSlideOnRemoveReferencePosition(client1, 1, createReference1);
        (0, node_assert_1.strict)(segoff.segment);
        node_assert_1.strict.equal(client1.getPosition(segoff.segment), 3);
        node_assert_1.strict.equal(segoff.offset, 1);
        const createReference2 = client2.makeOpMessage({ type: ops_js_1.MergeTreeDeltaType.INSERT }, ++seq, insert2.sequenceNumber);
        segoff = getSlideOnRemoveReferencePosition(client1, 2, createReference2);
        (0, node_assert_1.strict)(segoff.segment);
        node_assert_1.strict.equal(client1.getPosition(segoff.segment), 0);
        node_assert_1.strict.equal(segoff.offset, 2);
        // On a removed, unacked segment
        let remove = client1.makeOpMessage(client1.removeRangeLocal(2, 5), ++seq);
        segoff = getSlideOnRemoveReferencePosition(client1, 3, createReference2);
        (0, node_assert_1.strict)(segoff.segment);
        node_assert_1.strict.notEqual((0, segmentInfos_js_1.toRemovalInfo)(segoff.segment), undefined);
        node_assert_1.strict.equal(client1.getPosition(segoff.segment), 2);
        node_assert_1.strict.equal(segoff.offset, 0);
        // Slid from a removed, acked segment
        client1.applyMsg(remove);
        segoff = getSlideOnRemoveReferencePosition(client1, 3, createReference2);
        (0, node_assert_1.strict)(segoff.segment);
        node_assert_1.strict.equal((0, segmentInfos_js_1.toRemovalInfo)(segoff.segment), undefined);
        node_assert_1.strict.equal(client1.getPosition(segoff.segment), 2);
        node_assert_1.strict.equal(segoff.offset, 0);
        // On a removed, unacked segment, end of string
        remove = client1.makeOpMessage(client1.removeRangeLocal(2, 3), ++seq);
        segoff = getSlideOnRemoveReferencePosition(client1, 3, createReference2);
        (0, node_assert_1.strict)(segoff.segment);
        node_assert_1.strict.notEqual((0, segmentInfos_js_1.toRemovalInfo)(segoff.segment), undefined);
        node_assert_1.strict.equal(client1.getPosition(segoff.segment), 2);
        node_assert_1.strict.equal(segoff.offset, 0);
        // Slid from a removed, acked segment, end of string
        client1.applyMsg(remove);
        segoff = getSlideOnRemoveReferencePosition(client1, 3, createReference2);
        (0, node_assert_1.strict)(segoff.segment);
        node_assert_1.strict.equal((0, segmentInfos_js_1.toRemovalInfo)(segoff.segment), undefined);
        node_assert_1.strict.equal(client1.getPosition(segoff.segment), 0);
        node_assert_1.strict.equal(segoff.offset, 1);
    });
    it("Remove all segments with sliding local reference", () => {
        const client1 = new testClient_js_1.TestClient();
        const client2 = new testClient_js_1.TestClient();
        client1.startOrUpdateCollaboration("1");
        client2.startOrUpdateCollaboration("2");
        let seq = 0;
        for (let i = 0; i < 5; i++) {
            const insert = client1.makeOpMessage(client1.insertTextLocal(client1.getLength(), i.toString()), ++seq);
            insert.minimumSequenceNumber = seq - 1;
            client1.applyMsg(insert);
            client2.applyMsg(insert);
        }
        const segInfo = client1.getContainingSegment(2);
        const c1LocalRef = client1.createLocalReferencePosition(segInfo.segment, segInfo.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), 2);
        const remove = client2.makeOpMessage(client2.removeRangeLocal(0, client2.getLength()), ++seq);
        remove.minimumSequenceNumber = seq - 1;
        client1.applyMsg(remove);
        client2.applyMsg(remove);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(c1LocalRef), referencePositions_js_1.DetachedReferencePosition);
        node_assert_1.strict.equal(c1LocalRef.getSegment(), undefined);
    });
    it("References can have offsets on removed segment", () => {
        const client1 = new testClient_js_1.TestClient();
        const client2 = new testClient_js_1.TestClient();
        client1.startOrUpdateCollaboration("1");
        client2.startOrUpdateCollaboration("2");
        let seq = 0;
        const insert1 = client1.makeOpMessage(client1.insertTextLocal(0, "ABCD"), ++seq);
        client1.applyMsg(insert1);
        client2.applyMsg(insert1);
        const segInfo1 = client1.getContainingSegment(1);
        const LocalRef1 = client1.createLocalReferencePosition(segInfo1.segment, segInfo1.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
        const segInfo3 = client1.getContainingSegment(3);
        const LocalRef2 = client1.createLocalReferencePosition(segInfo3.segment, segInfo3.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
        const insert2 = client1.makeOpMessage(client1.insertTextLocal(2, "XY"), ++seq);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(LocalRef1), 1);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(LocalRef2), 5);
        const c2SegInfo1 = client2.getContainingSegment(1);
        const c2SegInfo3 = client2.getContainingSegment(3);
        const remove = client2.makeOpMessage(client2.removeRangeLocal(0, client2.getLength()), ++seq);
        const c2LocalRef1 = client2.createLocalReferencePosition(c2SegInfo1.segment, c2SegInfo1.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
        const c2LocalRef2 = client2.createLocalReferencePosition(c2SegInfo3.segment, c2SegInfo3.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
        node_assert_1.strict.equal(client2.localReferencePositionToPosition(c2LocalRef1), 0);
        node_assert_1.strict.equal(client2.localReferencePositionToPosition(c2LocalRef2), 0);
        client1.applyMsg(insert2);
        client2.applyMsg(insert2);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(LocalRef1), 1);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(LocalRef2), 5);
        node_assert_1.strict.equal(client2.localReferencePositionToPosition(c2LocalRef1), 0);
        node_assert_1.strict.equal(client2.localReferencePositionToPosition(c2LocalRef2), 2);
        client1.applyMsg(remove);
        client2.applyMsg(remove);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(LocalRef1), 0);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(LocalRef2), 1);
        node_assert_1.strict.equal(client2.localReferencePositionToPosition(c2LocalRef1), 0);
        node_assert_1.strict.equal(client2.localReferencePositionToPosition(c2LocalRef2), 1);
    });
    it("Transient references can be created on removed segments", () => {
        const client1 = new testClient_js_1.TestClient();
        const client2 = new testClient_js_1.TestClient();
        client1.startOrUpdateCollaboration("1");
        client2.startOrUpdateCollaboration("2");
        let seq = 0;
        const insertOp = client1.makeOpMessage(client1.insertTextLocal(0, "ABCD"), ++seq);
        client1.applyMsg(insertOp);
        client2.applyMsg(insertOp);
        client1.removeRangeLocal(0, 2);
        const opFromBeforeRemovePerspective = client2.makeOpMessage(client2.insertTextLocal(3, "X"));
        const { segment, offset } = client1.getContainingSegment(0, {
            referenceSequenceNumber: opFromBeforeRemovePerspective.referenceSequenceNumber,
            clientId: opFromBeforeRemovePerspective.clientId,
        });
        (0, node_assert_1.strict)(segment && (0, segmentInfos_js_1.toRemovalInfo)(segment) !== undefined);
        const transientRef = client1.createLocalReferencePosition(segment, offset, ops_js_1.ReferenceType.Transient, {});
        node_assert_1.strict.equal(transientRef.getSegment(), segment);
        node_assert_1.strict.equal(transientRef.getOffset(), 0);
    });
    it("References can have offsets when slid to locally removed segment", () => {
        const client1 = new testClient_js_1.TestClient();
        const client2 = new testClient_js_1.TestClient();
        client1.startOrUpdateCollaboration("1");
        client2.startOrUpdateCollaboration("2");
        let seq = 0;
        const insert1 = client1.makeOpMessage(client1.insertTextLocal(0, "ABCDE"), ++seq);
        client1.applyMsg(insert1);
        client2.applyMsg(insert1);
        const segInfo = client1.getContainingSegment(4);
        const localRef = client1.createLocalReferencePosition(segInfo.segment, segInfo.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
        const createReference1 = client1.makeOpMessage({ type: ops_js_1.MergeTreeDeltaType.INSERT }, ++seq, insert1.sequenceNumber);
        const remove1 = client2.makeOpMessage(client2.removeRangeLocal(4, 5), ++seq);
        const insert2 = client1.makeOpMessage(client1.insertTextLocal(2, "XY"), ++seq);
        const remove2 = client2.makeOpMessage(client2.removeRangeLocal(1, 4), ++seq);
        const segoff = getSlideOnRemoveReferencePosition(client2, 4, createReference1);
        const c2LocalRef = client2.createLocalReferencePosition(segoff.segment, segoff.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(localRef), 6);
        node_assert_1.strict.equal(client2.localReferencePositionToPosition(c2LocalRef), 1);
        client1.applyMsg(remove1);
        client2.applyMsg(remove1);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(localRef), 5);
        node_assert_1.strict.equal(client2.localReferencePositionToPosition(c2LocalRef), 1);
        client1.applyMsg(insert2);
        client2.applyMsg(insert2);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(localRef), 5);
        node_assert_1.strict.equal(client2.getText(), "AXY");
        node_assert_1.strict.equal(client2.localReferencePositionToPosition(c2LocalRef), 3);
        client1.applyMsg(remove2);
        client2.applyMsg(remove2);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(localRef), 2);
        node_assert_1.strict.equal(client2.localReferencePositionToPosition(c2LocalRef), 2);
    });
    it("Split segment with no references and append to segment with references", () => {
        const clients = (0, testClientLogger_js_1.createClientsAtInitialState)({ initialState: "" }, "A", "B");
        const messages = [];
        let seq = 0;
        messages.push(clients.A.makeOpMessage(clients.A.insertTextLocal(0, "0123456789"), ++seq));
        // initialize the local reference collection on the segment, but keep it empty
        {
            const segInfo = clients.A.getContainingSegment(9);
            const segment = segInfo.segment;
            (0, node_assert_1.strict)(segment !== undefined && textSegment_js_1.TextSegment.is(segment));
            node_assert_1.strict.strictEqual(segment.text[segInfo.offset], "9");
            const localRef = clients.A.createLocalReferencePosition(segment, segInfo.offset, ops_js_1.ReferenceType.Simple, undefined);
            clients.A.removeLocalReferencePosition(localRef);
        }
        // split the segment
        messages.push(clients.A.makeOpMessage(clients.A.insertTextLocal(5, "ABCD"), ++seq));
        // add a local reference to the newly inserted segment that caused the split
        {
            const segInfo = clients.A.getContainingSegment(6);
            const segment = segInfo.segment;
            (0, node_assert_1.strict)(segment !== undefined && textSegment_js_1.TextSegment.is(segment));
            node_assert_1.strict.strictEqual(segment.text[segInfo.offset], "B");
            clients.A.createLocalReferencePosition(segment, segInfo.offset, ops_js_1.ReferenceType.Simple, undefined);
        }
        // apply all the ops
        while (messages.length > 0) {
            const msg = messages.shift();
            for (const c of clients.all)
                c.applyMsg(msg);
        }
        // regression: would fire 0x2be on zamboni during segment append
        for (const c of clients.all)
            c.updateMinSeq(seq);
    });
    describe("avoids removing StayOnRemove references on local + remote concurrent delete", () => {
        let client;
        let localRefA;
        let localRefB;
        let seq;
        beforeEach(() => {
            seq = 0;
            client = new testClient_js_1.TestClient();
            client.startOrUpdateCollaboration("1");
            client.enqueueMsg(client.makeOpMessage(client.insertTextLocal(0, "B"), ++seq));
            client.enqueueMsg(client.makeOpMessage(client.insertTextLocal(0, "A"), ++seq));
            client.applyMessages(2);
            node_assert_1.strict.equal(client.getText(), "AB");
            localRefA = client.createLocalReferencePosition(client.getContainingSegment(0).segment, 0, ops_js_1.ReferenceType.StayOnRemove, {});
            localRefB = client.createLocalReferencePosition(client.getContainingSegment(1).segment, 0, ops_js_1.ReferenceType.StayOnRemove, {});
            for (const ref of [localRefA, localRefB]) {
                ref.callbacks = {
                    beforeSlide: () => node_assert_1.strict.fail("Unexpected slide"),
                    afterSlide: () => node_assert_1.strict.fail("Unexpected slide"),
                };
            }
        });
        it("when references would slide forward", () => {
            const originalSegment = localRefA.getSegment();
            client.removeRangeLocal(0, 1);
            client.removeRangeRemote(0, 1, ++seq, seq - 1, "2");
            (0, node_assert_1.strict)(localRefA.getSegment() === originalSegment, "ref was removed");
        });
        it("when references would slide backward", () => {
            const originalSegment = localRefB.getSegment();
            client.removeRangeLocal(1, 2);
            client.removeRangeRemote(1, 2, ++seq, seq - 1, "2");
            (0, node_assert_1.strict)(localRefB.getSegment() === originalSegment, "ref was removed");
        });
        it("when references would slide off the string", () => {
            const originalSegment = localRefA.getSegment();
            client.removeRangeLocal(0, 2);
            client.removeRangeRemote(0, 2, ++seq, seq - 1, "2");
            (0, node_assert_1.strict)(localRefA.getSegment() === originalSegment, "ref was removed");
        });
    });
    it("slides to correct position with backward sliding preference", () => {
        const client1 = new testClient_js_1.TestClient();
        const client2 = new testClient_js_1.TestClient();
        client1.startOrUpdateCollaboration("1");
        client2.startOrUpdateCollaboration("2");
        let seq = 0;
        const insert1 = client1.makeOpMessage(client1.insertTextLocal(0, "abcXdef"), ++seq);
        client1.applyMsg(insert1);
        client2.applyMsg(insert1);
        const segInfo = client1.getContainingSegment(3);
        const localRef = client1.createLocalReferencePosition(segInfo.segment, segInfo.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined, localReference_js_1.SlidingPreference.BACKWARD);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(localRef), 3);
        const insert2 = client1.makeOpMessage(client1.insertTextLocal(4, "ghi"), ++seq);
        client1.applyMsg(insert2);
        client2.applyMsg(insert2);
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(localRef), 3);
        const remove1 = client1.makeOpMessage(client1.removeRangeLocal(1, 4), ++seq);
        client1.applyMsg(remove1);
        client2.applyMsg(remove1);
        node_assert_1.strict.equal(client1.getText(), "aghidef");
        node_assert_1.strict.equal(client1.localReferencePositionToPosition(localRef), 0);
        node_assert_1.strict.equal(client2.getText(), "aghidef");
        node_assert_1.strict.equal(client2.localReferencePositionToPosition(localRef), 0);
    });
    const tgCases = [
        {
            name: "when the ref is not in a tracking group",
            addRef: () => { },
        },
        {
            name: "when the ref is in a TrackingGroup",
            addRef: (ref) => {
                const tg = new mergeTreeTracking_js_1.TrackingGroup();
                tg.link(ref);
            },
        },
        {
            name: "when the ref is in an UnorderedTrackingGroup",
            addRef: (ref) => {
                const tg = new mergeTreeTracking_js_1.UnorderedTrackingGroup();
                tg.link(ref);
            },
        },
    ];
    describe("doesn't crash for remove ref then link to undefined", () => {
        for (const { name, addRef } of tgCases) {
            it(name, () => {
                const client1 = new testClient_js_1.TestClient();
                const client2 = new testClient_js_1.TestClient();
                client1.startOrUpdateCollaboration("1");
                client2.startOrUpdateCollaboration("2");
                let seq = 0;
                const insert1 = client1.makeOpMessage(client1.insertTextLocal(0, "abcdef"), ++seq);
                client1.applyMsg(insert1);
                client2.applyMsg(insert1);
                const segInfo = client1.getContainingSegment(3);
                (0, node_assert_1.strict)(segInfo.segment);
                const localRef = client1.createLocalReferencePosition(segInfo.segment, segInfo.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
                addRef(localRef);
                node_assert_1.strict.equal(localRef.getSegment(), segInfo.segment);
                (0, node_assert_1.strict)(segInfo.segment.localRefs);
                (0, node_assert_1.strict)(!segInfo.segment.localRefs.empty);
                segInfo.segment.localRefs.removeLocalRef(localRef);
                (0, node_assert_1.strict)(segInfo.segment.localRefs.empty);
                // Cast is necessary because LocalReference is not exported, so we can't directly call link.
                // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
                localRef.link(undefined, 0, undefined);
                (0, node_assert_1.strict)(segInfo.segment.localRefs.empty);
                node_assert_1.strict.equal(segInfo.segment.localRefs.empty, true);
                node_assert_1.strict.equal(segInfo.segment.localRefs.has(localRef), false);
                node_assert_1.strict.equal(localRef.getSegment(), undefined);
                node_assert_1.strict.equal(localRef.getOffset(), 0);
            });
        }
    });
    describe("doesn't crash for link to undefined then remove ref", () => {
        for (const { name, addRef } of tgCases) {
            it(name, () => {
                const client1 = new testClient_js_1.TestClient();
                const client2 = new testClient_js_1.TestClient();
                client1.startOrUpdateCollaboration("1");
                client2.startOrUpdateCollaboration("2");
                let seq = 0;
                const insert1 = client1.makeOpMessage(client1.insertTextLocal(0, "abcdef"), ++seq);
                client1.applyMsg(insert1);
                client2.applyMsg(insert1);
                const segInfo = client1.getContainingSegment(3);
                (0, node_assert_1.strict)(segInfo.segment);
                const localRef = client1.createLocalReferencePosition(segInfo.segment, segInfo.offset, ops_js_1.ReferenceType.SlideOnRemove, undefined);
                addRef(localRef);
                node_assert_1.strict.equal(localRef.getSegment(), segInfo.segment);
                (0, node_assert_1.strict)(segInfo.segment.localRefs);
                (0, node_assert_1.strict)(!segInfo.segment.localRefs.empty);
                // Cast is necessary because LocalReference is not exported, so we can't directly call link
                // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
                localRef.link(undefined, 0, undefined);
                (0, node_assert_1.strict)(segInfo.segment.localRefs.empty);
                segInfo.segment.localRefs.removeLocalRef(localRef);
                (0, node_assert_1.strict)(segInfo.segment.localRefs.empty);
                node_assert_1.strict.equal(segInfo.segment.localRefs.empty, true);
                node_assert_1.strict.equal(segInfo.segment.localRefs.has(localRef), false);
                node_assert_1.strict.equal(localRef.getSegment(), undefined);
                node_assert_1.strict.equal(localRef.getOffset(), 0);
            });
        }
    });
});
//# sourceMappingURL=client.localReference.spec.js.map