From d1028a6b71803ad6f63014aacc0a9b4e1200d41c Mon Sep 17 00:00:00 2001 From: afwolfe <3718652+afwolfe@users.noreply.github.com> Date: Sun, 12 Jun 2022 18:19:05 -0400 Subject: [PATCH 1/4] Remove name from timeDescription when using Family Sharing --- .../Logic/TransactionReaderOCRText.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Sources/CardVision/Logic/TransactionReaderOCRText.swift b/Sources/CardVision/Logic/TransactionReaderOCRText.swift index c3125bd..5dc55e1 100644 --- a/Sources/CardVision/Logic/TransactionReaderOCRText.swift +++ b/Sources/CardVision/Logic/TransactionReaderOCRText.swift @@ -9,6 +9,12 @@ import Foundation typealias TransactionReaderOCRText = [String] +extension String { + func isMatchedBy(regex: String) -> Bool { + return (self.range(of: regex, options: .regularExpression) ?? nil) != nil + } +} + extension TransactionReaderOCRText { func parseTransactions(screenshotDate: Date) -> [Transaction] { var ocrText = self @@ -117,6 +123,15 @@ fileprivate extension Array where Element == String { if timeDescription.contains("hour") && !timeDescription.contains("ago") { timeDescription = timeDescription + " " + (pop() ?? "") } + // Attempt to remove family member's name from description when using Family Sharing. + // ex. "NAME - Yesterday" + if timeDescription.contains(" ") && !timeDescription.isMatchedBy(regex: "^[0-9]"){ + // If string contains spaces and does not start with a number. + timeDescription = timeDescription + .replacingOccurrences(of: "-", with: " ") // Remove the "-", sometimes it isn't picked up by OCR + .split(separator: " ", maxSplits: 1)[1] // Get everything after the first space + .trimmingCharacters(in: .whitespaces) // Trim whitespace + } // Check if declined and pending let declined = Self.isDeclinedTransaction(declinedCandidate: memo) From 7c0981dae675057fe297ecf1c66dfc280224586c Mon Sep 17 00:00:00 2001 From: afwolfe <3718652+afwolfe@users.noreply.github.com> Date: Sun, 12 Jun 2022 19:58:22 -0400 Subject: [PATCH 2/4] Add basic unit tests for Family Sharing timeDescriptions --- Tests/CardVisionTests/TransactionTests.swift | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Tests/CardVisionTests/TransactionTests.swift diff --git a/Tests/CardVisionTests/TransactionTests.swift b/Tests/CardVisionTests/TransactionTests.swift new file mode 100644 index 0000000..ab62072 --- /dev/null +++ b/Tests/CardVisionTests/TransactionTests.swift @@ -0,0 +1,59 @@ +// +// TransactionTests.swift +// +// +// Created by Alex Wolfe on 6/12/22. +// + +import XCTest +@testable import CardVision + +final class TransactionTests: XCTestCase { + var ocrText:[String]! + var date:Date! + override func setUp() { + super.setUp() + // TODO: Mock current date in Swift to make it easier to compare + date = Date() + ocrText = [ + "Grocery Store", "$80.25", "Somewhere, USA", "2%", "15 minutes ago", + "Grocery Store", "$101.99", "Somewhere, USA", "2%", "2 hours ago", + "Convenience Store", "$9.92", "Somewhere, USA", "1%", "Yesterday", + // TODO: Test additional dates after mocking a static date. + ] + } + + func testTransactionDates() { + let transactions = ocrText.parseTransactions(screenshotDate: Date()) + debugPrint(transactions) + XCTAssertEqual(transactions.count, ocrText.count / 5, "Transactions should equal the length of ocrText/5") + // FIXME: Without a mocked date, these tests will break until at least 2 hours after midnight. + assert(Calendar.current.isDate(date, inSameDayAs: transactions[0].date)) + assert(Calendar.current.isDate(date, inSameDayAs: transactions[1].date)) + assert(Calendar.current.isDate(date.date(byAddingDays: -1), inSameDayAs: transactions[2].date)) + // TODO: Test additional dates after mocking a static date. + } + + func testTransactionDatesFamilySharing() { + for i in 0...ocrText.count-1 { // modify ocrText array to prepend "Partner" to timestamps + if (i+1) % 5 == 0 { + if (i+1) % 10 == 0 { ocrText[i] = "Partner " + ocrText[i] } // OCR sometimes doesn't pick up on - + else { ocrText[i] = "Partner - " + ocrText[i] } + debugPrint(ocrText[i]) + } + } + + let transactions = ocrText.parseTransactions(screenshotDate: Date()) + // FIXME: Without a mocked date, these tests will break until at least 2 hours after midnight. + assert(Calendar.current.isDate(date, inSameDayAs: transactions[0].date)) + assert(Calendar.current.isDate(date, inSameDayAs: transactions[1].date)) + assert(Calendar.current.isDate(date.date(byAddingDays: -1), inSameDayAs: transactions[2].date)) + // TODO: Test additional dates after mocking a static date. + } + + static var allTests = [( + "testTransactionDates", testTransactionDates, + "testTransactionDatesFamilySharing", testTransactionDatesFamilySharing + )] +} + From b65f258ca904307242b05be9e5536a501a585be6 Mon Sep 17 00:00:00 2001 From: afwolfe <3718652+afwolfe@users.noreply.github.com> Date: Sat, 18 Jun 2022 13:51:52 -0400 Subject: [PATCH 3/4] Add Date+current and Date+mock extensions, update TransactionReaderOCRTextTests --- .../CardVision/Extensions/Date+current.swift | 21 +++++ .../Logic/TransactionReaderOCRText.swift | 16 ++-- .../CardVision/Models/TransactionImage.swift | 2 +- .../Extensions/Date+mock.swift | 21 +++++ .../TransactionReaderOCRTextTests.swift | 80 +++++++++++++++++++ Tests/CardVisionTests/TransactionTests.swift | 59 -------------- 6 files changed, 134 insertions(+), 65 deletions(-) create mode 100644 Sources/CardVision/Extensions/Date+current.swift create mode 100644 Tests/CardVisionTests/Extensions/Date+mock.swift create mode 100644 Tests/CardVisionTests/TransactionReaderOCRTextTests.swift delete mode 100644 Tests/CardVisionTests/TransactionTests.swift diff --git a/Sources/CardVision/Extensions/Date+current.swift b/Sources/CardVision/Extensions/Date+current.swift new file mode 100644 index 0000000..c2714d3 --- /dev/null +++ b/Sources/CardVision/Extensions/Date+current.swift @@ -0,0 +1,21 @@ +// +// Date+current.swift +// +// Implements a Date.current method to get the current date in a way that can be mocked for testing. +// Adapted from: https://dev.to/ivanmisuno/deterministic-unit-tests-for-current-date-dependent-code-in-swift-2h72 +// Created by Alex Wolfe on 6/18/22. +// + +import Foundation + + +internal var __date_currentImpl = { Date() } + +extension Date { + /// Return current date + /// Please note that use of `Date()` and `Date(timeIntervalSinceNow:)` should not be prohibited + /// through lint rules or commit hooks, always use `Date.current` + static var current: Date { + return __date_currentImpl() + } +} diff --git a/Sources/CardVision/Logic/TransactionReaderOCRText.swift b/Sources/CardVision/Logic/TransactionReaderOCRText.swift index 5dc55e1..0fce04d 100644 --- a/Sources/CardVision/Logic/TransactionReaderOCRText.swift +++ b/Sources/CardVision/Logic/TransactionReaderOCRText.swift @@ -106,7 +106,7 @@ fileprivate extension Array where Element == String { foundMemo = pop() } - guard let memo = foundMemo else { return nil } + guard var memo = foundMemo else { return nil } // Not all transactions have daily cash rewards var dailyCash: String? @@ -126,12 +126,18 @@ fileprivate extension Array where Element == String { // Attempt to remove family member's name from description when using Family Sharing. // ex. "NAME - Yesterday" if timeDescription.contains(" ") && !timeDescription.isMatchedBy(regex: "^[0-9]"){ - // If string contains spaces and does not start with a number. - timeDescription = timeDescription + let splitTimeDescription = timeDescription .replacingOccurrences(of: "-", with: " ") // Remove the "-", sometimes it isn't picked up by OCR - .split(separator: " ", maxSplits: 1)[1] // Get everything after the first space + .split(separator: " ", maxSplits: 1) + // Prepend the family member to the memo. + let familyMember = String(splitTimeDescription[0]) + memo = familyMember + " - " + memo + // If string contains spaces and does not start with a number. + timeDescription = splitTimeDescription[1] // Get everything after the first space .trimmingCharacters(in: .whitespaces) // Trim whitespace } + + // Check if declined and pending let declined = Self.isDeclinedTransaction(declinedCandidate: memo) @@ -281,7 +287,7 @@ extension IntermediateTransaction { return nil } - return baseDate.date(byAddingHours: -minutes) + return baseDate.date(byAddingMinutes: -minutes) } func leadingValue(in string: String, containing: String) -> Int? { diff --git a/Sources/CardVision/Models/TransactionImage.swift b/Sources/CardVision/Models/TransactionImage.swift index 5f10f51..58fbd0f 100644 --- a/Sources/CardVision/Models/TransactionImage.swift +++ b/Sources/CardVision/Models/TransactionImage.swift @@ -31,7 +31,7 @@ public extension TransactionImage { } let urlCreationDate = try? url.resourceValues(forKeys: [.creationDateKey]).creationDate - creationDate = urlCreationDate ?? Date() + creationDate = urlCreationDate ?? Date.current imageURL = url self.image = image diff --git a/Tests/CardVisionTests/Extensions/Date+mock.swift b/Tests/CardVisionTests/Extensions/Date+mock.swift new file mode 100644 index 0000000..ef2f8ea --- /dev/null +++ b/Tests/CardVisionTests/Extensions/Date+mock.swift @@ -0,0 +1,21 @@ +// +// Date+mock.swift +// +// Overrides the current date to the set mockDate. +// Adapted from: https://dev.to/ivanmisuno/deterministic-unit-tests-for-current-date-dependent-code-in-swift-2h72 +// Created by Alex Wolfe on 6/18/22. +// + +import Foundation +@testable import CardVision + +// Mock current date to simplify testing +extension Date { + + // Monday, January 1, 2018 12:00:00 PM UTC + static var mockDate: Date = Date(timeIntervalSinceReferenceDate: 536500800) + + static func overrideCurrentDate(_ currentDate: @autoclosure @escaping () -> Date) { + __date_currentImpl = currentDate + } +} diff --git a/Tests/CardVisionTests/TransactionReaderOCRTextTests.swift b/Tests/CardVisionTests/TransactionReaderOCRTextTests.swift new file mode 100644 index 0000000..ab70396 --- /dev/null +++ b/Tests/CardVisionTests/TransactionReaderOCRTextTests.swift @@ -0,0 +1,80 @@ +// +// TransactionTests.swift +// +// +// Created by Alex Wolfe on 6/12/22. +// + +import XCTest +@testable import CardVision + +final class TransactionReaderOCRTextTests: XCTestCase { + + var ocrText:[String]! + + override func setUp() { + super.setUp() + + // mockDate is Monday, January 1, 2018 12:00:00 PM UTC + Date.overrideCurrentDate(Date.mockDate) + ocrText = [ + "Transaction 1", "$111.11", "Somewhere, USA", "2%", "15 minutes ago", + "Transaction 2", "$222.22", "Somewhere, USA", "2%", "2 hours ago", + "Transaction 3", "$333.33", "Somewhere, USA", "1%", "Monday", // Monday resolves to the current day (Monday, would not be used to describe current day in Apple Wallet. + "Transaction 4", "$444.44", "Somewhere, USA", "1%", "Yesterday", + "Transaction 5", "$555.55", "Somewhere, USA", "1%", "Saturday", + "Transaction 6", "$666.66", "Somewhere, USA", "1%", "Friday", + "Transaction 7", "$777.77", "Somewhere, USA", "1%", "Thursday", + "Transaction 8", "$888.88", "Somewhere, USA", "1%", "Wednesday", + "Transaction 9", "$999.99", "Somewhere, USA", "1%", "Tuesday", + "Transaction 10", "$1000.00", "Somewhere, USA", "1%", "12/25/2017", + ] + + } + + func testTransactionText() { + let transactions = ocrText.parseTransactions(screenshotDate: Date.current) + verifyTransactionDates(transactions: transactions) + + // Verify the memo of each transaction: + let expectedMemo = "Somewhere, USA" + for transaction in transactions { + XCTAssertEqual(expectedMemo, transaction.memo) + } + } + + func testTransactionTextFamilySharing() { + for i in 0...ocrText.count-1 { // modify ocrText array to prepend "Partner" to timestamps + if (i+1) % 5 == 0 { + if (i+1) % 10 == 0 { ocrText[i] = "Partner " + ocrText[i] } // OCR sometimes doesn't pick up on the "-" + else { ocrText[i] = "Partner - " + ocrText[i] } + } + } + let transactions = ocrText.parseTransactions(screenshotDate: Date.current) + verifyTransactionDates(transactions: transactions) + + // Verify the memo of each transaction: + let expectedMemo = "Partner - Somewhere, USA" + for transaction in transactions { + XCTAssertEqual(expectedMemo, transaction.memo) + } + } + + func verifyTransactionDates(transactions: [Transaction]) { + // Helper method to make sure that the transaction dates are correct. + XCTAssertEqual(transactions.count, ocrText.count / 5, "Transactions should equal the length of ocrText/5") + XCTAssertEqual(Date.current.date(byAddingMinutes: -15), transactions[0].date) + XCTAssertEqual(Date.current.date(byAddingHours: -2), transactions[1].date) + XCTAssertEqual(Date.current, transactions[2].date) + for i in 3...transactions.count-1 { + let daysAgo = 2-i + XCTAssert(Calendar.current.isDate(Date.current.date(byAddingDays: daysAgo), inSameDayAs: transactions[i].date)) + } + + } + + static var allTests = [( + "testTransactionText", testTransactionText, + "testTransactionTextFamilySharing", testTransactionTextFamilySharing + )] +} diff --git a/Tests/CardVisionTests/TransactionTests.swift b/Tests/CardVisionTests/TransactionTests.swift deleted file mode 100644 index ab62072..0000000 --- a/Tests/CardVisionTests/TransactionTests.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// TransactionTests.swift -// -// -// Created by Alex Wolfe on 6/12/22. -// - -import XCTest -@testable import CardVision - -final class TransactionTests: XCTestCase { - var ocrText:[String]! - var date:Date! - override func setUp() { - super.setUp() - // TODO: Mock current date in Swift to make it easier to compare - date = Date() - ocrText = [ - "Grocery Store", "$80.25", "Somewhere, USA", "2%", "15 minutes ago", - "Grocery Store", "$101.99", "Somewhere, USA", "2%", "2 hours ago", - "Convenience Store", "$9.92", "Somewhere, USA", "1%", "Yesterday", - // TODO: Test additional dates after mocking a static date. - ] - } - - func testTransactionDates() { - let transactions = ocrText.parseTransactions(screenshotDate: Date()) - debugPrint(transactions) - XCTAssertEqual(transactions.count, ocrText.count / 5, "Transactions should equal the length of ocrText/5") - // FIXME: Without a mocked date, these tests will break until at least 2 hours after midnight. - assert(Calendar.current.isDate(date, inSameDayAs: transactions[0].date)) - assert(Calendar.current.isDate(date, inSameDayAs: transactions[1].date)) - assert(Calendar.current.isDate(date.date(byAddingDays: -1), inSameDayAs: transactions[2].date)) - // TODO: Test additional dates after mocking a static date. - } - - func testTransactionDatesFamilySharing() { - for i in 0...ocrText.count-1 { // modify ocrText array to prepend "Partner" to timestamps - if (i+1) % 5 == 0 { - if (i+1) % 10 == 0 { ocrText[i] = "Partner " + ocrText[i] } // OCR sometimes doesn't pick up on - - else { ocrText[i] = "Partner - " + ocrText[i] } - debugPrint(ocrText[i]) - } - } - - let transactions = ocrText.parseTransactions(screenshotDate: Date()) - // FIXME: Without a mocked date, these tests will break until at least 2 hours after midnight. - assert(Calendar.current.isDate(date, inSameDayAs: transactions[0].date)) - assert(Calendar.current.isDate(date, inSameDayAs: transactions[1].date)) - assert(Calendar.current.isDate(date.date(byAddingDays: -1), inSameDayAs: transactions[2].date)) - // TODO: Test additional dates after mocking a static date. - } - - static var allTests = [( - "testTransactionDates", testTransactionDates, - "testTransactionDatesFamilySharing", testTransactionDatesFamilySharing - )] -} - From f78be733c90f69ea0f7024aeca2d8671d4084cbf Mon Sep 17 00:00:00 2001 From: afwolfe <3718652+afwolfe@users.noreply.github.com> Date: Sun, 26 Jun 2022 19:20:46 -0400 Subject: [PATCH 4/4] More robust handling of transaction OCR text when using Family Sharing --- .../Extensions/String+isMatchedBy.swift | 15 +++++++ .../Logic/TransactionReaderOCRText.swift | 36 +++++++++++------ .../TransactionReaderOCRTextTests.swift | 39 ++++++++++++------- 3 files changed, 63 insertions(+), 27 deletions(-) create mode 100644 Sources/CardVision/Extensions/String+isMatchedBy.swift diff --git a/Sources/CardVision/Extensions/String+isMatchedBy.swift b/Sources/CardVision/Extensions/String+isMatchedBy.swift new file mode 100644 index 0000000..62631fb --- /dev/null +++ b/Sources/CardVision/Extensions/String+isMatchedBy.swift @@ -0,0 +1,15 @@ +// +// String+isMatchedBy.swift +// +// +// Created by Alex Wolfe on 6/26/22. +// + +import Foundation + +extension String { + /// Returns whether the string matches the given regular expression + func isMatchedBy(regex: String) -> Bool { + return (self.range(of: regex, options: .regularExpression) ?? nil) != nil + } +} diff --git a/Sources/CardVision/Logic/TransactionReaderOCRText.swift b/Sources/CardVision/Logic/TransactionReaderOCRText.swift index 0fce04d..2a0a2c0 100644 --- a/Sources/CardVision/Logic/TransactionReaderOCRText.swift +++ b/Sources/CardVision/Logic/TransactionReaderOCRText.swift @@ -9,11 +9,6 @@ import Foundation typealias TransactionReaderOCRText = [String] -extension String { - func isMatchedBy(regex: String) -> Bool { - return (self.range(of: regex, options: .regularExpression) ?? nil) != nil - } -} extension TransactionReaderOCRText { func parseTransactions(screenshotDate: Date) -> [Transaction] { @@ -41,6 +36,7 @@ fileprivate extension Array where Element == String { return top } + /// Determines if the payee and memo combination constitute a Daily Cash transaction. static func isDailyCashTransaction(payee: String, memo: String) -> Bool { // Non-daily cash payee let nonDailyCashPayees = ["Payment", "Daily Cash Adjustment", "Balance Adjustment"] @@ -51,19 +47,31 @@ fileprivate extension Array where Element == String { return !(memo.contains("Refund") || isDeclinedTransaction(declinedCandidate: memo)) } + /// Determines if the given amount string is a valid monetary amount static func isAmount(amountCandidate: String) -> Bool { - amountCandidate.range(of: #"^\+*\$[\d,]*\.\d\d$"#, options: .regularExpression) != nil + amountCandidate.isMatchedBy(regex: #"^\+*\$[\d,]*\.\d\d$"#) } + /// Determines if the given transaction string is "Declined" static func isDeclinedTransaction(declinedCandidate: String) -> Bool { declinedCandidate.contains("Declined") } + /// Determines if the given transaction string is "Pending" static func isPendingTransaction(pendingCandidate: String) -> Bool { pendingCandidate.contains("Pending") } + /// Determines if the given string contains a valid timestamp + static func isTimestamp(timestampCandidate: String) -> Bool { + return (timestampCandidate.isMatchedBy(regex: "[0-9]{1,2} (?:minute|hour)s{0,1} ago") || // relative timestamp + timestampCandidate.isMatchedBy(regex: "\\d{1,2}\\/\\d{1,2}\\/\\d{2}") || // mm/dd/yy date stamp + timestampCandidate.isMatchedBy(regex: "(?i)W*(?:Mon|Tues|Wednes|Thurs|Fri|Satur|Sun|Yester)day\\b[sS]*")) // Day of week, including "Yesterday" + + } + // TODO: Refactor this to a better place + /// Iterates over the ocrText array to process the raw text into an IntermediateTransaction mutating func nextTransaction() -> IntermediateTransaction? { if Self.debug { print(self.debugDescription) @@ -73,7 +81,7 @@ fileprivate extension Array where Element == String { return nil } - // Sometimes payee names get broken into additiona lines + // Sometimes payee names get broken into additional lines // Keep iterating until we find a valid transaction amount var foundAmount: String? while foundAmount == nil { @@ -116,18 +124,22 @@ fileprivate extension Array where Element == String { dailyCash = pop() } while !(dailyCash?.contains { $0 == "%" } ?? true) } - + + // Sometimes "ago" winds up on the next line and separators from Family Sharing mess with the timestamp. + // Keep building the string until it contains a valid time stamp. guard var timeDescription = baTimeDescription ?? pop() else { return nil } - - // sometimes "ago" ends up on the next line - if timeDescription.contains("hour") && !timeDescription.contains("ago") { + while (!Self.isTimestamp(timestampCandidate: timeDescription) && count > 0) { timeDescription = timeDescription + " " + (pop() ?? "") } + timeDescription = timeDescription + .replacingOccurrences(of: "-", with: " ") + .replacingOccurrences(of: "•", with: " ") + // Attempt to remove family member's name from description when using Family Sharing. // ex. "NAME - Yesterday" + // If the description contains spaces and does not start with a number, it likely starts with the family member's name. if timeDescription.contains(" ") && !timeDescription.isMatchedBy(regex: "^[0-9]"){ let splitTimeDescription = timeDescription - .replacingOccurrences(of: "-", with: " ") // Remove the "-", sometimes it isn't picked up by OCR .split(separator: " ", maxSplits: 1) // Prepend the family member to the memo. let familyMember = String(splitTimeDescription[0]) diff --git a/Tests/CardVisionTests/TransactionReaderOCRTextTests.swift b/Tests/CardVisionTests/TransactionReaderOCRTextTests.swift index ab70396..3acb5d8 100644 --- a/Tests/CardVisionTests/TransactionReaderOCRTextTests.swift +++ b/Tests/CardVisionTests/TransactionReaderOCRTextTests.swift @@ -1,5 +1,5 @@ // -// TransactionTests.swift +// TransactionReaderOCRTextTests.swift // // // Created by Alex Wolfe on 6/12/22. @@ -18,9 +18,9 @@ final class TransactionReaderOCRTextTests: XCTestCase { // mockDate is Monday, January 1, 2018 12:00:00 PM UTC Date.overrideCurrentDate(Date.mockDate) ocrText = [ - "Transaction 1", "$111.11", "Somewhere, USA", "2%", "15 minutes ago", + "Transaction 1", "$111.11", "Pending", "Card Number Used", "2%", "15 minutes", "ago", // test extra splitting on "ago" "Transaction 2", "$222.22", "Somewhere, USA", "2%", "2 hours ago", - "Transaction 3", "$333.33", "Somewhere, USA", "1%", "Monday", // Monday resolves to the current day (Monday, would not be used to describe current day in Apple Wallet. + "Transaction 3", "$333.33", "Somewhere, USA", "1%", "Monday", // Monday resolves to the current day in these tests, (However, it would not be used to describe current day in Apple Wallet.) "Transaction 4", "$444.44", "Somewhere, USA", "1%", "Yesterday", "Transaction 5", "$555.55", "Somewhere, USA", "1%", "Saturday", "Transaction 6", "$666.66", "Somewhere, USA", "1%", "Friday", @@ -32,37 +32,46 @@ final class TransactionReaderOCRTextTests: XCTestCase { } + func testTransactionText() { let transactions = ocrText.parseTransactions(screenshotDate: Date.current) verifyTransactionDates(transactions: transactions) // Verify the memo of each transaction: + XCTAssertEqual("Pending", transactions[0].memo) let expectedMemo = "Somewhere, USA" - for transaction in transactions { - XCTAssertEqual(expectedMemo, transaction.memo) + for i in 1...transactions.count-1 { + XCTAssertEqual(expectedMemo, transactions[i].memo) } } + /// Verifies transaction parsing when using family sharing with variations on how the partner name is parsed. func testTransactionTextFamilySharing() { - for i in 0...ocrText.count-1 { // modify ocrText array to prepend "Partner" to timestamps - if (i+1) % 5 == 0 { - if (i+1) % 10 == 0 { ocrText[i] = "Partner " + ocrText[i] } // OCR sometimes doesn't pick up on the "-" - else { ocrText[i] = "Partner - " + ocrText[i] } - } - } + ocrText = [ + "Transaction 1", "$111.11", "Pending", "Card Number Used", "2%", "Partner - 15 minutes", "ago", // test extra splitting on "ago" + "Transaction 2", "$222.22", "Somewhere, USA", "2%", "Partner", "2 hours ago", // Separate partner field + "Transaction 3", "$333.33", "Somewhere, USA", "1%", "Partner - Monday", // with dash + "Transaction 4", "$444.44", "Somewhere, USA", "1%", "Partner • Yesterday", // with dot instead of dash + "Transaction 5", "$555.55", "Somewhere, USA", "1%", "Partner Saturday", // no separator + "Transaction 6", "$666.66", "Somewhere, USA", "1%", "Partner - Friday", + "Transaction 7", "$777.77", "Somewhere, USA", "1%", "-", "Partner Thursday", // extra dash before + "Transaction 8", "$888.88", "Somewhere, USA", "1%", "Partner", "Wednesday", + "Transaction 9", "$999.99", "Somewhere, USA", "1%", "-", "Partner", "Tuesday", //extra dash before and separator + "Transaction 10", "$1000.00", "Somewhere, USA", "1%", "Partner", "12/25/2017" + ] let transactions = ocrText.parseTransactions(screenshotDate: Date.current) verifyTransactionDates(transactions: transactions) // Verify the memo of each transaction: + XCTAssertEqual("Partner - Pending", transactions[0].memo) let expectedMemo = "Partner - Somewhere, USA" - for transaction in transactions { - XCTAssertEqual(expectedMemo, transaction.memo) + for i in 1...transactions.count-1 { + XCTAssertEqual(expectedMemo, transactions[i].memo) } } + /// Helper method to make sure that the default transaction dates are correct. func verifyTransactionDates(transactions: [Transaction]) { - // Helper method to make sure that the transaction dates are correct. - XCTAssertEqual(transactions.count, ocrText.count / 5, "Transactions should equal the length of ocrText/5") XCTAssertEqual(Date.current.date(byAddingMinutes: -15), transactions[0].date) XCTAssertEqual(Date.current.date(byAddingHours: -2), transactions[1].date) XCTAssertEqual(Date.current, transactions[2].date)