diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9761268 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: Test + +on: + push: + branches: + - main + pull_request: + branches: + - '*' + workflow_dispatch: + +jobs: + example: + runs-on: ${{ matrix.runsOn }} + env: + DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}/Contents/Developer" + strategy: + matrix: + include: + - xcode: "Xcode_12.5.1.app" + runsOn: macOS-11 + - xcode: "Xcode_13.2.1.app" + runsOn: macOS-12 + steps: + - uses: actions/checkout@v2 + - name: Run tests + run: xcodebuild -project ./Example/Example.xcodeproj -scheme Example test -destination platform='iOS Simulator',name='iPhone 11' -quiet -enableCodeCoverage YES -derivedDataPath "./output" \ No newline at end of file diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..dc1c807 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,9 @@ +--extensionacl on-declarations +--redundanttype explicit +--swiftversion 5.5 +--maxwidth 120 +--header "{file}\nPickBetter\n\nCopyright © {year} MFB Technologies, Inc. All rights reserved. All rights reserved.\n\nThis source code is licensed under the MIT license found in the\nLICENSE file in the root directory of this source tree." +--allman false +--exclude **/*/output +--wraparguments before-first +--wrapcollections before-first \ No newline at end of file diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..a432951 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,26 @@ +disabled_rules: + - multiple_closures_with_trailing_closure # by SwiftUI + - trailing_comma # conflicts with SwiftFormat + - opening_brace # conflicts with SwiftFormat +excluded: # paths to ignore during linting. Takes precedence over `included`. + - Carthage + - Pods + - ./**/.build + - ./**/output + - ./**/test_output + - ./**/Previews +identifier_name: + allowed_symbols: "_" + excluded: # excluded via string array + - id + - to + - vm + - vc + - _min + - _max + - or + - by +type_name: + allowed_symbols: "_" + excluded: + - ID diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/Example-macOS/Example_macOS.entitlements b/Example/Example-macOS/Example_macOS.entitlements new file mode 100644 index 0000000..f2ef3ae --- /dev/null +++ b/Example/Example-macOS/Example_macOS.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/Example/Example-macOS/Preview Content/Preview Assets.xcassets/Contents.json b/Example/Example-macOS/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Example/Example-macOS/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Example-tvOS/Preview Content/Preview Assets.xcassets/Contents.json b/Example/Example-tvOS/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Example/Example-tvOS/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4ef6cc4 --- /dev/null +++ b/Example/Example.xcodeproj/project.pbxproj @@ -0,0 +1,827 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 4994CA7027EE23C100F54306 /* SingleOptionalValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4994CA6F27EE23C100F54306 /* SingleOptionalValueTests.swift */; }; + 4994CA7E27EE2ADA00F54306 /* ExampleUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4994CA7D27EE2ADA00F54306 /* ExampleUITestCase.swift */; }; + 4994CA8027EE2B6E00F54306 /* XCUIElementQuery+ExactlyOneMatching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4994CA7F27EE2B6E00F54306 /* XCUIElementQuery+ExactlyOneMatching.swift */; }; + 4994CA8927EE72B300F54306 /* MutliValueUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4994CA8727EE729C00F54306 /* MutliValueUITests.swift */; }; + 49A241AE2800E7BA003DD15F /* XCUIElement+Trigger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A241AD2800E7BA003DD15F /* XCUIElement+Trigger.swift */; }; + 49A241BD2800EBBD003DD15F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49A241BC2800EBBD003DD15F /* Preview Assets.xcassets */; }; + 49A241EC2800EBF1003DD15F /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E598B027B230AF00C1F2CF /* ExampleApp.swift */; }; + 49A242052800EF34003DD15F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49A242042800EF34003DD15F /* Preview Assets.xcassets */; }; + 49A242362800EF75003DD15F /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E598B027B230AF00C1F2CF /* ExampleApp.swift */; }; + 49B89D1127DBC66B004BB78F /* XCUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B89D1027DBC66B004BB78F /* XCUITestCase.swift */; }; + 49BF84E52825867C006A9084 /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 49BF84E42825867C006A9084 /* Shared */; }; + 49BF84E728258689006A9084 /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 49BF84E628258689006A9084 /* Shared */; }; + 49BF84E92825CF49006A9084 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49BF84E82825CF49006A9084 /* Assets.xcassets */; }; + 49BF84EA2825CF49006A9084 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49BF84E82825CF49006A9084 /* Assets.xcassets */; }; + 49BF84EB2825CF49006A9084 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49BF84E82825CF49006A9084 /* Assets.xcassets */; }; + 49BF84ED2825D01C006A9084 /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 49BF84EC2825D01C006A9084 /* Shared */; }; + 49E598B127B230AF00C1F2CF /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E598B027B230AF00C1F2CF /* ExampleApp.swift */; }; + 49E598B827B230AF00C1F2CF /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49E598B727B230AF00C1F2CF /* Preview Assets.xcassets */; }; + 49E598CC27B230AF00C1F2CF /* SingleValueUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E598CB27B230AF00C1F2CF /* SingleValueUITests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 49E598C827B230AF00C1F2CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 49E598A527B230AF00C1F2CF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 49E598AC27B230AF00C1F2CF; + remoteInfo = Example; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 490F6D7B2800E4E200DAF85E /* Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example.entitlements; sourceTree = ""; }; + 4994CA6F27EE23C100F54306 /* SingleOptionalValueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleOptionalValueTests.swift; sourceTree = ""; }; + 4994CA7D27EE2ADA00F54306 /* ExampleUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleUITestCase.swift; sourceTree = ""; }; + 4994CA7F27EE2B6E00F54306 /* XCUIElementQuery+ExactlyOneMatching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElementQuery+ExactlyOneMatching.swift"; sourceTree = ""; }; + 4994CA8727EE729C00F54306 /* MutliValueUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutliValueUITests.swift; sourceTree = ""; }; + 49A241AD2800E7BA003DD15F /* XCUIElement+Trigger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Trigger.swift"; sourceTree = ""; }; + 49A241B32800EBBC003DD15F /* Example-macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 49A241BC2800EBBD003DD15F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 49A241BE2800EBBD003DD15F /* Example_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example_macOS.entitlements; sourceTree = ""; }; + 49A241FB2800EF33003DD15F /* Example-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 49A242042800EF34003DD15F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 49B89D1027DBC66B004BB78F /* XCUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUITestCase.swift; sourceTree = ""; }; + 49BF84E228258594006A9084 /* Shared */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Shared; sourceTree = ""; }; + 49BF84E328258666006A9084 /* Shared */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Shared; sourceTree = ""; }; + 49BF84E82825CF49006A9084 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 49E598AD27B230AF00C1F2CF /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 49E598B027B230AF00C1F2CF /* ExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleApp.swift; sourceTree = ""; }; + 49E598B727B230AF00C1F2CF /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 49E598C727B230AF00C1F2CF /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 49E598CB27B230AF00C1F2CF /* SingleValueUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleValueUITests.swift; sourceTree = ""; }; + 49E598DB27B2312500C1F2CF /* swiftui-pick-better */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "swiftui-pick-better"; path = ..; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 49A241B02800EBBC003DD15F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 49BF84E728258689006A9084 /* Shared in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 49A241F82800EF33003DD15F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 49BF84ED2825D01C006A9084 /* Shared in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 49E598AA27B230AF00C1F2CF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 49BF84E52825867C006A9084 /* Shared in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 49E598C427B230AF00C1F2CF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 49A241B42800EBBC003DD15F /* Example-macOS */ = { + isa = PBXGroup; + children = ( + 49A241BE2800EBBD003DD15F /* Example_macOS.entitlements */, + 49A241BB2800EBBD003DD15F /* Preview Content */, + ); + path = "Example-macOS"; + sourceTree = ""; + }; + 49A241BB2800EBBD003DD15F /* Preview Content */ = { + isa = PBXGroup; + children = ( + 49A241BC2800EBBD003DD15F /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 49A241FC2800EF33003DD15F /* Example-tvOS */ = { + isa = PBXGroup; + children = ( + 49A242032800EF34003DD15F /* Preview Content */, + ); + path = "Example-tvOS"; + sourceTree = ""; + }; + 49A242032800EF34003DD15F /* Preview Content */ = { + isa = PBXGroup; + children = ( + 49A242042800EF34003DD15F /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 49E598A427B230AF00C1F2CF = { + isa = PBXGroup; + children = ( + 49BF84E228258594006A9084 /* Shared */, + 49E598DA27B2312400C1F2CF /* Packages */, + 49E598AF27B230AF00C1F2CF /* Example */, + 49E598CA27B230AF00C1F2CF /* ExampleUITests */, + 49A241B42800EBBC003DD15F /* Example-macOS */, + 49A241FC2800EF33003DD15F /* Example-tvOS */, + 49E598AE27B230AF00C1F2CF /* Products */, + 49E598DC27B2314400C1F2CF /* Frameworks */, + ); + sourceTree = ""; + }; + 49E598AE27B230AF00C1F2CF /* Products */ = { + isa = PBXGroup; + children = ( + 49E598AD27B230AF00C1F2CF /* Example.app */, + 49E598C727B230AF00C1F2CF /* ExampleUITests.xctest */, + 49A241B32800EBBC003DD15F /* Example-macOS.app */, + 49A241FB2800EF33003DD15F /* Example-tvOS.app */, + ); + name = Products; + sourceTree = ""; + }; + 49E598AF27B230AF00C1F2CF /* Example */ = { + isa = PBXGroup; + children = ( + 49BF84E82825CF49006A9084 /* Assets.xcassets */, + 490F6D7B2800E4E200DAF85E /* Example.entitlements */, + 49E598B027B230AF00C1F2CF /* ExampleApp.swift */, + 49E598B627B230AF00C1F2CF /* Preview Content */, + ); + path = Example; + sourceTree = ""; + }; + 49E598B627B230AF00C1F2CF /* Preview Content */ = { + isa = PBXGroup; + children = ( + 49E598B727B230AF00C1F2CF /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 49E598CA27B230AF00C1F2CF /* ExampleUITests */ = { + isa = PBXGroup; + children = ( + 4994CA8727EE729C00F54306 /* MutliValueUITests.swift */, + 49E598CB27B230AF00C1F2CF /* SingleValueUITests.swift */, + 4994CA6F27EE23C100F54306 /* SingleOptionalValueTests.swift */, + 49B89D1027DBC66B004BB78F /* XCUITestCase.swift */, + 4994CA7D27EE2ADA00F54306 /* ExampleUITestCase.swift */, + 4994CA7F27EE2B6E00F54306 /* XCUIElementQuery+ExactlyOneMatching.swift */, + 49A241AD2800E7BA003DD15F /* XCUIElement+Trigger.swift */, + ); + path = ExampleUITests; + sourceTree = ""; + }; + 49E598DA27B2312400C1F2CF /* Packages */ = { + isa = PBXGroup; + children = ( + 49E598DB27B2312500C1F2CF /* swiftui-pick-better */, + 49BF84E328258666006A9084 /* Shared */, + ); + name = Packages; + sourceTree = ""; + }; + 49E598DC27B2314400C1F2CF /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 49A241B22800EBBC003DD15F /* Example-macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 49A241D52800EBBD003DD15F /* Build configuration list for PBXNativeTarget "Example-macOS" */; + buildPhases = ( + 49A241AF2800EBBC003DD15F /* Sources */, + 49A241B02800EBBC003DD15F /* Frameworks */, + 49A241B12800EBBC003DD15F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Example-macOS"; + packageProductDependencies = ( + 49BF84E628258689006A9084 /* Shared */, + ); + productName = "Example-macOS"; + productReference = 49A241B32800EBBC003DD15F /* Example-macOS.app */; + productType = "com.apple.product-type.application"; + }; + 49A241FA2800EF33003DD15F /* Example-tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 49A2421C2800EF34003DD15F /* Build configuration list for PBXNativeTarget "Example-tvOS" */; + buildPhases = ( + 49A241F72800EF33003DD15F /* Sources */, + 49A241F82800EF33003DD15F /* Frameworks */, + 49A241F92800EF33003DD15F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Example-tvOS"; + packageProductDependencies = ( + 49BF84EC2825D01C006A9084 /* Shared */, + ); + productName = "Example-tvOS"; + productReference = 49A241FB2800EF33003DD15F /* Example-tvOS.app */; + productType = "com.apple.product-type.application"; + }; + 49E598AC27B230AF00C1F2CF /* Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 49E598D127B230AF00C1F2CF /* Build configuration list for PBXNativeTarget "Example" */; + buildPhases = ( + 49E598A927B230AF00C1F2CF /* Sources */, + 49E598AA27B230AF00C1F2CF /* Frameworks */, + 49E598AB27B230AF00C1F2CF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Example; + packageProductDependencies = ( + 49BF84E42825867C006A9084 /* Shared */, + ); + productName = Example; + productReference = 49E598AD27B230AF00C1F2CF /* Example.app */; + productType = "com.apple.product-type.application"; + }; + 49E598C627B230AF00C1F2CF /* ExampleUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 49E598D727B230AF00C1F2CF /* Build configuration list for PBXNativeTarget "ExampleUITests" */; + buildPhases = ( + 49E598C327B230AF00C1F2CF /* Sources */, + 49E598C427B230AF00C1F2CF /* Frameworks */, + 49E598C527B230AF00C1F2CF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 49E598C927B230AF00C1F2CF /* PBXTargetDependency */, + ); + name = ExampleUITests; + productName = ExampleUITests; + productReference = 49E598C727B230AF00C1F2CF /* ExampleUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 49E598A527B230AF00C1F2CF /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1330; + LastUpgradeCheck = 1330; + TargetAttributes = { + 49A241B22800EBBC003DD15F = { + CreatedOnToolsVersion = 13.3; + }; + 49A241FA2800EF33003DD15F = { + CreatedOnToolsVersion = 13.3; + }; + 49E598AC27B230AF00C1F2CF = { + CreatedOnToolsVersion = 13.2.1; + }; + 49E598C627B230AF00C1F2CF = { + CreatedOnToolsVersion = 13.2.1; + TestTargetID = 49E598AC27B230AF00C1F2CF; + }; + }; + }; + buildConfigurationList = 49E598A827B230AF00C1F2CF /* Build configuration list for PBXProject "Example" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 49E598A427B230AF00C1F2CF; + productRefGroup = 49E598AE27B230AF00C1F2CF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 49E598AC27B230AF00C1F2CF /* Example */, + 49E598C627B230AF00C1F2CF /* ExampleUITests */, + 49A241B22800EBBC003DD15F /* Example-macOS */, + 49A241FA2800EF33003DD15F /* Example-tvOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 49A241B12800EBBC003DD15F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 49BF84EA2825CF49006A9084 /* Assets.xcassets in Resources */, + 49A241BD2800EBBD003DD15F /* Preview Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 49A241F92800EF33003DD15F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 49BF84EB2825CF49006A9084 /* Assets.xcassets in Resources */, + 49A242052800EF34003DD15F /* Preview Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 49E598AB27B230AF00C1F2CF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 49BF84E92825CF49006A9084 /* Assets.xcassets in Resources */, + 49E598B827B230AF00C1F2CF /* Preview Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 49E598C527B230AF00C1F2CF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 49A241AF2800EBBC003DD15F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 49A241EC2800EBF1003DD15F /* ExampleApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 49A241F72800EF33003DD15F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 49A242362800EF75003DD15F /* ExampleApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 49E598A927B230AF00C1F2CF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 49E598B127B230AF00C1F2CF /* ExampleApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 49E598C327B230AF00C1F2CF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4994CA7E27EE2ADA00F54306 /* ExampleUITestCase.swift in Sources */, + 49E598CC27B230AF00C1F2CF /* SingleValueUITests.swift in Sources */, + 49B89D1127DBC66B004BB78F /* XCUITestCase.swift in Sources */, + 49A241AE2800E7BA003DD15F /* XCUIElement+Trigger.swift in Sources */, + 4994CA7027EE23C100F54306 /* SingleOptionalValueTests.swift in Sources */, + 4994CA8027EE2B6E00F54306 /* XCUIElementQuery+ExactlyOneMatching.swift in Sources */, + 4994CA8927EE72B300F54306 /* MutliValueUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 49E598C927B230AF00C1F2CF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 49E598AC27B230AF00C1F2CF /* Example */; + targetProxy = 49E598C827B230AF00C1F2CF /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 49A241D62800EBBD003DD15F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "Example-macOS/Example_macOS.entitlements"; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Example-macOS/Preview Content\""; + DEVELOPMENT_TEAM = D79J55YKW6; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.mfbtech.picker-better.Example-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 49A241D72800EBBD003DD15F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "Example-macOS/Example_macOS.entitlements"; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Example-macOS/Preview Content\""; + DEVELOPMENT_TEAM = D79J55YKW6; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.mfbtech.picker-better.Example-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 49A2421D2800EF34003DD15F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Example-tvOS/Preview Content\""; + DEVELOPMENT_TEAM = D79J55YKW6; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.mfbtech.picker-better.Example-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 14.0; + }; + name = Debug; + }; + 49A2421E2800EF34003DD15F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Example-tvOS/Preview Content\""; + DEVELOPMENT_TEAM = D79J55YKW6; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.mfbtech.picker-better.Example-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 14.0; + }; + name = Release; + }; + 49E598CF27B230AF00C1F2CF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 49E598D027B230AF00C1F2CF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 49E598D227B230AF00C1F2CF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Example/Example.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; + DEVELOPMENT_TEAM = D79J55YKW6; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.pick-better.Example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = 14.0; + WATCHOS_DEPLOYMENT_TARGET = 7.0; + }; + name = Debug; + }; + 49E598D327B230AF00C1F2CF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Example/Example.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; + DEVELOPMENT_TEAM = D79J55YKW6; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.pick-better.Example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = 14.0; + WATCHOS_DEPLOYMENT_TARGET = 7.0; + }; + name = Release; + }; + 49E598D827B230AF00C1F2CF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = D79J55YKW6; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.pick-better.ExampleUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Example; + }; + name = Debug; + }; + 49E598D927B230AF00C1F2CF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = D79J55YKW6; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.pick-better.ExampleUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Example; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 49A241D52800EBBD003DD15F /* Build configuration list for PBXNativeTarget "Example-macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 49A241D62800EBBD003DD15F /* Debug */, + 49A241D72800EBBD003DD15F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 49A2421C2800EF34003DD15F /* Build configuration list for PBXNativeTarget "Example-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 49A2421D2800EF34003DD15F /* Debug */, + 49A2421E2800EF34003DD15F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 49E598A827B230AF00C1F2CF /* Build configuration list for PBXProject "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 49E598CF27B230AF00C1F2CF /* Debug */, + 49E598D027B230AF00C1F2CF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 49E598D127B230AF00C1F2CF /* Build configuration list for PBXNativeTarget "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 49E598D227B230AF00C1F2CF /* Debug */, + 49E598D327B230AF00C1F2CF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 49E598D727B230AF00C1F2CF /* Build configuration list for PBXNativeTarget "ExampleUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 49E598D827B230AF00C1F2CF /* Debug */, + 49E598D927B230AF00C1F2CF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + 49BF84E42825867C006A9084 /* Shared */ = { + isa = XCSwiftPackageProductDependency; + productName = Shared; + }; + 49BF84E628258689006A9084 /* Shared */ = { + isa = XCSwiftPackageProductDependency; + productName = Shared; + }; + 49BF84EC2825D01C006A9084 /* Shared */ = { + isa = XCSwiftPackageProductDependency; + productName = Shared; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 49E598A527B230AF00C1F2CF /* Project object */; +} diff --git a/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/Example-macOS.xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example-macOS.xcscheme new file mode 100644 index 0000000..2f94a1d --- /dev/null +++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example-macOS.xcscheme @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme new file mode 100644 index 0000000..927218c --- /dev/null +++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..e12e779 --- /dev/null +++ b/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,15 @@ +{ + "colors" : [ + { + "color" : { + "platform" : "universal", + "reference" : "systemPinkColor" + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Example/Assets.xcassets/Contents.json b/Example/Example/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Example/Example/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Example/Example.entitlements b/Example/Example/Example.entitlements new file mode 100644 index 0000000..ee95ab7 --- /dev/null +++ b/Example/Example/Example.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/Example/Example/ExampleApp.swift b/Example/Example/ExampleApp.swift new file mode 100644 index 0000000..3aae123 --- /dev/null +++ b/Example/Example/ExampleApp.swift @@ -0,0 +1,19 @@ +// ExampleApp.swift +// PickBetter +// +// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +import Shared +import SwiftUI + +@main +struct ExampleApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json b/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/ExampleUITests/ExampleUITestCase.swift b/Example/ExampleUITests/ExampleUITestCase.swift new file mode 100644 index 0000000..0ff1549 --- /dev/null +++ b/Example/ExampleUITests/ExampleUITestCase.swift @@ -0,0 +1,118 @@ +// ExampleUITestCase.swift +// PickBetter +// +// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +import XCTest +#if os(iOS) + import Example +#elseif os(macOS) + import Example_macOS +#endif + +class ExampleUITestCase: XCUITestCase { + var _sectionNavItem: (() throws -> XCUIElement)? { + nil + } + + func sectionNavItem() throws -> XCUIElement { + try XCTUnwrap(_sectionNavItem)() + } + + var _picker: (() throws -> XCUIElement)? { + nil + } + + func picker() throws -> XCUIElement { + try XCTUnwrap(_picker)() + } + + override func setUpWithError() throws { + try super.setUpWithError() + + #if os(iOS) + // iPad has a bug where view is blank when starting in portrait + // Let's rotate it landscape and back to work around that bug. + XCUIDevice.shared.orientation = .portrait + XCUIDevice.shared.orientation = .landscapeRight + XCUIDevice.shared.orientation = .portrait + #endif + } + + func gridStyleToggle() throws -> XCUIElement { + try allElements().matching(identifier: "gridStyleToggle").firstMatch + } + + func isGridStyleSelected() throws -> Bool { + try gridStyleToggle().label == "Grid Style" + } + + func setGridStyle() throws { + if try !isGridStyleSelected() { + try gridStyleToggle().trigger() + } + XCTAssert(try isGridStyleSelected()) + } + + func setListStyle() throws { + if try isGridStyleSelected() { + try gridStyleToggle().trigger() + } + XCTAssert(try !isGridStyleSelected()) + } + + func singleValueNavItem() throws -> XCUIElement { + try allElements().matching(identifier: "singleValueSectionNavItem").firstMatch + } + + func singleOptionalValueNavItem() throws -> XCUIElement { + try allElements().matching(identifier: "singleOptionalValueSectionNavItem").firstMatch + } + + func multiValueNavItem() throws -> XCUIElement { + try allElements().matching(identifier: "multiValueSectionNavItem").firstMatch + } + + func singleValuePicker() throws -> XCUIElement { + try allElements().matching(identifier: "singleValuePicker").exactlyOneMatch() + } + + func singleOptionalValuePicker() throws -> XCUIElement { + try allElements().matching(identifier: "singleOptionalValuePicker").exactlyOneMatch() + } + + func multiValuePicker() throws -> XCUIElement { + try allElements().matching(identifier: "multiValuePicker").exactlyOneMatch() + } + + func cellZero() throws -> XCUIElement { + try allElements().children(matching: .cell) + .element(matching: elementCompoundOrPredicate(labeled: ["Cell - 0", "Cell - 0, Selected"])) + } + + func buttonZero() throws -> XCUIElement { + try allElements().children(matching: .button) + .element(matching: elementCompoundOrPredicate(labeled: ["Cell - 0", "Cell - 0, Selected"])) + } + + func cellOne() throws -> XCUIElement { + try allElements().children(matching: .cell) + .element(matching: elementCompoundOrPredicate(labeled: ["Cell - 1", "Cell - 1, Selected"])) + } + + func buttonOne() throws -> XCUIElement { + try allElements().children(matching: .button) + .element(matching: elementCompoundOrPredicate(labeled: ["Cell - 1", "Cell - 1, Selected"])) + } + + func button(_ buttonNumber: Int) throws -> XCUIElement { + try allElements().children(matching: .button) + .element(matching: elementCompoundOrPredicate(labeled: [ + "Cell - \(buttonNumber)", + "Cell - \(buttonNumber), Selected", + ])) + } +} diff --git a/Example/ExampleUITests/MutliValueUITests.swift b/Example/ExampleUITests/MutliValueUITests.swift new file mode 100644 index 0000000..480fdda --- /dev/null +++ b/Example/ExampleUITests/MutliValueUITests.swift @@ -0,0 +1,72 @@ +// MutliValueUITests.swift +// PickBetter +// +// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +import XCTest +#if os(iOS) + import Example +#elseif os(macOS) + import Example_macOS +#endif + +final class MultiValueUITests: ExampleUITestCase { + override var _sectionNavItem: (() throws -> XCUIElement)? { + multiValueNavItem + } + + override var _picker: (() throws -> XCUIElement)? { + multiValuePicker + } + + func sharedTestSteps() throws { + _ = try sectionNavItem().waitForExistence(timeout: 1) + XCTAssert(try sectionNavItem().exists, "Navigation link/tab for 'Multi' value picker must exist") + try sectionNavItem().trigger() + + _ = try picker().waitForExistence(timeout: 1) + XCTAssert(try picker().exists, "Multi value picker must exist") + _ = try buttonZero().waitForExistence(timeout: 1) + _ = try buttonOne().waitForExistence(timeout: 1) + + XCTAssert(try buttonZero().exists, "Button Zero must exist") + XCTAssert(try buttonOne().exists, "Button One must exist") + XCTAssert( + try !buttonZero().isSelected && (try !buttonOne().isSelected), + "Initial state should have no item selected" + ) + + try buttonZero().trigger() + XCTAssert(try buttonZero().isSelected, "'0' should be selected after being tapped") + XCTAssert(try !buttonOne().isSelected, "'1' should not be selected before being tapped.") + + try buttonOne().trigger() + XCTAssert(try buttonZero().isSelected, "'0' should still be selected without being tapped again to deselect") + XCTAssert(try buttonOne().isSelected, "'1' should be selected after being tapped.") + + try buttonOne().trigger() + XCTAssert(try buttonZero().isSelected, "'0' should still be selected without being tapped again to deselect") + XCTAssert(try !buttonOne().isSelected, "'1' should no longer be selected after being tapped again to deselect") + + try buttonZero().trigger() + XCTAssert(try !buttonZero().isSelected, "'0' should no longer be selected after being tapped again to deselect") + XCTAssert(try !buttonOne().isSelected, "'1' is still not selected after being deselected and '0' is deselected") + } + + func testGridStyleDeselectAndSelectNew() throws { + _ = try gridStyleToggle().waitForExistence(timeout: 1) + try setGridStyle() + + try sharedTestSteps() + } + + func testListStyleDeselectAndSelectNew() throws { + _ = try gridStyleToggle().waitForExistence(timeout: 1) + try setListStyle() + + try sharedTestSteps() + } +} diff --git a/Example/ExampleUITests/SingleOptionalValueTests.swift b/Example/ExampleUITests/SingleOptionalValueTests.swift new file mode 100644 index 0000000..9418347 --- /dev/null +++ b/Example/ExampleUITests/SingleOptionalValueTests.swift @@ -0,0 +1,70 @@ +// SingleOptionalValueTests.swift +// PickBetter +// +// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +import XCTest +#if os(iOS) + import Example +#elseif os(macOS) + import Example_macOS +#endif + +final class SingleOptionalValueUITests: ExampleUITestCase { + override var _sectionNavItem: (() throws -> XCUIElement)? { + singleOptionalValueNavItem + } + + override var _picker: (() throws -> XCUIElement)? { + singleOptionalValuePicker + } + + func sharedTestSteps() throws { + _ = try gridStyleToggle().waitForExistence(timeout: 1) + try setGridStyle() + + _ = try sectionNavItem().waitForExistence(timeout: 1) + XCTAssert(try sectionNavItem().exists, "Navigation link/tab for 'Single' value picker must exist") + try sectionNavItem().trigger() + + _ = try picker().waitForExistence(timeout: 1) + XCTAssert(try picker().exists, "Single value picker must exist") + _ = try buttonZero().waitForExistence(timeout: 1) + _ = try buttonOne().waitForExistence(timeout: 1) + XCTAssert(try buttonZero().exists, "Button Zero must exist") + XCTAssert(try buttonOne().exists, "Button One must exist") + XCTAssert( + try !buttonZero().isSelected && (try !buttonOne().isSelected), + "Initial state should have no item selected" + ) + + try buttonZero().trigger() + XCTAssert(try buttonZero().isSelected, "'0' should be selected after being tapped") + XCTAssert(try !buttonOne().isSelected, "'1' should not be selected after '0' is tapped.") + + try buttonZero().trigger() + XCTAssert(try !buttonZero().isSelected, "'0' should no longer be selected after being tapped while selected") + XCTAssert(try !buttonOne().isSelected, "'1' should not be selected before being tapped.") + + try buttonOne().trigger() + XCTAssert(try !buttonZero().isSelected, "'0' should no longer be selected after tapping a new item.") + XCTAssert(try buttonOne().isSelected, "'1' should be selected after it is tapped.") + } + + func testGridStyleDeselectAndSelectNew() throws { + _ = try gridStyleToggle().waitForExistence(timeout: 1) + try setGridStyle() + + try sharedTestSteps() + } + + func testListStyleDeselectAndSelectNew() throws { + _ = try gridStyleToggle().waitForExistence(timeout: 1) + try setListStyle() + + try sharedTestSteps() + } +} diff --git a/Example/ExampleUITests/SingleValueUITests.swift b/Example/ExampleUITests/SingleValueUITests.swift new file mode 100644 index 0000000..459c8fd --- /dev/null +++ b/Example/ExampleUITests/SingleValueUITests.swift @@ -0,0 +1,95 @@ +// SingleValueUITests.swift +// PickBetter +// +// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +import XCTest +#if os(iOS) + import Example +#elseif os(macOS) + import Example_macOS +#endif + +final class SingleValueUITests: ExampleUITestCase { + override var _sectionNavItem: (() throws -> XCUIElement)? { + singleValueNavItem + } + + override var _picker: (() throws -> XCUIElement)? { + singleValuePicker + } + + func sharedTestSteps() throws { + _ = try sectionNavItem().waitForExistence(timeout: 1) + XCTAssert(try sectionNavItem().exists, "Navigation link/tab for 'Single' value picker must exist") + #if os(tvOS) + guard try findVertically(try sectionNavItem(), method: .upOnly) else { + XCTFail("Failed to find navigation picker") + return + } + #endif + try sectionNavItem().trigger() + + _ = try picker().waitForExistence(timeout: 1) + XCTAssert(try picker().exists, "Single value picker must exist") + _ = try buttonZero().waitForExistence(timeout: 1) + _ = try buttonOne().waitForExistence(timeout: 1) + XCTAssert(try buttonZero().exists, "Button Zero must exist") + XCTAssert(try buttonOne().exists, "Button One must exist") + XCTAssert(try buttonZero().isSelected, "Initial state should have the first item selected which is '0'") + + #if os(tvOS) + guard try findVertically(try button(3), method: .downOnly) else { + XCTFail("Failed to find '3' to select it.") + return + } + guard try findHorizontally(try buttonZero(), method: .leftOnly) else { + XCTFail("Failed to find '0'") + return + } + #endif + try buttonZero().trigger() + XCTAssert(try buttonZero().isSelected, "The 'Single' value picker does not allow de-selecting.") + XCTAssert(try !buttonOne().isSelected, "'1' should not be selected before being tapped.") + + #if os(tvOS) + guard try findHorizontally(try buttonOne(), method: .rightOnly) else { + XCTFail("Failed to find '1' to select it.") + return + } + #endif + try buttonOne().trigger() + XCTAssert(try !buttonZero().isSelected, "'0' should no longer be selected after tapping a new item.") + XCTAssert(try buttonOne().isSelected, "'1' should be selected after it is tapped.") + XCTAssert(try !buttonZero().isSelected, "'0' should no longer be selected after tapping a new item.") + } + + func testGridStyleDeselectAndSelectNew() throws { + _ = try gridStyleToggle().waitForExistence(timeout: 1) + #if os(tvOS) + guard try findVertically(try gridStyleToggle(), method: .downOnly) else { + XCTFail("Failed to find grid toggle") + return + } + #endif + try setGridStyle() + + try sharedTestSteps() + } + + func testListStyleDeselectAndSelectNew() throws { + _ = try gridStyleToggle().waitForExistence(timeout: 1) + #if os(tvOS) + guard try findVertically(try gridStyleToggle(), method: .downOnly) else { + XCTFail("Failed to find grid toggle") + return + } + #endif + try setListStyle() + + try sharedTestSteps() + } +} diff --git a/Example/ExampleUITests/XCUIElement+Trigger.swift b/Example/ExampleUITests/XCUIElement+Trigger.swift new file mode 100644 index 0000000..748debb --- /dev/null +++ b/Example/ExampleUITests/XCUIElement+Trigger.swift @@ -0,0 +1,32 @@ +// XCUIElement+Trigger.swift +// PickBetter +// +// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +import Foundation +import XCTest + +extension XCUIElement { + func trigger() { + #if os(iOS) || os(watchOS) + tap() + #elseif os(macOS) + click() + #elseif os(tvOS) + XCUIRemote.shared.press(.select) + #endif + } + + func details() throws -> String { + guard identifier.isEmpty else { + return identifier + } + guard label.isEmpty else { + return label + } + return debugDescription + } +} diff --git a/Example/ExampleUITests/XCUIElementQuery+ExactlyOneMatching.swift b/Example/ExampleUITests/XCUIElementQuery+ExactlyOneMatching.swift new file mode 100644 index 0000000..65aa1cf --- /dev/null +++ b/Example/ExampleUITests/XCUIElementQuery+ExactlyOneMatching.swift @@ -0,0 +1,16 @@ +// XCUIElementQuery+ExactlyOneMatching.swift +// PickBetter +// +// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +import XCTest + +extension XCUIElementQuery { + public func exactlyOneMatch() throws -> XCUIElement { + XCTAssertEqual(count, 1, "Requiring only one element match the query resulting in `self`") + return firstMatch + } +} diff --git a/Example/ExampleUITests/XCUITestCase.swift b/Example/ExampleUITests/XCUITestCase.swift new file mode 100644 index 0000000..938fc1a --- /dev/null +++ b/Example/ExampleUITests/XCUITestCase.swift @@ -0,0 +1,210 @@ +// XCUITestCase.swift +// PickBetter +// +// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +import XCTest + +class XCUITestCase: XCTestCase { + private var _app: XCUIApplication? + + func app() throws -> XCUIApplication { + try XCTUnwrap(_app) + } + + func allElements() throws -> XCUIElementQuery { + try app().descendants(matching: .any) + } + + func elementPredicate(labeled label: String) -> NSPredicate { + NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \XCUIElement.label), + rightExpression: NSExpression(forConstantValue: label), + modifier: .direct, + type: .equalTo, + options: [] + ) + } + + func elementCompoundOrPredicate(labeled labels: Set) -> NSPredicate { + NSCompoundPredicate(orPredicateWithSubpredicates: labels.map(elementPredicate(labeled:))) + } + + override func setUpWithError() throws { + _app = XCUIApplication() + continueAfterFailure = false + + try app().launch() + } +} + +#if os(iOS) || os(tvOS) + extension XCUITestCase { + var focusPredicate: NSPredicate { + NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \XCUIElement.hasFocus), + rightExpression: NSExpression(forConstantValue: true), + modifier: .direct, + type: .equalTo, + options: [] + ) + } + + func currentFocus() throws -> XCUIElement { + try allElements().descendants(matching: .any).element(matching: focusPredicate) + } + } +#endif + +#if os(tvOS) + enum VerticalFocusSearchMethod { + case downFirst + case upFirst + case downOnly + case upOnly + + var singleDirection: Bool { + self == .downOnly || self == .upOnly + } + + var firstDirection: FocusMoveDirection { + switch self { + case .downOnly, .downFirst: + return .down + case .upOnly, .upFirst: + return .up + } + } + } + + enum HorizontalFocusSearchMethod { + case rightFirst + case leftFirst + case rightOnly + case leftOnly + + var singleDirection: Bool { + self == .rightOnly || self == .leftOnly + } + + var firstDirection: FocusMoveDirection { + switch self { + case .rightOnly, .rightFirst: + return .right + case .leftOnly, .leftFirst: + return .left + } + } + } + + enum FocusMoveDirection { + case up + case down + case left + case right + + func invert() -> Self { + switch self { + case .up: + return .down + case .down: + return .up + case .left: + return .right + case .right: + return .left + } + } + + func progressForGridSearch() -> Self { + switch self { + case .up: + return .left + case .down: + return .right + case .left: + return .up + case .right: + return .down + } + } + } + + extension FocusMoveDirection { + var remoteDirection: XCUIRemote.Button { + switch self { + case .up: + return .up + case .down: + return .down + case .left: + return .left + case .right: + return .right + } + } + } + + extension XCUITestCase { + func findVertically(_ element: XCUIElement, method: VerticalFocusSearchMethod) throws -> Bool { + var isEndReached = false + var currentDirection: FocusMoveDirection = method.firstDirection + while !element.exists || !element.hasFocus { + let previous: String = try currentFocus().details() + moveFocus(direction: currentDirection) + let current: String = try currentFocus().details() + if previous == current { + if isEndReached || method.singleDirection { + return false + } + isEndReached = true + currentDirection = currentDirection.invert() + } + } + return true + } + + func findHorizontally(_ element: XCUIElement, method: HorizontalFocusSearchMethod) throws -> Bool { + var isEndReached = false + var currentDirection: FocusMoveDirection = method.firstDirection + while !element.exists || !element.hasFocus { + let previous: String = try currentFocus().details() + moveFocus(direction: currentDirection) + let current: String = try currentFocus().details() + if previous == current { + if isEndReached || method.singleDirection { + return false + } + isEndReached = true + currentDirection = currentDirection.invert() + } + } + return true + } + + func findInGrid(_ element: XCUIElement, firstDirection: FocusMoveDirection) throws -> Bool { + var isEndReached = false + var currentDirection: FocusMoveDirection = firstDirection + while !element.exists || !element.hasFocus { + let previous: String = try currentFocus().details() + moveFocus(direction: currentDirection) + let current: String = try currentFocus().details() + if previous == current { + if isEndReached { + return false + } + isEndReached = true + currentDirection = currentDirection.progressForGridSearch() + } + } + return true + } + + func moveFocus(direction: FocusMoveDirection) { + XCUIRemote.shared.press(direction.remoteDirection) + } + } +#endif diff --git a/Example/Shared/.gitignore b/Example/Shared/.gitignore new file mode 100644 index 0000000..3b29812 --- /dev/null +++ b/Example/Shared/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Example/Shared/Package.swift b/Example/Shared/Package.swift new file mode 100644 index 0000000..dcf8f78 --- /dev/null +++ b/Example/Shared/Package.swift @@ -0,0 +1,31 @@ +// swift-tools-version: 5.4 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Shared", + platforms: [ + .iOS(.v14), + .macOS(.v11), + .watchOS(.v7), + .tvOS(.v14), + ], + products: [ + .library( + name: "Shared", + targets: ["Shared"] + ), + ], + dependencies: [ + .package(name: "swiftui-pick-better", path: "../../"), + ], + targets: [ + .target( + name: "Shared", + dependencies: [ + .product(name: "PickBetter", package: "swiftui-pick-better"), + ] + ), + ] +) diff --git a/Example/Shared/README.md b/Example/Shared/README.md new file mode 100644 index 0000000..4cf9513 --- /dev/null +++ b/Example/Shared/README.md @@ -0,0 +1,3 @@ +# Shared + +A description of this package. diff --git a/Example/Shared/Sources/Shared/BackPortedButtonToggleStyle.swift b/Example/Shared/Sources/Shared/BackPortedButtonToggleStyle.swift new file mode 100644 index 0000000..258b6eb --- /dev/null +++ b/Example/Shared/Sources/Shared/BackPortedButtonToggleStyle.swift @@ -0,0 +1,29 @@ +// BackPortedButtonToggleStyle.swift +// PickBetter +// +// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +import Foundation +import SwiftUI + +public struct BackPortedButtonToggleStyle: ToggleStyle { + public func makeBody(configuration: Configuration) -> some View { + Button( + action: { configuration.$isOn.wrappedValue.toggle() }, + label: { + ZStack { + if configuration.isOn { + Color.secondary + } else { + Color.clear + } + configuration.label + .foregroundColor(configuration.isOn ? .primary : .secondary) + } + } + ) + } +} diff --git a/Example/Shared/Sources/Shared/ContentView.swift b/Example/Shared/Sources/Shared/ContentView.swift new file mode 100644 index 0000000..36d6731 --- /dev/null +++ b/Example/Shared/Sources/Shared/ContentView.swift @@ -0,0 +1,227 @@ +// ContentView.swift +// PickBetter +// +// Copyright © 2022 MFB Technologies, Inc. All rights reserved. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +import PickBetter +import SwiftUI + +public struct ContentView: View { + @State private var tab: TabOption = .singleValue + @State private var isGridStyle: Bool = true + @State private var isNavigationExpanded: Bool = true + #if os(iOS) + @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.verticalSizeClass) var verticalSizeClass + #endif + private let items: [Item] = (0 ..< 100).map { Item(id: $0) } + + public init() {} + + public var body: some View { + #if os(iOS) + NavigationView { + HStack(spacing: .zero) { + navigation() + mainBody() + } + .toolbar { + Toggle( + isOn: $isGridStyle, + label: { Text(isGridStyle ? "Grid Style" : "List Style") } + ) + .accessibilityIdentifier(gridStyleToggle) + } + .navigationBarTitleDisplayMode(.inline) + } + .navigationViewStyle(StackNavigationViewStyle()) + #elseif os(macOS) + NavigationView { + navigation() + } + .toolbar { + Toggle( + isOn: $isGridStyle, + label: { Text(isGridStyle ? "Grid Style" : "List Style") } + ) + .accessibilityIdentifier(gridStyleToggle) + } + #elseif os(tvOS) + navigation() + #endif + } + + // MARK: Accessibility Ids + + private var gridStyleToggle: String { "gridStyleToggle" } + private var singleSectionNavItem: String { "singleValueSectionNavItem" } + private var singleOptionalSectionNavItem: String { "singleOptionalValueSectionNavItem" } + private var multiSectionNavItem: String { "multiValueSectionNavItem" } + + // MARK: Content + + private func singleValue() -> some View { + SingleValueSelection(items: items, isGridStyle: isGridStyle) + } + + private var singleValueNavLabel: some View { + #if os(iOS) + Image(systemName: "1.circle") + #elseif os(macOS) || os(tvOS) + HStack { + Image(systemName: "1.circle") + Text("Single Value") + } + .font(.title) + #endif + } + + private func singleOptionalValue() -> some View { + SingleOptionalValueSelection(items: items, isGridStyle: isGridStyle) + } + + private var singleOptionalValueNavLabel: some View { + #if os(iOS) + Image(systemName: "questionmark.circle") + #elseif os(macOS) || os(tvOS) + HStack { + Image(systemName: "questionmark.circle") + Text("Single Optional Value") + } + .font(.title) + #endif + } + + private func multiValue() -> some View { + MultiValueSelection(items: items, isGridStyle: isGridStyle) + } + + private var multiValueNavLabel: some View { + #if os(iOS) + Image(systemName: "n.circle") + #elseif os(macOS) || os(tvOS) + HStack { + Image(systemName: "n.circle") + Text("Multi Value") + } + .font(.title) + #endif + } + + private func navigationButton