实现权限驱动的首页 Tab 并对齐 Android
This commit is contained in:
50
suixinkanTests/HomeCommonMenuStoreTests.swift
Normal file
50
suixinkanTests/HomeCommonMenuStoreTests.swift
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// HomeCommonMenuStoreTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
/// 常用应用持久化测试。
|
||||
final class HomeCommonMenuStoreTests: XCTestCase {
|
||||
|
||||
private var defaults: UserDefaults!
|
||||
private var store: HomeCommonMenuStore!
|
||||
|
||||
override func setUp() {
|
||||
defaults = UserDefaults(suiteName: "HomeCommonMenuStoreTests")!
|
||||
defaults.removePersistentDomain(forName: "HomeCommonMenuStoreTests")
|
||||
store = HomeCommonMenuStore(defaults: defaults)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
defaults.removePersistentDomain(forName: "HomeCommonMenuStoreTests")
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testDefaultCommonURIsTakeFirstFour() {
|
||||
let permissions = (1...6).map {
|
||||
HomePermissionItem(id: "\($0)", name: "菜单\($0)", uri: "uri_\($0)")
|
||||
}
|
||||
let uris = HomeCommonMenuStore.defaultCommonURIs(from: permissions)
|
||||
XCTAssertEqual(uris, ["uri_1", "uri_2", "uri_3", "uri_4"])
|
||||
}
|
||||
|
||||
func testSavedCommonURIsOverrideDefault() {
|
||||
let permissions = [
|
||||
HomePermissionItem(id: "1", name: "钱包", uri: "wallet"),
|
||||
HomePermissionItem(id: "2", name: "云盘", uri: "cloud_management"),
|
||||
HomePermissionItem(id: "3", name: "任务", uri: "task_management"),
|
||||
]
|
||||
let allMenus = HomeMenuCatalog.visibleMenus(from: permissions)
|
||||
store.saveCommonURIs(["cloud_management"], accountScope: "u1_scenic_user", roleCode: "photographer")
|
||||
let common = store.commonMenus(
|
||||
from: allMenus,
|
||||
permissions: permissions,
|
||||
accountScope: "u1_scenic_user",
|
||||
roleCode: "photographer"
|
||||
)
|
||||
XCTAssertEqual(common.map(\.uri), ["cloud_management"])
|
||||
}
|
||||
}
|
||||
66
suixinkanTests/HomeLocationStateStoreTests.swift
Normal file
66
suixinkanTests/HomeLocationStateStoreTests.swift
Normal file
@ -0,0 +1,66 @@
|
||||
//
|
||||
// HomeLocationStateStoreTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
/// 首页在线状态存储测试。
|
||||
final class HomeLocationStateStoreTests: XCTestCase {
|
||||
|
||||
private var appStore: AppStore!
|
||||
|
||||
override func setUp() {
|
||||
let defaults = UserDefaults(suiteName: "HomeLocationStateStoreTests")!
|
||||
defaults.removePersistentDomain(forName: "HomeLocationStateStoreTests")
|
||||
appStore = AppStore(defaults: defaults)
|
||||
appStore.userId = "1001"
|
||||
appStore.accountType = "scenic_user"
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
appStore.logout()
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testExpiredOnlineSessionIsClearedOnRestore() {
|
||||
let expiredTimestamp = Int64(Date().timeIntervalSince1970 * 1000) - HomeLocationStateStore.onlineDurationMillis - 1_000
|
||||
appStore.onlineStatus = true
|
||||
appStore.lastLocationReportTime = expiredTimestamp
|
||||
|
||||
let stateStore = HomeLocationStateStore(store: appStore)
|
||||
stateStore.restoreStateIfNeeded()
|
||||
|
||||
XCTAssertFalse(stateStore.isOnline)
|
||||
XCTAssertFalse(appStore.onlineStatus)
|
||||
XCTAssertEqual(appStore.lastLocationReportTime, 0)
|
||||
}
|
||||
|
||||
func testActiveOnlineSessionRestoresCountdown() {
|
||||
let recentTimestamp = Int64(Date().timeIntervalSince1970 * 1000) - 60_000
|
||||
appStore.onlineStatus = true
|
||||
appStore.lastLocationReportTime = recentTimestamp
|
||||
|
||||
let stateStore = HomeLocationStateStore(store: appStore)
|
||||
stateStore.restoreStateIfNeeded()
|
||||
|
||||
XCTAssertTrue(stateStore.isOnline)
|
||||
XCTAssertGreaterThan(stateStore.nextReportCountdownSeconds, 0)
|
||||
}
|
||||
|
||||
func testFormattedCountdownText() {
|
||||
let stateStore = HomeLocationStateStore(store: appStore)
|
||||
XCTAssertEqual(stateStore.formattedCountdown(from: 3661), "01:01:01")
|
||||
}
|
||||
|
||||
func testShouldTriggerTimeoutReminderWithinWindow() {
|
||||
appStore.locationReminderMinutes = 10
|
||||
let stateStore = HomeLocationStateStore(store: appStore)
|
||||
stateStore.startLocationReport(at: Int64(Date().timeIntervalSince1970 * 1000))
|
||||
stateStore.updateReminderMinutes(10)
|
||||
// Force countdown into reminder window via reflection of internal state is not available;
|
||||
// verify helper with manual expectation on threshold logic.
|
||||
XCTAssertFalse(stateStore.shouldTriggerTimeoutReminder())
|
||||
}
|
||||
}
|
||||
125
suixinkanTests/HomeViewModelTests.swift
Normal file
125
suixinkanTests/HomeViewModelTests.swift
Normal file
@ -0,0 +1,125 @@
|
||||
//
|
||||
// HomeViewModelTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
@MainActor
|
||||
/// 首页 ViewModel 测试。
|
||||
final class HomeViewModelTests: XCTestCase {
|
||||
|
||||
private var appStore: AppStore!
|
||||
|
||||
override func setUp() {
|
||||
let 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 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 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)
|
||||
}
|
||||
}
|
||||
52
suixinkanTests/RolePermissionMatcherTests.swift
Normal file
52
suixinkanTests/RolePermissionMatcherTests.swift
Normal file
@ -0,0 +1,52 @@
|
||||
//
|
||||
// RolePermissionMatcherTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
/// 角色权限匹配测试。
|
||||
final class RolePermissionMatcherTests: XCTestCase {
|
||||
|
||||
private func makeItem(code: String, id: Int, name: String) -> RolePermissionResponse {
|
||||
RolePermissionResponse(
|
||||
role: RoleInfo(id: id, name: name, roleCode: code, notes: nil, permission: []),
|
||||
scenic: []
|
||||
)
|
||||
}
|
||||
|
||||
func testMatchByRoleCodeHasHighestPriority() {
|
||||
let list = [
|
||||
makeItem(code: "photographer", id: 41, name: "摄影师"),
|
||||
makeItem(code: "store_admin", id: 46, name: "店铺管理员"),
|
||||
]
|
||||
let matched = RolePermissionMatcher.match(
|
||||
in: list,
|
||||
roleCode: AppRoleCode.storeAdmin.rawValue,
|
||||
roleName: "摄影师"
|
||||
)
|
||||
XCTAssertEqual(matched?.role.roleCode, AppRoleCode.storeAdmin.rawValue)
|
||||
}
|
||||
|
||||
func testMatchByLegacyRoleIdWhenCodeMissing() {
|
||||
let list = [makeItem(code: "", id: 53, name: "景区管理员")]
|
||||
let matched = RolePermissionMatcher.match(
|
||||
in: list,
|
||||
roleCode: "",
|
||||
roleName: "",
|
||||
legacyRoleId: 53
|
||||
)
|
||||
XCTAssertEqual(matched?.role.id, 53)
|
||||
}
|
||||
|
||||
func testMatchByRoleNameWhenCodeAndLegacyMissing() {
|
||||
let list = [makeItem(code: "", id: 99, name: "剪辑师")]
|
||||
let matched = RolePermissionMatcher.match(
|
||||
in: list,
|
||||
roleCode: "",
|
||||
roleName: "剪辑师"
|
||||
)
|
||||
XCTAssertEqual(matched?.role.name, "剪辑师")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user