Fix color handling in views and fix tests. Update CategoryModel with proper color system
This commit is contained in:
parent
9dc9e70d73
commit
19611664d3
13 changed files with 481 additions and 371 deletions
40
.github/workflows/tests.yml
vendored
40
.github/workflows/tests.yml
vendored
|
|
@ -7,21 +7,29 @@ on:
|
|||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
ios-build:
|
||||
name: Build and Test iOS app
|
||||
runs-on: macos-13
|
||||
|
||||
- name: Select Xcode
|
||||
run: sudo xcode-select -switch /Applications/Xcode.app
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build and Test
|
||||
run: |
|
||||
xcodebuild test \
|
||||
-project Coinly.xcodeproj \
|
||||
-scheme Coinly \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 14,OS=latest' \
|
||||
-enableCodeCoverage YES \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO
|
||||
- name: Set Xcode version
|
||||
run: sudo xcode-select -s /Applications/Xcode.app
|
||||
|
||||
- name: List available schemes
|
||||
run: xcodebuild -list -project Coinly.xcodeproj
|
||||
|
||||
- name: List available simulators
|
||||
run: xcrun simctl list devices available
|
||||
|
||||
- name: Build and test
|
||||
run: |
|
||||
xcodebuild clean build test \
|
||||
-project Coinly.xcodeproj \
|
||||
-scheme "Coinly" \
|
||||
-sdk iphonesimulator \
|
||||
-destination "platform=iOS Simulator,name=iPhone 14,OS=16.4" \
|
||||
ONLY_ACTIVE_ARCH=YES \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
|
|
|
|||
|
|
@ -6,8 +6,25 @@
|
|||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
F762F5752D7506E700D76FFE /* AccountTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F762F5742D7506E700D76FFE /* AccountTests.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
F70CDCCB2D7502EB00FF9D53 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = F70CDC3D2D74D15500FF9D53 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = F70CDC442D74D15500FF9D53;
|
||||
remoteInfo = Coinly;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
F70CDC452D74D15500FF9D53 /* Coinly.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Coinly.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F70CDCC72D7502EB00FF9D53 /* CoinlyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoinlyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F762F5742D7506E700D76FFE /* AccountTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTests.swift; sourceTree = "<group>"; };
|
||||
F79B1D252D75096900B5F35C /* CoinlyTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CoinlyTests.xctestplan; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
|
|
@ -26,12 +43,21 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F70CDCC42D7502EB00FF9D53 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
F70CDC3C2D74D15500FF9D53 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F79B1D252D75096900B5F35C /* CoinlyTests.xctestplan */,
|
||||
F762F5732D75067400D76FFE /* CoinlyTests */,
|
||||
F70CDC472D74D15500FF9D53 /* Coinly */,
|
||||
F70CDC462D74D15500FF9D53 /* Products */,
|
||||
);
|
||||
|
|
@ -41,10 +67,19 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
F70CDC452D74D15500FF9D53 /* Coinly.app */,
|
||||
F70CDCC72D7502EB00FF9D53 /* CoinlyTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F762F5732D75067400D76FFE /* CoinlyTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F762F5742D7506E700D76FFE /* AccountTests.swift */,
|
||||
);
|
||||
path = CoinlyTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
|
@ -70,6 +105,26 @@
|
|||
productReference = F70CDC452D74D15500FF9D53 /* Coinly.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
F70CDCC62D7502EB00FF9D53 /* CoinlyTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = F70CDCCD2D7502EC00FF9D53 /* Build configuration list for PBXNativeTarget "CoinlyTests" */;
|
||||
buildPhases = (
|
||||
F70CDCC32D7502EB00FF9D53 /* Sources */,
|
||||
F70CDCC42D7502EB00FF9D53 /* Frameworks */,
|
||||
F70CDCC52D7502EB00FF9D53 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
F70CDCCC2D7502EB00FF9D53 /* PBXTargetDependency */,
|
||||
);
|
||||
name = CoinlyTests;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = CoinlyTests;
|
||||
productReference = F70CDCC72D7502EB00FF9D53 /* CoinlyTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
|
|
@ -83,6 +138,11 @@
|
|||
F70CDC442D74D15500FF9D53 = {
|
||||
CreatedOnToolsVersion = 16.2;
|
||||
};
|
||||
F70CDCC62D7502EB00FF9D53 = {
|
||||
CreatedOnToolsVersion = 16.2;
|
||||
LastSwiftMigration = 1620;
|
||||
TestTargetID = F70CDC442D74D15500FF9D53;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = F70CDC402D74D15500FF9D53 /* Build configuration list for PBXProject "Coinly" */;
|
||||
|
|
@ -100,6 +160,7 @@
|
|||
projectRoot = "";
|
||||
targets = (
|
||||
F70CDC442D74D15500FF9D53 /* Coinly */,
|
||||
F70CDCC62D7502EB00FF9D53 /* CoinlyTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
|
@ -112,6 +173,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F70CDCC52D7502EB00FF9D53 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
|
|
@ -122,8 +190,24 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F70CDCC32D7502EB00FF9D53 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F762F5752D7506E700D76FFE /* AccountTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
F70CDCCC2D7502EB00FF9D53 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = F70CDC442D74D15500FF9D53 /* Coinly */;
|
||||
targetProxy = F70CDCCB2D7502EB00FF9D53 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
F70CDC512D74D15600FF9D53 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
|
|
@ -302,6 +386,45 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
F70CDCCE2D7502EC00FF9D53 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = DQDRL8F7U2;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.vadymsamoilenko.coinly.CoinlyTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Coinly.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Coinly";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F70CDCCF2D7502EC00FF9D53 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = DQDRL8F7U2;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.vadymsamoilenko.coinly.CoinlyTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Coinly.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Coinly";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
|
|
@ -323,6 +446,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
F70CDCCD2D7502EC00FF9D53 /* Build configuration list for PBXNativeTarget "CoinlyTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F70CDCCE2D7502EC00FF9D53 /* Debug */,
|
||||
F70CDCCF2D7502EC00FF9D53 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = F70CDC3D2D74D15500FF9D53 /* Project object */;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
BlueprintIdentifier = "F70CDC442D74D15500FF9D53"
|
||||
BuildableName = "Coinly.app"
|
||||
BlueprintName = "Coinly"
|
||||
ReferencedContainer = "container:Coinly.xcodeproj">
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
|
|
@ -29,6 +29,19 @@
|
|||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F70CDCC62D7502EB00FF9D53"
|
||||
BuildableName = "CoinlyTests.xctest"
|
||||
BlueprintName = "CoinlyTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
|
|
@ -47,7 +60,7 @@
|
|||
BlueprintIdentifier = "F70CDC442D74D15500FF9D53"
|
||||
BuildableName = "Coinly.app"
|
||||
BlueprintName = "Coinly"
|
||||
ReferencedContainer = "container:Coinly.xcodeproj">
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
|
|
@ -64,7 +77,7 @@
|
|||
BlueprintIdentifier = "F70CDC442D74D15500FF9D53"
|
||||
BuildableName = "Coinly.app"
|
||||
BlueprintName = "Coinly"
|
||||
ReferencedContainer = "container:Coinly.xcodeproj">
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>Coinly.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
79
Coinly.xcodeproj/xcshareddata/xcschemes/CoinlyTests.xcscheme
Normal file
79
Coinly.xcodeproj/xcshareddata/xcschemes/CoinlyTests.xcscheme
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<TestPlans>
|
||||
<TestPlanReference
|
||||
reference = "container:CoinlyTests.xctestplan"
|
||||
default = "YES">
|
||||
</TestPlanReference>
|
||||
</TestPlans>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F70CDCC62D7502EB00FF9D53"
|
||||
BuildableName = "CoinlyTests.xctest"
|
||||
BlueprintName = "CoinlyTests"
|
||||
ReferencedContainer = "container:Coinly.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 = "F70CDC442D74D15500FF9D53"
|
||||
BuildableName = "Coinly.app"
|
||||
BlueprintName = "Coinly"
|
||||
ReferencedContainer = "container:Coinly.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F70CDC442D74D15500FF9D53"
|
||||
BuildableName = "Coinly.app"
|
||||
BlueprintName = "Coinly"
|
||||
ReferencedContainer = "container:Coinly.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
|
|
@ -4,11 +4,19 @@
|
|||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>Coinly.xcscheme_^#shared#^_</key>
|
||||
<key>CoinlyTests.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>F70CDC442D74D15500FF9D53</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -6,26 +6,36 @@
|
|||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct CategoryModel: Identifiable {
|
||||
let id = UUID()
|
||||
let name: String
|
||||
let icon: String // SF Symbols name
|
||||
let color: String // Будем хранить как строку
|
||||
}
|
||||
|
||||
extension CategoryModel {
|
||||
let icon: String
|
||||
let colorName: String // переименовали свойство с color на colorName
|
||||
|
||||
static let categories = [
|
||||
CategoryModel(name: "Food", icon: "cart.fill", color: "red"),
|
||||
CategoryModel(name: "Transport", icon: "car.fill", color: "blue"),
|
||||
CategoryModel(name: "Entertainment", icon: "tv.fill", color: "purple"),
|
||||
CategoryModel(name: "Shopping", icon: "bag.fill", color: "orange"),
|
||||
CategoryModel(name: "Salary", icon: "dollarsign.circle.fill", color: "green"),
|
||||
CategoryModel(name: "Other", icon: "square.fill", color: "gray")
|
||||
CategoryModel(name: "Food", icon: "cart.fill", colorName: "systemRed"),
|
||||
CategoryModel(name: "Transport", icon: "car.fill", colorName: "systemBlue"),
|
||||
CategoryModel(name: "Entertainment", icon: "tv.fill", colorName: "systemPurple"),
|
||||
CategoryModel(name: "Shopping", icon: "bag.fill", colorName: "systemOrange"),
|
||||
CategoryModel(name: "Salary", icon: "dollarsign.circle.fill", colorName: "systemGreen"),
|
||||
CategoryModel(name: "Other", icon: "square.fill", colorName: "systemGray")
|
||||
]
|
||||
|
||||
static func category(for name: String) -> CategoryModel {
|
||||
categories.first { $0.name == name } ?? categories.last!
|
||||
}
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
switch self.colorName {
|
||||
case "systemRed": return Color(uiColor: .systemRed)
|
||||
case "systemBlue": return Color(uiColor: .systemBlue)
|
||||
case "systemPurple": return Color(uiColor: .systemPurple)
|
||||
case "systemOrange": return Color(uiColor: .systemOrange)
|
||||
case "systemGreen": return Color(uiColor: .systemGreen)
|
||||
case "systemGray": return Color(uiColor: .systemGray)
|
||||
default: return Color(uiColor: .systemGray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,151 +0,0 @@
|
|||
import XCTest
|
||||
@testable import Coinly
|
||||
|
||||
class AccountTests: XCTestCase {
|
||||
var sut: AccountModel! // system under test
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// Создаем тестовый аккаунт перед каждым тестом
|
||||
sut = AccountModel(
|
||||
name: "Test Account",
|
||||
type: .wallet,
|
||||
currency: .usd,
|
||||
balance: 1000
|
||||
)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
sut = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testAccountCreation() {
|
||||
XCTAssertNotNil(sut)
|
||||
XCTAssertEqual(sut.name, "Test Account")
|
||||
XCTAssertEqual(sut.type, .wallet)
|
||||
XCTAssertEqual(sut.currency, .usd)
|
||||
XCTAssertEqual(sut.balance, 1000)
|
||||
XCTAssertTrue(sut.isActive)
|
||||
}
|
||||
|
||||
func testCreditCardOperations() {
|
||||
// Создаем кредитную карту для теста
|
||||
let creditCard = AccountModel(
|
||||
name: "Test Credit Card",
|
||||
type: .creditCard,
|
||||
currency: .usd,
|
||||
balance: 0,
|
||||
creditLimit: 1000,
|
||||
interestRate: 19.99
|
||||
)
|
||||
|
||||
// Тестируем добавление покупки
|
||||
var card = creditCard
|
||||
XCTAssertTrue(card.addPurchase(500))
|
||||
XCTAssertEqual(card.balance, 500)
|
||||
|
||||
// Тестируем превышение лимита
|
||||
XCTAssertFalse(card.addPurchase(600))
|
||||
XCTAssertEqual(card.balance, 500)
|
||||
|
||||
// Тестируем оплату
|
||||
XCTAssertTrue(card.makePayment(200))
|
||||
XCTAssertEqual(card.balance, 300)
|
||||
}
|
||||
|
||||
func testAvailableCredit() {
|
||||
let creditCard = AccountModel(
|
||||
name: "Test Credit Card",
|
||||
type: .creditCard,
|
||||
currency: .usd,
|
||||
balance: 500,
|
||||
creditLimit: 1000
|
||||
)
|
||||
|
||||
XCTAssertEqual(creditCard.availableCredit, 500)
|
||||
}
|
||||
}
|
||||
|
||||
class TransactionTests: XCTestCase {
|
||||
func testTransactionCreation() {
|
||||
let transaction = TransactionModel(
|
||||
amount: 100,
|
||||
date: Date(),
|
||||
type: .expense,
|
||||
category: "Food",
|
||||
note: "Lunch",
|
||||
originalCurrency: .usd
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.amount, 100)
|
||||
XCTAssertEqual(transaction.type, .expense)
|
||||
XCTAssertEqual(transaction.category, "Food")
|
||||
XCTAssertEqual(transaction.note, "Lunch")
|
||||
XCTAssertEqual(transaction.originalCurrency, .usd)
|
||||
}
|
||||
|
||||
func testCurrencyConversion() {
|
||||
let transaction = TransactionModel(
|
||||
amount: 100,
|
||||
date: Date(),
|
||||
type: .expense,
|
||||
category: "Food",
|
||||
originalCurrency: .usd
|
||||
)
|
||||
|
||||
let settings = AppSettings.shared
|
||||
settings.currency = .eur
|
||||
|
||||
let convertedAmount = transaction.amountInCurrentCurrency()
|
||||
XCTAssertNotEqual(convertedAmount, 100)
|
||||
}
|
||||
}
|
||||
|
||||
class AccountsStoreTests: XCTestCase {
|
||||
var store: AccountsStore!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
store = AccountsStore()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
store = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testAddAccount() {
|
||||
let initialCount = store.accounts.count
|
||||
|
||||
let newAccount = AccountModel(
|
||||
name: "Test Account",
|
||||
type: .wallet,
|
||||
currency: .usd,
|
||||
balance: 1000
|
||||
)
|
||||
|
||||
store.addAccount(newAccount)
|
||||
|
||||
XCTAssertEqual(store.accounts.count, initialCount + 1)
|
||||
XCTAssertEqual(store.accounts.last?.name, "Test Account")
|
||||
}
|
||||
|
||||
func testDeleteAccount() {
|
||||
let account = AccountModel(
|
||||
name: "Test Account",
|
||||
type: .wallet,
|
||||
currency: .usd,
|
||||
balance: 1000
|
||||
)
|
||||
|
||||
store.addAccount(account)
|
||||
let initialCount = store.accounts.count
|
||||
|
||||
if let index = store.accounts.firstIndex(where: { $0.id == account.id }) {
|
||||
store.deleteAccount(at: IndexSet([index]))
|
||||
}
|
||||
|
||||
XCTAssertEqual(store.accounts.count, initialCount - 1)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
import XCTest
|
||||
|
||||
class CoinlyUITests: XCTestCase {
|
||||
var app: XCUIApplication!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
continueAfterFailure = false
|
||||
app = XCUIApplication()
|
||||
app.launch()
|
||||
}
|
||||
|
||||
func testAddNewTransaction() {
|
||||
// Нажимаем на вкладку Transactions
|
||||
app.tabBars.buttons["Transactions"].tap()
|
||||
|
||||
// Нажимаем кнопку добавления
|
||||
app.navigationBars.buttons["Add"].tap()
|
||||
|
||||
// Вводим сумму
|
||||
let amountField = app.textFields["Amount"]
|
||||
amountField.tap()
|
||||
amountField.typeText("100")
|
||||
|
||||
// Выбираем тип транзакции
|
||||
app.segmentedControls.buttons["Expense"].tap()
|
||||
|
||||
// Выбираем категорию
|
||||
app.buttons["Category"].tap()
|
||||
app.buttons["Food"].tap()
|
||||
|
||||
// Сохраняем транзакцию
|
||||
app.navigationBars.buttons["Add"].tap()
|
||||
|
||||
// Проверяем, что транзакция появилась в списке
|
||||
XCTAssertTrue(app.staticTexts["$100.00"].exists)
|
||||
}
|
||||
|
||||
func testAddNewAccount() {
|
||||
// Нажимаем на вкладку Dashboard
|
||||
app.tabBars.buttons["Dashboard"].tap()
|
||||
|
||||
// Открываем список счетов
|
||||
app.buttons["Show All Accounts"].tap()
|
||||
|
||||
// Нажимаем кнопку добавления
|
||||
app.navigationBars.buttons["Add"].tap()
|
||||
|
||||
// Выбираем тип счета
|
||||
app.buttons["Wallet"].tap()
|
||||
|
||||
// Заполняем данные счета
|
||||
let nameField = app.textFields["Account Name"]
|
||||
nameField.tap()
|
||||
nameField.typeText("Test Wallet")
|
||||
|
||||
let balanceField = app.textFields["Balance"]
|
||||
balanceField.tap()
|
||||
balanceField.typeText("1000")
|
||||
|
||||
// Сохраняем счет
|
||||
app.navigationBars.buttons["Add"].tap()
|
||||
|
||||
// Проверяем, что счет появился в списке
|
||||
XCTAssertTrue(app.staticTexts["Test Wallet"].exists)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,3 @@
|
|||
//
|
||||
// PieChartView.swift
|
||||
// Coinly
|
||||
//
|
||||
// Created by Vadym Samoilenko on 02/03/2025.
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PieChartView: View {
|
||||
|
|
@ -19,16 +11,18 @@ struct PieChartView: View {
|
|||
let slices: [PieSlice]
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
ForEach(slices) { slice in
|
||||
PieSliceView(
|
||||
startAngle: startAngle(for: slice),
|
||||
endAngle: endAngle(for: slice),
|
||||
color: colorFor(category: slice.category)
|
||||
)
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
ForEach(slices) { slice in
|
||||
PieSliceShape(
|
||||
startAngle: startAngle(for: slice),
|
||||
endAngle: endAngle(for: slice)
|
||||
)
|
||||
.fill(slice.category.color)
|
||||
}
|
||||
}
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
}
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
}
|
||||
|
||||
private func startAngle(for slice: PieSlice) -> Double {
|
||||
|
|
@ -42,39 +36,46 @@ struct PieChartView: View {
|
|||
private func endAngle(for slice: PieSlice) -> Double {
|
||||
startAngle(for: slice) + (slice.percentage * 360)
|
||||
}
|
||||
}
|
||||
|
||||
struct PieSliceShape: Shape {
|
||||
let startAngle: Double
|
||||
let endAngle: Double
|
||||
|
||||
private func colorFor(category: CategoryModel) -> Color {
|
||||
switch category.color {
|
||||
case "red": return .red
|
||||
case "blue": return .blue
|
||||
case "purple": return .purple
|
||||
case "orange": return .orange
|
||||
case "green": return .green
|
||||
case "gray": return .gray
|
||||
default: return .gray
|
||||
}
|
||||
func path(in rect: CGRect) -> Path {
|
||||
let center = CGPoint(x: rect.midX, y: rect.midY)
|
||||
let radius = min(rect.width, rect.height) / 2
|
||||
var path = Path()
|
||||
|
||||
path.move(to: center)
|
||||
path.addArc(
|
||||
center: center,
|
||||
radius: radius,
|
||||
startAngle: .degrees(startAngle),
|
||||
endAngle: .degrees(endAngle),
|
||||
clockwise: false
|
||||
)
|
||||
path.closeSubpath()
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
struct PieSliceView: View {
|
||||
let startAngle: Double
|
||||
let endAngle: Double
|
||||
let color: Color
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
Path { path in
|
||||
let center = CGPoint(x: geometry.size.width/2, y: geometry.size.height/2)
|
||||
let radius = min(geometry.size.width, geometry.size.height)/2
|
||||
path.move(to: center)
|
||||
path.addArc(center: center,
|
||||
radius: radius,
|
||||
startAngle: .degrees(startAngle),
|
||||
endAngle: .degrees(endAngle),
|
||||
clockwise: false)
|
||||
path.closeSubpath()
|
||||
}
|
||||
.fill(color)
|
||||
}
|
||||
struct PieChartView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PieChartView(slices: [
|
||||
PieChartView.PieSlice(
|
||||
category: CategoryModel.categories[0],
|
||||
amount: 100,
|
||||
percentage: 0.4
|
||||
),
|
||||
PieChartView.PieSlice(
|
||||
category: CategoryModel.categories[1],
|
||||
amount: 150,
|
||||
percentage: 0.6
|
||||
)
|
||||
])
|
||||
.frame(height: 200)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,118 +8,73 @@ struct TransactionRowView: View {
|
|||
CategoryModel.category(for: transaction.category)
|
||||
}
|
||||
|
||||
private var categoryColor: Color {
|
||||
switch category.color {
|
||||
case "red": return Color(uiColor: .systemRed)
|
||||
case "blue": return Color(uiColor: .systemBlue)
|
||||
case "purple": return Color(uiColor: .systemPurple)
|
||||
case "orange": return Color(uiColor: .systemOrange)
|
||||
case "green": return Color(uiColor: .systemGreen)
|
||||
case "gray": return Color(uiColor: .systemGray)
|
||||
default: return Color(uiColor: .systemGray)
|
||||
}
|
||||
}
|
||||
|
||||
let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "d MMM yyyy"
|
||||
return formatter
|
||||
}()
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: AppStyle.paddingMedium) {
|
||||
HStack(spacing: 12) {
|
||||
// Category Icon
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(categoryColor)
|
||||
.frame(width: 36, height: 36)
|
||||
.fill(category.color.opacity(0.1))
|
||||
.frame(width: 44, height: 44)
|
||||
|
||||
Image(systemName: category.icon)
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(category.color)
|
||||
}
|
||||
|
||||
// Transaction Details
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack {
|
||||
Text(transaction.category)
|
||||
.font(.system(size: 17, weight: .semibold))
|
||||
.foregroundColor(AppStyle.labelPrimary)
|
||||
|
||||
if transaction.isExpense {
|
||||
Text("Expense")
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(Color(uiColor: .systemRed))
|
||||
}
|
||||
Text(transaction.category)
|
||||
.font(.headline)
|
||||
if let note = transaction.note {
|
||||
Text(note)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color(uiColor: .secondaryLabel))
|
||||
}
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Text(dateFormatter.string(from: transaction.date))
|
||||
|
||||
if let note = transaction.note {
|
||||
Text("•")
|
||||
Text(note)
|
||||
}
|
||||
}
|
||||
.font(.system(size: 15))
|
||||
.foregroundColor(Color(uiColor: .systemGray))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
// Amount
|
||||
// Amount and Date
|
||||
VStack(alignment: .trailing, spacing: 4) {
|
||||
Text(transaction.amountInCurrentCurrency().formatAsCurrency())
|
||||
.font(.system(size: 17, weight: .semibold))
|
||||
.font(.headline)
|
||||
.foregroundColor(transaction.isExpense ?
|
||||
Color(uiColor: .systemRed) :
|
||||
Color(uiColor: .systemGreen))
|
||||
|
||||
Text(transaction.originalCurrency.rawValue)
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(Color(uiColor: .systemGray))
|
||||
Text(transaction.date, style: .date)
|
||||
.font(.caption)
|
||||
.foregroundColor(Color(uiColor: .secondaryLabel))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, AppStyle.paddingMedium)
|
||||
.padding(.vertical, 12)
|
||||
.background(Color(uiColor: .secondarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
}
|
||||
|
||||
struct TransactionRowView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack(spacing: 8) {
|
||||
Group {
|
||||
TransactionRowView(transaction: TransactionModel(
|
||||
amount: 100.00,
|
||||
date: Date(),
|
||||
type: .income,
|
||||
category: "Salary",
|
||||
note: "Monthly salary",
|
||||
originalCurrency: .usd
|
||||
))
|
||||
|
||||
TransactionRowView(transaction: TransactionModel(
|
||||
amount: 25.99,
|
||||
amount: 42.50,
|
||||
date: Date(),
|
||||
type: .expense,
|
||||
category: "Food",
|
||||
note: "Lunch",
|
||||
note: "Lunch at work",
|
||||
originalCurrency: .usd
|
||||
))
|
||||
.preferredColorScheme(.light)
|
||||
|
||||
TransactionRowView(transaction: TransactionModel(
|
||||
amount: 50.00,
|
||||
amount: 1200,
|
||||
date: Date(),
|
||||
type: .expense,
|
||||
category: "Transport",
|
||||
note: "Fuel",
|
||||
originalCurrency: .usd
|
||||
type: .income,
|
||||
category: "Salary",
|
||||
note: "Monthly payment",
|
||||
originalCurrency: .eur
|
||||
))
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
.previewLayout(.sizeThatFits)
|
||||
.padding()
|
||||
.background(Color(uiColor: .systemBackground))
|
||||
.environmentObject(AppSettings.shared)
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
CoinlyTests.xctestplan
Normal file
25
CoinlyTests.xctestplan
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"configurations" : [
|
||||
{
|
||||
"id" : "06F004A5-B20A-4C00-A918-8D468952AE86",
|
||||
"name" : "Test Scheme Action",
|
||||
"options" : {
|
||||
|
||||
}
|
||||
}
|
||||
],
|
||||
"defaultOptions" : {
|
||||
|
||||
},
|
||||
"testTargets" : [
|
||||
{
|
||||
"parallelizable" : true,
|
||||
"target" : {
|
||||
"containerPath" : "container:Coinly.xcodeproj",
|
||||
"identifier" : "F70CDCC62D7502EB00FF9D53",
|
||||
"name" : "CoinlyTests"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 1
|
||||
}
|
||||
83
CoinlyTests/AccountTests.swift
Normal file
83
CoinlyTests/AccountTests.swift
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import XCTest
|
||||
@testable import Coinly
|
||||
|
||||
final class AccountTests: XCTestCase {
|
||||
var sut: AccountModel!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
sut = AccountModel(
|
||||
name: "Test Account",
|
||||
type: .wallet,
|
||||
currency: .usd,
|
||||
balance: 1000
|
||||
)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
sut = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testAccountCreation() {
|
||||
XCTAssertNotNil(sut)
|
||||
XCTAssertEqual(sut.name, "Test Account")
|
||||
XCTAssertEqual(sut.type, .wallet)
|
||||
XCTAssertEqual(sut.currency, .usd)
|
||||
XCTAssertEqual(sut.balance, 1000)
|
||||
XCTAssertTrue(sut.isActive)
|
||||
}
|
||||
|
||||
func testCreditCardOperations() {
|
||||
// Создаем кредитную карту для теста
|
||||
let creditCard = AccountModel(
|
||||
name: "Test Credit Card",
|
||||
type: .creditCard,
|
||||
currency: .usd,
|
||||
balance: 0,
|
||||
creditLimit: 1000,
|
||||
interestRate: 19.99
|
||||
)
|
||||
|
||||
// Тестируем добавление покупки
|
||||
var card = creditCard
|
||||
XCTAssertTrue(card.addPurchase(500))
|
||||
XCTAssertEqual(card.balance, 500)
|
||||
|
||||
// Тестируем превышение лимита
|
||||
XCTAssertFalse(card.addPurchase(600))
|
||||
XCTAssertEqual(card.balance, 500)
|
||||
|
||||
// Тестируем оплату
|
||||
XCTAssertTrue(card.makePayment(200))
|
||||
XCTAssertEqual(card.balance, 300)
|
||||
}
|
||||
|
||||
func testAvailableCredit() {
|
||||
let creditCard = AccountModel(
|
||||
name: "Test Credit Card",
|
||||
type: .creditCard,
|
||||
currency: .usd,
|
||||
balance: 500,
|
||||
creditLimit: 1000
|
||||
)
|
||||
|
||||
XCTAssertEqual(creditCard.availableCredit, 500)
|
||||
}
|
||||
|
||||
func testCurrencyConversion() {
|
||||
let account = AccountModel(
|
||||
name: "EUR Account",
|
||||
type: .wallet,
|
||||
currency: .eur,
|
||||
balance: 100
|
||||
)
|
||||
|
||||
let settings = AppSettings.shared
|
||||
settings.currency = .usd
|
||||
|
||||
// Используем форматированную строку для сравнения
|
||||
let expectedAmount = account.balance.formatAsCurrency()
|
||||
XCTAssertEqual(account.formattedBalance, expectedAmount)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue