mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			525 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			525 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Swift
		
	
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | 
						|
 | 
						|
import Combine
 | 
						|
import GRDB
 | 
						|
import Quick
 | 
						|
import Nimble
 | 
						|
 | 
						|
@testable import Session
 | 
						|
 | 
						|
class ThreadSettingsViewModelSpec: QuickSpec {
 | 
						|
    typealias ParentType = SessionTableViewModel<ThreadSettingsViewModel.NavButton, ThreadSettingsViewModel.Section, ThreadSettingsViewModel.Setting>
 | 
						|
    
 | 
						|
    // MARK: - Spec
 | 
						|
    
 | 
						|
    override func spec() {
 | 
						|
        var mockStorage: Storage!
 | 
						|
        var mockGeneralCache: MockGeneralCache!
 | 
						|
        var cancellables: [AnyCancellable] = []
 | 
						|
        var dependencies: Dependencies!
 | 
						|
        var viewModel: ThreadSettingsViewModel!
 | 
						|
        var didTriggerSearchCallbackTriggered: Bool = false
 | 
						|
        
 | 
						|
        describe("a ThreadSettingsViewModel") {
 | 
						|
            // MARK: - Configuration
 | 
						|
            
 | 
						|
            beforeEach {
 | 
						|
                mockStorage = SynchronousStorage(
 | 
						|
                    customWriter: try! DatabaseQueue(),
 | 
						|
                    customMigrations: [
 | 
						|
                        SNUtilitiesKit.migrations(),
 | 
						|
                        SNSnodeKit.migrations(),
 | 
						|
                        SNMessagingKit.migrations(),
 | 
						|
                        SNUIKit.migrations()
 | 
						|
                    ]
 | 
						|
                )
 | 
						|
                mockGeneralCache = MockGeneralCache()
 | 
						|
                dependencies = Dependencies(
 | 
						|
                    generalCache: Atomic(mockGeneralCache),
 | 
						|
                    storage: mockStorage,
 | 
						|
                    scheduler: .immediate
 | 
						|
                )
 | 
						|
                mockGeneralCache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)")
 | 
						|
                mockStorage.write { db in
 | 
						|
                    try SessionThread(
 | 
						|
                        id: "TestId",
 | 
						|
                        variant: .contact
 | 
						|
                    ).insert(db)
 | 
						|
                    
 | 
						|
                    try Identity(
 | 
						|
                        variant: .x25519PublicKey,
 | 
						|
                        data: Data(hex: TestConstants.publicKey)
 | 
						|
                    ).insert(db)
 | 
						|
                    
 | 
						|
                    try Profile(
 | 
						|
                        id: "05\(TestConstants.publicKey)",
 | 
						|
                        name: "TestMe"
 | 
						|
                    ).insert(db)
 | 
						|
                    
 | 
						|
                    try Profile(
 | 
						|
                        id: "TestId",
 | 
						|
                        name: "TestUser"
 | 
						|
                    ).insert(db)
 | 
						|
                }
 | 
						|
                viewModel = ThreadSettingsViewModel(
 | 
						|
                    dependencies: dependencies,
 | 
						|
                    threadId: "TestId",
 | 
						|
                    threadVariant: .contact,
 | 
						|
                    didTriggerSearch: {
 | 
						|
                        didTriggerSearchCallbackTriggered = true
 | 
						|
                    }
 | 
						|
                )
 | 
						|
                cancellables.append(
 | 
						|
                    viewModel.observableSettingsData
 | 
						|
                        .receiveOnMain(immediately: true)
 | 
						|
                        .sink(
 | 
						|
                            receiveCompletion: { _ in },
 | 
						|
                            receiveValue: { viewModel.updateSettings($0) }
 | 
						|
                        )
 | 
						|
                )
 | 
						|
            }
 | 
						|
            
 | 
						|
            afterEach {
 | 
						|
                cancellables.forEach { $0.cancel() }
 | 
						|
                
 | 
						|
                mockStorage = nil
 | 
						|
                cancellables = []
 | 
						|
                dependencies = nil
 | 
						|
                viewModel = nil
 | 
						|
                didTriggerSearchCallbackTriggered = false
 | 
						|
            }
 | 
						|
            
 | 
						|
            // MARK: - Basic Tests
 | 
						|
            
 | 
						|
            context("with any conversation type") {
 | 
						|
                it("triggers the search callback when tapping search") {
 | 
						|
                    viewModel.settingsData
 | 
						|
                        .first(where: { $0.model == .content })?
 | 
						|
                        .elements
 | 
						|
                        .first(where: { $0.id == .searchConversation })?
 | 
						|
                        .onTap?(nil)
 | 
						|
                    
 | 
						|
                    expect(didTriggerSearchCallbackTriggered).to(beTrue())
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("mutes a conversation") {
 | 
						|
                    viewModel.settingsData
 | 
						|
                        .first(where: { $0.model == .content })?
 | 
						|
                        .elements
 | 
						|
                        .first(where: { $0.id == .notificationMute })?
 | 
						|
                        .onTap?(nil)
 | 
						|
                    
 | 
						|
                    expect(
 | 
						|
                        mockStorage
 | 
						|
                            .read { db in try SessionThread.fetchOne(db, id: "TestId") }?
 | 
						|
                            .mutedUntilTimestamp
 | 
						|
                    )
 | 
						|
                    .toNot(beNil())
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("unmutes a conversation") {
 | 
						|
                    mockStorage.write { db in
 | 
						|
                        try SessionThread
 | 
						|
                            .updateAll(
 | 
						|
                                db,
 | 
						|
                                SessionThread.Columns.mutedUntilTimestamp.set(to: 1234567890)
 | 
						|
                            )
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    expect(
 | 
						|
                        mockStorage
 | 
						|
                            .read { db in try SessionThread.fetchOne(db, id: "TestId") }?
 | 
						|
                            .mutedUntilTimestamp
 | 
						|
                    )
 | 
						|
                    .toNot(beNil())
 | 
						|
                    
 | 
						|
                    viewModel.settingsData
 | 
						|
                        .first(where: { $0.model == .content })?
 | 
						|
                        .elements
 | 
						|
                        .first(where: { $0.id == .notificationMute })?
 | 
						|
                        .onTap?(nil)
 | 
						|
                
 | 
						|
                    expect(
 | 
						|
                        mockStorage
 | 
						|
                            .read { db in try SessionThread.fetchOne(db, id: "TestId") }?
 | 
						|
                            .mutedUntilTimestamp
 | 
						|
                    )
 | 
						|
                    .to(beNil())
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            context("with a note-to-self conversation") {
 | 
						|
                beforeEach {
 | 
						|
                    mockStorage.write { db in
 | 
						|
                        try SessionThread.deleteAll(db)
 | 
						|
                        
 | 
						|
                        try SessionThread(
 | 
						|
                            id: "05\(TestConstants.publicKey)",
 | 
						|
                            variant: .contact
 | 
						|
                        ).insert(db)
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    viewModel = ThreadSettingsViewModel(
 | 
						|
                        dependencies: dependencies,
 | 
						|
                        threadId: "05\(TestConstants.publicKey)",
 | 
						|
                        threadVariant: .contact,
 | 
						|
                        didTriggerSearch: {
 | 
						|
                            didTriggerSearchCallbackTriggered = true
 | 
						|
                        }
 | 
						|
                    )
 | 
						|
                    cancellables.append(
 | 
						|
                        viewModel.observableSettingsData
 | 
						|
                            .receiveOnMain(immediately: true)
 | 
						|
                            .sink(
 | 
						|
                                receiveCompletion: { _ in },
 | 
						|
                                receiveValue: { viewModel.updateSettings($0) }
 | 
						|
                            )
 | 
						|
                    )
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("has the correct title") {
 | 
						|
                    expect(viewModel.title).to(equal("vc_settings_title".localized()))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("starts in the standard nav state") {
 | 
						|
                    expect(viewModel.navState.firstValue())
 | 
						|
                        .to(equal(.standard))
 | 
						|
                    
 | 
						|
                    expect(viewModel.leftNavItems.firstValue()).to(equal([]))
 | 
						|
                    expect(viewModel.rightNavItems.firstValue())
 | 
						|
                        .to(equal([
 | 
						|
                            ParentType.NavItem(
 | 
						|
                                id: .edit,
 | 
						|
                                systemItem: .edit,
 | 
						|
                                accessibilityIdentifier: "Edit button"
 | 
						|
                            )
 | 
						|
                        ]))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("has no mute button") {
 | 
						|
                    expect(
 | 
						|
                        viewModel.settingsData
 | 
						|
                            .first(where: { $0.model == .content })?
 | 
						|
                            .elements
 | 
						|
                            .first(where: { $0.id == .notificationMute })
 | 
						|
                    ).to(beNil())
 | 
						|
                }
 | 
						|
                
 | 
						|
                context("when entering edit mode") {
 | 
						|
                    beforeEach {
 | 
						|
                        viewModel.rightNavItems.firstValue()??.first?.action?()
 | 
						|
                        
 | 
						|
                        let leftAccessory: SessionCell.Accessory? = viewModel.settingsData.first?
 | 
						|
                            .elements.first?
 | 
						|
                            .leftAccessory
 | 
						|
                        
 | 
						|
                        switch leftAccessory {
 | 
						|
                            case .threadInfo(_, _, _, _, let titleChanged): titleChanged?("TestNew")
 | 
						|
                            default: break
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    it("enters the editing state") {
 | 
						|
                        expect(viewModel.navState.firstValue())
 | 
						|
                            .to(equal(.editing))
 | 
						|
                        
 | 
						|
                        expect(viewModel.leftNavItems.firstValue())
 | 
						|
                            .to(equal([
 | 
						|
                                ParentType.NavItem(
 | 
						|
                                    id: .cancel,
 | 
						|
                                    systemItem: .cancel,
 | 
						|
                                    accessibilityIdentifier: "Cancel button"
 | 
						|
                                )
 | 
						|
                            ]))
 | 
						|
                        expect(viewModel.rightNavItems.firstValue())
 | 
						|
                            .to(equal([
 | 
						|
                                ParentType.NavItem(
 | 
						|
                                    id: .done,
 | 
						|
                                    systemItem: .done,
 | 
						|
                                    accessibilityIdentifier: "Done button"
 | 
						|
                                )
 | 
						|
                            ]))
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    context("when cancelling edit mode") {
 | 
						|
                        beforeEach {
 | 
						|
                            viewModel.leftNavItems.firstValue()??.first?.action?()
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        it("exits editing mode") {
 | 
						|
                            expect(viewModel.navState.firstValue())
 | 
						|
                                .to(equal(.standard))
 | 
						|
                            
 | 
						|
                            expect(viewModel.leftNavItems.firstValue()).to(equal([]))
 | 
						|
                            expect(viewModel.rightNavItems.firstValue())
 | 
						|
                                .to(equal([
 | 
						|
                                    ParentType.NavItem(
 | 
						|
                                        id: .edit,
 | 
						|
                                        systemItem: .edit,
 | 
						|
                                        accessibilityIdentifier: "Edit button"
 | 
						|
                                    )
 | 
						|
                                ]))
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        it("does not update the nickname for the current user") {
 | 
						|
                            expect(
 | 
						|
                                mockStorage
 | 
						|
                                    .read { db in
 | 
						|
                                        try Profile.fetchOne(db, id: "05\(TestConstants.publicKey)")
 | 
						|
                                    }?
 | 
						|
                                    .nickname
 | 
						|
                            )
 | 
						|
                            .to(beNil())
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    context("when saving edit mode") {
 | 
						|
                        beforeEach {
 | 
						|
                            viewModel.rightNavItems.firstValue()??.first?.action?()
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        it("exits editing mode") {
 | 
						|
                            expect(viewModel.navState.firstValue())
 | 
						|
                                .to(equal(.standard))
 | 
						|
                            
 | 
						|
                            expect(viewModel.leftNavItems.firstValue()).to(equal([]))
 | 
						|
                            expect(viewModel.rightNavItems.firstValue())
 | 
						|
                                .to(equal([
 | 
						|
                                    ParentType.NavItem(
 | 
						|
                                        id: .edit,
 | 
						|
                                        systemItem: .edit,
 | 
						|
                                        accessibilityIdentifier: "Edit button"
 | 
						|
                                    )
 | 
						|
                                ]))
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        it("updates the nickname for the current user") {
 | 
						|
                            expect(
 | 
						|
                                mockStorage
 | 
						|
                                    .read { db in
 | 
						|
                                        try Profile.fetchOne(db, id: "05\(TestConstants.publicKey)")
 | 
						|
                                    }?
 | 
						|
                                    .nickname
 | 
						|
                            )
 | 
						|
                            .to(equal("TestNew"))
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            context("with a one-to-one conversation") {
 | 
						|
                beforeEach {
 | 
						|
                    mockStorage.write { db in
 | 
						|
                        try SessionThread.deleteAll(db)
 | 
						|
                        
 | 
						|
                        try SessionThread(
 | 
						|
                            id: "TestId",
 | 
						|
                            variant: .contact
 | 
						|
                        ).insert(db)
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("has the correct title") {
 | 
						|
                    expect(viewModel.title).to(equal("vc_settings_title".localized()))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("starts in the standard nav state") {
 | 
						|
                    expect(viewModel.navState.firstValue())
 | 
						|
                        .to(equal(.standard))
 | 
						|
                    
 | 
						|
                    expect(viewModel.leftNavItems.firstValue()).to(equal([]))
 | 
						|
                    expect(viewModel.rightNavItems.firstValue())
 | 
						|
                        .to(equal([
 | 
						|
                            ParentType.NavItem(
 | 
						|
                                id: .edit,
 | 
						|
                                systemItem: .edit,
 | 
						|
                                accessibilityIdentifier: "Edit button"
 | 
						|
                            )
 | 
						|
                        ]))
 | 
						|
                }
 | 
						|
                
 | 
						|
                context("when entering edit mode") {
 | 
						|
                    beforeEach {
 | 
						|
                        viewModel.rightNavItems.firstValue()??.first?.action?()
 | 
						|
                        
 | 
						|
                        let leftAccessory: SessionCell.Accessory? = viewModel.settingsData.first?
 | 
						|
                            .elements.first?
 | 
						|
                            .leftAccessory
 | 
						|
                        
 | 
						|
                        switch leftAccessory {
 | 
						|
                            case .threadInfo(_, _, _, _, let titleChanged): titleChanged?("TestUserNew")
 | 
						|
                            default: break
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    it("enters the editing state") {
 | 
						|
                        expect(viewModel.navState.firstValue())
 | 
						|
                            .to(equal(.editing))
 | 
						|
                        
 | 
						|
                        expect(viewModel.leftNavItems.firstValue())
 | 
						|
                            .to(equal([
 | 
						|
                                ParentType.NavItem(
 | 
						|
                                    id: .cancel,
 | 
						|
                                    systemItem: .cancel,
 | 
						|
                                    accessibilityIdentifier: "Cancel button"
 | 
						|
                                )
 | 
						|
                            ]))
 | 
						|
                        expect(viewModel.rightNavItems.firstValue())
 | 
						|
                            .to(equal([
 | 
						|
                                ParentType.NavItem(
 | 
						|
                                    id: .done,
 | 
						|
                                    systemItem: .done,
 | 
						|
                                    accessibilityIdentifier: "Done button"
 | 
						|
                                )
 | 
						|
                            ]))
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    context("when cancelling edit mode") {
 | 
						|
                        beforeEach {
 | 
						|
                            viewModel.leftNavItems.firstValue()??.first?.action?()
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        it("exits editing mode") {
 | 
						|
                            expect(viewModel.navState.firstValue())
 | 
						|
                                .to(equal(.standard))
 | 
						|
                            
 | 
						|
                            expect(viewModel.leftNavItems.firstValue()).to(equal([]))
 | 
						|
                            expect(viewModel.rightNavItems.firstValue())
 | 
						|
                                .to(equal([
 | 
						|
                                    ParentType.NavItem(
 | 
						|
                                        id: .edit,
 | 
						|
                                        systemItem: .edit,
 | 
						|
                                        accessibilityIdentifier: "Edit button"
 | 
						|
                                    )
 | 
						|
                                ]))
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        it("does not update the nickname for the current user") {
 | 
						|
                            expect(
 | 
						|
                                mockStorage
 | 
						|
                                    .read { db in try Profile.fetchOne(db, id: "TestId") }?
 | 
						|
                                    .nickname
 | 
						|
                            )
 | 
						|
                            .to(beNil())
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    context("when saving edit mode") {
 | 
						|
                        beforeEach {
 | 
						|
                            viewModel.rightNavItems.firstValue()??.first?.action?()
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        it("exits editing mode") {
 | 
						|
                            expect(viewModel.navState.firstValue())
 | 
						|
                                .to(equal(.standard))
 | 
						|
                            
 | 
						|
                            expect(viewModel.leftNavItems.firstValue()).to(equal([]))
 | 
						|
                            expect(viewModel.rightNavItems.firstValue())
 | 
						|
                                .to(equal([
 | 
						|
                                    ParentType.NavItem(
 | 
						|
                                        id: .edit,
 | 
						|
                                        systemItem: .edit,
 | 
						|
                                        accessibilityIdentifier: "Edit button"
 | 
						|
                                    )
 | 
						|
                                ]))
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        it("updates the nickname for the current user") {
 | 
						|
                            expect(
 | 
						|
                                mockStorage
 | 
						|
                                    .read { db in try Profile.fetchOne(db, id: "TestId") }?
 | 
						|
                                    .nickname
 | 
						|
                            )
 | 
						|
                            .to(equal("TestUserNew"))
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            context("with a group conversation") {
 | 
						|
                beforeEach {
 | 
						|
                    mockStorage.write { db in
 | 
						|
                        try SessionThread.deleteAll(db)
 | 
						|
                        
 | 
						|
                        try SessionThread(
 | 
						|
                            id: "TestId",
 | 
						|
                            variant: .closedGroup
 | 
						|
                        ).insert(db)
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    viewModel = ThreadSettingsViewModel(
 | 
						|
                        dependencies: dependencies,
 | 
						|
                        threadId: "TestId",
 | 
						|
                        threadVariant: .closedGroup,
 | 
						|
                        didTriggerSearch: {
 | 
						|
                            didTriggerSearchCallbackTriggered = true
 | 
						|
                        }
 | 
						|
                    )
 | 
						|
                    cancellables.append(
 | 
						|
                        viewModel.observableSettingsData
 | 
						|
                            .receiveOnMain(immediately: true)
 | 
						|
                            .sink(
 | 
						|
                                receiveCompletion: { _ in },
 | 
						|
                                receiveValue: { viewModel.updateSettings($0) }
 | 
						|
                            )
 | 
						|
                    )
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("has the correct title") {
 | 
						|
                    expect(viewModel.title).to(equal("vc_group_settings_title".localized()))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("starts in the standard nav state") {
 | 
						|
                    expect(viewModel.navState.firstValue())
 | 
						|
                        .to(equal(.standard))
 | 
						|
                    
 | 
						|
                    expect(viewModel.leftNavItems.firstValue()).to(equal([]))
 | 
						|
                    expect(viewModel.rightNavItems.firstValue()).to(equal([]))
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            context("with a community conversation") {
 | 
						|
                beforeEach {
 | 
						|
                    mockStorage.write { db in
 | 
						|
                        try SessionThread.deleteAll(db)
 | 
						|
                        
 | 
						|
                        try SessionThread(
 | 
						|
                            id: "TestId",
 | 
						|
                            variant: .openGroup
 | 
						|
                        ).insert(db)
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    viewModel = ThreadSettingsViewModel(
 | 
						|
                        dependencies: dependencies,
 | 
						|
                        threadId: "TestId",
 | 
						|
                        threadVariant: .openGroup,
 | 
						|
                        didTriggerSearch: {
 | 
						|
                            didTriggerSearchCallbackTriggered = true
 | 
						|
                        }
 | 
						|
                    )
 | 
						|
                    cancellables.append(
 | 
						|
                        viewModel.observableSettingsData
 | 
						|
                            .receiveOnMain(immediately: true)
 | 
						|
                            .sink(
 | 
						|
                                receiveCompletion: { _ in },
 | 
						|
                                receiveValue: { viewModel.updateSettings($0) }
 | 
						|
                            )
 | 
						|
                    )
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("has the correct title") {
 | 
						|
                    expect(viewModel.title).to(equal("vc_group_settings_title".localized()))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("starts in the standard nav state") {
 | 
						|
                    expect(viewModel.navState.firstValue())
 | 
						|
                        .to(equal(.standard))
 | 
						|
                    
 | 
						|
                    expect(viewModel.leftNavItems.firstValue()).to(equal([]))
 | 
						|
                    expect(viewModel.rightNavItems.firstValue()).to(equal([]))
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |