Refactor ZStack layouts to .background(), add Year View accessibility IDs, triage QA test plan

Replace ZStack-with-gradient patterns with idiomatic .background() modifier
across onboarding, customize, and settings views. Add accessibility identifiers
to Year View charts for UI test automation. Mark 67 impossible-to-automate
tests RED in QA plan and scaffold initial Year View and Settings onboarding tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-20 09:17:52 -06:00
parent ffc74f1a27
commit 5895b387be
22 changed files with 1469 additions and 1378 deletions

View File

@@ -8,9 +8,9 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
06E4767B5977FAC8B644FC92 /* IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CFAE86F485C853DB3239DD9 /* IntegrationTests.swift */; }; 06E4767B5977FAC8B644FC92 /* IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CFAE86F485C853DB3239DD9 /* IntegrationTests.swift */; };
A1B2C3D4E5F607080910ABCD /* DayViewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E5F60708091011ABCDE001 /* DayViewViewModelTests.swift */; }; 1AB245144C89927264D16645 /* InsightsEmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6988985985DE9C29CFDFA96 /* InsightsEmptyStateTests.swift */; };
1C0DAB51279DB0FB003B1F21 /* Feels/Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1C0DAB50279DB0FB003B1F21 /* Feels/Localizable.xcstrings */; }; 1C0DAB51279DB0FB003B1F21 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1C0DAB50279DB0FB003B1F21 /* Localizable.xcstrings */; };
1C0DAB52279DB0FB003B1F22 /* Feels/Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1C0DAB50279DB0FB003B1F21 /* Feels/Localizable.xcstrings */; }; 1C0DAB52279DB0FB003B1F22 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1C0DAB50279DB0FB003B1F21 /* Localizable.xcstrings */; };
1C9566442EF8F5F70032E68F /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = 1C9566432EF8F5F70032E68F /* Algorithms */; }; 1C9566442EF8F5F70032E68F /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = 1C9566432EF8F5F70032E68F /* Algorithms */; };
1CB4D0A028787D8A00902A56 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CB4D09F28787D8A00902A56 /* StoreKit.framework */; }; 1CB4D0A028787D8A00902A56 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CB4D09F28787D8A00902A56 /* StoreKit.framework */; };
1CD90B07278C7DE0001C4FEA /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B06278C7DE0001C4FEA /* Tests_iOS.swift */; }; 1CD90B07278C7DE0001C4FEA /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B06278C7DE0001C4FEA /* Tests_iOS.swift */; };
@@ -25,50 +25,52 @@
1CDE000F2F3BBD26006AE6A1 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 1CA00002300000000000002A /* PostHog */; }; 1CDE000F2F3BBD26006AE6A1 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 1CA00002300000000000002A /* PostHog */; };
1CDEFBBF2F3B8736006AE6A1 /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = 1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */; }; 1CDEFBBF2F3B8736006AE6A1 /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = 1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */; };
1CDEFBC02F3B8736006AE6A1 /* 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 */; };
46F07FA9D330456697C9AC29 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD90B47278C7E7A001C4FEA /* WidgetKit.framework */; }; 46F07FA9D330456697C9AC29 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD90B47278C7E7A001C4FEA /* WidgetKit.framework */; };
4F1C717B7747918A459322CB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4D304CD05CC7C662CCD7DCB /* Foundation.framework */; }; 4F1C717B7747918A459322CB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4D304CD05CC7C662CCD7DCB /* Foundation.framework */; };
54259F7B3F4E959B3F4055E4 /* StreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E2A2FC314F88244CA946BF /* StreakTests.swift */; }; 54259F7B3F4E959B3F4055E4 /* StreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E2A2FC314F88244CA946BF /* StreakTests.swift */; };
9559409B5AEEAB40EBCB6AF9 /* VoteLogicsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD717F91BD65382B7DDFE3C4 /* VoteLogicsTests.swift */; };
EEB21B1CAA8EAEB497BD9FB3 /* DataControllerCRUDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5566271983AEDF1D33C34FE6 /* DataControllerCRUDTests.swift */; };
A018FE95582C04ED0F1806DC /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */; };
E0579E66FFBBF124AC625ACD /* WaitHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */; };
C26D40397E1AA24816FB3751 /* TabBarScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */; };
2EE4D94530F6BF39B26FB4D4 /* DayScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427CD9C91D43AB6A0302B4DD /* DayScreen.swift */; };
A371ED1B0784315F96FFC6BD /* EntryDetailScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */; };
92C1523E0398F866DB4CA027 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA8B21231D67DED575502 /* SettingsScreen.swift */; };
AA11110011111100AAAAAAAA /* AppLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11111111111111AAAAAAAA /* AppLaunchTests.swift */; };
BB22220022222200BBBBBBBB /* MoodLoggingEmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB22222222222222BBBBBBBB /* MoodLoggingEmptyStateTests.swift */; };
CC33330033333300CCCCCCCC /* MoodLoggingWithDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC33333333333333CCCCCCCC /* MoodLoggingWithDataTests.swift */; };
DD44440044444400DDDDDDDD /* EntryDetailTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD44444444444444DDDDDDDD /* EntryDetailTests.swift */; };
EE55550055555500EEEEEEEE /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE55555555555555EEEEEEEE /* SettingsTests.swift */; };
FF66660066666600FFFFFFFF /* SecondaryTabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF66666666666666FFFFFFFF /* SecondaryTabTests.swift */; };
A1B2C3D400000000C9D0E1F2 /* NoteEditorScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */; };
B2C3D4E500000000D0E1F2A3 /* CustomizeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */; };
C3D4E500000000E1F2A3B4C5 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */; };
D4E5F6A700000000F2A3B4C5 /* MoodReplacementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E5F6A7B8C9D0E1F2A3B4C5 /* MoodReplacementTests.swift */; };
E5F6A7B800000000A3B4C5D6 /* EmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F6A7B8C9D0E1F2A3B4C5D6 /* EmptyStateTests.swift */; };
F6A7B8C900000000B4C5D6E7 /* EntryDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A7B8C9D0E1F2A3B4C5D6E7 /* EntryDeleteTests.swift */; };
A7B8C9D000000000C5D6E7F8 /* NotesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B8C9D0E1F2A3B4C5D6E7F8 /* NotesTests.swift */; };
F75470AA2BA1E9EFF8F5265A /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DC4C498A1185DC831F4593 /* LocalizationTests.swift */; };
E3482DB0421C12E11517BDC8 /* TrialBannerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CD463209E0909393545D62 /* TrialBannerTests.swift */; };
A4B459F8CE7F5534DE4FADCA /* DarkModeStylesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8114D2CE12EC5392371BB415 /* DarkModeStylesTests.swift */; };
1AB245144C89927264D16645 /* InsightsEmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6988985985DE9C29CFDFA96 /* InsightsEmptyStateTests.swift */; };
756B9857B0657D2DB2D6D4E2 /* AppResumeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359E1D32D936859E5A0C9F3 /* AppResumeTests.swift */; };
6F9C9C4B50CF8C1769171FF9 /* NoteEditTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469470483072085BE9E04E12 /* NoteEditTests.swift */; }; 6F9C9C4B50CF8C1769171FF9 /* NoteEditTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469470483072085BE9E04E12 /* NoteEditTests.swift */; };
B8C9D0E100000000D6E7F8A9 /* MonthViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C9D0E1F2A3B4C5D6E7F8A9 /* MonthViewTests.swift */; }; 756B9857B0657D2DB2D6D4E2 /* AppResumeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359E1D32D936859E5A0C9F3 /* AppResumeTests.swift */; };
C9D0E1F200000000E7F8A9B0 /* SettingsActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D0E1F2A3B4C5D6E7F8A9B0 /* SettingsActionTests.swift */; }; 92C1523E0398F866DB4CA027 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA8B21231D67DED575502 /* SettingsScreen.swift */; };
D0E1F2A300000000F8A9B0C1 /* CustomizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1F2A3B4C5D6E7F8A9B0C1 /* CustomizationTests.swift */; }; 9559409B5AEEAB40EBCB6AF9 /* VoteLogicsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD717F91BD65382B7DDFE3C4 /* VoteLogicsTests.swift */; };
E1F2A3B400000000A9B0C1D2 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F2A3B4C5D6E7F8A9B0C1D2 /* OnboardingTests.swift */; }; A018FE95582C04ED0F1806DC /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */; };
F2A3B400000000B0C1D2E3F4 /* StabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2A3B4C5D6E7F8A9B0C1D2E3 /* StabilityTests.swift */; }; A1B2C3D400000000C9D0E1F2 /* NoteEditorScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */; };
A1B2C3D4E5F607080910ABCD /* DayViewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E5F60708091011ABCDE001 /* DayViewViewModelTests.swift */; };
A371ED1B0784315F96FFC6BD /* EntryDetailScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */; };
A3B4C5D600000000C1D2E3F4 /* DataPersistenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4C5D6E7F8A9B0C1D2E3F4 /* DataPersistenceTests.swift */; }; A3B4C5D600000000C1D2E3F4 /* DataPersistenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4C5D6E7F8A9B0C1D2E3F4 /* DataPersistenceTests.swift */; };
B4C5D6E700000000D2E3F4A5 /* PaywallGateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C5D6E7F8A9B0C1D2E3F4A5 /* PaywallGateTests.swift */; }; A4B459F8CE7F5534DE4FADCA /* DarkModeStylesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8114D2CE12EC5392371BB415 /* DarkModeStylesTests.swift */; };
C5D6E7F800000000E3F4A5B6 /* AppThemeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D6E7F8A9B0C1D2E3F4A5B6 /* AppThemeTests.swift */; }; A7B8C9D000000000C5D6E7F8 /* NotesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B8C9D0E1F2A3B4C5D6E7F8 /* NotesTests.swift */; };
D6E7F8A900000000F4A5B6C7 /* IconPackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E7F8A9B0C1D2E3F4A5B6C7 /* IconPackTests.swift */; };
E7F8A9B000000000A5B6C7D8 /* PremiumCustomizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7F8A9B0C1D2E3F4A5B6C7D8 /* PremiumCustomizationTests.swift */; };
F8A9B0C100000000B6C7D8E9 /* HeaderMoodLoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A9B0C1D2E3F4A5B6C7D8E9 /* HeaderMoodLoggingTests.swift */; };
A9B0C1D200000000C7D8E9FA /* DayViewGroupingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B0C1D2E3F4A5B6C7D8E9FA /* DayViewGroupingTests.swift */; }; A9B0C1D200000000C7D8E9FA /* DayViewGroupingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B0C1D2E3F4A5B6C7D8E9FA /* DayViewGroupingTests.swift */; };
AA11110011111100AAAAAAAA /* AppLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11111111111111AAAAAAAA /* AppLaunchTests.swift */; };
B0C1D2E300000000D8E9FA0B /* AllDayViewStylesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C1D2E3F4A5B6C7D8E9FA0B /* AllDayViewStylesTests.swift */; }; B0C1D2E300000000D8E9FA0B /* AllDayViewStylesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C1D2E3F4A5B6C7D8E9FA0B /* AllDayViewStylesTests.swift */; };
B2C3D4E500000000D0E1F2A3 /* CustomizeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */; };
B4C5D6E700000000D2E3F4A5 /* PaywallGateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C5D6E7F8A9B0C1D2E3F4A5 /* PaywallGateTests.swift */; };
B8C9D0E100000000D6E7F8A9 /* MonthViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C9D0E1F2A3B4C5D6E7F8A9 /* MonthViewTests.swift */; };
BB22220022222200BBBBBBBB /* MoodLoggingEmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB22222222222222BBBBBBBB /* MoodLoggingEmptyStateTests.swift */; };
C1D2E3F400000000E9FA0B1C /* MonthViewInteractionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D2E3F4A5B6C7D8E9FA0B1C /* MonthViewInteractionTests.swift */; }; C1D2E3F400000000E9FA0B1C /* MonthViewInteractionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D2E3F4A5B6C7D8E9FA0B1C /* MonthViewInteractionTests.swift */; };
C26D40397E1AA24816FB3751 /* TabBarScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */; };
C3D4E500000000E1F2A3B4C5 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */; };
C5D6E7F800000000E3F4A5B6 /* AppThemeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D6E7F8A9B0C1D2E3F4A5B6 /* AppThemeTests.swift */; };
C9D0E1F200000000E7F8A9B0 /* SettingsActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D0E1F2A3B4C5D6E7F8A9B0 /* SettingsActionTests.swift */; };
CC33330033333300CCCCCCCC /* MoodLoggingWithDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC33333333333333CCCCCCCC /* MoodLoggingWithDataTests.swift */; };
D0E1F2A300000000F8A9B0C1 /* CustomizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1F2A3B4C5D6E7F8A9B0C1 /* CustomizationTests.swift */; };
D1AD0A0469EADFB1446E9B09 /* YearViewDisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0246E9F406F872E5DEEB7269 /* YearViewDisplayTests.swift */; };
D4E5F6A700000000F2A3B4C5 /* MoodReplacementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E5F6A7B8C9D0E1F2A3B4C5 /* MoodReplacementTests.swift */; };
D6E7F8A900000000F4A5B6C7 /* IconPackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E7F8A9B0C1D2E3F4A5B6C7 /* IconPackTests.swift */; };
DD44440044444400DDDDDDDD /* EntryDetailTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD44444444444444DDDDDDDD /* EntryDetailTests.swift */; };
E0579E66FFBBF124AC625ACD /* WaitHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */; };
E1F2A3B400000000A9B0C1D2 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F2A3B4C5D6E7F8A9B0C1D2 /* OnboardingTests.swift */; };
E3482DB0421C12E11517BDC8 /* TrialBannerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CD463209E0909393545D62 /* TrialBannerTests.swift */; };
E5F6A7B800000000A3B4C5D6 /* EmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F6A7B8C9D0E1F2A3B4C5D6 /* EmptyStateTests.swift */; };
E7F8A9B000000000A5B6C7D8 /* PremiumCustomizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7F8A9B0C1D2E3F4A5B6C7D8 /* PremiumCustomizationTests.swift */; };
EE55550055555500EEEEEEEE /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE55555555555555EEEEEEEE /* SettingsTests.swift */; };
EEB21B1CAA8EAEB497BD9FB3 /* DataControllerCRUDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5566271983AEDF1D33C34FE6 /* DataControllerCRUDTests.swift */; };
F2A3B400000000B0C1D2E3F4 /* StabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2A3B4C5D6E7F8A9B0C1D2E3 /* StabilityTests.swift */; };
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 */; };
FD30D4508D4C61AB10AC1E71 /* SettingsOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFDAD20AE6C6914EDD87DCBC /* SettingsOnboardingTests.swift */; };
FF66660066666600FFFFFFFF /* SecondaryTabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF66666666666666FFFFFFFF /* SecondaryTabTests.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -117,7 +119,10 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1C0DAB50279DB0FB003B1F21 /* Feels/Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Feels/Localizable.xcstrings; sourceTree = "<group>"; }; 0246E9F406F872E5DEEB7269 /* YearViewDisplayTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = YearViewDisplayTests.swift; sourceTree = "<group>"; };
0359E1D32D936859E5A0C9F3 /* AppResumeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppResumeTests.swift; sourceTree = "<group>"; };
17DC4C498A1185DC831F4593 /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = "<group>"; };
1C0DAB50279DB0FB003B1F21 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Feels/Localizable.xcstrings; sourceTree = "<group>"; };
1CB4D09F28787D8A00902A56 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.5.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; 1CB4D09F28787D8A00902A56 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.5.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
1CD90AF5278C7DE0001C4FEA /* Feels.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Feels.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1CD90AF5278C7DE0001C4FEA /* Feels.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Feels.app; sourceTree = BUILT_PRODUCTS_DIR; };
1CD90AFB278C7DE0001C4FEA /* Feels.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Feels.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1CD90AFB278C7DE0001C4FEA /* Feels.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Feels.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -139,58 +144,57 @@
1CD90B70278C8000001C4FEA /* Feels (iOS)Dev.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "Feels (iOS)Dev.entitlements"; sourceTree = "<group>"; }; 1CD90B70278C8000001C4FEA /* Feels (iOS)Dev.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "Feels (iOS)Dev.entitlements"; sourceTree = "<group>"; };
1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = "<group>"; }; 1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = "<group>"; };
1E594AEAB5F046E3B3ED7C47 /* Feels Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Feels Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 1E594AEAB5F046E3B3ED7C47 /* Feels Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Feels Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
29E2A2FC314F88244CA946BF /* StreakTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StreakTests.swift; sourceTree = "<group>"; };
5566271983AEDF1D33C34FE6 /* DataControllerCRUDTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataControllerCRUDTests.swift; sourceTree = "<group>"; };
9CFAE86F485C853DB3239DD9 /* IntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = IntegrationTests.swift; sourceTree = "<group>"; };
D4E5F60708091011ABCDE001 /* DayViewViewModelTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DayViewViewModelTests.swift; sourceTree = "<group>"; };
B60015D02A064FF582E232FD /* Feels Watch App/Feels Watch AppDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Feels Watch App/Feels Watch AppDebug.entitlements"; sourceTree = "<group>"; };
B8AB4CD73C2B4DC89C6FE84D /* Feels Watch App/Feels Watch App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Feels Watch App/Feels Watch App.entitlements"; sourceTree = "<group>"; };
DA0D74ACDD741CFA1F14F50F /* FeelsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FeelsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DD717F91BD65382B7DDFE3C4 /* VoteLogicsTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = VoteLogicsTests.swift; sourceTree = "<group>"; };
F4D304CD05CC7C662CCD7DCB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = "<group>"; };
5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitHelpers.swift; sourceTree = "<group>"; };
C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarScreen.swift; sourceTree = "<group>"; };
427CD9C91D43AB6A0302B4DD /* DayScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayScreen.swift; sourceTree = "<group>"; };
7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDetailScreen.swift; sourceTree = "<group>"; };
881CA8B21231D67DED575502 /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
AA11111111111111AAAAAAAA /* AppLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLaunchTests.swift; sourceTree = "<group>"; };
BB22222222222222BBBBBBBB /* MoodLoggingEmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodLoggingEmptyStateTests.swift; sourceTree = "<group>"; };
CC33333333333333CCCCCCCC /* MoodLoggingWithDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodLoggingWithDataTests.swift; sourceTree = "<group>"; };
DD44444444444444DDDDDDDD /* EntryDetailTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDetailTests.swift; sourceTree = "<group>"; };
EE55555555555555EEEEEEEE /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; };
FF66666666666666FFFFFFFF /* SecondaryTabTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondaryTabTests.swift; sourceTree = "<group>"; };
A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteEditorScreen.swift; sourceTree = "<group>"; };
B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeScreen.swift; sourceTree = "<group>"; };
C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = "<group>"; };
D4E5F6A7B8C9D0E1F2A3B4C5 /* MoodReplacementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodReplacementTests.swift; sourceTree = "<group>"; };
E5F6A7B8C9D0E1F2A3B4C5D6 /* EmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStateTests.swift; sourceTree = "<group>"; };
F6A7B8C9D0E1F2A3B4C5D6E7 /* EntryDeleteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDeleteTests.swift; sourceTree = "<group>"; };
A7B8C9D0E1F2A3B4C5D6E7F8 /* NotesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesTests.swift; sourceTree = "<group>"; };
17DC4C498A1185DC831F4593 /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = "<group>"; };
21CD463209E0909393545D62 /* TrialBannerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrialBannerTests.swift; sourceTree = "<group>"; }; 21CD463209E0909393545D62 /* TrialBannerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrialBannerTests.swift; sourceTree = "<group>"; };
8114D2CE12EC5392371BB415 /* DarkModeStylesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarkModeStylesTests.swift; sourceTree = "<group>"; }; 29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = "<group>"; };
A6988985985DE9C29CFDFA96 /* InsightsEmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsightsEmptyStateTests.swift; sourceTree = "<group>"; }; 29E2A2FC314F88244CA946BF /* StreakTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StreakTests.swift; sourceTree = "<group>"; };
0359E1D32D936859E5A0C9F3 /* AppResumeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppResumeTests.swift; sourceTree = "<group>"; }; 427CD9C91D43AB6A0302B4DD /* DayScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayScreen.swift; sourceTree = "<group>"; };
469470483072085BE9E04E12 /* NoteEditTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteEditTests.swift; sourceTree = "<group>"; }; 469470483072085BE9E04E12 /* NoteEditTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteEditTests.swift; sourceTree = "<group>"; };
B8C9D0E1F2A3B4C5D6E7F8A9 /* MonthViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthViewTests.swift; sourceTree = "<group>"; }; 5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitHelpers.swift; sourceTree = "<group>"; };
C9D0E1F2A3B4C5D6E7F8A9B0 /* SettingsActionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsActionTests.swift; sourceTree = "<group>"; }; 5566271983AEDF1D33C34FE6 /* DataControllerCRUDTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataControllerCRUDTests.swift; sourceTree = "<group>"; };
D0E1F2A3B4C5D6E7F8A9B0C1 /* CustomizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizationTests.swift; sourceTree = "<group>"; }; 7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDetailScreen.swift; sourceTree = "<group>"; };
E1F2A3B4C5D6E7F8A9B0C1D2 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = "<group>"; }; 8114D2CE12EC5392371BB415 /* DarkModeStylesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarkModeStylesTests.swift; sourceTree = "<group>"; };
F2A3B4C5D6E7F8A9B0C1D2E3 /* StabilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StabilityTests.swift; sourceTree = "<group>"; }; 881CA8B21231D67DED575502 /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
9CFAE86F485C853DB3239DD9 /* IntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = IntegrationTests.swift; sourceTree = "<group>"; };
A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteEditorScreen.swift; sourceTree = "<group>"; };
A3B4C5D6E7F8A9B0C1D2E3F4 /* DataPersistenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataPersistenceTests.swift; sourceTree = "<group>"; }; A3B4C5D6E7F8A9B0C1D2E3F4 /* DataPersistenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataPersistenceTests.swift; sourceTree = "<group>"; };
B4C5D6E7F8A9B0C1D2E3F4A5 /* PaywallGateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallGateTests.swift; sourceTree = "<group>"; }; A6988985985DE9C29CFDFA96 /* InsightsEmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsightsEmptyStateTests.swift; sourceTree = "<group>"; };
C5D6E7F8A9B0C1D2E3F4A5B6 /* AppThemeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppThemeTests.swift; sourceTree = "<group>"; }; A7B8C9D0E1F2A3B4C5D6E7F8 /* NotesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesTests.swift; sourceTree = "<group>"; };
D6E7F8A9B0C1D2E3F4A5B6C7 /* IconPackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconPackTests.swift; sourceTree = "<group>"; };
E7F8A9B0C1D2E3F4A5B6C7D8 /* PremiumCustomizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumCustomizationTests.swift; sourceTree = "<group>"; };
F8A9B0C1D2E3F4A5B6C7D8E9 /* HeaderMoodLoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderMoodLoggingTests.swift; sourceTree = "<group>"; };
A9B0C1D2E3F4A5B6C7D8E9FA /* DayViewGroupingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayViewGroupingTests.swift; sourceTree = "<group>"; }; A9B0C1D2E3F4A5B6C7D8E9FA /* DayViewGroupingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayViewGroupingTests.swift; sourceTree = "<group>"; };
AA11111111111111AAAAAAAA /* AppLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLaunchTests.swift; sourceTree = "<group>"; };
B0C1D2E3F4A5B6C7D8E9FA0B /* AllDayViewStylesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDayViewStylesTests.swift; sourceTree = "<group>"; }; B0C1D2E3F4A5B6C7D8E9FA0B /* AllDayViewStylesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDayViewStylesTests.swift; sourceTree = "<group>"; };
B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeScreen.swift; sourceTree = "<group>"; };
B4C5D6E7F8A9B0C1D2E3F4A5 /* PaywallGateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallGateTests.swift; sourceTree = "<group>"; };
B60015D02A064FF582E232FD /* Feels Watch AppDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Feels Watch App/Feels Watch AppDebug.entitlements"; sourceTree = "<group>"; };
B8AB4CD73C2B4DC89C6FE84D /* Feels Watch App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Feels Watch App/Feels Watch App.entitlements"; sourceTree = "<group>"; };
B8C9D0E1F2A3B4C5D6E7F8A9 /* MonthViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthViewTests.swift; sourceTree = "<group>"; };
BB22222222222222BBBBBBBB /* MoodLoggingEmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodLoggingEmptyStateTests.swift; sourceTree = "<group>"; };
C1D2E3F4A5B6C7D8E9FA0B1C /* MonthViewInteractionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthViewInteractionTests.swift; sourceTree = "<group>"; }; C1D2E3F4A5B6C7D8E9FA0B1C /* MonthViewInteractionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthViewInteractionTests.swift; sourceTree = "<group>"; };
C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = "<group>"; };
C5D6E7F8A9B0C1D2E3F4A5B6 /* AppThemeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppThemeTests.swift; sourceTree = "<group>"; };
C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarScreen.swift; sourceTree = "<group>"; };
C9D0E1F2A3B4C5D6E7F8A9B0 /* SettingsActionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsActionTests.swift; sourceTree = "<group>"; };
CC33333333333333CCCCCCCC /* MoodLoggingWithDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodLoggingWithDataTests.swift; sourceTree = "<group>"; };
D0E1F2A3B4C5D6E7F8A9B0C1 /* CustomizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizationTests.swift; sourceTree = "<group>"; };
D4E5F60708091011ABCDE001 /* DayViewViewModelTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DayViewViewModelTests.swift; sourceTree = "<group>"; };
D4E5F6A7B8C9D0E1F2A3B4C5 /* MoodReplacementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodReplacementTests.swift; sourceTree = "<group>"; };
D6E7F8A9B0C1D2E3F4A5B6C7 /* IconPackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconPackTests.swift; sourceTree = "<group>"; };
DA0D74ACDD741CFA1F14F50F /* FeelsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FeelsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DD44444444444444DDDDDDDD /* EntryDetailTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDetailTests.swift; sourceTree = "<group>"; };
DD717F91BD65382B7DDFE3C4 /* VoteLogicsTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = VoteLogicsTests.swift; sourceTree = "<group>"; };
DFDAD20AE6C6914EDD87DCBC /* SettingsOnboardingTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SettingsOnboardingTests.swift; sourceTree = "<group>"; };
E1F2A3B4C5D6E7F8A9B0C1D2 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = "<group>"; };
E5F6A7B8C9D0E1F2A3B4C5D6 /* EmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStateTests.swift; sourceTree = "<group>"; };
E7F8A9B0C1D2E3F4A5B6C7D8 /* PremiumCustomizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumCustomizationTests.swift; sourceTree = "<group>"; };
EE55555555555555EEEEEEEE /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; };
F2A3B4C5D6E7F8A9B0C1D2E3 /* StabilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StabilityTests.swift; sourceTree = "<group>"; };
F4D304CD05CC7C662CCD7DCB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
F6A7B8C9D0E1F2A3B4C5D6E7 /* EntryDeleteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDeleteTests.swift; sourceTree = "<group>"; };
F8A9B0C1D2E3F4A5B6C7D8E9 /* HeaderMoodLoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderMoodLoggingTests.swift; sourceTree = "<group>"; };
FF66666666666666FFFFFFFF /* SecondaryTabTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondaryTabTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
1C000C162EE93AE3009C9ED5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { 1C000C162EE93AE3009C9ED5 /* Exceptions for "Shared" folder in "FeelsWidgetExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet; isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = ( membershipExceptions = (
AccessibilityIdentifiers.swift, AccessibilityIdentifiers.swift,
@@ -221,7 +225,7 @@
); );
target = 1CD90B44278C7E7A001C4FEA /* FeelsWidgetExtension */; target = 1CD90B44278C7E7A001C4FEA /* FeelsWidgetExtension */;
}; };
2166CE8AA7264FC2B4BFAAAC /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { 2166CE8AA7264FC2B4BFAAAC /* Exceptions for "Shared" folder in "Feels Watch App" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet; isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = ( membershipExceptions = (
Models/Mood.swift, Models/Mood.swift,
@@ -236,9 +240,41 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFileSystemSynchronizedRootGroup section */
1C00073D2EE9388A009C9ED5 /* Shared */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (2166CE8AA7264FC2B4BFAAAC /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 1C000C162EE93AE3009C9ED5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Shared; sourceTree = "<group>"; }; 1C00073D2EE9388A009C9ED5 /* Shared */ = {
1C0009922EE938FC009C9ED5 /* FeelsWidget2 */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = FeelsWidget2; sourceTree = "<group>"; }; isa = PBXFileSystemSynchronizedRootGroup;
579031D619ED4B989145EEB1 /* Feels Watch App */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "Feels Watch App"; sourceTree = "<group>"; }; exceptions = (
2166CE8AA7264FC2B4BFAAAC /* Exceptions for "Shared" folder in "Feels Watch App" target */,
1C000C162EE93AE3009C9ED5 /* Exceptions for "Shared" folder in "FeelsWidgetExtension" target */,
);
explicitFileTypes = {
};
explicitFolders = (
);
path = Shared;
sourceTree = "<group>";
};
1C0009922EE938FC009C9ED5 /* FeelsWidget2 */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = FeelsWidget2;
sourceTree = "<group>";
};
579031D619ED4B989145EEB1 /* Feels Watch App */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = "Feels Watch App";
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */ /* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -306,9 +342,9 @@
1CD90AE5278C7DDF001C4FEA = { 1CD90AE5278C7DDF001C4FEA = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B8AB4CD73C2B4DC89C6FE84D /* Feels Watch App/Feels Watch App.entitlements */, B8AB4CD73C2B4DC89C6FE84D /* Feels Watch App.entitlements */,
B60015D02A064FF582E232FD /* Feels Watch App/Feels Watch AppDebug.entitlements */, B60015D02A064FF582E232FD /* Feels Watch AppDebug.entitlements */,
1C0DAB50279DB0FB003B1F21 /* Feels/Localizable.xcstrings */, 1C0DAB50279DB0FB003B1F21 /* Localizable.xcstrings */,
1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */, 1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */,
1CD90B6A278C7F75001C4FEA /* Feels (iOS).entitlements */, 1CD90B6A278C7F75001C4FEA /* Feels (iOS).entitlements */,
1CD90B70278C8000001C4FEA /* Feels (iOS)Dev.entitlements */, 1CD90B70278C8000001C4FEA /* Feels (iOS)Dev.entitlements */,
@@ -386,33 +422,12 @@
A9B0C1D2E3F4A5B6C7D8E9FA /* DayViewGroupingTests.swift */, A9B0C1D2E3F4A5B6C7D8E9FA /* DayViewGroupingTests.swift */,
B0C1D2E3F4A5B6C7D8E9FA0B /* AllDayViewStylesTests.swift */, B0C1D2E3F4A5B6C7D8E9FA0B /* AllDayViewStylesTests.swift */,
C1D2E3F4A5B6C7D8E9FA0B1C /* MonthViewInteractionTests.swift */, C1D2E3F4A5B6C7D8E9FA0B1C /* MonthViewInteractionTests.swift */,
0246E9F406F872E5DEEB7269 /* YearViewDisplayTests.swift */,
DFDAD20AE6C6914EDD87DCBC /* SettingsOnboardingTests.swift */,
); );
path = "Tests iOS"; path = "Tests iOS";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
3A62ED77167DA212DE1CCB7D /* Helpers */ = {
isa = PBXGroup;
children = (
29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */,
5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
B697A2092711045D69109EA1 /* Screens */ = {
isa = PBXGroup;
children = (
C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */,
427CD9C91D43AB6A0302B4DD /* DayScreen.swift */,
7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */,
881CA8B21231D67DED575502 /* SettingsScreen.swift */,
A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */,
B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */,
C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */,
);
path = Screens;
sourceTree = "<group>";
};
1CD90B11278C7DE0001C4FEA /* Tests macOS */ = { 1CD90B11278C7DE0001C4FEA /* Tests macOS */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -446,6 +461,15 @@
path = FeelsTests; path = FeelsTests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
3A62ED77167DA212DE1CCB7D /* Helpers */ = {
isa = PBXGroup;
children = (
29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */,
5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
88F4C25CA0D11FB136B0B8A6 /* iOS */ = { 88F4C25CA0D11FB136B0B8A6 /* iOS */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -454,6 +478,20 @@
name = iOS; name = iOS;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B697A2092711045D69109EA1 /* Screens */ = {
isa = PBXGroup;
children = (
C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */,
427CD9C91D43AB6A0302B4DD /* DayScreen.swift */,
7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */,
881CA8B21231D67DED575502 /* SettingsScreen.swift */,
A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */,
B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */,
C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */,
);
path = Screens;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@@ -676,7 +714,7 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
1C0DAB51279DB0FB003B1F21 /* Feels/Localizable.xcstrings in Resources */, 1C0DAB51279DB0FB003B1F21 /* Localizable.xcstrings in Resources */,
1CDEFBBF2F3B8736006AE6A1 /* Configuration.storekit in Resources */, 1CDEFBBF2F3B8736006AE6A1 /* Configuration.storekit in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -707,7 +745,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
1CDEFBC02F3B8736006AE6A1 /* Configuration.storekit in Resources */, 1CDEFBC02F3B8736006AE6A1 /* Configuration.storekit in Resources */,
1C0DAB52279DB0FB003B1F22 /* Feels/Localizable.xcstrings in Resources */, 1C0DAB52279DB0FB003B1F22 /* Localizable.xcstrings in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -787,6 +825,8 @@
A9B0C1D200000000C7D8E9FA /* DayViewGroupingTests.swift in Sources */, A9B0C1D200000000C7D8E9FA /* DayViewGroupingTests.swift in Sources */,
B0C1D2E300000000D8E9FA0B /* AllDayViewStylesTests.swift in Sources */, B0C1D2E300000000D8E9FA0B /* AllDayViewStylesTests.swift in Sources */,
C1D2E3F400000000E9FA0B1C /* MonthViewInteractionTests.swift in Sources */, C1D2E3F400000000E9FA0B1C /* MonthViewInteractionTests.swift in Sources */,
D1AD0A0469EADFB1446E9B09 /* YearViewDisplayTests.swift in Sources */,
FD30D4508D4C61AB10AC1E71 /* SettingsOnboardingTests.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@@ -125,6 +125,11 @@ enum AccessibilityID {
// MARK: - Year View // MARK: - Year View
enum YearView { enum YearView {
static let heatmap = "year_heatmap" static let heatmap = "year_heatmap"
static let donutChart = "year_donut_chart"
static let barChart = "year_bar_chart"
static let statsSection = "year_stats_section"
static func cardHeader(year: Int) -> String { "year_card_header_\(year)" }
static let shareButton = "year_share_button"
} }
// MARK: - Onboarding // MARK: - Onboarding

View File

@@ -25,16 +25,7 @@ struct OnboardingDay: View {
@ObservedObject var onboardingData: OnboardingData @ObservedObject var onboardingData: OnboardingData
var body: some View { var body: some View {
ZStack { VStack(spacing: 0) {
// Gradient background
LinearGradient(
colors: [Color(hex: "4facfe"), Color(hex: "00f2fe")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
VStack(spacing: 0) {
Spacer() Spacer()
// Icon // Icon
@@ -103,8 +94,15 @@ struct OnboardingDay: View {
} }
.padding(.horizontal, 30) .padding(.horizontal, 30)
.padding(.bottom, 80) .padding(.bottom, 80)
}
} }
.background(
LinearGradient(
colors: [Color(hex: "4facfe"), Color(hex: "00f2fe")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
)
.accessibilityIdentifier(AccessibilityID.Onboarding.dayScreen) .accessibilityIdentifier(AccessibilityID.Onboarding.dayScreen)
} }
} }

View File

@@ -12,17 +12,7 @@ struct OnboardingStyle: View {
@State private var selectedTheme: AppTheme = .celestial @State private var selectedTheme: AppTheme = .celestial
var body: some View { var body: some View {
ZStack { ScrollView(showsIndicators: false) {
// Gradient background that updates with theme
LinearGradient(
colors: selectedTheme.previewColors,
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
.animation(.easeInOut(duration: 0.4), value: selectedTheme)
ScrollView(showsIndicators: false) {
VStack(spacing: 0) { VStack(spacing: 0) {
// Icon // Icon
ZStack { ZStack {
@@ -85,8 +75,16 @@ struct OnboardingStyle: View {
.padding(.top, 24) .padding(.top, 24)
.padding(.bottom, 80) .padding(.bottom, 80)
} }
}
} }
.background(
LinearGradient(
colors: selectedTheme.previewColors,
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
.animation(.easeInOut(duration: 0.4), value: selectedTheme)
)
.onAppear { .onAppear {
// Apply default theme on appear // Apply default theme on appear
selectedTheme.apply() selectedTheme.apply()

View File

@@ -15,16 +15,7 @@ struct OnboardingSubscription: View {
let completionClosure: ((OnboardingData) -> Void) let completionClosure: ((OnboardingData) -> Void)
var body: some View { var body: some View {
ZStack { VStack(spacing: 0) {
// Gradient background
LinearGradient(
colors: [Color(hex: "11998e"), Color(hex: "38ef7d")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
VStack(spacing: 0) {
Spacer() Spacer()
// Crown icon // Crown icon
@@ -137,8 +128,15 @@ struct OnboardingSubscription: View {
} }
.padding(.horizontal, 24) .padding(.horizontal, 24)
.padding(.bottom, 50) .padding(.bottom, 50)
}
} }
.background(
LinearGradient(
colors: [Color(hex: "11998e"), Color(hex: "38ef7d")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
)
.accessibilityIdentifier(AccessibilityID.Onboarding.subscriptionScreen) .accessibilityIdentifier(AccessibilityID.Onboarding.subscriptionScreen)
.sheet(isPresented: $showSubscriptionStore, onDismiss: { .sheet(isPresented: $showSubscriptionStore, onDismiss: {
// After subscription store closes, complete onboarding // After subscription store closes, complete onboarding

View File

@@ -16,44 +16,35 @@ struct OnboardingTitle: View {
@ObservedObject var onboardingData: OnboardingData @ObservedObject var onboardingData: OnboardingData
var body: some View { var body: some View {
ZStack { ScrollView {
VStack{
Image("average", bundle: .main) Text(String(localized: "onboarding_title_title"))
.foregroundColor(Color(UIColor.darkText)) .font(.title)
.opacity(0.04) .foregroundColor(Color(UIColor.white))
.scaleEffect(1.2) .padding([.trailing, .leading], 55)
.padding(.bottom, 55) .padding([.top], 25)
.accessibilityHidden(true)
ForEach(OnboardingTitle.titleOptions, id: \.self) { option in
ScrollView { Button(action: {
VStack{
Text(String(localized: "onboarding_title_title"))
.font(.title)
.foregroundColor(Color(UIColor.white))
.padding([.trailing, .leading], 55)
.padding([.top], 25)
ForEach(OnboardingTitle.titleOptions, id: \.self) { option in
Button(action: {
// onboardingData.title = option // onboardingData.title = option
}, label: { }, label: {
Text(option) Text(option)
.font(.subheadline.weight(.bold)) .font(.subheadline.weight(.bold))
.foregroundColor(.white) .foregroundColor(.white)
.padding(10) .padding(10)
.background(RoundedRectangle(cornerRadius: 10).stroke().foregroundColor(Color.white)) .background(RoundedRectangle(cornerRadius: 10).stroke().foregroundColor(Color.white))
.cornerRadius(10) .cornerRadius(10)
}) })
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
.padding([.top], 10) .padding([.top], 10)
} }
Text(String(localized: "onboarding_title_type_your_own")) Text(String(localized: "onboarding_title_type_your_own"))
.font(.body) .font(.body)
.foregroundColor(Color(UIColor.white)) .foregroundColor(Color(UIColor.white))
.padding([.top], 25) .padding([.top], 25)
.padding([.trailing, .leading], 55) .padding([.trailing, .leading], 55)
// TextField("Notification", text: $onboardingData.title) // TextField("Notification", text: $onboardingData.title)
// .frame(height: 44) // .frame(height: 44)
// .foregroundColor(Color(UIColor.white)) // .foregroundColor(Color(UIColor.white))
@@ -63,12 +54,21 @@ struct OnboardingTitle: View {
// .overlay(RoundedRectangle(cornerRadius: 16).stroke(Color.white)) // .overlay(RoundedRectangle(cornerRadius: 16).stroke(Color.white))
// .padding([.leading, .trailing], 75) // .padding([.leading, .trailing], 75)
// .padding([.top], 45) // .padding([.top], 45)
Spacer() Spacer()
} }
}
.background {
ZStack {
Color.orange
Image("average", bundle: .main)
.foregroundColor(Color(UIColor.darkText))
.opacity(0.04)
.scaleEffect(1.2)
.padding(.bottom, 55)
.accessibilityHidden(true)
} }
} }
.background(.orange)
} }
} }

View File

@@ -19,8 +19,56 @@ struct OnboardingWrapup: View {
} }
var body: some View { var body: some View {
ZStack { GeometryReader { geometry in
GeometryReader { geometry in VStack {
ScrollView {
Spacer()
Text(String(localized: "onboarding_wrap_up_1"))
.padding(.top)
.padding()
.font(.title)
.foregroundColor(Color(UIColor.white))
Text(formatter.string(from: onboardingData.date))
.font(.title)
.fontWeight(.bold)
.padding()
.foregroundColor(Color(UIColor.white))
Text(String(localized: "onboarding_wrap_up_3"))
.font(.title)
.padding()
.foregroundColor(Color(UIColor.white))
Text(onboardingData.inputDay.localizedValue)
.font(.title)
.fontWeight(.bold)
.padding()
.foregroundColor(Color(UIColor.white))
Button(action: {
AnalyticsManager.shared.track(.onboardingCompleted(dayId: String(onboardingData.inputDay.rawValue)))
completionClosure(onboardingData)
}, label: {
Text(String(localized: "onboarding_wrap_up_complete_button"))
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color(hex: "31d158"))
.padding()
.background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(Color.white))
.cornerRadius(10)
})
.padding([.top], 65)
}
.multilineTextAlignment(.center)
}
.frame(maxWidth: geometry.size.width)
}
.background {
ZStack {
Color(hex: "31d158")
VStack { VStack {
Spacer() Spacer()
Image("great", bundle: .main) Image("great", bundle: .main)
@@ -30,55 +78,8 @@ struct OnboardingWrapup: View {
.accessibilityHidden(true) .accessibilityHidden(true)
Spacer() Spacer()
} }
VStack {
ScrollView {
Spacer()
Text(String(localized: "onboarding_wrap_up_1"))
.padding(.top)
.padding()
.font(.title)
.foregroundColor(Color(UIColor.white))
Text(formatter.string(from: onboardingData.date))
.font(.title)
.fontWeight(.bold)
.padding()
.foregroundColor(Color(UIColor.white))
Text(String(localized: "onboarding_wrap_up_3"))
.font(.title)
.padding()
.foregroundColor(Color(UIColor.white))
Text(onboardingData.inputDay.localizedValue)
.font(.title)
.fontWeight(.bold)
.padding()
.foregroundColor(Color(UIColor.white))
Button(action: {
AnalyticsManager.shared.track(.onboardingCompleted(dayId: String(onboardingData.inputDay.rawValue)))
completionClosure(onboardingData)
}, label: {
Text(String(localized: "onboarding_wrap_up_complete_button"))
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color(hex: "31d158"))
.padding()
.background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(Color.white))
.cornerRadius(10)
})
.padding([.top], 65)
}
.multilineTextAlignment(.center)
}
.frame(maxWidth: geometry.size.width)
} }
} }
.background(Color(hex: "31d158"))
} }
} }

View File

@@ -41,8 +41,6 @@ struct AddMoodHeaderView: View {
Text(String(imagePack.rawValue)) Text(String(imagePack.rawValue))
.hidden() .hidden()
theme.currentTheme.secondaryBGColor
VStack(spacing: 16) { VStack(spacing: 16) {
Text(ShowBasedOnVoteLogics.getVotingTitle(onboardingData: onboardingData)) Text(ShowBasedOnVoteLogics.getVotingTitle(onboardingData: onboardingData))
.font(.title2.bold()) .font(.title2.bold())
@@ -66,6 +64,7 @@ struct AddMoodHeaderView: View {
} }
} }
} }
.background(theme.currentTheme.secondaryBGColor)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.accessibilityIdentifier(AccessibilityID.DayView.moodHeader) .accessibilityIdentifier(AccessibilityID.DayView.moodHeader)

View File

@@ -24,17 +24,17 @@ struct CustomizeContentView: View {
Button(action: { showThemePicker = true }) { Button(action: { showThemePicker = true }) {
HStack(spacing: 16) { HStack(spacing: 16) {
// Emoji preview // Emoji preview
ZStack { Text("🎨")
LinearGradient( .font(.title)
colors: [.purple.opacity(0.8), .blue.opacity(0.8), .cyan.opacity(0.8)], .frame(width: 56, height: 56)
startPoint: .topLeading, .background(
endPoint: .bottomTrailing LinearGradient(
colors: [.purple.opacity(0.8), .blue.opacity(0.8), .cyan.opacity(0.8)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
) )
Text("🎨") .clipShape(RoundedRectangle(cornerRadius: 12))
.font(.title)
}
.frame(width: 56, height: 56)
.clipShape(RoundedRectangle(cornerRadius: 12))
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text("Browse Themes") Text("Browse Themes")

View File

@@ -259,26 +259,25 @@ struct AppThemePreviewSheet: View {
} }
private var heroSection: some View { private var heroSection: some View {
ZStack { VStack(spacing: 16) {
// Gradient background Text(theme.emoji)
.font(.system(size: 72))
.shadow(color: .black.opacity(0.3), radius: 8, x: 0, y: 4)
Text(theme.tagline)
.font(.title3.weight(.medium))
.foregroundColor(.white)
.shadow(color: .black.opacity(0.3), radius: 4, x: 0, y: 2)
}
.frame(maxWidth: .infinity)
.frame(height: 200)
.background(
LinearGradient( LinearGradient(
colors: theme.previewColors + [theme.previewColors[0].opacity(0.5)], colors: theme.previewColors + [theme.previewColors[0].opacity(0.5)],
startPoint: .topLeading, startPoint: .topLeading,
endPoint: .bottomTrailing endPoint: .bottomTrailing
) )
)
VStack(spacing: 16) {
Text(theme.emoji)
.font(.system(size: 72))
.shadow(color: .black.opacity(0.3), radius: 8, x: 0, y: 4)
Text(theme.tagline)
.font(.title3.weight(.medium))
.foregroundColor(.white)
.shadow(color: .black.opacity(0.3), radius: 4, x: 0, y: 2)
}
}
.frame(height: 200)
.clipShape(RoundedRectangle(cornerRadius: 20)) .clipShape(RoundedRectangle(cornerRadius: 20))
.padding(.horizontal, 20) .padding(.horizontal, 20)
.padding(.top, 16) .padding(.top, 16)

View File

@@ -22,40 +22,37 @@ struct DayFilterPickerView: View {
(Calendar.current.shortWeekdaySymbols[6], 7)] (Calendar.current.shortWeekdaySymbols[6], 7)]
var body: some View { var body: some View {
ZStack { VStack {
theme.currentTheme.secondaryBGColor HStack {
ForEach(weekdays.indices, id: \.self) { dayIdx in
VStack { let day = String(weekdays[dayIdx].0)
HStack { let value = weekdays[dayIdx].1
ForEach(weekdays.indices, id: \.self) { dayIdx in let isSelected = filteredDays.currentFilters.contains(value)
let day = String(weekdays[dayIdx].0) Button(action: {
let value = weekdays[dayIdx].1 if isSelected {
let isSelected = filteredDays.currentFilters.contains(value) filteredDays.removeFilter(filter: value)
Button(action: { } else {
if isSelected { filteredDays.addFilter(newFilter: value)
filteredDays.removeFilter(filter: value)
} else {
filteredDays.addFilter(newFilter: value)
}
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
}) {
Text(day.capitalized)
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(Color(uiColor: .tertiarySystemBackground))
.foregroundColor(isSelected ? .green : .red)
.cornerRadius(8)
} }
.buttonStyle(.plain) let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
}) {
Text(day.capitalized)
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(Color(uiColor: .tertiarySystemBackground))
.foregroundColor(isSelected ? .green : .red)
.cornerRadius(8)
} }
.buttonStyle(.plain)
} }
Text(String(localized: "day_picker_view_text"))
.padding(.top)
.foregroundColor(textColor)
} }
.padding() Text(String(localized: "day_picker_view_text"))
.padding(.top)
.foregroundColor(textColor)
} }
.padding()
.background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
} }

View File

@@ -50,47 +50,45 @@ struct IconPickerView: View {
] ]
var body: some View { var body: some View {
ZStack { VStack {
theme.currentTheme.secondaryBGColor ScrollView(.horizontal) {
VStack { HStack {
ScrollView(.horizontal) { Button(action: {
HStack { UIApplication.shared.setAlternateIconName(nil)
AnalyticsManager.shared.track(.appIconChanged(iconTitle: "default"))
}, label: {
Image("AppIconImage", bundle: .main)
.resizable()
.frame(width: 50, height:50)
.cornerRadius(10)
})
.accessibilityLabel(String(localized: "Default app icon"))
.accessibilityHint(String(localized: "Double tap to select"))
ForEach(iconSets, id: \.self.0){ iconSet in
Button(action: { Button(action: {
UIApplication.shared.setAlternateIconName(nil) UIApplication.shared.setAlternateIconName(iconSet.1) { (error) in
AnalyticsManager.shared.track(.appIconChanged(iconTitle: "default")) // FIXME: Handle error
}
AnalyticsManager.shared.track(.appIconChanged(iconTitle: iconSet.1))
}, label: { }, label: {
Image("AppIconImage", bundle: .main) Image(iconSet.0, bundle: .main)
.resizable() .resizable()
.frame(width: 50, height:50) .frame(width: 50, height:50)
.cornerRadius(10) .cornerRadius(10)
}) })
.accessibilityLabel(String(localized: "Default app icon")) .accessibilityLabel(String(localized: "App icon style \(iconSet.1.replacingOccurrences(of: "AppIcon", with: "").replacingOccurrences(of: "Image", with: ""))"))
.accessibilityHint(String(localized: "Double tap to select")) .accessibilityHint(String(localized: "Double tap to select"))
ForEach(iconSets, id: \.self.0){ iconSet in
Button(action: {
UIApplication.shared.setAlternateIconName(iconSet.1) { (error) in
// FIXME: Handle error
}
AnalyticsManager.shared.track(.appIconChanged(iconTitle: iconSet.1))
}, label: {
Image(iconSet.0, bundle: .main)
.resizable()
.frame(width: 50, height:50)
.cornerRadius(10)
})
.accessibilityLabel(String(localized: "App icon style \(iconSet.1.replacingOccurrences(of: "AppIcon", with: "").replacingOccurrences(of: "Image", with: ""))"))
.accessibilityHint(String(localized: "Double tap to select"))
}
} }
.padding()
} }
.background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(theme.currentTheme.bgColor))
.padding() .padding()
.cornerRadius(10)
} }
.background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(theme.currentTheme.bgColor))
.padding()
.cornerRadius(10)
} }
.background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
} }

View File

@@ -16,58 +16,56 @@ struct PersonalityPackPickerView: View {
private var textColor: Color { theme.currentTheme.labelColor } private var textColor: Color { theme.currentTheme.labelColor }
var body: some View { var body: some View {
ZStack { VStack {
theme.currentTheme.secondaryBGColor ForEach(PersonalityPack.allCases, id: \.self) { aPack in
VStack { VStack(spacing: 10) {
ForEach(PersonalityPack.allCases, id: \.self) { aPack in Text(String(aPack.title()))
VStack(spacing: 10) { .font(.body)
Text(String(aPack.title())) .foregroundColor(textColor)
.font(.body)
.foregroundColor(textColor) Text(aPack.randomPushNotificationStrings().title)
.font(.body)
Text(aPack.randomPushNotificationStrings().title) .foregroundColor(Color(UIColor.systemGray))
.font(.body) Text(aPack.randomPushNotificationStrings().body)
.foregroundColor(Color(UIColor.systemGray)) .font(.body)
Text(aPack.randomPushNotificationStrings().body) .foregroundColor(Color(UIColor.systemGray))
.font(.body) }
.foregroundColor(Color(UIColor.systemGray)) .frame(minWidth: 0, maxWidth: .infinity)
} .padding()
.frame(minWidth: 0, maxWidth: .infinity) .contentShape(Rectangle())
.padding() .background(
.contentShape(Rectangle()) RoundedRectangle(cornerRadius: 10, style: .continuous)
.background( .fill(personalityPack == aPack ? theme.currentTheme.bgColor : .clear)
RoundedRectangle(cornerRadius: 10, style: .continuous) .padding(5)
.fill(personalityPack == aPack ? theme.currentTheme.bgColor : .clear) )
.padding(5) .onTapGesture {
) let impactMed = UIImpactFeedbackGenerator(style: .heavy)
.onTapGesture { impactMed.impactOccurred()
let impactMed = UIImpactFeedbackGenerator(style: .heavy) personalityPack = aPack
impactMed.impactOccurred() AnalyticsManager.shared.track(.personalityPackChanged(packTitle: aPack.title()))
personalityPack = aPack LocalNotification.rescheduleNotifiations()
AnalyticsManager.shared.track(.personalityPackChanged(packTitle: aPack.title())) // }
LocalNotification.rescheduleNotifiations() }
// } // .blur(radius: aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW ? 5 : 0)
} .alert(isPresented: $showOver18Alert) {
// .blur(radius: aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW ? 5 : 0) let primaryButton = Alert.Button.default(Text(String(localized: "customize_view_over18alert_ok"))) {
.alert(isPresented: $showOver18Alert) { showNSFW = true
let primaryButton = Alert.Button.default(Text(String(localized: "customize_view_over18alert_ok"))) { }
showNSFW = true let secondaryButton = Alert.Button.cancel(Text(String(localized: "customize_view_over18alert_no"))) {
} showNSFW = false
let secondaryButton = Alert.Button.cancel(Text(String(localized: "customize_view_over18alert_no"))) { }
showNSFW = false return Alert(title: Text(String(localized: "customize_view_over18alert_title")),
} message: Text(String(localized: "customize_view_over18alert_body")),
return Alert(title: Text(String(localized: "customize_view_over18alert_title")), primaryButton: primaryButton,
message: Text(String(localized: "customize_view_over18alert_body")), secondaryButton: secondaryButton)
primaryButton: primaryButton, }
secondaryButton: secondaryButton) if aPack.rawValue != (PersonalityPack.allCases.sorted(by: { $0.rawValue > $1.rawValue }).first?.rawValue) ?? 0 {
} Divider()
if aPack.rawValue != (PersonalityPack.allCases.sorted(by: { $0.rawValue > $1.rawValue }).first?.rawValue) ?? 0 {
Divider()
}
} }
} }
} }
.background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
} }

View File

@@ -15,19 +15,17 @@ struct ThemePickerView: View {
private var textColor: Color { selectedTheme.currentTheme.labelColor } private var textColor: Color { selectedTheme.currentTheme.labelColor }
var body: some View { var body: some View {
ZStack { VStack {
selectedTheme.currentTheme.secondaryBGColor HStack(spacing: 0) {
VStack { themeButton(for: .system)
HStack(spacing: 0) { themeButton(for: .iFeel)
themeButton(for: .system) themeButton(for: .dark)
themeButton(for: .iFeel) themeButton(for: .light)
themeButton(for: .dark)
themeButton(for: .light)
}
.padding(.top)
} }
.padding() .padding(.top)
} }
.padding()
.background(selectedTheme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
.onAppear { .onAppear {

View File

@@ -18,57 +18,54 @@ struct VotingLayoutPickerView: View {
} }
var body: some View { var body: some View {
ZStack { VStack(alignment: .leading, spacing: 12) {
theme.currentTheme.secondaryBGColor Text("Voting Layout")
.font(.headline)
.foregroundColor(textColor)
.padding(.horizontal)
.padding(.top)
VStack(alignment: .leading, spacing: 12) { ScrollView(.horizontal, showsIndicators: false) {
Text("Voting Layout") HStack(spacing: 8) {
.font(.headline) ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in
.foregroundColor(textColor) Button(action: {
.padding(.horizontal) if UIAccessibility.isReduceMotionEnabled {
.padding(.top) votingLayoutStyle = layout.rawValue
} else {
ScrollView(.horizontal, showsIndicators: false) { withAnimation(.easeInOut(duration: 0.2)) {
HStack(spacing: 8) {
ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in
Button(action: {
if UIAccessibility.isReduceMotionEnabled {
votingLayoutStyle = layout.rawValue votingLayoutStyle = layout.rawValue
} else {
withAnimation(.easeInOut(duration: 0.2)) {
votingLayoutStyle = layout.rawValue
}
} }
AnalyticsManager.shared.track(.votingLayoutChanged(layout: layout.displayName))
}) {
VStack(spacing: 6) {
layoutIcon(for: layout)
.frame(width: 44, height: 44)
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.6))
Text(layout.displayName)
.font(.caption)
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.8))
}
.frame(width: 70)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(currentLayout == layout ? Color.accentColor.opacity(0.15) : Color.clear)
)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(currentLayout == layout ? Color.accentColor : Color.clear, lineWidth: 2)
)
} }
.buttonStyle(.plain) AnalyticsManager.shared.track(.votingLayoutChanged(layout: layout.displayName))
}) {
VStack(spacing: 6) {
layoutIcon(for: layout)
.frame(width: 44, height: 44)
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.6))
Text(layout.displayName)
.font(.caption)
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.8))
}
.frame(width: 70)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(currentLayout == layout ? Color.accentColor.opacity(0.15) : Color.clear)
)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(currentLayout == layout ? Color.accentColor : Color.clear, lineWidth: 2)
)
} }
.buttonStyle(.plain)
} }
.padding(.horizontal)
} }
.padding(.bottom) .padding(.horizontal)
} }
.padding(.bottom)
} }
.background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
} }

File diff suppressed because it is too large Load Diff

View File

@@ -522,6 +522,7 @@ struct YearCard: View, Equatable {
} }
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.YearView.cardHeader(year: year))
Spacer() Spacer()
@@ -533,6 +534,7 @@ struct YearCard: View, Equatable {
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.YearView.shareButton)
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.vertical, 12) .padding(.vertical, 12)
@@ -543,6 +545,7 @@ struct YearCard: View, Equatable {
// Donut Chart // Donut Chart
MoodDonutChart(metrics: animatedMetrics, moodTint: moodTint) MoodDonutChart(metrics: animatedMetrics, moodTint: moodTint)
.frame(width: 100, height: 100) .frame(width: 100, height: 100)
.accessibilityIdentifier(AccessibilityID.YearView.donutChart)
// Bar Chart // Bar Chart
VStack(spacing: 6) { VStack(spacing: 6) {
@@ -574,10 +577,12 @@ struct YearCard: View, Equatable {
} }
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.accessibilityIdentifier(AccessibilityID.YearView.barChart)
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.bottom, 12) .padding(.bottom, 12)
.transition(.opacity.combined(with: .move(edge: .top))) .transition(.opacity.combined(with: .move(edge: .top)))
.accessibilityIdentifier(AccessibilityID.YearView.statsSection)
} }
Divider() Divider()

View File

@@ -82,6 +82,14 @@ enum UITestID {
static let header = "insights_header" static let header = "insights_header"
} }
enum Year {
static let donutChart = "year_donut_chart"
static let barChart = "year_bar_chart"
static let statsSection = "year_stats_section"
static func cardHeader(year: Int) -> String { "year_card_header_\(year)" }
static let shareButton = "year_share_button"
}
enum Month { enum Month {
static let grid = "month_grid" static let grid = "month_grid"
} }

View File

@@ -0,0 +1,48 @@
//
// SettingsOnboardingTests.swift
// Tests iOS
//
// TC-124: Show onboarding from Settings.
//
import XCTest
final class SettingsOnboardingTests: BaseUITestCase {
override var seedFixture: String? { "empty" }
override var bypassSubscription: Bool { true }
/// TC-124: Tapping Show Onboarding in Settings replays the onboarding flow.
func testSettings_ShowOnboarding_OpensOnboardingFlow() {
let tabBar = TabBarScreen(app: app)
let settingsScreen = tabBar.tapSettings()
settingsScreen.assertVisible()
// Switch to the Settings sub-tab (not Customize)
settingsScreen.tapSettingsTab()
captureScreenshot(name: "settings_before_show_onboarding")
// Scroll to and tap "Show Onboarding" button
let showOnboardingBtn = app.element("settings_show_onboarding")
guard showOnboardingBtn.waitForExistence(timeout: 2) ||
app.swipeUntilExists(showOnboardingBtn, direction: .up, maxSwipes: 8) else {
captureScreenshot(name: "settings_show_onboarding_not_found")
XCTFail("Show Onboarding button not found in Settings")
return
}
showOnboardingBtn.tapWhenReady()
// The sheet may take a moment to animate in.
// Look for the onboarding welcome screen or any text unique to onboarding.
let welcomeScreen = app.element(UITestID.Onboarding.welcome)
let welcomeText = app.staticTexts["Welcome to Feels"]
let found = welcomeScreen.waitForExistence(timeout: 8) ||
welcomeText.waitForExistence(timeout: 3)
captureScreenshot(name: "settings_show_onboarding_result")
XCTAssertTrue(found,
"Onboarding should appear after tapping Show Onboarding"
)
}
}

View File

@@ -0,0 +1,51 @@
//
// YearViewDisplayTests.swift
// Tests iOS
//
// Year View display tests: donut chart, bar chart.
// TC-035, TC-036
//
import XCTest
final class YearViewDisplayTests: BaseUITestCase {
override var seedFixture: String? { "week_of_moods" }
override var bypassSubscription: Bool { true }
/// TC-035: Year View shows donut chart with mood distribution.
func testYearView_DonutChartVisible() {
let tabBar = TabBarScreen(app: app)
tabBar.tapYear()
// Wait for Year tab to be selected and content to load
XCTAssertTrue(tabBar.yearTab.isSelected, "Year tab should be selected")
captureScreenshot(name: "year_view_loaded")
// The donut chart is inside the stats section of the first YearCard.
// Try finding by accessibility identifier first, then fall back to presence of "days" text.
let donutChart = app.element(UITestID.Year.donutChart)
let found = donutChart.waitForExistence(timeout: 8) ||
app.swipeUntilExists(donutChart, direction: .up, maxSwipes: 3, timeoutPerTry: 1.0)
captureScreenshot(name: "year_donut_chart")
XCTAssertTrue(found, "Donut chart should be visible in Year View")
}
/// TC-036: Year View shows bar chart with mood percentages.
func testYearView_BarChartVisible() {
let tabBar = TabBarScreen(app: app)
tabBar.tapYear()
XCTAssertTrue(tabBar.yearTab.isSelected, "Year tab should be selected")
let barChart = app.element(UITestID.Year.barChart)
let found = barChart.waitForExistence(timeout: 8) ||
app.swipeUntilExists(barChart, direction: .up, maxSwipes: 3, timeoutPerTry: 1.0)
captureScreenshot(name: "year_bar_chart")
XCTAssertTrue(found, "Bar chart should be visible in Year View")
}
}

Binary file not shown.

View File

@@ -1,53 +1,71 @@
# UI Test Prompt Template # UI Test Prompt Template (QA Plan Driven)
Copy/paste this prompt into Codex or Claude and replace the placeholders. Copy/paste this into Claude (or Codex), then replace placeholders.
```md ```md
Create an iOS UI test for this behavior: Task:
Create 3 solid iOS UI tests from the QA plan that compile and run reliably using the existing test architecture.
<DESCRIBE USER FLOW / EXPECTED BEHAVIOR> Project:
- Root: /Users/treyt/Desktop/code/Feels
- QA source: /Users/treyt/Desktop/code/Feels/docs/Feels_QA_Test_Plan.xlsx
Repository context: Optional explicit test IDs/names from me (if provided, prioritize these):
- Project root: /Users/treyt/Desktop/code/Feels <PASTE_TEST_IDS_AND_NAMES_HERE_OR_LEAVE_EMPTY>
- Follow these files strictly:
- /Users/treyt/Desktop/code/Feels/docs/XCUITest-Authoring.md
- /Users/treyt/Desktop/code/Feels/AGENTS.md
- /Users/treyt/Desktop/code/Feels/Tests iOS/README.md
- /Users/treyt/Desktop/code/Feels/Tests iOS/Helpers/BaseUITestCase.swift
- /Users/treyt/Desktop/code/Feels/Tests iOS/Helpers/WaitHelpers.swift
- /Users/treyt/Desktop/code/Feels/Shared/AccessibilityIdentifiers.swift
Implementation requirements: Mandatory references (read before coding):
1. Use the established pattern: - /Users/treyt/Desktop/code/Feels/docs/XCUITest-Authoring.md
- /Users/treyt/Desktop/code/Feels/AGENTS.md
- /Users/treyt/Desktop/code/Feels/Tests iOS/README.md
- /Users/treyt/Desktop/code/Feels/Tests iOS/Helpers/BaseUITestCase.swift
- /Users/treyt/Desktop/code/Feels/Tests iOS/Helpers/WaitHelpers.swift
- /Users/treyt/Desktop/code/Feels/Shared/AccessibilityIdentifiers.swift
- Existing suites in /Users/treyt/Desktop/code/Feels/Tests iOS/ (use them for style/patterns)
Selection rules:
1. If I pasted specific QA IDs/names, use those first.
2. If fewer than 3 are provided, choose remaining from the spreadsheet.
3. Pick the easiest automatable tests (low setup complexity, deterministic UI state, no external dependencies).
4. Skip cases likely to be flaky or blocked (network dependency, unstable animation-only behavior, uncertain app hooks).
5. Briefly justify why each selected test is “easy + stable”.
Implementation rules (do not reinvent):
1. Reuse existing architecture only:
- `BaseUITestCase` - `BaseUITestCase`
- `UITestID` / accessibility identifier selectors first - `UITestID` and accessibility identifiers
- screen objects in `Tests iOS/Screens/` - screen objects under `Tests iOS/Screens/`
- wait helpers (`tapWhenReady`, `waitForExistence`, `waitForDisappearance`) - wait helpers (`tapWhenReady`, `waitForExistence`, `waitForDisappearance`, etc.)
2. Do NOT use `sleep(...)`. 2. No `sleep(...)`.
3. Do NOT rely on localized/raw text selectors as primary selectors. 3. No raw/localized text selectors as primary locators.
4. If needed, add missing accessibility IDs in app code and wire them into tests. 4. Add missing accessibility IDs only when required, then wire them through current helper patterns.
5. Keep the test deterministic using fixture + launch flags from `BaseUITestCase`. 5. Keep tests deterministic with fixtures and launch flags from `BaseUITestCase`.
6. Add screenshots at meaningful checkpoints for triage. 6. Follow existing naming/style conventions from current passing tests.
7. Add screenshots at meaningful checkpoints for triage.
Test setup choices: Flake-resistance checklist (must satisfy):
- Suggested suite file: `Tests iOS/<SUITE_NAME>Tests.swift` - Each test has deterministic starting state (fixture + launch args).
- Suggested test method: `test<FEATURE>_<BEHAVIOR>()` - No arbitrary timing waits.
- Fixture to use: `<empty | single_mood | week_of_moods>` - Assertions target stable identifiers.
- Launch overrides if needed: - Test does not depend on current date text formatting unless already stabilized by existing helpers.
- `skipOnboarding = <true/false>` - If a new selector is needed, add app-side accessibility identifier first.
- `bypassSubscription = <true/false>`
- `expireTrial = <true/false>`
Validation requirements: Deliverable shape:
1. Run targeted suite: - Prefer one suite file with 3 test methods, unless existing suite placement is clearly better.
- `xcodebuild -project Feels.xcodeproj -scheme "Feels (iOS)" -destination 'platform=iOS Simulator,name=iPhone 16 Pro' -only-testing:"Tests iOS/<SUITE_NAME>" test` - Method naming: `test<Feature>_<Behavior>()`.
2. Report pass/fail summary. - Keep helper logic in screen objects/helpers instead of duplicating in test body.
3. If failures occur, fix and rerun until green.
Validation gates (required before done):
1. Run only the 3 new tests (not full suite), e.g.:
- `xcodebuild -project Feels.xcodeproj -scheme "Feels (iOS)" -destination 'platform=iOS Simulator,name=iPhone 16 Pro' -only-testing:"Tests iOS/<SuiteName>/testA" -only-testing:"Tests iOS/<SuiteName>/testB" -only-testing:"Tests iOS/<SuiteName>/testC" test`
2. If failures occur, fix and rerun until green.
3. Run the same targeted command a second time to check flakiness.
4. Only mark complete if both runs pass.
Output format: Output format:
1. Files changed 1. Selected QA test IDs/names with short reason for selection.
2. Why each change was needed 2. Files changed.
3. Test run summary 3. Key architecture decisions (how existing patterns were reused).
4. Any follow-up risks/gaps 4. Exact test command(s) executed.
5. Run results for pass #1 and pass #2.
6. Any residual risk/gaps.
``` ```