diff --git a/lib/services/code_parser.dart b/lib/services/code_parser.dart new file mode 100644 index 0000000..9dcd9a6 --- /dev/null +++ b/lib/services/code_parser.dart @@ -0,0 +1,39 @@ +// lib/services/code_parser.dart +class CodeParser { + static final _zongpaiRegex = RegExp( + r'^\d{2}(B|C|T)\d+$|^\d{2}(BW|CW)\d{4}$', + ); + static final _locationRegex = RegExp( + r'^[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+$', + ); + static final _transitRegex = RegExp( + r'^TRANS-', + ); + + static ParseResult parse(String code) { + if (code.isEmpty) { + return ParseResult(type: CodeType.invalid, value: code); + } + if (_transitRegex.hasMatch(code)) { + return ParseResult(type: CodeType.locationTransit, value: code); + } + if (_zongpaiRegex.hasMatch(code)) { + return ParseResult(type: CodeType.zongpaiNo, value: code); + } + if (_locationRegex.hasMatch(code)) { + return ParseResult(type: CodeType.locationNormal, value: code); + } + return ParseResult(type: CodeType.invalid, value: code); + } + + static bool isLocation(CodeType type) => + type == CodeType.locationNormal || type == CodeType.locationTransit; +} + +enum CodeType { zongpaiNo, locationNormal, locationTransit, invalid } + +class ParseResult { + final CodeType type; + final String value; + ParseResult({required this.type, required this.value}); +} diff --git a/test/services/code_parser_test.dart b/test/services/code_parser_test.dart new file mode 100644 index 0000000..bda585a --- /dev/null +++ b/test/services/code_parser_test.dart @@ -0,0 +1,58 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:pad_scanner/services/code_parser.dart'; + +void main() { + group('CodeParser.parse', () { + test('identifies zongpaiNo type B', () { + final result = CodeParser.parse('26B1'); + expect(result.type, CodeType.zongpaiNo); + expect(result.value, '26B1'); + }); + test('identifies zongpaiNo type C', () { + final result = CodeParser.parse('26C12'); + expect(result.type, CodeType.zongpaiNo); + }); + test('identifies zongpaiNo type T', () { + final result = CodeParser.parse('26T3'); + expect(result.type, CodeType.zongpaiNo); + }); + test('identifies zongpaiNo type BW (4 digits)', () { + final result = CodeParser.parse('26BW0001'); + expect(result.type, CodeType.zongpaiNo); + }); + test('identifies zongpaiNo type CW (4 digits)', () { + final result = CodeParser.parse('26CW0015'); + expect(result.type, CodeType.zongpaiNo); + }); + test('rejects BW with non-4-digit serial', () { + expect(CodeParser.parse('26BW01').type, CodeType.invalid); + }); + test('rejects CW with non-4-digit serial', () { + expect(CodeParser.parse('26CW15').type, CodeType.invalid); + }); + test('identifies normal location code', () { + final result = CodeParser.parse('A01-02-03'); + expect(result.type, CodeType.locationNormal); + expect(result.value, 'A01-02-03'); + }); + test('identifies transit location code', () { + final result = CodeParser.parse('TRANS-01'); + expect(result.type, CodeType.locationTransit); + }); + test('rejects lowercase location code', () { + expect(CodeParser.parse('a01-02-03').type, CodeType.invalid); + }); + test('rejects invalid code', () { + expect(CodeParser.parse('HELLO123').type, CodeType.invalid); + }); + test('rejects empty string', () { + expect(CodeParser.parse('').type, CodeType.invalid); + }); + test('isLocation helper', () { + expect(CodeParser.isLocation(CodeType.locationNormal), true); + expect(CodeParser.isLocation(CodeType.locationTransit), true); + expect(CodeParser.isLocation(CodeType.zongpaiNo), false); + expect(CodeParser.isLocation(CodeType.invalid), false); + }); + }); +}