feat: 添加获客员分成记录并重构列表
This commit is contained in:
277
suixinkanTests/CommissionRateLogTests.swift
Normal file
277
suixinkanTests/CommissionRateLogTests.swift
Normal file
@ -0,0 +1,277 @@
|
||||
//
|
||||
// CommissionRateLogTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
/// 获客员分成比例修改记录模型、接口与分页行为测试。
|
||||
@MainActor
|
||||
final class CommissionRateLogTests: XCTestCase {
|
||||
func testModelDecodesCompleteRecordAndBuildsBusinessDisplayText() throws {
|
||||
let data = """
|
||||
{
|
||||
"list": [{
|
||||
"id": 1,
|
||||
"binding_id": 10,
|
||||
"sale_user_id": 456,
|
||||
"store_user_id": 732,
|
||||
"before_rate": 10,
|
||||
"before_rate_label": "10%(旧)",
|
||||
"after_rate": 20,
|
||||
"after_rate_label": "20%(新)",
|
||||
"operator_type": "store_user",
|
||||
"operator_id": 732,
|
||||
"operator_name": "李师傅",
|
||||
"remark": " 调整合作比例 ",
|
||||
"created_at": "2026-07-23 11:00:00"
|
||||
}],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let response = try JSONDecoder().decode(CommissionRateLogListResponse.self, from: data)
|
||||
let item = try XCTUnwrap(response.list.first)
|
||||
|
||||
XCTAssertEqual(response.pageSize, 10)
|
||||
XCTAssertEqual(item.bindingId, 10)
|
||||
XCTAssertEqual(item.saleUserId, 456)
|
||||
XCTAssertEqual(item.storeUserId, 732)
|
||||
XCTAssertEqual(item.operatorType, "store_user")
|
||||
XCTAssertEqual(item.operatorId, 732)
|
||||
XCTAssertEqual(item.displayBeforeRate, "10%(旧)")
|
||||
XCTAssertEqual(item.displayAfterRate, "20%(新)")
|
||||
XCTAssertEqual(item.metadataLine, "2026-07-23 11:00")
|
||||
XCTAssertEqual(item.displayRemark, "调整合作比例")
|
||||
}
|
||||
|
||||
func testDisplayFallsBackToNumericRateUnknownOperatorAndRawTime() throws {
|
||||
let data = """
|
||||
{
|
||||
"list": [{
|
||||
"id": 2,
|
||||
"binding_id": 0,
|
||||
"sale_user_id": 456,
|
||||
"store_user_id": 0,
|
||||
"before_rate": 5,
|
||||
"before_rate_label": "",
|
||||
"after_rate": 15,
|
||||
"after_rate_label": " ",
|
||||
"operator_type": "",
|
||||
"operator_id": 0,
|
||||
"operator_name": "",
|
||||
"remark": " ",
|
||||
"created_at": "服务端时间"
|
||||
}],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let item = try JSONDecoder().decode(CommissionRateLogListResponse.self, from: data).list[0]
|
||||
|
||||
XCTAssertEqual(item.displayBeforeRate, "5%")
|
||||
XCTAssertEqual(item.displayAfterRate, "15%")
|
||||
XCTAssertEqual(item.metadataLine, "服务端时间")
|
||||
XCTAssertNil(item.displayRemark)
|
||||
}
|
||||
|
||||
func testAPIBuildsPathAndClampsPaginationParameters() async throws {
|
||||
let session = MockURLSession(responses: [envelope(list: [], total: 0, page: 1)])
|
||||
let api = OrderAPI(client: APIClient(environment: .testing, session: session))
|
||||
|
||||
_ = try await api.saleUserCommissionRateLogs(saleUserId: 456, page: 0, pageSize: 99)
|
||||
|
||||
let request = try XCTUnwrap(session.requests.first)
|
||||
XCTAssertEqual(request.httpMethod, "GET")
|
||||
XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/sale-user/commission-rate/logs")
|
||||
let query = URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)?.queryItems
|
||||
XCTAssertEqual(query?.first { $0.name == "sale_user_id" }?.value, "456")
|
||||
XCTAssertEqual(query?.first { $0.name == "page" }?.value, "1")
|
||||
XCTAssertEqual(query?.first { $0.name == "page_size" }?.value, "50")
|
||||
}
|
||||
|
||||
func testViewModelRetriesFailedPageAndDeduplicatesItems() async throws {
|
||||
let page1 = envelope(
|
||||
list: [
|
||||
record(id: 1, before: 10, after: 20),
|
||||
record(id: 1, before: 10, after: 20),
|
||||
record(id: 2, before: 20, after: 30),
|
||||
],
|
||||
total: 3,
|
||||
page: 1
|
||||
)
|
||||
let page2 = envelope(
|
||||
list: [
|
||||
record(id: 2, before: 20, after: 30),
|
||||
record(id: 3, before: 30, after: 40),
|
||||
],
|
||||
total: 3,
|
||||
page: 2
|
||||
)
|
||||
let response = HTTPURLResponse(
|
||||
url: URL(string: "https://api-test.zhifly.cn/mock")!,
|
||||
statusCode: 200,
|
||||
httpVersion: nil,
|
||||
headerFields: nil
|
||||
)!
|
||||
let session = MockURLSession(results: [
|
||||
.success((page1, response)),
|
||||
.failure(URLError(.timedOut)),
|
||||
.success((page2, response)),
|
||||
])
|
||||
let api = OrderAPI(client: APIClient(environment: .testing, session: session))
|
||||
let viewModel = CommissionRateLogViewModel(saleUserId: 456)
|
||||
|
||||
await viewModel.refresh(api: api)
|
||||
await viewModel.loadMore(api: api)
|
||||
|
||||
XCTAssertEqual(viewModel.items.map(\.id), [1, 2])
|
||||
XCTAssertTrue(viewModel.canLoadMore)
|
||||
XCTAssertFalse(viewModel.isLoadingMore)
|
||||
|
||||
await viewModel.loadMore(api: api)
|
||||
|
||||
XCTAssertEqual(viewModel.items.map(\.id), [1, 2, 3])
|
||||
XCTAssertFalse(viewModel.canLoadMore)
|
||||
let requestedPages = session.requests.compactMap { request in
|
||||
URLComponents(url: request.url!, resolvingAgainstBaseURL: false)?
|
||||
.queryItems?
|
||||
.first { $0.name == "page" }?
|
||||
.value
|
||||
}
|
||||
XCTAssertEqual(requestedPages, ["1", "2", "2"])
|
||||
}
|
||||
|
||||
func testTimelineCellOnlyMarksLatestRecord() {
|
||||
let item = CommissionRateLogEntity(
|
||||
id: 1,
|
||||
bindingId: 10,
|
||||
saleUserId: 456,
|
||||
storeUserId: 732,
|
||||
beforeRate: 10,
|
||||
beforeRateLabel: "10%",
|
||||
afterRate: 20,
|
||||
afterRateLabel: "20%",
|
||||
operatorType: "store_user",
|
||||
operatorId: 732,
|
||||
operatorName: "李师傅",
|
||||
remark: "",
|
||||
createdAt: "2026-07-23 11:00:00"
|
||||
)
|
||||
let cell = CommissionRateLogTimelineCell(style: .default, reuseIdentifier: nil)
|
||||
|
||||
cell.configure(with: item, isLatest: true)
|
||||
XCTAssertTrue(cell.accessibilityLabel?.contains("最新记录") == true)
|
||||
XCTAssertFalse(cell.accessibilityLabel?.contains("李师傅") == true)
|
||||
|
||||
cell.configure(with: item, isLatest: false)
|
||||
XCTAssertFalse(cell.accessibilityLabel?.contains("最新记录") == true)
|
||||
}
|
||||
|
||||
func testPageTitleUsesAcquirerNameAndFallsBackWhenEmpty() {
|
||||
let namedController = CommissionRateLogViewController(
|
||||
saleUserId: 456,
|
||||
acquirerName: " 获客员小王 "
|
||||
)
|
||||
namedController.setupNavigationBar()
|
||||
XCTAssertEqual(namedController.title, "给获客员小王的分成记录")
|
||||
|
||||
let fallbackController = CommissionRateLogViewController(
|
||||
saleUserId: 456,
|
||||
acquirerName: " "
|
||||
)
|
||||
fallbackController.setupNavigationBar()
|
||||
XCTAssertEqual(fallbackController.title, "修改记录")
|
||||
}
|
||||
|
||||
func testAcquirerCardUsesUnifiedActionsAndBusinessHierarchy() throws {
|
||||
let cell = CooperationAcquirerCell(style: .default, reuseIdentifier: nil)
|
||||
cell.configure(
|
||||
with: CooperativeSalerEntity(
|
||||
saleUserId: 456,
|
||||
name: "获客员小王",
|
||||
salerPhone: "13800138000",
|
||||
commissionRate: 20,
|
||||
commissionRateLabel: "20%",
|
||||
bindTime: "2026-07-23 11:00:00"
|
||||
)
|
||||
)
|
||||
let fittingSize = cell.contentView.systemLayoutSizeFitting(
|
||||
CGSize(width: 390, height: UIView.layoutFittingCompressedSize.height),
|
||||
withHorizontalFittingPriority: .required,
|
||||
verticalFittingPriority: .fittingSizeLevel
|
||||
)
|
||||
cell.frame = CGRect(x: 0, y: 0, width: 390, height: fittingSize.height)
|
||||
cell.layoutIfNeeded()
|
||||
|
||||
let remarkButton = try XCTUnwrap(
|
||||
subview(in: cell, identifier: "cooperation_acquirer_edit_remark") as? UIButton
|
||||
)
|
||||
let logButton = try XCTUnwrap(
|
||||
subview(in: cell, identifier: "cooperation_acquirer_commission_logs") as? UIButton
|
||||
)
|
||||
let commissionButton = try XCTUnwrap(
|
||||
subview(in: cell, identifier: "cooperation_acquirer_edit_commission") as? UIButton
|
||||
)
|
||||
let bindTimeLabel = try XCTUnwrap(
|
||||
subview(in: cell, identifier: "cooperation_acquirer_bind_time") as? UILabel
|
||||
)
|
||||
|
||||
XCTAssertEqual(remarkButton.bounds.height, AppSpacing.minTouchTarget, accuracy: 0.5)
|
||||
XCTAssertEqual(logButton.bounds.height, remarkButton.bounds.height, accuracy: 0.5)
|
||||
XCTAssertEqual(commissionButton.bounds.height, remarkButton.bounds.height, accuracy: 0.5)
|
||||
XCTAssertEqual(logButton.bounds.width, remarkButton.bounds.width, accuracy: 0.5)
|
||||
XCTAssertEqual(commissionButton.bounds.width, remarkButton.bounds.width, accuracy: 0.5)
|
||||
XCTAssertEqual(bindTimeLabel.text, "绑定于 2026-07-23 11:00")
|
||||
}
|
||||
|
||||
private func envelope(list: [[String: Any]], total: Int, page: Int) -> Data {
|
||||
let object: [String: Any] = [
|
||||
"code": 100000,
|
||||
"msg": "success",
|
||||
"data": [
|
||||
"list": list,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": 10,
|
||||
],
|
||||
]
|
||||
return try! JSONSerialization.data(withJSONObject: object)
|
||||
}
|
||||
|
||||
private func record(id: Int, before: Int, after: Int) -> [String: Any] {
|
||||
[
|
||||
"id": id,
|
||||
"binding_id": 10,
|
||||
"sale_user_id": 456,
|
||||
"store_user_id": 732,
|
||||
"before_rate": before,
|
||||
"before_rate_label": "\(before)%",
|
||||
"after_rate": after,
|
||||
"after_rate_label": "\(after)%",
|
||||
"operator_type": "store_user",
|
||||
"operator_id": 732,
|
||||
"operator_name": "李师傅",
|
||||
"remark": "",
|
||||
"created_at": "2026-07-23 11:00:00",
|
||||
]
|
||||
}
|
||||
|
||||
private func subview(in root: UIView, identifier: String) -> UIView? {
|
||||
if root.accessibilityIdentifier == identifier {
|
||||
return root
|
||||
}
|
||||
for child in root.subviews {
|
||||
if let match = subview(in: child, identifier: identifier) {
|
||||
return match
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user