feat(tests): Add unit tests for AudioManager, PresetManager, and Sound classes

feat(tests): Implement async XCTestCase extension for handling asynchronous operations
feat(audio): Enhance Sound class with public access and loadSound method
feat(settings): Update GlobalSettings methods to use @MainActor for thread safety
This commit is contained in:
Cody Bromley
2025-01-10 22:32:32 -06:00
parent e81cdec556
commit 0d17ec9923
14 changed files with 637 additions and 80 deletions
+3 -1
View File
@@ -13,7 +13,9 @@
"mjs",
"scss",
"CNAME",
"Dockerfile"
"Dockerfile",
"xcconfig",
"xctestplan"
],
"markdownlint.config": {
"default": true,
+142 -68
View File
@@ -14,14 +14,14 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
F91350732D233A44003C85BE /* PBXContainerItemProxy */ = {
F9ED6BAE2D321D3300240F13 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = F91350572D233A43003C85BE /* Project object */;
proxyType = 1;
remoteGlobalIDString = F913505E2D233A43003C85BE;
remoteInfo = Blankie;
};
F913507D2D233A44003C85BE /* PBXContainerItemProxy */ = {
F9ED70E42D3229AD00240F13 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = F91350572D233A43003C85BE /* Project object */;
proxyType = 1;
@@ -30,10 +30,21 @@
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
F9ED6F802D32292D00240F13 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
F913505F2D233A43003C85BE /* Blankie.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Blankie.app; sourceTree = BUILT_PRODUCTS_DIR; };
F91350722D233A44003C85BE /* BlankieTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BlankieTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F913507C2D233A44003C85BE /* BlankieUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BlankieUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F921ED992D272AE300D4F3D3 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Configuration.xcconfig; sourceTree = "<group>"; };
F93A3B0D2D26E93600EFC1C9 /* AppCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCommands.swift; sourceTree = "<group>"; };
F93A3B0E2D26E93600EFC1C9 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
@@ -41,6 +52,10 @@
F93A3B122D26E93600EFC1C9 /* Blankie.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Blankie.xcodeproj; sourceTree = "<group>"; };
F93A3B132D26E93600EFC1C9 /* BlankieApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlankieApp.swift; sourceTree = "<group>"; };
F93A3B302D26E93600EFC1C9 /* WindowObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowObserver.swift; sourceTree = "<group>"; };
F9ED65FE2D321AF800240F13 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
F9ED66022D321B7D00240F13 /* Blankie.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Blankie.xctestplan; sourceTree = "<group>"; };
F9ED6BAA2D321D3300240F13 /* BlankieTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BlankieTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F9ED70DE2D3229AD00240F13 /* BlankieUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BlankieUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -82,6 +97,16 @@
path = Resources;
sourceTree = "<group>";
};
F9ED6DAB2D321F5D00240F13 /* BlankieTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = BlankieTests;
sourceTree = "<group>";
};
F9ED70DF2D3229AD00240F13 /* BlankieUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = BlankieUITests;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@@ -92,14 +117,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F913506F2D233A44003C85BE /* Frameworks */ = {
F9ED6BA72D321D3300240F13 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
F91350792D233A44003C85BE /* Frameworks */ = {
F9ED70DB2D3229AD00240F13 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -113,7 +138,10 @@
isa = PBXGroup;
children = (
F91350602D233A43003C85BE /* Products */,
F9ED6DAB2D321F5D00240F13 /* BlankieTests */,
F9ED70DF2D3229AD00240F13 /* BlankieUITests */,
F93A3B122D26E93600EFC1C9 /* Blankie.xcodeproj */,
F9ED66022D321B7D00240F13 /* Blankie.xctestplan */,
F93A3B112D26E93600EFC1C9 /* Blankie.entitlements */,
F921ED992D272AE300D4F3D3 /* Configuration.xcconfig */,
F97369002D27A6F8006AF32C /* Models */,
@@ -125,6 +153,7 @@
F93A3B0E2D26E93600EFC1C9 /* AppState.swift */,
F93A3B132D26E93600EFC1C9 /* BlankieApp.swift */,
F93A3B302D26E93600EFC1C9 /* WindowObserver.swift */,
F9ED65FD2D321AF800240F13 /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -132,8 +161,8 @@
isa = PBXGroup;
children = (
F913505F2D233A43003C85BE /* Blankie.app */,
F91350722D233A44003C85BE /* BlankieTests.xctest */,
F913507C2D233A44003C85BE /* BlankieUITests.xctest */,
F9ED6BAA2D321D3300240F13 /* BlankieTests.xctest */,
F9ED70DE2D3229AD00240F13 /* BlankieUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -143,6 +172,14 @@
name = Products;
sourceTree = "<group>";
};
F9ED65FD2D321AF800240F13 /* Frameworks */ = {
isa = PBXGroup;
children = (
F9ED65FE2D321AF800240F13 /* XCTest.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -173,44 +210,51 @@
productReference = F913505F2D233A43003C85BE /* Blankie.app */;
productType = "com.apple.product-type.application";
};
F91350712D233A44003C85BE /* BlankieTests */ = {
F9ED6BA92D321D3300240F13 /* BlankieTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = F91350892D233A44003C85BE /* Build configuration list for PBXNativeTarget "BlankieTests" */;
buildConfigurationList = F9ED6BB02D321D3300240F13 /* Build configuration list for PBXNativeTarget "BlankieTests" */;
buildPhases = (
F913506E2D233A44003C85BE /* Sources */,
F913506F2D233A44003C85BE /* Frameworks */,
F91350702D233A44003C85BE /* Resources */,
F9ED6BA62D321D3300240F13 /* Sources */,
F9ED6BA72D321D3300240F13 /* Frameworks */,
F9ED6BA82D321D3300240F13 /* Resources */,
F9ED6F802D32292D00240F13 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
F91350742D233A44003C85BE /* PBXTargetDependency */,
F9ED6BAF2D321D3300240F13 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
F9ED6DAB2D321F5D00240F13 /* BlankieTests */,
);
name = BlankieTests;
packageProductDependencies = (
);
productName = BlankieTests;
productReference = F91350722D233A44003C85BE /* BlankieTests.xctest */;
productReference = F9ED6BAA2D321D3300240F13 /* BlankieTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
F913507B2D233A44003C85BE /* BlankieUITests */ = {
F9ED70DD2D3229AD00240F13 /* BlankieUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = F913508C2D233A44003C85BE /* Build configuration list for PBXNativeTarget "BlankieUITests" */;
buildConfigurationList = F9ED70E62D3229AD00240F13 /* Build configuration list for PBXNativeTarget "BlankieUITests" */;
buildPhases = (
F91350782D233A44003C85BE /* Sources */,
F91350792D233A44003C85BE /* Frameworks */,
F913507A2D233A44003C85BE /* Resources */,
F9ED70DA2D3229AD00240F13 /* Sources */,
F9ED70DB2D3229AD00240F13 /* Frameworks */,
F9ED70DC2D3229AD00240F13 /* Resources */,
);
buildRules = (
);
dependencies = (
F913507E2D233A44003C85BE /* PBXTargetDependency */,
F9ED70E52D3229AD00240F13 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
F9ED70DF2D3229AD00240F13 /* BlankieUITests */,
);
name = BlankieUITests;
packageProductDependencies = (
);
productName = BlankieUITests;
productReference = F913507C2D233A44003C85BE /* BlankieUITests.xctest */;
productReference = F9ED70DE2D3229AD00240F13 /* BlankieUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
@@ -227,11 +271,11 @@
CreatedOnToolsVersion = 16.2;
LastSwiftMigration = 1620;
};
F91350712D233A44003C85BE = {
F9ED6BA92D321D3300240F13 = {
CreatedOnToolsVersion = 16.2;
TestTargetID = F913505E2D233A43003C85BE;
};
F913507B2D233A44003C85BE = {
F9ED70DD2D3229AD00240F13 = {
CreatedOnToolsVersion = 16.2;
TestTargetID = F913505E2D233A43003C85BE;
};
@@ -260,8 +304,8 @@
projectRoot = "";
targets = (
F913505E2D233A43003C85BE /* Blankie */,
F91350712D233A44003C85BE /* BlankieTests */,
F913507B2D233A44003C85BE /* BlankieUITests */,
F9ED6BA92D321D3300240F13 /* BlankieTests */,
F9ED70DD2D3229AD00240F13 /* BlankieUITests */,
);
};
/* End PBXProject section */
@@ -274,14 +318,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F91350702D233A44003C85BE /* Resources */ = {
F9ED6BA82D321D3300240F13 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
F913507A2D233A44003C85BE /* Resources */ = {
F9ED70DC2D3229AD00240F13 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -326,14 +370,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F913506E2D233A44003C85BE /* Sources */ = {
F9ED6BA62D321D3300240F13 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
F91350782D233A44003C85BE /* Sources */ = {
F9ED70DA2D3229AD00240F13 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -343,15 +387,15 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
F91350742D233A44003C85BE /* PBXTargetDependency */ = {
F9ED6BAF2D321D3300240F13 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = F913505E2D233A43003C85BE /* Blankie */;
targetProxy = F91350732D233A44003C85BE /* PBXContainerItemProxy */;
targetProxy = F9ED6BAE2D321D3300240F13 /* PBXContainerItemProxy */;
};
F913507E2D233A44003C85BE /* PBXTargetDependency */ = {
F9ED70E52D3229AD00240F13 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = F913505E2D233A43003C85BE /* Blankie */;
targetProxy = F913507D2D233A44003C85BE /* PBXContainerItemProxy */;
targetProxy = F9ED70E42D3229AD00240F13 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
@@ -555,81 +599,111 @@
};
name = Release;
};
F913508A2D233A44003C85BE /* Debug */ = {
F9ED6BB12D321D3300240F13 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F921ED992D272AE300D4F3D3 /* Configuration.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.2;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "";
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).tests";
"PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "$(PRODUCT_BUNDLE_IDENTIFIER).tests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = macosx;
SUPPORTS_MACCATALYST = NO;
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Blankie.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Blankie";
XROS_DEPLOYMENT_TARGET = 2.2;
};
name = Debug;
};
F913508B2D233A44003C85BE /* Release */ = {
F9ED6BB22D321D3300240F13 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F921ED992D272AE300D4F3D3 /* Configuration.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.2;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "";
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).tests";
"PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "$(PRODUCT_BUNDLE_IDENTIFIER).tests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = macosx;
SUPPORTS_MACCATALYST = NO;
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Blankie.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Blankie";
XROS_DEPLOYMENT_TARGET = 2.2;
};
name = Release;
};
F913508D2D233A44003C85BE /* Debug */ = {
F9ED70E72D3229AD00240F13 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F921ED992D272AE300D4F3D3 /* Configuration.xcconfig */;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.2;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "";
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).uitests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = macosx;
SUPPORTS_MACCATALYST = NO;
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = Blankie;
XROS_DEPLOYMENT_TARGET = 2.2;
};
name = Debug;
};
F913508E2D233A44003C85BE /* Release */ = {
F9ED70E82D3229AD00240F13 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F921ED992D272AE300D4F3D3 /* Configuration.xcconfig */;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.2;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "";
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).uitests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = macosx;
SUPPORTS_MACCATALYST = NO;
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = Blankie;
XROS_DEPLOYMENT_TARGET = 2.2;
};
name = Release;
};
@@ -654,20 +728,20 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F91350892D233A44003C85BE /* Build configuration list for PBXNativeTarget "BlankieTests" */ = {
F9ED6BB02D321D3300240F13 /* Build configuration list for PBXNativeTarget "BlankieTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F913508A2D233A44003C85BE /* Debug */,
F913508B2D233A44003C85BE /* Release */,
F9ED6BB12D321D3300240F13 /* Debug */,
F9ED6BB22D321D3300240F13 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F913508C2D233A44003C85BE /* Build configuration list for PBXNativeTarget "BlankieUITests" */ = {
F9ED70E62D3229AD00240F13 /* Build configuration list for PBXNativeTarget "BlankieUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F913508D2D233A44003C85BE /* Debug */,
F913508E2D233A44003C85BE /* Release */,
F9ED70E72D3229AD00240F13 /* Debug */,
F9ED70E82D3229AD00240F13 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F913505E2D233A43003C85BE"
BuildableName = "Blankie.app"
BlueprintName = "Blankie"
ReferencedContainer = "container:Blankie.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:Blankie.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F9ED6BA92D321D3300240F13"
BuildableName = "BlankieTests.xctest"
BlueprintName = "BlankieTests"
ReferencedContainer = "container:Blankie.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F9ED70DD2D3229AD00240F13"
BuildableName = "BlankieUITests.xctest"
BlueprintName = "BlankieUITests"
ReferencedContainer = "container:Blankie.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F913505E2D233A43003C85BE"
BuildableName = "Blankie.app"
BlueprintName = "Blankie"
ReferencedContainer = "container:Blankie.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F913505E2D233A43003C85BE"
BuildableName = "Blankie.app"
BlueprintName = "Blankie"
ReferencedContainer = "container:Blankie.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+30
View File
@@ -0,0 +1,30 @@
{
"configurations" : [
{
"id" : "9B22136B-03AA-4556-A3CA-6156C33341BF",
"name" : "Test Scheme Action",
"options" : {
"defaultTestExecutionTimeAllowance" : 60,
"testTimeoutsEnabled" : true
}
}
],
"defaultOptions" : {
"targetForVariableExpansion" : {
"containerPath" : "container:Blankie.xcodeproj",
"identifier" : "F913505E2D233A43003C85BE",
"name" : "Blankie"
}
},
"testTargets" : [
{
"parallelizable" : true,
"target" : {
"containerPath" : "container:Blankie.xcodeproj",
"identifier" : "F9ED6BA92D321D3300240F13",
"name" : "BlankieTests"
}
}
],
"version" : 1
}
+63
View File
@@ -0,0 +1,63 @@
//
// AudioManagerTests.swift
// Blankie
//
// Created by Cody Bromley on 1/10/25.
//
import XCTest
@testable import Blankie
@MainActor
final class AudioManagerTests: XCTestCase {
var audioManager: AudioManager!
override func setUp() async throws {
try await super.setUp()
audioManager = AudioManager.shared
// Ensure we start with a clean state
GlobalSettings.shared.setAlwaysStartPaused(false)
audioManager.resetSounds()
}
override func tearDown() async throws {
// Reset to default state
GlobalSettings.shared.setAlwaysStartPaused(true)
audioManager.resetSounds()
try await super.tearDown()
}
func testInitialState() async throws {
XCTAssertFalse(audioManager.isGloballyPlaying)
XCTAssertFalse(audioManager.sounds.isEmpty)
}
func testTogglePlayback() async throws {
// Setup: Select a sound and verify initial state
XCTAssertFalse(audioManager.isGloballyPlaying)
audioManager.sounds[0].isSelected = true
// Test direct state changes
audioManager.setGlobalPlaybackState(true)
XCTAssertTrue(audioManager.isGloballyPlaying, "Should be playing after setting state to true")
audioManager.setGlobalPlaybackState(false)
XCTAssertFalse(
audioManager.isGloballyPlaying, "Should not be playing after setting state to false")
}
func testResetSounds() async throws {
// Select some sounds and adjust volumes
audioManager.sounds[0].isSelected = true
audioManager.sounds[0].volume = 0.5
audioManager.resetSounds()
// Verify all sounds are reset
for sound in audioManager.sounds {
XCTAssertFalse(sound.isSelected)
XCTAssertEqual(sound.volume, 1.0)
}
}
}
+35
View File
@@ -0,0 +1,35 @@
//
// BlankieTests.swift
// BlankieTests
//
// Created by Cody Bromley on 1/10/25.
//
import XCTest
@testable import Blankie
class BlankieTests: XCTestCase {
override func tearDown() async throws {
// Reset global state
await MainActor.run {
// Reset global settings
GlobalSettings.shared.setVolume(1.0)
GlobalSettings.shared.setAccentColor(nil)
GlobalSettings.shared.setAppearance(.system)
GlobalSettings.shared.setAlwaysStartPaused(true)
// Reset audio manager
AudioManager.shared.resetSounds()
}
// Delete non-default presets
await MainActor.run {
for preset in PresetManager.shared.presets.filter({ !$0.isDefault }) {
PresetManager.shared.deletePreset(preset)
}
}
try await super.tearDown()
}
}
+70
View File
@@ -0,0 +1,70 @@
//
// PresetManagerTests.swift
// Blankie
//
// Created by Cody Bromley on 1/10/25.
//
import XCTest
@testable import Blankie
final class PresetManagerTests: XCTestCase {
var presetManager: PresetManager!
override func setUp() {
super.setUp()
presetManager = PresetManager.shared
}
override func tearDown() async throws {
// Clean up test presets
await MainActor.run {
presetManager.presets
.filter { !$0.isDefault }
.forEach { presetManager.deletePreset($0) }
}
try await super.tearDown()
}
func testCreateNewPreset() async throws {
let presetName = "Test Preset"
await MainActor.run {
presetManager.saveNewPreset(name: presetName)
XCTAssertTrue(presetManager.presets.contains { $0.name == presetName })
}
}
func testDeletePreset() async throws {
let presetName = "Test Delete"
await MainActor.run {
presetManager.saveNewPreset(name: presetName)
if let preset = presetManager.presets.first(where: { $0.name == presetName }) {
presetManager.deletePreset(preset)
XCTAssertFalse(presetManager.presets.contains { $0.name == presetName })
} else {
XCTFail("Failed to create test preset")
}
}
}
func testUpdatePreset() async throws {
let originalName = "Original Name"
let newName = "Updated Name"
await MainActor.run {
presetManager.saveNewPreset(name: originalName)
if let preset = presetManager.presets.first(where: { $0.name == originalName }) {
presetManager.updatePreset(preset, newName: newName)
XCTAssertTrue(presetManager.presets.contains { $0.name == newName })
XCTAssertFalse(presetManager.presets.contains { $0.name == originalName })
} else {
XCTFail("Failed to create test preset")
}
}
}
}
+67
View File
@@ -0,0 +1,67 @@
//
// SoundTests.swift
// Blankie
//
// Created by Cody Bromley on 1/10/25.
//
import XCTest
@testable import Blankie
class MockSound: Sound {
override init(title: String, systemIconName: String, fileName: String) {
super.init(title: title, systemIconName: systemIconName, fileName: fileName)
self.isSelected = false // Must explicitly set to false
self.volume = 1.0 // Ensure initial volume is set
}
override func loadSound() {
// Don't actually load sound file in tests
player = nil
}
}
class SoundTests: XCTestCase {
var sound: MockSound!
override func setUp() {
super.setUp()
// Create a new instance for each test
sound = MockSound(title: "Test Sound", systemIconName: "speaker.wave", fileName: "test")
}
override func tearDown() async throws {
sound = nil
try await super.tearDown()
}
func testInitialState() {
// Create a fresh instance to test initial state
let newSound = MockSound(title: "Fresh Test", systemIconName: "speaker.wave", fileName: "test")
XCTAssertEqual(newSound.volume, 1.0, "Initial volume should be 1.0")
XCTAssertFalse(newSound.isSelected, "Sound should not be selected initially")
XCTAssertEqual(newSound.title, "Fresh Test", "Title should match initialization")
}
func testToggle() {
XCTAssertFalse(sound.isSelected, "Should start unselected")
sound.toggle()
XCTAssertTrue(sound.isSelected, "Should be selected after toggle")
sound.toggle()
XCTAssertFalse(sound.isSelected, "Should be unselected after second toggle")
}
func testInvalidVolume() {
let originalVolume = sound.volume
sound.volume = 2.0 // Should fail silently and keep old value
XCTAssertEqual(sound.volume, originalVolume, "Volume should not change when set above 1.0")
sound.volume = -1.0 // Should fail silently and keep old value
XCTAssertEqual(sound.volume, originalVolume, "Volume should not change when set below 0.0")
sound.volume = 0.5 // Should work
XCTAssertEqual(sound.volume, 0.5, "Volume should change for valid values")
}
}
+21
View File
@@ -0,0 +1,21 @@
//
// XCTestCase+Async.swift
// Blankie
//
// Created by Cody Bromley on 1/10/25.
//
import XCTest
extension XCTestCase {
func waitForAsync(timeout: TimeInterval = 1.0, completion: @escaping () async -> Void) async {
let expectation = expectation(description: "Async operation")
Task {
await completion()
expectation.fulfill()
}
await fulfillment(of: [expectation], timeout: timeout)
}
}
+43
View File
@@ -0,0 +1,43 @@
//
// BlankieUITests.swift
// BlankieUITests
//
// Created by Cody Bromley on 1/10/25.
//
import XCTest
final class BlankieUITests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
// In UI tests its important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
@MainActor
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
@MainActor
func testLaunchPerformance() throws {
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
}
}
@@ -0,0 +1,33 @@
//
// BlankieUITestsLaunchTests.swift
// BlankieUITests
//
// Created by Cody Bromley on 1/10/25.
//
import XCTest
final class BlankieUITestsLaunchTests: XCTestCase {
override class var runsForEachTargetApplicationUIConfiguration: Bool {
true
}
override func setUpWithError() throws {
continueAfterFailure = false
}
@MainActor
func testLaunch() throws {
let app = XCUIApplication()
app.launch()
// Insert steps here to perform after app launch but before taking a screenshot,
// such as logging into a test account or navigating somewhere in the app
let attachment = XCTAttachment(screenshot: app.screenshot())
attachment.name = "Launch Screen"
attachment.lifetime = .keepAlways
add(attachment)
}
}
+13 -2
View File
@@ -59,8 +59,8 @@ class AudioManager: ObservableObject {
.store(in: &cancellables)
}
}
func setPlaybackState(_ playing: Bool) {
guard !isInitializing else {
func setPlaybackState(_ playing: Bool, forceUpdate: Bool = false) {
guard !isInitializing || forceUpdate else {
print("🎵 AudioManager: Ignoring setPlaybackState during initialization")
return
}
@@ -210,6 +210,16 @@ class AudioManager: ObservableObject {
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
func updateNowPlayingState() async {
let playbackRate: Double = isGloballyPlaying ? 1.0 : 0.0
print(
"🎵 AudioManager: Updating now playing state to \(isGloballyPlaying), playbackRate: \(playbackRate)"
)
// Update volume through GlobalSettings
await GlobalSettings.shared.setVolume(isGloballyPlaying ? 1.0 : 0.0)
}
private func updatePlaybackState() {
// Update playback state
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isGloballyPlaying ? 1.0 : 0.0
@@ -273,6 +283,7 @@ class AudioManager: ObservableObject {
print(" - New state (post-toggle): \(isGloballyPlaying)")
}
@MainActor
func resetSounds() {
print("🎵 AudioManager: Resetting all sounds")
+3 -3
View File
@@ -10,9 +10,9 @@ import Combine
import SwiftUI
/// Represents a single sound with its associated properties and playback controls.
class Sound: ObservableObject, Identifiable {
open class Sound: ObservableObject, Identifiable {
let id = UUID()
public let id = UUID()
let title: String
let systemIconName: String
let fileName: String
@@ -94,7 +94,7 @@ class Sound: ObservableObject, Identifiable {
}
return player
}
private func loadSound() {
open func loadSound() {
guard let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension) else {
print("❌ Sound: File not found for '\(fileName)'")
ErrorReporter.shared.report(AudioError.fileNotFound)
+7 -6
View File
@@ -90,25 +90,26 @@ class GlobalSettings: ObservableObject {
}
// Public methods to update values
@MainActor
func setVolume(_ newVolume: Double) {
volume = newVolume
logCurrentSettings()
}
@MainActor
func setAppearance(_ newAppearance: AppearanceMode) {
DispatchQueue.main.async { [weak self] in
self?.appearance = newAppearance
self?.updateAppAppearance()
self?.logCurrentSettings()
}
appearance = newAppearance
updateAppAppearance()
logCurrentSettings()
}
@MainActor
func setAccentColor(_ newColor: Color?) {
customAccentColor = newColor
logCurrentSettings()
}
@MainActor
func setAlwaysStartPaused(_ value: Bool) {
alwaysStartPaused = value
logCurrentSettings()