Skip to content

Commit

Permalink
Implement the new Space selector bottom sheet #6410
Browse files Browse the repository at this point in the history
- Added space switching analytics
- Implemented unit and UI test for space selector bottom sheet
  • Loading branch information
gileluard committed Jul 27, 2022
1 parent 8ff2983 commit 356a466
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 60 deletions.
3 changes: 2 additions & 1 deletion RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ enum MockAppScreens {
MockTemplateSimpleScreenScreenState.self,
MockTemplateUserProfileScreenState.self,
MockTemplateRoomListScreenState.self,
MockTemplateRoomChatScreenState.self
MockTemplateRoomChatScreenState.self,
MockSpaceSelectorScreenState.self
]
}

Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,10 @@ final class SpaceSelectorBottomSheetCoordinator: Coordinator, Presentable {
case .cancel:
self.completion?(.cancel)
case .homeSelected:
self.trackSpaceSelection(with: nil)
self.completion?(.homeSelected)
case .spaceSelected(let item):
self.trackSpaceSelection(with: item.id)
self.completion?(.spaceSelected(item))
case .spaceDisclosure(let item):
self.pushSpace(withId: item.id)
Expand Down Expand Up @@ -140,4 +142,13 @@ final class SpaceSelectorBottomSheetCoordinator: Coordinator, Presentable {
}
}
}

private func trackSpaceSelection(with spaceId: String?) {
guard parameters.selectedSpaceId != spaceId else {
Analytics.shared.trackInteraction(.spacePanelSelectedSpace)
return
}

Analytics.shared.trackInteraction(.spacePanelSwitchSpace)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,30 @@ enum MockSpaceSelectorScreenState: MockScreenState, CaseIterable {
// A case for each state you want to represent
// with specific, minimal associated data that will allow you
// mock that screen.
case initialList
case emptyList

case selection

/// The associated screen
var screenType: Any.Type {
SpaceSelector.self
}

/// A list of screen state definitions
static var allCases: [MockSpaceSelectorScreenState] {
[.emptyList]
[.initialList, .emptyList, .selection]
}

/// Generate the view struct for the screen state.
var screenView: ([Any], AnyView) {
let service: MockSpaceSelectorService
switch self {
case .initialList:
service = MockSpaceSelectorService()
case .emptyList:
service = MockSpaceSelectorService(spaceList: [], parentSpaceName: nil, selectedSpaceId: nil)
service = MockSpaceSelectorService(spaceList: [MockSpaceSelectorService.homeItem])
case .selection:
service = MockSpaceSelectorService(selectedSpaceId: MockSpaceSelectorService.defaultSpaceList[2].id)
}
let viewModel = SpaceSelectorViewModel.makeViewModel(service: service)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,25 @@

import Foundation
import Combine
import UIKit

@available(iOS 14.0, *)
class MockSpaceSelectorService: SpaceSelectorServiceProtocol {

static let homeItem = SpaceSelectorListItemData(id: SpaceSelectorListItemDataHomeSpaceId, avatar: nil, icon: UIImage(systemName: "house"), displayName: "All Chats", notificationCount: 0, highlightedNotificationCount: 0, hasSubItems: false)
static let defaultSpaceList = [
homeItem,
SpaceSelectorListItemData(id: "!aaabaa:matrix.org", avatar: nil, icon: UIImage(systemName: "number"), displayName: "Default Space", notificationCount: 0, highlightedNotificationCount: 0, hasSubItems: false),
SpaceSelectorListItemData(id: "!zzasds:matrix.org", avatar: nil, icon: UIImage(systemName: "number"), displayName: "Space with sub items", notificationCount: 0, highlightedNotificationCount: 0, hasSubItems: true),
SpaceSelectorListItemData(id: "!scthve:matrix.org", avatar: nil, icon: UIImage(systemName: "number"), displayName: "Space with notifications", notificationCount: 55, highlightedNotificationCount: 0, hasSubItems: true),
SpaceSelectorListItemData(id: "!ferggs:matrix.org", avatar: nil, icon: UIImage(systemName: "number"), displayName: "Space with highlight", notificationCount: 99, highlightedNotificationCount: 50, hasSubItems: false)
]

var spaceListSubject: CurrentValueSubject<[SpaceSelectorListItemData], Never>
var parentSpaceNameSubject: CurrentValueSubject<String?, Never>
var selectedSpaceId: String?

init(spaceList: [SpaceSelectorListItemData], parentSpaceName: String?, selectedSpaceId: String?) {
init(spaceList: [SpaceSelectorListItemData] = defaultSpaceList, parentSpaceName: String? = nil, selectedSpaceId: String = SpaceSelectorListItemDataHomeSpaceId) {
self.spaceListSubject = CurrentValueSubject(spaceList)
self.parentSpaceNameSubject = CurrentValueSubject(parentSpaceName)
self.selectedSpaceId = selectedSpaceId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,26 @@ import XCTest
import RiotSwiftUI

@available(iOS 14.0, *)
class SpaceSelectorUITests: MockScreenTest {

override class var screenType: MockScreenState.Type {
return MockSpaceSelectorBottomSheetScreenState.self
}

override class func createTest() -> MockScreenTest {
return SpaceSelectorUITests(selector: #selector(verifySpaceSelectorBottomSheetScreen))
}

func verifySpaceSelectorBottomSheetScreen() throws {
guard let screenState = screenState as? MockSpaceSelectorBottomSheetScreenState else { fatalError("no screen") }
switch screenState {
case .presence(let presence):
verifySpaceSelectorBottomSheetPresence(presence: presence)
case .longDisplayName(let name):
verifySpaceSelectorBottomSheetLongName(name: name)
class SpaceSelectorUITests: MockScreenTestCase {

func testAnalyticsPromptNewUser() {
app.goToScreenWithIdentifier(MockSpaceSelectorScreenState.initialList.title)

let disclosureButtons = app.buttons.matching(identifier: "disclosureButton").allElementsBoundByIndex
XCTAssertEqual(disclosureButtons.count, MockSpaceSelectorService.defaultSpaceList.filter { $0.hasSubItems }.count)

let notificationBadges = app.staticTexts.matching(identifier: "notificationBadge").allElementsBoundByIndex
let itemsWithNotifications = MockSpaceSelectorService.defaultSpaceList.filter { $0.notificationCount > 0 }
XCTAssertEqual(notificationBadges.count, itemsWithNotifications.count)
for (index, notificationBadge) in notificationBadges.enumerated() {
XCTAssertEqual("\(itemsWithNotifications[index].notificationCount)", notificationBadge.label)
}

let spaceItemNameList = app.staticTexts.matching(identifier: "itemName").allElementsBoundByIndex
XCTAssertEqual(spaceItemNameList.count, MockSpaceSelectorService.defaultSpaceList.count)
for (index, item) in MockSpaceSelectorService.defaultSpaceList.enumerated() {
XCTAssertEqual(item.displayName, spaceItemNameList[index].label)
}
}

func verifySpaceSelectorBottomSheetPresence(presence: SpaceSelectorBottomSheetPresence) {
let presenceText = app.staticTexts["presenceText"]
XCTAssert(presenceText.exists)
XCTAssertEqual(presenceText.label, presence.title)
}

func verifySpaceSelectorBottomSheetLongName(name: String) {
let displayNameText = app.staticTexts["displayNameText"]
XCTAssert(displayNameText.exists)
XCTAssertEqual(displayNameText.label, name)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,22 @@ import Combine

@available(iOS 14.0, *)
class SpaceSelectorViewModelTests: XCTestCase {
private enum Constants {
static let presenceInitialValue: SpaceSelectorBottomSheetPresence = .offline
static let displayName = "Alice"
}
var service: MockSpaceSelectorBottomSheetService!
var viewModel: SpaceSelectorBottomSheetViewModelProtocol!
var context: SpaceSelectorBottomSheetViewModelType.Context!

var service: MockSpaceSelectorService!
var viewModel: SpaceSelectorViewModelProtocol!
var context: SpaceSelectorViewModelType.Context!
var cancellables = Set<AnyCancellable>()

override func setUpWithError() throws {
service = MockSpaceSelectorService(displayName: Constants.displayName, presence: Constants.presenceInitialValue)
viewModel = SpaceSelectorViewModel.makeSpaceSelectorViewModel(service: service)
service = MockSpaceSelectorService()
viewModel = SpaceSelectorViewModel.makeViewModel(service: service)
context = viewModel.context
}

func testInitialState() {
XCTAssertEqual(context.viewState.displayName, Constants.displayName)
XCTAssertEqual(context.viewState.presence, Constants.presenceInitialValue)
}

func testFirstPresenceReceived() throws {
let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(1).first()
XCTAssertEqual(try xcAwait(presencePublisher), [Constants.presenceInitialValue])
XCTAssertEqual(context.viewState.selectedSpaceId, MockSpaceSelectorService.homeItem.id)
XCTAssertEqual(context.viewState.items, MockSpaceSelectorService.defaultSpaceList)
XCTAssertNil(context.viewState.parentName)
}

func testPresenceUpdatesReceived() throws {
let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(3).first()
let awaitDeferred = xcAwaitDeferred(presencePublisher)
let newPresenceValue1: SpaceSelectorBottomSheetPresence = .online
let newPresenceValue2: SpaceSelectorBottomSheetPresence = .idle
service.simulateUpdate(presence: newPresenceValue1)
service.simulateUpdate(presence: newPresenceValue2)
XCTAssertEqual(try awaitDeferred(), [Constants.presenceInitialValue, newPresenceValue1, newPresenceValue2])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ struct SpaceSelectorListRow: View {
Text(displayName ?? "")
.foregroundColor(theme.colors.primaryContent)
.font(theme.fonts.bodySB)
.accessibility(identifier: "itemNameText")
.accessibility(identifier: "itemName")
Spacer()
if notificationCount > 0 {
Text("\(notificationCount)")
Expand All @@ -79,6 +79,7 @@ struct SpaceSelectorListRow: View {
.renderingMode(.template)
.foregroundColor(theme.colors.accent)
}
.accessibility(identifier: "disclosureButton")
}
}
.padding(.vertical, 8)
Expand All @@ -91,3 +92,24 @@ struct SpaceSelectorListRow: View {
}

}

// MARK: - Previews

struct SpaceSelectorListRow_Previews: PreviewProvider {

static var previews: some View {
sampleView.theme(.light).preferredColorScheme(.light)
sampleView.theme(.dark).preferredColorScheme(.dark)
}

static var sampleView: some View {
VStack(spacing: 8) {
SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: false, isSelected: false, notificationCount: 0, highlightedNotificationCount: 0, disclosureAction: nil)
SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: true, isSelected: false, notificationCount: 0, highlightedNotificationCount: 0, disclosureAction: nil)
SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: true, isSelected: false, notificationCount: 99, highlightedNotificationCount: 0, disclosureAction: nil)
SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: false, isSelected: false, notificationCount: 99, highlightedNotificationCount: 1, disclosureAction: nil)
SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: true, isSelected: true, notificationCount: 99, highlightedNotificationCount: 1, disclosureAction: nil)
}
}

}

0 comments on commit 356a466

Please sign in to comment.