// // ScenicSelectionViewModelTests.swift // suixinkanTests // // Created by Codex on 2026/6/23. // import XCTest @testable import suixinkan_ios @MainActor /// 景区选择 ViewModel 测试,覆盖搜索、定位和切换持久化。 final class ScenicSelectionViewModelTests: XCTestCase { /// 测试当前景区会优先展示并标记选中。 func testCurrentScenicMovesToFirstItem() { let context = AccountContext() context.replaceScopes( scenic: [ BusinessScope(id: 1, name: "西湖景区", kind: .scenic), BusinessScope(id: 2, name: "东湖景区", kind: .scenic) ], stores: [], currentScenicId: 2 ) let viewModel = ScenicSelectionViewModel() viewModel.reload(from: context) XCTAssertEqual(viewModel.items.map(\.id), [2, 1]) XCTAssertTrue(viewModel.items[0].isClosest) } /// 测试搜索会按景区名称和地址过滤。 func testSearchFiltersByNameAndAddress() { let viewModel = ScenicSelectionViewModel() let context = AccountContext() context.replaceScopes( scenic: [ BusinessScope(id: 1, name: "西湖景区", kind: .scenic, address: "杭州"), BusinessScope(id: 2, name: "东湖景区", kind: .scenic, address: "武汉") ], stores: [] ) viewModel.reload(from: context) viewModel.searchQuery = "武汉" XCTAssertEqual(viewModel.filteredItems.map(\.id), [2]) } /// 测试定位成功后会计算距离并标记最近景区。 func testLocationSuccessMarksClosestScenic() { let context = AccountContext() context.replaceScopes( scenic: [ BusinessScope(id: 1, name: "远景区", kind: .scenic, address: "远处", latitude: 31.0, longitude: 121.0), BusinessScope(id: 2, name: "近景区", kind: .scenic, address: "附近", latitude: 30.0001, longitude: 120.0001) ], stores: [] ) let viewModel = ScenicSelectionViewModel() viewModel.reload(from: context) viewModel.applyCurrentLocation(latitude: 30.0, longitude: 120.0) XCTAssertEqual(viewModel.items.first(where: \.isClosest)?.id, 2) XCTAssertEqual(viewModel.currentLocationText, "附近") XCTAssertNotNil(viewModel.items.first(where: { $0.id == 2 })?.distanceMeters) } /// 测试定位失败时保留列表并展示提示。 func testLocationFailureKeepsItemsAndWarning() { let context = AccountContext() context.replaceScopes(scenic: [BusinessScope(id: 1, name: "西湖景区", kind: .scenic, address: "杭州")], stores: []) let viewModel = ScenicSelectionViewModel() viewModel.reload(from: context) viewModel.applyLocationFailure("定位权限未开启") XCTAssertEqual(viewModel.items.map(\.id), [1]) XCTAssertEqual(viewModel.locationWarning, "定位权限未开启") } /// 测试切换景区会联动门店并保存账号快照。 func testSelectScenicUpdatesStoreAndPersistsSnapshot() { let suiteName = "ScenicSelectionViewModelTests.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: suiteName)! defer { defaults.removePersistentDomain(forName: suiteName) } let snapshotStore = AccountSnapshotStore(defaults: defaults) let accountContext = AccountContext() let permissionContext = PermissionContext() accountContext.replaceScopes( scenic: [ BusinessScope(id: 1, name: "西湖景区", kind: .scenic), BusinessScope(id: 2, name: "东湖景区", kind: .scenic) ], stores: [ BusinessScope(id: 10, name: "西湖门店", kind: .store, parentScenicId: 1), BusinessScope(id: 20, name: "东湖门店", kind: .store, parentScenicId: 2) ], currentScenicId: 1, currentStoreId: 10 ) permissionContext.replaceRolePermissions([RolePermissionResponse(role: RoleInfo(id: 7, name: "运营"), scenic: [])]) ScenicSelectionViewModel().select( scenicId: 2, accountContext: accountContext, permissionContext: permissionContext, snapshotStore: snapshotStore ) XCTAssertEqual(accountContext.currentScenic?.id, 2) XCTAssertEqual(accountContext.currentStore?.id, 20) XCTAssertEqual(snapshotStore.load()?.currentScenicId, 2) XCTAssertEqual(snapshotStore.load()?.currentStoreId, 20) XCTAssertEqual(snapshotStore.load()?.currentRoleId, 7) } }