diff --git a/Feels.xcodeproj/project.pbxproj b/Feels.xcodeproj/project.pbxproj index ca10152..fb37e75 100644 --- a/Feels.xcodeproj/project.pbxproj +++ b/Feels.xcodeproj/project.pbxproj @@ -26,11 +26,13 @@ 1CDEFBBF2F3B8736006AE6A1 /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = 1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */; }; 1CDEFBC02F3B8736006AE6A1 /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = 1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */; }; 2EE4D94530F6BF39B26FB4D4 /* DayScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427CD9C91D43AB6A0302B4DD /* DayScreen.swift */; }; + 39C43652C41F5459788A604D /* SpanishLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C2982F0B879A0C57273F0E /* SpanishLocalizationTests.swift */; }; 46F07FA9D330456697C9AC29 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD90B47278C7E7A001C4FEA /* WidgetKit.framework */; }; 4F1C717B7747918A459322CB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4D304CD05CC7C662CCD7DCB /* Foundation.framework */; }; 54259F7B3F4E959B3F4055E4 /* StreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E2A2FC314F88244CA946BF /* StreakTests.swift */; }; 6F9C9C4B50CF8C1769171FF9 /* NoteEditTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469470483072085BE9E04E12 /* NoteEditTests.swift */; }; 756B9857B0657D2DB2D6D4E2 /* AppResumeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359E1D32D936859E5A0C9F3 /* AppResumeTests.swift */; }; + 85EF4702AE378AB3198E67D3 /* AccessibilityTextSizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C033EE00E7E7B3448FB862DA /* AccessibilityTextSizeTests.swift */; }; 92C1523E0398F866DB4CA027 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA8B21231D67DED575502 /* SettingsScreen.swift */; }; 9559409B5AEEAB40EBCB6AF9 /* VoteLogicsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD717F91BD65382B7DDFE3C4 /* VoteLogicsTests.swift */; }; A018FE95582C04ED0F1806DC /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */; }; @@ -73,6 +75,7 @@ F6A7B8C900000000B4C5D6E7 /* EntryDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A7B8C9D0E1F2A3B4C5D6E7 /* EntryDeleteTests.swift */; }; F75470AA2BA1E9EFF8F5265A /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DC4C498A1185DC831F4593 /* LocalizationTests.swift */; }; F8A9B0C100000000B6C7D8E9 /* HeaderMoodLoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A9B0C1D2E3F4A5B6C7D8E9 /* HeaderMoodLoggingTests.swift */; }; + FE1A332D03B0F969BAF26CEF /* PersonalityPackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A84D4CB70B693A6CDA212035 /* PersonalityPackTests.swift */; }; FF66660066666600FFFFFFFF /* SecondaryTabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF66666666666666FFFFFFFF /* SecondaryTabTests.swift */; }; /* End PBXBuildFile section */ @@ -150,6 +153,7 @@ 21CD463209E0909393545D62 /* TrialBannerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrialBannerTests.swift; sourceTree = ""; }; 29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = ""; }; 29E2A2FC314F88244CA946BF /* StreakTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StreakTests.swift; sourceTree = ""; }; + 31C2982F0B879A0C57273F0E /* SpanishLocalizationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SpanishLocalizationTests.swift; sourceTree = ""; }; 35AF32CC88B36CDFCB338F2C /* TrialExpirationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TrialExpirationTests.swift; sourceTree = ""; }; 427CD9C91D43AB6A0302B4DD /* DayScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayScreen.swift; sourceTree = ""; }; 469470483072085BE9E04E12 /* NoteEditTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteEditTests.swift; sourceTree = ""; }; @@ -164,6 +168,7 @@ A3B4C5D6E7F8A9B0C1D2E3F4 /* DataPersistenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataPersistenceTests.swift; sourceTree = ""; }; A6988985985DE9C29CFDFA96 /* InsightsEmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsightsEmptyStateTests.swift; sourceTree = ""; }; A7B8C9D0E1F2A3B4C5D6E7F8 /* NotesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesTests.swift; sourceTree = ""; }; + A84D4CB70B693A6CDA212035 /* PersonalityPackTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PersonalityPackTests.swift; sourceTree = ""; }; A9B0C1D2E3F4A5B6C7D8E9FA /* DayViewGroupingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayViewGroupingTests.swift; sourceTree = ""; }; AA11111111111111AAAAAAAA /* AppLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLaunchTests.swift; sourceTree = ""; }; B0C1D2E3F4A5B6C7D8E9FA0B /* AllDayViewStylesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDayViewStylesTests.swift; sourceTree = ""; }; @@ -174,6 +179,7 @@ B8C9D0E1F2A3B4C5D6E7F8A9 /* MonthViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthViewTests.swift; sourceTree = ""; }; BB22222222222222BBBBBBBB /* MoodLoggingEmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodLoggingEmptyStateTests.swift; sourceTree = ""; }; BE4D06D4E7188339DE8BC040 /* SettingsLegalLinksTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SettingsLegalLinksTests.swift; sourceTree = ""; }; + C033EE00E7E7B3448FB862DA /* AccessibilityTextSizeTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccessibilityTextSizeTests.swift; sourceTree = ""; }; C1D2E3F4A5B6C7D8E9FA0B1C /* MonthViewInteractionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthViewInteractionTests.swift; sourceTree = ""; }; C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = ""; }; C5D6E7F8A9B0C1D2E3F4A5B6 /* AppThemeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppThemeTests.swift; sourceTree = ""; }; @@ -433,6 +439,9 @@ BE4D06D4E7188339DE8BC040 /* SettingsLegalLinksTests.swift */, F9A9583BE84E8DC13DB61F2B /* DeepLinkTests.swift */, 35AF32CC88B36CDFCB338F2C /* TrialExpirationTests.swift */, + A84D4CB70B693A6CDA212035 /* PersonalityPackTests.swift */, + 31C2982F0B879A0C57273F0E /* SpanishLocalizationTests.swift */, + C033EE00E7E7B3448FB862DA /* AccessibilityTextSizeTests.swift */, ); path = "Tests iOS"; sourceTree = ""; @@ -839,6 +848,9 @@ E78F98C41E83B81DAF43139E /* SettingsLegalLinksTests.swift in Sources */, C1A23F0250FD3380F0A0F999 /* DeepLinkTests.swift in Sources */, CFB41ED5D294B8997DB694E2 /* TrialExpirationTests.swift in Sources */, + FE1A332D03B0F969BAF26CEF /* PersonalityPackTests.swift in Sources */, + 39C43652C41F5459788A604D /* SpanishLocalizationTests.swift in Sources */, + 85EF4702AE378AB3198E67D3 /* AccessibilityTextSizeTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Shared/AccessibilityIdentifiers.swift b/Shared/AccessibilityIdentifiers.swift index a3a7beb..3a78c06 100644 --- a/Shared/AccessibilityIdentifiers.swift +++ b/Shared/AccessibilityIdentifiers.swift @@ -93,6 +93,9 @@ enum AccessibilityID { static func iconPackButton(_ name: String) -> String { "customize_iconpack_\(name.lowercased())" } + static func personalityPackButton(_ name: String) -> String { + "customize_personality_\(name.lowercased())" + } static func appThemeCard(_ name: String) -> String { "apptheme_card_\(name.lowercased())" } diff --git a/Shared/Views/CustomizeView/CustomizeView.swift b/Shared/Views/CustomizeView/CustomizeView.swift index 1fc5e8a..ae45d0f 100644 --- a/Shared/Views/CustomizeView/CustomizeView.swift +++ b/Shared/Views/CustomizeView/CustomizeView.swift @@ -589,6 +589,7 @@ struct PersonalityPackPickerCompact: View { ) } .buttonStyle(.plain) + .accessibilityIdentifier(AccessibilityID.Customize.personalityPackButton(aPack.title())) // .blur(radius: aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW ? 4 : 0) } } diff --git a/Tests iOS/AccessibilityTextSizeTests.swift b/Tests iOS/AccessibilityTextSizeTests.swift new file mode 100644 index 0000000..eb2c270 --- /dev/null +++ b/Tests iOS/AccessibilityTextSizeTests.swift @@ -0,0 +1,62 @@ +// +// AccessibilityTextSizeTests.swift +// Tests iOS +// +// TC-142: App remains functional at largest accessibility text size. +// + +import XCTest + +final class AccessibilityTextSizeTests: BaseUITestCase { + override var seedFixture: String? { "single_mood" } + override var bypassSubscription: Bool { true } + + override func setUp() { + // Do NOT call super — we need custom content size launch args + continueAfterFailure = false + + let application = XCUIApplication() + var args: [String] = [ + "--ui-testing", "--disable-animations", + "--reset-state", + "--bypass-subscription", + "--skip-onboarding", + "-AppleLanguages", "(en)", + "-AppleLocale", "en_US", + "-UIPreferredContentSizeCategoryName", "UICTContentSizeCategoryAccessibilityXXL" + ] + application.launchArguments = args + application.launchEnvironment = ["UI_TEST_FIXTURE": "single_mood"] + application.launch() + app = application + } + + /// TC-142: App launches and is navigable at largest accessibility text size. + func testLargestTextSize_AppRemainsNavigable() { + // Verify Day tab is loaded and has content + assertDayContentVisible() + + captureScreenshot(name: "accessibility_xxl_day") + + // Navigate through all tabs to verify nothing crashes + let tabBar = TabBarScreen(app: app) + + tabBar.tapMonth() + XCTAssertTrue( + tabBar.monthTab.waitForExistence(timeout: 5), + "Month tab should be accessible at XXL text size" + ) + captureScreenshot(name: "accessibility_xxl_month") + + tabBar.tapYear() + XCTAssertTrue( + tabBar.yearTab.waitForExistence(timeout: 5), + "Year tab should be accessible at XXL text size" + ) + captureScreenshot(name: "accessibility_xxl_year") + + let settingsScreen = tabBar.tapSettings() + settingsScreen.assertVisible() + captureScreenshot(name: "accessibility_xxl_settings") + } +} diff --git a/Tests iOS/Helpers/WaitHelpers.swift b/Tests iOS/Helpers/WaitHelpers.swift index d21530f..5fb4832 100644 --- a/Tests iOS/Helpers/WaitHelpers.swift +++ b/Tests iOS/Helpers/WaitHelpers.swift @@ -43,6 +43,7 @@ enum UITestID { static func votingLayoutButton(_ name: String) -> String { "customize_voting_\(name.lowercased())" } static func dayStyleButton(_ name: String) -> String { "customize_daystyle_\(name.lowercased())" } static func iconPackButton(_ name: String) -> String { "customize_iconpack_\(name.lowercased())" } + static func personalityPackButton(_ name: String) -> String { "customize_personality_\(name.lowercased())" } static func appThemeCard(_ name: String) -> String { "apptheme_card_\(name.lowercased())" } static let pickerDoneButton = "apptheme_picker_done" static let previewCancelButton = "apptheme_preview_cancel" diff --git a/Tests iOS/PersonalityPackTests.swift b/Tests iOS/PersonalityPackTests.swift new file mode 100644 index 0000000..d9e5eb4 --- /dev/null +++ b/Tests iOS/PersonalityPackTests.swift @@ -0,0 +1,51 @@ +// +// PersonalityPackTests.swift +// Tests iOS +// +// TC-052: Select personality pack in Customize tab. +// + +import XCTest + +final class PersonalityPackTests: BaseUITestCase { + override var seedFixture: String? { "single_mood" } + override var bypassSubscription: Bool { true } + + /// TC-052: Selecting a different personality pack updates the checkmark. + func testPersonalityPack_SelectCoach() { + let tabBar = TabBarScreen(app: app) + let settingsScreen = tabBar.tapSettings() + settingsScreen.assertVisible() + settingsScreen.tapCustomizeTab() + + let customizeScreen = CustomizeScreen(app: app) + + // Scroll down to personality pack section and select "Coach" + customizeScreen.selectPersonalityPack("Coach") + + // Verify the Coach button is now selected — checkmark image should appear + let checkmark = app.images.matching( + NSPredicate(format: "label CONTAINS 'checkmark'") + ).firstMatch + + // The Coach pack button should exist and the checkmark should be near it + let coachButton = customizeScreen.personalityPackButton(named: "Coach") + XCTAssertTrue( + coachButton.exists, + "Coach personality pack button should still be visible after selection" + ) + + captureScreenshot(name: "personality_pack_coach_selected") + + // Switch to a different pack to verify we can cycle + customizeScreen.selectPersonalityPack("Zen") + + let zenButton = customizeScreen.personalityPackButton(named: "Zen") + XCTAssertTrue( + zenButton.exists, + "Zen personality pack button should be visible after selection" + ) + + captureScreenshot(name: "personality_pack_zen_selected") + } +} diff --git a/Tests iOS/Screens/CustomizeScreen.swift b/Tests iOS/Screens/CustomizeScreen.swift index 02ee1d0..5c096f3 100644 --- a/Tests iOS/Screens/CustomizeScreen.swift +++ b/Tests iOS/Screens/CustomizeScreen.swift @@ -56,6 +56,16 @@ struct CustomizeScreen { button.tapWhenReady(timeout: 5) } + func personalityPackButton(named name: String) -> XCUIElement { + app.element(UITestID.Customize.personalityPackButton(name)) + } + + func selectPersonalityPack(_ name: String) { + let button = personalityPackButton(named: name) + _ = app.swipeUntilExists(button, direction: .up, maxSwipes: 8) + button.tapWhenReady(timeout: 5) + } + // MARK: - Assertions func assertThemeButtonExists(_ name: String, file: StaticString = #file, line: UInt = #line) { diff --git a/Tests iOS/SpanishLocalizationTests.swift b/Tests iOS/SpanishLocalizationTests.swift new file mode 100644 index 0000000..ed352b7 --- /dev/null +++ b/Tests iOS/SpanishLocalizationTests.swift @@ -0,0 +1,68 @@ +// +// SpanishLocalizationTests.swift +// Tests iOS +// +// TC-137: Spanish localization displays correctly. +// + +import XCTest + +final class SpanishLocalizationTests: BaseUITestCase { + override var seedFixture: String? { "week_of_moods" } + override var bypassSubscription: Bool { true } + + override func setUp() { + // Do NOT call super — we need custom language launch args + continueAfterFailure = false + + let application = XCUIApplication() + let args: [String] = [ + "--ui-testing", "--disable-animations", + "--reset-state", + "--bypass-subscription", + "--skip-onboarding", + "-AppleLanguages", "(es)", + "-AppleLocale", "es_ES" + ] + application.launchArguments = args + application.launchEnvironment = ["UI_TEST_FIXTURE": "week_of_moods"] + application.launch() + app = application + } + + /// TC-137: Key Spanish strings appear when launched in Spanish locale. + func testSpanishLocale_DisplaysSpanishStrings() { + // Day tab should load with data + let tabBar = app.tabBars.firstMatch + XCTAssertTrue(tabBar.waitForExistence(timeout: 5), "Tab bar should exist") + + captureScreenshot(name: "spanish_day_tab") + + // Tap the Settings tab by its Spanish label "Ajustes" + let settingsTabButton = app.tabBars.buttons["Ajustes"] + XCTAssertTrue( + settingsTabButton.waitForExistence(timeout: 5), + "Settings tab should show Spanish label 'Ajustes'" + ) + settingsTabButton.tap() + + // Verify Settings header is visible via accessibility ID + let settingsHeader = app.element(UITestID.Settings.header) + XCTAssertTrue( + settingsHeader.waitForExistence(timeout: 5), + "Settings header should be visible" + ) + + // Verify Spanish text "Ajustes" appears as a static text on screen + let ajustesText = app.staticTexts.matching( + NSPredicate(format: "label == %@", "Ajustes") + ).firstMatch + + XCTAssertTrue( + ajustesText.waitForExistence(timeout: 5), + "Settings should display 'Ajustes' in Spanish locale" + ) + + captureScreenshot(name: "spanish_settings_tab") + } +} diff --git a/docs/Feels_QA_Test_Plan.xlsx b/docs/Feels_QA_Test_Plan.xlsx index ba8fed8..d1b3fff 100644 Binary files a/docs/Feels_QA_Test_Plan.xlsx and b/docs/Feels_QA_Test_Plan.xlsx differ