mirror of
https://github.com/bitwarden/ios.git
synced 2026-02-04 02:14:09 -06:00
[PM-29638] feat: Archive vault group list premium expired card (#2280)
This commit is contained in:
parent
450a87427f
commit
30a494b5c5
@ -1300,3 +1300,7 @@
|
||||
"ArchivingItemsIsAPremiumFeatureDescriptionLong" = "Archiving items is a Premium feature. Your current plan does not include access to this feature.";
|
||||
"UpgradeToPremium" = "Upgrade to premium";
|
||||
"ThisItemIsArchived" = "This item is archived.";
|
||||
"YourPremiumSubscriptionEnded" = "Your Premium subscription ended";
|
||||
"YourPremiumSubscriptionEndedArchiveDescriptionLong" = "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it’ll be moved back into your vault.";
|
||||
"RestartPremium" = "Restart Premium";
|
||||
"ThisItemIsArchivedSavingChangesWillRestoreItToYourVault" = "This item is archived. Saving changes will restore it to your vault.";
|
||||
|
||||
@ -774,6 +774,7 @@ public class ServiceContainer: Services { // swiftlint:disable:this type_body_le
|
||||
collectionHelper: collectionHelper,
|
||||
configService: configService,
|
||||
errorReporter: errorReporter,
|
||||
stateService: stateService,
|
||||
),
|
||||
vaultListDataPreparator: DefaultVaultListDataPreparator(
|
||||
cipherMatchingHelperFactory: DefaultCipherMatchingHelperFactory(
|
||||
|
||||
@ -188,6 +188,7 @@ class VaultListSectionsBuilderCollectionTests: BitwardenTestCase {
|
||||
collectionHelper: collectionHelper,
|
||||
configService: MockConfigService(),
|
||||
errorReporter: errorReporter,
|
||||
stateService: MockStateService(),
|
||||
withData: withData,
|
||||
)
|
||||
}
|
||||
|
||||
@ -275,6 +275,7 @@ class VaultListSectionsBuilderFolderTests: BitwardenTestCase {
|
||||
collectionHelper: collectionHelper,
|
||||
configService: MockConfigService(),
|
||||
errorReporter: errorReporter,
|
||||
stateService: MockStateService(),
|
||||
withData: withData,
|
||||
)
|
||||
}
|
||||
|
||||
@ -104,6 +104,8 @@ class DefaultVaultListSectionsBuilder: VaultListSectionsBuilder { // swiftlint:d
|
||||
let errorReporter: ErrorReporter
|
||||
/// Vault list data prepared to be used by the builder.
|
||||
var preparedData: VaultListPreparedData
|
||||
/// The service used by the application to manage account state.
|
||||
let stateService: StateService
|
||||
/// The vault list data to build.
|
||||
private var vaultListData = VaultListData()
|
||||
|
||||
@ -116,11 +118,13 @@ class DefaultVaultListSectionsBuilder: VaultListSectionsBuilder { // swiftlint:d
|
||||
/// - errorReporter: The service used by the application to report non-fatal errors.
|
||||
/// - preparedData: `VaultListPreparedData` to be used as input to build the sections where the caller
|
||||
/// decides which to include depending on the builder methods called.
|
||||
/// - stateService: The service used by the application to manage account state.
|
||||
init(
|
||||
clientService: ClientService,
|
||||
collectionHelper: CollectionHelper,
|
||||
configService: ConfigService,
|
||||
errorReporter: ErrorReporter,
|
||||
stateService: StateService,
|
||||
withData preparedData: VaultListPreparedData,
|
||||
) {
|
||||
self.clientService = clientService
|
||||
@ -128,6 +132,7 @@ class DefaultVaultListSectionsBuilder: VaultListSectionsBuilder { // swiftlint:d
|
||||
self.configService = configService
|
||||
self.errorReporter = errorReporter
|
||||
self.preparedData = preparedData
|
||||
self.stateService = stateService
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
@ -328,8 +333,13 @@ class DefaultVaultListSectionsBuilder: VaultListSectionsBuilder { // swiftlint:d
|
||||
func addHiddenItemsSection() async -> VaultListSectionsBuilder {
|
||||
var items: [VaultListItem] = []
|
||||
|
||||
let hasPremium = await stateService.doesActiveAccountHavePremium()
|
||||
if await configService.getFeatureFlag(.archiveVaultItems) {
|
||||
items.append(VaultListItem(id: "Archive", itemType: .group(.archive, preparedData.ciphersArchivedCount)))
|
||||
if hasPremium || preparedData.ciphersArchivedCount > 0 {
|
||||
items.append(
|
||||
VaultListItem(id: "Archive", itemType: .group(.archive, preparedData.ciphersArchivedCount)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
items.append(VaultListItem(id: "Trash", itemType: .group(.trash, preparedData.ciphersDeletedCount)))
|
||||
|
||||
@ -23,6 +23,8 @@ struct DefaultVaultListSectionsBuilderFactory: VaultListSectionsBuilderFactory {
|
||||
let configService: ConfigService
|
||||
/// The service used by the application to report non-fatal errors.
|
||||
let errorReporter: ErrorReporter
|
||||
/// The service used by the application to manage account state.
|
||||
let stateService: StateService
|
||||
|
||||
func make(withData preparedData: VaultListPreparedData) -> VaultListSectionsBuilder {
|
||||
DefaultVaultListSectionsBuilder(
|
||||
@ -30,6 +32,7 @@ struct DefaultVaultListSectionsBuilderFactory: VaultListSectionsBuilderFactory {
|
||||
collectionHelper: collectionHelper,
|
||||
configService: configService,
|
||||
errorReporter: errorReporter,
|
||||
stateService: stateService,
|
||||
withData: preparedData,
|
||||
)
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ class VaultListSectionsBuilderFactoryTests: BitwardenTestCase {
|
||||
collectionHelper: collectionHelper,
|
||||
configService: configService,
|
||||
errorReporter: errorReporter,
|
||||
stateService: MockStateService(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ class VaultListSectionsBuilderTests: BitwardenTestCase { // swiftlint:disable:th
|
||||
var clientService: MockClientService!
|
||||
var configService: MockConfigService!
|
||||
var errorReporter: MockErrorReporter!
|
||||
var stateService: MockStateService!
|
||||
var subject: DefaultVaultListSectionsBuilder!
|
||||
|
||||
// MARK: Setup & Teardown
|
||||
@ -24,6 +25,7 @@ class VaultListSectionsBuilderTests: BitwardenTestCase { // swiftlint:disable:th
|
||||
clientService = MockClientService()
|
||||
configService = MockConfigService()
|
||||
errorReporter = MockErrorReporter()
|
||||
stateService = MockStateService()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@ -32,6 +34,7 @@ class VaultListSectionsBuilderTests: BitwardenTestCase { // swiftlint:disable:th
|
||||
clientService = nil
|
||||
configService = nil
|
||||
errorReporter = nil
|
||||
stateService = nil
|
||||
subject = nil
|
||||
}
|
||||
|
||||
@ -382,11 +385,12 @@ class VaultListSectionsBuilderTests: BitwardenTestCase { // swiftlint:disable:th
|
||||
}
|
||||
}
|
||||
|
||||
/// `addHiddenItemsSection()` adds the hidden items section to the list of sections with the count of
|
||||
/// archived and deleted ciphers when the archive feature flag is on.
|
||||
/// `addHiddenItemsSection()` adds the hidden items section with archive when the feature flag is on
|
||||
/// and the user has premium.
|
||||
@MainActor
|
||||
func test_addHiddenItemsSection_archiveFeatureFlagEnabled() async {
|
||||
func test_addHiddenItemsSection_archiveFeatureFlagEnabled_hasPremium() async {
|
||||
configService.featureFlagsBool[.archiveVaultItems] = true
|
||||
stateService.doesActiveAccountHavePremiumResult = true
|
||||
setUpSubject(withData: VaultListPreparedData(
|
||||
ciphersArchivedCount: 5,
|
||||
ciphersDeletedCount: 10,
|
||||
@ -403,6 +407,49 @@ class VaultListSectionsBuilderTests: BitwardenTestCase { // swiftlint:disable:th
|
||||
}
|
||||
}
|
||||
|
||||
/// `addHiddenItemsSection()` does not add archive when the feature flag is on but the user
|
||||
/// does not have premium and there are no archived items.
|
||||
@MainActor
|
||||
func test_addHiddenItemsSection_archiveFeatureFlagEnabled_noPremium_noArchivedItems() async {
|
||||
configService.featureFlagsBool[.archiveVaultItems] = true
|
||||
stateService.doesActiveAccountHavePremiumResult = false
|
||||
setUpSubject(withData: VaultListPreparedData(
|
||||
ciphersArchivedCount: 0,
|
||||
ciphersDeletedCount: 10,
|
||||
))
|
||||
|
||||
let vaultListData = await subject.addHiddenItemsSection().build()
|
||||
|
||||
assertInlineSnapshot(of: vaultListData.sections.dump(), as: .lines) {
|
||||
"""
|
||||
Section[HiddenItems]: Hidden items
|
||||
- Group[Trash]: Trash (10)
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
/// `addHiddenItemsSection()` adds archive when the feature flag is on and the user
|
||||
/// does not have premium but there are archived items (grandfathered).
|
||||
@MainActor
|
||||
func test_addHiddenItemsSection_archiveFeatureFlagEnabled_noPremium_hasArchivedItems() async {
|
||||
configService.featureFlagsBool[.archiveVaultItems] = true
|
||||
stateService.doesActiveAccountHavePremiumResult = false
|
||||
setUpSubject(withData: VaultListPreparedData(
|
||||
ciphersArchivedCount: 3,
|
||||
ciphersDeletedCount: 10,
|
||||
))
|
||||
|
||||
let vaultListData = await subject.addHiddenItemsSection().build()
|
||||
|
||||
assertInlineSnapshot(of: vaultListData.sections.dump(), as: .lines) {
|
||||
"""
|
||||
Section[HiddenItems]: Hidden items
|
||||
- Group[Archive]: Archive (3)
|
||||
- Group[Trash]: Trash (10)
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
/// `addTOTPSection()` adds the TOTP section with an item when there are TOTP items.
|
||||
func test_addTOTPSection() {
|
||||
setUpSubject(
|
||||
@ -780,6 +827,7 @@ class VaultListSectionsBuilderTests: BitwardenTestCase { // swiftlint:disable:th
|
||||
collectionHelper: collectionHelper,
|
||||
configService: configService,
|
||||
errorReporter: errorReporter,
|
||||
stateService: stateService,
|
||||
withData: withData,
|
||||
)
|
||||
}
|
||||
|
||||
@ -24,6 +24,9 @@ enum VaultGroupAction: Equatable, Sendable {
|
||||
///
|
||||
case itemPressed(_ item: VaultListItem)
|
||||
|
||||
/// The user tapped in "Restart Premium" subscription for archive.
|
||||
case restartPremiumSubscription
|
||||
|
||||
/// The user has started or stopped searching.
|
||||
case searchStateChanged(isSearching: Bool)
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ final class VaultGroupProcessor: StateProcessor<
|
||||
|
||||
typealias Services = HasAuthRepository
|
||||
& HasConfigService
|
||||
& HasEnvironmentService
|
||||
& HasErrorReporter
|
||||
& HasEventService
|
||||
& HasPasteboardService
|
||||
@ -109,6 +110,7 @@ final class VaultGroupProcessor: StateProcessor<
|
||||
override func perform(_ effect: VaultGroupEffect) async {
|
||||
switch effect {
|
||||
case .appeared:
|
||||
await loadHasPremiumAccount()
|
||||
await checkPersonalOwnershipPolicy()
|
||||
await loadItemTypesUserCanCreate()
|
||||
await streamVaultList()
|
||||
@ -160,6 +162,8 @@ final class VaultGroupProcessor: StateProcessor<
|
||||
case let .totp(_, model):
|
||||
navigateToViewItem(cipherListView: model.cipherListView, id: model.id)
|
||||
}
|
||||
case .restartPremiumSubscription:
|
||||
state.url = services.environmentService.upgradeToPremiumURL
|
||||
case let .searchStateChanged(isSearching):
|
||||
if !isSearching {
|
||||
state.searchText = ""
|
||||
@ -190,6 +194,12 @@ final class VaultGroupProcessor: StateProcessor<
|
||||
state.canShowVaultFilter = await services.vaultRepository.canShowVaultFilter()
|
||||
}
|
||||
|
||||
/// Loads whether the current account has premium subscription.
|
||||
///
|
||||
private func loadHasPremiumAccount() async {
|
||||
state.hasPremium = await services.stateService.doesActiveAccountHavePremium()
|
||||
}
|
||||
|
||||
/// Checks available item types user can create.
|
||||
///
|
||||
private func loadItemTypesUserCanCreate() async {
|
||||
|
||||
@ -186,6 +186,19 @@ class VaultGroupProcessorTests: BitwardenTestCase { // swiftlint:disable:this ty
|
||||
XCTAssertEqual(errorReporter.errors.last as? BitwardenTestError, .example)
|
||||
}
|
||||
|
||||
/// `perform(_:)` with `.appeared` updates the state depending on if the user has premium account.
|
||||
@MainActor
|
||||
func test_perform_appeared_hasPremiumAccount() {
|
||||
stateService.doesActiveAccountHavePremiumResult = true
|
||||
|
||||
let task = Task {
|
||||
await subject.perform(.appeared)
|
||||
}
|
||||
defer { task.cancel() }
|
||||
|
||||
waitFor(subject.state.hasPremium)
|
||||
}
|
||||
|
||||
/// `perform(_:)` with `.appeared` updates the state depending on if the
|
||||
/// personal ownership policy is enabled.
|
||||
@MainActor
|
||||
|
||||
@ -67,6 +67,9 @@ struct VaultGroupState: Equatable, Sendable {
|
||||
/// The `VaultListGroup` being displayed.
|
||||
var group: VaultListGroup = .login
|
||||
|
||||
/// Whether the user has a premium account.
|
||||
var hasPremium: Bool = false
|
||||
|
||||
/// The base url used to fetch icons.
|
||||
var iconBaseURL: URL?
|
||||
|
||||
@ -130,6 +133,11 @@ struct VaultGroupState: Equatable, Sendable {
|
||||
/// The search vault filter used to display a single or all vaults for the user.
|
||||
var searchVaultFilterType = VaultFilterType.allVaults
|
||||
|
||||
/// Whether the archive premium subscription ended card should be shown.
|
||||
var showArchivePremiumSubscriptionEndedCard: Bool {
|
||||
!hasPremium && group == .archive
|
||||
}
|
||||
|
||||
/// Whether to show the special web icons.
|
||||
var showWebIcons = true
|
||||
|
||||
|
||||
@ -130,4 +130,36 @@ class VaultGroupStateTests: BitwardenTestCase {
|
||||
let subjectTotp = VaultGroupState(group: .totp, vaultFilterType: .myVault)
|
||||
XCTAssertNil(subjectTotp.noItemsTitle)
|
||||
}
|
||||
|
||||
/// `showArchivePremiumSubscriptionEndedCard` returns `true` when the user doesn't have premium
|
||||
/// and is viewing the archive group.
|
||||
func test_showArchivePremiumSubscriptionEndedCard() {
|
||||
let subjectNoPremiumArchive = VaultGroupState(
|
||||
group: .archive,
|
||||
hasPremium: false,
|
||||
vaultFilterType: .myVault
|
||||
)
|
||||
XCTAssertTrue(subjectNoPremiumArchive.showArchivePremiumSubscriptionEndedCard)
|
||||
|
||||
let subjectHasPremiumArchive = VaultGroupState(
|
||||
group: .archive,
|
||||
hasPremium: true,
|
||||
vaultFilterType: .myVault
|
||||
)
|
||||
XCTAssertFalse(subjectHasPremiumArchive.showArchivePremiumSubscriptionEndedCard)
|
||||
|
||||
let subjectNoPremiumLogin = VaultGroupState(
|
||||
group: .login,
|
||||
hasPremium: false,
|
||||
vaultFilterType: .myVault
|
||||
)
|
||||
XCTAssertFalse(subjectNoPremiumLogin.showArchivePremiumSubscriptionEndedCard)
|
||||
|
||||
let subjectHasPremiumLogin = VaultGroupState(
|
||||
group: .login,
|
||||
hasPremium: true,
|
||||
vaultFilterType: .myVault
|
||||
)
|
||||
XCTAssertFalse(subjectHasPremiumLogin.showArchivePremiumSubscriptionEndedCard)
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,6 +178,50 @@ class VaultGroupViewTests: BitwardenTestCase {
|
||||
assertSnapshot(of: subject, as: .defaultPortrait)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func disabletest_snapshot_oneArchivedItem_premium() {
|
||||
processor.state.hasPremium = true
|
||||
processor.state.group = .archive
|
||||
processor.state.loadingState = .data(
|
||||
[
|
||||
VaultListSection(
|
||||
id: "Items",
|
||||
items: [
|
||||
.fixture(cipherListView: .fixture(
|
||||
login: .fixture(username: "email@example.com"),
|
||||
name: "Example",
|
||||
archivedDate: .now,
|
||||
)),
|
||||
],
|
||||
name: Localizations.items,
|
||||
),
|
||||
],
|
||||
)
|
||||
assertSnapshot(of: subject, as: .defaultPortrait)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func disabletest_snapshot_oneArchivedItem_noPremium() {
|
||||
processor.state.hasPremium = false
|
||||
processor.state.group = .archive
|
||||
processor.state.loadingState = .data(
|
||||
[
|
||||
VaultListSection(
|
||||
id: "Items",
|
||||
items: [
|
||||
.fixture(cipherListView: .fixture(
|
||||
login: .fixture(username: "email@example.com"),
|
||||
name: "Example",
|
||||
archivedDate: .now,
|
||||
)),
|
||||
],
|
||||
name: Localizations.items,
|
||||
),
|
||||
],
|
||||
)
|
||||
assertSnapshot(of: subject, as: .defaultPortrait)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func disabletest_snapshot_search_oneItem() {
|
||||
processor.state.isSearching = true
|
||||
|
||||
@ -55,6 +55,19 @@ struct VaultGroupView: View {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
/// The action card for premium subscription ended for archive.
|
||||
@ViewBuilder private var archivePremiumSubscriptionEndedCard: some View {
|
||||
if store.state.showArchivePremiumSubscriptionEndedCard {
|
||||
ActionCard(
|
||||
title: Localizations.yourPremiumSubscriptionEnded,
|
||||
message: Localizations.yourPremiumSubscriptionEndedArchiveDescriptionLong,
|
||||
actionButtonState: ActionCard.ButtonState(title: Localizations.restartPremium) {
|
||||
store.send(.restartPremiumSubscription)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var content: some View {
|
||||
searchOrGroup
|
||||
.onChange(of: store.state.url) { newValue in
|
||||
@ -205,6 +218,8 @@ struct VaultGroupView: View {
|
||||
@ViewBuilder
|
||||
private func groupView(with sections: [VaultListSection]) -> some View {
|
||||
VStack(spacing: 16) {
|
||||
archivePremiumSubscriptionEndedCard
|
||||
|
||||
ForEach(sections) { section in
|
||||
VaultListSectionView(section: section) { item in
|
||||
Button {
|
||||
|
||||
@ -20,6 +20,7 @@ final class VaultListProcessor: StateProcessor<
|
||||
& HasAuthRepository
|
||||
& HasAuthService
|
||||
& HasChangeKdfService
|
||||
& HasConfigService
|
||||
& HasErrorReporter
|
||||
& HasEventService
|
||||
& HasFlightRecorder
|
||||
@ -221,7 +222,10 @@ extension VaultListProcessor {
|
||||
await checkPendingLoginRequests()
|
||||
await checkPersonalOwnershipPolicy()
|
||||
await loadItemTypesUserCanCreate()
|
||||
state.shouldShowArchiveOnboardingActionCard = await services.stateService.shouldDoArchiveOnboarding()
|
||||
|
||||
if await services.configService.getFeatureFlag(.archiveVaultItems) {
|
||||
state.shouldShowArchiveOnboardingActionCard = await services.stateService.shouldDoArchiveOnboarding()
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the user needs to update their KDF settings.
|
||||
|
||||
@ -20,6 +20,7 @@ class VaultListProcessorTests: BitwardenTestCase { // swiftlint:disable:this typ
|
||||
var authRepository: MockAuthRepository!
|
||||
var authService: MockAuthService!
|
||||
var changeKdfService: MockChangeKdfService!
|
||||
var configService: MockConfigService!
|
||||
var coordinator: MockCoordinator<VaultRoute, AuthAction>!
|
||||
var errorReporter: MockErrorReporter!
|
||||
var flightRecorder: MockFlightRecorder!
|
||||
@ -49,6 +50,7 @@ class VaultListProcessorTests: BitwardenTestCase { // swiftlint:disable:this typ
|
||||
authService = MockAuthService()
|
||||
errorReporter = MockErrorReporter()
|
||||
changeKdfService = MockChangeKdfService()
|
||||
configService = MockConfigService()
|
||||
coordinator = MockCoordinator()
|
||||
errorReporter = MockErrorReporter()
|
||||
flightRecorder = MockFlightRecorder()
|
||||
@ -71,6 +73,7 @@ class VaultListProcessorTests: BitwardenTestCase { // swiftlint:disable:this typ
|
||||
authRepository: authRepository,
|
||||
authService: authService,
|
||||
changeKdfService: changeKdfService,
|
||||
configService: configService,
|
||||
errorReporter: errorReporter,
|
||||
flightRecorder: flightRecorder,
|
||||
notificationService: notificationService,
|
||||
@ -98,6 +101,7 @@ class VaultListProcessorTests: BitwardenTestCase { // swiftlint:disable:this typ
|
||||
authRepository = nil
|
||||
authService = nil
|
||||
changeKdfService = nil
|
||||
configService = nil
|
||||
coordinator = nil
|
||||
errorReporter = nil
|
||||
flightRecorder = nil
|
||||
@ -540,6 +544,7 @@ class VaultListProcessorTests: BitwardenTestCase { // swiftlint:disable:this typ
|
||||
/// `perform(_:)` with `.appeared` updates whether to show the archive onboarding card.
|
||||
@MainActor
|
||||
func test_perform_appeared_loadArchiveOnboarding() async {
|
||||
configService.featureFlagsBool[.archiveVaultItems] = true
|
||||
stateService.doesActiveAccountHavePremiumResult = true
|
||||
stateService.archiveOnboardingShown = false
|
||||
|
||||
@ -548,6 +553,19 @@ class VaultListProcessorTests: BitwardenTestCase { // swiftlint:disable:this typ
|
||||
XCTAssertTrue(subject.state.shouldShowArchiveOnboardingActionCard)
|
||||
}
|
||||
|
||||
/// `perform(_:)` with `.appeared` doesn't update whether to show the archive onboarding card
|
||||
/// when archive FF is turned off.
|
||||
@MainActor
|
||||
func test_perform_appeared_loadArchiveOnboarding_FFOff() async {
|
||||
configService.featureFlagsBool[.archiveVaultItems] = false
|
||||
stateService.doesActiveAccountHavePremiumResult = true
|
||||
stateService.archiveOnboardingShown = false
|
||||
|
||||
await subject.perform(.appeared)
|
||||
|
||||
XCTAssertFalse(subject.state.shouldShowArchiveOnboardingActionCard)
|
||||
}
|
||||
|
||||
/// `perform(_:)` with `.dismissArchiveOnboardingActionCard` dismisses the archive onboarding card
|
||||
/// and sets the archive onboarding shown property to true.
|
||||
@MainActor
|
||||
|
||||
@ -8,6 +8,9 @@ import Foundation
|
||||
protocol AddEditItemState: Sendable {
|
||||
// MARK: Properties
|
||||
|
||||
/// The info text to display when item is archived.
|
||||
var archiveInfoText: String { get }
|
||||
|
||||
/// The card item state.
|
||||
var cardItemState: CardItemState { get set }
|
||||
|
||||
|
||||
@ -91,7 +91,7 @@ struct AddEditItemView: View {
|
||||
}
|
||||
|
||||
if store.state.shouldDisplayAsArchived {
|
||||
InfoContainer(text: Localizations.thisItemIsArchived, icon: SharedAsset.Icons.archive24)
|
||||
InfoContainer(text: store.state.archiveInfoText, icon: SharedAsset.Icons.archive24)
|
||||
.accessibilityIdentifier("ArchivedLabel")
|
||||
}
|
||||
|
||||
|
||||
@ -145,6 +145,16 @@ struct CipherItemState: Equatable { // swiftlint:disable:this type_body_length
|
||||
self
|
||||
}
|
||||
|
||||
/// The info text to display when item is archived.
|
||||
var archiveInfoText: String {
|
||||
guard shouldDisplayAsArchived else {
|
||||
return ""
|
||||
}
|
||||
return accountHasPremium
|
||||
? Localizations.thisItemIsArchived
|
||||
: Localizations.thisItemIsArchivedSavingChangesWillRestoreItToYourVault
|
||||
}
|
||||
|
||||
/// Whether or not this item can be archived by the user.
|
||||
var canBeArchived: Bool {
|
||||
isArchiveVaultItemsFFEnabled && cipher.archivedDate == nil && cipher.deletedDate == nil
|
||||
|
||||
@ -68,6 +68,51 @@ class CipherItemStateTests: BitwardenTestCase { // swiftlint:disable:this type_b
|
||||
XCTAssertTrue(state.loginState.isTOTPAvailable)
|
||||
}
|
||||
|
||||
/// `archiveInfoText` returns nil when the item is not archived.
|
||||
func test_archiveInfoText_notArchived() throws {
|
||||
let state = try CipherItemState.initForArchive(archivedDate: nil)
|
||||
XCTAssertEqual(state.archiveInfoText, "")
|
||||
}
|
||||
|
||||
/// `archiveInfoText` returns nil when the feature flag is disabled.
|
||||
func test_archiveInfoText_featureFlagDisabled() throws {
|
||||
let state = try CipherItemState.initForArchive(
|
||||
archivedDate: .now,
|
||||
isArchiveVaultItemsFFEnabled: false,
|
||||
)
|
||||
XCTAssertEqual(state.archiveInfoText, "")
|
||||
}
|
||||
|
||||
/// `archiveInfoText` returns nil when the item is deleted.
|
||||
func test_archiveInfoText_deleted() throws {
|
||||
let state = try CipherItemState.initForArchive(
|
||||
archivedDate: .now,
|
||||
deletedDate: .now,
|
||||
)
|
||||
XCTAssertEqual(state.archiveInfoText, "")
|
||||
}
|
||||
|
||||
/// `archiveInfoText` returns the premium text when the item is archived and user has premium.
|
||||
func test_archiveInfoText_archivedWithPremium() throws {
|
||||
let state = try CipherItemState.initForArchive(
|
||||
archivedDate: .now,
|
||||
hasPremium: true,
|
||||
)
|
||||
XCTAssertEqual(state.archiveInfoText, Localizations.thisItemIsArchived)
|
||||
}
|
||||
|
||||
/// `archiveInfoText` returns the non-premium text when the item is archived and user lacks premium.
|
||||
func test_archiveInfoText_archivedWithoutPremium() throws {
|
||||
let state = try CipherItemState.initForArchive(
|
||||
archivedDate: .now,
|
||||
hasPremium: false,
|
||||
)
|
||||
XCTAssertEqual(
|
||||
state.archiveInfoText,
|
||||
Localizations.thisItemIsArchivedSavingChangesWillRestoreItToYourVault
|
||||
)
|
||||
}
|
||||
|
||||
/// `canAssignToCollection` returns false if the user doesn't have access to any organizations.
|
||||
func test_canAssignToCollection_noOrganizations() throws {
|
||||
let cipher = CipherView.fixture(organizationId: nil)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user