Files
attendi/Apps/Attendi/Tests/View Models/ContentViewModelTests.swift
T

320 lines
9.6 KiB
Swift

import Foundation
import Notifying
import Recording
import Testing
@testable import Attendi
@Suite("Content view model")
struct ContentViewModelTests {
typealias Model = ContentView.Model
// MARK: Load
@MainActor
@Suite("Load")
struct Load {
@Test
func `load fills the supported locales and aligns the picked locale`() async {
let preinstaller = PreinstallingMock()
preinstaller.localesSupported = [.dutch, .english]
preinstaller.equivalents = [.current: .english]
let model = Model(preinstaller: preinstaller)
await model.load(identifier: "")
#expect(model.locale == .english)
#expect(model.locales.count == 2)
#expect(model.locales.contains(.dutch))
#expect(model.locales.contains(.english))
}
@Test
func `load restores the persisted locale`() async {
let preinstaller = PreinstallingMock()
preinstaller.localesSupported = [.dutch, .english]
preinstaller.equivalents = [.dutch: .dutch]
let model = Model(preinstaller: preinstaller)
await model.load(identifier: Locale.dutch.identifier)
#expect(model.locale == .dutch)
}
@Test
func `load falls back to the default locale when the persisted locale is unsupported`() async {
let preinstaller = PreinstallingMock()
preinstaller.localesSupported = [.dutch, .english]
preinstaller.equivalents = [.byDefault: .english]
let model = Model(preinstaller: preinstaller)
await model.load(identifier: "fr-FR")
#expect(model.locale == .english)
}
@Test
func `load preinstalls the aligned locale`() async throws {
let preinstaller = PreinstallingMock()
preinstaller.localesSupported = [.dutch, .english]
preinstaller.equivalents = [.current: .english]
let model = Model(preinstaller: preinstaller)
await model.load(identifier: "")
try await Task.sleep(for: .seconds(0.1))
#expect(preinstaller.localesPreinstalled == [.english])
}
}
// MARK: Preinstall
@MainActor
@Suite("Preinstall")
struct Preinstall {
@Test
func `preinstalling the same locale twice runs once`() async throws {
let preinstaller = PreinstallingMock()
let model = Model(preinstaller: preinstaller)
model.locale = .dutch
model.preinstall()
model.preinstall()
try await Task.sleep(for: .seconds(0.1))
#expect(preinstaller.localesPreinstalled == [.dutch])
}
@Test
func `preinstalling a newly picked locale runs again`() async throws {
let preinstaller = PreinstallingMock()
let model = Model(preinstaller: preinstaller)
model.locale = .dutch
model.preinstall()
model.locale = .english
model.preinstall()
try await Task.sleep(for: .seconds(0.1))
#expect(preinstaller.localesPreinstalled == [.dutch, .english])
}
}
// MARK: Events
@MainActor
@Suite("Events")
struct Events {
@Test
func `a started event posts an info notification`() async throws {
let preinstaller = PreinstallingMock()
let model = Model(preinstaller: preinstaller)
model.locale = .dutch
model.preinstall()
try await Task.sleep(for: .seconds(0.1))
preinstaller.emit(.started(.dutch))
try await Task.sleep(for: .seconds(0.1))
#expect(model.notifier.notifications.count == 1)
#expect(model.notifier.notifications.first?.kind == .info)
#expect(model.notifier.notifications.first?.symbol == "arrow.down.circle")
#expect(model.notifier.notifications.first?.message == String(localized: .viewRecordingNotificationDownloadStarted(model.name(for: .dutch))))
}
@Test
func `a cancelled event posts a warning notification`() async throws {
let preinstaller = PreinstallingMock()
let model = Model(preinstaller: preinstaller)
model.locale = .dutch
model.preinstall()
try await Task.sleep(for: .seconds(0.1))
preinstaller.emit(.cancelled(.dutch))
try await Task.sleep(for: .seconds(0.1))
#expect(model.notifier.notifications.first?.kind == .warning)
#expect(model.notifier.notifications.first?.symbol == "xmark.circle")
#expect(model.notifier.notifications.first?.message == String(localized: .viewRecordingNotificationDownloadCancelled(model.name(for: .dutch))))
}
@Test
func `a failed event posts an error notification`() async throws {
let preinstaller = PreinstallingMock()
let model = Model(preinstaller: preinstaller)
model.locale = .dutch
model.preinstall()
try await Task.sleep(for: .seconds(0.1))
preinstaller.emit(.failed(.dutch))
try await Task.sleep(for: .seconds(0.1))
#expect(model.notifier.notifications.first?.kind == .error)
#expect(model.notifier.notifications.first?.symbol == "exclamationmark.triangle")
#expect(model.notifier.notifications.first?.message == String(localized: .viewRecordingNotificationDownloadFailed(model.name(for: .dutch))))
}
@Test
func `a cancellation of the current locale lets a re-pick retry`() async throws {
let preinstaller = PreinstallingMock()
let model = Model(preinstaller: preinstaller)
model.locale = .dutch
model.preinstall()
try await Task.sleep(for: .seconds(0.1))
preinstaller.emit(.cancelled(.dutch))
try await Task.sleep(for: .seconds(0.1))
model.preinstall()
try await Task.sleep(for: .seconds(0.1))
#expect(preinstaller.localesPreinstalled == [.dutch, .dutch])
}
@Test
func `a cancellation of another locale keeps the current preinstallation`() async throws {
let preinstaller = PreinstallingMock()
let model = Model(preinstaller: preinstaller)
model.locale = .english
model.preinstall()
try await Task.sleep(for: .seconds(0.1))
preinstaller.emit(.cancelled(.dutch))
try await Task.sleep(for: .seconds(0.1))
model.preinstall()
try await Task.sleep(for: .seconds(0.1))
#expect(preinstaller.localesPreinstalled == [.english])
}
}
// MARK: Name
@MainActor
@Suite("Name")
struct Name {
@Test
func `name pairs the localized language name with the region flag`() {
let model = Model(preinstaller: PreinstallingMock())
#expect(model.name(for: .english) == "\(languageName("en")) 🇺🇸")
#expect(model.name(for: .dutch) == "\(languageName("nl")) 🇳🇱")
}
@Test
func `name omits the flag for a locale without a region`() {
let model = Model(preinstaller: PreinstallingMock())
#expect(!model.name(for: Locale(identifier: "en")).containsFlag)
}
@Test
func `name omits the flag for a numeric region`() {
let model = Model(preinstaller: PreinstallingMock())
#expect(!model.name(for: Locale(identifier: "es-419")).containsFlag)
}
}
// MARK: Navigation Title
@MainActor
@Suite("Navigation title")
struct NavigationTitle {
@Test
func `navigation title names the selected locale`() {
let model = Model(preinstaller: PreinstallingMock())
model.locale = .english
#expect(String(localized: model.navigationTitle) == "Transcribe to \(model.name(for: .english))")
}
@Test
func `navigation title updates when the locale changes`() {
let model = Model(preinstaller: PreinstallingMock())
model.locale = .english
let english = String(localized: model.navigationTitle)
model.locale = .dutch
let dutch = String(localized: model.navigationTitle)
#expect(english == "Transcribe to \(model.name(for: .english))")
#expect(dutch == "Transcribe to \(model.name(for: .dutch))")
#expect(english != dutch)
}
}
}
// MARK: - Constants
private extension Locale {
/// The locale the model falls back to when no equivalent of the current locale is supported.
static let byDefault = Locale(identifier: "en_US")
/// A supported locale to preinstall in the tests.
static let dutch = Locale(identifier: "nl-NL")
/// Another supported locale to preinstall in the tests.
static let english = Locale(identifier: "en-US")
}
// MARK: - Helpers
/// The capitalized, localized language name for a language code, mirroring how the model builds the language part of a locale's name.
///
/// - Parameter code: The language code to name.
/// - Returns: The localized language name in the current locale, capitalized.
private func languageName(_ code: String) -> String {
(Locale.current.localizedString(forLanguageCode: code) ?? code).localizedCapitalized
}
private extension String {
/// Whether the string contains a Regional Indicator Symbol scalar — part of a flag emoji.
var containsFlag: Bool {
unicodeScalars.contains { $0.value >= 0x1F1E6 && $0.value <= 0x1F1FF }
}
}