From db2334489027f932da4067f4945f7e5a31438b7f Mon Sep 17 00:00:00 2001 From: Daniel Peter Date: Tue, 23 Feb 2021 10:37:22 +0100 Subject: [PATCH] UI tests (#46) * Add UI tests * Use Fastlane to run tests * Enable parallel testing --- .github/workflows/test.yml | 5 +- Dangerfile | 3 +- Example/Example.xcodeproj/project.pbxproj | 261 +++++++++++++++--- .../xcshareddata/swiftpm/Package.resolved | 45 +++ .../xcshareddata/xcschemes/Example.xcscheme | 24 +- .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../AccessibilityIdentifier.swift | 77 ++++++ .../Example/Detail Screen/DetailView.swift | 51 ++-- Example/Example/ExampleApp.swift | 6 +- Example/Example/Home Screen/HomeView.swift | 10 +- .../NavigationShortcuts.swift | 8 + .../NavigationShortcutsView.swift | 4 +- .../Settings Screen/SettingsView.swift | 18 +- Example/ExampleUITests/ExampleUITests.swift | 114 ++++++++ Example/ExampleUITests/Info.plist | 22 ++ Example/ExampleUITests/Screens/Base.swift | 14 + Example/ExampleUITests/Screens/Detail.swift | 55 ++++ Example/ExampleUITests/Screens/Home.swift | 42 +++ .../Screens/NavigationShortcuts.swift | 63 +++++ Example/ExampleUITests/Screens/Settings.swift | 57 ++++ .../Screens/XCUIApplication+BackButton.swift | 7 + .../XCUIElement+ExistsAfterTimeout.swift | 21 ++ ...IElementQuery+AccessibiltyIdentifier.swift | 8 + Example/FullTests.xctestplan | 49 ++++ Example/UITests.xctestplan | 24 ++ Example/UnitTests.xctestplan | 42 +++ Gemfile | 3 +- Gemfile.lock | 8 +- Makefile | 11 +- fastlane/Fastfile | 27 ++ 31 files changed, 1017 insertions(+), 80 deletions(-) create mode 100644 Example/Example.xcworkspace/contents.xcworkspacedata create mode 100644 Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Example/Example/Accessibility/AccessibilityIdentifier.swift create mode 100644 Example/ExampleUITests/ExampleUITests.swift create mode 100644 Example/ExampleUITests/Info.plist create mode 100644 Example/ExampleUITests/Screens/Base.swift create mode 100644 Example/ExampleUITests/Screens/Detail.swift create mode 100644 Example/ExampleUITests/Screens/Home.swift create mode 100644 Example/ExampleUITests/Screens/NavigationShortcuts.swift create mode 100644 Example/ExampleUITests/Screens/Settings.swift create mode 100644 Example/ExampleUITests/Screens/XCUIApplication+BackButton.swift create mode 100644 Example/ExampleUITests/Screens/XCUIElement+ExistsAfterTimeout.swift create mode 100644 Example/ExampleUITests/Screens/XCUIElementQuery+AccessibiltyIdentifier.swift create mode 100644 Example/FullTests.xctestplan create mode 100644 Example/UITests.xctestplan create mode 100644 Example/UnitTests.xctestplan create mode 100644 fastlane/Fastfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4fbb851..b078a8c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,12 +22,9 @@ jobs: bundler --version - name: Run tests - run: make test - - - name: danger env: DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: bundle exec danger + run: make test - name: Clean up run: make cleanup diff --git a/Dangerfile b/Dangerfile index 11197e1..f643dc2 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,5 +1,6 @@ xcov.report( - scheme: 'swift-composable-navigator-Package', + workspace: 'Example/Example.xcworkspace', + scheme: 'Example', derived_data_path: './Build', html_report: false, include_targets: 'ComposableNavigator.framework, ComposableNavigatorTCA.framework, ComposableDeeplinking.framework' diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 0a6af89..5aadd40 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -3,16 +3,14 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ - 23258303256D2524001A191B /* ComposableNavigator in Frameworks */ = {isa = PBXBuildFile; productRef = 23258302256D2524001A191B /* ComposableNavigator */; }; 235F93F525D3D4600045E37D /* NavigationShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 235F93F425D3D4600045E37D /* NavigationShortcutsView.swift */; }; 235F93FA25D3D7770045E37D /* NavigationShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 235F93F925D3D7770045E37D /* NavigationShortcuts.swift */; }; 236458A125D41D4A00190D4D /* AppDeeplinkParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236458A025D41D4A00190D4D /* AppDeeplinkParser.swift */; }; 236458A425D41D7A00190D4D /* HomeSettingsDeeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236458A325D41D7A00190D4D /* HomeSettingsDeeplink.swift */; }; - 236458A725D41E1B00190D4D /* ComposableDeeplinking in Frameworks */ = {isa = PBXBuildFile; productRef = 236458A625D41E1B00190D4D /* ComposableDeeplinking */; }; 236458AB25D41F1600190D4D /* DetailsDeeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236458AA25D41F1600190D4D /* DetailsDeeplink.swift */; }; 236458AE25D41F8000190D4D /* DetailsSettingsDeeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236458AD25D41F8000190D4D /* DetailsSettingsDeeplink.swift */; }; 236E65802559498C00C19B46 /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236E657F2559498C00C19B46 /* ExampleApp.swift */; }; @@ -21,10 +19,52 @@ 23A5B5B5255982A300B319DA /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A5B5B4255982A300B319DA /* HomeView.swift */; }; 23A5B5BB2559846B00B319DA /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A5B5BA2559846B00B319DA /* DetailView.swift */; }; 23A5B5C02559866000B319DA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A5B5BF2559866000B319DA /* SettingsView.swift */; }; + 23A9623F25E4098400AF0676 /* ComposableDeeplinking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 23A9623C25E4098400AF0676 /* ComposableDeeplinking.framework */; }; + 23A9624025E4098400AF0676 /* ComposableDeeplinking.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 23A9623C25E4098400AF0676 /* ComposableDeeplinking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 23A9624125E4098400AF0676 /* ComposableNavigator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 23A9623D25E4098400AF0676 /* ComposableNavigator.framework */; }; + 23A9624225E4098400AF0676 /* ComposableNavigator.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 23A9623D25E4098400AF0676 /* ComposableNavigator.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 23A9624325E4098400AF0676 /* ComposableNavigatorTCA.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 23A9623E25E4098400AF0676 /* ComposableNavigatorTCA.framework */; }; + 23A9624425E4098400AF0676 /* ComposableNavigatorTCA.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 23A9623E25E4098400AF0676 /* ComposableNavigatorTCA.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 23B1557E25E3F12700E3B9F7 /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23B1557D25E3F12700E3B9F7 /* Home.swift */; }; 23B7589E255949F60073D19F /* AppCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236E6592255949DA00C19B46 /* AppCore.swift */; }; - 23D1112325875790008DF0AA /* ComposableNavigatorTCA in Frameworks */ = {isa = PBXBuildFile; productRef = 23D1112225875790008DF0AA /* ComposableNavigatorTCA */; }; + 23C9A49A25E3A140005942F8 /* ExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C9A49925E3A140005942F8 /* ExampleUITests.swift */; }; + 23C9A4AB25E3A1B1005942F8 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C9A4AA25E3A1B1005942F8 /* Settings.swift */; }; + 23C9A4AF25E3A1CC005942F8 /* Detail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C9A4AE25E3A1CC005942F8 /* Detail.swift */; }; + 23C9A4B325E3A1DA005942F8 /* NavigationShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C9A4B225E3A1DA005942F8 /* NavigationShortcuts.swift */; }; + 23C9A4BA25E3A2EB005942F8 /* AccessibilityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C9A4B925E3A2EB005942F8 /* AccessibilityIdentifier.swift */; }; + 23C9A4BE25E3A4B5005942F8 /* XCUIElementQuery+AccessibiltyIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C9A4BD25E3A4B5005942F8 /* XCUIElementQuery+AccessibiltyIdentifier.swift */; }; + 23C9A4C625E3A748005942F8 /* XCUIElement+ExistsAfterTimeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C9A4C525E3A748005942F8 /* XCUIElement+ExistsAfterTimeout.swift */; }; + 23C9A4CC25E3A945005942F8 /* Base.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C9A4CB25E3A945005942F8 /* Base.swift */; }; + 23C9A4D125E3AA2F005942F8 /* AccessibilityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C9A4B925E3A2EB005942F8 /* AccessibilityIdentifier.swift */; }; + 23C9A4D725E3AFA9005942F8 /* XCUIApplication+BackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C9A4D625E3AFA9005942F8 /* XCUIApplication+BackButton.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 23C9A49C25E3A140005942F8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 236E65742559498C00C19B46 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 236E657B2559498C00C19B46; + remoteInfo = Example; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 23A9624525E4098400AF0676 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 23A9624025E4098400AF0676 /* ComposableDeeplinking.framework in Embed Frameworks */, + 23A9624425E4098400AF0676 /* ComposableNavigatorTCA.framework in Embed Frameworks */, + 23A9624225E4098400AF0676 /* ComposableNavigator.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 235F93F425D3D4600045E37D /* NavigationShortcutsView.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = NavigationShortcutsView.swift; sourceTree = ""; tabWidth = 2; }; 235F93F925D3D7770045E37D /* NavigationShortcuts.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = NavigationShortcuts.swift; sourceTree = ""; tabWidth = 2; }; @@ -37,11 +77,28 @@ 236E65832559498D00C19B46 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 236E65862559498D00C19B46 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 236E65882559498D00C19B46 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 236E6590255949C500C19B46 /* swift-composable-coordinator */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "swift-composable-coordinator"; path = ..; sourceTree = ""; }; 236E6592255949DA00C19B46 /* AppCore.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = AppCore.swift; sourceTree = ""; tabWidth = 2; }; 23A5B5B4255982A300B319DA /* HomeView.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; tabWidth = 2; }; 23A5B5BA2559846B00B319DA /* DetailView.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = ""; tabWidth = 2; }; 23A5B5BF2559866000B319DA /* SettingsView.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; tabWidth = 2; }; + 23A9623C25E4098400AF0676 /* ComposableDeeplinking.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ComposableDeeplinking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 23A9623D25E4098400AF0676 /* ComposableNavigator.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ComposableNavigator.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 23A9623E25E4098400AF0676 /* ComposableNavigatorTCA.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ComposableNavigatorTCA.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 23B1557D25E3F12700E3B9F7 /* Home.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Home.swift; sourceTree = ""; }; + 23BECD9925E3D31400A45D19 /* FullTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = FullTests.xctestplan; sourceTree = ""; }; + 23C90D7F25E3C6320076FA48 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; + 23C9A49725E3A140005942F8 /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 23C9A49925E3A140005942F8 /* ExampleUITests.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ExampleUITests.swift; sourceTree = ""; tabWidth = 2; }; + 23C9A49B25E3A140005942F8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 23C9A4AA25E3A1B1005942F8 /* Settings.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; tabWidth = 2; }; + 23C9A4AE25E3A1CC005942F8 /* Detail.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Detail.swift; sourceTree = ""; tabWidth = 2; }; + 23C9A4B225E3A1DA005942F8 /* NavigationShortcuts.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = NavigationShortcuts.swift; sourceTree = ""; tabWidth = 2; }; + 23C9A4B925E3A2EB005942F8 /* AccessibilityIdentifier.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifier.swift; sourceTree = ""; tabWidth = 2; }; + 23C9A4BD25E3A4B5005942F8 /* XCUIElementQuery+AccessibiltyIdentifier.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = "XCUIElementQuery+AccessibiltyIdentifier.swift"; sourceTree = ""; tabWidth = 2; }; + 23C9A4C525E3A748005942F8 /* XCUIElement+ExistsAfterTimeout.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = "XCUIElement+ExistsAfterTimeout.swift"; sourceTree = ""; tabWidth = 2; }; + 23C9A4CB25E3A945005942F8 /* Base.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Base.swift; sourceTree = ""; tabWidth = 2; }; + 23C9A4D625E3AFA9005942F8 /* XCUIApplication+BackButton.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = "XCUIApplication+BackButton.swift"; sourceTree = ""; tabWidth = 2; }; + 23D8C9E825E3C8E200D367D2 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,9 +106,16 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 23D1112325875790008DF0AA /* ComposableNavigatorTCA in Frameworks */, - 23258303256D2524001A191B /* ComposableNavigator in Frameworks */, - 236458A725D41E1B00190D4D /* ComposableDeeplinking in Frameworks */, + 23A9623F25E4098400AF0676 /* ComposableDeeplinking.framework in Frameworks */, + 23A9624325E4098400AF0676 /* ComposableNavigatorTCA.framework in Frameworks */, + 23A9624125E4098400AF0676 /* ComposableNavigator.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 23C9A49425E3A140005942F8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; @@ -81,8 +145,11 @@ 236E65732559498C00C19B46 = { isa = PBXGroup; children = ( - 236E6590255949C500C19B46 /* swift-composable-coordinator */, + 23D8C9E825E3C8E200D367D2 /* UITests.xctestplan */, + 23BECD9925E3D31400A45D19 /* FullTests.xctestplan */, + 23C90D7F25E3C6320076FA48 /* UnitTests.xctestplan */, 236E657E2559498C00C19B46 /* Example */, + 23C9A49825E3A140005942F8 /* ExampleUITests */, 236E657D2559498C00C19B46 /* Products */, 23B758A025594A010073D19F /* Frameworks */, ); @@ -92,6 +159,7 @@ isa = PBXGroup; children = ( 236E657C2559498C00C19B46 /* Example.app */, + 23C9A49725E3A140005942F8 /* ExampleUITests.xctest */, ); name = Products; sourceTree = ""; @@ -99,6 +167,7 @@ 236E657E2559498C00C19B46 /* Example */ = { isa = PBXGroup; children = ( + 23C9A4B825E3A2D7005942F8 /* Accessibility */, 2364589E25D41C2200190D4D /* Deeplinking */, 235F93FD25D3ECC00045E37D /* Navigation Shortcuts */, 23A5B5BE2559865600B319DA /* Settings Screen */, @@ -148,10 +217,46 @@ 23B758A025594A010073D19F /* Frameworks */ = { isa = PBXGroup; children = ( + 23A9623C25E4098400AF0676 /* ComposableDeeplinking.framework */, + 23A9623D25E4098400AF0676 /* ComposableNavigator.framework */, + 23A9623E25E4098400AF0676 /* ComposableNavigatorTCA.framework */, ); name = Frameworks; sourceTree = ""; }; + 23C9A49825E3A140005942F8 /* ExampleUITests */ = { + isa = PBXGroup; + children = ( + 23C9A4A325E3A1A1005942F8 /* Screens */, + 23C9A49925E3A140005942F8 /* ExampleUITests.swift */, + 23C9A49B25E3A140005942F8 /* Info.plist */, + ); + path = ExampleUITests; + sourceTree = ""; + }; + 23C9A4A325E3A1A1005942F8 /* Screens */ = { + isa = PBXGroup; + children = ( + 23B1557D25E3F12700E3B9F7 /* Home.swift */, + 23C9A4CB25E3A945005942F8 /* Base.swift */, + 23C9A4AA25E3A1B1005942F8 /* Settings.swift */, + 23C9A4AE25E3A1CC005942F8 /* Detail.swift */, + 23C9A4B225E3A1DA005942F8 /* NavigationShortcuts.swift */, + 23C9A4BD25E3A4B5005942F8 /* XCUIElementQuery+AccessibiltyIdentifier.swift */, + 23C9A4C525E3A748005942F8 /* XCUIElement+ExistsAfterTimeout.swift */, + 23C9A4D625E3AFA9005942F8 /* XCUIApplication+BackButton.swift */, + ); + path = Screens; + sourceTree = ""; + }; + 23C9A4B825E3A2D7005942F8 /* Accessibility */ = { + isa = PBXGroup; + children = ( + 23C9A4B925E3A2EB005942F8 /* AccessibilityIdentifier.swift */, + ); + path = Accessibility; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -162,6 +267,7 @@ 236E65782559498C00C19B46 /* Sources */, 236E65792559498C00C19B46 /* Frameworks */, 236E657A2559498C00C19B46 /* Resources */, + 23A9624525E4098400AF0676 /* Embed Frameworks */, ); buildRules = ( ); @@ -169,26 +275,45 @@ ); name = Example; packageProductDependencies = ( - 23258302256D2524001A191B /* ComposableNavigator */, - 23D1112225875790008DF0AA /* ComposableNavigatorTCA */, - 236458A625D41E1B00190D4D /* ComposableDeeplinking */, ); productName = Example; productReference = 236E657C2559498C00C19B46 /* Example.app */; productType = "com.apple.product-type.application"; }; + 23C9A49625E3A140005942F8 /* ExampleUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 23C9A49E25E3A140005942F8 /* Build configuration list for PBXNativeTarget "ExampleUITests" */; + buildPhases = ( + 23C9A49325E3A140005942F8 /* Sources */, + 23C9A49425E3A140005942F8 /* Frameworks */, + 23C9A49525E3A140005942F8 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 23C9A49D25E3A140005942F8 /* PBXTargetDependency */, + ); + name = ExampleUITests; + productName = ExampleUITests; + productReference = 23C9A49725E3A140005942F8 /* ExampleUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 236E65742559498C00C19B46 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1210; + LastSwiftUpdateCheck = 1240; LastUpgradeCheck = 1210; TargetAttributes = { 236E657B2559498C00C19B46 = { CreatedOnToolsVersion = 12.1; }; + 23C9A49625E3A140005942F8 = { + CreatedOnToolsVersion = 12.4; + TestTargetID = 236E657B2559498C00C19B46; + }; }; }; buildConfigurationList = 236E65772559498C00C19B46 /* Build configuration list for PBXProject "Example" */; @@ -200,11 +325,14 @@ Base, ); mainGroup = 236E65732559498C00C19B46; + packageReferences = ( + ); productRefGroup = 236E657D2559498C00C19B46 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 236E657B2559498C00C19B46 /* Example */, + 23C9A49625E3A140005942F8 /* ExampleUITests */, ); }; /* End PBXProject section */ @@ -219,6 +347,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 23C9A49525E3A140005942F8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -233,6 +368,7 @@ 235F93FA25D3D7770045E37D /* NavigationShortcuts.swift in Sources */, 236458A125D41D4A00190D4D /* AppDeeplinkParser.swift in Sources */, 236E65802559498C00C19B46 /* ExampleApp.swift in Sources */, + 23C9A4BA25E3A2EB005942F8 /* AccessibilityIdentifier.swift in Sources */, 235F93F525D3D4600045E37D /* NavigationShortcutsView.swift in Sources */, 236458A425D41D7A00190D4D /* HomeSettingsDeeplink.swift in Sources */, 23B7589E255949F60073D19F /* AppCore.swift in Sources */, @@ -240,8 +376,33 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 23C9A49325E3A140005942F8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 23C9A4D725E3AFA9005942F8 /* XCUIApplication+BackButton.swift in Sources */, + 23C9A4CC25E3A945005942F8 /* Base.swift in Sources */, + 23B1557E25E3F12700E3B9F7 /* Home.swift in Sources */, + 23C9A4B325E3A1DA005942F8 /* NavigationShortcuts.swift in Sources */, + 23C9A4AB25E3A1B1005942F8 /* Settings.swift in Sources */, + 23C9A4D125E3AA2F005942F8 /* AccessibilityIdentifier.swift in Sources */, + 23C9A4C625E3A748005942F8 /* XCUIElement+ExistsAfterTimeout.swift in Sources */, + 23C9A4BE25E3A4B5005942F8 /* XCUIElementQuery+AccessibiltyIdentifier.swift in Sources */, + 23C9A4AF25E3A1CC005942F8 /* Detail.swift in Sources */, + 23C9A49A25E3A140005942F8 /* ExampleUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 23C9A49D25E3A140005942F8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 236E657B2559498C00C19B46 /* Example */; + targetProxy = 23C9A49C25E3A140005942F8 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 236E65892559498D00C19B46 /* Debug */ = { isa = XCBuildConfiguration; @@ -364,8 +525,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Example/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -373,8 +535,9 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = feedback.Example; + PRODUCT_BUNDLE_IDENTIFIER = de.bahnx.composable.navigatior.Example; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -385,8 +548,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Example/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -394,13 +558,52 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = feedback.Example; + PRODUCT_BUNDLE_IDENTIFIER = de.bahnx.composable.navigatior.Example; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; + 23C9A49F25E3A140005942F8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = ExampleUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = de.bahnx.composable.navigatior.example.ui.tests.ExampleUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Example; + }; + name = Debug; + }; + 23C9A4A025E3A140005942F8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = ExampleUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = de.bahnx.composable.navigatior.example.ui.tests.ExampleUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Example; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -422,22 +625,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; -/* End XCConfigurationList section */ - -/* Begin XCSwiftPackageProductDependency section */ - 23258302256D2524001A191B /* ComposableNavigator */ = { - isa = XCSwiftPackageProductDependency; - productName = ComposableNavigator; - }; - 236458A625D41E1B00190D4D /* ComposableDeeplinking */ = { - isa = XCSwiftPackageProductDependency; - productName = ComposableDeeplinking; - }; - 23D1112225875790008DF0AA /* ComposableNavigatorTCA */ = { - isa = XCSwiftPackageProductDependency; - productName = ComposableNavigatorTCA; + 23C9A49E25E3A140005942F8 /* Build configuration list for PBXNativeTarget "ExampleUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 23C9A49F25E3A140005942F8 /* Debug */, + 23C9A4A025E3A140005942F8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; -/* End XCSwiftPackageProductDependency section */ +/* End XCConfigurationList section */ }; rootObject = 236E65742559498C00C19B46 /* Project object */; } diff --git a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 74756c6..1e5479e 100644 --- a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -10,6 +10,33 @@ "version": "0.2.0" } }, + { + "package": "Logger", + "repositoryURL": "https://github.com/shibapm/Logger", + "state": { + "branch": null, + "revision": "53c3ecca5abe8cf46697e33901ee774236d94cce", + "version": "0.2.3" + } + }, + { + "package": "PackageConfig", + "repositoryURL": "https://github.com/shibapm/PackageConfig.git", + "state": { + "branch": null, + "revision": "bf90dc69fa0792894b08a0b74cf34029694ae486", + "version": "0.13.0" + } + }, + { + "package": "Rocket", + "repositoryURL": "https://github.com/shibapm/Rocket", + "state": { + "branch": null, + "revision": "25613f7ffd16105c74417c1b4eb4c93fd04e2cdf", + "version": "1.1.0" + } + }, { "package": "swift-case-paths", "repositoryURL": "https://github.com/pointfreeco/swift-case-paths", @@ -36,6 +63,24 @@ "revision": "c466812aa2e22898f27557e2e780d3aad7a27203", "version": "1.8.2" } + }, + { + "package": "SwiftShell", + "repositoryURL": "https://github.com/kareman/SwiftShell", + "state": { + "branch": null, + "revision": "a6014fe94c3dbff0ad500e8da4f251a5d336530b", + "version": "5.1.0-beta.1" + } + }, + { + "package": "Yams", + "repositoryURL": "https://github.com/jpsim/Yams", + "state": { + "branch": null, + "revision": "c947a306d2e80ecb2c0859047b35c73b8e1ca27f", + "version": "2.0.0" + } } ] }, diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme index a5c876c..97c457d 100644 --- a/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme +++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> @@ -28,6 +28,18 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" codeCoverageEnabled = "YES"> + + + + + + + + @@ -59,6 +71,16 @@ ReferencedContainer = "container:.."> + + + + + + + + + + diff --git a/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/Example/Accessibility/AccessibilityIdentifier.swift b/Example/Example/Accessibility/AccessibilityIdentifier.swift new file mode 100644 index 0000000..74874b2 --- /dev/null +++ b/Example/Example/Accessibility/AccessibilityIdentifier.swift @@ -0,0 +1,77 @@ +import SwiftUI + +struct AccessibilityIdentifier { + let value: String +} + +extension View { + func accessibility(identifier: AccessibilityIdentifier) -> some View { + accessibility(identifier: identifier.value) + } +} + +extension AccessibilityIdentifier { + enum HomeScreen { + static let settingsNavigationBarItem = AccessibilityIdentifier(value: "home.settings.open") + + static func detail(for id: String) -> AccessibilityIdentifier { + AccessibilityIdentifier(value: "detail.\(id)") + } + + static func detailSettings(for id: String) -> AccessibilityIdentifier { + AccessibilityIdentifier(value: "detail.\(id).settings") + } + } + + struct SettingsScreen { + let prefix: String + + var shortcutsSheet: AccessibilityIdentifier { + AccessibilityIdentifier(value: "\(prefix).settings.shortcuts.sheet") + } + + var shortcutsPush: AccessibilityIdentifier { + AccessibilityIdentifier(value: "\(prefix).settings.shortcuts.push") + } + } + + struct DetailScreen { + let id: String + + var shortcuts: AccessibilityIdentifier { + AccessibilityIdentifier(value: "detail.\(id).shortcuts") + } + + var settings: AccessibilityIdentifier { + AccessibilityIdentifier(value: "detail.\(id).settings") + } + } + + struct NavigationShortcuts { + let prefix: String + + var detailShortcuts: AccessibilityIdentifier { + AccessibilityIdentifier(value: "\(prefix).detailShortcuts") + } + + var detailSettings: AccessibilityIdentifier { + AccessibilityIdentifier(value: "\(prefix).detailSettings") + } + + var detailSettingsShortcutsPush: AccessibilityIdentifier { + AccessibilityIdentifier(value: "\(prefix).detailSettingsShortcutsPush") + } + + var detailSettingsShortcutsSheet: AccessibilityIdentifier { + AccessibilityIdentifier(value: "\(prefix).detailSettingsShortcutsSheet") + } + + var homeSettings: AccessibilityIdentifier { + AccessibilityIdentifier(value: "\(prefix).homeSettings") + } + + var home: AccessibilityIdentifier { + AccessibilityIdentifier(value: "\(prefix).home") + } + } +} diff --git a/Example/Example/Detail Screen/DetailView.swift b/Example/Example/Detail Screen/DetailView.swift index 8d46938..1af2efd 100644 --- a/Example/Example/Detail Screen/DetailView.swift +++ b/Example/Example/Detail Screen/DetailView.swift @@ -34,27 +34,32 @@ struct DetailScreen: Screen { store: Store, settingsStore: Store ) -> some PathBuilder { - PathBuilders.screen( - DetailScreen.self, - content: { - DetailView( - store: store + PathBuilders.if( + screen: { (screen: DetailScreen) in + PathBuilders.screen( + DetailScreen.self, + content: { + DetailView( + store: store + ) + }, + nesting: PathBuilders.anyOf( + SettingsScreen.builder( + store: settingsStore, + entrypoint: "detail.\(screen.detailID)" + ) + .onDismiss(of: SettingsScreen.self) { + print("Detail settings dismissed") + }, + NavigationShortcutsScreen.builder( + store: store.scope( + state: \.navigationShortcuts, + action: DetailAction.navigationShortcuts + ) ) - }, - nesting: PathBuilders.anyOf( - SettingsScreen.builder( - store: settingsStore - ) - .onDismiss(of: SettingsScreen.self) { - print("Detail settings dismissed") - }, - NavigationShortcutsScreen.builder( - store: store.scope( - state: \.navigationShortcuts, - action: DetailAction.navigationShortcuts ) ) - ) + } ) } } @@ -79,8 +84,13 @@ struct DetailView: View { }, label: { Text("Go to [home/detail?id=\(viewStore.id)/shortcuts]") } ) + .accessibility( + identifier: AccessibilityIdentifier.DetailScreen(id: viewStore.id).shortcuts + ) - NavigationShortcuts() + NavigationShortcuts( + accessibilityIdentifiers: AccessibilityIdentifier.NavigationShortcuts(prefix: "detail.\(viewStore.id).shortcuts") + ) Spacer() } Spacer() @@ -91,6 +101,9 @@ struct DetailView: View { action: { viewStore.send(.settingsButtonTapped(currentID))}, label: { Image(systemName: "gear") } ) + .accessibility( + identifier: AccessibilityIdentifier.DetailScreen(id: viewStore.id).settings + ) ) .navigationBarTitle("Detail for \(viewStore.id)") } diff --git a/Example/Example/ExampleApp.swift b/Example/Example/ExampleApp.swift index 2135adb..ce5c18d 100644 --- a/Example/Example/ExampleApp.swift +++ b/Example/Example/ExampleApp.swift @@ -19,11 +19,7 @@ struct ExampleApp: App { init() { dataSource = Navigator.Datasource( - path: [ - HomeScreen().eraseToAnyScreen(), - DetailScreen(detailID: "0").eraseToAnyScreen(), - SettingsScreen().eraseToAnyScreen() - ] + root: HomeScreen() ) navigator = Navigator(dataSource: dataSource).debug() diff --git a/Example/Example/Home Screen/HomeView.swift b/Example/Example/Home Screen/HomeView.swift index 0c9c2c1..e62b37e 100644 --- a/Example/Example/Home Screen/HomeView.swift +++ b/Example/Example/Home Screen/HomeView.swift @@ -65,6 +65,8 @@ struct HomeView: View { Text("Element \(element)") } ) + .accessibility(identifier: AccessibilityIdentifier.HomeScreen.detail(for: element)) + Spacer() Image(systemName: "gear") .foregroundColor(.gray) @@ -73,6 +75,8 @@ struct HomeView: View { .openSettings(for: element, on: currentScreenID) ) } + .accessibility(addTraits: [.isButton]) + .accessibility(identifier: AccessibilityIdentifier.HomeScreen.detailSettings(for: element)) } } ) @@ -85,6 +89,7 @@ struct HomeView: View { action: { viewStore.send(.settingsButtonTapped) }, label: { Image(systemName: "gear") } ) + .accessibility(identifier: AccessibilityIdentifier.HomeScreen.settingsNavigationBarItem) ) .navigationBarTitle("Example App") } @@ -145,7 +150,10 @@ struct HomeScreen: Screen { } } ), - SettingsScreen.builder(store: settingsStore) + SettingsScreen.builder( + store: settingsStore, + entrypoint: "home" + ) ) .onDismiss { (screen: DetailScreen) in print("Dismissed \(screen)") diff --git a/Example/Example/Navigation Shortcuts/NavigationShortcuts.swift b/Example/Example/Navigation Shortcuts/NavigationShortcuts.swift index 53ce8a7..0301311 100644 --- a/Example/Example/Navigation Shortcuts/NavigationShortcuts.swift +++ b/Example/Example/Navigation Shortcuts/NavigationShortcuts.swift @@ -5,6 +5,8 @@ struct NavigationShortcuts: View { @Environment(\.navigator) var navigator @Environment(\.currentScreenID) var id + let accessibilityIdentifiers: AccessibilityIdentifier.NavigationShortcuts + var body: some View { Divider() Button( @@ -21,6 +23,7 @@ struct NavigationShortcuts: View { Text("Go to [home/detail?id=0/shortcuts]") } ) + .accessibility(identifier: accessibilityIdentifiers.detailShortcuts) Divider() Button( @@ -35,6 +38,7 @@ struct NavigationShortcuts: View { }, label: { Text("Go to [home/detail?id=0/settings]") } ) + .accessibility(identifier: accessibilityIdentifiers.detailSettings) Button( action: { @@ -49,6 +53,7 @@ struct NavigationShortcuts: View { }, label: { Text("Go to [home/detail?id=0/settings/shortcuts?style=push]") } ) + .accessibility(identifier: accessibilityIdentifiers.detailSettingsShortcutsPush) Button( action: { @@ -65,6 +70,7 @@ struct NavigationShortcuts: View { }, label: { Text("Go to [home/detail?id=0/settings/shortcuts?style=sheet]") } ) + .accessibility(identifier: accessibilityIdentifiers.detailSettingsShortcutsSheet) Divider() Button( @@ -78,10 +84,12 @@ struct NavigationShortcuts: View { }, label: { Text("Go to [home/settings]") } ) + .accessibility(identifier: accessibilityIdentifiers.homeSettings) Button( action: { navigator.goBack(to: HomeScreen()) }, label: { Text("Go back to [home]") } ) + .accessibility(identifier: accessibilityIdentifiers.home) } } diff --git a/Example/Example/Navigation Shortcuts/NavigationShortcutsView.swift b/Example/Example/Navigation Shortcuts/NavigationShortcutsView.swift index c51182c..3f5f947 100644 --- a/Example/Example/Navigation Shortcuts/NavigationShortcutsView.swift +++ b/Example/Example/Navigation Shortcuts/NavigationShortcutsView.swift @@ -39,7 +39,9 @@ struct NavigationShortcutsView: View { var body: some View { HStack { VStack(alignment: .leading, spacing: 16) { - NavigationShortcuts() + NavigationShortcuts( + accessibilityIdentifiers: AccessibilityIdentifier.NavigationShortcuts(prefix: "shortcuts") + ) Spacer() } Spacer() diff --git a/Example/Example/Settings Screen/SettingsView.swift b/Example/Example/Settings Screen/SettingsView.swift index 5dcde2c..7a68b17 100644 --- a/Example/Example/Settings Screen/SettingsView.swift +++ b/Example/Example/Settings Screen/SettingsView.swift @@ -24,11 +24,15 @@ struct SettingsScreen: Screen { let presentationStyle: ScreenPresentationStyle = .sheet(allowsPush: true) static func builder( - store: Store + store: Store, + entrypoint: String ) -> some PathBuilder { PathBuilders.screen( // settings content: { (screen: SettingsScreen) in - SettingsView(store: store) + SettingsView( + store: store, + accessibilityIdentifiers: AccessibilityIdentifier.SettingsScreen(prefix: entrypoint) + ) }, nesting: NavigationShortcutsScreen.builder( store: store.scope( @@ -57,6 +61,7 @@ struct SettingsView: View { @Environment(\.navigator) var navigator @Environment(\.currentScreenID) var id let store: Store + let accessibilityIdentifiers: AccessibilityIdentifier.SettingsScreen var body: some View { HStack { @@ -72,6 +77,7 @@ struct SettingsView: View { }, label: { Text("Go to [./shortcuts?style=push]") } ) + .accessibility(identifier: accessibilityIdentifiers.shortcutsPush) Button( action: { @@ -84,8 +90,11 @@ struct SettingsView: View { }, label: { Text("Go to [./shortcuts?style=sheet]") } ) + .accessibility(identifier: accessibilityIdentifiers.shortcutsSheet) - NavigationShortcuts() + NavigationShortcuts( + accessibilityIdentifiers: AccessibilityIdentifier.NavigationShortcuts(prefix: "settings") + ) Spacer() } @@ -103,7 +112,8 @@ struct SettingsView_Previews: PreviewProvider { initialState: SettingsState(), reducer: .empty, environment: () - ) + ), + accessibilityIdentifiers: AccessibilityIdentifier.SettingsScreen(prefix: "settings") ) } } diff --git a/Example/ExampleUITests/ExampleUITests.swift b/Example/ExampleUITests/ExampleUITests.swift new file mode 100644 index 0000000..2a10962 --- /dev/null +++ b/Example/ExampleUITests/ExampleUITests.swift @@ -0,0 +1,114 @@ +import Example +import XCTest + +final class ComposableNavigatorSheetUITests: XCTestCase { + var app: XCUIApplication! + + override func setUp() { + app = XCUIApplication() + app.launch() + continueAfterFailure = false + } + + func test_sheet() { + Home(app: app) + .goToSettings() + .assertVisible() + .dismissSheet() + .assertVisible() + } + + func test_sheet_sheet() { + Home(app: app) + .goToSettings() + .assertVisible() + .goToShortcutsSheet() + .assertVisible() + .goToBackToHome() + .assertVisible() + } + + func test_sheet_push() { + Home(app: app) + .goToSettings() + .assertVisible() + .goToShortcutsPush() + .assertVisible() + .goToBackToHome() + .assertVisible() + } +} + +final class ComposableNavigatorPushTests: XCTestCase { + var app: XCUIApplication! + + override func setUp() { + app = XCUIApplication() + app.launch() + continueAfterFailure = false + } + + func test_push() { + Home(app: app) + .goToDetail(for: "0") + .assertVisible() + .goToBackToHome() + .assertVisible() + } + + func test_push_push() { + Home(app: app) + .goToDetail(for: "0") + .assertVisible() + .goToShortcuts() + .assertVisible() + .goToBackToHome() + .assertVisible() + } + + func test_push_sheet() { + Home(app: app) + .goToDetail(for: "0") + .assertVisible() + .goToSettings() + .assertVisible() + .goBackToHome() + .assertVisible() + } +} + +final class ComposableNavigatorPathTransitionTests: XCTestCase { + var app: XCUIApplication! + + override func setUp() { + app = XCUIApplication() + app.launch() + continueAfterFailure = false + } + + func test_sheet_push_to_push_sheet_sheet() { + Home(app: app) + .goToSettings() + .assertVisible() + .goToShortcutsPush() + .assertVisible() + .goToDetailSettingsShortcutsSheet() + .assertVisible() + .goToBackToHome() + .assertVisible() + } + + func test_detail_one_settings_to_detail_zero_settings() { + Home(app: app) + .goToDetail(for: "1") + .assertVisible() + .goToSettings() + .shortcuts + .goToDetailZeroSettings() + .assertVisible() + .dismissSheet() + .assertVisible() + .goToBackToHome() + .assertVisible() + } +} diff --git a/Example/ExampleUITests/Info.plist b/Example/ExampleUITests/Info.plist new file mode 100644 index 0000000..64d65ca --- /dev/null +++ b/Example/ExampleUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Example/ExampleUITests/Screens/Base.swift b/Example/ExampleUITests/Screens/Base.swift new file mode 100644 index 0000000..ca9261c --- /dev/null +++ b/Example/ExampleUITests/Screens/Base.swift @@ -0,0 +1,14 @@ +import XCTest + +class Base { + let app: XCUIApplication + + init(app: XCUIApplication) { + self.app = app + } + + func pop(to predecessor: Predecessor) -> Predecessor { + app.backButton.tap() + return predecessor + } +} diff --git a/Example/ExampleUITests/Screens/Detail.swift b/Example/ExampleUITests/Screens/Detail.swift new file mode 100644 index 0000000..15d9761 --- /dev/null +++ b/Example/ExampleUITests/Screens/Detail.swift @@ -0,0 +1,55 @@ +import XCTest + +class Detail: Base { + let id: String + lazy var accessibilityIdentifiers = AccessibilityIdentifier.DetailScreen(id: id) + + lazy var shortcutsButton = app + .buttons[accessibilityIdentifiers.shortcuts] + .await() + + lazy var settingsButton = app + .buttons[accessibilityIdentifiers.settings] + .await() + + var shortcuts: NavigationShortcuts { + NavigationShortcuts(accessibilityPrefix: "detail.\(id)", app: app) + } + + init(app: XCUIApplication, id: String) { + self.id = id + super.init(app: app) + } + + @discardableResult + func assertVisible() -> Self { + XCTAssertTrue(shortcutsButton.exists) + return self + } + + @discardableResult + func goToShortcuts() -> NavigationShortcuts { + shortcutsButton.tap() + + return NavigationShortcuts( + accessibilityPrefix: "shortcuts", + app: app + ) + } + + @discardableResult + func goToSettings() -> Settings { + settingsButton.tap() + + return Settings( + predecessor: self, + prefix: "detail.\(id)", + app: app + ) + } + + @discardableResult + func goToBackToHome() -> Home { + return pop(to: Home(app: app)) + } +} diff --git a/Example/ExampleUITests/Screens/Home.swift b/Example/ExampleUITests/Screens/Home.swift new file mode 100644 index 0000000..7f69eac --- /dev/null +++ b/Example/ExampleUITests/Screens/Home.swift @@ -0,0 +1,42 @@ +@testable import Example +import XCTest + +class Home: Base { + lazy var settingsButton = app + .buttons[AccessibilityIdentifier.HomeScreen.settingsNavigationBarItem] + .await() + + func detail(for id: String) -> XCUIElement { + app.buttons[AccessibilityIdentifier.HomeScreen.detail(for: id)] + .await() + } + + func detailSettings(for id: String) -> XCUIElement { + app.buttons[AccessibilityIdentifier.HomeScreen.detailSettings(for: id)] + .await() + } + + @discardableResult + func assertVisible() -> Self { + XCTAssertTrue(settingsButton.exists) + return self + } + + @discardableResult + func goToSettings() -> Settings { + settingsButton.tap() + return Settings(predecessor: self, prefix: "home", app: app) + } + + @discardableResult + func goToDetail(for id: String) -> Detail { + detail(for: id).tap() + return Detail(app: app, id: id) + } + + @discardableResult + func goToDetailSettings(for id: String) -> Detail { + detail(for: id).tap() + return Detail(app: app, id: id) + } +} diff --git a/Example/ExampleUITests/Screens/NavigationShortcuts.swift b/Example/ExampleUITests/Screens/NavigationShortcuts.swift new file mode 100644 index 0000000..ff1f226 --- /dev/null +++ b/Example/ExampleUITests/Screens/NavigationShortcuts.swift @@ -0,0 +1,63 @@ +import XCTest + +class NavigationShortcuts: Base { + let accessibilityPrefix: String + + var accessibilityIdentifiers: AccessibilityIdentifier.NavigationShortcuts { + AccessibilityIdentifier.NavigationShortcuts(prefix: accessibilityPrefix) + } + + lazy var detailZeroSettingsButton = app + .buttons[accessibilityIdentifiers.detailSettings] + .await() + + lazy var detailSettingsShortcutsPush = app + .buttons[accessibilityIdentifiers.detailSettingsShortcutsPush] + .await() + + lazy var detailSettingsShortcutsSheet = app + .buttons[accessibilityIdentifiers.detailSettingsShortcutsSheet] + .await() + + lazy var backToHome = app + .buttons[accessibilityIdentifiers.home] + .await() + + init(accessibilityPrefix: String, app: XCUIApplication) { + self.accessibilityPrefix = accessibilityPrefix + super.init(app: app) + } + + @discardableResult + func assertVisible() -> Self { + XCTAssertTrue(backToHome.exists) + return self + } + + @discardableResult + func goToDetailZeroSettings() -> Settings { + detailZeroSettingsButton.tap() + return Settings( + predecessor: Detail(app: app, id: "0"), + prefix: "detail.0", + app: app + ) + } + + @discardableResult + func goToDetailSettingsShortcutsPush() -> NavigationShortcuts { + detailSettingsShortcutsPush.tap() + return NavigationShortcuts(accessibilityPrefix: "shortcuts", app: app) + } + + @discardableResult + func goToDetailSettingsShortcutsSheet() -> NavigationShortcuts { + detailSettingsShortcutsSheet.tap() + return NavigationShortcuts(accessibilityPrefix: "shortcuts", app: app) + } + + func goToBackToHome() -> Home { + backToHome.tap() + return Home(app: app) + } +} diff --git a/Example/ExampleUITests/Screens/Settings.swift b/Example/ExampleUITests/Screens/Settings.swift new file mode 100644 index 0000000..a1ac68e --- /dev/null +++ b/Example/ExampleUITests/Screens/Settings.swift @@ -0,0 +1,57 @@ +import XCTest + +class Settings: Base { + let predecessor: Predecessor + let prefix: String + + var shortcuts: NavigationShortcuts { + NavigationShortcuts(accessibilityPrefix: "settings", app: app) + } + + var accessibilityIdentifiers: AccessibilityIdentifier.SettingsScreen { + AccessibilityIdentifier.SettingsScreen(prefix: prefix) + } + + lazy var shortscutsSheetButton = app + .buttons[accessibilityIdentifiers.shortcutsSheet] + .await() + + lazy var shortscutsPushButton = app + .buttons[accessibilityIdentifiers.shortcutsPush] + .await() + + init(predecessor: Predecessor, prefix: String, app: XCUIApplication) { + self.predecessor = predecessor + self.prefix = prefix + super.init(app: app) + } + + @discardableResult + func assertVisible() -> Self { + XCTAssertTrue(shortscutsSheetButton.exists) + return self + } + + @discardableResult + func goToShortcutsPush() -> NavigationShortcuts { + shortscutsPushButton.tap() + return NavigationShortcuts(accessibilityPrefix: "shortcuts", app: app) + } + + @discardableResult + func goToShortcutsSheet() -> NavigationShortcuts { + shortscutsSheetButton.tap() + return NavigationShortcuts(accessibilityPrefix: "shortcuts", app: app) + } + + @discardableResult + func dismissSheet() -> Predecessor { + app.swipeDown(velocity: .fast) + return predecessor + } + + @discardableResult + func goBackToHome() -> Home { + shortcuts.goToBackToHome() + } +} diff --git a/Example/ExampleUITests/Screens/XCUIApplication+BackButton.swift b/Example/ExampleUITests/Screens/XCUIApplication+BackButton.swift new file mode 100644 index 0000000..d0ac854 --- /dev/null +++ b/Example/ExampleUITests/Screens/XCUIApplication+BackButton.swift @@ -0,0 +1,7 @@ +import XCTest + +extension XCUIApplication { + var backButton: XCUIElement { + navigationBars.buttons.element(boundBy: 0).await() + } +} diff --git a/Example/ExampleUITests/Screens/XCUIElement+ExistsAfterTimeout.swift b/Example/ExampleUITests/Screens/XCUIElement+ExistsAfterTimeout.swift new file mode 100644 index 0000000..10a13e1 --- /dev/null +++ b/Example/ExampleUITests/Screens/XCUIElement+ExistsAfterTimeout.swift @@ -0,0 +1,21 @@ +import XCTest + +extension XCUIElement { + func await(_ timeout: TimeInterval = 4.0) -> XCUIElement { + _ = exists(after: timeout, pollInterval: 0.2) + return self + } + + func exists(after timeout: TimeInterval, pollInterval: TimeInterval) -> Bool { + var elapsed: TimeInterval = 0 + while elapsed < timeout { + if waitForExistence(timeout: pollInterval) { + return true + } + + elapsed += pollInterval + } + + return false + } +} diff --git a/Example/ExampleUITests/Screens/XCUIElementQuery+AccessibiltyIdentifier.swift b/Example/ExampleUITests/Screens/XCUIElementQuery+AccessibiltyIdentifier.swift new file mode 100644 index 0000000..ebea71a --- /dev/null +++ b/Example/ExampleUITests/Screens/XCUIElementQuery+AccessibiltyIdentifier.swift @@ -0,0 +1,8 @@ +@testable import Example +import XCTest + +extension XCUIElementQuery { + subscript(identifier: AccessibilityIdentifier) -> XCUIElement { + return self[identifier.value] + } +} diff --git a/Example/FullTests.xctestplan b/Example/FullTests.xctestplan new file mode 100644 index 0000000..9a47f92 --- /dev/null +++ b/Example/FullTests.xctestplan @@ -0,0 +1,49 @@ +{ + "configurations" : [ + { + "id" : "D9600B5A-8B2E-4493-A051-F002D5B4D3C7", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + + }, + "testTargets" : [ + { + "parallelizable" : true, + "target" : { + "containerPath" : "container:Example.xcodeproj", + "identifier" : "23C9A49625E3A140005942F8", + "name" : "ExampleUITests" + } + }, + { + "parallelizable" : true, + "target" : { + "containerPath" : "container:..\/swift-composable-navigator.xcodeproj", + "identifier" : "swift-composable-navigator::ComposableDeeplinkingTests", + "name" : "ComposableDeeplinkingTests" + } + }, + { + "parallelizable" : true, + "target" : { + "containerPath" : "container:..\/swift-composable-navigator.xcodeproj", + "identifier" : "swift-composable-navigator::ComposableNavigatorTCATests", + "name" : "ComposableNavigatorTCATests" + } + }, + { + "parallelizable" : true, + "target" : { + "containerPath" : "container:..\/swift-composable-navigator.xcodeproj", + "identifier" : "swift-composable-navigator::ComposableNavigatorTests", + "name" : "ComposableNavigatorTests" + } + } + ], + "version" : 1 +} diff --git a/Example/UITests.xctestplan b/Example/UITests.xctestplan new file mode 100644 index 0000000..8c42267 --- /dev/null +++ b/Example/UITests.xctestplan @@ -0,0 +1,24 @@ +{ + "configurations" : [ + { + "id" : "08875A8E-F0D8-4FB1-9FD9-5A4055061C1D", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:Example.xcodeproj", + "identifier" : "23C9A49625E3A140005942F8", + "name" : "ExampleUITests" + } + } + ], + "version" : 1 +} diff --git a/Example/UnitTests.xctestplan b/Example/UnitTests.xctestplan new file mode 100644 index 0000000..8fa6ba9 --- /dev/null +++ b/Example/UnitTests.xctestplan @@ -0,0 +1,42 @@ +{ + "configurations" : [ + { + "id" : "D51A2EF0-51DA-4376-BB14-7B3B72809E3E", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "targetForVariableExpansion" : { + "containerPath" : "container:Example.xcodeproj", + "identifier" : "236E657B2559498C00C19B46", + "name" : "Example" + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:..\/swift-composable-navigator.xcodeproj", + "identifier" : "swift-composable-navigator::ComposableDeeplinkingTests", + "name" : "ComposableDeeplinkingTests" + } + }, + { + "target" : { + "containerPath" : "container:..\/swift-composable-navigator.xcodeproj", + "identifier" : "swift-composable-navigator::ComposableNavigatorTCATests", + "name" : "ComposableNavigatorTCATests" + } + }, + { + "target" : { + "containerPath" : "container:..\/swift-composable-navigator.xcodeproj", + "identifier" : "swift-composable-navigator::ComposableNavigatorTests", + "name" : "ComposableNavigatorTests" + } + } + ], + "version" : 1 +} diff --git a/Gemfile b/Gemfile index 9dc5bff..1123605 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,5 @@ source 'https://rubygems.org' gem 'danger', '~>8.2' -gem 'danger-xcov', '~>0.5.0' \ No newline at end of file +gem 'danger-xcov', '~>0.5.0' +gem 'fastlane', '~>2' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index b66fe35..c57dce5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,7 +7,7 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.1.0) - aws-partitions (1.427.0) + aws-partitions (1.428.0) aws-sdk-core (3.112.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) @@ -57,7 +57,7 @@ GEM domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) - emoji_regex (3.2.1) + emoji_regex (3.2.2) excon (0.79.0) faraday (1.3.0) faraday-net_http (~> 1.0) @@ -72,7 +72,7 @@ GEM faraday_middleware (1.0.0) faraday (~> 1.0) fastimage (2.2.2) - fastlane (2.174.0) + fastlane (2.175.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) artifactory (~> 3.0) @@ -96,6 +96,7 @@ GEM jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) multipart-post (~> 2.0.0) + naturally (~> 2.2) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) @@ -245,6 +246,7 @@ PLATFORMS DEPENDENCIES danger (~> 8.2) danger-xcov (~> 0.5.0) + fastlane (~> 2) BUNDLED WITH 2.1.4 diff --git a/Makefile b/Makefile index bfe2186..b7fd037 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,14 @@ PLATFORM_IOS = iOS Simulator,name=iPhone 8 -default: test - test: - swift package generate-xcodeproj - xcodebuild test -scheme swift-composable-navigator-Package \ - -derivedDataPath ./Build -enableCodeCoverage YES \ - -destination platform="$(PLATFORM_IOS)" + bundle exec fastlane test cleanup: rm -rf ./Build rm -rf ./swift-composable-navigator.xcodeproj - rm -rf ./xcov_report + rm -rf ./fastlane/xcov_report release: swift run rocket ${version} -.PHONY: test cleanup release +.PHONY: test cleanup release \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..ba15081 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,27 @@ +default_platform(:ios) + +platform :ios do + desc "Run tests" + lane :test do + sh("swift package generate-xcodeproj") + + scan( + workspace: "Example/Example.xcworkspace", + scheme: "Example", + clean: true, + output_types: "", + derived_data_path: "Build", + code_coverage: true, + devices: ["iPhone 8"], + testplan: "FullTests", + xcargs: "-parallel-testing-enabled YES -parallel-testing-worker-count 4 -quiet", + disable_xcpretty: true + ) + + danger( + dangerfile: "./Dangerfile", + github_api_token: ENV["DANGER_GITHUB_API_TOKEN"], + verbose: true + ) + end +end