mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-11-03 22:27:13 +01:00
Ghostgram for iOS, rebuilt from scratch on Telegram iOS 13.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
6.5 KiB
Swift
119 lines
6.5 KiB
Swift
#if canImport(UIKit)
|
|
import XCTest
|
|
import UIKit
|
|
@testable import RichTextEditorUIKit
|
|
import RichTextEditorCore
|
|
|
|
final class NativeTextCheckingClientTests: XCTestCase {
|
|
private func makeCanvas(_ text: String, id: String = "p") -> DocumentCanvasView {
|
|
let v = DocumentCanvasView()
|
|
v.setBlocks([.paragraph(ParagraphBlock(id: BlockID(id), runs: [TextRun(text: text)]))], width: 320)
|
|
v.frame = CGRect(x: 0, y: 0, width: 320, height: 600); v.layoutIfNeeded()
|
|
return v
|
|
}
|
|
private func firstStart(_ v: DocumentCanvasView) -> Int { v.boxes[0].textStart }
|
|
|
|
func test_applyNativeAnnotations_populatesRegionLocalWithStyle() {
|
|
let v = makeCanvas("hello wrold today")
|
|
let base = firstStart(v) // 1-based axis
|
|
v.applyNativeAnnotations(global: NSRange(location: base + 6, length: 5), style: .spelling) // "wrold"
|
|
let entry = v.spellResults[BlockID("p")]
|
|
XCTAssertEqual(entry?.ranges.map { $0.range }, [NSRange(location: 6, length: 5)])
|
|
XCTAssertEqual(entry?.ranges.map { $0.style }, [.spelling])
|
|
}
|
|
func test_clearNativeAnnotations_removesRange() {
|
|
let v = makeCanvas("hello wrold")
|
|
let base = firstStart(v)
|
|
v.applyNativeAnnotations(global: NSRange(location: base + 6, length: 5), style: .spelling)
|
|
v.clearNativeAnnotations(global: NSRange(location: base + 6, length: 5))
|
|
XCTAssertEqual(v.spellResults[BlockID("p")]?.ranges.count ?? 0, 0)
|
|
}
|
|
func test_applyNativeAnnotations_dropsLinkExcludedRange() {
|
|
let v = DocumentCanvasView()
|
|
v.setBlocks([.paragraph(ParagraphBlock(id: BlockID("p"), runs: [
|
|
TextRun(text: "see "), TextRun(text: "wrold", attributes: CharacterAttributes(link: "https://x.com"))]))], width: 320)
|
|
v.frame = CGRect(x: 0, y: 0, width: 320, height: 600); v.layoutIfNeeded()
|
|
let base = v.boxes[0].textStart
|
|
v.applyNativeAnnotations(global: NSRange(location: base + 4, length: 5), style: .spelling) // "wrold" (link)
|
|
XCTAssertEqual(v.spellResults[BlockID("p")]?.ranges.count ?? 0, 0) // excluded
|
|
}
|
|
|
|
// MARK: N3 — style classification + style-aware color
|
|
|
|
func test_style_spellingFromDisplayStyle2() {
|
|
// Observed live: a spelling flag carries NSTextAlternativesDisplayStyle == 2 and no alternatives.
|
|
let attrs: [NSAttributedString.Key: Any] = [DocumentCanvasView.displayStyleKey: 2]
|
|
XCTAssertEqual(DocumentCanvasView.style(from: attrs), .spelling)
|
|
}
|
|
func test_style_grammarFromOtherDisplayStyle() {
|
|
let attrs: [NSAttributedString.Key: Any] = [DocumentCanvasView.displayStyleKey: 1]
|
|
XCTAssertEqual(DocumentCanvasView.style(from: attrs), .grammar)
|
|
}
|
|
func test_style_correctionWhenAlternativesPresent() {
|
|
// style(from:) only checks PRESENCE of the alternatives key (the correction data), so any non-nil value.
|
|
let attrs: [NSAttributedString.Key: Any] = [
|
|
DocumentCanvasView.displayStyleKey: 0,
|
|
DocumentCanvasView.alternativesKey: "correction-alternatives",
|
|
]
|
|
XCTAssertEqual(DocumentCanvasView.style(from: attrs), .correction)
|
|
}
|
|
// MARK: N4 — `.correction` alternatives stash (via `nativeReplace`, the real controller-facing path)
|
|
|
|
/// Stands in for the private, non-publicly-constructible `NSTextAlternatives`: exposes the same two
|
|
/// KVC-readable properties the production code reads (`alternativeStrings`/`primaryString`).
|
|
private final class FakeTextAlternatives: NSObject {
|
|
@objc let alternativeStrings: [String]
|
|
@objc let primaryString: String
|
|
init(alternativeStrings: [String], primaryString: String) {
|
|
self.alternativeStrings = alternativeStrings
|
|
self.primaryString = primaryString
|
|
}
|
|
}
|
|
|
|
func test_nativeReplace_correctionStashesAlternativesViaKVC() {
|
|
let v = makeCanvas("hello wrold today")
|
|
let base = firstStart(v)
|
|
let range = v.nativeTextRange(forGlobalLocation: base + 6, length: 5)!
|
|
let alt = FakeTextAlternatives(alternativeStrings: ["world", "word"], primaryString: "world")
|
|
let s = NSAttributedString(string: "wrold", attributes: [
|
|
DocumentCanvasView.displayStyleKey: 0,
|
|
DocumentCanvasView.alternativesKey: alt,
|
|
])
|
|
v.nativeReplace(range, withAnnotatedString: s, relativeReplacementRange: NSRange(location: 0, length: 5))
|
|
let stashed = v.spellingAlternatives[BlockID("p")]?.first { $0.range == NSRange(location: 6, length: 5) }
|
|
XCTAssertEqual(stashed?.candidates, ["world", "word"])
|
|
XCTAssertEqual(stashed?.primary, "world")
|
|
XCTAssertEqual(v.spellResults[BlockID("p")]?.ranges.map { $0.style }, [.correction])
|
|
}
|
|
|
|
func test_nativeReplace_correctionWithUnexpectedAlternativesShape_doesNotCrashOrStash() {
|
|
let v = makeCanvas("hello wrold today")
|
|
let base = firstStart(v)
|
|
let range = v.nativeTextRange(forGlobalLocation: base + 6, length: 5)!
|
|
// Not an NSTextAlternatives-shaped object — the KVC reads must guard and no-op, not crash.
|
|
let s = NSAttributedString(string: "wrold", attributes: [
|
|
DocumentCanvasView.displayStyleKey: 0,
|
|
DocumentCanvasView.alternativesKey: "not-an-NSTextAlternatives-instance",
|
|
])
|
|
v.nativeReplace(range, withAnnotatedString: s, relativeReplacementRange: NSRange(location: 0, length: 5))
|
|
XCTAssertNil(v.spellingAlternatives[BlockID("p")]?.first { $0.range == NSRange(location: 6, length: 5) })
|
|
// The style classification + `spellResults` flag are unaffected by the missing candidates.
|
|
XCTAssertEqual(v.spellResults[BlockID("p")]?.ranges.map { $0.style }, [.correction])
|
|
}
|
|
|
|
func test_underlineColor_perStyle() {
|
|
let v = DocumentCanvasView()
|
|
v.setBlocks([.paragraph(ParagraphBlock(id: BlockID("p"), runs: [TextRun(text: "x")]))], width: 320)
|
|
XCTAssertEqual(v.spellingUnderlineColor(.spelling), v.mapper.theme.misspellingUnderline)
|
|
XCTAssertEqual(v.spellingUnderlineColor(.grammar), v.mapper.theme.grammarUnderline)
|
|
XCTAssertEqual(v.spellingUnderlineColor(.correction), v.mapper.theme.correctionUnderline)
|
|
}
|
|
func test_themeDefaults_grammarGreen_correctionPeriwinkle() {
|
|
XCTAssertEqual(RichTextEditorTheme.default.grammarUnderline, .systemGreen)
|
|
// Measured from a real UITextView's native iOS 26 autocorrect underline (#99ACEB) — see Task 2 brief.
|
|
XCTAssertEqual(RichTextEditorTheme.default.correctionUnderline,
|
|
UIColor(red: 153.0/255.0, green: 172.0/255.0, blue: 235.0/255.0, alpha: 1.0))
|
|
}
|
|
}
|
|
#endif
|