Restructured the Sample app target in the Xcode project and added its unit tests target as well.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import Foundation
|
||||
import Recording
|
||||
|
||||
/// A preinstalling service that records its requests, resolves the supported locales from configurable tables, and emits events
|
||||
/// on demand.
|
||||
@MainActor
|
||||
final class PreinstallingMock: Preinstalling {
|
||||
|
||||
/// The stream of events the service emits while preinstalling.
|
||||
let events: AsyncStream<PreinstallingEvent>
|
||||
|
||||
/// The supported equivalents the service resolves, keyed by the requested locale.
|
||||
var equivalents: [Locale: Locale] = [:]
|
||||
|
||||
/// The locales the service reports as supported.
|
||||
var localesSupported: [Locale] = []
|
||||
|
||||
/// The locales a preinstallation was requested for, in request order.
|
||||
private(set) var localesPreinstalled: [Locale] = []
|
||||
|
||||
/// The continuation that feeds ``events``.
|
||||
private let continuation: AsyncStream<PreinstallingEvent>.Continuation
|
||||
|
||||
init() {
|
||||
(events, continuation) = AsyncStream.makeStream(of: PreinstallingEvent.self)
|
||||
}
|
||||
|
||||
/// Emits the given event through ``events``.
|
||||
///
|
||||
/// - Parameter event: The event to emit.
|
||||
func emit(
|
||||
_ event: PreinstallingEvent
|
||||
) {
|
||||
continuation.yield(event)
|
||||
}
|
||||
|
||||
func preinstall(
|
||||
for locale: Locale
|
||||
) async {
|
||||
localesPreinstalled.append(locale)
|
||||
}
|
||||
|
||||
func supportedLocale(
|
||||
equivalentTo locale: Locale
|
||||
) async -> Locale? {
|
||||
equivalents[locale]
|
||||
}
|
||||
|
||||
func supportedLocales() async -> [Locale] {
|
||||
localesSupported
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
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()
|
||||
|
||||
#expect(model.locale == .english)
|
||||
#expect(model.locales.count == 2)
|
||||
#expect(model.locales.contains(.dutch))
|
||||
#expect(model.locales.contains(.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()
|
||||
|
||||
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: - Constants
|
||||
|
||||
private extension Locale {
|
||||
/// 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")
|
||||
}
|
||||
Reference in New Issue
Block a user