feat: add live module and optimize report list
This commit is contained in:
212
suixinkanTests/LiveViewModelTests.swift
Normal file
212
suixinkanTests/LiveViewModelTests.swift
Normal file
@ -0,0 +1,212 @@
|
||||
//
|
||||
// LiveViewModelTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
@MainActor
|
||||
/// 直播 ViewModel 测试。
|
||||
final class LiveViewModelTests: XCTestCase {
|
||||
func testLiveManageLoadsMoreCreatesAndControls() async {
|
||||
let api = MockLiveAPI()
|
||||
api.liveListResponses = [
|
||||
LiveListResponse(total: 12, items: (0 ..< 10).map { liveItem(id: $0) }),
|
||||
LiveListResponse(total: 12, items: [liveItem(id: 10), liveItem(id: 11)]),
|
||||
LiveListResponse(total: 1, items: [liveItem(id: 99)]),
|
||||
LiveListResponse(total: 1, items: [liveItem(id: 99, status: 2)]),
|
||||
LiveListResponse(total: 1, items: [liveItem(id: 99, status: 4)]),
|
||||
]
|
||||
let viewModel = LiveManageViewModel(currentScenicIdProvider: { 3 })
|
||||
|
||||
await viewModel.loadInitial(api: api)
|
||||
await viewModel.loadMoreIfNeeded(lastVisibleIndex: 8, api: api)
|
||||
await viewModel.createLive(title: " 新直播 ", coverUrl: "https://cdn/c.jpg", api: api)
|
||||
await viewModel.controlLive(liveItem(id: 99, status: 1), api: api)
|
||||
await viewModel.finishLive(liveItem(id: 99, status: 2), api: api)
|
||||
|
||||
XCTAssertEqual(api.liveListCalls.map(\.page), [1, 2, 1, 1, 1])
|
||||
XCTAssertEqual(api.createLiveCalls.first?.title, "新直播")
|
||||
XCTAssertEqual(api.startCalls, [99])
|
||||
XCTAssertEqual(api.finishCalls, [99])
|
||||
}
|
||||
|
||||
func testLiveDetailChangesModeAndStopsLiving() async {
|
||||
let api = MockLiveAPI()
|
||||
api.detailResponses = [
|
||||
liveItem(id: 8, status: 2, mode: 1),
|
||||
liveItem(id: 8, status: 2, mode: 2),
|
||||
liveItem(id: 8, status: 3, mode: 2),
|
||||
]
|
||||
let viewModel = LiveDetailViewModel(liveId: 8)
|
||||
|
||||
await viewModel.load(api: api)
|
||||
await viewModel.changeMode(.smoothFirst, api: api)
|
||||
await viewModel.controlLive(api: api)
|
||||
|
||||
XCTAssertEqual(viewModel.pushMode, .smoothFirst)
|
||||
XCTAssertEqual(api.modeCalls.first?.mode, 2)
|
||||
XCTAssertEqual(api.stopCalls, [8])
|
||||
}
|
||||
|
||||
func testAlbumDateValidationAndDeleteReload() async {
|
||||
let api = MockLiveAPI()
|
||||
api.albumListResponses = [
|
||||
LiveAlbumFolderListResponse(items: [folder(id: 1)], total: 1),
|
||||
LiveAlbumFolderListResponse(items: [folder(id: 2)], total: 1),
|
||||
LiveAlbumFolderListResponse(items: [], total: 0),
|
||||
]
|
||||
let viewModel = LiveAlbumListViewModel(currentScenicIdProvider: { 5 })
|
||||
var messages: [String] = []
|
||||
viewModel.onShowMessage = { messages.append($0) }
|
||||
let start = date("2026-07-08")
|
||||
let before = date("2026-07-01")
|
||||
|
||||
await viewModel.loadInitial(api: api)
|
||||
await viewModel.setStartDate(start, api: api)
|
||||
await viewModel.setEndDate(before, api: api)
|
||||
await viewModel.deleteFolder(folder(id: 2), api: api)
|
||||
|
||||
XCTAssertEqual(messages.last, "结束时间不能小于开始时间")
|
||||
XCTAssertEqual(api.albumListCalls.map(\.startTime), [nil, "2026-07-08", "2026-07-08"])
|
||||
XCTAssertEqual(api.deleteFolderCalls, [2])
|
||||
}
|
||||
|
||||
func testAlbumAddValidationUploadsAndCreates() async {
|
||||
let api = MockLiveAPI()
|
||||
let uploader = MockLiveUploader(urls: ["https://cdn/a.jpg"])
|
||||
let viewModel = LiveAlbumAddViewModel(currentScenicIdProvider: { 6 })
|
||||
var messages: [String] = []
|
||||
viewModel.onShowMessage = { messages.append($0) }
|
||||
|
||||
await viewModel.create(api: api)
|
||||
viewModel.updateTitle("作品")
|
||||
await viewModel.create(api: api)
|
||||
await viewModel.addLocalMedia(LivePickedMedia(data: Data(repeating: 1, count: 10), fileName: "a.jpg", size: 10, width: 1, height: 1), uploader: uploader)
|
||||
await viewModel.create(api: api)
|
||||
|
||||
XCTAssertEqual(messages.prefix(2), ["请输入作品名称", "请上传素材"])
|
||||
XCTAssertEqual(api.createAlbumCalls.first?.name, "作品")
|
||||
XCTAssertEqual(api.createAlbumCalls.first?.items.first?.url, "https://cdn/a.jpg")
|
||||
}
|
||||
|
||||
func testPreviewDeleteReloadsFolderDetail() async {
|
||||
let api = MockLiveAPI()
|
||||
api.folderDetailResponses = [
|
||||
folder(id: 4, items: [LiveAlbumFile(url: "a", type: 2, size: 1, id: 10)]),
|
||||
folder(id: 4, items: []),
|
||||
]
|
||||
let viewModel = LiveAlbumPreviewViewModel(folderId: 4, startIndex: 0)
|
||||
|
||||
await viewModel.load(api: api)
|
||||
await viewModel.deleteCurrentFile(api: api)
|
||||
|
||||
XCTAssertEqual(api.deleteFileCalls.first?.folderId, 4)
|
||||
XCTAssertEqual(api.deleteFileCalls.first?.fileId, 10)
|
||||
XCTAssertTrue(viewModel.files.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class MockLiveAPI: LiveServing {
|
||||
var liveListResponses: [LiveListResponse] = []
|
||||
var detailResponses: [LiveItem] = []
|
||||
var albumListResponses: [LiveAlbumFolderListResponse] = []
|
||||
var folderDetailResponses: [LiveAlbumFolder] = []
|
||||
|
||||
var liveListCalls: [(scenicId: String, page: Int, pageSize: Int)] = []
|
||||
var createLiveCalls: [(scenicId: String, title: String, coverImg: String)] = []
|
||||
var startCalls: [Int] = []
|
||||
var stopCalls: [Int] = []
|
||||
var finishCalls: [Int] = []
|
||||
var modeCalls: [(liveId: Int, mode: Int)] = []
|
||||
var albumListCalls: [(scenicId: String, startTime: String?, endTime: String?, page: Int, pageSize: Int)] = []
|
||||
var createAlbumCalls: [(scenicId: String, name: String, items: [LiveAlbumFile])] = []
|
||||
var deleteFolderCalls: [Int] = []
|
||||
var deleteFileCalls: [(folderId: Int, fileId: Int)] = []
|
||||
|
||||
func liveList(scenicId: String, page: Int, pageSize: Int) async throws -> LiveListResponse {
|
||||
liveListCalls.append((scenicId, page, pageSize))
|
||||
return liveListResponses.isEmpty ? LiveListResponse() : liveListResponses.removeFirst()
|
||||
}
|
||||
|
||||
func liveDetail(liveId: Int) async throws -> LiveItem {
|
||||
detailResponses.isEmpty ? liveItem(id: liveId) : detailResponses.removeFirst()
|
||||
}
|
||||
|
||||
func createLive(scenicId: String, title: String, coverImg: String) async throws {
|
||||
createLiveCalls.append((scenicId, title, coverImg))
|
||||
}
|
||||
|
||||
func startLive(liveId: Int) async throws {
|
||||
startCalls.append(liveId)
|
||||
}
|
||||
|
||||
func stopLive(liveId: Int) async throws {
|
||||
stopCalls.append(liveId)
|
||||
}
|
||||
|
||||
func finishLive(liveId: Int) async throws {
|
||||
finishCalls.append(liveId)
|
||||
}
|
||||
|
||||
func setPushMode(liveId: Int, mode: Int) async throws {
|
||||
modeCalls.append((liveId, mode))
|
||||
}
|
||||
|
||||
func albumList(scenicId: String, startTime: String?, endTime: String?, page: Int, pageSize: Int) async throws -> LiveAlbumFolderListResponse {
|
||||
albumListCalls.append((scenicId, startTime, endTime, page, pageSize))
|
||||
return albumListResponses.isEmpty ? LiveAlbumFolderListResponse() : albumListResponses.removeFirst()
|
||||
}
|
||||
|
||||
func createAlbumFolder(scenicId: String, name: String, items: [LiveAlbumFile]) async throws {
|
||||
createAlbumCalls.append((scenicId, name, items))
|
||||
}
|
||||
|
||||
func deleteAlbumFolder(folderId: Int) async throws {
|
||||
deleteFolderCalls.append(folderId)
|
||||
}
|
||||
|
||||
func albumFolderDetail(folderId: Int) async throws -> LiveAlbumFolder {
|
||||
folderDetailResponses.isEmpty ? folder(id: folderId) : folderDetailResponses.removeFirst()
|
||||
}
|
||||
|
||||
func deleteAlbumFile(folderId: Int, fileId: Int) async throws {
|
||||
deleteFileCalls.append((folderId, fileId))
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class MockLiveUploader: LiveOSSUploading {
|
||||
private var urls: [String]
|
||||
|
||||
init(urls: [String]) {
|
||||
self.urls = urls
|
||||
}
|
||||
|
||||
func uploadLiveCover(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||||
urls.isEmpty ? "https://cdn/default.jpg" : urls.removeFirst()
|
||||
}
|
||||
|
||||
func uploadLiveAlbumFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||||
urls.isEmpty ? "https://cdn/default.jpg" : urls.removeFirst()
|
||||
}
|
||||
}
|
||||
|
||||
private func liveItem(id: Int, status: Int = 1, mode: Int = 1) -> LiveItem {
|
||||
LiveItem(id: id, title: "直播\(id)", coverImg: "", pushUrl: "rtmp://push/\(id)", duration: 60, status: status, statusLabel: "", manualPushMode: mode)
|
||||
}
|
||||
|
||||
private func folder(id: Int, items: [LiveAlbumFile] = []) -> LiveAlbumFolder {
|
||||
LiveAlbumFolder(id: id, albumId: id, name: "相册\(id)", creator: "", items: items)
|
||||
}
|
||||
|
||||
private func date(_ text: String) -> Date {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
formatter.calendar = Calendar(identifier: .gregorian)
|
||||
formatter.locale = Locale(identifier: "zh_CN")
|
||||
return formatter.date(from: text)!
|
||||
}
|
||||
Reference in New Issue
Block a user