diff --git a/Canvas.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Canvas.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 402ceda791..458d2a9f67 100644
--- a/Canvas.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Canvas.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,5 +1,5 @@
{
- "originHash" : "102d90cdfa1716b86d908a5b9c78094cc7e8ac6573d7ecb6e8ea7a20d67852e4",
+ "originHash" : "484f3bb026c883046090249288537aa1c517090fdb78f2b0675a8aa42b38fbed",
"pins" : [
{
"identity" : "abseil-cpp-binary",
@@ -159,8 +159,26 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
- "revision" : "102a647b573f60f73afdce5613a51d71349fe507",
- "version" : "1.30.0"
+ "revision" : "2547102afd04fe49f1b286090f13ebce07284980",
+ "version" : "1.31.1"
+ }
+ },
+ {
+ "identity" : "swift-snapshot-testing",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swift-snapshot-testing",
+ "state" : {
+ "revision" : "2e6a85b73fc14e27d7542165ae73b1a10516ca9a",
+ "version" : "1.17.7"
+ }
+ },
+ {
+ "identity" : "swift-syntax",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/swiftlang/swift-syntax",
+ "state" : {
+ "revision" : "0687f71944021d616d34d922343dcef086855920",
+ "version" : "600.0.1"
}
},
{
diff --git a/Core/Core/Common/CommonUI/Fonts/UIFontExtensions.swift b/Core/Core/Common/CommonUI/Fonts/UIFontExtensions.swift
index 921c089008..0a343e5c8d 100644
--- a/Core/Core/Common/CommonUI/Fonts/UIFontExtensions.swift
+++ b/Core/Core/Common/CommonUI/Fonts/UIFontExtensions.swift
@@ -142,7 +142,14 @@ public extension UIFont {
static func applicationFontName(weight: UIFont.Weight, isItalic: Bool = false) -> String {
let isK5Font = AppEnvironment.shared.k5.isK5Enabled
- let font = isK5Font ? "BalsamiqSans" : "Lato"
+ let font: String
+ if isK5Font {
+ font = "BalsamiqSans"
+ } else if AppEnvironment.shared.app == .horizon {
+ font = "Figtree"
+ } else {
+ font = "Lato"
+ }
var suffix = ""
if isK5Font {
diff --git a/Horizon/Horizon/horizon.yml b/Horizon/Horizon/horizon.yml
index cf33c86e00..9c5b634a7e 100644
--- a/Horizon/Horizon/horizon.yml
+++ b/Horizon/Horizon/horizon.yml
@@ -34,3 +34,7 @@ schemes:
build:
targets:
Horizon: all
+ test:
+ targets:
+ - HorizonSnapshotTests
+ - HorizonUnitTests
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/AccountViewSnapshotTests.swift b/Horizon/HorizonSnapshotTests/Features/Account/AccountViewSnapshotTests.swift
new file mode 100644
index 0000000000..e17ec9e352
--- /dev/null
+++ b/Horizon/HorizonSnapshotTests/Features/Account/AccountViewSnapshotTests.swift
@@ -0,0 +1,168 @@
+//
+// This file is part of Canvas.
+// Copyright (C) 2024-present Instructure, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+//
+
+import Combine
+import Core
+import CoreData
+@testable import Horizon
+import HorizonUI
+import SnapshotTesting
+import SwiftUI
+import TestsFoundation
+import XCTest
+
+class AccountViewSnapshotTests: HorizonSnapshotTestCase {
+ func testAccountViewDefault() {
+ let viewModel = createMockAccountViewModel(
+ userName: "John Doe",
+ isExperienceSwitchAvailable: true,
+ isLoading: false
+ )
+ let view = AccountView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "account-default"
+ )
+ }
+
+ func testAccountViewLoading() {
+ let viewModel = createMockAccountViewModel(
+ userName: "John Doe",
+ isExperienceSwitchAvailable: true,
+ isLoading: true
+ )
+ let view = AccountView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "account-loading"
+ )
+ }
+
+ func testAccountViewNoExperienceSwitch() {
+ let viewModel = createMockAccountViewModel(
+ userName: "Jane Smith",
+ isExperienceSwitchAvailable: false,
+ isLoading: false
+ )
+ let view = AccountView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "account-no-experience-switch"
+ )
+ }
+
+ func testAccountViewAccessibility() {
+ let viewModel = createMockAccountViewModel(
+ userName: "Very Long User Name That Should Wrap",
+ isExperienceSwitchAvailable: true,
+ isLoading: false
+ )
+ let view = AccountView(viewModel: viewModel)
+
+ assertAccessibilitySnapshot(
+ of: view,
+ named: "account-accessibility"
+ )
+ }
+
+ // MARK: - Helper Methods
+
+ private func createMockAccountViewModel(
+ userName: String,
+ isExperienceSwitchAvailable: Bool,
+ isLoading: Bool
+ ) -> AccountViewModel {
+ let mockUserInteractor = MockGetUserInteractor(userName: userName, context: databaseClient)
+ let mockExperienceInteractor = MockExperienceSummaryInteractor(isAvailable: isExperienceSwitchAvailable)
+
+ let viewModel = AccountViewModel(
+ getUserInteractor: mockUserInteractor,
+ appExperienceInteractor: mockExperienceInteractor
+ )
+
+ // Trigger data loading
+ viewModel.getUserName()
+
+ // Override loading state after initial setup
+ viewModel.isLoading = isLoading
+
+ return viewModel
+ }
+}
+
+// MARK: - Mock Interactors
+
+private class MockGetUserInteractor: GetUserInteractor {
+ let userName: String
+ let context: NSManagedObjectContext
+
+ init(userName: String, context: NSManagedObjectContext) {
+ self.userName = userName
+ self.context = context
+ }
+
+ func getUser() -> AnyPublisher {
+ let user = UserProfile(context: context)
+ user.id = "1"
+ user.name = userName
+ user.shortName = userName
+ user.pronouns = nil
+ user.avatarURL = URL(string: "https://example.com/avatar.jpg")
+ user.email = "user@example.com"
+ user.locale = "en"
+
+ return Just(user)
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+
+ func canUpdateName() -> AnyPublisher {
+ Just(true)
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+}
+
+private class MockExperienceSummaryInteractor: ExperienceSummaryInteractor {
+ let isAvailable: Bool
+
+ init(isAvailable: Bool) {
+ self.isAvailable = isAvailable
+ }
+
+ func getExperienceSummary() -> AnyPublisher {
+ Just(.careerLearner)
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+
+ func isExperienceSwitchAvailable() -> AnyPublisher {
+ Just(isAvailable).eraseToAnyPublisher()
+ }
+
+ func isExperienceSwitchAvailableAsync() async -> Bool {
+ isAvailable
+ }
+
+ func switchExperience(to _: Experience) -> AnyPublisher {
+ Just(()).eraseToAnyPublisher()
+ }
+}
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Advanced/ProfileAdvancedViewSnapshotTests.swift b/Horizon/HorizonSnapshotTests/Features/Account/Advanced/ProfileAdvancedViewSnapshotTests.swift
new file mode 100644
index 0000000000..ba9a570c91
--- /dev/null
+++ b/Horizon/HorizonSnapshotTests/Features/Account/Advanced/ProfileAdvancedViewSnapshotTests.swift
@@ -0,0 +1,112 @@
+//
+// This file is part of Canvas.
+// Copyright (C) 2024-present Instructure, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+//
+
+import Combine
+import Core
+import CoreData
+@testable import Horizon
+import HorizonUI
+import SnapshotTesting
+import SwiftUI
+import TestsFoundation
+import XCTest
+
+class ProfileAdvancedViewSnapshotTests: HorizonSnapshotTestCase {
+ func testProfileAdvancedViewDefault() {
+ let viewModel = createMockProfileAdvancedViewModel(timeZone: "America/Denver")
+ let view = ProfileAdvancedView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "profile-advanced-default"
+ )
+ }
+
+ func testProfileAdvancedViewLoading() {
+ let viewModel = createMockProfileAdvancedViewModel(timeZone: "America/Denver")
+ viewModel.isLoading = true
+ let view = ProfileAdvancedView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "profile-advanced-loading"
+ )
+ }
+
+ private func createMockProfileAdvancedViewModel(timeZone: String) -> ProfileAdvancedViewModel {
+ let mockGetUserInteractor = MockAdvancedGetUserInteractor(timeZone: timeZone, context: databaseClient)
+ let mockUpdateInteractor = MockAdvancedUpdateUserProfileInteractor(context: databaseClient)
+
+ return ProfileAdvancedViewModel(
+ getUserInteractor: mockGetUserInteractor,
+ updateUserProfileInteractor: mockUpdateInteractor
+ )
+ }
+}
+
+private class MockAdvancedGetUserInteractor: GetUserInteractor {
+ let timeZone: String
+ let context: NSManagedObjectContext
+
+ init(timeZone: String, context: NSManagedObjectContext) {
+ self.timeZone = timeZone
+ self.context = context
+ }
+
+ func getUser() -> AnyPublisher {
+ let user = UserProfile(context: context)
+ user.id = "1"
+ user.name = "Test User"
+ user.shortName = "Test"
+ user.email = "test@example.com"
+ user.defaultTimeZone = timeZone
+
+ return Just(user)
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+
+ func canUpdateName() -> AnyPublisher {
+ Just(true)
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+}
+
+private class MockAdvancedUpdateUserProfileInteractor: UpdateUserProfileInteractor {
+ let context: NSManagedObjectContext
+
+ init(context: NSManagedObjectContext) {
+ self.context = context
+ }
+
+ func set(name _: String, shortName _: String) -> AnyPublisher {
+ fatalError("Not implemented")
+ }
+
+ func set(timeZone: String) -> AnyPublisher {
+ let user = UserProfile(context: context)
+ user.id = "1"
+ user.name = "Test User"
+ user.defaultTimeZone = timeZone
+
+ return Just(user)
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+}
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewDefault.profile-advanced-default-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewDefault.profile-advanced-default-iPhone13.png
new file mode 100644
index 0000000000..9ad7035f2d
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewDefault.profile-advanced-default-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewDefault.profile-advanced-default-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewDefault.profile-advanced-default-iPhoneSE.png
new file mode 100644
index 0000000000..416cd3a4ad
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewDefault.profile-advanced-default-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewLoading.profile-advanced-loading-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewLoading.profile-advanced-loading-iPhone13.png
new file mode 100644
index 0000000000..b4fc40af02
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewLoading.profile-advanced-loading-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewLoading.profile-advanced-loading-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewLoading.profile-advanced-loading-iPhoneSE.png
new file mode 100644
index 0000000000..239db12b3f
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Advanced/__Snapshots__/testProfileAdvancedViewLoading.profile-advanced-loading-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Notifications/NotificationSettingsViewSnapshotTests.swift b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/NotificationSettingsViewSnapshotTests.swift
new file mode 100644
index 0000000000..b5dffee5a8
--- /dev/null
+++ b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/NotificationSettingsViewSnapshotTests.swift
@@ -0,0 +1,179 @@
+//
+// This file is part of Canvas.
+// Copyright (C) 2024-present Instructure, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+//
+
+import Combine
+import CombineSchedulers
+import Core
+import CoreData
+@testable import Horizon
+import HorizonUI
+import SnapshotTesting
+import SwiftUI
+import TestsFoundation
+import XCTest
+
+class NotificationSettingsViewSnapshotTests: HorizonSnapshotTestCase {
+ func testNotificationSettingsViewDefault() {
+ let viewModel = createMockNotificationSettingsViewModel(
+ isOSNotificationEnabled: true,
+ isPushConfigured: true
+ )
+ viewModel.viewState = .data
+ let view = NotificationSettingsView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "notification-settings-default"
+ )
+ }
+
+ func testNotificationSettingsViewLoading() {
+ let viewModel = createMockNotificationSettingsViewModel(
+ isOSNotificationEnabled: true,
+ isPushConfigured: true
+ )
+ viewModel.viewState = .loading
+ let view = NotificationSettingsView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "notification-settings-loading"
+ )
+ }
+
+ func testNotificationSettingsViewPushDisabled() {
+ let viewModel = createMockNotificationSettingsViewModel(
+ isOSNotificationEnabled: false,
+ isPushConfigured: true
+ )
+ viewModel.viewState = .data
+ let view = NotificationSettingsView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "notification-settings-push-disabled"
+ )
+ }
+
+ func testNotificationSettingsViewNoPushConfigured() {
+ let viewModel = createMockNotificationSettingsViewModel(
+ isOSNotificationEnabled: true,
+ isPushConfigured: false
+ )
+ viewModel.viewState = .data
+ let view = NotificationSettingsView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "notification-settings-no-push-configured"
+ )
+ }
+
+ private func createMockNotificationSettingsViewModel(
+ isOSNotificationEnabled: Bool,
+ isPushConfigured: Bool
+ ) -> NotificationSettingsViewModel {
+ let mockInteractor = MockNotificationSettingsInteractor(isPushConfigured: isPushConfigured, context: databaseClient)
+ let mockRouter = MockRouter()
+
+ let viewModel = NotificationSettingsViewModel(
+ notificationSettingsInteractor: mockInteractor,
+ router: mockRouter,
+ scheduler: .immediate
+ )
+ viewModel.isOSNotificationEnabled = isOSNotificationEnabled
+
+ return viewModel
+ }
+}
+
+private class MockNotificationSettingsInteractor: NotificationSettingsInteractor {
+ let isPushConfigured: Bool
+ let context: NSManagedObjectContext
+
+ init(isPushConfigured: Bool, context: NSManagedObjectContext) {
+ self.isPushConfigured = isPushConfigured
+ self.context = context
+ }
+
+ func getNotificationPreferences() -> AnyPublisher<[NotificationPreference], Error> {
+ var preferences: [NotificationPreference] = []
+
+ let emailCategories: [NotificationPreference.AssociatedCategories] = [.announcement, .due_date, .grading]
+ let pushCategories: [NotificationPreference.AssociatedCategories] = isPushConfigured ? [.announcement, .due_date, .grading] : []
+
+ for category in emailCategories {
+ if let pref = NotificationPreference.make(context: context, category: category, frequency: .immediate, type: .email) {
+ preferences.append(pref)
+ }
+ }
+
+ for category in pushCategories {
+ if let pref = NotificationPreference.make(context: context, category: category, frequency: .immediate, type: .push) {
+ preferences.append(pref)
+ }
+ }
+
+ return Just(preferences)
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+
+ func isOSNotificationEnabled() -> AnyPublisher {
+ Just(true).eraseToAnyPublisher()
+ }
+
+ func updateNotificationPreferences(
+ type _: NotificationChannel.ChannelType,
+ visibleCategory _: NotificationPreference.VisibleCategories,
+ currentPreferences _: [NotificationPreference],
+ isOn _: Bool
+ ) -> AnyPublisher {
+ Just(())
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+}
+
+private class MockRouter: Router {
+ init() {
+ super.init(routes: [])
+ }
+}
+
+extension NotificationPreference {
+ fileprivate static func make(
+ context: NSManagedObjectContext,
+ category: AssociatedCategories,
+ frequency: Frequency,
+ type: NotificationChannel.ChannelType
+ ) -> NotificationPreference? {
+ guard let notificationCategory = NSEntityDescription.insertNewObject(
+ forEntityName: "NotificationCategory",
+ into: context
+ ) as? NotificationCategory else {
+ return nil
+ }
+ notificationCategory.category = category.rawValue
+ notificationCategory.channelID = "channel-1"
+ notificationCategory.frequency = frequency == .immediate ? .immediately : .never
+ notificationCategory.notifications = ["notif-1"]
+
+ return NotificationPreference(from: notificationCategory, type: type)
+ }
+}
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewDefault.notification-settings-default-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewDefault.notification-settings-default-iPhone13.png
new file mode 100644
index 0000000000..29d1cca7b8
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewDefault.notification-settings-default-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewDefault.notification-settings-default-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewDefault.notification-settings-default-iPhoneSE.png
new file mode 100644
index 0000000000..7b5e892f9e
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewDefault.notification-settings-default-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewLoading.notification-settings-loading-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewLoading.notification-settings-loading-iPhone13.png
new file mode 100644
index 0000000000..e3103bad85
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewLoading.notification-settings-loading-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewLoading.notification-settings-loading-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewLoading.notification-settings-loading-iPhoneSE.png
new file mode 100644
index 0000000000..738e3e97c7
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewLoading.notification-settings-loading-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewNoPushConfigured.notification-settings-no-push-configured-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewNoPushConfigured.notification-settings-no-push-configured-iPhone13.png
new file mode 100644
index 0000000000..aef96ce1a6
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewNoPushConfigured.notification-settings-no-push-configured-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewNoPushConfigured.notification-settings-no-push-configured-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewNoPushConfigured.notification-settings-no-push-configured-iPhoneSE.png
new file mode 100644
index 0000000000..f76cef62a1
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewNoPushConfigured.notification-settings-no-push-configured-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewPushDisabled.notification-settings-push-disabled-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewPushDisabled.notification-settings-push-disabled-iPhone13.png
new file mode 100644
index 0000000000..8773acc7b9
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewPushDisabled.notification-settings-push-disabled-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewPushDisabled.notification-settings-push-disabled-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewPushDisabled.notification-settings-push-disabled-iPhoneSE.png
new file mode 100644
index 0000000000..6d2aa1da60
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Notifications/__Snapshots__/testNotificationSettingsViewPushDisabled.notification-settings-push-disabled-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Profile/ProfileViewSnapshotTests.swift b/Horizon/HorizonSnapshotTests/Features/Account/Profile/ProfileViewSnapshotTests.swift
new file mode 100644
index 0000000000..52db871541
--- /dev/null
+++ b/Horizon/HorizonSnapshotTests/Features/Account/Profile/ProfileViewSnapshotTests.swift
@@ -0,0 +1,163 @@
+//
+// This file is part of Canvas.
+// Copyright (C) 2024-present Instructure, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+//
+
+import Combine
+import Core
+import CoreData
+@testable import Horizon
+import HorizonUI
+import SnapshotTesting
+import SwiftUI
+import TestsFoundation
+import XCTest
+
+class ProfileViewSnapshotTests: HorizonSnapshotTestCase {
+ func testProfileViewDefault() {
+ let viewModel = createMockProfileViewModel(
+ name: "John Doe",
+ displayName: "Johnny",
+ email: "john.doe@example.com",
+ canUpdateName: true
+ )
+ let view = ProfileView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "profile-default"
+ )
+ }
+
+ func testProfileViewReadOnly() {
+ let viewModel = createMockProfileViewModel(
+ name: "Jane Smith",
+ displayName: "Jane",
+ email: "jane.smith@example.com",
+ canUpdateName: false
+ )
+ let view = ProfileView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "profile-readonly"
+ )
+ }
+
+ func testProfileViewWithErrors() {
+ let viewModel = createMockProfileViewModel(
+ name: "",
+ displayName: "",
+ email: "test@example.com",
+ canUpdateName: true
+ )
+ let view = ProfileView(viewModel: viewModel)
+
+ assertSnapshot(
+ of: view,
+ named: "profile-errors"
+ )
+ }
+
+ private func createMockProfileViewModel(
+ name: String,
+ displayName: String,
+ email: String,
+ canUpdateName: Bool
+ ) -> ProfileViewModel {
+ let mockGetUserInteractor = MockGetUserInteractor(
+ name: name,
+ displayName: displayName,
+ email: email,
+ canUpdateName: canUpdateName,
+ context: databaseClient
+ )
+ let mockUpdateInteractor = MockUpdateUserProfileInteractor(context: databaseClient)
+
+ return ProfileViewModel(
+ getUserInteractor: mockGetUserInteractor,
+ updateUserProfileInteractor: mockUpdateInteractor
+ )
+ }
+}
+
+private class MockGetUserInteractor: GetUserInteractor {
+ let name: String
+ let displayName: String
+ let email: String
+ let canUpdate: Bool
+ let context: NSManagedObjectContext
+
+ init(name: String, displayName: String, email: String, canUpdateName: Bool, context: NSManagedObjectContext) {
+ self.name = name
+ self.displayName = displayName
+ self.email = email
+ canUpdate = canUpdateName
+ self.context = context
+ }
+
+ func getUser() -> AnyPublisher {
+ let user = UserProfile(context: context)
+ user.id = "1"
+ user.name = name
+ user.shortName = displayName
+ user.pronouns = nil
+ user.avatarURL = URL(string: "https://example.com/avatar.jpg")
+ user.email = email
+ user.locale = "en"
+
+ return Just(user)
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+
+ func canUpdateName() -> AnyPublisher {
+ Just(canUpdate)
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+}
+
+private class MockUpdateUserProfileInteractor: UpdateUserProfileInteractor {
+ let context: NSManagedObjectContext
+
+ init(context: NSManagedObjectContext) {
+ self.context = context
+ }
+
+ func set(name: String, shortName: String) -> AnyPublisher {
+ let user = UserProfile(context: context)
+ user.id = "1"
+ user.name = name
+ user.shortName = shortName
+ user.email = "test@example.com"
+
+ return Just(user)
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+
+ func set(timeZone: String) -> AnyPublisher {
+ let user = UserProfile(context: context)
+ user.id = "1"
+ user.name = "Test User"
+ user.defaultTimeZone = timeZone
+
+ return Just(user)
+ .setFailureType(to: Error.self)
+ .eraseToAnyPublisher()
+ }
+}
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewDefault.profile-default-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewDefault.profile-default-iPhone13.png
new file mode 100644
index 0000000000..959448df1c
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewDefault.profile-default-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewDefault.profile-default-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewDefault.profile-default-iPhoneSE.png
new file mode 100644
index 0000000000..fe11188aad
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewDefault.profile-default-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewReadOnly.profile-readonly-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewReadOnly.profile-readonly-iPhone13.png
new file mode 100644
index 0000000000..840b7e2a05
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewReadOnly.profile-readonly-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewReadOnly.profile-readonly-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewReadOnly.profile-readonly-iPhoneSE.png
new file mode 100644
index 0000000000..829d0f32a6
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewReadOnly.profile-readonly-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewWithErrors.profile-errors-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewWithErrors.profile-errors-iPhone13.png
new file mode 100644
index 0000000000..a12b9ca0d2
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewWithErrors.profile-errors-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewWithErrors.profile-errors-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewWithErrors.profile-errors-iPhoneSE.png
new file mode 100644
index 0000000000..2e1fa4d2ef
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/Profile/__Snapshots__/testProfileViewWithErrors.profile-errors-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewAccessibility.account-accessibility-accLarge.png b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewAccessibility.account-accessibility-accLarge.png
new file mode 100644
index 0000000000..342616a000
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewAccessibility.account-accessibility-accLarge.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewAccessibility.account-accessibility-large.png b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewAccessibility.account-accessibility-large.png
new file mode 100644
index 0000000000..84dae652d5
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewAccessibility.account-accessibility-large.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewAccessibility.account-accessibility-xxLarge.png b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewAccessibility.account-accessibility-xxLarge.png
new file mode 100644
index 0000000000..a4a71d485d
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewAccessibility.account-accessibility-xxLarge.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewDefault.account-default-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewDefault.account-default-iPhone13.png
new file mode 100644
index 0000000000..d975be26bd
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewDefault.account-default-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewDefault.account-default-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewDefault.account-default-iPhoneSE.png
new file mode 100644
index 0000000000..c740cb7328
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewDefault.account-default-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewLoading.account-loading-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewLoading.account-loading-iPhone13.png
new file mode 100644
index 0000000000..728fec0aac
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewLoading.account-loading-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewLoading.account-loading-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewLoading.account-loading-iPhoneSE.png
new file mode 100644
index 0000000000..2cbcefbfed
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewLoading.account-loading-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewNoExperienceSwitch.account-no-experience-switch-iPhone13.png b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewNoExperienceSwitch.account-no-experience-switch-iPhone13.png
new file mode 100644
index 0000000000..b72b56c9dd
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewNoExperienceSwitch.account-no-experience-switch-iPhone13.png differ
diff --git a/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewNoExperienceSwitch.account-no-experience-switch-iPhoneSE.png b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewNoExperienceSwitch.account-no-experience-switch-iPhoneSE.png
new file mode 100644
index 0000000000..571c00e255
Binary files /dev/null and b/Horizon/HorizonSnapshotTests/Features/Account/__Snapshots__/testAccountViewNoExperienceSwitch.account-no-experience-switch-iPhoneSE.png differ
diff --git a/Horizon/HorizonSnapshotTests/Helpers/SnapshotTestCase.swift b/Horizon/HorizonSnapshotTests/Helpers/SnapshotTestCase.swift
new file mode 100644
index 0000000000..25d24d83aa
--- /dev/null
+++ b/Horizon/HorizonSnapshotTests/Helpers/SnapshotTestCase.swift
@@ -0,0 +1,220 @@
+//
+// This file is part of Canvas.
+// Copyright (C) 2024-present Instructure, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+//
+
+@testable import Core
+import CoreData
+@testable import Horizon
+import HorizonUI
+import SnapshotTesting
+import SwiftUI
+import TestsFoundation
+import XCTest
+
+class HorizonSnapshotTestCase: XCTestCase {
+ struct Device {
+ let name: String
+ let config: ViewImageConfig
+
+ static let iPhone13 = Device(
+ name: "iPhone13",
+ config: .iPhone13
+ )
+
+ static let iPhoneSE = Device(
+ name: "iPhoneSE",
+ config: .iPhoneSe
+ )
+ }
+
+ static let defaultDevices = [
+ Device.iPhone13,
+ Device.iPhoneSE
+ ]
+
+ static let allDevices = [
+ Device.iPhone13,
+ Device.iPhoneSE
+ ]
+
+ func snapshotDirectory(file: StaticString = #file) -> String? {
+ let testFilePath = "\(file)"
+ if let featureRange = testFilePath.range(of: "/Features/") {
+ if let lastSlash = testFilePath.range(of: "/", options: .backwards, range: featureRange.upperBound ..< testFilePath.endIndex) {
+ let basePath = testFilePath[..(
+ of view: Content,
+ named name: String? = nil,
+ devices: [Device] = HorizonSnapshotTestCase.defaultDevices,
+ file: StaticString = #file,
+ testName: String = #function,
+ line: UInt = #line
+ ) {
+ for device in devices {
+ let snapshotName = name.map { "\($0)-\(device.name)" } ?? device.name
+
+ let failure = verifySnapshot(
+ of: view,
+ as: .image(layout: .device(config: device.config)),
+ named: snapshotName,
+ record: nil,
+ snapshotDirectory: snapshotDirectory(file: file),
+ file: file,
+ testName: testName,
+ line: line
+ )
+
+ if let failureMessage = failure {
+ XCTFail(failureMessage, file: file, line: line)
+ }
+ }
+ }
+
+ func assertSnapshot(
+ of viewController: UIViewController,
+ named name: String? = nil,
+ devices: [Device] = HorizonSnapshotTestCase.defaultDevices,
+ file: StaticString = #file,
+ testName: String = #function,
+ line: UInt = #line
+ ) {
+ for device in devices {
+ let snapshotName = name.map { "\($0)-\(device.name)" } ?? device.name
+
+ let failure = verifySnapshot(
+ of: viewController,
+ as: .image(on: device.config),
+ named: snapshotName,
+ record: nil,
+ snapshotDirectory: snapshotDirectory(file: file),
+ file: file,
+ testName: testName,
+ line: line
+ )
+
+ if let failureMessage = failure {
+ XCTFail(failureMessage, file: file, line: line)
+ }
+ }
+ }
+
+ func assertAccessibilitySnapshot(
+ of view: Content,
+ named name: String? = nil,
+ sizes: [UIContentSizeCategory] = [.large, .extraExtraLarge, .accessibilityLarge],
+ file: StaticString = #file,
+ testName: String = #function,
+ line: UInt = #line
+ ) {
+ for size in sizes {
+ let sizeString = accessibilitySizeString(for: size)
+ let snapshotName = name.map { "\($0)-\(sizeString)" } ?? sizeString
+
+ var config = Device.iPhone13.config
+ config.traits = UITraitCollection(preferredContentSizeCategory: size)
+
+ let failure = verifySnapshot(
+ of: view,
+ as: .image(layout: .device(config: config)),
+ named: snapshotName,
+ record: nil,
+ snapshotDirectory: snapshotDirectory(file: file),
+ file: file,
+ testName: testName,
+ line: line
+ )
+
+ if let failureMessage = failure {
+ XCTFail(failureMessage, file: file, line: line)
+ }
+ }
+ }
+
+ private func accessibilitySizeString(for category: UIContentSizeCategory) -> String {
+ switch category {
+ case .extraSmall: return "xSmall"
+ case .small: return "small"
+ case .medium: return "medium"
+ case .large: return "large"
+ case .extraLarge: return "xLarge"
+ case .extraExtraLarge: return "xxLarge"
+ case .extraExtraExtraLarge: return "xxxLarge"
+ case .accessibilityMedium: return "accMedium"
+ case .accessibilityLarge: return "accLarge"
+ case .accessibilityExtraLarge: return "accXLarge"
+ case .accessibilityExtraExtraLarge: return "accXXLarge"
+ case .accessibilityExtraExtraExtraLarge: return "accXXXLarge"
+ default: return "default"
+ }
+ }
+
+ var database: NSPersistentContainer {
+ return TestsFoundation.singleSharedTestDatabase
+ }
+
+ var databaseClient: NSManagedObjectContext {
+ return database.viewContext
+ }
+
+ var api: API { environment.api }
+ var environment: TestEnvironment!
+ var queue = OperationQueue()
+ var router = TestRouter()
+ var logger = TestLogger()
+
+ let window = UIWindow()
+
+ override func setUp() {
+ super.setUp()
+ HorizonUI.registerCustomFonts()
+ OfflineModeAssembly.mock(OfflineModeInteractorMock(mockIsFeatureFlagEnabled: false))
+ Clock.reset()
+ API.resetMocks()
+ LoginSession.clearAll()
+ TestsFoundation.singleSharedTestDatabase = resetSingleSharedTestDatabase()
+ environment = TestEnvironment()
+ AppEnvironment.shared = environment
+ environment.app = .horizon
+ environment.api = API()
+ environment.database = singleSharedTestDatabase
+ environment.globalDatabase = singleSharedTestDatabase
+ environment.router = router
+ environment.logger = logger
+ environment.currentSession = LoginSession.make()
+ environment.window = window
+ window.rootViewController = UIViewController()
+ window.makeKeyAndVisible()
+ }
+
+ override func tearDown() {
+ super.tearDown()
+ LoginSession.clearAll()
+ window.rootViewController = UIViewController()
+ }
+}
diff --git a/Horizon/HorizonSnapshotTests/horizon-snapshot-tests.yml b/Horizon/HorizonSnapshotTests/horizon-snapshot-tests.yml
new file mode 100644
index 0000000000..4076717809
--- /dev/null
+++ b/Horizon/HorizonSnapshotTests/horizon-snapshot-tests.yml
@@ -0,0 +1,18 @@
+---
+targets:
+ HorizonSnapshotTests:
+ type: bundle.unit-test
+ platform: iOS
+ settings:
+ APPLICATION_EXTENSION_API_ONLY: false
+ ENABLE_TESTABILITY: true
+ GENERATE_INFOPLIST_FILE: true
+ sources:
+ - path: ./
+ excludes:
+ - ".swiftlint.yml"
+ dependencies:
+ - target: Horizon
+ - target: Core/TestsFoundation
+ - package: SnapshotTesting
+ - package: HorizonUI
\ No newline at end of file
diff --git a/Horizon/project.yml b/Horizon/project.yml
index 4c8f7a0e06..7dc6c31dcb 100644
--- a/Horizon/project.yml
+++ b/Horizon/project.yml
@@ -23,6 +23,7 @@ sources:
include:
- Horizon/horizon.yml
- HorizonUnitTests/horizon-unit-tests.yml
+ - HorizonSnapshotTests/horizon-snapshot-tests.yml
projectReferences:
Core:
path: ../Core/Core.xcodeproj
@@ -47,3 +48,6 @@ packages:
FirebaseRemoteConfigSwift:
url: https://github.com/firebase/firebase-ios-sdk.git
exactVersion: 10.23.1
+ SnapshotTesting:
+ url: https://github.com/pointfreeco/swift-snapshot-testing
+ from: 1.17.0
diff --git a/Student/Student/student.yml b/Student/Student/student.yml
index c0933907b5..b8b1162f4b 100644
--- a/Student/Student/student.yml
+++ b/Student/Student/student.yml
@@ -22,6 +22,7 @@ targets:
- package: FirebaseCrashlytics
- package: FirebaseRemoteConfig
- package: FirebaseRemoteConfigSwift
+ - package: HorizonUI
- target: Core/Core
- target: SubmitAssignment
- target: Widgets
@@ -53,6 +54,8 @@ schemes:
targets:
- Core/CoreTests
- StudentUnitTests
+ - Horizon/HorizonUnitTests
+ - Horizon/HorizonSnapshotTests
- name: Core/CoreTester
skipped: true
gatherCoverageData: true
diff --git a/Student/project-ci.yml b/Student/project-ci.yml
index a50be593a1..3e4129b62e 100644
--- a/Student/project-ci.yml
+++ b/Student/project-ci.yml
@@ -30,6 +30,11 @@ projectReferences:
Horizon:
path: ../Horizon/Horizon.xcodeproj
packages:
+ # Local
+ HorizonUI:
+ path: ../packages/HorizonUI
+
+ # Remote
Pendo:
url: https://github.com/pendo-io/pendo-mobile-sdk
exactVersion: 3.7.5
diff --git a/Student/project.yml b/Student/project.yml
index 9228afadf5..cba5e6604b 100644
--- a/Student/project.yml
+++ b/Student/project.yml
@@ -32,6 +32,11 @@ projectReferences:
Horizon:
path: ../Horizon/Horizon.xcodeproj
packages:
+ # Local
+ HorizonUI:
+ path: ../packages/HorizonUI
+
+ # Remote
Pendo:
url: https://github.com/pendo-io/pendo-mobile-sdk
exactVersion: 3.7.5
diff --git a/TestPlans/CITests.xctestplan b/TestPlans/CITests.xctestplan
index 5973968d79..758bd2fda7 100644
--- a/TestPlans/CITests.xctestplan
+++ b/TestPlans/CITests.xctestplan
@@ -84,6 +84,13 @@
"identifier" : "41BA2B211CB47478407A6101",
"name" : "ParentUnitTests"
}
+ },
+ {
+ "target" : {
+ "containerPath" : "container:Horizon\/Horizon.xcodeproj",
+ "identifier" : "20252627727AC8643AB77E40",
+ "name" : "HorizonSnapshotTests"
+ }
}
],
"version" : 1