fix: remove rlement endpoints and links from the HealthChat

* fix(healthchat): switch off analytics, crash and bug reporting endpoints

Upstream ships *.localhost placeholders in Secrets.swift and marks the file assume-unchanged; with analytics on, the Element Call widget was handed Element's own PostHog and Sentry. nil hides the analytics prompt, the Analytics and Report a problem entries until HealthChat runs its own services.

* feat(healthchat): hide the Labs entry in settings

* fix(healthchat): point Copyright and the analytics terms at healthchat.ch

* docs(healthchat): add the 26.11.1 release notes

* feat(healthchat): drop the Element help links

* feat(healthchat): switch off location sharing

* test(healthchat): skip the analytics suite without PostHog
This commit is contained in:
Sedat-Qaja
2026-10-19 14:40:20 +02:00
committed by GitHub
parent 7b07b51a10
commit 3b7ed0a48b
21 changed files with 125 additions and 61 deletions
@@ -43,7 +43,13 @@ Upstream files the fork edits — conflicts live here every time:
migration contract"), `AppHooks/UserHooks.swift` (two HealthChat hook fields), migration contract"), `AppHooks/UserHooks.swift` (two HealthChat hook fields),
`AppSettings.swift` (VoIP push properties for the NSE), `ElementCallService.swift` (PushKit `AppSettings.swift` (VoIP push properties for the NSE), `ElementCallService.swift` (PushKit
token, expiration parsing), `NSE/Sources/NotificationHandler.swift` (VoIP push to Sygnal), token, expiration parsing), `NSE/Sources/NotificationHandler.swift` (VoIP push to Sygnal),
`UserSessionFlowCoordinator` (Members tab), `SettingsScreen*` (account status section), `UserSessionFlowCoordinator` (Members tab), `SettingsScreen*` (account status section, Labs
gate), the five help URLs are `URL?` (`AppSettings`, `SessionVerificationScreen*`,
`IdentityConfirmationScreen*`, `SecureBackupScreen*`, `RoomScreenModels` + `RoomScreenFooterView`,
`SecurityAndPrivacyScreenModels`, `TimelineViewModel` alert — nil hides the "Learn more" link), `UnitTests/Sources/AnalyticsTests.swift` (suite skipped without PostHog secrets),
`Components/Secrets/Secrets.swift` (analytics/sentry/rageshake nil, hand-edited; upstream's setup
marks it `assume-unchanged`, so run `git update-index --no-assume-unchanged` on it before an
edit shows up in `git status` or a commit),
`RoomMemberDetailsScreen*` + `UserProfileScreen*` (extended profile section), the five fork-gated `RoomMemberDetailsScreen*` + `UserProfileScreen*` (extended profile section), the five fork-gated
CI workflows, `.gitignore`, `project.pbxproj` (never resolve by hand — `xcodegen`). CI workflows, `.gitignore`, `project.pbxproj` (never resolve by hand — `xcodegen`).
+5 -5
View File
@@ -1,9 +1,9 @@
nonisolated enum Secrets { nonisolated enum Secrets {
static let sentryDSN: String? = "https://username@sentry.localhost/project_id" static let sentryDSN: String? = nil
static let sentryRustDSN: String? = "https://username@sentry.localhost/project_id" static let sentryRustDSN: String? = nil
static let postHogHost: String? = "https://posthog.localhost" static let postHogHost: String? = nil
static let postHogAPIKey: String? = "your_key" static let postHogAPIKey: String? = nil
static let rageshakeURL: String? = "https://rageshake.localhost/submit" static let rageshakeURL: String? = nil
static let mapLibreAPIKey: String? = "your_key" static let mapLibreAPIKey: String? = "your_key"
} }
@@ -12,7 +12,8 @@ import Foundation
/// - `accountProviders` / `allowOtherAccountProviders` lock every user to `healthchat.ch` (the server name; /// - `accountProviders` / `allowOtherAccountProviders` lock every user to `healthchat.ch` (the server name;
/// `.well-known` delegates to `matrix.healthchat.ch`) and hide the change-server / create-account UI. /// `.well-known` delegates to `matrix.healthchat.ch`) and hide the change-server / create-account UI.
/// - `oAuthRedirectURL` uses the app's own URL scheme so the OIDC/MAS callback needs no associated domain. /// - `oAuthRedirectURL` uses the app's own URL scheme so the OIDC/MAS callback needs no associated domain.
/// - Legacy `element.io` help/encryption/copyright URLs are passed through unchanged for now. /// - Legal links point at healthchat.ch. The encryption/verification/backup help links still go to
/// Element's help pages because no HealthChat equivalents exist yet.
struct HealthChatAppSettingsHook: AppSettingsHookProtocol { struct HealthChatAppSettingsHook: AppSettingsHookProtocol {
func configure(_ appSettings: AppSettings) -> AppSettings { func configure(_ appSettings: AppSettings) -> AppSettings {
appSettings.override(accountProviders: [.generic("healthchat.ch")], appSettings.override(accountProviders: [.generic("healthchat.ch")],
@@ -24,19 +25,25 @@ struct HealthChatAppSettingsHook: AppSettingsHookProtocol {
oAuthClientURIPath: nil, oAuthClientURIPath: nil,
websiteURL: "https://healthchat.ch", websiteURL: "https://healthchat.ch",
logoURL: "https://app.healthchat.ch/themes/element/img/logos/element-logo.png", logoURL: "https://app.healthchat.ch/themes/element/img/logos/element-logo.png",
copyrightURL: appSettings.copyrightURL, copyrightURL: "https://www.healthchat.ch/impressum",
acceptableUseURL: "https://www.healthchat.ch/datenschutz", acceptableUseURL: "https://www.healthchat.ch/datenschutz",
privacyURL: "https://www.healthchat.ch/datenschutz", privacyURL: "https://www.healthchat.ch/datenschutz",
encryptionURL: appSettings.encryptionURL, encryptionURL: nil,
deviceVerificationURL: appSettings.deviceVerificationURL, deviceVerificationURL: nil,
chatBackupDetailsURL: appSettings.chatBackupDetailsURL, chatBackupDetailsURL: nil,
identityPinningViolationDetailsURL: appSettings.identityPinningViolationDetailsURL, identityPinningViolationDetailsURL: nil,
historySharingDetailsURL: appSettings.historySharingDetailsURL, historySharingDetailsURL: nil,
elementWebHosts: ["app.healthchat.ch"], elementWebHosts: ["app.healthchat.ch"],
accountProvisioningHost: appSettings.accountProvisioningHost, accountProvisioningHost: appSettings.accountProvisioningHost,
bugReportApplicationID: appSettings.bugReportApplicationID, bugReportApplicationID: "healthchat-ios",
analyticsTermsURL: appSettings.analyticsTermsURL, analyticsTermsURL: "https://www.healthchat.ch/datenschutz",
mapTilerConfiguration: AppSettings.bundledMapTilerConfiguration) mapTilerConfiguration: Self.mapTilerConfiguration)
return appSettings return appSettings
} }
/// No MapTiler account yet: a nil key hides location sharing and shows received locations as a placeholder.
private static let mapTilerConfiguration = MapTilerConfiguration(baseURL: AppSettings.bundledMapTilerConfiguration.baseURL,
apiKey: nil,
lightStyleID: AppSettings.bundledMapTilerConfiguration.lightStyleID,
darkStyleID: AppSettings.bundledMapTilerConfiguration.darkStyleID)
} }
@@ -28,5 +28,10 @@ final class HealthChatSettingsScreenHook: SettingsScreenHookProtocol {
func refreshAccountStatus() { func refreshAccountStatus() {
statusLoader.load() statusLoader.load()
} }
// Upstream's experiments are not supported by the HealthChat server setup and never reviewed for it.
nonisolated var showsLabs: Bool {
false
}
} }
#endif #endif
@@ -15,6 +15,9 @@ nonisolated protocol SettingsScreenHookProtocol: Sendable {
/// Called whenever the settings screen appears, so the section reflects what the user just did. /// Called whenever the settings screen appears, so the section reflects what the user just did.
@MainActor func refreshAccountStatus() @MainActor func refreshAccountStatus()
/// Whether the Labs entry (experimental upstream features) is offered at all.
var showsLabs: Bool { get }
} }
struct DefaultSettingsScreenHook: SettingsScreenHookProtocol { struct DefaultSettingsScreenHook: SettingsScreenHookProtocol {
@@ -23,4 +26,8 @@ struct DefaultSettingsScreenHook: SettingsScreenHookProtocol {
} }
func refreshAccountStatus() { } func refreshAccountStatus() { }
var showsLabs: Bool {
true
}
} }
@@ -65,11 +65,11 @@ final nonisolated class AppSettings: @unchecked Sendable {
copyrightURL: URL, copyrightURL: URL,
acceptableUseURL: URL, acceptableUseURL: URL,
privacyURL: URL, privacyURL: URL,
encryptionURL: URL, encryptionURL: URL?,
deviceVerificationURL: URL, deviceVerificationURL: URL?,
chatBackupDetailsURL: URL, chatBackupDetailsURL: URL?,
identityPinningViolationDetailsURL: URL, identityPinningViolationDetailsURL: URL?,
historySharingDetailsURL: URL, historySharingDetailsURL: URL?,
elementWebHosts: [String], elementWebHosts: [String],
accountProvisioningHost: String, accountProvisioningHost: String,
bugReportApplicationID: String, bugReportApplicationID: String,
@@ -145,15 +145,15 @@ final nonisolated class AppSettings: @unchecked Sendable {
/// A URL that contains the app's Privacy Policy. /// A URL that contains the app's Privacy Policy.
private(set) var privacyURL: URL = "https://element.io/privacy" private(set) var privacyURL: URL = "https://element.io/privacy"
/// A URL where users can go read more about encryption in general. /// A URL where users can go read more about encryption in general.
private(set) var encryptionURL: URL = "https://element.io/help#encryption" private(set) var encryptionURL: URL? = "https://element.io/help#encryption"
/// A URL where users can go read more about device verification.. /// A URL where users can go read more about device verification..
private(set) var deviceVerificationURL: URL = "https://element.io/help#encryption-device-verification" private(set) var deviceVerificationURL: URL? = "https://element.io/help#encryption-device-verification"
/// A URL where users can go read more about the chat backup. /// A URL where users can go read more about the chat backup.
private(set) var chatBackupDetailsURL: URL = "https://element.io/help#encryption5" private(set) var chatBackupDetailsURL: URL? = "https://element.io/help#encryption5"
/// A URL where users can go read more about identity pinning violations /// A URL where users can go read more about identity pinning violations
private(set) var identityPinningViolationDetailsURL: URL = "https://element.io/help#encryption18" private(set) var identityPinningViolationDetailsURL: URL? = "https://element.io/help#encryption18"
/// A URL describing how history sharing works /// A URL describing how history sharing works
private(set) var historySharingDetailsURL: URL = "https://element.io/en/help#e2ee-history-sharing" private(set) var historySharingDetailsURL: URL? = "https://element.io/en/help#e2ee-history-sharing"
/// Any domains that Element web may be hosted on - used for handling links. /// Any domains that Element web may be hosted on - used for handling links.
private(set) var elementWebHosts = ["app.element.io", "staging.element.io", "develop.element.io"] private(set) var elementWebHosts = ["app.element.io", "staging.element.io", "develop.element.io"]
@@ -24,7 +24,7 @@ struct IdentityConfirmationScreenViewState: BindableState {
} }
var availableActions: [AvailableActions]? var availableActions: [AvailableActions]?
let learnMoreURL: URL let learnMoreURL: URL?
var bindings = IdentityConfirmationScreenBindings() var bindings = IdentityConfirmationScreenBindings()
} }
@@ -53,11 +53,13 @@ struct IdentityConfirmationScreen: View {
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.foregroundColor(.compound.textSecondary) .foregroundColor(.compound.textSecondary)
Button(L10n.actionLearnMore) { if let learnMoreURL = context.viewState.learnMoreURL {
UIApplication.shared.open(context.viewState.learnMoreURL) Button(L10n.actionLearnMore) {
UIApplication.shared.open(learnMoreURL)
}
.buttonStyle(.compound(.tertiary, size: .small))
.padding(.top, 16)
} }
.buttonStyle(.compound(.tertiary, size: .small))
.padding(.top, 16)
} }
} }
@@ -26,7 +26,7 @@ enum SessionVerificationScreenViewAction {
struct SessionVerificationScreenViewState: BindableState { struct SessionVerificationScreenViewState: BindableState {
let flow: SessionVerificationScreenFlow let flow: SessionVerificationScreenFlow
let learnMoreURL: URL let learnMoreURL: URL?
var verificationState: SessionVerificationScreenStateMachine.State var verificationState: SessionVerificationScreenStateMachine.State
@@ -92,10 +92,12 @@ struct SessionVerificationScreen: View {
isUserVerification: true, isUserVerification: true,
mediaProvider: context.mediaProvider) mediaProvider: context.mediaProvider)
case .userInitiator: case .userInitiator:
Button(L10n.actionLearnMore) { if let learnMoreURL = context.viewState.learnMoreURL {
UIApplication.shared.open(context.viewState.learnMoreURL) Button(L10n.actionLearnMore) {
UIApplication.shared.open(learnMoreURL)
}
.buttonStyle(.compound(.tertiary, size: .small))
} }
.buttonStyle(.compound(.tertiary, size: .small))
default: default:
EmptyView() EmptyView()
} }
@@ -118,8 +118,8 @@ enum RoomScreenFooterViewAction {
} }
enum RoomScreenFooterViewDetails { enum RoomScreenFooterViewDetails {
case pinViolation(member: RoomMemberProxyProtocol, learnMoreURL: URL) case pinViolation(member: RoomMemberProxyProtocol, learnMoreURL: URL?)
case verificationViolation(member: RoomMemberProxyProtocol, learnMoreURL: URL) case verificationViolation(member: RoomMemberProxyProtocol, learnMoreURL: URL?)
} }
enum PinnedEventsBannerState: Equatable { enum PinnedEventsBannerState: Equatable {
@@ -56,7 +56,7 @@ struct RoomScreenFooterView: View {
} }
private func pinViolation(member: RoomMemberProxyProtocol, private func pinViolation(member: RoomMemberProxyProtocol,
learnMoreURL: URL) -> some View { learnMoreURL: URL?) -> some View {
VStack(spacing: 16) { VStack(spacing: 16) {
HStack(spacing: 16) { HStack(spacing: 16) {
LoadableAvatarImage(url: member.avatarURL, LoadableAvatarImage(url: member.avatarURL,
@@ -86,7 +86,7 @@ struct RoomScreenFooterView: View {
} }
private func verificationViolation(member: RoomMemberProxyProtocol, private func verificationViolation(member: RoomMemberProxyProtocol,
learnMoreURL: URL) -> some View { learnMoreURL: URL?) -> some View {
VStack(spacing: 16) { VStack(spacing: 16) {
HStack(spacing: 16) { HStack(spacing: 16) {
LoadableAvatarImage(url: member.avatarURL, LoadableAvatarImage(url: member.avatarURL,
@@ -115,7 +115,7 @@ struct RoomScreenFooterView: View {
.padding(.bottom, 8) .padding(.bottom, 8)
} }
private func pinViolationDescriptionWithLearnMoreLink(displayName: String?, userID: String, url: URL) -> AttributedString { private func pinViolationDescriptionWithLearnMoreLink(displayName: String?, userID: String, url: URL?) -> AttributedString {
let userIDPlaceholder = "{mxid}" let userIDPlaceholder = "{mxid}"
let linkPlaceholder = "{link}" let linkPlaceholder = "{link}"
let displayName = displayName ?? fallbackDisplayName(userID) let displayName = displayName ?? fallbackDisplayName(userID)
@@ -125,14 +125,14 @@ struct RoomScreenFooterView: View {
userIDString.bold() userIDString.bold()
description.replace(userIDPlaceholder, with: userIDString) description.replace(userIDPlaceholder, with: userIDString)
var linkString = AttributedString(L10n.actionLearnMore) var linkString = AttributedString(url == nil ? "" : L10n.actionLearnMore)
linkString.link = url linkString.link = url
linkString.bold() linkString.bold()
description.replace(linkPlaceholder, with: linkString) description.replace(linkPlaceholder, with: linkString)
return description return description
} }
private func verificationViolationDescriptionWithLearnMoreLink(displayName: String?, userID: String, url: URL) -> AttributedString { private func verificationViolationDescriptionWithLearnMoreLink(displayName: String?, userID: String, url: URL?) -> AttributedString {
let userIDPlaceholder = "{mxid}" let userIDPlaceholder = "{mxid}"
let linkPlaceholder = "{link}" let linkPlaceholder = "{link}"
let displayName = displayName ?? fallbackDisplayName(userID) let displayName = displayName ?? fallbackDisplayName(userID)
@@ -142,7 +142,7 @@ struct RoomScreenFooterView: View {
userIDString.bold() userIDString.bold()
description.replace(userIDPlaceholder, with: userIDString) description.replace(userIDPlaceholder, with: userIDString)
var linkString = AttributedString(L10n.actionLearnMore) var linkString = AttributedString(url == nil ? "" : L10n.actionLearnMore)
linkString.link = url linkString.link = url
linkString.bold() linkString.bold()
description.replace(linkPlaceholder, with: linkString) description.replace(linkPlaceholder, with: linkString)
@@ -14,7 +14,7 @@ enum SecureBackupScreenViewModelAction {
} }
struct SecureBackupScreenViewState: BindableState { struct SecureBackupScreenViewState: BindableState {
let chatBackupDetailsURL: URL let chatBackupDetailsURL: URL?
var recoveryState = SecureBackupRecoveryState.unknown var recoveryState = SecureBackupRecoveryState.unknown
var keyBackupState = SecureBackupKeyBackupState.unknown var keyBackupState = SecureBackupKeyBackupState.unknown
var bindings: SecureBackupScreenViewStateBindings var bindings: SecureBackupScreenViewStateBindings
@@ -22,7 +22,7 @@ class SecureBackupScreenViewModel: SecureBackupScreenViewModelType, SecureBackup
init(secureBackupController: SecureBackupControllerProtocol, init(secureBackupController: SecureBackupControllerProtocol,
userIndicatorController: UserIndicatorControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol,
chatBackupDetailsURL: URL) { chatBackupDetailsURL: URL?) {
self.secureBackupController = secureBackupController self.secureBackupController = secureBackupController
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
@@ -64,10 +64,15 @@ struct SecureBackupScreen: View {
} }
private var keyBackupDescriptionWithLearnMoreLink: AttributedString { private var keyBackupDescriptionWithLearnMoreLink: AttributedString {
guard let chatBackupDetailsURL = context.viewState.chatBackupDetailsURL else {
// The link is its own sentence, so the full stop goes with it.
return AttributedString(L10n.screenChatBackupKeyBackupDescription("").replacing(/\s*\.\s*$/, with: ""))
}
let linkPlaceholder = "{link}" let linkPlaceholder = "{link}"
var description = AttributedString(L10n.screenChatBackupKeyBackupDescription(linkPlaceholder)) var description = AttributedString(L10n.screenChatBackupKeyBackupDescription(linkPlaceholder))
var linkString = AttributedString(L10n.actionLearnMore) var linkString = AttributedString(L10n.actionLearnMore)
linkString.link = context.viewState.chatBackupDetailsURL linkString.link = chatBackupDetailsURL
linkString.bold() linkString.bold()
description.replace(linkPlaceholder, with: linkString) description.replace(linkPlaceholder, with: linkString)
return description return description
@@ -162,7 +162,7 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
historyVisibility: SecurityAndPrivacyHistoryVisibility, historyVisibility: SecurityAndPrivacyHistoryVisibility,
isSpace: Bool, isSpace: Bool,
isKnockingEnabled: Bool, isKnockingEnabled: Bool,
historySharingDetailsURL: URL) { historySharingDetailsURL: URL?) {
self.isKnockingEnabled = isKnockingEnabled self.isKnockingEnabled = isKnockingEnabled
self.isSpace = isSpace self.isSpace = isSpace
@@ -269,7 +269,7 @@ struct SecurityAndPrivacyScreenStrings {
let accessSectionFooterString: AttributedString let accessSectionFooterString: AttributedString
let historySectionFooterString: AttributedString let historySectionFooterString: AttributedString
init(historySharingDetailsURL: URL) { init(historySharingDetailsURL: URL?) {
let linkPlaceholder = "{link}" let linkPlaceholder = "{link}"
var accessFooterString = AttributedString(L10n.screenSecurityAndPrivacyRoomAccessFooter(linkPlaceholder)) var accessFooterString = AttributedString(L10n.screenSecurityAndPrivacyRoomAccessFooter(linkPlaceholder))
@@ -280,7 +280,7 @@ struct SecurityAndPrivacyScreenStrings {
accessSectionFooterString = accessFooterString accessSectionFooterString = accessFooterString
var historyFooterString = AttributedString(L10n.screenSecurityAndPrivacyRoomHistorySectionFooter(linkPlaceholder)) var historyFooterString = AttributedString(L10n.screenSecurityAndPrivacyRoomHistorySectionFooter(linkPlaceholder))
var historyLinkString = AttributedString(L10n.actionLearnMore) var historyLinkString = AttributedString(historySharingDetailsURL == nil ? "" : L10n.actionLearnMore)
historyLinkString.link = historySharingDetailsURL historyLinkString.link = historySharingDetailsURL
historyLinkString.bold() historyLinkString.bold()
historyFooterString.replace(linkPlaceholder, with: historyLinkString) historyFooterString.replace(linkPlaceholder, with: historyLinkString)
@@ -211,11 +211,13 @@ struct SettingsScreen: View {
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.analytics) .accessibilityIdentifier(A11yIdentifiers.settingsScreen.analytics)
} }
ListRow(label: .default(title: L10n.screenAdvancedSettingsLabs, if context.viewState.settingsScreenHook.showsLabs {
icon: \.labs), ListRow(label: .default(title: L10n.screenAdvancedSettingsLabs,
kind: .navigationLink { icon: \.labs),
context.send(viewAction: .labs) kind: .navigationLink {
}) context.send(viewAction: .labs)
})
}
ListRow(label: .default(title: L10n.commonAbout, ListRow(label: .default(title: L10n.commonAbout,
icon: \.info), icon: \.info),
@@ -1143,9 +1143,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
state.bindings.alertInfo = .init(id: type, state.bindings.alertInfo = .init(id: type,
title: message, title: message,
primaryButton: .init(title: L10n.actionOk, action: nil), primaryButton: .init(title: L10n.actionOk, action: nil),
secondaryButton: .init(title: L10n.actionLearnMore) { [weak self] in secondaryButton: userSettings.app.historySharingDetailsURL.map { url in
guard let self else { return } .init(title: L10n.actionLearnMore) { [weak self] in self?.appMediator.open(url) }
appMediator.open(userSettings.app.historySharingDetailsURL)
}) })
case .inviteAgain: case .inviteAgain:
state.bindings.alertInfo = .init(id: .inviteAgain, state.bindings.alertInfo = .init(id: .inviteAgain,
+21
View File
@@ -9,6 +9,27 @@ matches `## Changes in <version>` exactly.
Upstream's `CHANGES.md` tracks element-hq's own releases and must not be edited here. Upstream's `CHANGES.md` tracks element-hq's own releases and must not be edited here.
## Changes in 26.11.1 (2026-10-19)
🙌 Improvements
- Copyright in "About" now opens the HealthChat Impressum.
- The Labs entry with Element's experimental features is no longer shown.
- Analytics and crash reporting are switched off entirely: the "Analytics" settings and the
onboarding prompt are gone, and calls no longer send usage data to Element.
- "Report a problem" is hidden until HealthChat runs its own report server.
- The "Learn more" links on identity confirmation, device verification, chat backup, identity
changes and history sharing are gone; they led to Element's documentation.
- Location sharing is switched off: the attachment menu no longer offers it, and received
locations show a placeholder instead of a map that could not load.
⚠️ Known issues
- Incoming calls are labelled as video calls on the call screen; voice calls still connect
as voice calls.
- An "Incoming call" notification is shown next to the call screen, and a few notifications
may show generic text, while Apple reviews the notification filtering entitlement.
## Changes in 26.11.0 (2026-10-19) ## Changes in 26.11.0 (2026-10-19)
✨ Features ✨ Features
+1
View File
@@ -11,6 +11,7 @@ import AnalyticsEvents
import PostHog import PostHog
import Testing import Testing
@Suite(.enabled(if: Secrets.postHogHost != nil && Secrets.postHogAPIKey != nil, "HealthChat ships without a PostHog configuration."))
@MainActor @MainActor
final class AnalyticsTests { final class AnalyticsTests {
private let appSettings: AppSettings private let appSettings: AppSettings
+11 -4
View File
@@ -104,9 +104,12 @@ final class BugReportServiceTests {
@Test @Test
@MainActor @MainActor
func configurations() async throws { func configurations() async throws {
guard case let .url(initialURL) = userSettings.app.bugReportRageshakeURL.publisher.value else { // HealthChat ships without a rageshake server (Secrets.rageshakeURL is nil), so the default
Issue.record("Unexpected initial configuration.") // configuration is `.disabled`; the transitions below are exercised from remote values instead.
return let initialURL: URL? = if case let .url(url) = userSettings.app.bugReportRageshakeURL.publisher.value {
url
} else {
nil
} }
let service = BugReportService(rageshakeURLPublisher: userSettings.app.bugReportRageshakeURL.publisher, let service = BugReportService(rageshakeURLPublisher: userSettings.app.bugReportRageshakeURL.publisher,
@@ -114,7 +117,7 @@ final class BugReportServiceTests {
sdkGitSHA: "1234", sdkGitSHA: "1234",
session: .mock, session: .mock,
appHooks: AppHooks()) appHooks: AppHooks())
#expect(service.isEnabled) #expect(service.isEnabled == (initialURL != nil))
userSettings.app.bugReportRageshakeURL.applyRemoteValue(.disabled) userSettings.app.bugReportRageshakeURL.applyRemoteValue(.disabled)
#expect(!service.isEnabled) #expect(!service.isEnabled)
@@ -137,6 +140,10 @@ final class BugReportServiceTests {
#expect(customConfigurationResponse.reportURL == "https://bugs.server.net/123") #expect(customConfigurationResponse.reportURL == "https://bugs.server.net/123")
userSettings.app.bugReportRageshakeURL.reset() userSettings.app.bugReportRageshakeURL.reset()
guard let initialURL else {
#expect(!service.isEnabled)
return
}
#expect(service.isEnabled) #expect(service.isEnabled)
let defaultConfigurationResponse = try await service.submitBugReport(bugReport, progressListener: progressSubject).get() let defaultConfigurationResponse = try await service.submitBugReport(bugReport, progressListener: progressSubject).get()