Add location report map page and history list aligned with Android.
Extract shared LocationReportService, wire location_report menu routing, and add unit tests for reporting cooldown and history pagination. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
77
suixinkanTests/LocationReportHistoryViewModelTests.swift
Normal file
77
suixinkanTests/LocationReportHistoryViewModelTests.swift
Normal file
@ -0,0 +1,77 @@
|
||||
//
|
||||
// LocationReportHistoryViewModelTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
@MainActor
|
||||
/// 位置上报历史 ViewModel 测试。
|
||||
final class LocationReportHistoryViewModelTests: XCTestCase {
|
||||
|
||||
private var appStore: AppStore!
|
||||
private var defaults: UserDefaults!
|
||||
|
||||
override func setUp() {
|
||||
defaults = UserDefaults(suiteName: "LocationReportHistoryViewModelTests")!
|
||||
defaults.removePersistentDomain(forName: "LocationReportHistoryViewModelTests")
|
||||
appStore = AppStore(defaults: defaults)
|
||||
appStore.userId = "staff-99"
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
appStore.logout()
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testLoadReportListPopulatesItems() async throws {
|
||||
let listJSON = """
|
||||
{"code":100000,"msg":"success","data":{"list":[{"id":1,"staff_id":99,"type":1,"latitude":"30.1","longitude":"120.2","address":"测试地址","remark":"","created_at":"2026-01-01 10:00:00"}],"total":1}}
|
||||
""".data(using: .utf8)!
|
||||
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [listJSON])))
|
||||
let viewModel = LocationReportHistoryViewModel(appStore: appStore)
|
||||
|
||||
await viewModel.loadReportList(api: api, refresh: true)
|
||||
|
||||
XCTAssertEqual(viewModel.reportList.count, 1)
|
||||
XCTAssertEqual(viewModel.reportList.first?.address, "测试地址")
|
||||
XCTAssertFalse(viewModel.canLoadMore)
|
||||
}
|
||||
|
||||
func testSelectFilterReloadsWithTypeCode() async throws {
|
||||
let allJSON = """
|
||||
{"code":100000,"msg":"success","data":{"list":[{"id":1,"staff_id":99,"type":1,"latitude":"30","longitude":"120","address":"A","remark":"","created_at":"2026-01-01 10:00:00"}],"total":1}}
|
||||
""".data(using: .utf8)!
|
||||
let markedJSON = """
|
||||
{"code":100000,"msg":"success","data":{"list":[{"id":2,"staff_id":99,"type":2,"latitude":"31","longitude":"121","address":"B","remark":"","created_at":"2026-01-02 10:00:00"}],"total":1}}
|
||||
""".data(using: .utf8)!
|
||||
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [allJSON, markedJSON])))
|
||||
let viewModel = LocationReportHistoryViewModel(appStore: appStore)
|
||||
|
||||
await viewModel.loadReportList(api: api, refresh: true)
|
||||
await viewModel.selectFilter(.marked, api: api)
|
||||
|
||||
XCTAssertEqual(viewModel.selectedFilter, .marked)
|
||||
XCTAssertEqual(viewModel.reportList.first?.type, 2)
|
||||
XCTAssertEqual(viewModel.reportList.first?.address, "B")
|
||||
}
|
||||
|
||||
func testPaginationAppendsItems() async throws {
|
||||
let page1JSON = """
|
||||
{"code":100000,"msg":"success","data":{"list":[{"id":1,"staff_id":99,"type":1,"latitude":"30","longitude":"120","address":"第一页","remark":"","created_at":"2026-01-01 10:00:00"}],"total":2}}
|
||||
""".data(using: .utf8)!
|
||||
let page2JSON = """
|
||||
{"code":100000,"msg":"success","data":{"list":[{"id":2,"staff_id":99,"type":1,"latitude":"31","longitude":"121","address":"第二页","remark":"","created_at":"2026-01-02 10:00:00"}],"total":2}}
|
||||
""".data(using: .utf8)!
|
||||
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [page1JSON, page2JSON])))
|
||||
let viewModel = LocationReportHistoryViewModel(appStore: appStore)
|
||||
|
||||
await viewModel.loadReportList(api: api, refresh: true)
|
||||
await viewModel.loadMoreIfNeeded(currentIndex: 0, api: api)
|
||||
|
||||
XCTAssertEqual(viewModel.reportList.count, 2)
|
||||
XCTAssertEqual(viewModel.reportList.last?.address, "第二页")
|
||||
XCTAssertFalse(viewModel.canLoadMore)
|
||||
}
|
||||
}
|
||||
98
suixinkanTests/LocationReportServiceTests.swift
Normal file
98
suixinkanTests/LocationReportServiceTests.swift
Normal file
@ -0,0 +1,98 @@
|
||||
//
|
||||
// LocationReportServiceTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
@MainActor
|
||||
/// 位置上报共享服务测试。
|
||||
final class LocationReportServiceTests: XCTestCase {
|
||||
|
||||
private var appStore: AppStore!
|
||||
private var defaults: UserDefaults!
|
||||
|
||||
override func setUp() {
|
||||
defaults = UserDefaults(suiteName: "LocationReportServiceTests")!
|
||||
defaults.removePersistentDomain(forName: "LocationReportServiceTests")
|
||||
appStore = AppStore(defaults: defaults)
|
||||
appStore.userId = "staff-1"
|
||||
appStore.currentScenicId = 100
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
appStore.logout()
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testReportWithCoordinatesType2UsesProvidedLocation() async throws {
|
||||
let reportJSON = """
|
||||
{"code":100000,"msg":"success","data":{"staff_id":"staff-1","expired":0,"status":1}}
|
||||
""".data(using: .utf8)!
|
||||
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [reportJSON])))
|
||||
let stateStore = HomeLocationStateStore(store: appStore)
|
||||
let service = LocationReportService(appStore: appStore, locationStateStore: stateStore)
|
||||
|
||||
let result = try await service.reportWithCoordinates(
|
||||
api: api,
|
||||
latitude: 30.1,
|
||||
longitude: 120.2,
|
||||
address: "标记地址",
|
||||
type: 2
|
||||
)
|
||||
|
||||
XCTAssertTrue(result.shouldShowSuccessDialog)
|
||||
XCTAssertEqual(result.locationInfoText, "标记地址")
|
||||
XCTAssertTrue(stateStore.isOnline)
|
||||
}
|
||||
|
||||
func testTooFrequentManualReportThrows() async throws {
|
||||
let reportJSON = """
|
||||
{"code":100000,"msg":"success","data":{"staff_id":"staff-1","expired":0,"status":1}}
|
||||
""".data(using: .utf8)!
|
||||
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [reportJSON, reportJSON])))
|
||||
let stateStore = HomeLocationStateStore(store: appStore)
|
||||
let service = LocationReportService(appStore: appStore, locationStateStore: stateStore)
|
||||
|
||||
_ = try await service.reportWithCoordinates(
|
||||
api: api,
|
||||
latitude: 30.0,
|
||||
longitude: 120.0,
|
||||
address: "测试地址",
|
||||
type: 1
|
||||
)
|
||||
do {
|
||||
_ = try await service.reportWithCoordinates(
|
||||
api: api,
|
||||
latitude: 30.0,
|
||||
longitude: 120.0,
|
||||
address: "测试地址",
|
||||
type: 1
|
||||
)
|
||||
XCTFail("Expected too frequent error")
|
||||
} catch let error as LocationReportServiceError {
|
||||
if case .tooFrequent = error {
|
||||
XCTAssertTrue(true)
|
||||
} else {
|
||||
XCTFail("Unexpected error \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testMissingScenicThrows() async {
|
||||
appStore.currentScenicId = 0
|
||||
let api = HomeAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [])))
|
||||
let stateStore = HomeLocationStateStore(store: appStore)
|
||||
let service = LocationReportService(appStore: appStore, locationStateStore: stateStore)
|
||||
|
||||
do {
|
||||
_ = try await service.manualReport(api: api)
|
||||
XCTFail("Expected missing scenic")
|
||||
} catch let error as LocationReportServiceError {
|
||||
XCTAssertEqual(error, .missingScenic)
|
||||
} catch {
|
||||
XCTFail("Unexpected error \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user