修复合作获客扫码绑定崩溃与 Toast 层级,并接入分成比例修改。

将扫码页提升到列表层弹出并补齐 AppDelegate.window,避免扫码时 UIKit 取主窗口崩溃;Toast 改用独立 UIWindow 显示在 sheet 之上;合作获客员支持短信验证修改分成比例,同时优化冷启动根视图创建时机并补充相关测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-03 20:37:24 +08:00
parent 00c3cd0a93
commit 49e997ddba
20 changed files with 905 additions and 70 deletions

View File

@ -0,0 +1,133 @@
//
// BindAcquirerViewModelTests.swift
// suixinkanTests
//
// Created by Codex on 2026/7/3.
//
import XCTest
@testable import suixinkan
/// ViewModel
@MainActor
final class BindAcquirerViewModelTests: XCTestCase {
/// 3
func testUpdateCommissionRateDraftFiltersInput() {
let viewModel = BindAcquirerViewModel()
viewModel.updateCommissionRateDraft("12a3.4")
XCTAssertEqual(viewModel.commissionRateDraft, "123")
viewModel.updateCommissionRateDraft("1234")
XCTAssertEqual(viewModel.commissionRateDraft, "123")
}
///
func testAcceptBindRequiresCommissionRate() async {
let viewModel = BindAcquirerViewModel()
let api = BindAcquirerMockAPI()
await viewModel.load(api: api, saleUserId: 101)
do {
try await viewModel.acceptBind(api: api)
XCTFail("Expected validation error")
} catch let error as CooperationOrderCommissionRateValidationError {
XCTAssertEqual(error.message, "请输入分账比例")
} catch {
XCTFail("Unexpected error: \(error)")
}
XCTAssertEqual(api.acceptBindCalls.count, 0)
}
///
func testAcceptBindRejectsOutOfRangeCommissionRate() async {
let viewModel = BindAcquirerViewModel()
let api = BindAcquirerMockAPI()
await viewModel.load(api: api, saleUserId: 101)
viewModel.updateCommissionRateDraft("101")
do {
try await viewModel.acceptBind(api: api)
XCTFail("Expected validation error")
} catch let error as CooperationOrderCommissionRateValidationError {
XCTAssertEqual(error.message, "分账比例需在0到100之间")
} catch {
XCTFail("Unexpected error: \(error)")
}
XCTAssertEqual(api.acceptBindCalls.count, 0)
}
/// accept-bind
func testAcceptBindPassesCommissionRateToAPI() async throws {
let viewModel = BindAcquirerViewModel()
let api = BindAcquirerMockAPI()
await viewModel.load(api: api, saleUserId: 101)
viewModel.updateCommissionRateDraft("20")
try await viewModel.acceptBind(api: api)
XCTAssertEqual(api.acceptBindCalls.count, 1)
XCTAssertEqual(api.acceptBindCalls.first?.saleUserId, 101)
XCTAssertEqual(api.acceptBindCalls.first?.commissionRate, 20)
}
}
@MainActor
private final class BindAcquirerMockAPI: CooperationOrderServing {
struct AcceptBindCall {
let saleUserId: Int
let commissionRate: Int
}
var acceptBindCalls: [AcceptBindCall] = []
func cooperativeSalers(storeId: Int?, page: Int, pageSize: Int) async throws -> CooperativeSalerListPayload {
fatalError("Not used in BindAcquirerViewModelTests")
}
func saleUserInviteInfo(saleUserId: Int) async throws -> SaleUserInviteInfo {
SaleUserInviteInfo(
saleUserId: saleUserId,
salerName: "测试获客员",
salerPhone: "13800138000"
)
}
func saleUserAcceptBind(saleUserId: Int, commissionRate: Int) async throws {
acceptBindCalls.append(AcceptBindCall(saleUserId: saleUserId, commissionRate: commissionRate))
}
func saleUserReferralLeads(page: Int, pageSize: Int) async throws -> ListPayload<ReferralLead> {
fatalError("Not used in BindAcquirerViewModelTests")
}
func saleUserAcquisitionOrders(keyword: String?, page: Int, pageSize: Int) async throws -> ListPayload<AcquisitionOrder> {
fatalError("Not used in BindAcquirerViewModelTests")
}
func saleUserReferralOrders(saleUserId: Int, page: Int, pageSize: Int) async throws -> ReferralOrderListPayload {
fatalError("Not used in BindAcquirerViewModelTests")
}
func saleUserBindReferralOrder(orderNumber: String, referralOrderNo: String) async throws {
fatalError("Not used in BindAcquirerViewModelTests")
}
func saleUserUpdateReferralOrderStatus(id: Int, status: Int) async throws {
fatalError("Not used in BindAcquirerViewModelTests")
}
func saleUserUpdateRemark(saleUserId: Int, photogRemarkName: String) async throws {
fatalError("Not used in BindAcquirerViewModelTests")
}
func saleUserCommissionRateSendSmsCode(phone: String) async throws {
fatalError("Not used in BindAcquirerViewModelTests")
}
func saleUserUpdateCommissionRate(
saleUserId: Int,
commissionRate: Int,
smsCode: String
) async throws -> CooperativeSaler {
fatalError("Not used in BindAcquirerViewModelTests")
}
}