Commit fffc51c75c
Unsigned
Layout: unified · split
gitbay.xcodeproj/project.pbxproj +110
| @@ -18,11 +18,19 @@ | |||
| 18 | remoteGlobalIDString = 8B289811303FF3E5001BF44A; | 18 | remoteGlobalIDString = 8B289811303FF3E5001BF44A; |
| 19 | remoteInfo = gitbay; | 19 | remoteInfo = gitbay; |
| 20 | }; | 20 | }; |
| 21 | 8B2898C7303FF3E5001BF44A /* PBXContainerItemProxy */ = { | ||
| 22 | isa = PBXContainerItemProxy; | ||
| 23 | containerPortal = 8B28980A303FF3E5001BF44A /* Project object */; | ||
| 24 | proxyType = 1; | ||
| 25 | remoteGlobalIDString = 8B289811303FF3E5001BF44A; | ||
| 26 | remoteInfo = gitbay; | ||
| 27 | }; | ||
| 21 | /* End PBXContainerItemProxy section */ | 28 | /* End PBXContainerItemProxy section */ |
| 22 | 29 | ||
| 23 | /* Begin PBXFileReference section */ | 30 | /* Begin PBXFileReference section */ |
| 24 | 8B289812303FF3E5001BF44A /* gitbay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = gitbay.app; sourceTree = BUILT_PRODUCTS_DIR; }; | 31 | 8B289812303FF3E5001BF44A /* gitbay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = gitbay.app; sourceTree = BUILT_PRODUCTS_DIR; }; |
| 25 | 8B2898A2303FF3E5001BF44A /* gitbayTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = gitbayTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; | 32 | 8B2898A2303FF3E5001BF44A /* gitbayTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = gitbayTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; |
| 33 | 8B2898C2303FF3E5001BF44A /* gitbayUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = gitbayUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; | ||
| 26 | /* End PBXFileReference section */ | 34 | /* End PBXFileReference section */ |
| 27 | 35 | ||
| 28 | /* Begin PBXFileSystemSynchronizedRootGroup section */ | 36 | /* Begin PBXFileSystemSynchronizedRootGroup section */ |
| @@ -36,6 +44,11 @@ | |||
| 36 | path = gitbayTests; | 44 | path = gitbayTests; |
| 37 | sourceTree = "<group>"; | 45 | sourceTree = "<group>"; |
| 38 | }; | 46 | }; |
| 47 | 8B2898C0303FF3E5001BF44A /* gitbayUITests */ = { | ||
| 48 | isa = PBXFileSystemSynchronizedRootGroup; | ||
| 49 | path = gitbayUITests; | ||
| 50 | sourceTree = "<group>"; | ||
| 51 | }; | ||
| 39 | /* End PBXFileSystemSynchronizedRootGroup section */ | 52 | /* End PBXFileSystemSynchronizedRootGroup section */ |
| 40 | 53 | ||
| 41 | /* Begin PBXFrameworksBuildPhase section */ | 54 | /* Begin PBXFrameworksBuildPhase section */ |
| @@ -54,6 +67,13 @@ | |||
| 54 | ); | 67 | ); |
| 55 | runOnlyForDeploymentPostprocessing = 0; | 68 | runOnlyForDeploymentPostprocessing = 0; |
| 56 | }; | 69 | }; |
| 70 | 8B2898C4303FF3E5001BF44A /* Frameworks */ = { | ||
| 71 | isa = PBXFrameworksBuildPhase; | ||
| 72 | buildActionMask = 2147483647; | ||
| 73 | files = ( | ||
| 74 | ); | ||
| 75 | runOnlyForDeploymentPostprocessing = 0; | ||
| 76 | }; | ||
| 57 | /* End PBXFrameworksBuildPhase section */ | 77 | /* End PBXFrameworksBuildPhase section */ |
| 58 | 78 | ||
| 59 | /* Begin PBXGroup section */ | 79 | /* Begin PBXGroup section */ |
| @@ -62,6 +82,7 @@ | |||
| 62 | children = ( | 82 | children = ( |
| 63 | 8B289814303FF3E5001BF44A /* gitbay */, | 83 | 8B289814303FF3E5001BF44A /* gitbay */, |
| 64 | 8B2898A0303FF3E5001BF44A /* gitbayTests */, | 84 | 8B2898A0303FF3E5001BF44A /* gitbayTests */, |
| 85 | 8B2898C0303FF3E5001BF44A /* gitbayUITests */, | ||
| 65 | 8B289813303FF3E5001BF44A /* Products */, | 86 | 8B289813303FF3E5001BF44A /* Products */, |
| 66 | ); | 87 | ); |
| 67 | sourceTree = "<group>"; | 88 | sourceTree = "<group>"; |
| @@ -71,6 +92,7 @@ | |||
| 71 | children = ( | 92 | children = ( |
| 72 | 8B289812303FF3E5001BF44A /* gitbay.app */, | 93 | 8B289812303FF3E5001BF44A /* gitbay.app */, |
| 73 | 8B2898A2303FF3E5001BF44A /* gitbayTests.xctest */, | 94 | 8B2898A2303FF3E5001BF44A /* gitbayTests.xctest */, |
| 95 | 8B2898C2303FF3E5001BF44A /* gitbayUITests.xctest */, | ||
| 74 | ); | 96 | ); |
| 75 | name = Products; | 97 | name = Products; |
| 76 | sourceTree = "<group>"; | 98 | sourceTree = "<group>"; |
| @@ -124,6 +146,29 @@ | |||
| 124 | productReference = 8B2898A2303FF3E5001BF44A /* gitbayTests.xctest */; | 146 | productReference = 8B2898A2303FF3E5001BF44A /* gitbayTests.xctest */; |
| 125 | productType = "com.apple.product-type.bundle.unit-test"; | 147 | productType = "com.apple.product-type.bundle.unit-test"; |
| 126 | }; | 148 | }; |
| 149 | 8B2898C1303FF3E5001BF44A /* gitbayUITests */ = { | ||
| 150 | isa = PBXNativeTarget; | ||
| 151 | buildConfigurationList = 8B2898CA303FF3E5001BF44A /* Build configuration list for PBXNativeTarget "gitbayUITests" */; | ||
| 152 | buildPhases = ( | ||
| 153 | 8B2898C3303FF3E5001BF44A /* Sources */, | ||
| 154 | 8B2898C4303FF3E5001BF44A /* Frameworks */, | ||
| 155 | 8B2898C5303FF3E5001BF44A /* Resources */, | ||
| 156 | ); | ||
| 157 | buildRules = ( | ||
| 158 | ); | ||
| 159 | dependencies = ( | ||
| 160 | 8B2898C6303FF3E5001BF44A /* PBXTargetDependency */, | ||
| 161 | ); | ||
| 162 | fileSystemSynchronizedGroups = ( | ||
| 163 | 8B2898C0303FF3E5001BF44A /* gitbayUITests */, | ||
| 164 | ); | ||
| 165 | name = gitbayUITests; | ||
| 166 | packageProductDependencies = ( | ||
| 167 | ); | ||
| 168 | productName = gitbayUITests; | ||
| 169 | productReference = 8B2898C2303FF3E5001BF44A /* gitbayUITests.xctest */; | ||
| 170 | productType = "com.apple.product-type.bundle.ui-testing"; | ||
| 171 | }; | ||
| 127 | /* End PBXNativeTarget section */ | 172 | /* End PBXNativeTarget section */ |
| 128 | 173 | ||
| 129 | /* Begin PBXProject section */ | 174 | /* Begin PBXProject section */ |
| @@ -141,6 +186,10 @@ | |||
| 141 | CreatedOnToolsVersion = 26.6; | 186 | CreatedOnToolsVersion = 26.6; |
| 142 | TestTargetID = 8B289811303FF3E5001BF44A; | 187 | TestTargetID = 8B289811303FF3E5001BF44A; |
| 143 | }; | 188 | }; |
| 189 | 8B2898C1303FF3E5001BF44A = { | ||
| 190 | CreatedOnToolsVersion = 26.6; | ||
| 191 | TestTargetID = 8B289811303FF3E5001BF44A; | ||
| 192 | }; | ||
| 144 | }; | 193 | }; |
| 145 | }; | 194 | }; |
| 146 | buildConfigurationList = 8B28980D303FF3E5001BF44A /* Build configuration list for PBXProject "gitbay" */; | 195 | buildConfigurationList = 8B28980D303FF3E5001BF44A /* Build configuration list for PBXProject "gitbay" */; |
| @@ -162,6 +211,7 @@ | |||
| 162 | targets = ( | 211 | targets = ( |
| 163 | 8B289811303FF3E5001BF44A /* gitbay */, | 212 | 8B289811303FF3E5001BF44A /* gitbay */, |
| 164 | 8B2898A1303FF3E5001BF44A /* gitbayTests */, | 213 | 8B2898A1303FF3E5001BF44A /* gitbayTests */, |
| 214 | 8B2898C1303FF3E5001BF44A /* gitbayUITests */, | ||
| 165 | ); | 215 | ); |
| 166 | }; | 216 | }; |
| 167 | /* End PBXProject section */ | 217 | /* End PBXProject section */ |
| @@ -181,6 +231,13 @@ | |||
| 181 | ); | 231 | ); |
| 182 | runOnlyForDeploymentPostprocessing = 0; | 232 | runOnlyForDeploymentPostprocessing = 0; |
| 183 | }; | 233 | }; |
| 234 | 8B2898C5303FF3E5001BF44A /* Resources */ = { | ||
| 235 | isa = PBXResourcesBuildPhase; | ||
| 236 | buildActionMask = 2147483647; | ||
| 237 | files = ( | ||
| 238 | ); | ||
| 239 | runOnlyForDeploymentPostprocessing = 0; | ||
| 240 | }; | ||
| 184 | /* End PBXResourcesBuildPhase section */ | 241 | /* End PBXResourcesBuildPhase section */ |
| 185 | 242 | ||
| 186 | /* Begin PBXSourcesBuildPhase section */ | 243 | /* Begin PBXSourcesBuildPhase section */ |
| @@ -198,6 +255,13 @@ | |||
| 198 | ); | 255 | ); |
| 199 | runOnlyForDeploymentPostprocessing = 0; | 256 | runOnlyForDeploymentPostprocessing = 0; |
| 200 | }; | 257 | }; |
| 258 | 8B2898C3303FF3E5001BF44A /* Sources */ = { | ||
| 259 | isa = PBXSourcesBuildPhase; | ||
| 260 | buildActionMask = 2147483647; | ||
| 261 | files = ( | ||
| 262 | ); | ||
| 263 | runOnlyForDeploymentPostprocessing = 0; | ||
| 264 | }; | ||
| 201 | /* End PBXSourcesBuildPhase section */ | 265 | /* End PBXSourcesBuildPhase section */ |
| 202 | 266 | ||
| 203 | /* Begin PBXTargetDependency section */ | 267 | /* Begin PBXTargetDependency section */ |
| @@ -206,6 +270,11 @@ | |||
| 206 | target = 8B289811303FF3E5001BF44A /* gitbay */; | 270 | target = 8B289811303FF3E5001BF44A /* gitbay */; |
| 207 | targetProxy = 8B2898A7303FF3E5001BF44A /* PBXContainerItemProxy */; | 271 | targetProxy = 8B2898A7303FF3E5001BF44A /* PBXContainerItemProxy */; |
| 208 | }; | 272 | }; |
| 273 | 8B2898C6303FF3E5001BF44A /* PBXTargetDependency */ = { | ||
| 274 | isa = PBXTargetDependency; | ||
| 275 | target = 8B289811303FF3E5001BF44A /* gitbay */; | ||
| 276 | targetProxy = 8B2898C7303FF3E5001BF44A /* PBXContainerItemProxy */; | ||
| 277 | }; | ||
| 209 | /* End PBXTargetDependency section */ | 278 | /* End PBXTargetDependency section */ |
| 210 | 279 | ||
| 211 | /* Begin XCBuildConfiguration section */ | 280 | /* Begin XCBuildConfiguration section */ |
| @@ -434,6 +503,38 @@ | |||
| 434 | }; | 503 | }; |
| 435 | name = Release; | 504 | name = Release; |
| 436 | }; | 505 | }; |
| 506 | 8B2898C8303FF3E5001BF44A /* Debug */ = { | ||
| 507 | isa = XCBuildConfiguration; | ||
| 508 | buildSettings = { | ||
| 509 | CURRENT_PROJECT_VERSION = 1; | ||
| 510 | DEVELOPMENT_TEAM = ZCNAX3VL9D; | ||
| 511 | GENERATE_INFOPLIST_FILE = YES; | ||
| 512 | MARKETING_VERSION = 1.0; | ||
| 513 | PRODUCT_BUNDLE_IDENTIFIER = org.gitbay.gitbayUITests; | ||
| 514 | PRODUCT_NAME = "$(TARGET_NAME)"; | ||
| 515 | SWIFT_EMIT_LOC_STRINGS = NO; | ||
| 516 | SWIFT_VERSION = 6.0; | ||
| 517 | TARGETED_DEVICE_FAMILY = "1,2"; | ||
| 518 | TEST_TARGET_NAME = gitbay; | ||
| 519 | }; | ||
| 520 | name = Debug; | ||
| 521 | }; | ||
| 522 | 8B2898C9303FF3E5001BF44A /* Release */ = { | ||
| 523 | isa = XCBuildConfiguration; | ||
| 524 | buildSettings = { | ||
| 525 | CURRENT_PROJECT_VERSION = 1; | ||
| 526 | DEVELOPMENT_TEAM = ZCNAX3VL9D; | ||
| 527 | GENERATE_INFOPLIST_FILE = YES; | ||
| 528 | MARKETING_VERSION = 1.0; | ||
| 529 | PRODUCT_BUNDLE_IDENTIFIER = org.gitbay.gitbayUITests; | ||
| 530 | PRODUCT_NAME = "$(TARGET_NAME)"; | ||
| 531 | SWIFT_EMIT_LOC_STRINGS = NO; | ||
| 532 | SWIFT_VERSION = 6.0; | ||
| 533 | TARGETED_DEVICE_FAMILY = "1,2"; | ||
| 534 | TEST_TARGET_NAME = gitbay; | ||
| 535 | }; | ||
| 536 | name = Release; | ||
| 537 | }; | ||
| 437 | /* End XCBuildConfiguration section */ | 538 | /* End XCBuildConfiguration section */ |
| 438 | 539 | ||
| 439 | /* Begin XCConfigurationList section */ | 540 | /* Begin XCConfigurationList section */ |
| @@ -464,6 +565,15 @@ | |||
| 464 | defaultConfigurationIsVisible = 0; | 565 | defaultConfigurationIsVisible = 0; |
| 465 | defaultConfigurationName = Release; | 566 | defaultConfigurationName = Release; |
| 466 | }; | 567 | }; |
| 568 | 8B2898CA303FF3E5001BF44A /* Build configuration list for PBXNativeTarget "gitbayUITests" */ = { | ||
| 569 | isa = XCConfigurationList; | ||
| 570 | buildConfigurations = ( | ||
| 571 | 8B2898C8303FF3E5001BF44A /* Debug */, | ||
| 572 | 8B2898C9303FF3E5001BF44A /* Release */, | ||
| 573 | ); | ||
| 574 | defaultConfigurationIsVisible = 0; | ||
| 575 | defaultConfigurationName = Release; | ||
| 576 | }; | ||
| 467 | /* End XCConfigurationList section */ | 577 | /* End XCConfigurationList section */ |
| 468 | 578 | ||
| 469 | /* Begin XCRemoteSwiftPackageReference section */ | 579 | /* Begin XCRemoteSwiftPackageReference section */ |
gitbay/Views/Issues/IssueListView.swift +1
| @@ -41,6 +41,7 @@ struct IssueListView: View { | |||
| 41 | } label: { | 41 | } label: { |
| 42 | Image(systemName: "plus") | 42 | Image(systemName: "plus") |
| 43 | } | 43 | } |
| 44 | .accessibilityIdentifier("issue-create-button") | ||
| 44 | } | 45 | } |
| 45 | } | 46 | } |
| 46 | .sheet(isPresented: $composing) { | 47 | .sheet(isPresented: $composing) { |
gitbay/Views/Issues/IssueView.swift +2
| @@ -147,6 +147,7 @@ struct IssueView: View { | |||
| 147 | } | 147 | } |
| 148 | .disabled(model.working) | 148 | .disabled(model.working) |
| 149 | .task { await model.loadMilestones() } | 149 | .task { await model.loadMilestones() } |
| 150 | .accessibilityIdentifier("milestone-menu") | ||
| 150 | } | 151 | } |
| 151 | } | 152 | } |
| 152 | 153 | ||
| @@ -232,6 +233,7 @@ struct IssueView: View { | |||
| 232 | } | 233 | } |
| 233 | } | 234 | } |
| 234 | .disabled(model.working) | 235 | .disabled(model.working) |
| 236 | .accessibilityIdentifier("issue-actions-menu") | ||
| 235 | } | 237 | } |
| 236 | } | 238 | } |
| 237 | } | 239 | } |
gitbay/Views/MRs/MRListView.swift +8
| @@ -39,6 +39,7 @@ struct MRListView: View { | |||
| 39 | } label: { | 39 | } label: { |
| 40 | Image(systemName: "plus") | 40 | Image(systemName: "plus") |
| 41 | } | 41 | } |
| 42 | .accessibilityIdentifier("mr-create-button") | ||
| 42 | } | 43 | } |
| 43 | } | 44 | } |
| 44 | .sheet(isPresented: $composing) { | 45 | .sheet(isPresented: $composing) { |
| @@ -72,17 +73,23 @@ private struct MRCreateSheet: View { | |||
| 72 | TextField("Source branch", text: $source) | 73 | TextField("Source branch", text: $source) |
| 73 | .autocorrectionDisabled() | 74 | .autocorrectionDisabled() |
| 74 | .textInputAutocapitalization(.never) | 75 | .textInputAutocapitalization(.never) |
| 76 | .accessibilityIdentifier("mr-source") | ||
| 75 | TextField("Target branch", text: $target) | 77 | TextField("Target branch", text: $target) |
| 76 | .autocorrectionDisabled() | 78 | .autocorrectionDisabled() |
| 77 | .textInputAutocapitalization(.never) | 79 | .textInputAutocapitalization(.never) |
| 80 | .accessibilityIdentifier("mr-target") | ||
| 78 | } | 81 | } |
| 79 | Section("Title") { | 82 | Section("Title") { |
| 80 | TextField("Title", text: $title, axis: .vertical) | 83 | TextField("Title", text: $title, axis: .vertical) |
| 81 | .lineLimit(1...3) | 84 | .lineLimit(1...3) |
| 85 | .autocorrectionDisabled() | ||
| 86 | .accessibilityIdentifier("mr-title") | ||
| 82 | } | 87 | } |
| 83 | Section("Body") { | 88 | Section("Body") { |
| 84 | TextEditor(text: $bodyText) | 89 | TextEditor(text: $bodyText) |
| 85 | .frame(minHeight: 120) | 90 | .frame(minHeight: 120) |
| 91 | .autocorrectionDisabled() | ||
| 92 | .accessibilityIdentifier("mr-body") | ||
| 86 | } | 93 | } |
| 87 | if let error = model.errorMessage { | 94 | if let error = model.errorMessage { |
| 88 | Section { | 95 | Section { |
| @@ -117,6 +124,7 @@ private struct MRCreateSheet: View { | |||
| 117 | || target.trimmingCharacters(in: .whitespaces).isEmpty | 124 | || target.trimmingCharacters(in: .whitespaces).isEmpty |
| 118 | || title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty | 125 | || title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty |
| 119 | ) | 126 | ) |
| 127 | .accessibilityIdentifier("mr-submit") | ||
| 120 | } | 128 | } |
| 121 | } | 129 | } |
| 122 | } | 130 | } |
gitbay/Views/MRs/MRView.swift +1
| @@ -274,6 +274,7 @@ struct MRView: View { | |||
| 274 | } | 274 | } |
| 275 | } | 275 | } |
| 276 | .disabled(model.working) | 276 | .disabled(model.working) |
| 277 | .accessibilityIdentifier("mr-actions-menu") | ||
| 277 | } | 278 | } |
| 278 | } | 279 | } |
| 279 | } | 280 | } |
gitbay/Views/Shared/ComposeSheet.swift +7
| @@ -19,11 +19,17 @@ struct ComposeSheet: View { | |||
| 19 | Section("Title") { | 19 | Section("Title") { |
| 20 | TextField("Title", text: $title, axis: .vertical) | 20 | TextField("Title", text: $title, axis: .vertical) |
| 21 | .lineLimit(1...3) | 21 | .lineLimit(1...3) |
| 22 | // Titles name code: paths, commands, branch names. | ||
| 23 | // Autocorrection rewrites them. | ||
| 24 | .autocorrectionDisabled() | ||
| 25 | .accessibilityIdentifier("compose-title") | ||
| 22 | } | 26 | } |
| 23 | Section("Body") { | 27 | Section("Body") { |
| 24 | TextEditor(text: $bodyText) | 28 | TextEditor(text: $bodyText) |
| 25 | .frame(minHeight: 160) | 29 | .frame(minHeight: 160) |
| 26 | .font(.body) | 30 | .font(.body) |
| 31 | .autocorrectionDisabled() | ||
| 32 | .accessibilityIdentifier("compose-body") | ||
| 27 | } | 33 | } |
| 28 | if let errorMessage { | 34 | if let errorMessage { |
| 29 | Section { | 35 | Section { |
| @@ -45,6 +51,7 @@ struct ComposeSheet: View { | |||
| 45 | } else { | 51 | } else { |
| 46 | Button(submitLabel, action: onSubmit) | 52 | Button(submitLabel, action: onSubmit) |
| 47 | .disabled(title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) | 53 | .disabled(title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) |
| 54 | .accessibilityIdentifier("compose-submit") | ||
| 48 | } | 55 | } |
| 49 | } | 56 | } |
| 50 | } | 57 | } |
gitbayUITests/LiveSmokeUITests.swift added +142
| @@ -0,0 +1,142 @@ | |||
| 1 | import XCTest | ||
| 2 | |||
| 3 | /// Live smoke flows against gitbay.org, driven by accessibility — the | ||
| 4 | /// coordinate-free verification the simulator's input pipeline cannot be | ||
| 5 | /// trusted to do (krz/gitbay-ios#5). | ||
| 6 | /// | ||
| 7 | /// These WRITE to the live instance (an MR is opened and closed, a | ||
| 8 | /// milestone assigned), so they only run when explicitly asked: | ||
| 9 | /// | ||
| 10 | /// TEST_RUNNER_GITBAY_UITEST_LIVE=1 xcodebuild test \ | ||
| 11 | /// -only-testing:gitbayUITests ... | ||
| 12 | /// | ||
| 13 | /// They assume the simulator is already signed in as an account that can | ||
| 14 | /// write to krz/gitbay-ios, and that branch `ui-smoke` exists. | ||
| 15 | final class LiveSmokeUITests: XCTestCase { | ||
| 16 | |||
| 17 | private var app: XCUIApplication! | ||
| 18 | |||
| 19 | override func setUpWithError() throws { | ||
| 20 | try XCTSkipUnless( | ||
| 21 | ProcessInfo.processInfo.environment["GITBAY_UITEST_LIVE"] == "1", | ||
| 22 | "live UI smoke is opt-in; set TEST_RUNNER_GITBAY_UITEST_LIVE=1" | ||
| 23 | ) | ||
| 24 | continueAfterFailure = false | ||
| 25 | app = XCUIApplication() | ||
| 26 | app.launch() | ||
| 27 | } | ||
| 28 | |||
| 29 | /// Everything in one ordered pass: milestone assign, edit-save, | ||
| 30 | /// MR create, MR close. One test so the flows share navigation and | ||
| 31 | /// the writes happen exactly once. | ||
| 32 | func testCreationAndEditingFlows() throws { | ||
| 33 | openRepo("krz/gitbay-ios") | ||
| 34 | |||
| 35 | // --- Issue #4: milestone picker assigns v1.0.0 --- | ||
| 36 | app.staticTexts["Issues"].firstMatch.tap() | ||
| 37 | let issueRow = app.staticTexts | ||
| 38 | .containing(NSPredicate(format: "label CONTAINS 'README.org'")).firstMatch | ||
| 39 | XCTAssertTrue(issueRow.waitForExistence(timeout: 10), "issue #4 not in the list") | ||
| 40 | issueRow.tap() | ||
| 41 | |||
| 42 | let milestoneMenu = app.descendants(matching: .any) | ||
| 43 | .matching(identifier: "milestone-menu").firstMatch | ||
| 44 | XCTAssertTrue(milestoneMenu.waitForExistence(timeout: 10)) | ||
| 45 | milestoneMenu.tap() | ||
| 46 | let milestoneChoice = app.buttons | ||
| 47 | .containing(NSPredicate(format: "label BEGINSWITH 'v1.0.0'")).firstMatch | ||
| 48 | XCTAssertTrue(milestoneChoice.waitForExistence(timeout: 5), "milestone menu did not open") | ||
| 49 | milestoneChoice.tap() | ||
| 50 | // The reload renders the assigned milestone in the header row. | ||
| 51 | XCTAssertTrue(app.staticTexts["v1.0.0"].firstMatch | ||
| 52 | .waitForExistence(timeout: 10), "milestone not shown after assign") | ||
| 53 | |||
| 54 | // --- Issue #4: edit sheet saves (content unchanged) --- | ||
| 55 | app.descendants(matching: .any).matching(identifier: "issue-actions-menu") | ||
| 56 | .firstMatch.tap() | ||
| 57 | let edit = app.buttons["Edit"].firstMatch | ||
| 58 | XCTAssertTrue(edit.waitForExistence(timeout: 5)) | ||
| 59 | edit.tap() | ||
| 60 | let title = app.descendants(matching: .any) | ||
| 61 | .matching(identifier: "compose-title").firstMatch | ||
| 62 | XCTAssertTrue(title.waitForExistence(timeout: 5), "edit sheet did not open") | ||
| 63 | XCTAssertTrue((title.value as? String)?.contains("README.org") == true, | ||
| 64 | "edit sheet did not prefill") | ||
| 65 | app.descendants(matching: .any).matching(identifier: "compose-submit") | ||
| 66 | .firstMatch.tap() | ||
| 67 | // Sheet dismissal proves the save round-tripped without error. | ||
| 68 | XCTAssertTrue(waitForDisappearance(title, timeout: 15), "edit save did not dismiss") | ||
| 69 | |||
| 70 | back() // to issues list | ||
| 71 | back() // to repo screen | ||
| 72 | |||
| 73 | // --- MR: create from ui-smoke, then close it --- | ||
| 74 | app.staticTexts["Merge Requests"].firstMatch.tap() | ||
| 75 | app.descendants(matching: .any).matching(identifier: "mr-create-button") | ||
| 76 | .firstMatch.tap() | ||
| 77 | |||
| 78 | let source = app.descendants(matching: .any) | ||
| 79 | .matching(identifier: "mr-source").firstMatch | ||
| 80 | XCTAssertTrue(source.waitForExistence(timeout: 5), "MR create sheet did not open") | ||
| 81 | source.tap() | ||
| 82 | source.typeText("ui-smoke") | ||
| 83 | |||
| 84 | let target = app.descendants(matching: .any) | ||
| 85 | .matching(identifier: "mr-target").firstMatch | ||
| 86 | // The default branch prefilled while the sheet loaded. | ||
| 87 | XCTAssertEqual(target.value as? String, "main", "target did not prefill") | ||
| 88 | |||
| 89 | let mrTitle = app.descendants(matching: .any) | ||
| 90 | .matching(identifier: "mr-title").firstMatch | ||
| 91 | mrTitle.tap() | ||
| 92 | mrTitle.typeText("UI smoke: mr create from the app") | ||
| 93 | |||
| 94 | app.descendants(matching: .any).matching(identifier: "mr-submit") | ||
| 95 | .firstMatch.tap() | ||
| 96 | |||
| 97 | let createdRow = app.staticTexts | ||
| 98 | .containing(NSPredicate(format: "label CONTAINS 'UI smoke'")).firstMatch | ||
| 99 | XCTAssertTrue(createdRow.waitForExistence(timeout: 15), "created MR not in the list") | ||
| 100 | createdRow.tap() | ||
| 101 | |||
| 102 | app.descendants(matching: .any).matching(identifier: "mr-actions-menu") | ||
| 103 | .firstMatch.tap() | ||
| 104 | let close = app.buttons["Close"].firstMatch | ||
| 105 | XCTAssertTrue(close.waitForExistence(timeout: 5)) | ||
| 106 | close.tap() | ||
| 107 | // The confirmation dialog's destructive Close. | ||
| 108 | let confirm = app.buttons["Close"].firstMatch | ||
| 109 | XCTAssertTrue(confirm.waitForExistence(timeout: 5), "close confirmation missing") | ||
| 110 | confirm.tap() | ||
| 111 | |||
| 112 | XCTAssertTrue(app.staticTexts["closed"].firstMatch | ||
| 113 | .waitForExistence(timeout: 15), "MR did not show closed after close") | ||
| 114 | } | ||
| 115 | |||
| 116 | // MARK: - Helpers | ||
| 117 | |||
| 118 | private func openRepo(_ path: String) { | ||
| 119 | let tab = app.buttons["Repositories"].firstMatch | ||
| 120 | XCTAssertTrue(tab.waitForExistence(timeout: 10)) | ||
| 121 | tab.tap() | ||
| 122 | let search = app.searchFields.firstMatch | ||
| 123 | XCTAssertTrue(search.waitForExistence(timeout: 10)) | ||
| 124 | search.tap() | ||
| 125 | search.typeText(path) | ||
| 126 | let row = app.staticTexts[path].firstMatch | ||
| 127 | XCTAssertTrue(row.waitForExistence(timeout: 15), "\(path) not in the repo list") | ||
| 128 | row.tap() | ||
| 129 | // Repo screen is loaded once its links render. | ||
| 130 | XCTAssertTrue(app.staticTexts["Files"].firstMatch.waitForExistence(timeout: 10)) | ||
| 131 | } | ||
| 132 | |||
| 133 | private func back() { | ||
| 134 | app.navigationBars.buttons.firstMatch.tap() | ||
| 135 | } | ||
| 136 | |||
| 137 | private func waitForDisappearance(_ element: XCUIElement, timeout: TimeInterval) -> Bool { | ||
| 138 | let predicate = NSPredicate(format: "exists == false") | ||
| 139 | let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element) | ||
| 140 | return XCTWaiter().wait(for: [expectation], timeout: timeout) == .completed | ||
| 141 | } | ||
| 142 | } | ||