Tell the accounts apart, and let a disabled source come back

This commit is contained in:
Arkadiusz Fal
2026-09-07 18:53:21 +02:00
parent 5ac6b7188d
commit 02efc71e7c
15 changed files with 533 additions and 317 deletions

View File

@@ -319,3 +319,140 @@ struct AccountStatusBadgeTests {
#expect(AccountStatusBadge.color(for: .checking) == .secondary)
}
}
// MARK: - Switcher labels
@Suite("Data context switcher labels")
@MainActor
struct DataContextSwitcherLabelTests {
private func makeDefaults() -> UserDefaults {
UserDefaults(suiteName: "yattee.tests.\(UUID().uuidString)")!
}
private func makeScope() -> CredentialIdentityScope {
CredentialIdentityScope(fixedScope: "identity-\(UUID().uuidString)")
}
/// The real managers a label is built from, with storage of their own.
private func makeFixture() -> (AccountsManager, InstancesManager, DataContextValidator) {
let instances = makeTestInstancesManager()
let accounts = AccountsManager(
identityScope: makeScope(),
instancesManager: instances,
defaults: makeDefaults()
)
let validator = DataContextValidator(
accountsManager: accounts,
instancesManager: instances,
toastManager: ToastManager()
)
return (accounts, instances, validator)
}
private func makeInstance(_ host: String) -> Instance {
Instance(type: .invidious, url: URL(string: "https://\(host)")!)
}
private func title(
_ account: Account,
_ accounts: AccountsManager,
_ validator: DataContextValidator,
usernames: [UUID: String] = [:]
) -> String {
DataContextSwitcherLabels.title(
for: .account(account.id),
accountsManager: accounts,
validator: validator,
username: { usernames[$0.id] }
)
}
@Test("A name nobody shares is left alone")
func uniqueNameIsPlain() {
let (accounts, instances, validator) = makeFixture()
let instance = makeInstance("inv.example.com")
instances.add(instance)
let account = accounts.addAccount(instanceID: instance.id, displayName: "Work")
accounts.setStatus(.signedIn, for: account.id)
#expect(title(account, accounts, validator) == "Work")
}
@Test("Two sources sharing a name are told apart by their host")
func sharedNameOnTwoSourcesGetsTheHost() {
let (accounts, instances, validator) = makeFixture()
let first = makeInstance("one.example.com")
let second = makeInstance("two.example.com")
instances.add(first)
instances.add(second)
let a = accounts.addAccount(instanceID: first.id, displayName: "Work")
let b = accounts.addAccount(instanceID: second.id, displayName: "Work")
accounts.setStatus(.signedIn, for: a.id)
accounts.setStatus(.signedIn, for: b.id)
#expect(title(a, accounts, validator) == "Work · one.example.com")
#expect(title(b, accounts, validator) == "Work · two.example.com")
}
@Test("Two logins on one source are told apart by who they are")
func sharedNameOnOneSourceGetsTheUsername() {
let (accounts, instances, validator) = makeFixture()
let instance = makeInstance("inv.example.com")
instances.add(instance)
let a = accounts.addAccount(instanceID: instance.id, displayName: "Work")
let b = accounts.addAccount(instanceID: instance.id, displayName: "Work")
accounts.setStatus(.signedIn, for: a.id)
accounts.setStatus(.signedIn, for: b.id)
let usernames = [a.id: "alice", b.id: "bob"]
// The host is what they have in common, so a host suffix would leave
// the user choosing between two identical rows.
#expect(title(a, accounts, validator, usernames: usernames) == "Work · alice@inv.example.com")
#expect(title(b, accounts, validator, usernames: usernames) == "Work · bob@inv.example.com")
}
@Test("A status the user has to act on is named in the label")
func statusIsAppended() {
let (accounts, instances, validator) = makeFixture()
let instance = makeInstance("inv.example.com")
instances.add(instance)
let account = accounts.addAccount(instanceID: instance.id, displayName: "Work")
accounts.setStatus(.credentialRejected, for: account.id)
let label = title(account, accounts, validator)
#expect(label.hasPrefix("Work"))
#expect(label.contains(AccountStatusBadge.text(for: .credentialRejected)))
}
@Test("The identifier is preferred, the host is the fallback")
func disambiguationPrefersTheIdentifier() {
#expect(
DataContextSwitcherLabels.disambiguated("Work", username: "alice", host: "inv.example.com")
== "Work · alice@inv.example.com"
)
#expect(
DataContextSwitcherLabels.disambiguated("Work", username: nil, host: "inv.example.com")
== "Work · inv.example.com"
)
#expect(DataContextSwitcherLabels.disambiguated("Work", username: "alice", host: nil) == "Work · alice")
}
@Test("A name that already carries the host does not repeat it")
func hostIsNotRepeated() {
#expect(
DataContextSwitcherLabels.disambiguated("inv.example.com", username: nil, host: "inv.example.com")
== "inv.example.com"
)
// The identifier still adds something the name does not have.
#expect(
DataContextSwitcherLabels.disambiguated("inv.example.com", username: "alice", host: "inv.example.com")
== "inv.example.com · alice@inv.example.com"
)
}
}

View File

@@ -71,6 +71,30 @@ struct InstanceFilteringTests {
#expect(enabledInstances.count == 1)
#expect(enabledInstances.first?.url.host == "enabled.example.com")
}
@Test("A disabled instance leaves the enabled listing without leaving the app")
func disablingPartitionsRatherThanRemoves() {
let manager = makeTestInstancesManager()
let instance = Instance(type: .invidious, url: URL(string: "https://inv.example.com")!)
manager.add(instance)
#expect(manager.enabledInstances.map(\.id) == [instance.id])
manager.toggleEnabled(instance)
// Every listing in the Sources tab is filtered to `enabledInstances`,
// so this is exactly the disappearance the recovery section undoes —
// and it can only undo it because the instance is still here.
#expect(manager.enabledInstances.isEmpty)
#expect(manager.instances.filter { !$0.isEnabled }.map(\.id) == [instance.id])
// Turning it back on takes the stale copy the row was drawn from: the
// row's instance is a value, and it stopped matching the stored one the
// moment it was switched off.
manager.toggleEnabled(instance)
#expect(manager.enabledInstances.map(\.id) == [instance.id])
}
}
// MARK: - Instance Type Tests