对齐 Android 立即收款页面 UI,并补齐推送状态与语音开关。
统一收款详情布局、设置金额弹窗和收款记录样式,接入 type 6/1 推送驱动与轮询 fallback,使 iOS 收款反馈与 Android 一致。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -14,7 +14,7 @@ final class PaymentViewModelTests: XCTestCase {
|
||||
/// 测试无景区时清空二维码且不请求接口。
|
||||
func testLoadPayCodeWithoutScenicClearsState() async {
|
||||
let api = PaymentMockAPI()
|
||||
let viewModel = PaymentCollectionViewModel()
|
||||
let viewModel = makeViewModel()
|
||||
viewModel.staticPayUrl = "old"
|
||||
|
||||
await viewModel.loadPayCode(api: api, scenicId: nil)
|
||||
@ -28,7 +28,7 @@ final class PaymentViewModelTests: XCTestCase {
|
||||
func testLoadPayCodeUsesScenicIdAndAppliesStaticURL() async {
|
||||
let api = PaymentMockAPI()
|
||||
api.payCodeResponse = PayCodeResponse(staticPayUrl: "https://pay.example.com/static", dynamicPayUrl: "https://pay.example.com/dynamic")
|
||||
let viewModel = PaymentCollectionViewModel()
|
||||
let viewModel = makeViewModel()
|
||||
|
||||
await viewModel.loadPayCode(api: api, scenicId: 9)
|
||||
|
||||
@ -39,7 +39,7 @@ final class PaymentViewModelTests: XCTestCase {
|
||||
|
||||
/// 测试设置金额会校验金额并生成动态收款 URL。
|
||||
func testApplyDynamicAmountBuildsDynamicURL() async {
|
||||
let viewModel = PaymentCollectionViewModel()
|
||||
let viewModel = makeViewModel()
|
||||
viewModel.dynamicPayUrl = "https://pay.example.com/dynamic?scenic_id=9"
|
||||
viewModel.amountText = "12.345"
|
||||
viewModel.remarkText = "茶水"
|
||||
@ -50,7 +50,71 @@ final class PaymentViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(viewModel.status, .waiting)
|
||||
}
|
||||
|
||||
/// 测试轮询命中新收款记录后进入成功态。
|
||||
/// 测试确认金额会校验格式并更新二维码。
|
||||
func testConfirmAmountUpdatesQRCode() {
|
||||
let viewModel = makeViewModel()
|
||||
viewModel.staticPayUrl = "https://pay.example.com/static"
|
||||
viewModel.dynamicPayUrl = "https://pay.example.com/dynamic?scenic_id=9"
|
||||
viewModel.amountText = "18.50"
|
||||
viewModel.remarkText = "门票"
|
||||
|
||||
XCTAssertTrue(viewModel.confirmAmount())
|
||||
XCTAssertTrue(viewModel.currentPayUrl.contains("amount=18.5"))
|
||||
XCTAssertEqual(viewModel.status, .waiting)
|
||||
XCTAssertNil(viewModel.scanSuccessTime)
|
||||
XCTAssertNil(viewModel.paymentSuccessData)
|
||||
}
|
||||
|
||||
/// 测试扫码成功推送会写入扫码时间并清空付款成功态。
|
||||
func testHandlePaymentPushScanSuccess() {
|
||||
let viewModel = makeViewModel()
|
||||
viewModel.paymentSuccessData = PaymentSuccessDisplayData(
|
||||
orderAmount: "10",
|
||||
orderNumber: "A",
|
||||
payTime: "2026-06-22 10:00",
|
||||
remark: ""
|
||||
)
|
||||
|
||||
viewModel.handlePaymentPush(type: 6, dataJSON: nil)
|
||||
|
||||
XCTAssertNotNil(viewModel.scanSuccessTime)
|
||||
XCTAssertNil(viewModel.paymentSuccessData)
|
||||
}
|
||||
|
||||
/// 测试付款成功推送会解析详情并清空扫码态。
|
||||
func testHandlePaymentPushPaymentSuccess() {
|
||||
let viewModel = makeViewModel()
|
||||
viewModel.scanSuccessTime = "2026-06-22 10:00"
|
||||
let payload = """
|
||||
{"order_amount":"88.00","order_number":"ORDER-1","pay_time":"2026-06-22 10:01","remark":"测试"}
|
||||
"""
|
||||
|
||||
viewModel.handlePaymentPush(type: 1, dataJSON: payload)
|
||||
|
||||
XCTAssertNil(viewModel.scanSuccessTime)
|
||||
XCTAssertEqual(viewModel.paymentSuccessData?.orderAmount, "88.00")
|
||||
XCTAssertEqual(viewModel.paymentSuccessData?.orderNumber, "ORDER-1")
|
||||
XCTAssertEqual(viewModel.paymentSuccessData?.payTime, "2026-06-22 10:01")
|
||||
XCTAssertEqual(viewModel.paymentSuccessData?.remark, "测试")
|
||||
}
|
||||
|
||||
/// 测试语音开关会写入偏好存储。
|
||||
func testToggleVoiceBroadcastPersistsPreference() {
|
||||
let defaults = UserDefaults(suiteName: "PaymentViewModelTests.voice")!
|
||||
defaults.removePersistentDomain(forName: "PaymentViewModelTests.voice")
|
||||
let preferences = AppPreferencesStore(defaults: defaults)
|
||||
let viewModel = makeViewModel(preferencesStore: preferences)
|
||||
|
||||
XCTAssertFalse(viewModel.isVoiceBroadcast)
|
||||
viewModel.toggleVoiceBroadcast()
|
||||
XCTAssertTrue(viewModel.isVoiceBroadcast)
|
||||
XCTAssertTrue(preferences.loadPaymentReceiveVoiceEnabled())
|
||||
|
||||
let reloaded = makeViewModel(preferencesStore: preferences)
|
||||
XCTAssertTrue(reloaded.isVoiceBroadcast)
|
||||
}
|
||||
|
||||
/// 测试轮询命中新收款记录后进入成功态并填充详情。
|
||||
func testPollingFindsNewPaymentRecord() async {
|
||||
let api = PaymentMockAPI()
|
||||
api.recordResponses = [
|
||||
@ -62,13 +126,18 @@ final class PaymentViewModelTests: XCTestCase {
|
||||
PaymentCollectionRecordItem(orderNumber: "B", userPhone: "139", orderAmount: "12.35", createDate: "2026-06-22", createTime: "10:01")
|
||||
])
|
||||
]
|
||||
let viewModel = PaymentCollectionViewModel()
|
||||
let viewModel = makeViewModel()
|
||||
viewModel.amountText = "12.35"
|
||||
viewModel.remarkText = "备注"
|
||||
await viewModel.primePaymentRecords(api: api, scenicId: 9)
|
||||
|
||||
await viewModel.pollUntilPaymentDetected(api: api, scenicId: 9, maxAttempts: 1, intervalNanoseconds: 0)
|
||||
|
||||
XCTAssertEqual(viewModel.status, .success(PaymentCollectionRecordItem(orderNumber: "B", userPhone: "139", orderAmount: "12.35", createDate: "2026-06-22", createTime: "10:01")))
|
||||
XCTAssertEqual(viewModel.paymentSuccessData?.orderAmount, "12.35")
|
||||
XCTAssertEqual(viewModel.paymentSuccessData?.orderNumber, "B")
|
||||
XCTAssertEqual(viewModel.paymentSuccessData?.payTime, "2026-06-22 10:01")
|
||||
XCTAssertEqual(viewModel.paymentSuccessData?.remark, "备注")
|
||||
}
|
||||
|
||||
/// 测试后端缺少日汇总时能从列表兜底生成分组。
|
||||
@ -84,6 +153,11 @@ final class PaymentViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(groups[0].analyse.orderCount, 2)
|
||||
XCTAssertEqual(groups[0].analyse.orderAmountSum, "20")
|
||||
}
|
||||
|
||||
/// 创建测试用 ViewModel,并允许注入独立偏好存储。
|
||||
private func makeViewModel(preferencesStore: AppPreferencesStore = AppPreferencesStore(defaults: UserDefaults(suiteName: "PaymentViewModelTests.default")!)) -> PaymentCollectionViewModel {
|
||||
PaymentCollectionViewModel(preferencesStore: preferencesStore)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
||||
Reference in New Issue
Block a user