对齐 Android 无权限强制弹窗、角色下拉与申请页交互,补充 UIButton configuration 适配,并增强景区排队 TTS 与相关单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
220 lines
10 KiB
Swift
220 lines
10 KiB
Swift
//
|
|
// 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.session.configureTestAccount()
|
|
appStore.permissions.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)
|
|
if let existing { viewModel.toggleScenic(existing) }
|
|
XCTAssertTrue(viewModel.selectedScenicIds.isEmpty)
|
|
}
|
|
|
|
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, "请选择景区和角色")
|
|
XCTAssertFalse(viewModel.canSubmit)
|
|
|
|
viewModel.selectRole(RoleOption(id: 10, name: "摄影师", description: "", isSelected: true))
|
|
await viewModel.submit(api: api)
|
|
XCTAssertEqual(message, "请选择景区和角色")
|
|
XCTAssertFalse(viewModel.canSubmit)
|
|
}
|
|
|
|
func testSubmitSuccessUsesAndroidPayloadAndCompletion() 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 session = MockURLSession(responses: [scenicJSON, submitJSON])
|
|
let api = HomeAPI(client: APIClient(environment: .testing, session: session))
|
|
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 didSubmit = false
|
|
viewModel.onSubmitSuccess = { didSubmit = true }
|
|
XCTAssertTrue(viewModel.canSubmit)
|
|
await viewModel.submit(api: api)
|
|
|
|
XCTAssertTrue(didSubmit)
|
|
XCTAssertFalse(viewModel.isSubmitting)
|
|
XCTAssertEqual(session.requests.last?.url?.path, "/api/yf-handset-app/photog/role-apply/submit")
|
|
let body = try XCTUnwrap(session.requests.last?.httpBody)
|
|
let object = try XCTUnwrap(JSONSerialization.jsonObject(with: body) as? [String: Any])
|
|
XCTAssertEqual(object["role_id"] as? Int, 10)
|
|
XCTAssertEqual(object["scenic_id"] as? [Int], [101])
|
|
}
|
|
|
|
func testSubmitFailureRestoresEnabledButtonAndKeepsSelection() async throws {
|
|
let scenicJSON = """
|
|
{"code":100000,"msg":"success","data":{"total":1,"list":[{"id":101,"name":"新景区"}]}}
|
|
""".data(using: .utf8)!
|
|
let failureJSON = #"{"code":100001,"msg":"failed","data":null}"#.data(using: .utf8)!
|
|
let api = HomeAPI(
|
|
client: APIClient(
|
|
environment: .testing,
|
|
session: MockURLSession(responses: [scenicJSON, failureJSON])
|
|
)
|
|
)
|
|
let viewModel = PermissionApplyViewModel(appStore: appStore)
|
|
viewModel.selectRole(RoleOption(id: 10, name: "摄影师", description: "", isSelected: true))
|
|
await viewModel.loadAvailableScenics(api: api)
|
|
viewModel.toggleScenic(try XCTUnwrap(viewModel.availableScenics.first))
|
|
var message: String?
|
|
viewModel.onShowMessage = { message = $0 }
|
|
|
|
await viewModel.submit(api: api)
|
|
|
|
XCTAssertEqual(message, "提交失败,请稍后重试")
|
|
XCTAssertFalse(viewModel.isSubmitting)
|
|
XCTAssertTrue(viewModel.canSubmit)
|
|
XCTAssertEqual(viewModel.selectedScenicIds, [101])
|
|
}
|
|
|
|
func testSwitchingRoleClearsScenicSelectionAndLoadedOptions() async throws {
|
|
let scenicJSON = """
|
|
{"code":100000,"msg":"success","data":{"total":1,"list":[{"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)
|
|
viewModel.toggleScenic(try XCTUnwrap(viewModel.availableScenics.first))
|
|
|
|
viewModel.selectRole(RoleOption(id: 11, name: "门店管理员", description: "", isSelected: true))
|
|
|
|
XCTAssertEqual(viewModel.selectedRoleId, 11)
|
|
XCTAssertTrue(viewModel.selectedScenicIds.isEmpty)
|
|
XCTAssertTrue(viewModel.availableScenics.isEmpty)
|
|
}
|
|
|
|
func testAvailableRolesAreDeduplicatedByRoleID() {
|
|
appStore.permissions.saveRolePermissionList([
|
|
RolePermissionResponse(
|
|
role: RoleInfo(id: 10, name: "摄影师", roleCode: "PHOTOGRAPHER", notes: "拍摄"),
|
|
scenic: [ScenicInfo(id: 100, name: "景区A", status: 1)]
|
|
),
|
|
RolePermissionResponse(
|
|
role: RoleInfo(id: 10, name: "摄影师", roleCode: "PHOTOGRAPHER", notes: "拍摄"),
|
|
scenic: [ScenicInfo(id: 101, name: "景区B", status: 1)]
|
|
),
|
|
])
|
|
|
|
let viewModel = PermissionApplyViewModel(appStore: appStore)
|
|
|
|
XCTAssertEqual(viewModel.availableRoles.map(\.id), [10])
|
|
XCTAssertEqual(viewModel.rolePermissionList.count, 2)
|
|
}
|
|
|
|
func testScenicSearchAndDeletionConfirmation() async throws {
|
|
let scenicJSON = """
|
|
{"code":100000,"msg":"success","data":{"total":3,"list":[{"id":100,"name":"已有景区"},{"id":101,"name":"西湖景区"},{"id":102,"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)
|
|
|
|
viewModel.updateScenicSearchQuery("西湖")
|
|
XCTAssertEqual(viewModel.filteredScenics.map(\.id), [101])
|
|
let scenic = try XCTUnwrap(viewModel.filteredScenics.first)
|
|
viewModel.toggleScenic(scenic)
|
|
XCTAssertTrue(viewModel.canSubmit)
|
|
|
|
viewModel.removeSelectedScenic(id: scenic.id, name: scenic.name)
|
|
XCTAssertEqual(viewModel.pendingDeletion, .scenic(id: 101, name: "西湖景区"))
|
|
viewModel.cancelDeletion()
|
|
XCTAssertEqual(viewModel.selectedScenicIds, [101])
|
|
|
|
viewModel.removeSelectedScenic(id: scenic.id, name: scenic.name)
|
|
viewModel.confirmDeletion()
|
|
XCTAssertTrue(viewModel.selectedScenicIds.isEmpty)
|
|
XCTAssertFalse(viewModel.canSubmit)
|
|
}
|
|
|
|
func testRoleDeletionRequiresConfirmationAndClearsScenics() async throws {
|
|
let scenicJSON = """
|
|
{"code":100000,"msg":"success","data":{"total":1,"list":[{"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)
|
|
viewModel.toggleScenic(try XCTUnwrap(viewModel.availableScenics.first))
|
|
|
|
viewModel.clearRoleSelection()
|
|
XCTAssertEqual(viewModel.pendingDeletion, .role(name: "摄影师"))
|
|
XCTAssertEqual(viewModel.selectedRoleId, 10)
|
|
viewModel.confirmDeletion()
|
|
|
|
XCTAssertNil(viewModel.selectedRoleId)
|
|
XCTAssertTrue(viewModel.selectedScenicIds.isEmpty)
|
|
XCTAssertTrue(viewModel.availableScenics.isEmpty)
|
|
}
|
|
|
|
func testExistingRoleListAndScenicDialogState() {
|
|
let viewModel = PermissionApplyViewModel(appStore: appStore)
|
|
XCTAssertEqual(viewModel.rolePermissionList.count, 1)
|
|
XCTAssertEqual(viewModel.availableRoles.first?.description, "拍摄")
|
|
|
|
let scenics = viewModel.rolePermissionList[0].scenic
|
|
viewModel.showScenicList(roleName: "摄影师", scenics: scenics)
|
|
XCTAssertTrue(viewModel.showScenicListDialog)
|
|
XCTAssertEqual(viewModel.dialogRoleName, "摄影师")
|
|
XCTAssertEqual(viewModel.dialogScenicList.map(\.id), [100])
|
|
viewModel.hideScenicList()
|
|
XCTAssertFalse(viewModel.showScenicListDialog)
|
|
}
|
|
}
|