// // HomeViewModelTests.swift // suixinkanTests // import UIKit import XCTest @testable import suixinkan @MainActor /// 首页 ViewModel 测试。 final class HomeViewModelTests: XCTestCase { private var appStore: AppStore! private var defaults: UserDefaults! override func setUp() { defaults = UserDefaults(suiteName: "HomeViewModelTests")! defaults.removePersistentDomain(forName: "HomeViewModelTests") appStore = AppStore(defaults: defaults) appStore.userId = "2001" appStore.accountType = "scenic_user" appStore.roleCode = AppRoleCode.photographer.rawValue appStore.roleName = "摄影师" appStore.currentScenicId = 10 appStore.currentScenicName = "测试景区" } override func tearDown() { appStore.logout() super.tearDown() } func testMinimalTopRoleFlag() { appStore.roleCode = AppRoleCode.storeAdmin.rawValue let viewModel = HomeViewModel(appStore: appStore) viewModel.refreshLocalDisplayState() XCTAssertTrue(viewModel.isMinimalTopRole) appStore.roleCode = AppRoleCode.photographer.rawValue viewModel.refreshLocalDisplayState() XCTAssertFalse(viewModel.isMinimalTopRole) } func testLocationReportPromptDefaultsToFixedHomeCopy() { let viewModel = HomeViewModel(appStore: appStore) XCTAssertEqual(viewModel.locationInfoText, HomeViewModel.locationReportPromptText) XCTAssertEqual(viewModel.locationInfoText, "您已进入打卡范围") } func testQuickActionsOnlineEntrySyncsOfflineStyle() throws { let view = HomeQuickActionsView() view.apply(isOnline: false) let statusContainer = try statusActionContainer(in: view) XCTAssertEqual(statusContainer.backgroundColor?.homeTestHexRGB, AppColor.cardBackground.homeTestHexRGB) XCTAssertEqual(statusContainer.layer.borderWidth, 1) XCTAssertEqual(statusLabel(in: statusContainer)?.text, "离线") XCTAssertEqual(statusLabel(in: statusContainer)?.textColor.homeTestHexRGB, AppColor.textTabInactive.homeTestHexRGB) XCTAssertEqual(statusIcon(in: statusContainer)?.accessibilityIdentifier, "wifi.slash") XCTAssertEqual(statusIcon(in: statusContainer)?.tintColor.homeTestHexRGB, AppColor.textTabInactive.homeTestHexRGB) } func testQuickActionsOnlineEntrySyncsOnlineStyle() throws { let view = HomeQuickActionsView() view.apply(isOnline: true) let statusContainer = try statusActionContainer(in: view) XCTAssertEqual(statusContainer.backgroundColor?.homeTestHexRGB, AppColor.online.homeTestHexRGB) XCTAssertEqual(statusLabel(in: statusContainer)?.text, "在线") XCTAssertEqual(statusLabel(in: statusContainer)?.textColor.homeTestHexRGB, AppColor.successBackground.homeTestHexRGB) XCTAssertEqual(statusIcon(in: statusContainer)?.accessibilityIdentifier, "wifi") XCTAssertEqual(statusIcon(in: statusContainer)?.tintColor.homeTestHexRGB, AppColor.successBackground.homeTestHexRGB) } func testRebuildCommonMenusFiltersUnknownURIs() { appStore.savePermissionItems([ HomePermissionItem(id: "1", name: "钱包", uri: "wallet"), HomePermissionItem(id: "2", name: "未知", uri: "unknown_uri"), ]) let viewModel = HomeViewModel(appStore: appStore) viewModel.rebuildCommonMenus() XCTAssertEqual(viewModel.commonMenus.map(\.uri), ["wallet"]) } func testRebuildCommonMenusPersistsDefaultURIsOnFirstLoad() { let menuStore = HomeCommonMenuStore(defaults: defaults) appStore.savePermissionItems([ HomePermissionItem(id: "1", name: "钱包", uri: "wallet"), HomePermissionItem(id: "2", name: "云盘", uri: "cloud_management"), HomePermissionItem(id: "3", name: "任务", uri: "task_management"), HomePermissionItem(id: "4", name: "设置", uri: "system_settings"), HomePermissionItem(id: "5", name: "消息", uri: "message_center"), ]) let viewModel = HomeViewModel(appStore: appStore, commonMenuStore: menuStore) viewModel.rebuildCommonMenus() XCTAssertEqual(viewModel.commonMenus.map(\.uri), [ "wallet", "cloud_management", "task_management", "system_settings", ]) XCTAssertEqual( menuStore.savedCommonURIs( accountScope: appStore.accountCachePrefix, roleCode: appStore.roleCode ), ["wallet", "cloud_management", "task_management", "system_settings"] ) } func testEmptyRolePermissionShowsPermissionDialog() async throws { let responseData = try TestJSON.envelope(data: [RolePermissionResponse]()) let session = MockURLSession(responses: [responseData]) let api = HomeAPI(client: APIClient(session: session)) let viewModel = HomeViewModel(appStore: appStore) await viewModel.loadPermissions(api: api, force: true) XCTAssertTrue(viewModel.showPermissionDialog) XCTAssertTrue(viewModel.commonMenus.isEmpty) } func testRolePermissionBuildsCommonMenusAndStoreCardRole() async throws { appStore.roleCode = AppRoleCode.storeAdmin.rawValue let permissionResponse = RolePermissionResponse( role: RoleInfo( id: 46, name: "店铺管理员", roleCode: AppRoleCode.storeAdmin.rawValue, notes: nil, permission: [ PermissionItem(id: 1, pid: 0, name: "钱包", uri: "wallet", iconSrc: nil, children: nil), PermissionItem(id: 2, pid: 0, name: "云盘", uri: "cloud_management", iconSrc: nil, children: nil), ] ), scenic: [ScenicInfo(id: 10, name: "测试景区", status: 1)] ) let storeResponse = StoreListResponse( total: 1, list: [StoreItem(id: 5, scenicId: 10, name: "测试门店", address: "地址", status: 1, statusText: "营业中")] ) let permissionJSON = try TestJSON.envelope(data: [permissionResponse]) let storeJSON = try TestJSON.envelope(data: storeResponse) let session = MockURLSession(responses: [permissionJSON, storeJSON]) let api = HomeAPI(client: APIClient(session: session)) let viewModel = HomeViewModel(appStore: appStore) await viewModel.loadPermissions(api: api, force: true) XCTAssertFalse(viewModel.showPermissionDialog) XCTAssertTrue(viewModel.isMinimalTopRole) XCTAssertEqual(viewModel.commonMenus.count, 2) XCTAssertEqual(viewModel.storeItem?.name, "测试门店") } func testMarkNeedsPermissionReload() async throws { let permissionResponse = RolePermissionResponse( role: RoleInfo( id: 41, name: "摄影师", roleCode: AppRoleCode.photographer.rawValue, notes: nil, permission: [ PermissionItem(id: 1, pid: 0, name: "钱包", uri: "wallet", iconSrc: nil, children: nil), ] ), scenic: [] ) appStore.savePermissionItems([HomePermissionItem(id: "1", name: "钱包", uri: "wallet")]) let permissionJSON = try TestJSON.envelope(data: [permissionResponse]) let session = MockURLSession(responses: [permissionJSON]) let api = HomeAPI(client: APIClient(session: session)) let viewModel = HomeViewModel(appStore: appStore) viewModel.markNeedsPermissionReload() await viewModel.reloadIfNeeded(api: api) 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, "") } private func statusActionContainer(in view: HomeQuickActionsView) throws -> UIView { let stackView = try XCTUnwrap(view.subviews.compactMap { $0 as? UIStackView }.first) XCTAssertEqual(stackView.arrangedSubviews.count, 3) return stackView.arrangedSubviews[2] } private func statusLabel(in view: UIView) -> UILabel? { if let label = view as? UILabel { return label } for subview in view.subviews { if let label = statusLabel(in: subview) { return label } } return nil } private func statusIcon(in view: UIView) -> UIImageView? { if let imageView = view as? UIImageView { return imageView } for subview in view.subviews { if let imageView = statusIcon(in: subview) { return imageView } } return nil } } private extension UIColor { var homeTestHexRGB: UInt { var red: CGFloat = 0 var green: CGFloat = 0 var blue: CGFloat = 0 var alpha: CGFloat = 0 getRed(&red, green: &green, blue: &blue, alpha: &alpha) let r = Int(round(red * 255)) let g = Int(round(green * 255)) let b = Int(round(blue * 255)) return UInt(r << 16 | g << 8 | b) } }