Skip to content

Commit 1b489a9

Browse files
committed
AppSettingsAction: add setJetpackBenefitsBannerLastDismissedTime and loadJetpackBenefitsBannerVisibility for Jetpack banner visibility from dismissal.
1 parent 70ce8eb commit 1b489a9

File tree

4 files changed

+164
-1
lines changed

4 files changed

+164
-1
lines changed

Storage/StorageTests/Model/AppSettings/GeneralAppSettingsTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import XCTest
22
@testable import Storage
33

4-
class GeneralAppSettingsTests: XCTestCase {
4+
final class GeneralAppSettingsTests: XCTestCase {
55

66
func test_it_returns_the_correct_status_of_a_stored_feedback() {
77
// Given

Yosemite/Yosemite/Actions/AppSettingsAction.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Combine
12
import Foundation
23
import Storage
34

@@ -145,6 +146,14 @@ public enum AppSettingsAction: Action {
145146
///
146147
case resetEligibilityErrorInfo
147148

149+
/// Sets the last time when Jetpack benefits banner is dismissed in the Dashboard.
150+
///
151+
case setJetpackBenefitsBannerLastDismissedTime(time: Date)
152+
153+
/// Loads the visibility of Jetpack benefits banner in the Dashboard. The banner is not shown for five days after the last time it is dismissed.
154+
///
155+
case loadJetpackBenefitsBannerVisibility(currentTime: Date, calendar: Calendar, onCompletion: (Bool) -> Void)
156+
148157
// MARK: - General Store Settings
149158

150159
/// Sets telemetry availability status information.

Yosemite/Yosemite/Stores/AppSettingsStore.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ public class AppSettingsStore: Store {
162162
setEligibilityErrorInfo(errorInfo: errorInfo, onCompletion: onCompletion)
163163
case .resetEligibilityErrorInfo:
164164
setEligibilityErrorInfo(errorInfo: nil)
165+
case .setJetpackBenefitsBannerLastDismissedTime(time: let time):
166+
setJetpackBenefitsBannerLastDismissedTime(time: time)
167+
case .loadJetpackBenefitsBannerVisibility(currentTime: let currentTime, calendar: let calendar, onCompletion: let onCompletion):
168+
loadJetpackBenefitsBannerVisibility(currentTime: currentTime, calendar: calendar, onCompletion: onCompletion)
165169
case .setTelemetryAvailability(siteID: let siteID, isAvailable: let isAvailable):
166170
setTelemetryAvailability(siteID: siteID, isAvailable: isAvailable)
167171
case .setTelemetryLastReportedTime(siteID: let siteID, time: let time):
@@ -307,6 +311,34 @@ private extension AppSettingsStore {
307311
}
308312
}
309313

314+
// Visibility of Jetpack benefits banner in the Dashboard
315+
316+
func setJetpackBenefitsBannerLastDismissedTime(time: Date, onCompletion: ((Result<Void, Error>) -> Void)? = nil) {
317+
do {
318+
let settings = loadOrCreateGeneralAppSettings().copy(lastJetpackBenefitsBannerDismissedTime: time)
319+
try saveGeneralAppSettings(settings)
320+
onCompletion?(.success(()))
321+
} catch {
322+
onCompletion?(.failure(error))
323+
}
324+
}
325+
326+
func loadJetpackBenefitsBannerVisibility(currentTime: Date, calendar: Calendar, onCompletion: (Bool) -> Void) {
327+
let settings = loadOrCreateGeneralAppSettings()
328+
329+
guard let lastDismissedTime = settings.lastJetpackBenefitsBannerDismissedTime else {
330+
// If the banner has not been dismissed before, the banner is default to be visible.
331+
return onCompletion(true)
332+
}
333+
334+
guard let numberOfDaysSinceLastDismissal = calendar.dateComponents([.day], from: lastDismissedTime, to: currentTime).day else {
335+
return onCompletion(true)
336+
}
337+
onCompletion(numberOfDaysSinceLastDismissal >= 5)
338+
}
339+
340+
// File operations
341+
310342
/// Load the `GeneralAppSettings` from file or create an empty one if it doesn't exist.
311343
func loadOrCreateGeneralAppSettings() -> GeneralAppSettings {
312344
guard let settings: GeneralAppSettings = try? fileStorage.data(for: generalAppSettingsFileURL) else {
@@ -749,6 +781,7 @@ enum AppSettingsStoreErrors: Error {
749781
case noProductsSettings
750782
case writeProductsSettings
751783
case noEligibilityErrorInfo
784+
case noLastJetpackBenefitsBannerDismissedTime
752785
}
753786

754787

Yosemite/YosemiteTests/Stores/AppSettingsStoreTests.swift

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,127 @@ final class AppSettingsStoreTests: XCTestCase {
519519
XCTAssertTrue(isEnabled)
520520
}
521521

522+
func test_loadJetpackBenefitsBannerVisibility_returns_true_on_new_generalAppSettings() throws {
523+
// Given
524+
try fileStorage?.deleteFile(at: expectedGeneralAppSettingsFileURL)
525+
526+
let currentTime = Date(timeIntervalSince1970: 1638105924)
527+
let calendar = Calendar(identifier: .gregorian)
528+
529+
// When
530+
let isVisible: Bool = waitFor { promise in
531+
let action = AppSettingsAction.loadJetpackBenefitsBannerVisibility(currentTime: currentTime, calendar: calendar) { isVisible in
532+
promise(isVisible)
533+
}
534+
self.subject?.onAction(action)
535+
}
536+
537+
// Then
538+
XCTAssertTrue(isVisible)
539+
}
540+
541+
func test_loadJetpackBenefitsBannerVisibility_returns_true_after_setting_last_dismissed_date_exactly_five_days_ago_without_dst() throws {
542+
// Given
543+
try fileStorage?.deleteFile(at: expectedGeneralAppSettingsFileURL)
544+
545+
// GMT - Tuesday, November 23, 2021 1:25:24 PM
546+
let lastDismissedTime = Date(timeIntervalSince1970: 1637673924)
547+
// GMT - Sunday, November 28, 2021 1:25:24 PM - exactly five days after the last dismissed date without DST
548+
let currentTime = Date(timeIntervalSince1970: 1638105924)
549+
let calendar: Calendar = {
550+
var calendar = Calendar(identifier: .gregorian)
551+
guard let timeZoneWithoutDailySavingTime = TimeZone(identifier: "Asia/Taipei") else {
552+
XCTFail("Unexpected time zone.")
553+
return calendar
554+
}
555+
calendar.timeZone = timeZoneWithoutDailySavingTime
556+
return calendar
557+
}()
558+
559+
let updateAction = AppSettingsAction.setJetpackBenefitsBannerLastDismissedTime(time: lastDismissedTime)
560+
subject?.onAction(updateAction)
561+
562+
// When
563+
let isVisible: Bool = waitFor { promise in
564+
let action = AppSettingsAction.loadJetpackBenefitsBannerVisibility(currentTime: currentTime, calendar: calendar) { isVisible in
565+
promise(isVisible)
566+
}
567+
self.subject?.onAction(action)
568+
}
569+
570+
// Then
571+
XCTAssertTrue(isVisible)
572+
}
573+
574+
/// Tests an edge case where the time interval since the last dismissed date is less than 5 24-hour days, but is exactly 5 days on calendar with daily
575+
/// saving time.
576+
func test_loadJetpackBenefitsBannerVisibility_returns_false_after_setting_last_dismissed_date_exactly_five_24hr_days_ago() throws {
577+
// Given
578+
try fileStorage?.deleteFile(at: expectedGeneralAppSettingsFileURL)
579+
580+
// America/New York (EDT) - November 03, 2021 09:43:17 AM
581+
let lastDismissedTime = Date(timeIntervalSince1970: 1635946997)
582+
// America/New York (EST) - November 08, 2021 08:43:17 AM - exactly five 24-hour days after the last dismissed date.
583+
// But with daily saving time in America/New York, it is still less than five days.
584+
let currentTime = Date(timeIntervalSince1970: 1636378997)
585+
let calendar: Calendar = {
586+
var calendar = Calendar(identifier: .gregorian)
587+
guard let timeZoneWithDailySavingTime = TimeZone(identifier: "America/New_York") else {
588+
XCTFail("Unexpected time zone.")
589+
return calendar
590+
}
591+
calendar.timeZone = timeZoneWithDailySavingTime
592+
return calendar
593+
}()
594+
595+
let updateAction = AppSettingsAction.setJetpackBenefitsBannerLastDismissedTime(time: lastDismissedTime)
596+
subject?.onAction(updateAction)
597+
598+
// When
599+
let isVisible: Bool = waitFor { promise in
600+
let action = AppSettingsAction.loadJetpackBenefitsBannerVisibility(currentTime: currentTime, calendar: calendar) { isVisible in
601+
promise(isVisible)
602+
}
603+
self.subject?.onAction(action)
604+
}
605+
606+
// Then
607+
XCTAssertFalse(isVisible)
608+
}
609+
610+
func test_loadJetpackBenefitsBannerVisibility_returns_false_after_setting_last_dismissed_date_less_than_five_days_ago() throws {
611+
// Given
612+
try fileStorage?.deleteFile(at: expectedGeneralAppSettingsFileURL)
613+
614+
// GMT - Tuesday, November 23, 2021 1:25:24 PM
615+
let lastDismissedTime = Date(timeIntervalSince1970: 1637673924)
616+
// GMT - Sunday, November 28, 2021 1:25:23 PM - exactly 1 second less than five days after the last dismissed date without DST
617+
let currentTime = Date(timeIntervalSince1970: 1638105923)
618+
let calendar: Calendar = {
619+
var calendar = Calendar(identifier: .gregorian)
620+
guard let timeZoneWithoutDailySavingTime = TimeZone(identifier: "Asia/Taipei") else {
621+
XCTFail("Unexpected time zone.")
622+
return calendar
623+
}
624+
calendar.timeZone = timeZoneWithoutDailySavingTime
625+
return calendar
626+
}()
627+
628+
let updateAction = AppSettingsAction.setJetpackBenefitsBannerLastDismissedTime(time: lastDismissedTime)
629+
subject?.onAction(updateAction)
630+
631+
// When
632+
let isVisible: Bool = waitFor { promise in
633+
let action = AppSettingsAction.loadJetpackBenefitsBannerVisibility(currentTime: currentTime, calendar: calendar) { isVisible in
634+
promise(isVisible)
635+
}
636+
self.subject?.onAction(action)
637+
}
638+
639+
// Then
640+
XCTAssertFalse(isVisible)
641+
}
642+
522643
// MARK: - General Store Settings
523644

524645
func test_saving_isTelemetryAvailable_works_correctly() throws {

0 commit comments

Comments
 (0)