Skip to content

Commit a1b1366

Browse files
cliffordhclaude
andcommitted
Modernize ViewModels with @observable and add unit tests
Migrate LibraryViewModel and StreakViewModel from ObservableObject/@Published/Combine to iOS 17+ @observable macro with async/await. Add LibraryDataSource protocol for dependency injection and extract streak calculation as a pure static method on StreakData. Add unit tests for streak logic and library filtering. Update deploy target to iOS 26, apply latest Xcode project settings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 16632bf commit a1b1366

15 files changed

Lines changed: 772 additions & 232 deletions

ios/MyApp.xcodeproj/project.pbxproj

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@
3232
remoteGlobalIDString = 91B04F792EEFC9B400F7AAF9;
3333
remoteInfo = MyAppWidgetExtension;
3434
};
35+
91C025CE2FD6ED8200049C07 /* PBXContainerItemProxy */ = {
36+
isa = PBXContainerItemProxy;
37+
containerPortal = 910DF9EB2EEFBD8600D8B585 /* Project object */;
38+
proxyType = 1;
39+
remoteGlobalIDString = 910DF9F22EEFBD8600D8B585;
40+
remoteInfo = MyApp;
41+
};
3542
/* End PBXContainerItemProxy section */
3643

3744
/* Begin PBXCopyFilesBuildPhase section */
@@ -53,6 +60,7 @@
5360
91B04F7A2EEFC9B400F7AAF9 /* MyAppWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MyAppWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
5461
91B04F7C2EEFC9B400F7AAF9 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
5562
91B04F7E2EEFC9B500F7AAF9 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
63+
91C025CA2FD6ED8200049C07 /* MyAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5664
/* End PBXFileReference section */
5765

5866
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -89,6 +97,11 @@
8997
path = Widget;
9098
sourceTree = "<group>";
9199
};
100+
91C025CB2FD6ED8200049C07 /* MyAppTests */ = {
101+
isa = PBXFileSystemSynchronizedRootGroup;
102+
path = MyAppTests;
103+
sourceTree = "<group>";
104+
};
92105
/* End PBXFileSystemSynchronizedRootGroup section */
93106

94107
/* Begin PBXFrameworksBuildPhase section */
@@ -120,6 +133,13 @@
120133
);
121134
runOnlyForDeploymentPostprocessing = 0;
122135
};
136+
91C025C72FD6ED8200049C07 /* Frameworks */ = {
137+
isa = PBXFrameworksBuildPhase;
138+
buildActionMask = 2147483647;
139+
files = (
140+
);
141+
runOnlyForDeploymentPostprocessing = 0;
142+
};
123143
/* End PBXFrameworksBuildPhase section */
124144

125145
/* Begin PBXGroup section */
@@ -128,6 +148,7 @@
128148
children = (
129149
91B04F9A2EEFCA3300F7AAF9 /* Widget */,
130150
910DFA382EEFBF3600D8B585 /* Sources */,
151+
91C025CB2FD6ED8200049C07 /* MyAppTests */,
131152
91B04F7B2EEFC9B400F7AAF9 /* Frameworks */,
132153
910DF9F42EEFBD8600D8B585 /* Products */,
133154
);
@@ -138,6 +159,7 @@
138159
children = (
139160
910DF9F32EEFBD8600D8B585 /* MyApp.app */,
140161
91B04F7A2EEFC9B400F7AAF9 /* MyAppWidgetExtension.appex */,
162+
91C025CA2FD6ED8200049C07 /* MyAppTests.xctest */,
141163
);
142164
name = Products;
143165
sourceTree = "<group>";
@@ -212,22 +234,49 @@
212234
productReference = 91B04F7A2EEFC9B400F7AAF9 /* MyAppWidgetExtension.appex */;
213235
productType = "com.apple.product-type.app-extension";
214236
};
237+
91C025C92FD6ED8200049C07 /* MyAppTests */ = {
238+
isa = PBXNativeTarget;
239+
buildConfigurationList = 91C025D22FD6ED8200049C07 /* Build configuration list for PBXNativeTarget "MyAppTests" */;
240+
buildPhases = (
241+
91C025C62FD6ED8200049C07 /* Sources */,
242+
91C025C72FD6ED8200049C07 /* Frameworks */,
243+
91C025C82FD6ED8200049C07 /* Resources */,
244+
);
245+
buildRules = (
246+
);
247+
dependencies = (
248+
91C025CF2FD6ED8200049C07 /* PBXTargetDependency */,
249+
);
250+
fileSystemSynchronizedGroups = (
251+
91C025CB2FD6ED8200049C07 /* MyAppTests */,
252+
);
253+
name = MyAppTests;
254+
packageProductDependencies = (
255+
);
256+
productName = MyAppTests;
257+
productReference = 91C025CA2FD6ED8200049C07 /* MyAppTests.xctest */;
258+
productType = "com.apple.product-type.bundle.unit-test";
259+
};
215260
/* End PBXNativeTarget section */
216261

217262
/* Begin PBXProject section */
218263
910DF9EB2EEFBD8600D8B585 /* Project object */ = {
219264
isa = PBXProject;
220265
attributes = {
221266
BuildIndependentTargetsInParallel = 1;
222-
LastSwiftUpdateCheck = 2610;
223-
LastUpgradeCheck = 2610;
267+
LastSwiftUpdateCheck = 2650;
268+
LastUpgradeCheck = 2650;
224269
TargetAttributes = {
225270
910DF9F22EEFBD8600D8B585 = {
226271
CreatedOnToolsVersion = 26.1.1;
227272
};
228273
91B04F792EEFC9B400F7AAF9 = {
229274
CreatedOnToolsVersion = 26.1.1;
230275
};
276+
91C025C92FD6ED8200049C07 = {
277+
CreatedOnToolsVersion = 26.5;
278+
TestTargetID = 910DF9F22EEFBD8600D8B585;
279+
};
231280
};
232281
};
233282
buildConfigurationList = 910DF9EE2EEFBD8600D8B585 /* Build configuration list for PBXProject "MyApp" */;
@@ -255,6 +304,7 @@
255304
targets = (
256305
910DF9F22EEFBD8600D8B585 /* MyApp */,
257306
91B04F792EEFC9B400F7AAF9 /* MyAppWidgetExtension */,
307+
91C025C92FD6ED8200049C07 /* MyAppTests */,
258308
);
259309
};
260310
/* End PBXProject section */
@@ -274,6 +324,13 @@
274324
);
275325
runOnlyForDeploymentPostprocessing = 0;
276326
};
327+
91C025C82FD6ED8200049C07 /* Resources */ = {
328+
isa = PBXResourcesBuildPhase;
329+
buildActionMask = 2147483647;
330+
files = (
331+
);
332+
runOnlyForDeploymentPostprocessing = 0;
333+
};
277334
/* End PBXResourcesBuildPhase section */
278335

279336
/* Begin PBXSourcesBuildPhase section */
@@ -291,6 +348,13 @@
291348
);
292349
runOnlyForDeploymentPostprocessing = 0;
293350
};
351+
91C025C62FD6ED8200049C07 /* Sources */ = {
352+
isa = PBXSourcesBuildPhase;
353+
buildActionMask = 2147483647;
354+
files = (
355+
);
356+
runOnlyForDeploymentPostprocessing = 0;
357+
};
294358
/* End PBXSourcesBuildPhase section */
295359

296360
/* Begin PBXTargetDependency section */
@@ -299,6 +363,11 @@
299363
target = 91B04F792EEFC9B400F7AAF9 /* MyAppWidgetExtension */;
300364
targetProxy = 91B04F8C2EEFC9B500F7AAF9 /* PBXContainerItemProxy */;
301365
};
366+
91C025CF2FD6ED8200049C07 /* PBXTargetDependency */ = {
367+
isa = PBXTargetDependency;
368+
target = 910DF9F22EEFBD8600D8B585 /* MyApp */;
369+
targetProxy = 91C025CE2FD6ED8200049C07 /* PBXContainerItemProxy */;
370+
};
302371
/* End PBXTargetDependency section */
303372

304373
/* Begin XCBuildConfiguration section */
@@ -360,6 +429,7 @@
360429
MTL_FAST_MATH = YES;
361430
ONLY_ACTIVE_ARCH = YES;
362431
SDKROOT = iphoneos;
432+
STRING_CATALOG_GENERATE_SYMBOLS = YES;
363433
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
364434
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
365435
};
@@ -416,6 +486,7 @@
416486
MTL_ENABLE_DEBUG_INFO = NO;
417487
MTL_FAST_MATH = YES;
418488
SDKROOT = iphoneos;
489+
STRING_CATALOG_GENERATE_SYMBOLS = YES;
419490
SWIFT_COMPILATION_MODE = wholemodule;
420491
VALIDATE_PRODUCT = YES;
421492
};
@@ -440,6 +511,7 @@
440511
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
441512
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
442513
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
514+
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
443515
LD_RUNPATH_SEARCH_PATHS = (
444516
"$(inherited)",
445517
"@executable_path/Frameworks",
@@ -481,6 +553,7 @@
481553
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
482554
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
483555
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
556+
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
484557
LD_RUNPATH_SEARCH_PATHS = (
485558
"$(inherited)",
486559
"@executable_path/Frameworks",
@@ -565,6 +638,48 @@
565638
};
566639
name = Release;
567640
};
641+
91C025D02FD6ED8200049C07 /* Debug */ = {
642+
isa = XCBuildConfiguration;
643+
buildSettings = {
644+
BUNDLE_LOADER = "$(TEST_HOST)";
645+
CODE_SIGN_STYLE = Automatic;
646+
CURRENT_PROJECT_VERSION = 1;
647+
GENERATE_INFOPLIST_FILE = YES;
648+
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
649+
MARKETING_VERSION = 1.0;
650+
PRODUCT_BUNDLE_IDENTIFIER = com.example.MyAppTests;
651+
PRODUCT_NAME = "$(TARGET_NAME)";
652+
STRING_CATALOG_GENERATE_SYMBOLS = NO;
653+
SWIFT_APPROACHABLE_CONCURRENCY = YES;
654+
SWIFT_EMIT_LOC_STRINGS = NO;
655+
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
656+
SWIFT_VERSION = 5.0;
657+
TARGETED_DEVICE_FAMILY = "1,2";
658+
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MyApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MyApp";
659+
};
660+
name = Debug;
661+
};
662+
91C025D12FD6ED8200049C07 /* Release */ = {
663+
isa = XCBuildConfiguration;
664+
buildSettings = {
665+
BUNDLE_LOADER = "$(TEST_HOST)";
666+
CODE_SIGN_STYLE = Automatic;
667+
CURRENT_PROJECT_VERSION = 1;
668+
GENERATE_INFOPLIST_FILE = YES;
669+
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
670+
MARKETING_VERSION = 1.0;
671+
PRODUCT_BUNDLE_IDENTIFIER = com.example.MyAppTests;
672+
PRODUCT_NAME = "$(TARGET_NAME)";
673+
STRING_CATALOG_GENERATE_SYMBOLS = NO;
674+
SWIFT_APPROACHABLE_CONCURRENCY = YES;
675+
SWIFT_EMIT_LOC_STRINGS = NO;
676+
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
677+
SWIFT_VERSION = 5.0;
678+
TARGETED_DEVICE_FAMILY = "1,2";
679+
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MyApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MyApp";
680+
};
681+
name = Release;
682+
};
568683
/* End XCBuildConfiguration section */
569684

570685
/* Begin XCConfigurationList section */
@@ -595,6 +710,15 @@
595710
defaultConfigurationIsVisible = 0;
596711
defaultConfigurationName = Release;
597712
};
713+
91C025D22FD6ED8200049C07 /* Build configuration list for PBXNativeTarget "MyAppTests" */ = {
714+
isa = XCConfigurationList;
715+
buildConfigurations = (
716+
91C025D02FD6ED8200049C07 /* Debug */,
717+
91C025D12FD6ED8200049C07 /* Release */,
718+
);
719+
defaultConfigurationIsVisible = 0;
720+
defaultConfigurationName = Release;
721+
};
598722
/* End XCConfigurationList section */
599723

600724
/* Begin XCRemoteSwiftPackageReference section */

ios/MyApp.xcodeproj/xcshareddata/xcschemes/MyApp.xcscheme

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "2620"
3+
LastUpgradeVersion = "2650"
44
version = "1.7">
55
<BuildAction
66
parallelizeBuildables = "YES"
@@ -29,6 +29,19 @@
2929
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
3030
shouldUseLaunchSchemeArgsEnv = "YES"
3131
shouldAutocreateTestPlan = "YES">
32+
<Testables>
33+
<TestableReference
34+
skipped = "NO"
35+
parallelizable = "YES">
36+
<BuildableReference
37+
BuildableIdentifier = "primary"
38+
BlueprintIdentifier = "91C025C92FD6ED8200049C07"
39+
BuildableName = "MyAppTests.xctest"
40+
BlueprintName = "MyAppTests"
41+
ReferencedContainer = "container:MyApp.xcodeproj">
42+
</BuildableReference>
43+
</TestableReference>
44+
</Testables>
3245
</TestAction>
3346
<LaunchAction
3447
buildConfiguration = "Debug"

ios/MyApp.xcodeproj/xcshareddata/xcschemes/MyAppWidgetExtension.xcscheme

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "2620"
3+
LastUpgradeVersion = "2650"
44
version = "2.0">
55
<BuildAction
66
parallelizeBuildables = "YES"
@@ -43,6 +43,19 @@
4343
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
4444
shouldUseLaunchSchemeArgsEnv = "YES"
4545
shouldAutocreateTestPlan = "YES">
46+
<Testables>
47+
<TestableReference
48+
skipped = "NO"
49+
parallelizable = "YES">
50+
<BuildableReference
51+
BuildableIdentifier = "primary"
52+
BlueprintIdentifier = "91C025C92FD6ED8200049C07"
53+
BuildableName = "MyAppTests.xctest"
54+
BlueprintName = "MyAppTests"
55+
ReferencedContainer = "container:MyApp.xcodeproj">
56+
</BuildableReference>
57+
</TestableReference>
58+
</Testables>
4659
</TestAction>
4760
<LaunchAction
4861
buildConfiguration = "Debug"

0 commit comments

Comments
 (0)