This repository has been archived by the owner. It is now read-only.
Permalink
Cannot retrieve contributors at this time
executable file
253 lines (207 sloc)
9.66 KB
| /* This Source Code Form is subject to the terms of the Mozilla Public | |
| * License, v. 2.0. If a copy of the MPL was not distributed with this | |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
| import MappaMundi | |
| import XCTest | |
| class ScreenGraphTest: XCTestCase { | |
| var navigator: MMNavigator<TestUserState>! | |
| var app: XCUIApplication! | |
| override func setUp() { | |
| app = XCUIApplication() | |
| navigator = createTestGraph(for: self, with: app).navigator() | |
| app.terminate() | |
| app.launchArguments = [LaunchArguments.Test, LaunchArguments.ClearProfile, LaunchArguments.SkipIntro, LaunchArguments.SkipWhatsNew] | |
| app.activate() | |
| } | |
| } | |
| extension ScreenGraphTest { | |
| func testUserStateChanges() { | |
| XCTAssertNil(navigator.userState.url, "Current url is empty") | |
| navigator.userState.url = "https://mozilla.org" | |
| navigator.performAction(TestActions.LoadURLByTyping) | |
| // The UserState is mutated in BrowserTab. | |
| navigator.goto(BrowserTab) | |
| XCTAssertTrue(navigator.userState.url?.starts(with: "www.mozilla.org") ?? false, "Current url recorded by from the url bar is \(navigator.userState.url ?? "nil")") | |
| } | |
| func testBackStack() { | |
| // We'll go through the browser tab, through the menu. | |
| navigator.goto(SettingsScreen) | |
| // Going back, there is no explicit way back to the browser tab, | |
| // and the menu will have dismissed. We should be detecting the existence of | |
| // elements as we go through each screen state, so if there are errors, they'll be | |
| // reported in the graph below. | |
| navigator.goto(BrowserTab) | |
| } | |
| func testSimpleToggleAction() { | |
| // Switch night mode on, by toggling. | |
| navigator.performAction(TestActions.ToggleNightMode) | |
| XCTAssertTrue(navigator.userState.nightMode) | |
| navigator.back() | |
| XCTAssertEqual(navigator.screenState, BrowserTab) | |
| // Nothing should happen here, because night mode is already on. | |
| navigator.toggleOn(navigator.userState.nightMode, withAction: TestActions.ToggleNightMode) | |
| XCTAssertTrue(navigator.userState.nightMode) | |
| XCTAssertEqual(navigator.screenState, BrowserTab) | |
| // Switch night mode off. | |
| navigator.toggleOff(navigator.userState.nightMode, withAction: TestActions.ToggleNightMode) | |
| XCTAssertFalse(navigator.userState.nightMode) | |
| navigator.back() | |
| XCTAssertEqual(navigator.screenState, BrowserTab) | |
| } | |
| func testChainedActionPerf1() { | |
| let navigator = self.navigator! | |
| measure { | |
| navigator.userState.url = defaultURL | |
| navigator.performAction(TestActions.LoadURLByPasting) | |
| XCTAssertEqual(navigator.screenState, WebPageLoading) | |
| } | |
| } | |
| func testChainedActionPerf2() { | |
| let navigator = self.navigator! | |
| measure { | |
| navigator.userState.url = defaultURL | |
| navigator.performAction(TestActions.LoadURLByTyping) | |
| XCTAssertEqual(navigator.screenState, WebPageLoading) | |
| } | |
| navigator.userState.url = defaultURL | |
| navigator.performAction(TestActions.LoadURL) | |
| XCTAssertEqual(navigator.screenState, WebPageLoading) | |
| } | |
| func testConditionalEdgesSimple() { | |
| XCTAssertTrue(navigator.can(goto: PasscodeSettingsOff)) | |
| XCTAssertFalse(navigator.can(goto: PasscodeSettingsOn)) | |
| navigator.goto(PasscodeSettingsOff) | |
| XCTAssertEqual(navigator.screenState, PasscodeSettingsOff) | |
| } | |
| func testConditionalEdgesRerouting() { | |
| // The navigator should dynamically reroute to the target screen | |
| // if the userState changes. | |
| // This test adds to the graph a passcode setting screen. In that screen, | |
| // there is a noop action that fatalErrors if it is taken. | |
| // | |
| let map = createTestGraph(for: self, with: app) | |
| func typePasscode(_ passCode: String) { | |
| passCode.forEach { char in | |
| app.keys["\(char)"].tap() | |
| } | |
| } | |
| map.addScreenState(SetPasscodeScreen) { screenState in | |
| // This is a silly way to organize things here, | |
| // and is an artifical way to show that the navigator is re-routing midway through | |
| // a goto. | |
| screenState.onEnter() { userState in | |
| typePasscode(userState.newPasscode) | |
| typePasscode(userState.newPasscode) | |
| userState.passcode = userState.newPasscode | |
| } | |
| screenState.noop(forAction: "FatalError", transitionTo: PasscodeSettingsOn, if: "passcode == nil") { _ in fatalError() } | |
| screenState.noop(forAction: "Very", "Long", "Path", "Of", "Actions", transitionTo: PasscodeSettingsOn, if: "passcode != nil") { _ in } | |
| } | |
| navigator = map.navigator() | |
| XCTAssertTrue(navigator.can(goto: PasscodeSettingsOn)) | |
| XCTAssertTrue(navigator.can(goto: PasscodeSettingsOff)) | |
| XCTAssertTrue(navigator.can(goto: "FatalError")) | |
| navigator.goto(PasscodeSettingsOn) | |
| XCTAssertTrue(navigator.can(goto: PasscodeSettingsOn)) | |
| XCTAssertFalse(navigator.can(goto: PasscodeSettingsOff)) | |
| XCTAssertFalse(navigator.can(goto: "FatalError")) | |
| XCTAssertEqual(navigator.screenState, PasscodeSettingsOn) | |
| } | |
| } | |
| private let defaultURL = "https://example.com" | |
| class TestUserState: MMUserState { | |
| required init() { | |
| super.init() | |
| initialScreenState = FirstRun | |
| } | |
| var url: String? = nil | |
| var nightMode = false | |
| var passcode: String? = nil | |
| var newPasscode: String = "111111" | |
| } | |
| let PasscodeSettingsOn = "PasscodeSettingsOn" | |
| let PasscodeSettingsOff = "PasscodeSettingsOff" | |
| let WebPageLoading = "WebPageLoading" | |
| fileprivate class TestActions { | |
| static let ToggleNightMode = "menu-NightMode" | |
| static let LoadURL = "LoadURL" | |
| static let LoadURLByTyping = "LoadURLByTyping" | |
| static let LoadURLByPasting = "LoadURLByPasting" | |
| } | |
| private var isTablet: Bool { | |
| // There is more value in a variable having the same name, | |
| // so it can be used in both predicates and in code | |
| // than avoiding the duplication of one line of code. | |
| return UIDevice.current.userInterfaceIdiom == .pad | |
| } | |
| fileprivate func createTestGraph(for test: XCTestCase, with app: XCUIApplication) -> MMScreenGraph<TestUserState> { | |
| let map = MMScreenGraph(for: test, with: TestUserState.self) | |
| map.addScreenState(FirstRun) { screenState in | |
| screenState.noop(to: BrowserTab) | |
| screenState.tap(app.textFields["url"], to: URLBarOpen) | |
| } | |
| map.addScreenState(WebPageLoading) { screenState in | |
| screenState.dismissOnUse = true | |
| // Would like to use app.otherElements.deviceStatusBars.networkLoadingIndicators.element | |
| // but this means exposing some of SnapshotHelper to another target. | |
| screenState.onEnterWaitFor("exists != true", | |
| element: app.progressIndicators.element(boundBy: 0)) | |
| screenState.noop(to: BrowserTab) | |
| } | |
| map.addScreenState(BrowserTab) { screenState in | |
| screenState.onEnter { userState in | |
| userState.url = app.textFields["url"].value as? String | |
| } | |
| screenState.tap(app.buttons["TabToolbar.menuButton"], to: BrowserTabMenu) | |
| screenState.tap(app.textFields["url"], to: URLBarOpen) | |
| screenState.gesture(forAction: TestActions.LoadURLByPasting, TestActions.LoadURL) { userState in | |
| UIPasteboard.general.string = userState.url ?? defaultURL | |
| app.textFields["url"].press(forDuration: 1.0) | |
| app.tables["Context Menu"].cells["menu-PasteAndGo"].firstMatch.tap() | |
| } | |
| } | |
| map.addScreenState(URLBarOpen) { screenState in | |
| screenState.gesture(forAction: TestActions.LoadURLByTyping, TestActions.LoadURL) { userState in | |
| let urlString = userState.url ?? defaultURL | |
| app.textFields["address"].typeText("\(urlString)\r") | |
| } | |
| } | |
| map.addScreenAction(TestActions.LoadURL, transitionTo: WebPageLoading) | |
| map.addScreenState(BrowserTabMenu) { screenState in | |
| screenState.dismissOnUse = true | |
| screenState.onEnterWaitFor(element: app.tables["Context Menu"]) | |
| screenState.tap(app.tables.cells["Settings"], to: SettingsScreen) | |
| screenState.tap(app.cells["menu-NightMode"], forAction: TestActions.ToggleNightMode, transitionTo: BrowserTabMenu) { userState in | |
| userState.nightMode = !userState.nightMode | |
| } | |
| screenState.backAction = { | |
| if isTablet { | |
| // There is no Cancel option in iPad. | |
| app.otherElements["PopoverDismissRegion"].tap() | |
| } else { | |
| app.buttons["PhotonMenu.close"].tap() | |
| } | |
| } | |
| } | |
| let navigationControllerBackAction = { | |
| app.navigationBars.element(boundBy: 0).buttons.element(boundBy: 0).tap() | |
| } | |
| map.addScreenState(SettingsScreen) { screenState in | |
| let table = app.tables["AppSettingsTableViewController.tableView"] | |
| screenState.onEnterWaitFor(element: table) | |
| screenState.tap(table.cells["TouchIDPasscode"], to: PasscodeSettingsOff, if: "passcode == nil") | |
| screenState.tap(table.cells["TouchIDPasscode"], to: PasscodeSettingsOn, if: "passcode != nil") | |
| screenState.backAction = navigationControllerBackAction | |
| } | |
| map.addScreenState(PasscodeSettingsOn) { screenState in | |
| screenState.backAction = navigationControllerBackAction | |
| } | |
| map.addScreenState(PasscodeSettingsOff) { screenState in | |
| screenState.tap(app.staticTexts["Turn Passcode On"], to: SetPasscodeScreen) | |
| screenState.backAction = navigationControllerBackAction | |
| } | |
| map.addScreenState(SetPasscodeScreen) { screenState in | |
| screenState.backAction = navigationControllerBackAction | |
| } | |
| return map | |
| } |