Skip to content

Commit 6d7ade5

Browse files
authored
[Woo POS][Barcodes] Ignore empty scanner input (#15843)
2 parents ea22858 + 2f53a9d commit 6d7ade5

File tree

2 files changed

+94
-9
lines changed

2 files changed

+94
-9
lines changed

WooCommerce/Classes/POS/Presentation/Barcode Scanning/HIDBarcodeParser.swift

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,51 @@ final class HIDBarcodeParser {
2626
/// Process a key press event
2727
/// - Parameter key: The key that was pressed
2828
func processKeyPress(_ key: UIKey) {
29-
let currentTime = timeProvider.now()
30-
31-
// If characters are entered too slowly, it's probably typing and we should ignore it
32-
if let lastTime = lastKeyPressTime,
33-
currentTime.timeIntervalSince(lastTime) > configuration.maximumInterCharacterTime {
34-
onScan(.failure(HIDBarcodeParserError.timedOut(barcode: buffer)))
35-
resetScan()
29+
guard shouldRecogniseAsScanKeystroke(key) else {
30+
return
3631
}
3732

38-
lastKeyPressTime = currentTime
39-
4033
let character = key.characters
4134
if configuration.terminatingStrings.contains(character) {
4235
processScan()
4336
} else {
4437
guard !excludedKeys.contains(key.keyCode) else { return }
38+
checkForTimeoutBetweenKeystrokes()
4539
buffer.append(character)
4640
}
4741
}
4842

43+
private func shouldRecogniseAsScanKeystroke(_ key: UIKey) -> Bool {
44+
guard key.characters.isNotEmpty else {
45+
// This prevents a double-trigger-pull on a Star scanner from adding an error row –
46+
// Star use this as a shortcut to switch to the software keyboard. They send keycode 174 0xAE, which is
47+
// undefined and reserved in UIKeyboardHIDUsage. The scanner doesn't send a character with the code.
48+
// There seems to be no reason to handle empty input when considering scans.
49+
return false
50+
}
51+
52+
if buffer.isEmpty && configuration.terminatingStrings.contains(key.characters) {
53+
// We prefer to show all partial scans, but if we just get an enter with no numbers, ignoring it makes testing easier
54+
return false
55+
}
56+
57+
return true
58+
}
59+
60+
private func checkForTimeoutBetweenKeystrokes() {
61+
// If characters are entered too slowly, it's probably typing and we should ignore the old input.
62+
// The key we just received is still considered for adding to the buffer – we may simply reset the buffer first.
63+
let currentTime = timeProvider.now()
64+
65+
if let lastTime = lastKeyPressTime,
66+
currentTime.timeIntervalSince(lastTime) > configuration.maximumInterCharacterTime {
67+
onScan(.failure(HIDBarcodeParserError.timedOut(barcode: buffer)))
68+
resetScan()
69+
}
70+
71+
lastKeyPressTime = currentTime
72+
}
73+
4974
private let excludedKeys: [UIKeyboardHIDUsage] = [
5075
.keyboardCapsLock,
5176
.keyboardF1,
@@ -138,6 +163,7 @@ final class HIDBarcodeParser {
138163
}
139164

140165
private func processScan() {
166+
checkForTimeoutBetweenKeystrokes()
141167
if buffer.count >= configuration.minimumBarcodeLength {
142168
onScan(.success(buffer))
143169
} else {

WooCommerce/WooCommerceTests/POS/Presentation/Barcode Scanning/HIDBarcodeParserTests.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,65 @@ struct HIDBarcodeParserTests {
334334
Issue.record("Expected failure result")
335335
}
336336
}
337+
338+
@Test("Parser does not show an error row for empty scan")
339+
func testEmptyScanDoesntError() {
340+
var results: [Result<String, Error>] = []
341+
let parser = HIDBarcodeParser(
342+
configuration: testConfiguration,
343+
onScan: { result in
344+
results.append(result)
345+
}
346+
)
347+
348+
// Just send the terminator, no scan input
349+
parser.processKeyPress(MockUIKey(character: "\r"))
350+
351+
#expect(results.isEmpty)
352+
}
353+
354+
@Test("Parser does not start a timeout for an ignored character")
355+
func testEmptyScanDoesntStartTimeoutForIgnoredCharacter() {
356+
var results: [Result<String, Error>] = []
357+
let mockTimeProvider = MockTimeProvider()
358+
let parser = HIDBarcodeParser(
359+
configuration: testConfiguration,
360+
onScan: { result in
361+
results.append(result)
362+
},
363+
timeProvider: mockTimeProvider
364+
)
365+
366+
// Scan a barcode with two terminators, then scan another barcode – only the two codes should be parsed
367+
parser.processKeyPress(MockUIKey(character: "1"))
368+
parser.processKeyPress(MockUIKey(character: "2"))
369+
parser.processKeyPress(MockUIKey(character: "3"))
370+
parser.processKeyPress(MockUIKey(character: "\r")) // Scan is recognised here
371+
parser.processKeyPress(MockUIKey(character: "\n", keyCode: .keyboardDownArrow)) // This is ignored
372+
373+
// Time between scans
374+
mockTimeProvider.advance(by: 1.5)
375+
376+
// Scan the second barcode
377+
parser.processKeyPress(MockUIKey(character: "4")) // Risk of an error row here if `\n` isn't ignored
378+
parser.processKeyPress(MockUIKey(character: "5"))
379+
parser.processKeyPress(MockUIKey(character: "6"))
380+
parser.processKeyPress(MockUIKey(character: "\r"))
381+
parser.processKeyPress(MockUIKey(character: "\n", keyCode: .keyboardDownArrow))
382+
383+
384+
#expect(results.count == 2)
385+
if case .success(let barcode1) = results[0] {
386+
#expect(barcode1 == "123")
387+
} else {
388+
Issue.record("Expected success result for first scan")
389+
}
390+
if case .success(let barcode2) = results[1] {
391+
#expect(barcode2 == "456")
392+
} else {
393+
Issue.record("Expected success result for second scan")
394+
}
395+
}
337396
}
338397

339398
// MARK: - Test Helpers

0 commit comments

Comments
 (0)