-
-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## 📜 Description Added native unit tests on iOS. ## 💡 Motivation and Context ### Where to write unit tests? The problem with current integration of unit-tests on iOS is the fact that we depends on `react` dependency heavily which is available in `example` or `FabricExample` projects. Of course I didn't want to duplicate tests (and there is no clear option on how to add them for `KeyboardController.xcodeproj` i. e. root) so I decided to create a new "HelloWorld" project and link files that I want to test. Fortunately current codebase is designed quite well (thanks for supporting both architectures 😀), so I have a separated business-logic (keyboard frame tracking etc.) and consumers (react views/bridge bindings). So I didn't make any modifications to the source code. The only one "suspicious" thing is adding extension for `reactTag` property (I rely on it in Extensions file and Extension file needs to be linked since `ViewHierarchyNavigator` uses `UIResponder.current` extension). ### Unit tests approach I decided to test everything as unit tests. Of course some of methods, such as `becomeFirstResponder` doesn't work (actually they work, but there is no way to check that method has been called as we can do in jest - and by default iOS doesn't provide any mocks). So I've created `TestableTextField`/`TestableTextView` and for hierarchy creation used these classes. Some of corresponding Android tests can not be ported to iOS: - get all inputs -> relies on window usage, and actual window doesn't contain any TextInputs -> most likely we need to pass params and pass a mock from unit tests but I don't want to change source code right now - testSetFocusToNextDoesNothingIfLastElement -> there is no way to check that view keeps focus - testSetFocusToPrevDoesNothingIfFirstElement -> there is no way to check that view keeps focus But overall I'm happy to have at least something to have a better protection over accident changes that can break the library code. ### CI integration Also I added a job on CI to run these tests continuously. ## 📢 Changelog ### CI - added a job for running unit-tests; ### iOS - added `KeyboardControllerNative` project; - excluded `KeyboardControllerNative` from Pods (to prevent compilation errors); ### JS - excluded `KeyboardControllerNative` from being published to `npm`; ## 🤔 How Has This Been Tested? Tested locally and on CI. ## 📝 Checklist - [ ] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
- Loading branch information
1 parent
2e66fa0
commit 1c3ae50
Showing
16 changed files
with
1,000 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
633 changes: 633 additions & 0 deletions
633
ios/KeyboardControllerNative/KeyboardControllerNative.xcodeproj/project.pbxproj
Large diffs are not rendered by default.
Oops, something went wrong.
11 changes: 11 additions & 0 deletions
11
...trollerNative/KeyboardControllerNative/Assets.xcassets/AccentColor.colorset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"colors": [ | ||
{ | ||
"idiom": "universal" | ||
} | ||
], | ||
"info": { | ||
"author": "xcode", | ||
"version": 1 | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
...ontrollerNative/KeyboardControllerNative/Assets.xcassets/AppIcon.appiconset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"images": [ | ||
{ | ||
"idiom": "universal", | ||
"platform": "ios", | ||
"size": "1024x1024" | ||
} | ||
], | ||
"info": { | ||
"author": "xcode", | ||
"version": 1 | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
ios/KeyboardControllerNative/KeyboardControllerNative/Assets.xcassets/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"info": { | ||
"author": "xcode", | ||
"version": 1 | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
ios/KeyboardControllerNative/KeyboardControllerNative/ContentView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// | ||
// ContentView.swift | ||
// KeyboardControllerNative | ||
// | ||
// Created by Kiryl Ziusko on 29/03/2024. | ||
// | ||
|
||
import SwiftUI | ||
|
||
struct ContentView: View { | ||
var body: some View { | ||
VStack { | ||
Image(systemName: "globe") | ||
.imageScale(.large) | ||
.foregroundStyle(.tint) | ||
Text("Hello, world!") | ||
} | ||
.padding() | ||
} | ||
} | ||
|
||
#Preview { | ||
ContentView() | ||
} |
15 changes: 15 additions & 0 deletions
15
ios/KeyboardControllerNative/KeyboardControllerNative/Extension+UIView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// Extension+UIView.swift | ||
// Tests | ||
// | ||
// Created by Kiryl Ziusko on 21/02/2024. | ||
// | ||
|
||
import Foundation | ||
import UIKit | ||
|
||
public extension UIView { | ||
var reactTag: NSNumber { | ||
return tag as NSNumber | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
ios/KeyboardControllerNative/KeyboardControllerNative/KeyboardControllerNativeApp.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// | ||
// KeyboardControllerNativeApp.swift | ||
// KeyboardControllerNative | ||
// | ||
// Created by Kiryl Ziusko on 29/03/2024. | ||
// | ||
|
||
import SwiftUI | ||
|
||
@main | ||
struct KeyboardControllerNativeApp: App { | ||
var body: some Scene { | ||
WindowGroup { | ||
ContentView() | ||
} | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
...llerNative/KeyboardControllerNative/Preview Content/Preview Assets.xcassets/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"info": { | ||
"author": "xcode", | ||
"version": 1 | ||
} | ||
} |
175 changes: 175 additions & 0 deletions
175
...eyboardControllerNative/KeyboardControllerNativeTests/KeyboardControllerNativeTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
// | ||
// KeyboardControllerNativeTests.swift | ||
// KeyboardControllerNativeTests | ||
// | ||
// Created by Kiryl Ziusko on 29/03/2024. | ||
// | ||
|
||
@testable import KeyboardControllerNative | ||
import XCTest | ||
|
||
extension XCTestCase { | ||
func waitForFocusChange( | ||
to textField: TestableInput, | ||
timeout: TimeInterval = 10.0, | ||
file: StaticString = #file, | ||
line: UInt = #line | ||
) { | ||
let expectation = XCTestExpectation(description: "Wait for focus change to \(textField.tag)") | ||
|
||
XCTAssertFalse( | ||
textField.becomeFirstResponderCalled, | ||
"Expected focus shouldn't be initially set for tag \(textField.tag)" | ||
) | ||
|
||
DispatchQueue.main.async { | ||
XCTAssertTrue( | ||
textField.becomeFirstResponderCalled, | ||
"Expected focus to be set to text field with tag \(textField.tag)", | ||
file: file, | ||
line: line | ||
) | ||
expectation.fulfill() | ||
} | ||
|
||
wait(for: [expectation], timeout: timeout) | ||
} | ||
} | ||
|
||
protocol TestableInput: UIView, TextInput { | ||
var becomeFirstResponderCalled: Bool { get set } | ||
func becomeFirstResponder() -> Bool | ||
} | ||
|
||
class TestableTextField: UITextField, TestableInput { | ||
var becomeFirstResponderCalled = false | ||
|
||
override func becomeFirstResponder() -> Bool { | ||
becomeFirstResponderCalled = true | ||
return super.becomeFirstResponder() | ||
} | ||
} | ||
|
||
class TestableTextView: UITextView, TestableInput { | ||
var becomeFirstResponderCalled = false | ||
|
||
override func becomeFirstResponder() -> Bool { | ||
becomeFirstResponderCalled = true | ||
return super.becomeFirstResponder() | ||
} | ||
} | ||
|
||
final class KeyboardControllerNativeTests: XCTestCase { | ||
var rootView: UIView! | ||
var textFields: [TestableInput]! | ||
|
||
override func setUpWithError() throws { | ||
super.setUp() | ||
|
||
rootView = UIView() | ||
textFields = (1 ... 13).map { tag in | ||
let textField = (tag % 2 == 0 ? TestableTextField() : TestableTextView()) as TestableInput | ||
textField.tag = tag | ||
let isEditable = tag != 3 && tag != 4 // Assuming ids 3 and 4 are not editable, similar to our Android test | ||
(textField as? UITextField)?.isEnabled = isEditable | ||
(textField as? UITextView)?.isEditable = isEditable | ||
|
||
return textField | ||
} | ||
|
||
let subView = UIView() | ||
for (index, textField) in textFields.enumerated() { | ||
if index == 4 { | ||
rootView.addSubview(subView) | ||
} | ||
if index >= 4, index <= 6 { | ||
subView.addSubview(textField) | ||
} else { | ||
rootView.addSubview(textField) | ||
} | ||
} | ||
} | ||
|
||
override func tearDownWithError() throws { | ||
// Put teardown code here. This method is called after the invocation of each test method in the class. | ||
} | ||
|
||
func testSetFocusToNextShouldSetFocusToNextField() throws { | ||
let textInput1 = textFields[0] | ||
FocusedInputHolder.shared.set(textInput1) | ||
|
||
ViewHierarchyNavigator.setFocusTo(direction: "next") | ||
|
||
waitForFocusChange(to: textFields[1]) | ||
} | ||
|
||
func testSetFocusToPrevShouldSetFocusToPreviousField() throws { | ||
let textInput2 = textFields[1] | ||
FocusedInputHolder.shared.set(textInput2) | ||
|
||
ViewHierarchyNavigator.setFocusTo(direction: "prev") | ||
|
||
waitForFocusChange(to: textFields[0]) | ||
} | ||
|
||
func testSetFocusToNextShouldSkipNonEditableFields() throws { | ||
let textInput2 = textFields[1] | ||
FocusedInputHolder.shared.set(textInput2) | ||
|
||
ViewHierarchyNavigator.setFocusTo(direction: "next") | ||
|
||
waitForFocusChange(to: textFields[4]) | ||
} | ||
|
||
func testSetFocusToPrevShouldSkipNonEditableFields() throws { | ||
let textInput5 = textFields[4] | ||
FocusedInputHolder.shared.set(textInput5) | ||
|
||
ViewHierarchyNavigator.setFocusTo(direction: "prev") | ||
|
||
waitForFocusChange(to: textFields[1]) | ||
} | ||
|
||
func testSetFocusToNextWithinGroup() throws { | ||
let textInput5 = textFields[4] | ||
FocusedInputHolder.shared.set(textInput5) | ||
|
||
ViewHierarchyNavigator.setFocusTo(direction: "next") | ||
|
||
waitForFocusChange(to: textFields[5]) | ||
} | ||
|
||
func testSetFocusToPrevWithinGroup() throws { | ||
let textInput6 = textFields[5] | ||
FocusedInputHolder.shared.set(textInput6) | ||
|
||
ViewHierarchyNavigator.setFocusTo(direction: "prev") | ||
|
||
waitForFocusChange(to: textFields[4]) | ||
} | ||
|
||
func testSetFocusToNextExitsGroup() throws { | ||
let textInput7 = textFields[6] | ||
FocusedInputHolder.shared.set(textInput7) | ||
|
||
ViewHierarchyNavigator.setFocusTo(direction: "next") | ||
|
||
waitForFocusChange(to: textFields[7]) | ||
} | ||
|
||
func testSetFocusToPrevEntersGroupAtLastElement() throws { | ||
let textInput8 = textFields[7] | ||
FocusedInputHolder.shared.set(textInput8) | ||
|
||
ViewHierarchyNavigator.setFocusTo(direction: "prev") | ||
|
||
waitForFocusChange(to: textFields[6]) | ||
} | ||
|
||
func testPerformanceExample() throws { | ||
// This is an example of a performance test case. | ||
measure { | ||
// Put the code you want to measure the time of here. | ||
} | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
...ardControllerNative/KeyboardControllerNativeUITests/KeyboardControllerNativeUITests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// | ||
// KeyboardControllerNativeUITests.swift | ||
// KeyboardControllerNativeUITests | ||
// | ||
// Created by Kiryl Ziusko on 29/03/2024. | ||
// | ||
|
||
import XCTest | ||
|
||
final class KeyboardControllerNativeUITests: XCTestCase { | ||
override func setUpWithError() throws { | ||
// Put setup code here. This method is called before the invocation of each test method in the class. | ||
|
||
// In UI tests it is usually best to stop immediately when a failure occurs. | ||
continueAfterFailure = false | ||
|
||
// In UI tests it’s important to set the initial state - such as interface orientation, | ||
// which required for your tests before they run. The setUp method is a good place to do this. | ||
} | ||
|
||
override func tearDownWithError() throws { | ||
// Put teardown code here. This method is called after the invocation of each test method in the class. | ||
} | ||
|
||
func testExample() throws { | ||
// UI tests must launch the application that they test. | ||
let app = XCUIApplication() | ||
app.launch() | ||
|
||
// Use XCTAssert and related functions to verify your tests produce the correct results. | ||
} | ||
|
||
func testLaunchPerformance() throws { | ||
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { | ||
// This measures how long it takes to launch your application. | ||
measure(metrics: [XCTApplicationLaunchMetric()]) { | ||
XCUIApplication().launch() | ||
} | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
...erNative/KeyboardControllerNativeUITests/KeyboardControllerNativeUITestsLaunchTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// | ||
// KeyboardControllerNativeUITestsLaunchTests.swift | ||
// KeyboardControllerNativeUITests | ||
// | ||
// Created by Kiryl Ziusko on 29/03/2024. | ||
// | ||
|
||
import XCTest | ||
|
||
// swiftlint:disable:next type_name | ||
final class KeyboardControllerNativeUITestsLaunchTests: XCTestCase { | ||
override class var runsForEachTargetApplicationUIConfiguration: Bool { | ||
true | ||
} | ||
|
||
override func setUpWithError() throws { | ||
continueAfterFailure = false | ||
} | ||
|
||
func testLaunch() throws { | ||
let app = XCUIApplication() | ||
app.launch() | ||
|
||
// Insert steps here to perform after app launch but before taking a screenshot, | ||
// such as logging into a test account or navigating somewhere in the app | ||
|
||
let attachment = XCTAttachment(screenshot: app.screenshot()) | ||
attachment.name = "Launch Screen" | ||
attachment.lifetime = .keepAlways | ||
add(attachment) | ||
} | ||
} |
Oops, something went wrong.