完善实名认证、钱包与打卡点模块 UI 与业务逻辑。
对齐 Android 实名认证流程与审核页、钱包提现/积分兑换界面,优化打卡点列表与详情交互,并补充相关资源、主题 token 与单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -39,8 +39,10 @@ final class PunchPointViewModelTests: XCTestCase {
|
||||
let viewModel = PunchPointListViewModel(currentScenicIdProvider: { 9 })
|
||||
var messages: [String] = []
|
||||
var qrcode: String?
|
||||
var removalID: Int64?
|
||||
viewModel.onShowMessage = { messages.append($0) }
|
||||
viewModel.onShowQRCode = { qrcode = $0 }
|
||||
viewModel.onBeginDeleteAnimation = { removalID = $0 }
|
||||
|
||||
await viewModel.loadInitial(api: api)
|
||||
viewModel.showQRCode(for: viewModel.items[1])
|
||||
@ -50,6 +52,9 @@ final class PunchPointViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(messages, ["未审核通过不可查看二维码", "暂无二维码"])
|
||||
XCTAssertEqual(qrcode, "https://cdn/qr.png")
|
||||
XCTAssertEqual(removalID, 1)
|
||||
XCTAssertEqual(viewModel.items.map(\.id), [1, 2])
|
||||
viewModel.completeDeleteAnimation(id: 1)
|
||||
XCTAssertEqual(viewModel.items.map(\.id), [2])
|
||||
XCTAssertEqual(api.deletedIds, [1])
|
||||
}
|
||||
@ -140,6 +145,59 @@ final class PunchPointViewModelTests: XCTestCase {
|
||||
XCTAssertTrue(api.addRequests.isEmpty)
|
||||
}
|
||||
|
||||
func testImageLimitAcceptsNineAndRejectsFurtherSelection() async {
|
||||
let uploader = MockPunchPointUploader()
|
||||
let viewModel = PunchPointFormViewModel(currentScenicIdProvider: { 7 })
|
||||
var messages: [String] = []
|
||||
viewModel.onShowMessage = { messages.append($0) }
|
||||
let images = (0 ..< 10).map {
|
||||
PunchPointImageState(data: Data([UInt8($0)]), fileName: "\($0).jpg")
|
||||
}
|
||||
|
||||
await viewModel.addLocalImages(images, uploader: uploader)
|
||||
await viewModel.addLocalImages(
|
||||
[PunchPointImageState(data: Data([99]), fileName: "extra.jpg")],
|
||||
uploader: uploader
|
||||
)
|
||||
|
||||
XCTAssertEqual(viewModel.images.count, 9)
|
||||
XCTAssertEqual(uploader.uploads.count, 9)
|
||||
XCTAssertEqual(messages.last, "最多只能选择9张图片")
|
||||
}
|
||||
|
||||
func testFailedImageCanRetryAndBecomeUploaded() async {
|
||||
let uploader = SequencedPunchPointUploader()
|
||||
let viewModel = PunchPointFormViewModel(currentScenicIdProvider: { 7 })
|
||||
|
||||
await viewModel.addLocalImages(
|
||||
[PunchPointImageState(data: Data([1]), fileName: "retry.jpg")],
|
||||
uploader: uploader
|
||||
)
|
||||
XCTAssertNotNil(viewModel.images.first?.errorMessage)
|
||||
|
||||
await viewModel.retryUpload(at: 0, uploader: uploader)
|
||||
|
||||
XCTAssertNil(viewModel.images.first?.errorMessage)
|
||||
XCTAssertEqual(viewModel.images.first?.uploadedURL, "https://cdn/retry.jpg")
|
||||
XCTAssertEqual(uploader.callCount, 2)
|
||||
}
|
||||
|
||||
func testAutoLocationFillsCoordinateAndAddress() async {
|
||||
let location = MockPunchPointLocationProvider(
|
||||
snapshot: HomeLocationSnapshot(latitude: 31.2, longitude: 121.5, address: "当前位置地址")
|
||||
)
|
||||
let viewModel = PunchPointFormViewModel(currentScenicIdProvider: { 7 }, locationProvider: location)
|
||||
var changedCoordinate: CLLocationCoordinate2D?
|
||||
viewModel.onCoordinateChange = { changedCoordinate = $0 }
|
||||
|
||||
await viewModel.autoLocation()
|
||||
|
||||
XCTAssertEqual(viewModel.coordinate?.latitude, 31.2)
|
||||
XCTAssertEqual(viewModel.coordinate?.longitude, 121.5)
|
||||
XCTAssertEqual(viewModel.address, "当前位置地址")
|
||||
XCTAssertEqual(changedCoordinate?.latitude, 31.2)
|
||||
}
|
||||
|
||||
private func item(id: Int64, status: Int = 1, mpQrcode: String? = nil) -> PunchPointItem {
|
||||
PunchPointItem(
|
||||
id: id,
|
||||
@ -233,6 +291,25 @@ private final class SlowPunchPointUploader: PunchPointImageUploading {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class SequencedPunchPointUploader: PunchPointImageUploading {
|
||||
var callCount = 0
|
||||
|
||||
func uploadPunchPointImage(
|
||||
data: Data,
|
||||
fileName: String,
|
||||
scenicId: Int,
|
||||
onProgress: @escaping (Int) -> Void
|
||||
) async throws -> String {
|
||||
callCount += 1
|
||||
if callCount == 1 {
|
||||
throw URLError(.cannotConnectToHost)
|
||||
}
|
||||
onProgress(100)
|
||||
return "https://cdn/retry.jpg"
|
||||
}
|
||||
}
|
||||
|
||||
private final class MockPunchPointLocationProvider: LocationProviding, @unchecked Sendable {
|
||||
let snapshot: HomeLocationSnapshot
|
||||
let reverseAddress: String
|
||||
|
||||
Reference in New Issue
Block a user