Files
ichmagmaus111andClaude Opus 5.5 0622057a4f Ghostgram: open-source release
Ghostgram for iOS, rebuilt from scratch on Telegram iOS 13.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:57:09 +01:00

164 lines
9.4 KiB
Swift

#if canImport(UIKit)
import XCTest
import UIKit
@testable import RichTextEditorUIKit
import RichTextEditorCore
final class CanvasTableNavTests: XCTestCase {
private func cell(_ id: String, _ text: String) -> Cell {
Cell(id: BlockID(id), blocks: [.paragraph(ParagraphBlock(id: BlockID(id + "p"), runs: [TextRun(text: text)]))])
}
private func canvas() -> DocumentCanvasView {
let v = DocumentCanvasView()
v.setBlocks([.table(TableBlock(id: BlockID("t"),
columns: [ColumnSpec(width: 120), ColumnSpec(width: 120)],
rows: [Row(id: BlockID("r0"), cells: [cell("a", "Alpha"), cell("b", "Beta")])]))], width: 320)
v.frame = CGRect(x: 0, y: 0, width: 320, height: 300); v.layoutIfNeeded()
return v
}
func test_rightAtEndOfCellA_entersCellB() {
let v = canvas()
let rA = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("ap")) }!
let rB = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("bp")) }!
let endOfA = rA.globalStart + rA.length
let next = (v.position(from: DocumentTextPosition(endOfA), in: .right, offset: 1) as! DocumentTextPosition).offset
XCTAssertEqual(next, rB.globalStart)
}
func test_leftAtStartOfCellB_returnsToCellAEnd() {
let v = canvas()
let rA = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("ap")) }!
let rB = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("bp")) }!
let prev = (v.position(from: DocumentTextPosition(rB.globalStart), in: .left, offset: 1) as! DocumentTextPosition).offset
XCTAssertEqual(prev, rA.globalStart + rA.length)
}
// A 2-row 2-column table with a paragraph above ("Top") and below ("Bot").
// (0,0)"Alpha"(ap) (0,1)"Beta"(bp) / (1,0)"Gamma"(cp) (1,1)"Delta"(dp).
private func canvas2x2() -> DocumentCanvasView {
let v = DocumentCanvasView()
v.setBlocks([
.paragraph(ParagraphBlock(id: BlockID("top"), runs: [TextRun(text: "Top")])),
.table(TableBlock(id: BlockID("t"), columns: [ColumnSpec(width: 120), ColumnSpec(width: 120)],
rows: [
Row(id: BlockID("r0"), cells: [cell("a", "Alpha"), cell("b", "Beta")]),
Row(id: BlockID("r1"), cells: [cell("c", "Gamma"), cell("d", "Delta")]),
])),
.paragraph(ParagraphBlock(id: BlockID("bot"), runs: [TextRun(text: "Bot")])),
], width: 320)
v.frame = CGRect(x: 0, y: 0, width: 320, height: 700); v.layoutIfNeeded()
return v
}
private func ref(_ v: DocumentCanvasView, at pos: Int) -> TextNodeRef? {
v.leafRegion(containingGlobal: pos)?.region.ref
}
// Correction 1: Backspace at a cell's first-paragraph start moves to the previous cell (no delete).
func test_backspaceAtCellBStart_movesToPrevCell_noDelete() {
let v = canvas() // 1-row 2-col table (Alpha|Beta)
let rA = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("ap")) }!
let rB = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("bp")) }!
let sizeBefore = v.documentSizeValue
v.selectedTextRange = DocumentTextRange(DocumentTextPosition(rB.globalStart), DocumentTextPosition(rB.globalStart))
v.deleteBackward()
XCTAssertEqual(v.documentSizeValue, sizeBefore, "nothing deleted")
XCTAssertEqual(v.head, rA.globalStart + rA.length, "caret moved to end of the previous cell")
XCTAssertEqual(v.anchor, v.head)
}
// Correction 1 edge: Backspace at the table's FIRST cell start is a no-op (no previous cell).
func test_backspaceAtFirstCellStart_isNoOp() {
let v = canvas()
let rA = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("ap")) }!
v.selectedTextRange = DocumentTextRange(DocumentTextPosition(rA.globalStart), DocumentTextPosition(rA.globalStart))
v.deleteBackward()
XCTAssertEqual(v.head, rA.globalStart, "caret stays — no previous cell to move to")
}
// Correction 2: Up in a 2nd-row cell moves to the cell above (same column), not above the table.
func test_upInSecondRowCell_movesToCellAbove() {
let v = canvas2x2()
let rC = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("cp")) }! // (1,0) "Gamma"
let up = (v.position(from: DocumentTextPosition(rC.globalStart + 2), in: .up, offset: 1) as! DocumentTextPosition).offset
XCTAssertEqual(ref(v, at: up), .paragraph(BlockID("ap")), "Up lands in the cell above (0,0), not above the table")
}
// Correction 2 edge: Up in a top-row cell (no cell above) escapes to the text above the table.
func test_upInFirstRowCell_escapesAboveTable() {
let v = canvas2x2()
let rA = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("ap")) }! // (0,0) "Alpha"
let up = (v.position(from: DocumentTextPosition(rA.globalStart + 2), in: .up, offset: 1) as! DocumentTextPosition).offset
XCTAssertEqual(ref(v, at: up), .paragraph(BlockID("top")), "Up from the top row escapes above the table")
}
// Correction 2 (symmetric): Down in a 1st-row cell moves to the cell below.
func test_downInFirstRowCell_movesToCellBelow() {
let v = canvas2x2()
let rA = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("ap")) }! // (0,0) "Alpha"
let down = (v.position(from: DocumentTextPosition(rA.globalStart + 2), in: .down, offset: 1) as! DocumentTextPosition).offset
XCTAssertEqual(ref(v, at: down), .paragraph(BlockID("cp")), "Down lands in the cell below (1,0)")
}
// Correction 2 edge: Down in a bottom-row cell (no cell below) escapes to the text below the table.
func test_downInLastRowCell_escapesBelowTable() {
let v = canvas2x2()
let rC = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("cp")) }! // (1,0) "Gamma"
let down = (v.position(from: DocumentTextPosition(rC.globalStart + 2), in: .down, offset: 1) as! DocumentTextPosition).offset
XCTAssertEqual(ref(v, at: down), .paragraph(BlockID("bot")), "Down from the bottom row escapes below the table")
}
// Correction 1: Backspace at (1,0) start moves to the previous row's LAST cell (row-major chain).
func test_backspaceAtRowStartCell_movesToPreviousRowLastCell() {
let v = canvas2x2()
let bp = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("bp")) }! // (0,1) "Beta"
let cp = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("cp")) }! // (1,0) "Gamma"
let sizeBefore = v.documentSizeValue
v.selectedTextRange = DocumentTextRange(DocumentTextPosition(cp.globalStart), DocumentTextPosition(cp.globalStart))
v.deleteBackward()
XCTAssertEqual(v.documentSizeValue, sizeBefore, "nothing deleted")
XCTAssertEqual(v.head, bp.globalStart + bp.length, "moved to (0,1) end — previous cell row-major")
}
// Backspace at the FIRST cell with a block above moves to the END of that block (exits the table
// upward), without deleting.
func test_backspaceAtFirstCellStart_withBlockAbove_movesToBlockBeforeEnd() {
let v = canvas2x2() // "Top" sits above the table
let top = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("top")) }!
let ap = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("ap")) }! // (0,0)
let sizeBefore = v.documentSizeValue
v.selectedTextRange = DocumentTextRange(DocumentTextPosition(ap.globalStart), DocumentTextPosition(ap.globalStart))
v.deleteBackward()
XCTAssertEqual(v.documentSizeValue, sizeBefore, "nothing deleted")
XCTAssertEqual(v.head, top.globalStart + top.length, "moved to the end of the block before the table")
}
// Backspace at the first cell when the block before the table is an IMAGE → its caption end.
func test_backspaceAtFirstCellStart_withImageBefore_movesToCaptionEnd() {
let v = DocumentCanvasView()
v.setBlocks([
.media(MediaBlock(id: BlockID("img"), mediaID: "x", naturalSize: Size2D(width: 80, height: 50),
caption: [TextRun(text: "Cap")])),
.table(TableBlock(id: BlockID("t"), columns: [ColumnSpec(width: 140), ColumnSpec(width: 140)],
rows: [Row(id: BlockID("r0"), cells: [cell("a", "Alpha"), cell("b", "Beta")])])),
], width: 340)
v.frame = CGRect(x: 0, y: 0, width: 340, height: 500); v.layoutIfNeeded()
let imageBox = v.boxes.first { $0 is MediaBlockBox }!
let ap = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("ap")) }!
let sizeBefore = v.documentSizeValue
v.selectedTextRange = DocumentTextRange(DocumentTextPosition(ap.globalStart), DocumentTextPosition(ap.globalStart))
v.deleteBackward()
XCTAssertEqual(v.documentSizeValue, sizeBefore, "nothing deleted")
XCTAssertEqual(v.head, imageBox.textStart + imageBox.textLength, "moved to the image's caption end")
}
// Correction 2 edge: Up from the only row when the table is the FIRST block lands in a real region.
func test_upInOnlyRowCell_tableIsFirstBlock_landsInRealRegion() {
let v = canvas() // table-only, single row
let rA = v.allLeafRegions().first { $0.ref == .paragraph(BlockID("ap")) }!
let up = (v.position(from: DocumentTextPosition(rA.globalStart + 2), in: .up, offset: 1) as! DocumentTextPosition).offset
XCTAssertNotNil(v.leafRegion(containingGlobal: up), "Up with nothing above lands in a real region (no gap/crash)")
}
}
#endif