Add PlantGuide iOS app with plant identification and care management
- Implement camera capture and plant identification workflow - Add Core Data persistence for plants, care schedules, and cached API data - Create collection view with grid/list layouts and filtering - Build plant detail views with care information display - Integrate Trefle botanical API for plant care data - Add local image storage for captured plant photos - Implement dependency injection container for testability - Include accessibility support throughout the app Bug fixes in this commit: - Fix Trefle API decoding by removing duplicate CodingKeys - Fix LocalCachedImage to load from correct PlantImages directory - Set dateAdded when saving plants for proper collection sorting Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
797
PlantGuideTests/TrefleMapperTests.swift
Normal file
797
PlantGuideTests/TrefleMapperTests.swift
Normal file
@@ -0,0 +1,797 @@
|
||||
//
|
||||
// TrefleMapperTests.swift
|
||||
// PlantGuideTests
|
||||
//
|
||||
// Unit tests for TrefleMapper functions.
|
||||
// Tests all mapping functions with various input values and edge cases.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import PlantGuide
|
||||
|
||||
final class TrefleMapperTests: XCTestCase {
|
||||
|
||||
// MARK: - mapToLightRequirement Tests
|
||||
|
||||
func testMapToLightRequirement_WithZero_ReturnsFullShade() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 0)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .fullShade)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithOne_ReturnsFullShade() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 1)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .fullShade)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithTwo_ReturnsFullShade() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 2)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .fullShade)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithThree_ReturnsLowLight() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 3)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .lowLight)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithFour_ReturnsLowLight() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 4)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .lowLight)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithFive_ReturnsPartialShade() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 5)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .partialShade)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithSix_ReturnsPartialShade() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 6)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .partialShade)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithSeven_ReturnsFullSun() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 7)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .fullSun)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithEight_ReturnsFullSun() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 8)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .fullSun)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithNine_ReturnsFullSun() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 9)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .fullSun)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithTen_ReturnsFullSun() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 10)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .fullSun)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithNil_ReturnsPartialShadeDefault() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: nil)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .partialShade)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithOutOfRangePositive_ReturnsPartialShadeDefault() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: 15)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .partialShade)
|
||||
}
|
||||
|
||||
func testMapToLightRequirement_WithNegative_ReturnsPartialShadeDefault() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToLightRequirement(from: -1)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .partialShade)
|
||||
}
|
||||
|
||||
// MARK: - mapToWateringSchedule Tests
|
||||
|
||||
func testMapToWateringSchedule_WithNilGrowth_ReturnsWeeklyModerateDefault() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToWateringSchedule(from: nil)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.frequency, .weekly)
|
||||
XCTAssertEqual(result.amount, .moderate)
|
||||
}
|
||||
|
||||
func testMapToWateringSchedule_WithHighAtmosphericHumidity_ReturnsWeeklyLight() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(atmosphericHumidity: 8, soilHumidity: nil)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToWateringSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.frequency, .weekly)
|
||||
XCTAssertEqual(result.amount, .light)
|
||||
}
|
||||
|
||||
func testMapToWateringSchedule_WithHighHumidity10_ReturnsWeeklyLight() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(atmosphericHumidity: 10, soilHumidity: nil)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToWateringSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.frequency, .weekly)
|
||||
XCTAssertEqual(result.amount, .light)
|
||||
}
|
||||
|
||||
func testMapToWateringSchedule_WithMediumHumidity_ReturnsTwiceWeeklyModerate() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(atmosphericHumidity: 5, soilHumidity: nil)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToWateringSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.frequency, .twiceWeekly)
|
||||
XCTAssertEqual(result.amount, .moderate)
|
||||
}
|
||||
|
||||
func testMapToWateringSchedule_WithMediumHumidity4_ReturnsTwiceWeeklyModerate() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(atmosphericHumidity: 4, soilHumidity: nil)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToWateringSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.frequency, .twiceWeekly)
|
||||
XCTAssertEqual(result.amount, .moderate)
|
||||
}
|
||||
|
||||
func testMapToWateringSchedule_WithMediumHumidity6_ReturnsTwiceWeeklyModerate() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(atmosphericHumidity: 6, soilHumidity: nil)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToWateringSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.frequency, .twiceWeekly)
|
||||
XCTAssertEqual(result.amount, .moderate)
|
||||
}
|
||||
|
||||
func testMapToWateringSchedule_WithLowHumidity_ReturnsWeeklyThorough() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(atmosphericHumidity: 2, soilHumidity: nil)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToWateringSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.frequency, .weekly)
|
||||
XCTAssertEqual(result.amount, .thorough)
|
||||
}
|
||||
|
||||
func testMapToWateringSchedule_WithLowHumidity0_ReturnsWeeklyThorough() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(atmosphericHumidity: 0, soilHumidity: nil)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToWateringSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.frequency, .weekly)
|
||||
XCTAssertEqual(result.amount, .thorough)
|
||||
}
|
||||
|
||||
func testMapToWateringSchedule_WithNoAtmosphericHumidity_FallsBackToSoilHumidity() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(atmosphericHumidity: nil, soilHumidity: 8)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToWateringSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.frequency, .weekly)
|
||||
XCTAssertEqual(result.amount, .light)
|
||||
}
|
||||
|
||||
func testMapToWateringSchedule_WithNoHumidityValues_ReturnsWeeklyModerateDefault() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(atmosphericHumidity: nil, soilHumidity: nil)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToWateringSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.frequency, .weekly)
|
||||
XCTAssertEqual(result.amount, .moderate)
|
||||
}
|
||||
|
||||
// MARK: - mapToTemperatureRange Tests
|
||||
|
||||
func testMapToTemperatureRange_WithNilGrowth_ReturnsDefaultRange() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToTemperatureRange(from: nil)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.minimumCelsius, 15.0)
|
||||
XCTAssertEqual(result.maximumCelsius, 30.0)
|
||||
XCTAssertNil(result.optimalCelsius)
|
||||
XCTAssertFalse(result.frostTolerant)
|
||||
}
|
||||
|
||||
func testMapToTemperatureRange_WithValidTemperatures_ReturnsCorrectRange() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(minTempC: 10.0, maxTempC: 35.0)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToTemperatureRange(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.minimumCelsius, 10.0)
|
||||
XCTAssertEqual(result.maximumCelsius, 35.0)
|
||||
XCTAssertEqual(result.optimalCelsius, 22.5) // (10 + 35) / 2
|
||||
XCTAssertFalse(result.frostTolerant)
|
||||
}
|
||||
|
||||
func testMapToTemperatureRange_WithNegativeMinTemp_SetsFrostTolerantTrue() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(minTempC: -5.0, maxTempC: 25.0)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToTemperatureRange(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.minimumCelsius, -5.0)
|
||||
XCTAssertEqual(result.maximumCelsius, 25.0)
|
||||
XCTAssertTrue(result.frostTolerant)
|
||||
}
|
||||
|
||||
func testMapToTemperatureRange_WithZeroMinTemp_SetsFrostTolerantFalse() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(minTempC: 0.0, maxTempC: 30.0)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToTemperatureRange(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.minimumCelsius, 0.0)
|
||||
XCTAssertFalse(result.frostTolerant)
|
||||
}
|
||||
|
||||
func testMapToTemperatureRange_WithOnlyMinTemp_ReturnsDefaultMaxAndNoOptimal() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(minTempC: 5.0, maxTempC: nil)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToTemperatureRange(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.minimumCelsius, 5.0)
|
||||
XCTAssertEqual(result.maximumCelsius, 30.0) // Default
|
||||
XCTAssertNil(result.optimalCelsius) // No optimal when missing data
|
||||
}
|
||||
|
||||
func testMapToTemperatureRange_WithOnlyMaxTemp_ReturnsDefaultMinAndNoOptimal() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(minTempC: nil, maxTempC: 40.0)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToTemperatureRange(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.minimumCelsius, 15.0) // Default
|
||||
XCTAssertEqual(result.maximumCelsius, 40.0)
|
||||
XCTAssertNil(result.optimalCelsius) // No optimal when missing data
|
||||
}
|
||||
|
||||
// MARK: - mapToFertilizerSchedule Tests
|
||||
|
||||
func testMapToFertilizerSchedule_WithNilGrowth_ReturnsNil() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToFertilizerSchedule(from: nil)
|
||||
|
||||
// Then
|
||||
XCTAssertNil(result)
|
||||
}
|
||||
|
||||
func testMapToFertilizerSchedule_WithNilSoilNutriments_ReturnsNil() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(soilNutriments: nil)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToFertilizerSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertNil(result)
|
||||
}
|
||||
|
||||
func testMapToFertilizerSchedule_WithHighNutrients_ReturnsBiweeklyBalanced() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(soilNutriments: 8)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToFertilizerSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result?.frequency, .biweekly)
|
||||
XCTAssertEqual(result?.type, .balanced)
|
||||
}
|
||||
|
||||
func testMapToFertilizerSchedule_WithHighNutrients10_ReturnsBiweeklyBalanced() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(soilNutriments: 10)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToFertilizerSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result?.frequency, .biweekly)
|
||||
XCTAssertEqual(result?.type, .balanced)
|
||||
}
|
||||
|
||||
func testMapToFertilizerSchedule_WithHighNutrients7_ReturnsBiweeklyBalanced() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(soilNutriments: 7)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToFertilizerSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result?.frequency, .biweekly)
|
||||
XCTAssertEqual(result?.type, .balanced)
|
||||
}
|
||||
|
||||
func testMapToFertilizerSchedule_WithMediumNutrients_ReturnsMonthlyBalanced() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(soilNutriments: 5)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToFertilizerSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result?.frequency, .monthly)
|
||||
XCTAssertEqual(result?.type, .balanced)
|
||||
}
|
||||
|
||||
func testMapToFertilizerSchedule_WithMediumNutrients4_ReturnsMonthlyBalanced() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(soilNutriments: 4)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToFertilizerSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result?.frequency, .monthly)
|
||||
XCTAssertEqual(result?.type, .balanced)
|
||||
}
|
||||
|
||||
func testMapToFertilizerSchedule_WithMediumNutrients6_ReturnsMonthlyBalanced() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(soilNutriments: 6)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToFertilizerSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result?.frequency, .monthly)
|
||||
XCTAssertEqual(result?.type, .balanced)
|
||||
}
|
||||
|
||||
func testMapToFertilizerSchedule_WithLowNutrients_ReturnsQuarterlyOrganic() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(soilNutriments: 2)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToFertilizerSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result?.frequency, .quarterly)
|
||||
XCTAssertEqual(result?.type, .organic)
|
||||
}
|
||||
|
||||
func testMapToFertilizerSchedule_WithLowNutrients0_ReturnsQuarterlyOrganic() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(soilNutriments: 0)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToFertilizerSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result?.frequency, .quarterly)
|
||||
XCTAssertEqual(result?.type, .organic)
|
||||
}
|
||||
|
||||
func testMapToFertilizerSchedule_WithLowNutrients3_ReturnsQuarterlyOrganic() {
|
||||
// Given
|
||||
let growth = createGrowthDTO(soilNutriments: 3)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToFertilizerSchedule(from: growth)
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result?.frequency, .quarterly)
|
||||
XCTAssertEqual(result?.type, .organic)
|
||||
}
|
||||
|
||||
// MARK: - mapToBloomingSeason Tests
|
||||
|
||||
func testMapToBloomingSeason_WithNilBloomMonths_ReturnsNil() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: nil)
|
||||
|
||||
// Then
|
||||
XCTAssertNil(result)
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithEmptyArray_ReturnsNil() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: [])
|
||||
|
||||
// Then
|
||||
XCTAssertNil(result)
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithSpringMonths_ReturnsSpring() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: ["mar", "apr", "may"])
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result, [.spring])
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithSummerMonths_ReturnsSummer() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: ["jun", "jul", "aug"])
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result, [.summer])
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithFallMonths_ReturnsFall() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: ["sep", "oct", "nov"])
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result, [.fall])
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithWinterMonths_ReturnsWinter() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: ["dec", "jan", "feb"])
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result, [.winter])
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithMultipleSeasons_ReturnsOrderedSeasons() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: ["apr", "may", "jun", "jul"])
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result, [.spring, .summer])
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithMixedSeasons_ReturnsAllSeasonsOrdered() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: ["jan", "apr", "jul", "oct"])
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result, [.spring, .summer, .fall, .winter])
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithUppercaseMonths_ReturnsCorrectSeasons() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: ["MAR", "APR", "MAY"])
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result, [.spring])
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithMixedCaseMonths_ReturnsCorrectSeasons() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: ["Mar", "Apr", "May"])
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result, [.spring])
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithInvalidMonths_ReturnsNil() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: ["xyz", "abc", "123"])
|
||||
|
||||
// Then
|
||||
XCTAssertNil(result)
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithMixedValidAndInvalidMonths_ReturnsValidOnly() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: ["apr", "xyz", "may"])
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result, [.spring])
|
||||
}
|
||||
|
||||
func testMapToBloomingSeason_WithSingleMonth_ReturnsSingleSeason() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToBloomingSeason(from: ["jul"])
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result, [.summer])
|
||||
}
|
||||
|
||||
// MARK: - mapToHumidityLevel Tests
|
||||
|
||||
func testMapToHumidityLevel_WithNil_ReturnsNil() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToHumidityLevel(from: nil)
|
||||
|
||||
// Then
|
||||
XCTAssertNil(result)
|
||||
}
|
||||
|
||||
func testMapToHumidityLevel_WithLowValue_ReturnsLow() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToHumidityLevel(from: 1)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .low)
|
||||
}
|
||||
|
||||
func testMapToHumidityLevel_WithModerateValue_ReturnsModerate() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToHumidityLevel(from: 4)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .moderate)
|
||||
}
|
||||
|
||||
func testMapToHumidityLevel_WithHighValue_ReturnsHigh() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToHumidityLevel(from: 7)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .high)
|
||||
}
|
||||
|
||||
func testMapToHumidityLevel_WithVeryHighValue_ReturnsVeryHigh() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToHumidityLevel(from: 9)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .veryHigh)
|
||||
}
|
||||
|
||||
// MARK: - mapToGrowthRate Tests
|
||||
|
||||
func testMapToGrowthRate_WithNil_ReturnsNil() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToGrowthRate(from: nil)
|
||||
|
||||
// Then
|
||||
XCTAssertNil(result)
|
||||
}
|
||||
|
||||
func testMapToGrowthRate_WithSlow_ReturnsSlow() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToGrowthRate(from: "slow")
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .slow)
|
||||
}
|
||||
|
||||
func testMapToGrowthRate_WithModerate_ReturnsModerate() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToGrowthRate(from: "moderate")
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .moderate)
|
||||
}
|
||||
|
||||
func testMapToGrowthRate_WithMedium_ReturnsModerate() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToGrowthRate(from: "medium")
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .moderate)
|
||||
}
|
||||
|
||||
func testMapToGrowthRate_WithRapid_ReturnsFast() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToGrowthRate(from: "rapid")
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .fast)
|
||||
}
|
||||
|
||||
func testMapToGrowthRate_WithFast_ReturnsFast() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToGrowthRate(from: "fast")
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, .fast)
|
||||
}
|
||||
|
||||
func testMapToGrowthRate_WithUnknownValue_ReturnsNil() {
|
||||
// When
|
||||
let result = TrefleMapper.mapToGrowthRate(from: "unknown")
|
||||
|
||||
// Then
|
||||
XCTAssertNil(result)
|
||||
}
|
||||
|
||||
// MARK: - mapToPlantCareInfo Integration Test
|
||||
|
||||
func testMapToPlantCareInfo_CreatesCompleteEntity() {
|
||||
// Given
|
||||
let species = createSpeciesDTO(
|
||||
id: 834,
|
||||
commonName: "Swiss cheese plant",
|
||||
scientificName: "Monstera deliciosa",
|
||||
light: 6,
|
||||
atmosphericHumidity: 8,
|
||||
minTempC: 15,
|
||||
maxTempC: 30,
|
||||
soilNutriments: 5,
|
||||
growthRate: "moderate",
|
||||
bloomMonths: ["may", "jun"]
|
||||
)
|
||||
|
||||
// When
|
||||
let result = TrefleMapper.mapToPlantCareInfo(from: species)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.scientificName, "Monstera deliciosa")
|
||||
XCTAssertEqual(result.commonName, "Swiss cheese plant")
|
||||
XCTAssertEqual(result.lightRequirement, .partialShade) // light 6 -> partialShade
|
||||
XCTAssertEqual(result.wateringSchedule.frequency, .weekly) // humidity 8 -> weekly
|
||||
XCTAssertEqual(result.wateringSchedule.amount, .light) // humidity 8 -> light
|
||||
XCTAssertEqual(result.temperatureRange.minimumCelsius, 15)
|
||||
XCTAssertEqual(result.temperatureRange.maximumCelsius, 30)
|
||||
XCTAssertFalse(result.temperatureRange.frostTolerant)
|
||||
XCTAssertNotNil(result.fertilizerSchedule)
|
||||
XCTAssertEqual(result.fertilizerSchedule?.frequency, .monthly) // nutrients 5 -> monthly
|
||||
XCTAssertEqual(result.humidity, .high) // humidity 8 -> high
|
||||
XCTAssertEqual(result.growthRate, .moderate)
|
||||
XCTAssertEqual(result.bloomingSeason, [.spring, .summer])
|
||||
XCTAssertEqual(result.trefleID, 834)
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
|
||||
private func createGrowthDTO(
|
||||
atmosphericHumidity: Int? = nil,
|
||||
soilHumidity: Int? = nil,
|
||||
soilNutriments: Int? = nil,
|
||||
minTempC: Double? = nil,
|
||||
maxTempC: Double? = nil,
|
||||
bloomMonths: [String]? = nil
|
||||
) -> TrefleGrowthDTO {
|
||||
// Use JSON decoding to create DTO since it has no public init
|
||||
var json: [String: Any] = [:]
|
||||
|
||||
if let humidity = atmosphericHumidity {
|
||||
json["atmospheric_humidity"] = humidity
|
||||
}
|
||||
if let soil = soilHumidity {
|
||||
json["soil_humidity"] = soil
|
||||
}
|
||||
if let nutrients = soilNutriments {
|
||||
json["soil_nutriments"] = nutrients
|
||||
}
|
||||
if let minTemp = minTempC {
|
||||
json["minimum_temperature"] = ["deg_c": minTemp]
|
||||
}
|
||||
if let maxTemp = maxTempC {
|
||||
json["maximum_temperature"] = ["deg_c": maxTemp]
|
||||
}
|
||||
if let months = bloomMonths {
|
||||
json["bloom_months"] = months
|
||||
}
|
||||
|
||||
let data = try! JSONSerialization.data(withJSONObject: json)
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
return try! decoder.decode(TrefleGrowthDTO.self, from: data)
|
||||
}
|
||||
|
||||
private func createSpeciesDTO(
|
||||
id: Int,
|
||||
commonName: String?,
|
||||
scientificName: String,
|
||||
light: Int? = nil,
|
||||
atmosphericHumidity: Int? = nil,
|
||||
minTempC: Double? = nil,
|
||||
maxTempC: Double? = nil,
|
||||
soilNutriments: Int? = nil,
|
||||
growthRate: String? = nil,
|
||||
bloomMonths: [String]? = nil
|
||||
) -> TrefleSpeciesDTO {
|
||||
var json: [String: Any] = [
|
||||
"id": id,
|
||||
"slug": scientificName.lowercased().replacingOccurrences(of: " ", with: "-"),
|
||||
"scientific_name": scientificName
|
||||
]
|
||||
|
||||
if let name = commonName {
|
||||
json["common_name"] = name
|
||||
}
|
||||
|
||||
var growthJson: [String: Any] = [:]
|
||||
if let l = light { growthJson["light"] = l }
|
||||
if let h = atmosphericHumidity { growthJson["atmospheric_humidity"] = h }
|
||||
if let min = minTempC { growthJson["minimum_temperature"] = ["deg_c": min] }
|
||||
if let max = maxTempC { growthJson["maximum_temperature"] = ["deg_c": max] }
|
||||
if let n = soilNutriments { growthJson["soil_nutriments"] = n }
|
||||
if let months = bloomMonths { growthJson["bloom_months"] = months }
|
||||
|
||||
if !growthJson.isEmpty {
|
||||
json["growth"] = growthJson
|
||||
}
|
||||
|
||||
if let rate = growthRate {
|
||||
json["specifications"] = ["growth_rate": rate]
|
||||
}
|
||||
|
||||
let data = try! JSONSerialization.data(withJSONObject: json)
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
return try! decoder.decode(TrefleSpeciesDTO.self, from: data)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user