新增宣传页 Mock 模块并接入首页路由。
从 xh_test 同步 ScenicPromotion 演示页,补齐 MVVM 结构、本地资源、单元测试与模块文档,注册 promotion_page 首页入口。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -48,6 +48,7 @@ final class HomeMenuRouterTests: XCTestCase {
|
||||
("location_report", .destination(.locationReport)),
|
||||
("location_report_history", .destination(.locationReportHistory)),
|
||||
("report_photographer", .destination(.photographerReport)),
|
||||
("promotion_page", .destination(.scenicPromotion)),
|
||||
("pm", .destination(.projectManagement)),
|
||||
("project_edit", .destination(.projectManagement)),
|
||||
("pm_manager", .destination(.pmProjectManagement)),
|
||||
|
||||
183
suixinkanTests/ScenicPromotion/ScenicPromotionTests.swift
Normal file
183
suixinkanTests/ScenicPromotion/ScenicPromotionTests.swift
Normal file
@ -0,0 +1,183 @@
|
||||
//
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user