添加景区选择与合作订单流程并对齐 Android

This commit is contained in:
2026-07-07 10:48:43 +08:00
parent ef1d3b4af5
commit c4057537d2
50 changed files with 5603 additions and 191 deletions

View File

@ -0,0 +1,86 @@
//
// BindAcquirerViewModelTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
@MainActor
final class BindAcquirerViewModelTests: XCTestCase {
func testAcceptBindValidationRequiresRate() async {
let viewModel = BindAcquirerViewModel()
viewModel.initSaleUserId(1)
var message: String?
viewModel.onShowMessage = { message = $0 }
await viewModel.acceptBind(api: makeAPI(responses: []))
XCTAssertEqual(message, "请输入分账比例")
}
func testAcceptBindValidationRejectsOutOfRange() async {
let viewModel = BindAcquirerViewModel()
viewModel.initSaleUserId(1)
viewModel.updateCommissionRateDraft("120")
var message: String?
viewModel.onShowMessage = { message = $0 }
await viewModel.acceptBind(api: makeAPI(responses: []))
XCTAssertEqual(message, "分账比例需在0到100之间")
}
func testLoadInviteInfoMarksAlreadyBound() async throws {
let json = """
{"code":100000,"msg":"success","data":{"sale_user_id":7,"is_bound":true,"saler_name":"","saler_phone":"13800138000","title":""}}
""".data(using: .utf8)!
let api = makeAPI(responses: [json])
let viewModel = BindAcquirerViewModel()
viewModel.initSaleUserId(7)
await viewModel.loadInviteInfo(api: api)
XCTAssertTrue(viewModel.isAlreadyBound)
XCTAssertEqual(viewModel.inviteInfo?.salerName, "张三")
}
func testAcceptBindSuccessCallsCallback() async throws {
let inviteJSON = """
{"code":100000,"msg":"success","data":{"sale_user_id":2,"saler_name":""}}
""".data(using: .utf8)!
let bindJSON = """
{"code":100000,"msg":"success","data":{}}
""".data(using: .utf8)!
let api = makeAPI(responses: [inviteJSON, bindJSON])
let viewModel = BindAcquirerViewModel()
viewModel.initSaleUserId(2)
viewModel.updateCommissionRateDraft("30")
var didBind = false
viewModel.onBindSuccess = { didBind = true }
await viewModel.loadInviteInfo(api: api)
await viewModel.acceptBind(api: api)
XCTAssertTrue(didBind)
}
func testLoadInviteInfoAlreadyBoundMessage() async throws {
let json = try TestJSON.errorEnvelope(code: 100001, msg: "已绑定获客员")
let api = makeAPI(responses: [json])
let viewModel = BindAcquirerViewModel()
viewModel.initSaleUserId(9)
var shouldPop = false
viewModel.onShouldPop = { shouldPop = true }
await viewModel.loadInviteInfo(api: api)
XCTAssertTrue(viewModel.isAlreadyBound)
XCTAssertFalse(shouldPop)
}
private func makeAPI(responses: [Data]) -> OrderAPI {
OrderAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: responses)))
}
}

View File

@ -0,0 +1,88 @@
//
// CooperationAcquirerViewModelTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
@MainActor
final class CooperationAcquirerViewModelTests: XCTestCase {
private var appStore: AppStore!
private var defaults: UserDefaults!
override func setUp() {
defaults = UserDefaults(suiteName: "CooperationAcquirerViewModelTests")!
defaults.removePersistentDomain(forName: "CooperationAcquirerViewModelTests")
appStore = AppStore(defaults: defaults)
appStore.currentStoreId = 5
}
func testSaveRemarkWithValidLengthCallsAPI() async throws {
let json = """
{"code":100000,"msg":"success","data":{}}
""".data(using: .utf8)!
let viewModel = CooperationAcquirerViewModel(appStore: appStore)
var message: String?
viewModel.onShowMessage = { message = $0 }
viewModel.startEditRemark(CooperativeSalerEntity(saleUserId: 1))
viewModel.updateRemarkDraft("备注A")
await viewModel.saveRemark(api: makeAPI(responses: [json]))
XCTAssertEqual(message, "备注已保存")
}
func testSaveCommissionRateRequiresSmsCode() async {
let viewModel = CooperationAcquirerViewModel(appStore: appStore)
var message: String?
viewModel.onShowMessage = { message = $0 }
viewModel.startEditCommissionRate(CooperativeSalerEntity(saleUserId: 1, salerPhone: "13800138000"))
viewModel.updateCommissionRateDraft("20")
await viewModel.saveCommissionRate(api: makeAPI(responses: []))
XCTAssertEqual(message, "请输入验证码")
}
func testProcessScanResultNavigatesWithValidQR() {
let viewModel = CooperationAcquirerViewModel(appStore: appStore)
var navigatedId: Int?
viewModel.onNavigateBindAcquirer = { navigatedId = $0 }
viewModel.processScanResult(#"{"method":"sale_user_bind","sale_user_id":88}"#)
XCTAssertEqual(navigatedId, 88)
}
func testProcessScanResultShowsErrorForInvalidQR() {
let viewModel = CooperationAcquirerViewModel(appStore: appStore)
var message: String?
viewModel.onNavigateBindAcquirer = { _ in XCTFail("Should not navigate") }
viewModel.onShowMessage = { message = $0 }
viewModel.processScanResult("invalid")
XCTAssertEqual(message, "请扫描正确的获客员二维码")
}
func testLoadAcquirersUsesStoreId() async throws {
let json = """
{"code":100000,"msg":"success","data":{"total":1,"list":[{"sale_user_id":3,"name":"A"}]}}
""".data(using: .utf8)!
let session = MockURLSession(responses: [json])
let api = OrderAPI(client: APIClient(environment: .testing, session: session))
let viewModel = CooperationAcquirerViewModel(appStore: appStore)
await viewModel.loadAcquirers(api: api)
XCTAssertEqual(viewModel.acquirers.count, 1)
let url = try XCTUnwrap(session.requests.first?.url?.absoluteString)
XCTAssertTrue(url.contains("store_id=5"))
}
private func makeAPI(responses: [Data]) -> OrderAPI {
OrderAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: responses)))
}
}

View File

@ -0,0 +1,63 @@
//
// CooperationOrderListViewModelTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
@MainActor
final class CooperationOrderListViewModelTests: XCTestCase {
func testRefreshLeadsLoadsFirstPage() async throws {
let json = """
{"code":100000,"msg":"success","data":{"total":1,"list":[{"id":1,"user_phone":"13800138000","created_at":"2026-01-01"}]}}
""".data(using: .utf8)!
let api = OrderAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [json])))
let viewModel = CooperationOrderListViewModel()
await viewModel.refreshLeads(api: api)
XCTAssertEqual(viewModel.leads.count, 1)
XCTAssertFalse(viewModel.canLoadMore)
XCTAssertFalse(viewModel.isRefreshing)
}
func testSearchUsesKeywordQuery() async throws {
let json = """
{"code":100000,"msg":"success","data":{"total":0,"list":[]}}
""".data(using: .utf8)!
let session = MockURLSession(responses: [json])
let api = OrderAPI(client: APIClient(environment: .testing, session: session))
let viewModel = CooperationOrderListViewModel()
viewModel.onTabSelected(1)
viewModel.onSearchTextChange("张三")
await viewModel.refreshAcquisitionOrders(api: api)
let url = try XCTUnwrap(session.requests.first?.url?.absoluteString)
XCTAssertTrue(url.contains("acquisition-orders"))
XCTAssertTrue(url.contains("keyword="))
}
func testLoadMoreAppendsLeads() async throws {
let page1 = """
{"code":100000,"msg":"success","data":{"total":2,"list":[{"id":1,"user_phone":"13800138000"}]}}
""".data(using: .utf8)!
let page2 = """
{"code":100000,"msg":"success","data":{"total":2,"list":[{"id":2,"user_phone":"13800138001"}]}}
""".data(using: .utf8)!
let api = OrderAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [page1, page2])))
let viewModel = CooperationOrderListViewModel()
await viewModel.refreshLeads(api: api)
await viewModel.loadMore(api: api)
XCTAssertEqual(viewModel.leads.count, 2)
}
func testShouldLoadMoreNearEnd() {
let viewModel = CooperationOrderListViewModel()
XCTAssertFalse(viewModel.shouldLoadMore(at: 0))
}
}

View File

@ -0,0 +1,68 @@
//
// CooperationOrderModelsTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
///
final class CooperationOrderModelsTests: XCTestCase {
func testRemarkNameValidationAllowsTenCharacters() {
XCTAssertNil(CooperationOrderFeature.remarkNameValidationMessage("1234567890"))
}
func testRemarkNameValidationRejectsOverTenCharacters() {
XCTAssertEqual(
CooperationOrderFeature.remarkNameValidationMessage("12345678901"),
"备注名不能超过10个字符"
)
}
func testReferralLeadDisplayFields() throws {
let json = """
{
"id": 1,
"lead_id": 9,
"display_name": "",
"user_mobile_masked": "138****0000",
"remark": "",
"created_at": "2026-01-01",
"image_urls": ["https://img/1.jpg"]
}
""".data(using: .utf8)!
let lead = try JSONDecoder().decode(ReferralLeadEntity.self, from: json)
XCTAssertEqual(lead.displayId, "9")
XCTAssertEqual(lead.displayPhone, "138****0000")
XCTAssertEqual(lead.displaySalerName, "")
XCTAssertEqual(lead.displayImages, ["https://img/1.jpg"])
}
func testAcquisitionOrderDisplayFields() throws {
let json = """
{
"project_name": "",
"order_type_label": "",
"order_number": "NO001",
"photog_remark_name": "",
"sale_user_name": "",
"created_at": "2026-01-02",
"user_mobile_masked": "139****1111",
"order_amount": "99.00"
}
""".data(using: .utf8)!
let order = try JSONDecoder().decode(AcquisitionOrderEntity.self, from: json)
XCTAssertEqual(order.displayPartner, "备注名")
XCTAssertEqual(order.displayCustomerPhone, "139****1111")
XCTAssertEqual(order.displayAmount, "99.00")
}
func testCooperativeSalerDisplayCommissionRateUsesLabelFirst() {
let entity = CooperativeSalerEntity(
commissionRate: 10,
commissionRateLabel: "10%(固定)"
)
XCTAssertEqual(entity.displayCommissionRate, "10%(固定)")
}
}

View File

@ -148,4 +148,27 @@ final class HomeViewModelTests: XCTestCase {
XCTAssertEqual(viewModel.commonMenus.map(\.uri), ["wallet"])
XCTAssertEqual(session.requests.count, 1)
}
func testRefreshLocalDisplayStateClearsWhenNameMissing() {
appStore.currentScenicId = 10
appStore.currentScenicName = ""
let viewModel = HomeViewModel(appStore: appStore)
viewModel.refreshLocalDisplayState()
XCTAssertEqual(viewModel.currentScenicName, "")
XCTAssertEqual(appStore.currentScenicId, 0)
XCTAssertEqual(appStore.currentScenicName, "")
}
func testRefreshLocalDisplayStateClearsWhenIdMissing() {
appStore.currentScenicId = 0
appStore.currentScenicName = "残留名称"
let viewModel = HomeViewModel(appStore: appStore)
viewModel.refreshLocalDisplayState()
XCTAssertEqual(viewModel.currentScenicName, "")
XCTAssertEqual(appStore.currentScenicName, "")
}
}

View File

@ -166,4 +166,69 @@ final class OrderListViewModelTests: XCTestCase {
XCTAssertEqual(list[1].photoTravel?.orderPhotoNum, 6)
XCTAssertEqual(list[1].photoTravel?.needCheckIn, true)
}
func testReferralOrderEntityDecodesImages() throws {
let json = """
{
"id": 1,
"referral_order_no": "LD001",
"order_no": "LD001",
"user_mobile": "13800138000",
"remark": "",
"created_at": "2026-06-25",
"status": 1,
"status_label": "",
"images": ["https://cdn.example.com/a.jpg", "https://cdn.example.com/b.jpg"]
}
""".data(using: .utf8)!
let entity = try JSONDecoder().decode(ReferralOrderEntity.self, from: json)
XCTAssertEqual(entity.images.count, 2)
XCTAssertEqual(OrderSourceMapping.lead(from: entity).remark, "")
}
func testOrderSourceLeadMappingUsesStatusLabelFallback() throws {
let json = """
{
"id": 1,
"referral_order_no": "LD002",
"order_no": "LD002",
"user_mobile": "13800138000",
"remark": "",
"created_at": "",
"status": 1,
"status_label": "已完成",
"images": []
}
""".data(using: .utf8)!
let entity = try JSONDecoder().decode(ReferralOrderEntity.self, from: json)
XCTAssertEqual(OrderSourceMapping.lead(from: entity).remark, "")
}
func testOrderSourceSelectionFromBoundReferralUsesRemarkFallback() throws {
let json = """
{
"id": 53,
"order_no": "LD202606250008",
"sale_user_id": 6,
"saler_name": "管铜彬",
"photog_remark_name": "管铜彬1234567",
"saler_phone": "19281871413",
"display_text": "管铜彬1234567 19281871413",
"user_mobile": "13333333331",
"remark": "",
"bind_order_number": "260625128009",
"source_type": 2,
"source_type_label": "下级线索",
"status": 2,
"status_label": "已完成",
"created_at": "2026-06-25 17:13:51"
}
""".data(using: .utf8)!
let referral = try JSONDecoder().decode(BoundReferralOrderEntity.self, from: json)
let selection = OrderSourceMapping.selection(from: referral)
XCTAssertEqual(selection?.person.name, "1234567")
XCTAssertEqual(selection?.lead.remark, "1234567 19281871413")
XCTAssertEqual(selection?.lead.key, "LD202606250008")
}
}

View File

@ -0,0 +1,56 @@
//
// PermissionApplyStatusViewModelTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
@MainActor
/// ViewModel
final class PermissionApplyStatusViewModelTests: XCTestCase {
func testLoadPendingDataMatchesApplyCode() async throws {
let json = """
{"code":100000,"msg":"success","data":[{"code":"AP001","role_id":1,"role_name":"","scenic_list":[{"id":1,"name":"A"}],"status":1,"status_label":"","created_at":"2026-01-01","audit_note":"","audited_at":null,"audited_by":null}]}
""".data(using: .utf8)!
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [json])))
let viewModel = PermissionApplyStatusViewModel(applyCode: "AP001")
await viewModel.loadPendingData(api: api)
XCTAssertEqual(viewModel.pendingData?.code, "AP001")
XCTAssertEqual(viewModel.pendingData?.status, 1)
XCTAssertFalse(viewModel.isLoading)
}
func testNavigateToEditWhenRejected() async throws {
let json = """
{"code":100000,"msg":"success","data":[{"code":"AP002","role_id":2,"role_name":"","scenic_list":[],"status":3,"status_label":"","created_at":"","audit_note":"","audited_at":null,"audited_by":null}]}
""".data(using: .utf8)!
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [json])))
let viewModel = PermissionApplyStatusViewModel(applyCode: "AP002")
await viewModel.loadPendingData(api: api)
var edited: RoleApplyPendingResponse?
viewModel.onNavigateToEdit = { edited = $0 }
viewModel.navigateToEditIfRejected()
XCTAssertEqual(edited?.status, 3)
}
func testNavigateToEditIgnoredWhenReviewing() async throws {
let json = """
{"code":100000,"msg":"success","data":[{"code":"AP003","role_id":1,"role_name":"","scenic_list":[],"status":1,"status_label":"","created_at":"","audit_note":"","audited_at":null,"audited_by":null}]}
""".data(using: .utf8)!
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [json])))
let viewModel = PermissionApplyStatusViewModel(applyCode: "AP003")
await viewModel.loadPendingData(api: api)
var edited: RoleApplyPendingResponse?
viewModel.onNavigateToEdit = { edited = $0 }
viewModel.navigateToEditIfRejected()
XCTAssertNil(edited)
}
}

View File

@ -0,0 +1,85 @@
//
// PermissionApplyViewModelTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
@MainActor
/// ViewModel
final class PermissionApplyViewModelTests: XCTestCase {
private var appStore: AppStore!
private var defaults: UserDefaults!
override func setUp() {
defaults = UserDefaults(suiteName: "PermissionApplyViewModelTests")!
defaults.removePersistentDomain(forName: "PermissionApplyViewModelTests")
appStore = AppStore(defaults: defaults)
appStore.saveRolePermissionList([
RolePermissionResponse(
role: RoleInfo(id: 10, name: "摄影师", roleCode: "PHOTOGRAPHER", notes: "拍摄"),
scenic: [ScenicInfo(id: 100, name: "已有景区", status: 1)]
),
])
}
override func tearDown() {
appStore.logout()
super.tearDown()
}
func testExistingScenicIsDisabledAfterLoad() async throws {
let scenicJSON = """
{"code":100000,"msg":"success","data":{"total":2,"list":[{"id":100,"name":""},{"id":101,"name":""}]}}
""".data(using: .utf8)!
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [scenicJSON])))
let viewModel = PermissionApplyViewModel(appStore: appStore)
viewModel.selectRole(RoleOption(id: 10, name: "摄影师", description: "拍摄", isSelected: true))
await viewModel.loadAvailableScenics(api: api)
let existing = viewModel.availableScenics.first { $0.id == 100 }
let fresh = viewModel.availableScenics.first { $0.id == 101 }
XCTAssertEqual(existing?.isDisabled, true)
XCTAssertEqual(existing?.isSelected, true)
XCTAssertEqual(fresh?.isDisabled, false)
}
func testSubmitValidationRequiresRoleAndScenic() async {
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [])))
let viewModel = PermissionApplyViewModel(appStore: appStore)
var message: String?
viewModel.onShowMessage = { message = $0 }
await viewModel.submit(api: api)
XCTAssertEqual(message, "请先选择角色")
viewModel.selectRole(RoleOption(id: 10, name: "摄影师", description: "", isSelected: true))
await viewModel.submit(api: api)
XCTAssertEqual(message, "请至少选择一个景区")
}
func testSubmitSuccessReturnsApplyCode() async throws {
let scenicJSON = """
{"code":100000,"msg":"success","data":{"total":1,"list":[{"id":101,"name":""}]}}
""".data(using: .utf8)!
let submitJSON = """
{"code":100000,"msg":"success","data":{"code":"AP999"}}
""".data(using: .utf8)!
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [scenicJSON, submitJSON])))
let viewModel = PermissionApplyViewModel(appStore: appStore)
viewModel.selectRole(RoleOption(id: 10, name: "摄影师", description: "", isSelected: true))
await viewModel.loadAvailableScenics(api: api)
if let scenic = viewModel.availableScenics.first(where: { $0.id == 101 }) {
viewModel.toggleScenic(scenic)
}
var applyCode: String?
viewModel.onSubmitSuccess = { applyCode = $0 }
await viewModel.submit(api: api)
XCTAssertEqual(applyCode, "AP999")
}
}

View File

@ -44,6 +44,22 @@ final class ProfileViewModelTests: XCTestCase {
}
}
func testAccountTypeLabelForStoreAccount() {
AppStore.shared.accountType = V9StoreUser.accountTypeValue
let viewModel = ProfileViewModel()
XCTAssertEqual(viewModel.accountTypeLabel, "门店账号")
XCTAssertTrue(viewModel.isStoreAccount)
AppStore.shared.accountType = ""
}
func testAccountTypeLabelForScenicAccount() {
AppStore.shared.accountType = V9ScenicUser.accountTypeValue
let viewModel = ProfileViewModel()
XCTAssertEqual(viewModel.accountTypeLabel, "景区账号")
XCTAssertFalse(viewModel.isStoreAccount)
AppStore.shared.accountType = ""
}
func testApplyLocalProfileUpdateSyncsNicknameAndAvatar() {
AppStore.shared.userName = "全局昵称"
AppStore.shared.avatar = "https://cdn.example.com/new.jpg"

View File

@ -0,0 +1,28 @@
//
// ScenicDistanceCalculatorTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
///
final class ScenicDistanceCalculatorTests: XCTestCase {
func testDistanceKilometersBetweenKnownPoints() {
// ~ 0.9km
let km = ScenicDistanceCalculator.distanceKilometers(
fromLat: 39.9087,
fromLng: 116.3975,
toLat: 39.9163,
toLng: 116.3972
)
XCTAssertGreaterThan(km, 0.5)
XCTAssertLessThan(km, 1.5)
}
func testFormatDistanceKilometers() {
XCTAssertEqual(ScenicDistanceCalculator.formatDistanceKilometers(1.234), "1.23")
XCTAssertEqual(ScenicDistanceCalculator.formatDistanceKilometers(0), "0")
}
}

View File

@ -0,0 +1,102 @@
//
// ScenicSelectionViewModelTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
/// ViewModel
final class ScenicSelectionViewModelTests: XCTestCase {
private var appStore: AppStore!
private var defaults: UserDefaults!
override func setUp() {
defaults = UserDefaults(suiteName: "ScenicSelectionViewModelTests")!
defaults.removePersistentDomain(forName: "ScenicSelectionViewModelTests")
appStore = AppStore(defaults: defaults)
}
override func tearDown() {
appStore.logout()
super.tearDown()
}
func testSearchFiltersByNameAndAddress() {
appStore.saveRoleScenicList([
ScenicInfo(id: 1, name: "西湖景区", status: 1, location: ScenicLocationInfo(lng: 120.1, lat: 30.2, address: "杭州西湖"), coverImg: nil),
ScenicInfo(id: 2, name: "灵隐寺", status: 1, location: ScenicLocationInfo(lng: 120.2, lat: 30.3, address: "杭州灵隐"), coverImg: nil),
])
let viewModel = ScenicSelectionViewModel(appStore: appStore)
viewModel.loadFromLocal()
XCTAssertEqual(viewModel.spots.count, 2)
viewModel.onSearchQueryChange("西湖")
XCTAssertEqual(viewModel.spots.count, 1)
XCTAssertEqual(viewModel.spots.first?.name, "西湖景区")
viewModel.onSearchQueryChange("灵隐")
XCTAssertEqual(viewModel.spots.count, 1)
XCTAssertEqual(viewModel.spots.first?.name, "灵隐寺")
}
func testNearestTagWhenLocationProvided() {
appStore.saveRoleScenicList([
ScenicInfo(id: 1, name: "", status: 1, location: ScenicLocationInfo(lng: 116.40, lat: 39.91, address: "A"), coverImg: nil),
ScenicInfo(id: 2, name: "", status: 1, location: ScenicLocationInfo(lng: 116.50, lat: 39.95, address: "B"), coverImg: nil),
])
let items = ScenicSpotDisplayMapper.buildItems(
scenicList: appStore.roleScenicList(),
searchQuery: "",
currentLat: 39.9087,
currentLng: 116.3975,
selectedScenicId: 0
)
XCTAssertEqual(items.first?.openStatus, .nearest)
}
func testClosedStatusTag() {
appStore.saveRoleScenicList([
ScenicInfo(id: 1, name: "停业", status: 2, location: nil, coverImg: nil),
])
let viewModel = ScenicSelectionViewModel(appStore: appStore)
viewModel.loadFromLocal()
XCTAssertEqual(viewModel.spots.first?.openStatus, .closed)
XCTAssertEqual(viewModel.spots.first?.statusLabel, "暂未营业")
}
func testSelectSpotWritesAppStore() {
appStore.saveRoleScenicList([
ScenicInfo(id: 5, name: "测试景区", status: 1),
])
let viewModel = ScenicSelectionViewModel(appStore: appStore)
var completed = false
viewModel.onSelectionComplete = { completed = true }
viewModel.loadFromLocal()
viewModel.selectSpot(viewModel.spots[0])
XCTAssertEqual(appStore.currentScenicId, 5)
XCTAssertEqual(appStore.currentScenicName, "测试景区")
XCTAssertTrue(completed)
XCTAssertTrue(viewModel.spots.first?.isSelected == true)
}
func testOnApplyNowRoutesToStatusWhenPendingReviewing() async throws {
let pendingJSON = """
{"code":100000,"msg":"success","data":[{"code":"AP001","role_id":1,"role_name":"","scenic_list":[],"status":1,"status_label":"","created_at":"","audit_note":"","audited_at":null,"audited_by":null}]}
""".data(using: .utf8)!
let api = await MainActor.run {
HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [pendingJSON])))
}
let viewModel = ScenicSelectionViewModel(appStore: appStore)
var statusCode: String?
viewModel.onNavigateApplyStatus = { statusCode = $0 }
await viewModel.onApplyNow(api: api)
XCTAssertEqual(statusCode, "AP001")
}
}