From 535bcc342493f9df887633117044ac0a30877b09 Mon Sep 17 00:00:00 2001 From: Bartosz Sypytkowski Date: Fri, 7 Jul 2023 11:11:22 +0200 Subject: [PATCH] added tests for quoting multiple elements in arrays --- src/structs/ContentType.js | 9 +++-- src/types/YWeakLink.js | 80 ++++++++++++++++++++----------------- tests/y-weak-links.tests.js | 69 ++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 40 deletions(-) diff --git a/src/structs/ContentType.js b/src/structs/ContentType.js index 1613a11e..e3e9241b 100644 --- a/src/structs/ContentType.js +++ b/src/structs/ContentType.js @@ -10,7 +10,8 @@ import { readYXmlText, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item, YEvent, AbstractType, // eslint-disable-line readYWeakLink, - unlinkFrom + unlinkFrom, + ID } from '../internals.js' import * as error from 'lib0/error' @@ -113,15 +114,17 @@ export class ContentType { // when removing weak links, remove references to them // from type they're pointing to const type = /** @type {WeakLink} */ (this.type); + const end = /** @type {ID} */ (type._quoteEnd.item) for (let item = type._firstItem; item !== null; item = item.right) { if (item.linked) { unlinkFrom(transaction, item, type) } - if (item === type._lastItem) { + const lastId = item.lastId + if (lastId.client === end.client && lastId.clock === end.clock) { break; } } - type._firstItem = type._lastItem = null + type._firstItem = null } let item = this.type._start diff --git a/src/types/YWeakLink.js b/src/types/YWeakLink.js index 9df1d734..b86bded3 100644 --- a/src/types/YWeakLink.js +++ b/src/types/YWeakLink.js @@ -40,14 +40,12 @@ export class YWeakLink extends AbstractType { * @param {RelativePosition} start * @param {RelativePosition} end * @param {Item|null} firstItem - * @param {Item|null} lastItem */ - constructor(start, end, firstItem, lastItem) { + constructor(start, end, firstItem) { super() this._quoteStart = start this._quoteEnd = end this._firstItem = firstItem - this._lastItem = lastItem } /** @@ -90,11 +88,16 @@ export class YWeakLink extends AbstractType { unqote() { let result = /** @type {Array} */ ([]) let item = this._firstItem + const end = /** @type {ID} */ (this._quoteEnd.item) //TODO: moved elements - while (item !== null && item !== this._lastItem) { + while (item !== null) { if (!item.deleted) { result = result.concat(item.content.getContent()) } + const lastId = item.lastId + if (lastId.client === end.client && lastId.clock === end.clock) { + break; + } item = item.right } return result @@ -118,22 +121,22 @@ export class YWeakLink extends AbstractType { // in such case we need to cut of the linked element into a // separate block let firstItem = this._firstItem !== null ? this._firstItem : getItemCleanStart(transaction, /** @type {ID} */ (this._quoteStart.item)) - let lastItem = this._lastItem !== null ? this._lastItem : getItemCleanEnd(transaction, y.store, /** @type {ID} */(this._quoteEnd.item)) + getItemCleanEnd(transaction, y.store, /** @type {ID} */(this._quoteEnd.item)) if (firstItem.parentSub !== null) { // for maps, advance to most recent item while (firstItem.right !== null) { firstItem = firstItem.right } - lastItem = firstItem } this._firstItem = firstItem - this._lastItem = lastItem /** @type {Item|null} */ let item = firstItem - for (; item !== null; item = item.right) { + let end = /** @type {ID} */ (this._quoteEnd.item) + for (;item !== null; item = item.right) { createLink(transaction, item, this) - if (item === lastItem) { + const lastId = item.lastId + if (lastId.client === end.client && lastId.clock === end.clock) { break; } } @@ -145,14 +148,14 @@ export class YWeakLink extends AbstractType { * @return {YWeakLink} */ _copy () { - return new YWeakLink(this._quoteStart, this._quoteEnd, this._firstItem, this._lastItem) + return new YWeakLink(this._quoteStart, this._quoteEnd, this._firstItem) } /** * @return {YWeakLink} */ clone () { - return new YWeakLink(this._quoteStart, this._quoteEnd, this._firstItem, this._lastItem) + return new YWeakLink(this._quoteStart, this._quoteEnd, this._firstItem) } /** @@ -194,7 +197,7 @@ export const readYWeakLink = decoder => { const startID = readID(decoder.restDecoder) const start = new RelativePosition(null, null, startID, startAssoc) const end = new RelativePosition(null, null, isSingle ? startID : readID(decoder.restDecoder), endAssoc) - return new YWeakLink(start, end, null, null) + return new YWeakLink(start, end, null) } const invalidQuotedRange = error.create('Invalid quoted range length.') @@ -223,33 +226,36 @@ export const arrayWeakLink = (transaction, parent, index, length = 1) => { index -= startItem.length } } - - if (startItem !== null) { - let endItem = startItem - let remaining = length - for (;endItem !== null && endItem.right !== null && endItem.length > remaining; endItem = endItem.right) { - // iterate over the items to reach the last block in the quoted range - remaining -= endItem.length - } - if (endItem.length >= remaining) { - endItem = getItemCleanEnd(transaction, transaction.doc.store, createID(startItem.id.client, startItem.id.clock + remaining - 1)) - const start = new RelativePosition(null, null, startItem.id, 0) - const end = new RelativePosition(null, null, endItem.lastId, -1) - const link = new YWeakLink(start, end, startItem, endItem) - if (parent.doc !== null) { - transact(parent.doc, (transaction) => { - for (let item = link._firstItem; item !== null; item = item = item.right) { - createLink(transaction, item, link) - if (item === link._lastItem) { - break; - } - } - }) + let endItem = startItem + let remaining = length + for (;endItem !== null; endItem = endItem.right) { + if (!endItem.deleted && endItem.countable) { + if (remaining > endItem.length) { + remaining -= endItem.length + } else { + endItem = getItemCleanEnd(transaction, transaction.doc.store, createID(endItem.id.client, endItem.id.clock + remaining - 1)) + break; } - return link } } - + if (startItem !== null && endItem !== null) { + const start = new RelativePosition(null, null, startItem.id, 0) + const end = new RelativePosition(null, null, endItem.lastId, -1) + const link = new YWeakLink(start, end, startItem) + if (parent.doc !== null) { + transact(parent.doc, (transaction) => { + const end = /** @type {ID} */ (link._quoteEnd.item) + for (let item = link._firstItem; item !== null; item = item = item.right) { + createLink(transaction, item, link) + const lastId = item.lastId + if (lastId.client === end.client && lastId.clock === end.clock) { + break; + } + } + }) + } + return link + } throw invalidQuotedRange } @@ -265,7 +271,7 @@ export const mapWeakLink = (parent, key) => { if (item !== undefined) { const start = new RelativePosition(null, null, item.id, 0) const end = new RelativePosition(null, null, item.id, -1) - const link = new YWeakLink(start, end, item, item) + const link = new YWeakLink(start, end, item) if (parent.doc !== null) { transact(parent.doc, (transaction) => { createLink(transaction, item, link) diff --git a/tests/y-weak-links.tests.js b/tests/y-weak-links.tests.js index 1b5a2176..1eaa081e 100644 --- a/tests/y-weak-links.tests.js +++ b/tests/y-weak-links.tests.js @@ -42,6 +42,75 @@ export const testBasicArray = tc => { t.compare(array1.get(3).deref(), 2) } +/** + * @param {t.TestCase} tc + */ +export const testArrayQuoteMultipleElements = tc => { + const { testConnector, array0, array1 } = init(tc, {users:2}) + const nested = new Y.Map([['key', 'value']]) + array0.insert(0, [1, 2, nested, 3]) + array0.insert(0, [array0.quote(1, 3)]) + + const link0 = array0.get(0) + t.compare(link0.unqote(), [2, nested, 3]) + t.compare(array0.get(1), 1) + t.compare(array0.get(2), 2) + t.compare(array0.get(3), nested) + t.compare(array0.get(4), 3) + + testConnector.flushAllMessages() + + const link1 = array1.get(0) + let unqoted = link1.unqote() + t.compare(unqoted[0], 2) + t.compare(unqoted[1].toJSON(), {'key':'value'}) + t.compare(unqoted[2], 3) + t.compare(array1.get(1), 1) + t.compare(array1.get(2), 2) + t.compare(array1.get(3).toJSON(), {'key':'value'}) + t.compare(array1.get(4), 3) + + array1.insert(3, ['A', 'B']) + unqoted = link1.unqote() + t.compare(unqoted[0], 2) + t.compare(unqoted[1], 'A') + t.compare(unqoted[2], 'B') + t.compare(unqoted[3].toJSON(), {'key':'value'}) + t.compare(unqoted[4], 3) + + testConnector.flushAllMessages() + + t.compare(array0.get(0).unqote(), [2, 'A', 'B', nested, 3]) +} + +/** + * @param {t.TestCase} tc + */ +export const testSelfQuotation = tc => { + const { testConnector, array0, array1 } = init(tc, {users:2}) + array0.insert(0, [1, 2, 3, 4]) + const link0 = array0.quote(0, 3) + array0.insert(1, [link0]) // link is inserted into its own range + + t.compare(link0.unqote(), [1, link0, 2, 3]) + t.compare(array0.get(0), 1) + t.compare(array0.get(1), link0) + t.compare(array0.get(2), 2) + t.compare(array0.get(3), 3) + t.compare(array0.get(4), 4) + + testConnector.flushAllMessages() + + const link1 = array1.get(1) + let unqoted = link1.unqote() + t.compare(unqoted, [1, link1, 2, 3]) + t.compare(array1.get(0), 1) + t.compare(array1.get(1), link1) + t.compare(array1.get(2), 2) + t.compare(array1.get(3), 3) + t.compare(array1.get(4), 4) +} + /** * @param {t.TestCase} tc */