Skip to content

Get mark at position returns Null if the mark was previously removed #214

@gn-norbi-d

Description

@gn-norbi-d

It seems that after sending a Null() value for an existing mark, next time when I retrieve the marks at that position, it also returns the mark with Null value. Based on what I understood, in theory this shouldn't happen, a Null() value should simply remove that mark.

During some additional debugging, I've seen that depending which API I'm using, I'm getting different results.

In the following scenario:

  • I add a mark with the same name and value to three different locations: 0, 2, 4.
  • I remove the mark from position 0 by sending Null() value for the mark with the same name
  • calling marks(obj: id) returns the remaining two marks
  • calling marksAt(obj: id, heads: heads()) returns the correct marks, same as marks(obj: id) above
  • calling marksAt(obj: id, position: .index(2)) returns the correct mark at position 2
  • calling marksAt(obj: id, position: .index(1)) returns empty list as there never existed a mark
  • (!) calling marksAt(obj: id, position: .index(0)) returns a mark with Null() value (it should return empty)

A unittest reproducing this issue:

import Automerge

func testAutomergeMarksAtPosition() throws {
    let automergeDoc = Automerge.Document(textEncoding: .utf16)
    let textId = try! automergeDoc.putObject(obj: .ROOT, key: "text", ty: .Text)
    try! automergeDoc.spliceText(obj: textId, start: UInt64(0), delete: 0, value: "abcdefg")
    try! automergeDoc.mark(obj: textId, start: UInt64(0), end: UInt64(1), expand: .none, name: "name1", value: ScalarValue.Int(1))
    try! automergeDoc.mark(obj: textId, start: UInt64(2), end: UInt64(3), expand: .none, name: "name1", value: ScalarValue.Int(1))
    try! automergeDoc.mark(obj: textId, start: UInt64(4), end: UInt64(5), expand: .none, name: "name1", value: ScalarValue.Int(1))

    // Get all the marks in the text, works as expected
    var attrs = try automergeDoc.marks(obj: textId)
    XCTAssertEqual(
        attrs,
        [
            Mark(start: 0, end: 1, name: "name1", value: ScalarValue.Int(1)),
            Mark(start: 2, end: 3, name: "name1", value: ScalarValue.Int(1)),
            Mark(start: 4, end: 5, name: "name1", value: ScalarValue.Int(1))
        ]
    )

    // Remove the mark at location 0
    try! automergeDoc.mark(obj: textId, start: UInt64(0), end: UInt64(1), expand: .none, name: "name1", value: ScalarValue.Null)

    // Get all the marks in the text and check that the mark at location 0 was removed
    attrs = try automergeDoc.marks(obj: textId)
    XCTAssertEqual(
        attrs,
        [
            Mark(start: 2, end: 3, name: "name1", value: ScalarValue.Int(1)),
            Mark(start: 4, end: 5, name: "name1", value: ScalarValue.Int(1))
        ]
    )

    // Get the mark at location 2 to verify it returns the correct mark
    attrs = try automergeDoc.marksAt(obj: textId, position: .index(2))
    XCTAssertEqual(
        attrs,
        [
            Mark(start: 2, end: 2, name: "name1", value: ScalarValue.Int(1))
        ]
    )

    // Get all the marks in the head() snapshot, verify that only the two remaining marks are returned
    attrs = try automergeDoc.marksAt(obj: textId, heads: automergeDoc.heads())
    XCTAssertEqual(
        attrs,
        [
            Mark(start: 2, end: 3, name: "name1", value: ScalarValue.Int(1)),
            Mark(start: 4, end: 5, name: "name1", value: ScalarValue.Int(1))
        ]
    )
    // Get the mark at a location that never had a mark
    attrs = try automergeDoc.marksAt(obj: textId, position: .index(1))
    XCTAssertEqual(attrs, [])

    // Get the mark at location 0 (which was removed previously by sending Null
    // FIXME: this should return empty as above
    attrs = try automergeDoc.marksAt(obj: textId, position: .index(0))
    XCTAssertNotEqual(
        attrs,
        [
            Mark(start: 0, end: 0, name: "name1", value: ScalarValue.Null)
        ]
    )
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions