diff --git a/PortfolioJournal.xcodeproj/project.pbxproj b/PortfolioJournal.xcodeproj/project.pbxproj index e6ca074..f48459c 100644 --- a/PortfolioJournal.xcodeproj/project.pbxproj +++ b/PortfolioJournal.xcodeproj/project.pbxproj @@ -459,7 +459,7 @@ CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournalDebug.entitlements; ENABLE_USER_SCRIPT_SANDBOXING = NO; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets; DEVELOPMENT_TEAM = 2825Q76T7H; ENABLE_PREVIEWS = YES; @@ -498,7 +498,7 @@ CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournal.entitlements; ENABLE_USER_SCRIPT_SANDBOXING = NO; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets; DEVELOPMENT_TEAM = 2825Q76T7H; ENABLE_PREVIEWS = YES; @@ -655,7 +655,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets; DEVELOPMENT_TEAM = 2825Q76T7H; GENERATE_INFOPLIST_FILE = NO; @@ -688,7 +688,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets; DEVELOPMENT_TEAM = 2825Q76T7H; GENERATE_INFOPLIST_FILE = NO; @@ -719,7 +719,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_TEAM = 2825Q76T7H; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.6; @@ -743,7 +743,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_TEAM = 2825Q76T7H; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.6; @@ -766,7 +766,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_TEAM = 2825Q76T7H; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.6; @@ -789,7 +789,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_TEAM = 2825Q76T7H; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.6; diff --git a/PortfolioJournal/ViewModels/ChartsViewModel.swift b/PortfolioJournal/ViewModels/ChartsViewModel.swift index 8f410e2..094a12c 100644 --- a/PortfolioJournal/ViewModels/ChartsViewModel.swift +++ b/PortfolioJournal/ViewModels/ChartsViewModel.swift @@ -448,17 +448,19 @@ class ChartsViewModel: ObservableObject { sources = sourceRepository.sources.filter { shouldIncludeSource($0) } } - let monthsLimit = timeRange.months ?? maxHistoryMonths let sourceIds = sources.compactMap { $0.id } - // Performance: Reuse cached snapshots when possible + // Performance: cache the FULL history once and slice by time range in memory. + // The cache is shared across chart types and ranges, so it must never be + // fetched with a range-dependent months limit (that truncated Year vs Year + // and "All" after visiting a chart with a shorter range). var snapshots: [Snapshot] if let cached = cachedSnapshots { snapshots = cached } else { snapshots = snapshotRepository.fetchSnapshots( for: sourceIds, - months: monthsLimit + months: maxHistoryMonths ) hiddenHistoryMonths = freemiumValidator.hiddenHistoryMonths(in: snapshots) snapshots = freemiumValidator.filterSnapshots(snapshots) diff --git a/PortfolioJournalUITests/PortfolioJournalUITests.swift b/PortfolioJournalUITests/PortfolioJournalUITests.swift index 076943f..456e57a 100644 --- a/PortfolioJournalUITests/PortfolioJournalUITests.swift +++ b/PortfolioJournalUITests/PortfolioJournalUITests.swift @@ -151,10 +151,42 @@ final class PortfolioJournalUITests: XCTestCase { } Thread.sleep(forTimeInterval: 3.0) - let shot = XCTAttachment(screenshot: capture.screenshot()) - shot.name = "Charts_Redesign" - shot.lifetime = .keepAlways - add(shot) + func snap(_ name: String) { + let shot = XCTAttachment(screenshot: capture.screenshot()) + shot.name = name + shot.lifetime = .keepAlways + add(shot) + } + + snap("01_Evolution_default") + + // Switch period to All (exercises the shared snapshot cache across ranges) + let allSegment = capture.buttons["All"].firstMatch + if allSegment.waitForExistence(timeout: 3) { + allSegment.tap() + Thread.sleep(forTimeInterval: 2.0) + snap("02_Evolution_All") + } + + // Switch chart type to a free multi-control chart and back (cache reuse path) + let compareTile = capture.buttons["Compare"].firstMatch + if compareTile.waitForExistence(timeout: 3) { + compareTile.tap() + Thread.sleep(forTimeInterval: 2.0) + snap("03_Compare") + } + let periodTile = capture.buttons["Period vs Period"].firstMatch + if periodTile.waitForExistence(timeout: 3) { + periodTile.tap() + Thread.sleep(forTimeInterval: 2.0) + snap("04_PeriodVsPeriod") + } + let evolutionTile = capture.buttons["Evolution"].firstMatch + if evolutionTile.waitForExistence(timeout: 3) { + evolutionTile.tap() + Thread.sleep(forTimeInterval: 2.0) + snap("05_Evolution_back") + } } /// Captures full-screen screenshots of the main tabs with demo data for App Store