diff --git a/EAN13View/Source/EAN13BarcodeGenerator.swift b/EAN13View/Source/EAN13BarcodeGenerator.swift index 47d5fc9..ba93b16 100644 --- a/EAN13View/Source/EAN13BarcodeGenerator.swift +++ b/EAN13View/Source/EAN13BarcodeGenerator.swift @@ -1,18 +1,18 @@ struct EAN13BarcodeGenerator{ - struct DigitEndcoding { + struct DigitEncoding { - enum EndCoding: String { + enum Encoding: String { case l case g case r } let bitValue: String - init?(value: Int, endCoding: EndCoding) { + init?(value: Int, encoding: Encoding) { guard value <= 9 && value >= 0 else { return nil } - let code = DigitEndcoding.rValues[value] - switch endCoding { + let code = DigitEncoding.rValues[value] + switch encoding { case .r: bitValue = String(code, radix: 2) case .g: @@ -50,15 +50,16 @@ struct EAN13BarcodeGenerator{ ] func generate(value: [Int]) -> [Bool]{ - let secondEndcoding = secondCoding[value.first!] - .toEndCoding() + guard let firstDigit = value.first else { return [] } + let secondEncoding = secondCoding[firstDigit] + .toEncoding() let second = zip(value[1...6], 0...5).compactMap{ value, index -> String in - return DigitEndcoding(value: value, endCoding: secondEndcoding[index])!.bitValue + return DigitEncoding(value: value, encoding: secondEncoding[index])?.bitValue ?? "" }.reduce("", { a, b in a + b }) let third = value[7...12].compactMap{ value -> String in - return DigitEndcoding(value: value, endCoding: .r)!.bitValue + return DigitEncoding(value: value, encoding: .r)?.bitValue ?? "" }.reduce("", { a, b in a + b }) diff --git a/EAN13View/Source/EAN13Validator.swift b/EAN13View/Source/EAN13Validator.swift index 91a3cb8..87196e6 100644 --- a/EAN13View/Source/EAN13Validator.swift +++ b/EAN13View/Source/EAN13Validator.swift @@ -3,13 +3,18 @@ struct EAN13Validator{ func isValid(value: [Int]) -> Bool{ guard value.count == 13 else { return false } - let even = value.prefix(12).filter{$0 % 2 == 0}.reduce(0) { a, b in - a + b - } - let odd = value.prefix(12).filter{$0 % 2 == 1}.reduce(0) { a, b in - a + b + var sumOddPosition = 0 + var sumEvenPosition = 0 + + for (index, digit) in value.prefix(12).enumerated() { + if index % 2 == 0 { + sumOddPosition += digit + } else { + sumEvenPosition += digit + } } - let checkDigit = (10 - (even + odd * 3) % 10) % 10 - return value.last! == checkDigit + + let checkDigit = (10 - (sumOddPosition + sumEvenPosition * 3) % 10) % 10 + return value.last == checkDigit } } diff --git a/EAN13View/Source/String+EAN13.swift b/EAN13View/Source/String+EAN13.swift index fae20e7..472471a 100644 --- a/EAN13View/Source/String+EAN13.swift +++ b/EAN13View/Source/String+EAN13.swift @@ -1,9 +1,9 @@ extension String{ - func toEndCoding() -> [EAN13BarcodeGenerator.DigitEndcoding.EndCoding]{ + func toEncoding() -> [EAN13BarcodeGenerator.DigitEncoding.Encoding]{ return self.lowercased().compactMap { EAN13BarcodeGenerator - .DigitEndcoding - .EndCoding(rawValue: String($0)) + .DigitEncoding + .Encoding(rawValue: String($0)) } } } diff --git a/Package.swift b/Package.swift index d7bdfbc..7177b07 100644 --- a/Package.swift +++ b/Package.swift @@ -19,5 +19,9 @@ let package = Package( path: "EAN13View/Source", publicHeadersPath: ".." ), + .testTarget( + name: "EAN13ViewTests", + dependencies: ["EAN13View"] + ), ] ) diff --git a/Tests/EAN13ViewTests/EAN13ViewTests.swift b/Tests/EAN13ViewTests/EAN13ViewTests.swift new file mode 100644 index 0000000..b4228fe --- /dev/null +++ b/Tests/EAN13ViewTests/EAN13ViewTests.swift @@ -0,0 +1,56 @@ +import XCTest +@testable import EAN13View + +final class EAN13ViewTests: XCTestCase { + + func testValidatorValidEAN() { + // Known valid EAN + let validEAN = "5901234123457" + XCTAssertNoThrow(try EAN13(value: validEAN)) + } + + func testValidatorInvalidEAN_WrongCheckDigit() { + // Last digit changed from 7 to 8 + let invalidEAN = "5901234123458" + XCTAssertThrowsError(try EAN13(value: invalidEAN)) { error in + XCTAssertEqual(error as? EAN13.Error, EAN13.Error.invalidEAN) + } + } + + func testValidatorInvalidEAN_SwappedDigits() { + // Swapped first two digits: 9501234123457 + // Previous buggy validator would accept this. + let invalidEAN = "9501234123457" + XCTAssertThrowsError(try EAN13(value: invalidEAN)) { error in + XCTAssertEqual(error as? EAN13.Error, EAN13.Error.invalidEAN) + } + } + + func testGeneratorStructure() { + let validEAN = "5901234123457" + guard let ean = try? EAN13(value: validEAN) else { + XCTFail("Should be valid EAN") + return + } + + // EAN13 total modules should be 95 + XCTAssertEqual(ean.lines.count, 95) + + // Start marker 101 + XCTAssertEqual(ean.lines[0], true) + XCTAssertEqual(ean.lines[1], false) + XCTAssertEqual(ean.lines[2], true) + + // End marker 101 + XCTAssertEqual(ean.lines[92], true) + XCTAssertEqual(ean.lines[93], false) + XCTAssertEqual(ean.lines[94], true) + } + + static var allTests = [ + ("testValidatorValidEAN", testValidatorValidEAN), + ("testValidatorInvalidEAN_WrongCheckDigit", testValidatorInvalidEAN_WrongCheckDigit), + ("testValidatorInvalidEAN_SwappedDigits", testValidatorInvalidEAN_SwappedDigits), + ("testGeneratorStructure", testGeneratorStructure), + ] +} diff --git a/Tests/EAN13ViewTests/XCTestManifests.swift b/Tests/EAN13ViewTests/XCTestManifests.swift new file mode 100644 index 0000000..3e2b9ab --- /dev/null +++ b/Tests/EAN13ViewTests/XCTestManifests.swift @@ -0,0 +1,9 @@ +import XCTest + +#if !canImport(ObjectiveC) +public func allTests() -> [XCTestCaseEntry] { + return [ + testCase(EAN13ViewTests.allTests), + ] +} +#endif diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift new file mode 100644 index 0000000..a20020b --- /dev/null +++ b/Tests/LinuxMain.swift @@ -0,0 +1,7 @@ +import XCTest + +import EAN13ViewTests + +var tests = [XCTestCaseEntry]() +tests += EAN13ViewTests.allTests() +XCTMain(tests)