diff --git a/.gitignore b/.gitignore index b7417d21..1a7bd576 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ xcuserdata/ ## Compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout +*.xcscheme ## Compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ diff --git a/Scribe.xcodeproj/project.pbxproj b/Scribe.xcodeproj/project.pbxproj index 5bdf4186..7160bbb5 100644 --- a/Scribe.xcodeproj/project.pbxproj +++ b/Scribe.xcodeproj/project.pbxproj @@ -211,6 +211,7 @@ D12EB9C42C81C10900181765 /* HEInterfaceVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12EB9B92C81C0E700181765 /* HEInterfaceVariables.swift */; }; D12EB9C52C81C10900181765 /* HEInterfaceVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12EB9B92C81C0E700181765 /* HEInterfaceVariables.swift */; }; D1362A39274C106A00C00E48 /* ColorVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = D190B240274056D400705659 /* ColorVariables.swift */; }; + D13E0DC92C86530E007F00AF /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13E0DC82C86530E007F00AF /* TestExtensions.swift */; }; D15E297F29E41B3B006B2C81 /* FRLanguageData.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = D15E297E29E41B3B006B2C81 /* FRLanguageData.sqlite */; }; D15E298229E41B56006B2C81 /* DELanguageData.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = D15E298129E41B56006B2C81 /* DELanguageData.sqlite */; }; D15E298429E41B74006B2C81 /* ITLanguageData.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = D15E298329E41B74006B2C81 /* ITLanguageData.sqlite */; }; @@ -800,6 +801,13 @@ remoteGlobalIDString = D109A219275B68B3005E2271; remoteInfo = Portuguese; }; + D13E0DCA2C86530E007F00AF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 38BD212822D5907E00C6795D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 38BD212F22D5907E00C6795D; + remoteInfo = Scribe; + }; D160866A270B6D3C00134D48 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 38BD212822D5907E00C6795D /* Project object */; @@ -933,6 +941,8 @@ D111E9B927AFE7B200746F92 /* Annotate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Annotate.swift; sourceTree = ""; }; D12EB9B92C81C0E700181765 /* HEInterfaceVariables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HEInterfaceVariables.swift; sourceTree = ""; }; D1362A37274C040F00C00E48 /* PRIVACY.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = PRIVACY.txt; sourceTree = ""; }; + D13E0DC62C86530E007F00AF /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D13E0DC82C86530E007F00AF /* TestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestExtensions.swift; sourceTree = ""; }; D155AD282BDC6CC20075B18C /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; D15E297E29E41B3B006B2C81 /* FRLanguageData.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; name = FRLanguageData.sqlite; path = Keyboards/LanguageKeyboards/French/FRLanguageData.sqlite; sourceTree = SOURCE_ROOT; }; D15E298129E41B56006B2C81 /* DELanguageData.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = DELanguageData.sqlite; sourceTree = ""; }; @@ -1061,6 +1071,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D13E0DC32C86530E007F00AF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; D1608662270B6D3C00134D48 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1224,6 +1241,7 @@ D1362A37274C040F00C00E48 /* PRIVACY.txt */, D190B29027426F4900705659 /* README.md */, D155AD282BDC6CC20075B18C /* .swiftlint.yml */, + D13E0DC72C86530E007F00AF /* Tests */, 38BD213122D5907E00C6795D /* Products */, D1A3EEED28F8D3EA00896C65 /* Frameworks */, ); @@ -1246,6 +1264,7 @@ D1AFDF3D29CA66D00033BF27 /* English.appex */, D1AFDFBA29CA66F40033BF27 /* Danish.appex */, D1AFE01029CA6E900033BF27 /* Hebrew.appex */, + D13E0DC62C86530E007F00AF /* Tests.xctest */, ); name = Products; sourceTree = ""; @@ -1336,6 +1355,30 @@ path = Portuguese; sourceTree = ""; }; + D13E0DC72C86530E007F00AF /* Tests */ = { + isa = PBXGroup; + children = ( + D13E0DD02C86531C007F00AF /* Keyboards */, + ); + path = Tests; + sourceTree = ""; + }; + D13E0DD02C86531C007F00AF /* Keyboards */ = { + isa = PBXGroup; + children = ( + D13E0DD12C865323007F00AF /* KeyboardsBase */, + ); + path = Keyboards; + sourceTree = ""; + }; + D13E0DD12C865323007F00AF /* KeyboardsBase */ = { + isa = PBXGroup; + children = ( + D13E0DC82C86530E007F00AF /* TestExtensions.swift */, + ); + path = KeyboardsBase; + sourceTree = ""; + }; D1608666270B6D3C00134D48 /* Spanish */ = { isa = PBXGroup; children = ( @@ -1667,6 +1710,24 @@ productReference = D109A21A275B68B3005E2271 /* Portuguese.appex */; productType = "com.apple.product-type.app-extension"; }; + D13E0DC52C86530E007F00AF /* Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D13E0DCC2C86530E007F00AF /* Build configuration list for PBXNativeTarget "Tests" */; + buildPhases = ( + D13E0DC22C86530E007F00AF /* Sources */, + D13E0DC32C86530E007F00AF /* Frameworks */, + D13E0DC42C86530E007F00AF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D13E0DCB2C86530E007F00AF /* PBXTargetDependency */, + ); + name = Tests; + productName = Tests; + productReference = D13E0DC62C86530E007F00AF /* Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; D1608664270B6D3C00134D48 /* Spanish */ = { isa = PBXNativeTarget; buildConfigurationList = D160866D270B6D3C00134D48 /* Build configuration list for PBXNativeTarget "Spanish" */; @@ -1842,7 +1903,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1420; + LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1530; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -1858,6 +1919,10 @@ D109A219275B68B3005E2271 = { CreatedOnToolsVersion = 13.1; }; + D13E0DC52C86530E007F00AF = { + CreatedOnToolsVersion = 15.4; + TestTargetID = 38BD212F22D5907E00C6795D; + }; D1608664270B6D3C00134D48 = { CreatedOnToolsVersion = 13.0; }; @@ -1905,6 +1970,7 @@ D1671A5F275A1E8700A7C118 /* Russian */, D1608664270B6D3C00134D48 /* Spanish */, D18EA8982760D4A6001E1358 /* Swedish */, + D13E0DC52C86530E007F00AF /* Tests */, ); }; /* End PBXProject section */ @@ -1957,6 +2023,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D13E0DC42C86530E007F00AF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; D1608663270B6D3C00134D48 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -2336,6 +2409,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D13E0DC22C86530E007F00AF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D13E0DC92C86530E007F00AF /* TestExtensions.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D1608661270B6D3C00134D48 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2855,6 +2936,11 @@ target = D109A219275B68B3005E2271 /* Portuguese */; targetProxy = D109A21F275B68B3005E2271 /* PBXContainerItemProxy */; }; + D13E0DCB2C86530E007F00AF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 38BD212F22D5907E00C6795D /* Scribe */; + targetProxy = D13E0DCA2C86530E007F00AF /* PBXContainerItemProxy */; + }; D160866B270B6D3C00134D48 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D1608664270B6D3C00134D48 /* Spanish */; @@ -3279,6 +3365,51 @@ }; name = Release; }; + D13E0DCD2C86530E007F00AF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ATJ9U3WZ27; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = be.scri.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Scribe.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Scribe"; + }; + name = Debug; + }; + D13E0DCE2C86530E007F00AF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ATJ9U3WZ27; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = be.scri.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Scribe.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Scribe"; + }; + name = Release; + }; D160866E270B6D3C00134D48 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3793,6 +3924,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D13E0DCC2C86530E007F00AF /* Build configuration list for PBXNativeTarget "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D13E0DCD2C86530E007F00AF /* Debug */, + D13E0DCE2C86530E007F00AF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; D160866D270B6D3C00134D48 /* Build configuration list for PBXNativeTarget "Spanish" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Tests/Keyboards/KeyboardsBase/TestExtensions.swift b/Tests/Keyboards/KeyboardsBase/TestExtensions.swift new file mode 100644 index 00000000..f90c9cfb --- /dev/null +++ b/Tests/Keyboards/KeyboardsBase/TestExtensions.swift @@ -0,0 +1,244 @@ +/** + * Tests for class extensions used in Scribe keyboards. + * + * Copyright (C) 2024 Scribe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import Foundation +@testable import Scribe +import XCTest + +// MARK: secondToLast + +class ExtensionTest: XCTestCase { + func testSecondToLastNotNil() { + let array = [1, 2, 3, 4, 5] + + let result = array.secondToLast()! + + XCTAssertEqual(result, 4) + } + + func testSecondToLastNil() { + let array = [String]() + + let result = array.secondToLast() + + XCTAssertEqual(result, nil) + } +} + +// MARK: unique + +extension ExtensionTest { + func testUniqueElements() { + let array = [1, 2, 3, 4, 5] + let expectedResult = [1, 2, 3, 4, 5] + + let result = array.unique() + + XCTAssertEqual(result, expectedResult) + } + + func testUniqueElementsWithDuplicates() { + let array = [1, 2, 2, 4, 5] + let expectedResult = [1, 2, 4, 5] + + let result = array.unique() + + XCTAssertEqual(result, expectedResult) + } +} + +// MARK: index + +extension ExtensionTest { + func testIndexValidIndex() { + let string = "Hello, World!" + let index = 5 + + let result = string.index(fromIdx: index) + + XCTAssertEqual(result, string.index(string.startIndex, offsetBy: index)) + } +} + +// MARK: substring + +extension ExtensionTest { + func testSubstringFromIndexCorrectStringValidIndex() { + let string = "Hello, World!" + let index = 7 + + let result = string.substring(fromIdx: index) + + XCTAssertEqual(result, "World!") + } + + func testSubstringToIndexCorrectStringValidIndex() { + let string = "Hello, World!" + let index = 5 + + let result = string.substring(toIdx: index) + + XCTAssertEqual(result, "Hello") + } + + func testSubstringRangeCorrectStringValidRange() { + let string = "Hello, World!" + let range = Range(1 ... 4) + + let result = string.substring(with: range) + + XCTAssertEqual(result, "ello") + } +} + +// MARK: insertPriorToCursor + +extension ExtensionTest { + func testInsertPriorToCursor() { + let string = "Hello │" + let char = "Scribe" + let expectedResult = "Hello Scribe│" + + let result = string.insertPriorToCursor(char: char) + + XCTAssertEqual(result, expectedResult) + } +} + +// MARK: deletePriorToCursor + +extension ExtensionTest { + func testDeletePriorToCursor() { + let string = "Hello│" + let expectedResult = "Hell│" + + let result = string.deletePriorToCursor() + + XCTAssertEqual(result, expectedResult) + } +} + +// MARK: isLowercase + +extension ExtensionTest { + func testIsLowercase() { + XCTAssertEqual("hello".isLowercase, true) + XCTAssertEqual("HELLO".isLowercase, false) + XCTAssertEqual("Hello".isLowercase, false) + XCTAssertEqual("👋hello".isLowercase, true) + } +} + +// MARK: isUppercase + +extension ExtensionTest { + func testIsUppercase() { + XCTAssertEqual("HELLO".isUppercase, true) + XCTAssertEqual("Hello".isUppercase, false) + XCTAssertEqual("hello".isUppercase, false) + XCTAssertEqual("👋HELLO".isUppercase, true) + } +} + +// MARK: isCapitalized + +extension ExtensionTest { + func testIsCapitalized() { + XCTAssertEqual("Hello".isCapitalized, true) + XCTAssertEqual("hello".isCapitalized, false) + XCTAssertEqual("HELLO".isCapitalized, false) + XCTAssertEqual("👋HELLO".isCapitalized, false) + } +} + +// MARK: count + +extension ExtensionTest { + func testCount() { + XCTAssertEqual("Hello, World!".count(of: "!"), 1) + XCTAssertEqual("Hello, World!".count(of: "@"), 0) + XCTAssertEqual("Hello, World!".count(of: "l"), 3) + XCTAssertEqual("".count(of: "!"), 0) + XCTAssertEqual("👋".count(of: "👋"), 1) + } +} + +// MARK: capitalize + +extension ExtensionTest { + func testCapitalize() { + XCTAssertEqual("hello".capitalize(), "Hello") + XCTAssertEqual("HELLO".capitalize(), "Hello") + XCTAssertEqual("hELLO".capitalize(), "Hello") + XCTAssertEqual("".capitalize(), "") + XCTAssertEqual("👋hello".capitalize(), "👋hello") + } +} + +// MARK: isNumeric + +extension ExtensionTest { + func testIsNumberic() { + XCTAssertEqual("123".isNumeric, true) + XCTAssertEqual("0123".isNumeric, true) + XCTAssertEqual("hello".isNumeric, false) + XCTAssertEqual("👋".isNumeric, false) + } +} + +// MARK: trailingSpacesTrimmed + +extension ExtensionTest { + func testTrailingSpacesTrimmed() { + XCTAssertEqual("".trailingSpacesTrimmed, "") + XCTAssertEqual("Hello ".trailingSpacesTrimmed, "Hello") + XCTAssertEqual("Hello".trailingSpacesTrimmed, "Hello") + } +} + +// MARK: setColorForText + +extension ExtensionTest { + func testSetColorForExistingText() { + let string = "Hello, World!" + let attributedString = NSMutableAttributedString(string: string) + let textForAttribute = "World" + let color = UIColor.scribeBlue + + attributedString.setColorForText(textForAttribute: textForAttribute, withColor: color) + + let range = (attributedString.string as NSString).range(of: textForAttribute, options: .caseInsensitive) + + attributedString.enumerateAttribute(.foregroundColor, in: range, options: []) { value, _, _ in + XCTAssertEqual(value as! UIColor, color) + } + } + + func testSetColorForNonExistingText() { + let string = "Hello, World!" + let attributedString = NSMutableAttributedString(string: string) + let textForAttribute = "Universe" + let color = UIColor.red + + attributedString.setColorForText(textForAttribute: textForAttribute, withColor: color) + + let range = (attributedString.string as NSString).range(of: textForAttribute, options: .caseInsensitive) + XCTAssertEqual(range.location, NSNotFound) + } +}