diff --git a/PediFoods.xcodeproj/project.pbxproj b/PediFoods.xcodeproj/project.pbxproj index 02166ad..077e564 100644 --- a/PediFoods.xcodeproj/project.pbxproj +++ b/PediFoods.xcodeproj/project.pbxproj @@ -92,6 +92,7 @@ 9EDDCECCAEEB8FA4909DA9D4 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBFCE20C54A79DEA8F4512B /* AppCoordinator.swift */; }; 9F09502CF9331F0C94BFA5F0 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2B2C5BBDFF4473C472F45B4 /* AppState.swift */; }; A115420D6854B7C9DCF339DE /* CartStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F9CDFF62AE19F3EE36A1D19 /* CartStateTests.swift */; }; + A24EE365C06DBA8FD2D91213 /* HomeFiltersFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44E3CD64AF31CEFFD932D7D /* HomeFiltersFlowTests.swift */; }; A3DFC65C635BE5D96660A432 /* Inputs.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB95D6804877D2ED244811D0 /* Inputs.swift */; }; A68ED6FBE5307F8674A5A4AB /* HomeView+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0CCEF22D356F9EC723999D /* HomeView+Data.swift */; }; A6AE21C3CC220D50251865BE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63DCCE423400D4D3785EE0F0 /* Assets.xcassets */; }; @@ -270,6 +271,7 @@ B2CE116C585A7529CF309B3D /* SnackbarCenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnackbarCenterTests.swift; sourceTree = ""; }; B33118037CF56CD70C332E2C /* UITestSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestSupport.swift; sourceTree = ""; }; B35D1C07050150E305FAF09A /* PizzaProductDetailSheet+Flow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PizzaProductDetailSheet+Flow.swift"; sourceTree = ""; }; + B44E3CD64AF31CEFFD932D7D /* HomeFiltersFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeFiltersFlowTests.swift; sourceTree = ""; }; B807C1B779CAE1340D377D43 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; B85C265A23C9BA93E3DD6898 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; BBD89D11DF567A6A6E078CC8 /* HomeGuestFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeGuestFlowTests.swift; sourceTree = ""; }; @@ -516,6 +518,7 @@ C3A0787FEAE67318791D6CFD /* AuthenticatedProfileNavigationTests.swift */, BEAA5F0BA8EBC7F2D19472D6 /* AuthenticatedSessionFlowTests.swift */, 7034E89267D9F5D86BF1E424 /* CartCheckoutFlowTests.swift */, + B44E3CD64AF31CEFFD932D7D /* HomeFiltersFlowTests.swift */, BBD89D11DF567A6A6E078CC8 /* HomeGuestFlowTests.swift */, FE0C7A506EDE8D0FD8F16AC6 /* OrderDetailsFlowTests.swift */, 56E3E6C1834786CA3621B7BB /* PediFoodsUITestsLaunchTests.swift */, @@ -738,6 +741,7 @@ 09FA9556CD659F93C25447DB /* AuthenticatedProfileNavigationTests.swift in Sources */, 1AFC280D183E5DC53949C317 /* AuthenticatedSessionFlowTests.swift in Sources */, 4B85C214A0BF3DF50311C76C /* CartCheckoutFlowTests.swift in Sources */, + A24EE365C06DBA8FD2D91213 /* HomeFiltersFlowTests.swift in Sources */, D7B6082157DA0DF2382EB943 /* HomeGuestFlowTests.swift in Sources */, E3C2AE0D45A31B955EFAEEAD /* OrderDetailsFlowTests.swift in Sources */, 80B476CCE2B90BEE2FA4CB37 /* PediFoodsUITestsLaunchTests.swift in Sources */, diff --git a/PediFoods/ContentView.swift b/PediFoods/ContentView.swift index 5f06711..3d77f86 100644 --- a/PediFoods/ContentView.swift +++ b/PediFoods/ContentView.swift @@ -52,7 +52,7 @@ struct ContentView: View { .transition(.opacity) } } - .sheet(item: $appState.activeModal) { modal in + .sheet(item: $appState.activeModal, onDismiss: { appState.activeModal = nil }) { modal in switch modal { case .addressPicker: AddressPickerModalView(appState: $appState, selectedTab: $selectedTab) diff --git a/PediFoodsUITests/HomeFiltersFlowTests.swift b/PediFoodsUITests/HomeFiltersFlowTests.swift new file mode 100644 index 0000000..b936d6e --- /dev/null +++ b/PediFoodsUITests/HomeFiltersFlowTests.swift @@ -0,0 +1,68 @@ +import XCTest + +/// Covers Home's filter sheet (FiltersModalView.swift, ~700 lines, was +/// 0% covered), reachable via the search bar's filter icon +/// (identifier "slider.horizontal.3"). Uses the authenticated QA account +/// rather than guest mode - the guest address picker +/// (PublicLocationPickerView) currently can't load its states/cities list +/// at all, because the backend rejects the simulator's App Attest bypass +/// with 403 APP_ATTEST_VERIFICATION_FAILED (confirmed directly via curl +/// against /api/public/session, a pre-existing backend-side issue, see +/// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md). That +/// leaves the guest address-picker sheet re-presenting itself indefinitely +/// (HomeView+Data.loadGuestStores() keeps re-triggering it while no +/// location is set), permanently covering Home. Logging in with the +/// standing QA account swaps AddressPickerModalView to the +/// App-Attest-free AddressesView instead, routing around the guest wall +/// entirely. +final class HomeFiltersFlowTests: XCTestCase { + override func setUpWithError() throws { + continueAfterFailure = false + } + + override func tearDownWithError() throws { + XCUIApplication().logoutIfAuthenticated() + } + + func testFiltersSheetSelectionsAndApply() throws { + let app = XCUIApplication() + app.launch() + XCTAssertTrue(app.ensureLoggedIn(), "Login never completed") + + // ensureLoggedIn can land on whichever tab it detected the + // authenticated session from - switch explicitly. + XCTAssertTrue(app.buttons["Home"].waitForExistence(timeout: 10)) + app.buttons["Home"].tap() + + // The filter icon lives inside Home's collapsing header + // (SearchBar, positioned with .offset(y: collapseProgress * -120)) + // - a plain .tap() was separately confirmed (while still chasing + // the guest-mode blocker above) to sometimes resolve to an invalid + // "Computed hit point {-1, -1}" for this transform-positioned + // element. Tapping a normalized-offset coordinate on the element + // bypasses XCUITest's automatic hit-point computation. + let filterIcon = app.buttons.matching(identifier: "slider.horizontal.3").firstMatch + XCTAssertTrue(filterIcon.waitForExistence(timeout: 10), "Filter icon never appeared on Home") + filterIcon.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() + XCTAssertTrue(app.staticTexts["Filtros"].waitForExistence(timeout: 5), "Never reached the filters sheet") + + // Sort option - just needs to be selectable, no assertion on the + // resulting Home state (that's real backend store data, out of + // scope here). + let ratingSort = app.buttons["Avaliação"] + if ratingSort.exists { + ratingSort.tap() + } + + // Price tier - label is the raw "$"/"$$"/etc symbol. + let mediumPrice = app.buttons["$$"] + if mediumPrice.exists { + mediumPrice.tap() + } + + app.buttons["Aplicar Filtros"].tap() + + // Applying dismisses back to Home. + XCTAssertTrue(app.textFields["Search menu, restaurant or craving"].waitForExistence(timeout: 10), "Never returned to Home after applying filters") + } +}