// // ScenicPromotionTests.swift // suixinkanTests // // Created by Codex on 2026/7/2. // import XCTest @testable import suixinkan /// 宣传页主页面 ViewModel 测试,覆盖加载、场景切换与素材选中逻辑。 @MainActor final class ScenicPromotionViewModelTests: XCTestCase { /// 测试正常场景加载后进入 normal 状态并选中首个打卡点与素材。 func testApplyNormalScenarioLoadsBoardAndSelection() { let viewModel = ScenicPromotionViewModel() viewModel.applyScenario(.normal) XCTAssertEqual(viewModel.pageState, .normal) XCTAssertNotNil(viewModel.board) XCTAssertEqual(viewModel.board?.scenicName, "那拉提景区") XCTAssertNotNil(viewModel.selectedSpot) XCTAssertNotNil(viewModel.selectedMedia) XCTAssertFalse(viewModel.mediaItems.isEmpty) } /// 测试空数据场景清空看板并进入 empty 状态。 func testApplyEmptyScenarioClearsBoard() { let viewModel = ScenicPromotionViewModel() viewModel.applyScenario(.normal) viewModel.applyScenario(.empty) XCTAssertEqual(viewModel.pageState, .empty) XCTAssertNil(viewModel.board) } /// 测试异常场景进入 error 状态。 func testApplyErrorScenarioSetsErrorState() { let viewModel = ScenicPromotionViewModel() viewModel.applyScenario(.error) if case .error = viewModel.pageState { XCTAssertTrue(true) } else { XCTFail("Expected error page state") } } /// 测试无媒体场景清空素材列表。 func testNoMediaScenarioHidesMediaItems() { let viewModel = ScenicPromotionViewModel() viewModel.applyScenario(.normal) viewModel.applyScenario(.noMedia) XCTAssertTrue(viewModel.mediaItems.isEmpty) XCTAssertNil(viewModel.selectedMedia) } /// 测试无排队场景隐藏排队信息。 func testNoQueueScenarioHidesQueueInfo() { let viewModel = ScenicPromotionViewModel() viewModel.applyScenario(.normal) viewModel.applyScenario(.noQueue) XCTAssertNil(viewModel.queue) XCTAssertFalse(viewModel.hasQueueData) } /// 测试切换打卡点与素材模式会更新选中素材。 func testSelectSpotAndModeUpdatesSelectedMedia() { let viewModel = ScenicPromotionViewModel() viewModel.applyScenario(.normal) guard let board = viewModel.board, board.spots.count > 1 else { XCTFail("Expected multiple spots in mock board") return } let secondSpot = board.spots[1] viewModel.selectSpot(secondSpot) XCTAssertEqual(viewModel.selectedSpot?.id, secondSpot.id) viewModel.selectMode(.photo) XCTAssertEqual(viewModel.selectedMode, .photo) XCTAssertEqual(viewModel.selectedMedia?.mode, .photo) } /// 测试左右切换素材循环选中。 func testMoveMediaCyclesSelection() { let viewModel = ScenicPromotionViewModel() viewModel.applyScenario(.normal) guard viewModel.mediaItems.count > 1 else { XCTFail("Expected multiple media items") return } let firstID = viewModel.selectedMedia?.id viewModel.moveMedia(1) XCTAssertNotEqual(viewModel.selectedMedia?.id, firstID) viewModel.moveMedia(-1) XCTAssertEqual(viewModel.selectedMedia?.id, firstID) } } /// 宣传页设置 ViewModel 测试,覆盖上传校验与提交规则。 @MainActor final class ScenicPromotionSettingsViewModelTests: XCTestCase { /// 测试正常场景加载 Mock 配置数据。 func testApplyNormalScenarioLoadsSettingsData() { let viewModel = ScenicPromotionSettingsViewModel() viewModel.applyScenario(.normal) XCTAssertEqual(viewModel.pageState, .normal) XCTAssertFalse(viewModel.queuePoints.isEmpty) XCTAssertFalse(viewModel.materialPoints.isEmpty) XCTAssertFalse(viewModel.selectedQueuePointIDs.isEmpty) } /// 测试未选择素材点时上传会被拦截。 func testAddMaterialFailsWithoutMaterialPoint() { let viewModel = ScenicPromotionSettingsViewModel() viewModel.applyScenario(.normal) viewModel.selectedMaterialPointID = nil XCTAssertFalse(viewModel.addMaterial(kind: .video)) } /// 测试选择素材点后可以 Mock 上传素材。 func testAddMaterialSucceedsWithMaterialPoint() { let viewModel = ScenicPromotionSettingsViewModel() viewModel.applyScenario(.normal) guard let point = viewModel.materialPoints.first else { XCTFail("Expected material points") return } viewModel.selectMaterialPoint(point) let beforeCount = viewModel.materials(for: .video).count XCTAssertTrue(viewModel.addMaterial(kind: .video)) XCTAssertEqual(viewModel.materials(for: .video).count, beforeCount + 1) } /// 测试删除素材会从当前点位列表移除。 func testDeleteMaterialRemovesItem() { let viewModel = ScenicPromotionSettingsViewModel() viewModel.applyScenario(.normal) guard let point = viewModel.materialPoints.first else { XCTFail("Expected material points") return } viewModel.selectMaterialPoint(point) guard viewModel.addMaterial(kind: .image), let material = viewModel.materials(for: .image).last else { XCTFail("Expected uploaded material") return } viewModel.deleteMaterial(material) XCTAssertFalse(viewModel.materials(for: .image).contains(where: { $0.id == material.id })) } /// 测试未绑定排队点时提交失败。 func testSubmitFailsWithoutQueuePoints() { let viewModel = ScenicPromotionSettingsViewModel() viewModel.applyScenario(.normal) viewModel.selectedQueuePointIDs = [] XCTAssertFalse(viewModel.submit()) } /// 测试绑定排队点后提交成功。 func testSubmitSucceedsWithQueuePoints() { let viewModel = ScenicPromotionSettingsViewModel() viewModel.applyScenario(.normal) XCTAssertTrue(viewModel.submit()) } }