| 1 | import Foundation |
| 2 | import XCTest |
| 3 | @testable import DocsKit |
| 4 | |
| 5 | final class DocumentListCommandTests: XCTestCase { |
| 6 | @MainActor func testSelectedListConversionAndLiftSurviveReplacedNodeIDs() async throws { |
| 7 | let replica = CRDTReplica(guid: "native-list-command-test") |
| 8 | _ = try await replica.open(update: Data()) |
| 9 | _ = try await replica.command(["op": .string("insert_element"), "at": .number(0), "type": .string("paragraph")]) |
| 10 | let paste = #"{"op":"display_paste","at":0,"length":0,"blocks":[{"type":"bullet_list","children":[{"type":"list_item","children":[{"type":"paragraph","children":[{"type":"text","runs":[{"insert":"first"}]}]}]},{"type":"list_item","children":[{"type":"paragraph","children":[{"type":"text","runs":[{"insert":"second"}]}]}]},{"type":"list_item","children":[{"type":"paragraph","children":[{"type":"text","runs":[{"insert":"third"}]}]}]}]}]}"# |
| 11 | _ = try await replica.command(JSONDecoder().decode([String: JSONValue].self, from: Data(paste.utf8))) |
| 12 | for type in ["ordered_list", "ordered_list"] { |
| 13 | let source = try await replica.presentation(source: 0, selection: NSRange(location: 0, length: 0)) |
| 14 | let (generation, projection, _) = try XCTUnwrap(source) |
| 15 | let paths = pathsToParagraphs(projection.children).filter { $0.contains { $0.type == "list_item" } } |
| 16 | let commands = DocumentCommands.listCommands(type: type, paths: paths) |
| 17 | _ = try await replica.command(["op": .string("batch"), "generation": .number(Double(generation)), "commands": .array(commands.map(JSONValue.object))]) |
| 18 | await replica.retainGenerations([]) |
| 19 | } |
| 20 | let final = try await replica.projection() |
| 21 | XCTAssertEqual(final.children.map(\.plainText).filter { !$0.isEmpty }, ["first", "second", "third"]) |
| 22 | XCTAssertFalse(final.children.contains { ["bullet_list", "ordered_list"].contains($0.type) }) |
| 23 | await replica.close() |
| 24 | } |
| 25 | |
| 26 | private func pathsToParagraphs(_ nodes: [CRDTNode], ancestors: [CRDTNode] = []) -> [[CRDTNode]] { |
| 27 | nodes.flatMap { node in |
| 28 | let path = ancestors + [node] |
| 29 | return node.type == "paragraph" ? [path] : pathsToParagraphs(node.children ?? [], ancestors: path) |
| 30 | } |
| 31 | } |
| 32 | } |
| 33 | |