192 lines
6.5 KiB
Swift
192 lines
6.5 KiB
Swift
//
|
|
// TravelAlbumAIRetouchTemplateViewModelTests.swift
|
|
// suixinkanTests
|
|
//
|
|
|
|
import XCTest
|
|
@testable import suixinkan
|
|
|
|
/// AI 修图模板选择状态测试。
|
|
@MainActor
|
|
final class TravelAlbumAIRetouchTemplateViewModelTests: XCTestCase {
|
|
func testLoadDefaultsRequiredSelectionsAndLeavesAtmosphereEmpty() async {
|
|
let api = makeAPI()
|
|
let viewModel = TravelAlbumAIRetouchTemplateViewModel(
|
|
albumId: 8,
|
|
scenicId: 18,
|
|
materialIds: [4, 3, 2, 1]
|
|
)
|
|
|
|
await viewModel.loadTemplates(api: api)
|
|
|
|
XCTAssertEqual(api.aiRetouchTemplateScenicIds, [18])
|
|
XCTAssertEqual(viewModel.selectedRefinedTemplateId, 11)
|
|
XCTAssertNil(viewModel.selectedAtmosphereTemplateId)
|
|
XCTAssertEqual(viewModel.selectedCoverTemplateId, 31)
|
|
XCTAssertTrue(viewModel.showsCoverTemplates)
|
|
XCTAssertTrue(viewModel.canSubmit)
|
|
}
|
|
|
|
func testAtmosphereSelectionTogglesOffWhenTappedAgain() async {
|
|
let api = makeAPI()
|
|
let viewModel = TravelAlbumAIRetouchTemplateViewModel(
|
|
albumId: 8,
|
|
scenicId: 18,
|
|
materialIds: [1]
|
|
)
|
|
await viewModel.loadTemplates(api: api)
|
|
|
|
viewModel.toggleTemplate(id: 21, category: .atmosphere)
|
|
XCTAssertEqual(viewModel.selectedAtmosphereTemplateId, 21)
|
|
|
|
viewModel.toggleTemplate(id: 21, category: .atmosphere)
|
|
XCTAssertNil(viewModel.selectedAtmosphereTemplateId)
|
|
}
|
|
|
|
func testThreeMaterialsHideCoverAndOmitCoverFromSubmission() async {
|
|
let api = makeAPI()
|
|
let viewModel = TravelAlbumAIRetouchTemplateViewModel(
|
|
albumId: 8,
|
|
scenicId: 18,
|
|
materialIds: [3, 1, 2]
|
|
)
|
|
var submitted = false
|
|
viewModel.onSubmitted = { submitted = true }
|
|
await viewModel.loadTemplates(api: api)
|
|
|
|
XCTAssertFalse(viewModel.showsCoverTemplates)
|
|
XCTAssertNil(viewModel.selectedCoverTemplateId)
|
|
|
|
await viewModel.submit(api: api)
|
|
|
|
XCTAssertTrue(submitted)
|
|
XCTAssertEqual(api.aiRetouchRequests.count, 1)
|
|
XCTAssertEqual(api.aiRetouchRequests.first?.materialIds, [1, 2, 3])
|
|
XCTAssertNil(api.aiRetouchRequests.first?.aiPreviewTabs)
|
|
}
|
|
|
|
func testFourMaterialsSubmitAllSelectedTemplateIds() async {
|
|
let api = makeAPI()
|
|
let viewModel = TravelAlbumAIRetouchTemplateViewModel(
|
|
albumId: 8,
|
|
scenicId: 18,
|
|
materialIds: [1, 2, 3, 4]
|
|
)
|
|
await viewModel.loadTemplates(api: api)
|
|
viewModel.toggleTemplate(id: 12, category: .refined)
|
|
viewModel.toggleTemplate(id: 21, category: .atmosphere)
|
|
|
|
await viewModel.submit(api: api)
|
|
|
|
XCTAssertEqual(
|
|
api.aiRetouchRequests.first,
|
|
TravelAlbumAIRetouchRequest(
|
|
userEquityTravelId: 8,
|
|
materialIds: [1, 2, 3, 4],
|
|
refinedTemplateId: 12,
|
|
atmosphereTemplateId: 21,
|
|
aiPreviewTabs: 31
|
|
)
|
|
)
|
|
}
|
|
|
|
func testMissingRequiredCoverTemplateDisablesSubmission() async {
|
|
let api = makeAPI()
|
|
api.aiRetouchTemplatesResponse = TravelAlbumAIRetouchTemplatesResponse(
|
|
refinedTemplates: [template(11, "清透")],
|
|
atmosphereTemplates: [template(21, "暖阳")],
|
|
coverTemplates: []
|
|
)
|
|
let viewModel = TravelAlbumAIRetouchTemplateViewModel(
|
|
albumId: 8,
|
|
scenicId: 18,
|
|
materialIds: [1, 2, 3, 4]
|
|
)
|
|
|
|
await viewModel.loadTemplates(api: api)
|
|
|
|
XCTAssertFalse(viewModel.canSubmit)
|
|
XCTAssertEqual(viewModel.validationMessage, "暂无可用的封面风格模板")
|
|
}
|
|
|
|
func testLoadFailureExposesRetryMessageAndKeepsSubmissionDisabled() async {
|
|
let api = makeAPI()
|
|
api.aiRetouchTemplatesError = APIError.serverCode(500, "模板服务繁忙")
|
|
let viewModel = TravelAlbumAIRetouchTemplateViewModel(
|
|
albumId: 8,
|
|
scenicId: 18,
|
|
materialIds: [1]
|
|
)
|
|
|
|
await viewModel.loadTemplates(api: api)
|
|
|
|
XCTAssertEqual(viewModel.loadErrorMessage, "模板服务繁忙")
|
|
XCTAssertFalse(viewModel.canSubmit)
|
|
XCTAssertTrue(viewModel.refinedTemplates.isEmpty)
|
|
}
|
|
|
|
func testSubmitFailureKeepsSelectionAndAllowsRetry() async {
|
|
let api = makeAPI()
|
|
api.submitAIRetouchError = APIError.serverCode(500, "提交服务繁忙")
|
|
let viewModel = TravelAlbumAIRetouchTemplateViewModel(
|
|
albumId: 8,
|
|
scenicId: 18,
|
|
materialIds: [1]
|
|
)
|
|
var message: String?
|
|
var submitted = false
|
|
viewModel.onShowMessage = { message = $0 }
|
|
viewModel.onSubmitted = { submitted = true }
|
|
await viewModel.loadTemplates(api: api)
|
|
|
|
await viewModel.submit(api: api)
|
|
|
|
XCTAssertEqual(api.aiRetouchRequests.count, 1)
|
|
XCTAssertEqual(message, "提交服务繁忙")
|
|
XCTAssertFalse(submitted)
|
|
XCTAssertFalse(viewModel.isSubmitting)
|
|
XCTAssertTrue(viewModel.canSubmit)
|
|
XCTAssertEqual(viewModel.selectedRefinedTemplateId, 11)
|
|
}
|
|
|
|
func testSubmittingPreventsDuplicateRequest() async {
|
|
let api = makeAPI()
|
|
api.submitAIRetouchDelayNanoseconds = 50_000_000
|
|
let viewModel = TravelAlbumAIRetouchTemplateViewModel(
|
|
albumId: 8,
|
|
scenicId: 18,
|
|
materialIds: [1]
|
|
)
|
|
await viewModel.loadTemplates(api: api)
|
|
|
|
let firstSubmission = Task { await viewModel.submit(api: api) }
|
|
await waitUntil { viewModel.isSubmitting }
|
|
await viewModel.submit(api: api)
|
|
await firstSubmission.value
|
|
|
|
XCTAssertEqual(api.aiRetouchRequests.count, 1)
|
|
XCTAssertFalse(viewModel.isSubmitting)
|
|
}
|
|
|
|
private func makeAPI() -> TravelAlbumMockAPI {
|
|
let api = TravelAlbumMockAPI()
|
|
api.aiRetouchTemplatesResponse = TravelAlbumAIRetouchTemplatesResponse(
|
|
refinedTemplates: [template(11, "清透"), template(12, "自然")],
|
|
atmosphereTemplates: [template(21, "暖阳")],
|
|
coverTemplates: [template(31, "杂志")]
|
|
)
|
|
return api
|
|
}
|
|
|
|
private func template(_ id: Int, _ name: String) -> TravelAlbumAIRetouchTemplate {
|
|
TravelAlbumAIRetouchTemplate(id: id, name: name, previewURL: "https://cdn.example.com/\(id).jpg")
|
|
}
|
|
|
|
private func waitUntil(_ condition: @escaping () -> Bool) async {
|
|
for _ in 0 ..< 100 {
|
|
if condition() { return }
|
|
await Task.yield()
|
|
}
|
|
}
|
|
}
|