05e005e9ce
Batch of in-progress app work from the working tree. - feat(keyboard): CursorNavigation + CursorDragPad for caret movement; KeyboardSoundFeedback for system key click sounds - feat(dictionary): DictionaryAliasGenerator + PersonalDictionaryEntrySheet; TranscriptPostProcessor quality gate; retire DictionaryLearner - feat(ui): TabBarVisibility handling; drop PageHeaderRow / PageHeaderConfirmButton; refresh views and localizable strings - fix(security): move the hardcoded DeepSeek key out of PreconfiguredKeys.swift into a gitignored PreconfiguredKeys.local.swift (seeded from .example by generate-xcodeproj.sh) - docs(agents): add Conventional Commits versioning + bilingual changelog rules - chore(gitignore): ignore PreconfiguredKeys.local.swift, .cache/, pycache Custom language model / lexicon work stays on feature/custom-language-model-asr. Changelog bullets added under [Unreleased]; no version bump.
140 lines
4.5 KiB
Swift
140 lines
4.5 KiB
Swift
// CursorNavigationTests.swift
|
|
// OSGKeyboardTests
|
|
|
|
import XCTest
|
|
@testable import OSGKeyboardShared
|
|
|
|
final class CursorNavigationTests: XCTestCase {
|
|
|
|
/// Uses the built-in 1/2-unit width table. `lineWidth` is therefore in
|
|
/// "units" (≈ Latin characters) for these tests.
|
|
private func config(lineWidth: CGFloat) -> CursorNavigation.VisualLineLayoutConfig {
|
|
CursorNavigation.VisualLineLayoutConfig(lineWidth: lineWidth)
|
|
}
|
|
|
|
func testColumnOnFirstLine() {
|
|
XCTAssertEqual(CursorNavigation.column(before: "hello"), 5)
|
|
XCTAssertEqual(CursorNavigation.column(before: nil), 0)
|
|
}
|
|
|
|
func testColumnAfterNewline() {
|
|
XCTAssertEqual(CursorNavigation.column(before: "hello\nwor"), 3)
|
|
}
|
|
|
|
func testDefaultDisplayWidthLatinAndCJK() {
|
|
XCTAssertEqual(CursorNavigation.defaultDisplayWidth("a"), 1)
|
|
XCTAssertEqual(CursorNavigation.defaultDisplayWidth("中"), 2)
|
|
XCTAssertEqual(CursorNavigation.defaultDisplayWidth("\n"), 0)
|
|
}
|
|
|
|
func testVisualLineDownAcrossSoftWrap() {
|
|
// 20 Latin chars, wrap at 16 → line0 [0,16), line1 [16,20).
|
|
let text = String(repeating: "a", count: 20)
|
|
let before = String(text.prefix(8))
|
|
let after = String(text.suffix(12))
|
|
|
|
let result = CursorNavigation.visualLineDownOffset(
|
|
before: before,
|
|
after: after,
|
|
preferredDisplayColumn: nil,
|
|
config: config(lineWidth: 16)
|
|
)
|
|
XCTAssertNotNil(result)
|
|
// Sticky column 8; line1 only has 4 units → clamp to its end.
|
|
XCTAssertEqual(result?.offset, 12)
|
|
}
|
|
|
|
func testVisualLineDownToShorterWrappedLineClampsToEnd() {
|
|
// Caret at col 15 (clearly on line0); line1 has only 4 chars.
|
|
let text = String(repeating: "a", count: 20)
|
|
let before = String(text.prefix(15))
|
|
let after = String(text.suffix(5))
|
|
|
|
let result = CursorNavigation.visualLineDownOffset(
|
|
before: before,
|
|
after: after,
|
|
preferredDisplayColumn: 15,
|
|
config: config(lineWidth: 16)
|
|
)
|
|
XCTAssertNotNil(result)
|
|
XCTAssertEqual(result?.offset, 5)
|
|
XCTAssertEqual(result?.stickyColumn, 15)
|
|
}
|
|
|
|
func testVisualLineUpAcrossSoftWrap() {
|
|
let text = String(repeating: "a", count: 20)
|
|
let before = String(text.prefix(18))
|
|
let after = String(text.suffix(2))
|
|
|
|
let result = CursorNavigation.visualLineUpOffset(
|
|
before: before,
|
|
after: after,
|
|
preferredDisplayColumn: 8,
|
|
config: config(lineWidth: 16)
|
|
)
|
|
XCTAssertNotNil(result)
|
|
XCTAssertEqual(result?.offset, -10)
|
|
}
|
|
|
|
func testVisualLineDownAcrossHardNewline() {
|
|
let before = "hello\nwor"
|
|
let after = "ld\nfoo"
|
|
|
|
let result = CursorNavigation.visualLineDownOffset(
|
|
before: before,
|
|
after: after,
|
|
preferredDisplayColumn: nil,
|
|
config: config(lineWidth: 100)
|
|
)
|
|
XCTAssertNotNil(result)
|
|
// "hello\nworld\nfoo" — caret before "ld"; column 3 lands after "foo".
|
|
XCTAssertEqual(result?.offset, 6)
|
|
}
|
|
|
|
func testVisualLineDownPreservesStickyColumnOnLongerNextLine() {
|
|
let before = "hello\nwor"
|
|
let after = "ld\nfoobarbaz"
|
|
|
|
let result = CursorNavigation.visualLineDownOffset(
|
|
before: before,
|
|
after: after,
|
|
preferredDisplayColumn: 5,
|
|
config: config(lineWidth: 100)
|
|
)
|
|
XCTAssertNotNil(result)
|
|
// "ld\n" (3) + column 5 on "foobarbaz" = 8 total from cursor.
|
|
XCTAssertEqual(result?.offset, 8)
|
|
XCTAssertEqual(result?.stickyColumn, 5)
|
|
}
|
|
|
|
func testVisualLineUpOnFirstLineReturnsNil() {
|
|
XCTAssertNil(
|
|
CursorNavigation.visualLineUpOffset(
|
|
before: "hello",
|
|
after: " world",
|
|
preferredDisplayColumn: nil,
|
|
config: config(lineWidth: 100)
|
|
)
|
|
)
|
|
}
|
|
|
|
func testVisualLineDownWithNoFollowingTextReturnsNil() {
|
|
XCTAssertNil(
|
|
CursorNavigation.visualLineDownOffset(
|
|
before: "hello",
|
|
after: nil,
|
|
preferredDisplayColumn: nil,
|
|
config: config(lineWidth: 100)
|
|
)
|
|
)
|
|
XCTAssertNil(
|
|
CursorNavigation.visualLineDownOffset(
|
|
before: "hello",
|
|
after: "",
|
|
preferredDisplayColumn: nil,
|
|
config: config(lineWidth: 100)
|
|
)
|
|
)
|
|
}
|
|
}
|