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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions