import Foundation import RastrilloApple import XCTest @testable import DocsKit final class DocumentToolbarSelectionTests: XCTestCase { @MainActor func testTypingKeepsPresentationAvailableWhileCommandsWaitForProjection() async throws { let model = DocsModel(client: NativeHTTP(origin: URL(string: "https://example.invalid")!)) model.detail = DocumentDetail(sqid: "fixture", title: "Fixture", guid: "fixture", can_write: true, can_share: false) model.projection = CRDTProjection(children: [], canUndo: true, canRedo: false) let content = NSMutableAttributedString(string: "bold\n") content.addAttribute(.docsMarks, value: try JSONEncoder().encode(["strong": JSONValue.object([:])]), range: NSRange(location: 0, length: 4)) model.selection = NSRange(location: 4, length: 0) let commands = DocumentCommands(model: model, content: content) let settled = try XCTUnwrap(DocumentToolbarSelection(commands: commands)) XCTAssertEqual(settled.marks, ["strong"]) XCTAssertTrue(settled.canUndo) // Native input advances the caret before the replica publishes its text. XCTAssertTrue(model.enqueue(["op": .string("display_edit"), "at": .number(4), "length": .number(0), "text": .string("!!")])) model.selection = NSRange(location: 6, length: 0) XCTAssertTrue(model.hasPendingInput) XCTAssertTrue(commands.isAvailable, "Transient input must not dim the whole toolbar") XCTAssertFalse(commands.isEnabled) XCTAssertNil(DocumentToolbarSelection(commands: commands), "Do not replace the bold highlight using stale text") XCTAssertFalse(commands.format("em"), "Formatting must still reject an unacknowledged selection") XCTAssertNil(model.typingMarks) await model.discardAll() // A new projection still needs the native view's acknowledgement. model.detail = DocumentDetail(sqid: "fixture", title: "Fixture", guid: "fixture", can_write: true, can_share: false) model.projection = CRDTProjection(children: [], canUndo: true, canRedo: false) model.projectionGeneration = 2 model.selection = NSRange(location: 4, length: 0) XCTAssertTrue(commands.isAvailable) XCTAssertNil(DocumentToolbarSelection(commands: commands)) model.didRender(2) XCTAssertEqual(DocumentToolbarSelection(commands: commands), settled) model.selection = NSRange(location: 0, length: 2) XCTAssertTrue(try XCTUnwrap(DocumentToolbarSelection(commands: commands)).hasSelection) } @MainActor func testToolbarLabelsFollowHeadingFontSizeAndAlignment() throws { let model = DocsModel(client: NativeHTTP(origin: URL(string: "https://example.invalid")!)) model.detail = DocumentDetail(sqid: "fixture", title: "Fixture", guid: "fixture", can_write: true, can_share: false) model.projection = CRDTProjection(children: [CRDTNode(id: "heading", type: "heading", attrs: ["level": "2", "align": "center"], children: [CRDTNode(id: "text", type: "text", runs: [CRDTRun(insert: "Title", marks: ["font_serif": .object([:]), "size_24": .object([:])])])])], canUndo: false, canRedo: false) model.selection = NSRange(location: 1, length: 0) let state = try XCTUnwrap(DocumentToolbarSelection(commands: DocumentCommands(model: model, content: NativeTextProjection.render(model.projection!)))) XCTAssertEqual(state.style, "heading2") XCTAssertEqual(state.font, "serif") XCTAssertEqual(state.size, 24) XCTAssertEqual(state.alignment, "center") for level in 4...6 { model.projection?.children[0].attrs?["level"] = String(level) let imported = try XCTUnwrap(DocumentToolbarSelection(commands: DocumentCommands(model: model, content: NativeTextProjection.render(model.projection!)))) XCTAssertEqual(imported.styleLabel, String(format: docsMacString("Heading %d"), level)) } for (mark, expected) in [("size_sm", 10), ("size_lg", 14), ("size_xl", 18)] { model.typingMarks = [mark: .object([:])] let legacy = try XCTUnwrap(DocumentToolbarSelection(commands: DocumentCommands(model: model, content: NativeTextProjection.render(model.projection!)))) XCTAssertEqual(legacy.size, expected) } } @MainActor func testReadOnlyAndPausedInputRemainDisabled() { let model = DocsModel(client: NativeHTTP(origin: URL(string: "https://example.invalid")!)) let commands = DocumentCommands(model: model, content: NSAttributedString(string: "")) model.detail = DocumentDetail(sqid: "fixture", title: "Fixture", guid: "fixture", can_write: false, can_share: false) XCTAssertFalse(commands.isAvailable) XCTAssertFalse(commands.format("strong")) model.detail = DocumentDetail(sqid: "fixture", title: "Fixture", guid: "fixture", can_write: true, can_share: false) model.inputPaused = true XCTAssertFalse(commands.isAvailable) XCTAssertFalse(commands.format("strong")) model.inputPaused = false model.composing = true XCTAssertTrue(commands.isAvailable) XCTAssertFalse(commands.isEnabled) XCTAssertNil(DocumentToolbarSelection(commands: commands)) XCTAssertFalse(commands.format("strong")) } }