Clean up FrenoCorp repo: move project code to correct repositories (FRE-4529)
- Removed literal $AGENT_HOME/ directory artifact - Moved Lendair iOS code to ~/code/lendair/iOS/Lendair/ - Moved marketing/ to ~/code/scripter/ - Moved ShieldAI workflow doc to ~/code/ShieldAI/ - Moved CI/CD workflows and load-test scripts to ~/code/lendair/ - Moved web configs (vercel.json, .env.example, index.html) to ~/code/lendair/web/ - Removed root-level project configs (package.json, tsconfig.json, vite.config.ts, etc.) - Removed shared/exports/ and scripts/ - Updated all 8 agent AGENTS.md files with Repository Rules section - Clarified: FrenoCorp is for agent notes/memories/plans only, not project code Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,447 +0,0 @@
|
||||
import XCTest
|
||||
import SwiftUI
|
||||
@testable import Lendair
|
||||
|
||||
// MARK: - Mock Challenge Service
|
||||
|
||||
final class MockChallengeService: ChallengeServiceProtocol {
|
||||
var challenges: [Challenge] = []
|
||||
var selectedChallenge: (challenge: Challenge, participants: [ChallengeParticipant])?
|
||||
var joinCalledIds: [String] = []
|
||||
var leaveCalledIds: [String] = []
|
||||
var createCalled = false
|
||||
var updateCalled = false
|
||||
var leaderboard: [LeaderboardEntry] = []
|
||||
var listCallCount = 0
|
||||
var listError: Error?
|
||||
|
||||
func listChallenges(filter: ChallengeFilter = ChallengeFilter()) async throws -> [Challenge] {
|
||||
listCallCount += 1
|
||||
if let error = listError { throw error }
|
||||
return challenges
|
||||
}
|
||||
|
||||
func getChallenge(id: String) async throws -> (challenge: Challenge, participants: [ChallengeParticipant]) {
|
||||
if let selected = selectedChallenge { return selected }
|
||||
throw ChallengeError.notFound
|
||||
}
|
||||
|
||||
func createChallenge(request: CreateChallengeRequest) async throws -> Challenge {
|
||||
createCalled = true
|
||||
return Challenge(
|
||||
id: "new-1",
|
||||
title: request.title,
|
||||
description: request.description,
|
||||
challengeType: request.challengeType,
|
||||
status: .active,
|
||||
startDate: request.startDate,
|
||||
endDate: request.endDate,
|
||||
targetMetric: request.targetMetric,
|
||||
targetValue: request.targetValue,
|
||||
targetUnit: request.targetMetric.unit,
|
||||
participantCount: 1,
|
||||
rules: request.rules,
|
||||
imageUrl: nil,
|
||||
createdBy: "current-user",
|
||||
createdByName: "Current User",
|
||||
clubId: request.clubId,
|
||||
participationStatus: .participating,
|
||||
userProgress: 0,
|
||||
createdAt: Date()
|
||||
)
|
||||
}
|
||||
|
||||
func updateChallenge(id: String, request: UpdateChallengeRequest) async throws -> Challenge {
|
||||
updateCalled = true
|
||||
return Challenge(
|
||||
id: id,
|
||||
title: request.title ?? "Updated",
|
||||
description: request.description ?? "",
|
||||
challengeType: request.challengeType ?? .distance,
|
||||
status: .active,
|
||||
startDate: Date(),
|
||||
endDate: Date().addingTimeInterval(30 * 24 * 3600),
|
||||
targetMetric: request.targetMetric ?? .distance,
|
||||
targetValue: request.targetValue ?? 100,
|
||||
targetUnit: (request.targetMetric ?? .distance).unit,
|
||||
participantCount: 0,
|
||||
rules: request.rules,
|
||||
imageUrl: nil,
|
||||
createdBy: "current-user",
|
||||
createdByName: "Current User",
|
||||
clubId: nil,
|
||||
participationStatus: .participating,
|
||||
userProgress: 0,
|
||||
createdAt: Date()
|
||||
)
|
||||
}
|
||||
|
||||
func joinChallenge(id: String) async throws {
|
||||
joinCalledIds.append(id)
|
||||
}
|
||||
|
||||
func leaveChallenge(id: String) async throws {
|
||||
leaveCalledIds.append(id)
|
||||
}
|
||||
|
||||
func getLeaderboard(challengeId: String) async throws -> [LeaderboardEntry] {
|
||||
return leaderboard
|
||||
}
|
||||
|
||||
func submitProgress(challengeId: String, progress: ProgressSubmission) async throws -> (progress: Double, percentage: Double) {
|
||||
return (progress.value, min((progress.value / 100) * 100, 100))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helper: Sample Challenges
|
||||
|
||||
extension Challenge {
|
||||
static func sample(
|
||||
id: String = "test-1",
|
||||
title: String = "Test Challenge",
|
||||
challengeType: ChallengeType = .distance,
|
||||
status: ChallengeStatus = .active,
|
||||
participationStatus: ParticipationStatus = .participating,
|
||||
userProgress: Double = 0,
|
||||
targetValue: Double = 100,
|
||||
startDate: Date = Date().addingTimeInterval(-7 * 24 * 3600),
|
||||
endDate: Date = Date().addingTimeInterval(23 * 24 * 3600)
|
||||
) -> Challenge {
|
||||
Challenge(
|
||||
id: id,
|
||||
title: title,
|
||||
description: "Test description",
|
||||
challengeType: challengeType,
|
||||
status: status,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
targetMetric: .distance,
|
||||
targetValue: targetValue,
|
||||
targetUnit: "km",
|
||||
participantCount: 10,
|
||||
rules: nil,
|
||||
imageUrl: nil,
|
||||
createdBy: "user-1",
|
||||
createdByName: "Test User",
|
||||
clubId: nil,
|
||||
participationStatus: participationStatus,
|
||||
userProgress: userProgress,
|
||||
createdAt: Date()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ChallengeServiceTests
|
||||
|
||||
final class ChallengeServiceTests: XCTestCase {
|
||||
// MARK: - Fetch Challenges
|
||||
|
||||
@MainActor
|
||||
func testFetchChallengesLoadsData() async {
|
||||
let mock = MockChallengeService()
|
||||
mock.challenges = [.sample(id: "1"), .sample(id: "2")]
|
||||
|
||||
let viewModel = ChallengeViewModel(service: mock)
|
||||
await viewModel.fetchChallenges()
|
||||
|
||||
XCTAssertEqual(viewModel.challenges.count, 2)
|
||||
XCTAssertFalse(viewModel.isLoading)
|
||||
XCTAssertEqual(mock.listCallCount, 1)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testFetchChallengesHandlesError() async {
|
||||
let mock = MockChallengeService()
|
||||
mock.listError = ChallengeError.unauthorized
|
||||
|
||||
let viewModel = ChallengeViewModel(service: mock)
|
||||
await viewModel.fetchChallenges()
|
||||
|
||||
XCTAssertTrue(viewModel.challenges.isEmpty)
|
||||
XCTAssertFalse(viewModel.isLoading)
|
||||
XCTAssertEqual(viewModel.error, .unauthorized)
|
||||
}
|
||||
|
||||
// MARK: - Challenge Type Display
|
||||
|
||||
func testChallengeTypeDisplayNames() {
|
||||
XCTAssertEqual(ChallengeType.distance.displayName, "Distance")
|
||||
XCTAssertEqual(ChallengeType.time.displayName, "Time")
|
||||
XCTAssertEqual(ChallengeType.frequency.displayName, "Frequency")
|
||||
XCTAssertEqual(ChallengeType.elevation.displayName, "Elevation")
|
||||
XCTAssertEqual(ChallengeType.calories.displayName, "Calories")
|
||||
XCTAssertEqual(ChallengeType.streak.displayName, "Streak")
|
||||
}
|
||||
|
||||
func testChallengeTypeIcons() {
|
||||
XCTAssertEqual(ChallengeType.distance.icon, "arrow.right.arrow.left")
|
||||
XCTAssertEqual(ChallengeType.time.icon, "stopwatch.fill")
|
||||
XCTAssertEqual(ChallengeType.frequency.icon, "repeat")
|
||||
XCTAssertEqual(ChallengeType.elevation.icon, "mountain.2.fill")
|
||||
XCTAssertEqual(ChallengeType.calories.icon, "flame.fill")
|
||||
XCTAssertEqual(ChallengeType.streak.icon, "calendar.badge.clock")
|
||||
}
|
||||
|
||||
func testChallengeMetricUnits() {
|
||||
XCTAssertEqual(ChallengeMetric.distance.unit, "km")
|
||||
XCTAssertEqual(ChallengeMetric.time.unit, "min")
|
||||
XCTAssertEqual(ChallengeMetric.frequency.unit, "sessions")
|
||||
XCTAssertEqual(ChallengeMetric.elevation.unit, "m")
|
||||
XCTAssertEqual(ChallengeMetric.calories.unit, "kcal")
|
||||
}
|
||||
|
||||
// MARK: - Challenge Time States
|
||||
|
||||
func testChallengeIsUpcoming() {
|
||||
let future = Challenge.sample(
|
||||
id: "1",
|
||||
startDate: Date().addingTimeInterval(7 * 24 * 3600),
|
||||
endDate: Date().addingTimeInterval(37 * 24 * 3600)
|
||||
)
|
||||
XCTAssertTrue(future.isUpcoming)
|
||||
}
|
||||
|
||||
func testChallengeIsActive() {
|
||||
let active = Challenge.sample(id: "1")
|
||||
XCTAssertTrue(active.isActive)
|
||||
}
|
||||
|
||||
func testChallengeIsCompleted() {
|
||||
let past = Challenge.sample(
|
||||
id: "1",
|
||||
startDate: Date().addingTimeInterval(-30 * 24 * 3600),
|
||||
endDate: Date().addingTimeInterval(-7 * 24 * 3600)
|
||||
)
|
||||
XCTAssertTrue(past.isCompleted)
|
||||
}
|
||||
|
||||
// MARK: - Progress Percentage
|
||||
|
||||
func testProgressPercentage() {
|
||||
var challenge = Challenge.sample(id: "1", userProgress: 50, targetValue: 100)
|
||||
XCTAssertEqual(challenge.progressPercentage, 50)
|
||||
}
|
||||
|
||||
func testProgressPercentageOverTarget() {
|
||||
var challenge = Challenge.sample(id: "1", userProgress: 120, targetValue: 100)
|
||||
XCTAssertEqual(challenge.progressPercentage, 100)
|
||||
}
|
||||
|
||||
func testProgressPercentageNoProgress() {
|
||||
var challenge = Challenge.sample(id: "1", userProgress: 0, targetValue: 100)
|
||||
XCTAssertEqual(challenge.progressPercentage, 0)
|
||||
}
|
||||
|
||||
func testProgressPercentageNilProgress() {
|
||||
var challenge = Challenge.sample(id: "1", userProgress: nil, targetValue: 100)
|
||||
XCTAssertEqual(challenge.progressPercentage, 0)
|
||||
}
|
||||
|
||||
// MARK: - Days Remaining
|
||||
|
||||
func testDaysRemaining() {
|
||||
let challenge = Challenge.sample(
|
||||
id: "1",
|
||||
endDate: Date().addingTimeInterval(5 * 24 * 3600)
|
||||
)
|
||||
XCTAssertGreaterThan(challenge.daysRemaining, 0)
|
||||
}
|
||||
|
||||
// MARK: - Computed Filters
|
||||
|
||||
@MainActor
|
||||
func testActiveChallengesFiltersCorrectly() async {
|
||||
let mock = MockChallengeService()
|
||||
mock.challenges = [
|
||||
Challenge.sample(id: "1", startDate: Date().addingTimeInterval(-1 * 86400), endDate: Date().addingTimeInterval(7 * 86400)),
|
||||
Challenge.sample(id: "2", startDate: Date().addingTimeInterval(7 * 86400), endDate: Date().addingTimeInterval(37 * 86400)),
|
||||
Challenge.sample(id: "3", startDate: Date().addingTimeInterval(-10 * 86400), endDate: Date().addingTimeInterval(-1 * 86400)),
|
||||
]
|
||||
|
||||
let viewModel = ChallengeViewModel(service: mock)
|
||||
await viewModel.fetchChallenges()
|
||||
|
||||
XCTAssertEqual(viewModel.activeChallenges.count, 1)
|
||||
XCTAssertEqual(viewModel.activeChallenges.first?.id, "1")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testUpcomingChallengesFiltersCorrectly() async {
|
||||
let mock = MockChallengeService()
|
||||
mock.challenges = [
|
||||
Challenge.sample(id: "1", startDate: Date().addingTimeInterval(-1 * 86400), endDate: Date().addingTimeInterval(7 * 86400)),
|
||||
Challenge.sample(id: "2", startDate: Date().addingTimeInterval(7 * 86400), endDate: Date().addingTimeInterval(37 * 86400)),
|
||||
]
|
||||
|
||||
let viewModel = ChallengeViewModel(service: mock)
|
||||
await viewModel.fetchChallenges()
|
||||
|
||||
XCTAssertEqual(viewModel.upcomingChallenges.count, 1)
|
||||
XCTAssertEqual(viewModel.upcomingChallenges.first?.id, "2")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testCompletedChallengesFiltersCorrectly() async {
|
||||
let mock = MockChallengeService()
|
||||
mock.challenges = [
|
||||
Challenge.sample(id: "1", startDate: Date().addingTimeInterval(-10 * 86400), endDate: Date().addingTimeInterval(-1 * 86400)),
|
||||
Challenge.sample(id: "2", startDate: Date().addingTimeInterval(-1 * 86400), endDate: Date().addingTimeInterval(7 * 86400)),
|
||||
]
|
||||
|
||||
let viewModel = ChallengeViewModel(service: mock)
|
||||
await viewModel.fetchChallenges()
|
||||
|
||||
XCTAssertEqual(viewModel.completedChallenges.count, 1)
|
||||
XCTAssertEqual(viewModel.completedChallenges.first?.id, "1")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testUserChallengesFiltersParticipating() async {
|
||||
let mock = MockChallengeService()
|
||||
mock.challenges = [
|
||||
Challenge.sample(id: "1", participationStatus: .participating),
|
||||
Challenge.sample(id: "2", participationStatus: .notParticipating),
|
||||
Challenge.sample(id: "3", participationStatus: .participating),
|
||||
]
|
||||
|
||||
let viewModel = ChallengeViewModel(service: mock)
|
||||
await viewModel.fetchChallenges()
|
||||
|
||||
XCTAssertEqual(viewModel.userChallenges.count, 2)
|
||||
XCTAssertTrue(viewModel.userChallenges.allSatisfy { $0.participationStatus == .participating })
|
||||
}
|
||||
|
||||
// MARK: - Join and Leave
|
||||
|
||||
@MainActor
|
||||
func testJoinChallengeUpdatesLocalState() async {
|
||||
let mock = MockChallengeService()
|
||||
let challenge = Challenge.sample(id: "1", participationStatus: .notParticipating)
|
||||
mock.challenges = [challenge]
|
||||
|
||||
let viewModel = ChallengeViewModel(service: mock)
|
||||
viewModel.challenges = [challenge]
|
||||
|
||||
await viewModel.joinChallenge(id: "1")
|
||||
|
||||
XCTAssertEqual(viewModel.challenges.first?.participationStatus, .participating)
|
||||
XCTAssertEqual(viewModel.challenges.first?.participantCount, 11)
|
||||
XCTAssertEqual(mock.joinCalledIds, ["1"])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testLeaveChallengeUpdatesLocalState() async {
|
||||
let mock = MockChallengeService()
|
||||
let challenge = Challenge.sample(id: "1", participationStatus: .participating)
|
||||
mock.challenges = [challenge]
|
||||
|
||||
let viewModel = ChallengeViewModel(service: mock)
|
||||
viewModel.challenges = [challenge]
|
||||
|
||||
await viewModel.leaveChallenge(id: "1")
|
||||
|
||||
XCTAssertEqual(viewModel.challenges.first?.participationStatus, .notParticipating)
|
||||
XCTAssertEqual(viewModel.challenges.first?.participantCount, 9)
|
||||
XCTAssertEqual(mock.leaveCalledIds, ["1"])
|
||||
}
|
||||
|
||||
// MARK: - Challenge Equality
|
||||
|
||||
func testChallengeEquality() {
|
||||
let a = Challenge.sample(id: "1", participationStatus: .participating)
|
||||
let b = Challenge.sample(id: "1", participationStatus: .participating)
|
||||
let c = Challenge.sample(id: "1", participationStatus: .notParticipating)
|
||||
|
||||
XCTAssertEqual(a, b)
|
||||
XCTAssertNotEqual(a, c)
|
||||
}
|
||||
|
||||
// MARK: - Create Challenge
|
||||
|
||||
@MainActor
|
||||
func testCreateChallengeAddsToList() async {
|
||||
let mock = MockChallengeService()
|
||||
let viewModel = ChallengeViewModel(service: mock)
|
||||
|
||||
let request = CreateChallengeRequest(
|
||||
title: "New Challenge",
|
||||
description: "A new challenge",
|
||||
challengeType: .distance,
|
||||
startDate: Date(),
|
||||
endDate: Date().addingTimeInterval(30 * 24 * 3600),
|
||||
targetMetric: .distance,
|
||||
targetValue: 50,
|
||||
rules: nil,
|
||||
clubId: nil
|
||||
)
|
||||
|
||||
let result = await viewModel.createChallenge(request: request)
|
||||
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(viewModel.challenges.count, 1)
|
||||
XCTAssertEqual(viewModel.challenges.first?.title, "New Challenge")
|
||||
XCTAssertTrue(mock.createCalled)
|
||||
}
|
||||
|
||||
// MARK: - Leaderboard
|
||||
|
||||
@MainActor
|
||||
func testFetchLeaderboardLoadsData() async {
|
||||
let mock = MockChallengeService()
|
||||
mock.leaderboard = [
|
||||
LeaderboardEntry(
|
||||
id: "1", position: 1, participantId: "user1",
|
||||
participantName: "Alice", participantAvatarUrl: nil,
|
||||
progress: 100, progressPercentage: 100
|
||||
),
|
||||
LeaderboardEntry(
|
||||
id: "2", position: 2, participantId: "user2",
|
||||
participantName: "Bob", participantAvatarUrl: nil,
|
||||
progress: 75, progressPercentage: 75
|
||||
),
|
||||
]
|
||||
|
||||
let viewModel = ChallengeViewModel(service: mock)
|
||||
await viewModel.fetchLeaderboard(challengeId: "1")
|
||||
|
||||
XCTAssertEqual(viewModel.leaderboard.count, 2)
|
||||
XCTAssertEqual(viewModel.leaderboard.first?.position, 1)
|
||||
}
|
||||
|
||||
// MARK: - Submit Progress
|
||||
|
||||
@MainActor
|
||||
func testSubmitProgressUpdatesChallenge() async {
|
||||
let mock = MockChallengeService()
|
||||
let challenge = Challenge.sample(id: "1", userProgress: 0)
|
||||
mock.challenges = [challenge]
|
||||
|
||||
let viewModel = ChallengeViewModel(service: mock)
|
||||
viewModel.challenges = [challenge]
|
||||
|
||||
let progress = ProgressSubmission(metric: .distance, value: 50, activityDate: Date())
|
||||
await viewModel.submitProgress(challengeId: "1", progress: progress)
|
||||
|
||||
XCTAssertEqual(viewModel.challenges.first?.userProgress, 50)
|
||||
}
|
||||
|
||||
// MARK: - Challenge Filter Defaults
|
||||
|
||||
func testChallengeFilterDefaults() {
|
||||
let filter = ChallengeFilter()
|
||||
XCTAssertEqual(filter.limit, 20)
|
||||
XCTAssertEqual(filter.offset, 0)
|
||||
XCTAssertNil(filter.challengeType)
|
||||
XCTAssertNil(filter.status)
|
||||
}
|
||||
|
||||
// MARK: - Challenge Status Cases
|
||||
|
||||
func testChallengeStatusCases() {
|
||||
XCTAssertEqual(ChallengeStatus.allCases.count, 4)
|
||||
XCTAssertEqual(ChallengeStatus.upcoming.rawValue, "upcoming")
|
||||
XCTAssertEqual(ChallengeStatus.active.rawValue, "active")
|
||||
XCTAssertEqual(ChallengeStatus.completed.rawValue, "completed")
|
||||
XCTAssertEqual(ChallengeStatus.cancelled.rawValue, "cancelled")
|
||||
}
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
import XCTest
|
||||
import SwiftUI
|
||||
@testable import Lendair
|
||||
|
||||
// MARK: - Mock Club Service
|
||||
|
||||
final class MockClubService: ClubServiceProtocol {
|
||||
var clubs: [Club] = []
|
||||
var selectedClub: (club: Club, members: [ClubMember])?
|
||||
var joinCalledIds: [String] = []
|
||||
var leaveCalledIds: [String] = []
|
||||
var createCalled = false
|
||||
var updateCalled = false
|
||||
var listCallCount = 0
|
||||
var listError: Error?
|
||||
|
||||
func listClubs(filter: ClubFilter = ClubFilter()) async throws -> [Club] {
|
||||
listCallCount += 1
|
||||
if let error = listError { throw error }
|
||||
return clubs
|
||||
}
|
||||
|
||||
func getClub(id: String) async throws -> (club: Club, members: [ClubMember]) {
|
||||
if let selected = selectedClub { return selected }
|
||||
throw ClubError.notFound
|
||||
}
|
||||
|
||||
func createClub(request: CreateClubRequest) async throws -> Club {
|
||||
createCalled = true
|
||||
return Club(
|
||||
id: "new-1",
|
||||
name: request.name,
|
||||
description: request.description,
|
||||
clubType: request.clubType,
|
||||
privacy: request.privacy,
|
||||
location: request.location,
|
||||
latitude: request.latitude,
|
||||
longitude: request.longitude,
|
||||
memberCount: 1,
|
||||
maxMembers: request.maxMembers,
|
||||
imageUrl: nil,
|
||||
rules: request.rules,
|
||||
ownerId: "current-user",
|
||||
ownerName: "Current User",
|
||||
membershipStatus: .active,
|
||||
createdAt: Date()
|
||||
)
|
||||
}
|
||||
|
||||
func updateClub(id: String, request: UpdateClubRequest) async throws -> Club {
|
||||
updateCalled = true
|
||||
return Club(
|
||||
id: id,
|
||||
name: request.name ?? "Updated",
|
||||
description: request.description ?? "",
|
||||
clubType: request.clubType ?? .running,
|
||||
privacy: request.privacy ?? .publicPrivacy,
|
||||
location: request.location ?? "",
|
||||
latitude: request.latitude,
|
||||
longitude: request.longitude,
|
||||
memberCount: 0,
|
||||
maxMembers: request.maxMembers,
|
||||
imageUrl: nil,
|
||||
rules: request.rules,
|
||||
ownerId: "current-user",
|
||||
ownerName: "Current User",
|
||||
membershipStatus: .active,
|
||||
createdAt: Date()
|
||||
)
|
||||
}
|
||||
|
||||
func joinClub(id: String) async throws {
|
||||
joinCalledIds.append(id)
|
||||
}
|
||||
|
||||
func leaveClub(id: String) async throws {
|
||||
leaveCalledIds.append(id)
|
||||
}
|
||||
|
||||
func inviteMember(clubId: String, email: String) async throws {}
|
||||
func removeMember(clubId: String, memberId: String) async throws {}
|
||||
}
|
||||
|
||||
// MARK: - Helper: Sample Clubs
|
||||
|
||||
extension Club {
|
||||
static func sample(
|
||||
id: String = "test-1",
|
||||
name: String = "Test Club",
|
||||
clubType: ClubType = .running,
|
||||
privacy: ClubPrivacy = .publicPrivacy,
|
||||
membershipStatus: MembershipStatus = .active
|
||||
) -> Club {
|
||||
Club(
|
||||
id: id,
|
||||
name: name,
|
||||
description: "Test description",
|
||||
clubType: clubType,
|
||||
privacy: privacy,
|
||||
location: "Test Location",
|
||||
latitude: nil,
|
||||
longitude: nil,
|
||||
memberCount: 10,
|
||||
maxMembers: 50,
|
||||
imageUrl: nil,
|
||||
rules: nil,
|
||||
ownerId: "owner-1",
|
||||
ownerName: "Test Owner",
|
||||
membershipStatus: membershipStatus,
|
||||
createdAt: Date()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ClubServiceTests
|
||||
|
||||
final class ClubServiceTests: XCTestCase {
|
||||
// MARK: - Fetch Clubs
|
||||
|
||||
@MainActor
|
||||
func testFetchClubsLoadsData() async {
|
||||
let mock = MockClubService()
|
||||
mock.clubs = [.sample(id: "1"), .sample(id: "2")]
|
||||
|
||||
let viewModel = ClubViewModel(service: mock)
|
||||
await viewModel.fetchClubs()
|
||||
|
||||
XCTAssertEqual(viewModel.clubs.count, 2)
|
||||
XCTAssertFalse(viewModel.isLoading)
|
||||
XCTAssertEqual(mock.listCallCount, 1)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testFetchClubsHandlesError() async {
|
||||
let mock = MockClubService()
|
||||
mock.listError = ClubError.unauthorized
|
||||
|
||||
let viewModel = ClubViewModel(service: mock)
|
||||
await viewModel.fetchClubs()
|
||||
|
||||
XCTAssertTrue(viewModel.clubs.isEmpty)
|
||||
XCTAssertFalse(viewModel.isLoading)
|
||||
XCTAssertEqual(viewModel.error, .unauthorized)
|
||||
}
|
||||
|
||||
// MARK: - Club Types
|
||||
|
||||
func testClubTypeDisplayNames() {
|
||||
XCTAssertEqual(ClubType.running.displayName, "Running")
|
||||
XCTAssertEqual(ClubType.walking.displayName, "Walking")
|
||||
XCTAssertEqual(ClubType.cycling.displayName, "Cycling")
|
||||
XCTAssertEqual(ClubType.triathlon.displayName, "Triathlon")
|
||||
XCTAssertEqual(ClubType.crossfit.displayName, "CrossFit")
|
||||
XCTAssertEqual(ClubType.general.displayName, "General Fitness")
|
||||
}
|
||||
|
||||
func testClubTypeIcons() {
|
||||
XCTAssertEqual(ClubType.running.icon, "figure.run")
|
||||
XCTAssertEqual(ClubType.walking.icon, "figure.walk")
|
||||
XCTAssertEqual(ClubType.cycling.icon, "bicycle")
|
||||
XCTAssertEqual(ClubType.triathlon.icon, "triangle.fill")
|
||||
XCTAssertEqual(ClubType.crossfit.icon, "dumbbell.fill")
|
||||
XCTAssertEqual(ClubType.general.icon, "heart.fill")
|
||||
}
|
||||
|
||||
func testClubPrivacyDisplayNames() {
|
||||
XCTAssertEqual(ClubPrivacy.publicPrivacy.displayName, "Public")
|
||||
XCTAssertEqual(ClubPrivacy.privateClub.displayName, "Private")
|
||||
XCTAssertEqual(ClubPrivacy.invitationOnly.displayName, "Invitation Only")
|
||||
}
|
||||
|
||||
// MARK: - Club Computed Properties
|
||||
|
||||
@MainActor
|
||||
func testPublicClubsFiltersCorrectly() async {
|
||||
let mock = MockClubService()
|
||||
mock.clubs = [
|
||||
.sample(id: "1", privacy: .publicPrivacy),
|
||||
.sample(id: "2", privacy: .privateClub),
|
||||
.sample(id: "3", privacy: .publicPrivacy),
|
||||
]
|
||||
|
||||
let viewModel = ClubViewModel(service: mock)
|
||||
await viewModel.fetchClubs()
|
||||
|
||||
XCTAssertEqual(viewModel.publicClubs.count, 2)
|
||||
XCTAssertEqual(viewModel.publicClubs.first?.id, "1")
|
||||
XCTAssertEqual(viewModel.publicClubs.last?.id, "3")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testUserClubsFiltersActiveMembers() async {
|
||||
let mock = MockClubService()
|
||||
mock.clubs = [
|
||||
.sample(id: "1", membershipStatus: .active),
|
||||
.sample(id: "2", membershipStatus: .pending),
|
||||
.sample(id: "3", membershipStatus: .active),
|
||||
]
|
||||
|
||||
let viewModel = ClubViewModel(service: mock)
|
||||
await viewModel.fetchClubs()
|
||||
|
||||
XCTAssertEqual(viewModel.userClubs.count, 2)
|
||||
XCTAssertTrue(viewModel.userClubs.allSatisfy { $0.membershipStatus == .active })
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testPendingClubsFiltersCorrectly() async {
|
||||
let mock = MockClubService()
|
||||
mock.clubs = [
|
||||
.sample(id: "1", membershipStatus: .active),
|
||||
.sample(id: "2", membershipStatus: .pending),
|
||||
.sample(id: "3", membershipStatus: .pending),
|
||||
]
|
||||
|
||||
let viewModel = ClubViewModel(service: mock)
|
||||
await viewModel.fetchClubs()
|
||||
|
||||
XCTAssertEqual(viewModel.pendingClubs.count, 2)
|
||||
}
|
||||
|
||||
// MARK: - Join and Leave
|
||||
|
||||
@MainActor
|
||||
func testJoinClubUpdatesLocalState() async {
|
||||
let mock = MockClubService()
|
||||
let club = Club.sample(id: "1", membershipStatus: .left)
|
||||
mock.clubs = [club]
|
||||
|
||||
let viewModel = ClubViewModel(service: mock)
|
||||
viewModel.clubs = [club]
|
||||
|
||||
await viewModel.joinClub(id: "1")
|
||||
|
||||
XCTAssertEqual(viewModel.clubs.first?.membershipStatus, .active)
|
||||
XCTAssertEqual(viewModel.clubs.first?.memberCount, 11)
|
||||
XCTAssertEqual(mock.joinCalledIds, ["1"])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testLeaveClubUpdatesLocalState() async {
|
||||
let mock = MockClubService()
|
||||
let club = Club.sample(id: "1", membershipStatus: .active, memberCount: 10)
|
||||
mock.clubs = [club]
|
||||
|
||||
let viewModel = ClubViewModel(service: mock)
|
||||
viewModel.clubs = [club]
|
||||
|
||||
await viewModel.leaveClub(id: "1")
|
||||
|
||||
XCTAssertEqual(viewModel.clubs.first?.membershipStatus, .left)
|
||||
XCTAssertEqual(viewModel.clubs.first?.memberCount, 9)
|
||||
XCTAssertEqual(mock.leaveCalledIds, ["1"])
|
||||
}
|
||||
|
||||
// MARK: - Club Equality
|
||||
|
||||
func testClubEquality() {
|
||||
let a = Club.sample(id: "1", membershipStatus: .active)
|
||||
let b = Club.sample(id: "1", membershipStatus: .active)
|
||||
let c = Club.sample(id: "1", membershipStatus: .pending)
|
||||
|
||||
XCTAssertEqual(a, b)
|
||||
XCTAssertNotEqual(a, c)
|
||||
}
|
||||
|
||||
// MARK: - Club Capacity
|
||||
|
||||
func testAvailableSpots() {
|
||||
let club = Club.sample(id: "1", memberCount: 10, maxMembers: 50)
|
||||
XCTAssertEqual(club.availableSpots, 40)
|
||||
}
|
||||
|
||||
func testIsFull() {
|
||||
let club = Club.sample(id: "1", memberCount: 50, maxMembers: 50)
|
||||
XCTAssertTrue(club.isFull)
|
||||
}
|
||||
|
||||
func testUnlimitedCapacity() {
|
||||
let club = Club.sample(id: "1", memberCount: 100, maxMembers: nil)
|
||||
XCTAssertFalse(club.isFull)
|
||||
XCTAssertNil(club.availableSpots)
|
||||
}
|
||||
|
||||
// MARK: - Create Club
|
||||
|
||||
@MainActor
|
||||
func testCreateClubAddsToList() async {
|
||||
let mock = MockClubService()
|
||||
let viewModel = ClubViewModel(service: mock)
|
||||
|
||||
let request = CreateClubRequest(
|
||||
name: "New Club",
|
||||
description: "A new club",
|
||||
clubType: .running,
|
||||
privacy: .publicPrivacy,
|
||||
location: "Test Location",
|
||||
latitude: nil,
|
||||
longitude: nil,
|
||||
maxMembers: 50,
|
||||
rules: nil
|
||||
)
|
||||
|
||||
let result = await viewModel.createClub(request: request)
|
||||
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(viewModel.clubs.count, 1)
|
||||
XCTAssertEqual(viewModel.clubs.first?.name, "New Club")
|
||||
XCTAssertTrue(mock.createCalled)
|
||||
}
|
||||
|
||||
// MARK: - Member Role
|
||||
|
||||
func testMemberRoleDisplayNames() {
|
||||
XCTAssertEqual(MemberRole.owner.displayName, "Owner")
|
||||
XCTAssertEqual(MemberRole.admin.displayName, "Admin")
|
||||
XCTAssertEqual(MemberRole.member.displayName, "Member")
|
||||
}
|
||||
|
||||
// MARK: - Club Filter Defaults
|
||||
|
||||
func testClubFilterDefaults() {
|
||||
let filter = ClubFilter()
|
||||
XCTAssertEqual(filter.limit, 20)
|
||||
XCTAssertEqual(filter.offset, 0)
|
||||
XCTAssertNil(filter.clubType)
|
||||
XCTAssertNil(filter.privacy)
|
||||
}
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
import XCTest
|
||||
import SwiftUI
|
||||
@testable import Lendair
|
||||
|
||||
// MARK: - Mock Service
|
||||
|
||||
final class MockNotificationsService: NotificationsServiceProtocol {
|
||||
var notifications: [NotificationItem] = []
|
||||
var markedReadIds: [String] = []
|
||||
var markAllCalled = false
|
||||
var listCallCount = 0
|
||||
var listError: Error?
|
||||
var mockUnreadCount: Int = 0
|
||||
var getUnreadCountCallCount = 0
|
||||
|
||||
func list(params: NotificationListParams = NotificationListParams()) async throws -> [NotificationItem] {
|
||||
listCallCount += 1
|
||||
if let error = listError {
|
||||
throw error
|
||||
}
|
||||
return notifications
|
||||
}
|
||||
|
||||
func markAsRead(id: String) async throws {
|
||||
markedReadIds.append(id)
|
||||
}
|
||||
|
||||
func markAllAsRead() async throws {
|
||||
markAllCalled = true
|
||||
}
|
||||
|
||||
func getUnreadCount() async throws -> Int {
|
||||
getUnreadCountCallCount += 1
|
||||
return mockUnreadCount
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helper: Sample Notifications
|
||||
|
||||
extension NotificationItem {
|
||||
static func sample(
|
||||
id: String = "test-1",
|
||||
type: NotificationType = .loanApproved,
|
||||
title: String = "Test",
|
||||
message: String = "Test message",
|
||||
isRead: Bool = false
|
||||
) -> NotificationItem {
|
||||
NotificationItem(
|
||||
id: id,
|
||||
type: type,
|
||||
title: title,
|
||||
message: message,
|
||||
createdAt: Date(),
|
||||
isRead: isRead
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NotificationServiceTests
|
||||
|
||||
final class NotificationServiceTests: XCTestCase {
|
||||
// MARK: - Fetch Notifications
|
||||
|
||||
@MainActor
|
||||
func testFetchNotificationsLoadsData() async {
|
||||
let mock = MockNotificationsService()
|
||||
mock.notifications = [.sample(id: "1"), .sample(id: "2")]
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
await viewModel.fetchNotifications()
|
||||
|
||||
XCTAssertEqual(viewModel.notifications.count, 2)
|
||||
XCTAssertFalse(viewModel.isLoading)
|
||||
XCTAssertEqual(mock.listCallCount, 1)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testFetchNotificationsSortsByCreatedAtDescending() async {
|
||||
let mock = MockNotificationsService()
|
||||
let older = NotificationItem.sample(id: "1", createdAt: Date().addingTimeInterval(-3600))
|
||||
let newer = NotificationItem.sample(id: "2", createdAt: Date())
|
||||
mock.notifications = [newer, older]
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
await viewModel.fetchNotifications()
|
||||
|
||||
XCTAssertEqual(viewModel.notifications.first?.id, "2")
|
||||
XCTAssertEqual(viewModel.notifications.last?.id, "1")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testFetchNotificationsSetsLoadingState() async {
|
||||
let mock = MockNotificationsService()
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
|
||||
await viewModel.fetchNotifications()
|
||||
XCTAssertFalse(viewModel.isLoading)
|
||||
XCTAssertNotNil(viewModel.lastRefreshDate)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testFetchNotificationsHandlesError() async {
|
||||
let mock = MockNotificationsService()
|
||||
mock.listError = NotificationError.unauthorized
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
await viewModel.fetchNotifications()
|
||||
|
||||
XCTAssertTrue(viewModel.notifications.isEmpty)
|
||||
XCTAssertFalse(viewModel.isLoading)
|
||||
XCTAssertEqual(viewModel.error, .unauthorized)
|
||||
}
|
||||
|
||||
// MARK: - Mark As Read
|
||||
|
||||
@MainActor
|
||||
func testMarkAsReadUpdatesLocalState() async {
|
||||
let mock = MockNotificationsService()
|
||||
let unread = NotificationItem.sample(id: "1", isRead: false)
|
||||
mock.notifications = [unread]
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
viewModel.notifications = [unread]
|
||||
|
||||
await viewModel.markAsRead(id: "1")
|
||||
|
||||
XCTAssertTrue(viewModel.notifications.first?.isRead == true)
|
||||
XCTAssertEqual(mock.markedReadIds, ["1"])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testMarkAsReadIgnoresUnknownId() async {
|
||||
let mock = MockNotificationsService()
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
viewModel.notifications = [.sample(id: "1")]
|
||||
|
||||
await viewModel.markAsRead(id: "999")
|
||||
|
||||
XCTAssertTrue(mock.markedReadIds.isEmpty)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testMarkAsReadReducesUnreadCount() async {
|
||||
let mock = MockNotificationsService()
|
||||
let read = NotificationItem.sample(id: "1", isRead: true)
|
||||
let unread = NotificationItem.sample(id: "2", isRead: false)
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
viewModel.notifications = [read, unread]
|
||||
|
||||
XCTAssertEqual(viewModel.unreadCount, 1)
|
||||
|
||||
await viewModel.markAsRead(id: "2")
|
||||
XCTAssertEqual(viewModel.unreadCount, 0)
|
||||
}
|
||||
|
||||
// MARK: - Mark All As Read
|
||||
|
||||
@MainActor
|
||||
func testMarkAllAsReadUpdatesAllNotifications() async {
|
||||
let mock = MockNotificationsService()
|
||||
let unread1 = NotificationItem.sample(id: "1", isRead: false)
|
||||
let unread2 = NotificationItem.sample(id: "2", isRead: false)
|
||||
let read = NotificationItem.sample(id: "3", isRead: true)
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
viewModel.notifications = [unread1, unread2, read]
|
||||
|
||||
await viewModel.markAllAsRead()
|
||||
|
||||
XCTAssertTrue(viewModel.notifications.allSatisfy { $0.isRead })
|
||||
XCTAssertTrue(mock.markAllCalled)
|
||||
XCTAssertEqual(viewModel.unreadCount, 0)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testMarkAllAsReadNoOpWhenAllRead() async {
|
||||
let mock = MockNotificationsService()
|
||||
let read1 = NotificationItem.sample(id: "1", isRead: true)
|
||||
let read2 = NotificationItem.sample(id: "2", isRead: true)
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
viewModel.notifications = [read1, read2]
|
||||
|
||||
await viewModel.markAllAsRead()
|
||||
|
||||
XCTAssertFalse(mock.markAllCalled)
|
||||
}
|
||||
|
||||
// MARK: - Unread Count
|
||||
|
||||
@MainActor
|
||||
func testUnreadCountCalculatesCorrectly() async {
|
||||
let mock = MockNotificationsService()
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
viewModel.notifications = [
|
||||
NotificationItem.sample(id: "1", isRead: false),
|
||||
NotificationItem.sample(id: "2", isRead: true),
|
||||
NotificationItem.sample(id: "3", isRead: false),
|
||||
]
|
||||
|
||||
XCTAssertEqual(viewModel.unreadCount, 2)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testUnreadCountIsEmptyWhenNoNotifications() async {
|
||||
let mock = MockNotificationsService()
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
|
||||
XCTAssertEqual(viewModel.unreadCount, 0)
|
||||
}
|
||||
|
||||
// MARK: - Refresh
|
||||
|
||||
@MainActor
|
||||
func testRefreshReloadsData() async {
|
||||
let mock = MockNotificationsService()
|
||||
mock.notifications = [.sample(id: "1")]
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
await viewModel.refresh()
|
||||
|
||||
XCTAssertEqual(mock.listCallCount, 1)
|
||||
XCTAssertEqual(viewModel.notifications.count, 1)
|
||||
}
|
||||
|
||||
// MARK: - Badge Count
|
||||
|
||||
@MainActor
|
||||
func testFetchUnreadCountSetsBadgeCount() async {
|
||||
let mock = MockNotificationsService()
|
||||
mock.mockUnreadCount = 5
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
await viewModel.fetchUnreadCount()
|
||||
|
||||
XCTAssertEqual(viewModel.badgeCount, 5)
|
||||
XCTAssertEqual(mock.getUnreadCountCallCount, 1)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testFetchUnreadCountZero() async {
|
||||
let mock = MockNotificationsService()
|
||||
mock.mockUnreadCount = 0
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
await viewModel.fetchUnreadCount()
|
||||
|
||||
XCTAssertEqual(viewModel.badgeCount, 0)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testMarkAsReadDecrementsBadgeCount() async {
|
||||
let mock = MockNotificationsService()
|
||||
let unread = NotificationItem.sample(id: "1", isRead: false)
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
viewModel.notifications = [unread]
|
||||
viewModel.badgeCount = 3
|
||||
|
||||
await viewModel.markAsRead(id: "1")
|
||||
|
||||
XCTAssertEqual(viewModel.badgeCount, 2)
|
||||
XCTAssertTrue(viewModel.notifications.first?.isRead == true)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testMarkAsReadBadgeCountDoesNotGoBelowZero() async {
|
||||
let mock = MockNotificationsService()
|
||||
let unread = NotificationItem.sample(id: "1", isRead: false)
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
viewModel.notifications = [unread]
|
||||
viewModel.badgeCount = 0
|
||||
|
||||
await viewModel.markAsRead(id: "1")
|
||||
|
||||
XCTAssertEqual(viewModel.badgeCount, 0)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testMarkAllAsReadResetsBadgeCount() async {
|
||||
let mock = MockNotificationsService()
|
||||
let unread1 = NotificationItem.sample(id: "1", isRead: false)
|
||||
let unread2 = NotificationItem.sample(id: "2", isRead: false)
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
viewModel.notifications = [unread1, unread2]
|
||||
viewModel.badgeCount = 7
|
||||
|
||||
await viewModel.markAllAsRead()
|
||||
|
||||
XCTAssertEqual(viewModel.badgeCount, 0)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testFetchNotificationsUpdatesBadgeCount() async {
|
||||
let mock = MockNotificationsService()
|
||||
mock.notifications = [
|
||||
NotificationItem.sample(id: "1", isRead: false),
|
||||
NotificationItem.sample(id: "2", isRead: true),
|
||||
NotificationItem.sample(id: "3", isRead: false),
|
||||
]
|
||||
|
||||
let viewModel = NotificationsViewModel(notificationsService: mock)
|
||||
viewModel.badgeCount = 0
|
||||
await viewModel.fetchNotifications()
|
||||
|
||||
XCTAssertEqual(viewModel.badgeCount, 2)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NotificationModelTests
|
||||
|
||||
final class NotificationModelTests: XCTestCase {
|
||||
func testNotificationTypeIcons() {
|
||||
XCTAssertEqual(NotificationType.loanApproved.icon, "checkmark.circle.fill")
|
||||
XCTAssertEqual(NotificationType.loanRejected.icon, "xmark.circle.fill")
|
||||
XCTAssertEqual(NotificationType.paymentReceived.icon, "arrow.down.circle.fill")
|
||||
XCTAssertEqual(NotificationType.paymentDue.icon, "exclamationmark.circle.fill")
|
||||
XCTAssertEqual(NotificationType.newLender.icon, "person.circle.fill")
|
||||
XCTAssertEqual(NotificationType.systemUpdate.icon, "info.circle.fill")
|
||||
}
|
||||
|
||||
func testNotificationTypeColors() {
|
||||
XCTAssertEqual(NotificationType.loanApproved.color, .green)
|
||||
XCTAssertEqual(NotificationType.loanRejected.color, .red)
|
||||
XCTAssertEqual(NotificationType.paymentReceived.color, .green)
|
||||
XCTAssertEqual(NotificationType.paymentDue.color, .orange)
|
||||
XCTAssertEqual(NotificationType.newLender.color, .blue)
|
||||
XCTAssertEqual(NotificationType.systemUpdate.color, .gray)
|
||||
}
|
||||
|
||||
func testNotificationItemEquality() {
|
||||
let a = NotificationItem.sample(id: "1", isRead: false)
|
||||
let b = NotificationItem.sample(id: "1", isRead: false)
|
||||
let c = NotificationItem.sample(id: "1", isRead: true)
|
||||
|
||||
XCTAssertEqual(a, b)
|
||||
XCTAssertNotEqual(a, c)
|
||||
}
|
||||
|
||||
func testNotificationTypeRawValue() {
|
||||
XCTAssertEqual(NotificationType.loanApproved.rawValue, "LOAN_APPROVED")
|
||||
XCTAssertEqual(NotificationType.paymentDue.rawValue, "PAYMENT_DUE")
|
||||
}
|
||||
|
||||
func testNotificationListParamsDefaults() {
|
||||
let params = NotificationListParams()
|
||||
XCTAssertEqual(params.limit, 20)
|
||||
XCTAssertEqual(params.offset, 0)
|
||||
}
|
||||
|
||||
func testNotificationListParamsCustom() {
|
||||
let params = NotificationListParams(limit: 50, offset: 100)
|
||||
XCTAssertEqual(params.limit, 50)
|
||||
XCTAssertEqual(params.offset, 100)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user