diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index b70b3abe..af0e7d1f 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -17,4 +17,15 @@ jobs: run: xcodebuild -scheme McBopomofo -configuration Release build - name: Build run: xcodebuild -scheme McBopomofoInstaller -configuration Release build - + - name: Test CandidateUI + run: swift test + working-directory: Packages/CandidateUI + - name: Test CandidateUI + run: swift test + working-directory: Packages/CandidateUI + - name: Test OpenCCBridge + run: swift test + working-directory: Packages/OpenCCBridge + - name: Test VXHanConvert + run: swift test + working-directory: Packages/VXHanConvert diff --git a/Packages/CandidateUI/Package.swift b/Packages/CandidateUI/Package.swift index 65fad68a..c8267e82 100644 --- a/Packages/CandidateUI/Package.swift +++ b/Packages/CandidateUI/Package.swift @@ -21,5 +21,9 @@ let package = Package( .target( name: "CandidateUI", dependencies: []), + .testTarget( + name: "CandidateUITests", + dependencies: ["CandidateUI"]), + ] ) diff --git a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift index 073eef4a..c583b9bb 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift @@ -43,7 +43,11 @@ public protocol CandidateControllerDelegate: AnyObject { @objc (VTCandidateController) public class CandidateController: NSWindowController { - @objc public weak var delegate: CandidateControllerDelegate? + @objc public weak var delegate: CandidateControllerDelegate? { + didSet { + reloadData() + } + } @objc public var selectedCandidateIndex: UInt = UInt.max @objc public var visible: Bool = false { didSet { diff --git a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift index 9f01ace7..d12721bf 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift @@ -309,6 +309,10 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat if selectedRow != -1 { // keep track of the highlighted index in the key label strip let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin) + // firstVisibleRow cannot be larger than selectedRow. + if firstVisibleRow > selectedRow { + return + } keyLabelStripView.highlightedIndex = UInt(selectedRow - firstVisibleRow) keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame) @@ -343,7 +347,7 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat var newIndex = selectedCandidateIndex if forward { - if newIndex == itemCount - 1 { + if newIndex >= itemCount - 1 { return false } newIndex = min(newIndex + labelCount, itemCount - 1) @@ -371,8 +375,12 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat return false } var newIndex = selectedCandidateIndex + if newIndex == UInt.max { + return false + } + if forward { - if newIndex == itemCount - 1 { + if newIndex >= itemCount - 1 { return false } newIndex += 1 diff --git a/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift b/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift new file mode 100644 index 00000000..1aa3003d --- /dev/null +++ b/Packages/CandidateUI/Tests/CandidateUITests/HorizontalCandidateControllerTests.swift @@ -0,0 +1,113 @@ +import XCTest +@testable import CandidateUI + +class HorizontalCandidateControllerTests: XCTestCase { + + class Mock: CandidateControllerDelegate { + let candidates = ["A", "B", "C", "D", "E", "F", "G", "H"] + var selected: String? + func candidateCountForController(_ controller: CandidateController) -> UInt { + UInt(candidates.count) + } + + func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String { + candidates[Int(index)] + } + + func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) { + selected = candidates[Int(index)] + } + } + + func testReloadData() { + let controller = HorizontalCandidateController() + let mock = Mock() + controller.delegate = mock + controller.keyLabels = ["1", "2", "3", "4"] + controller.reloadData() + XCTAssert(controller.selectedCandidateIndex == 0) + } + + func testHighlightNextCandidate() { + let controller = HorizontalCandidateController() + let mock = Mock() + controller.keyLabels = ["1", "2", "3", "4"] + controller.delegate = mock + var result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 1) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 2) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 3) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 4) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 5) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 6) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 7) + result = controller.highlightNextCandidate() + XCTAssert(result == false) + XCTAssert(controller.selectedCandidateIndex == 7) + } + + func testHighlightPreviousCandidate() { + let controller = HorizontalCandidateController() + let mock = Mock() + controller.keyLabels = ["1", "2", "3", "4"] + controller.delegate = mock + _ = controller.showNextPage() + XCTAssert(controller.selectedCandidateIndex == 4) + var result = controller.highlightPreviousCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 3) + result = controller.highlightPreviousCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 2) + result = controller.highlightPreviousCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 1) + result = controller.highlightPreviousCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 0) + result = controller.highlightPreviousCandidate() + XCTAssert(result == false) + XCTAssert(controller.selectedCandidateIndex == 0) + } + + func testShowNextPage() { + let controller = HorizontalCandidateController() + let mock = Mock() + controller.keyLabels = ["1", "2", "3", "4"] + _ = controller.delegate = mock + var result = controller.showNextPage() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 4) + result = controller.showNextPage() + XCTAssert(result == false) + XCTAssert(controller.selectedCandidateIndex == 4) + } + + func testShowPreviousPage() { + let controller = HorizontalCandidateController() + let mock = Mock() + controller.keyLabels = ["1", "2", "3", "4"] + controller.delegate = mock + _ = controller.showNextPage() + var result = controller.showPreviousPage() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 0) + result = controller.showPreviousPage() + XCTAssert(result == false) + XCTAssert(controller.selectedCandidateIndex == 0) + } + +} diff --git a/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift b/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift new file mode 100644 index 00000000..afc54c41 --- /dev/null +++ b/Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift @@ -0,0 +1,117 @@ +import XCTest +@testable import CandidateUI + +class VerticalCandidateControllerTests: XCTestCase { + + class Mock: CandidateControllerDelegate { + let candidates = ["A", "B", "C", "D", "E", "F", "G", "H"] + var selected: String? + func candidateCountForController(_ controller: CandidateController) -> UInt { + UInt(candidates.count) + } + + func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String { + candidates[Int(index)] + } + + func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) { + selected = candidates[Int(index)] + } + } + + func testReloadData() { + let controller = VerticalCandidateController() + let mock = Mock() + controller.delegate = mock + controller.keyLabels = ["1", "2", "3", "4"] + controller.reloadData() + XCTAssert(controller.selectedCandidateIndex == 0) + } + + func testHighlightNextCandidate() { + let controller = VerticalCandidateController() + let mock = Mock() + controller.keyLabels = ["1", "2", "3", "4"] + controller.delegate = mock + controller.reloadData() + var result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 1) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 2) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 3) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 4) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 5) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 6) + result = controller.highlightNextCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 7) + result = controller.highlightNextCandidate() + XCTAssert(result == false) + XCTAssert(controller.selectedCandidateIndex == 7) + } + + func testHighlightPreviousCandidate() { + let controller = VerticalCandidateController() + let mock = Mock() + controller.keyLabels = ["1", "2", "3", "4"] + controller.delegate = mock + _ = controller.showNextPage() + XCTAssert(controller.selectedCandidateIndex == 4) + var result = controller.highlightPreviousCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 3) + result = controller.highlightPreviousCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 2) + result = controller.highlightPreviousCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 1) + result = controller.highlightPreviousCandidate() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 0) + result = controller.highlightPreviousCandidate() + XCTAssert(result == false) + XCTAssert(controller.selectedCandidateIndex == 0) + } + + func testShowNextPage() { + let controller = VerticalCandidateController() + let mock = Mock() + controller.keyLabels = ["1", "2", "3", "4"] + _ = controller.delegate = mock + var result = controller.showNextPage() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 4) + result = controller.showNextPage() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 7) + result = controller.showNextPage() + XCTAssert(result == false) + XCTAssert(controller.selectedCandidateIndex == 7) + } + + func testShowPreviousPage() { + let controller = VerticalCandidateController() + let mock = Mock() + controller.keyLabels = ["1", "2", "3", "4"] + controller.delegate = mock + _ = controller.showNextPage() + var result = controller.showPreviousPage() + XCTAssert(result == true) + XCTAssert(controller.selectedCandidateIndex == 0) + result = controller.showPreviousPage() + XCTAssert(result == false) + XCTAssert(controller.selectedCandidateIndex == 0) + } + +}