diff --git a/.vscode/settings.json b/.vscode/settings.json index dacc0c6..7256741 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,7 +13,9 @@ "mjs", "scss", "CNAME", - "Dockerfile" + "Dockerfile", + "xcconfig", + "xctestplan" ], "markdownlint.config": { "default": true, diff --git a/Blankie.xcodeproj/project.pbxproj b/Blankie.xcodeproj/project.pbxproj index 2d09652..9a8e8a2 100644 --- a/Blankie.xcodeproj/project.pbxproj +++ b/Blankie.xcodeproj/project.pbxproj @@ -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 = ""; }; F93A3B0D2D26E93600EFC1C9 /* AppCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCommands.swift; sourceTree = ""; }; F93A3B0E2D26E93600EFC1C9 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; @@ -41,6 +52,10 @@ F93A3B122D26E93600EFC1C9 /* Blankie.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Blankie.xcodeproj; sourceTree = ""; }; F93A3B132D26E93600EFC1C9 /* BlankieApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlankieApp.swift; sourceTree = ""; }; F93A3B302D26E93600EFC1C9 /* WindowObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowObserver.swift; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; + F9ED6DAB2D321F5D00240F13 /* BlankieTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = BlankieTests; + sourceTree = ""; + }; + F9ED70DF2D3229AD00240F13 /* BlankieUITests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = BlankieUITests; + sourceTree = ""; + }; /* 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 = ""; }; @@ -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 = ""; @@ -143,6 +172,14 @@ name = Products; sourceTree = ""; }; + F9ED65FD2D321AF800240F13 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F9ED65FE2D321AF800240F13 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* 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; diff --git a/Blankie.xcodeproj/xcshareddata/xcschemes/Blankie.xcscheme b/Blankie.xcodeproj/xcshareddata/xcschemes/Blankie.xcscheme new file mode 100644 index 0000000..46a0b41 --- /dev/null +++ b/Blankie.xcodeproj/xcshareddata/xcschemes/Blankie.xcscheme @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Blankie.xctestplan b/Blankie.xctestplan new file mode 100644 index 0000000..cbc6b74 --- /dev/null +++ b/Blankie.xctestplan @@ -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 +} diff --git a/BlankieTests/AudioManagerTests.swift b/BlankieTests/AudioManagerTests.swift new file mode 100644 index 0000000..1a33bad --- /dev/null +++ b/BlankieTests/AudioManagerTests.swift @@ -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) + } + } +} diff --git a/BlankieTests/BlankieTests.swift b/BlankieTests/BlankieTests.swift new file mode 100644 index 0000000..df2105b --- /dev/null +++ b/BlankieTests/BlankieTests.swift @@ -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() + } +} diff --git a/BlankieTests/PresetManagerTests.swift b/BlankieTests/PresetManagerTests.swift new file mode 100644 index 0000000..7c7630c --- /dev/null +++ b/BlankieTests/PresetManagerTests.swift @@ -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") + } + } + } +} diff --git a/BlankieTests/SoundTests.swift b/BlankieTests/SoundTests.swift new file mode 100644 index 0000000..c427278 --- /dev/null +++ b/BlankieTests/SoundTests.swift @@ -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") + } +} diff --git a/BlankieTests/XCTestCase+Async.swift b/BlankieTests/XCTestCase+Async.swift new file mode 100644 index 0000000..2058a27 --- /dev/null +++ b/BlankieTests/XCTestCase+Async.swift @@ -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) + } +} diff --git a/BlankieUITests/BlankieUITests.swift b/BlankieUITests/BlankieUITests.swift new file mode 100644 index 0000000..d21b802 --- /dev/null +++ b/BlankieUITests/BlankieUITests.swift @@ -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 it’s 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() + } + } + } +} diff --git a/BlankieUITests/BlankieUITestsLaunchTests.swift b/BlankieUITests/BlankieUITestsLaunchTests.swift new file mode 100644 index 0000000..190a1b8 --- /dev/null +++ b/BlankieUITests/BlankieUITestsLaunchTests.swift @@ -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) + } +} diff --git a/Managers/Audio/AudioManager.swift b/Managers/Audio/AudioManager.swift index 8632ced..9515df7 100644 --- a/Managers/Audio/AudioManager.swift +++ b/Managers/Audio/AudioManager.swift @@ -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") diff --git a/Managers/Audio/Sound.swift b/Managers/Audio/Sound.swift index 45c4dab..c26662e 100644 --- a/Managers/Audio/Sound.swift +++ b/Managers/Audio/Sound.swift @@ -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) diff --git a/Managers/Settings/GlobalSettings.swift b/Managers/Settings/GlobalSettings.swift index 7c86812..dc6fd14 100644 --- a/Managers/Settings/GlobalSettings.swift +++ b/Managers/Settings/GlobalSettings.swift @@ -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()