diff --git a/.github/workflows/prepare.yaml b/.github/workflows/prepare.yaml new file mode 100644 index 0000000..99d94b7 --- /dev/null +++ b/.github/workflows/prepare.yaml @@ -0,0 +1,63 @@ +name: patrol prepare + +on: + workflow_dispatch: + pull_request: + +jobs: + prepare-ios: + runs-on: ${{ matrix.os }} + name: iOS on ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [macos-latest] + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Install tools + run: | + brew update + brew install swift-format + brew install clang-format + brew install xcbeautify + + - name: swift-format lint + run: test -z $(swift-format lint --recursive --strict .) + + - name: swift-format format + if: success() || failure() + run: | + swift-format format --recursive --in-place . + git update-index --refresh + git diff-index --quiet HEAD -- + + - name: clang-format + if: success() || failure() + run: | + find . -iname '*.h' -o -iname '*.m' \ + | xargs -I {} clang-format --dry-run --Werror {} + + - name: Start iOS simulator + if: success() || failure() + uses: futureware-tech/simulator-action@v2 + with: + model: iPhone 14 + os: iOS + os_version: 16.2 + erase_before_boot: true + shutdown_after_job: true + + - name: Run UI tests + run: | + set -o pipefail + xcodebuild test \ + -scheme Landmarks \ + -only-testing LandmarksUITests \ + -configuration Debug \ + -sdk iphoneos \ + -destination 'platform=iOS Simulator,OS=16.2,name=iPhone 14' \ + | xcbeautify --renderer github-actions diff --git a/Landmarks.xcodeproj/project.pbxproj b/Landmarks.xcodeproj/project.pbxproj index 139f6b6..c2895d7 100644 --- a/Landmarks.xcodeproj/project.pbxproj +++ b/Landmarks.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 989490072B0CE475001B6A7A /* LandmarksUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 989490062B0CE475001B6A7A /* LandmarksUITests.m */; }; 98A2611E28B169B900FEE658 /* LandmarksApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A2611D28B169B900FEE658 /* LandmarksApp.swift */; }; 98A2612028B169B900FEE658 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A2611F28B169B900FEE658 /* ContentView.swift */; }; 98A2612228B169BA00FEE658 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 98A2612128B169BA00FEE658 /* Assets.xcassets */; }; @@ -33,7 +34,20 @@ 98CF8FD828B29C8100993DE6 /* HikeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CF8FD428B29C8100993DE6 /* HikeView.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 9894900A2B0CE475001B6A7A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 98A2611228B169B900FEE658 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 98A2611928B169B900FEE658; + remoteInfo = Landmarks; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ + 989490042B0CE475001B6A7A /* LandmarksUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LandmarksUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 989490062B0CE475001B6A7A /* LandmarksUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LandmarksUITests.m; sourceTree = ""; }; + 9894900F2B0CEB2C001B6A7A /* TestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TestPlan.xctestplan; sourceTree = ""; }; 98A2611A28B169B900FEE658 /* Landmarks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Landmarks.app; sourceTree = BUILT_PRODUCTS_DIR; }; 98A2611D28B169B900FEE658 /* LandmarksApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandmarksApp.swift; sourceTree = ""; }; 98A2611F28B169B900FEE658 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -62,6 +76,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 989490012B0CE475001B6A7A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 98A2611728B169B900FEE658 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -72,10 +93,20 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 989490052B0CE475001B6A7A /* LandmarksUITests */ = { + isa = PBXGroup; + children = ( + 989490062B0CE475001B6A7A /* LandmarksUITests.m */, + ); + path = LandmarksUITests; + sourceTree = ""; + }; 98A2611128B169B900FEE658 = { isa = PBXGroup; children = ( + 9894900F2B0CEB2C001B6A7A /* TestPlan.xctestplan */, 98A2611C28B169B900FEE658 /* Landmarks */, + 989490052B0CE475001B6A7A /* LandmarksUITests */, 98A2611B28B169B900FEE658 /* Products */, ); sourceTree = ""; @@ -84,6 +115,7 @@ isa = PBXGroup; children = ( 98A2611A28B169B900FEE658 /* Landmarks.app */, + 989490042B0CE475001B6A7A /* LandmarksUITests.xctest */, ); name = Products; sourceTree = ""; @@ -186,6 +218,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 989490032B0CE475001B6A7A /* LandmarksUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9894900E2B0CE475001B6A7A /* Build configuration list for PBXNativeTarget "LandmarksUITests" */; + buildPhases = ( + 989490002B0CE475001B6A7A /* Sources */, + 989490012B0CE475001B6A7A /* Frameworks */, + 989490022B0CE475001B6A7A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 9894900B2B0CE475001B6A7A /* PBXTargetDependency */, + ); + name = LandmarksUITests; + productName = LandmarksUITests; + productReference = 989490042B0CE475001B6A7A /* LandmarksUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 98A2611928B169B900FEE658 /* Landmarks */ = { isa = PBXNativeTarget; buildConfigurationList = 98A2612828B169BA00FEE658 /* Build configuration list for PBXNativeTarget "Landmarks" */; @@ -213,6 +263,10 @@ LastSwiftUpdateCheck = 1340; LastUpgradeCheck = 1340; TargetAttributes = { + 989490032B0CE475001B6A7A = { + CreatedOnToolsVersion = 15.0.1; + TestTargetID = 98A2611928B169B900FEE658; + }; 98A2611928B169B900FEE658 = { CreatedOnToolsVersion = 13.4.1; }; @@ -232,11 +286,19 @@ projectRoot = ""; targets = ( 98A2611928B169B900FEE658 /* Landmarks */, + 989490032B0CE475001B6A7A /* LandmarksUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 989490022B0CE475001B6A7A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 98A2611828B169B900FEE658 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -251,6 +313,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 989490002B0CE475001B6A7A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 989490072B0CE475001B6A7A /* LandmarksUITests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 98A2611628B169B900FEE658 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -280,7 +350,61 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 9894900B2B0CE475001B6A7A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 98A2611928B169B900FEE658 /* Landmarks */; + targetProxy = 9894900A2B0CE475001B6A7A /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ + 9894900C2B0CE475001B6A7A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = U3EG6EALX7; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = pl.leancode.patrol.Landmarks.LandmarksUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Landmarks; + }; + name = Debug; + }; + 9894900D2B0CE475001B6A7A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = U3EG6EALX7; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = pl.leancode.patrol.Landmarks.LandmarksUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Landmarks; + }; + name = Release; + }; 98A2612628B169BA00FEE658 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -403,7 +527,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Landmarks/Preview Content\""; - DEVELOPMENT_TEAM = VX8P4VC6NH; + DEVELOPMENT_TEAM = U3EG6EALX7; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -411,12 +535,13 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = pl.baftek.Landmarks; + PRODUCT_BUNDLE_IDENTIFIER = pl.leancode.patrol.Landmarks; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -432,7 +557,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Landmarks/Preview Content\""; - DEVELOPMENT_TEAM = VX8P4VC6NH; + DEVELOPMENT_TEAM = U3EG6EALX7; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -440,12 +565,13 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = pl.baftek.Landmarks; + PRODUCT_BUNDLE_IDENTIFIER = pl.leancode.patrol.Landmarks; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -456,6 +582,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 9894900E2B0CE475001B6A7A /* Build configuration list for PBXNativeTarget "LandmarksUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9894900C2B0CE475001B6A7A /* Debug */, + 9894900D2B0CE475001B6A7A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 98A2611528B169B900FEE658 /* Build configuration list for PBXProject "Landmarks" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Landmarks.xcodeproj/xcshareddata/xcschemes/Landmarks.xcscheme b/Landmarks.xcodeproj/xcshareddata/xcschemes/Landmarks.xcscheme new file mode 100644 index 0000000..35a1e3e --- /dev/null +++ b/Landmarks.xcodeproj/xcshareddata/xcschemes/Landmarks.xcscheme @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Landmarks.xcodeproj/xcuserdata/bartek.xcuserdatad/xcschemes/xcschememanagement.plist b/Landmarks.xcodeproj/xcuserdata/bartek.xcuserdatad/xcschemes/xcschememanagement.plist index 8742bd7..f14e0a6 100644 --- a/Landmarks.xcodeproj/xcuserdata/bartek.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Landmarks.xcodeproj/xcuserdata/bartek.xcuserdatad/xcschemes/xcschememanagement.plist @@ -10,5 +10,18 @@ 0 + SuppressBuildableAutocreation + + 989490032B0CE475001B6A7A + + primary + + + 98A2611928B169B900FEE658 + + primary + + + diff --git a/Landmarks/Resources/landmarkData.json b/Landmarks/Resources/landmarkData.json index 97509b8..fc2286d 100644 --- a/Landmarks/Resources/landmarkData.json +++ b/Landmarks/Resources/landmarkData.json @@ -6,7 +6,7 @@ "state": "California", "id": 1001, "isFeatured": true, - "isFavorite": true, + "isFavorite": false, "park": "Joshua Tree National Park", "coordinates": { "longitude": -116.166868, @@ -22,7 +22,7 @@ "state": "Alaska", "id": 1002, "isFeatured": false, - "isFavorite": false, + "isFavorite": true, "park": "Lake Clark National Park and Preserve", "coordinates": { "longitude": -152.665167, @@ -38,7 +38,7 @@ "state": "Alaska", "id": 1003, "isFeatured": false, - "isFavorite": true, + "isFavorite": false, "park": "Klondike Gold Rush National Historical Park", "coordinates": { "longitude": -135.334571, @@ -54,7 +54,7 @@ "state": "Montana", "id": 1004, "isFeatured": true, - "isFavorite": true, + "isFavorite": false, "park": "Glacier National Park", "coordinates": { "longitude": -113.536248, diff --git a/Landmarks/Views/Landmarks/LandmarkDetail.swift b/Landmarks/Views/Landmarks/LandmarkDetail.swift index f6a1992..86e5c89 100644 --- a/Landmarks/Views/Landmarks/LandmarkDetail.swift +++ b/Landmarks/Views/Landmarks/LandmarkDetail.swift @@ -22,7 +22,9 @@ struct LandmarkDetail: View { HStack { Text(landmark.name) .font(.title) - FavoriteButton(isSet: $modelData.landmarks[landmarkIndex].isFavorite) + FavoriteButton( + isSet: $modelData.landmarks[landmarkIndex].isFavorite + ).accessibilityIdentifier("favorite") } HStack { diff --git a/Landmarks/Views/Landmarks/LandmarkList.swift b/Landmarks/Views/Landmarks/LandmarkList.swift index 9ed4e33..fe57a09 100644 --- a/Landmarks/Views/Landmarks/LandmarkList.swift +++ b/Landmarks/Views/Landmarks/LandmarkList.swift @@ -14,6 +14,15 @@ struct LandmarkList: View { } } + func makeLabel(_ landmark: Landmark) -> String { + var label = landmark.name + if landmark.isFavorite { + label = label + ", favorited" + } + + return label + } + var body: some View { NavigationView { List { @@ -25,6 +34,7 @@ struct LandmarkList: View { LandmarkDetail(landmark: landmark) } label: { LandmarkRow(landmark: landmark) + .accessibilityLabel(makeLabel(landmark)) } .navigationTitle("Landmarks") } diff --git a/Landmarks/Views/Landmarks/LandmarkRow.swift b/Landmarks/Views/Landmarks/LandmarkRow.swift index 339e9df..27d4e8d 100644 --- a/Landmarks/Views/Landmarks/LandmarkRow.swift +++ b/Landmarks/Views/Landmarks/LandmarkRow.swift @@ -9,12 +9,14 @@ struct LandmarkRow: View { .resizable() .frame(width: 50, height: 50) Text(landmark.name) + .accessibilityHidden(true) Spacer() if landmark.isFavorite { Image(systemName: "star.fill") .foregroundColor(.yellow) + .accessibilityHidden(true) } } } diff --git a/LandmarksUITests/LandmarksUITests.m b/LandmarksUITests/LandmarksUITests.m new file mode 100644 index 0000000..87fc9a7 --- /dev/null +++ b/LandmarksUITests/LandmarksUITests.m @@ -0,0 +1,51 @@ +#import + +@interface LandmarksUITests : XCTestCase + +@end + +@implementation LandmarksUITests + +- (void)testFavorite { + XCUIApplication *app = [[XCUIApplication alloc] init]; + [app launch]; + + XCUIElementQuery *element = [[app descendantsMatchingType:XCUIElementTypeAny] + matchingIdentifier:@"Turtle Rock"]; + [[element firstMatch] tap]; +} + +- (void)testFavoriteUsingPredicates { + XCUIApplication *app = [[XCUIApplication alloc] init]; + [app launch]; + + // Open landmark page + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"label = %@", @"Turtle Rock"]; + XCUIElementQuery *query = [[app descendantsMatchingType:XCUIElementTypeAny] + matchingPredicate:predicate]; + [[query firstMatch] tap]; + + // Mark landmark as favorite + predicate = [NSPredicate predicateWithFormat:@"identifier = %@", @"favorite"]; + query = [[app descendantsMatchingType:XCUIElementTypeAny] + matchingPredicate:predicate]; + [[query firstMatch] tap]; + + // Go to landmarks list + predicate = [NSPredicate predicateWithFormat:@"label = %@", @"Landmarks"]; + query = [[app descendantsMatchingType:XCUIElementTypeAny] + matchingPredicate:predicate]; + [[query firstMatch] tap]; + + // Assert that landmark is favorited + predicate = [NSPredicate + predicateWithFormat:@"label = %@", @"Turtle Rock, favorited"]; + query = [[app descendantsMatchingType:XCUIElementTypeAny] + matchingPredicate:predicate]; + XCUIElement *element = query.allElementsBoundByIndex.firstObject; + + XCTAssertTrue(element.exists); +} + +@end diff --git a/TestPlan.xctestplan b/TestPlan.xctestplan new file mode 100644 index 0000000..7117bc9 --- /dev/null +++ b/TestPlan.xctestplan @@ -0,0 +1,28 @@ +{ + "configurations" : [ + { + "id" : "22044977-F61F-42AA-A608-D66AB9D36992", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "targetForVariableExpansion" : { + "containerPath" : "container:Landmarks.xcodeproj", + "identifier" : "98A2611928B169B900FEE658", + "name" : "Landmarks" + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:Landmarks.xcodeproj", + "identifier" : "989490032B0CE475001B6A7A", + "name" : "LandmarksUITests" + } + } + ], + "version" : 1 +}