Files
buzz/mobile/ios/NativeControlTests/AddMembersIdentityTests.swift
Tom Brow e3e169afcc fix(mobile): keep New DM avatars steady across picker updates
Every state update from Dart reloads the native member picker, which built
fresh cells showing the initials fallback and fetched each avatar again over
the platform channel, so avatars blinked whenever results refreshed, most
visibly right after foregrounding. Keep loaded avatars by key and draw them
at once when a cell is rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>
Signed-off-by: Tom Brow <tomb@squareup.com>
2026-11-07 16:18:51 -08:00

53 lines
3.1 KiB
Swift

import Flutter
import UIKit
@main
struct AddMembersIdentityTests {
@MainActor static func main() {
let keys = [String(repeating: "a", count: 64), String(repeating: "b", count: 64)]
let labels = ["Scout · aaaaaaaa", "Scout · bbbbbbbb"]
let users: [[String: Any]] = zip(keys, labels).map { key, label in
["pubkey": key, "name": label, "detail": "Same owner", "agent": true, "initial": "S", "avatarKey": key]
}
var selected: [String] = []
var avatarReplies: [String: (Data?) -> Void] = [:]
let avatar = UIGraphicsImageRenderer(size: CGSize(width: 40, height: 40)).image { context in
UIColor.blue.setFill()
context.fill(CGRect(x: 0, y: 0, width: 40, height: 40))
}.pngData()
let picker = NativeAddMembersViewController(
state: ["users": users, "selected": [], "query": "", "loading": false],
loadAvatar: { key, _, reply in avatarReplies[key] = reply },
send: { event, values in
if event == "toggle", let key = values["pubkey"] as? String { selected.append(key) }
}
)
picker.loadViewIfNeeded()
for index in keys.indices {
let path = IndexPath(row: index, section: 1)
let cell = picker.tableView(picker.tableView, cellForRowAt: path)
let content = cell.contentConfiguration as? UIListContentConfiguration
precondition(content?.text == labels[index], "Visible identity must retain its qualifier")
precondition(cell.isAccessibilityElement, "The row owns accessibility")
precondition(cell.contentView.accessibilityElementsHidden, "No duplicate content stop")
precondition(cell.accessibilityLabel == "\(labels[index]), Same owner", "VoiceOver must retain the qualifier")
avatarReplies[keys[index]]?(avatar)
precondition(cell.accessibilityLabel == "\(labels[index]), Same owner", "Avatar loading retains identity")
precondition(cell.contentView.accessibilityElementsHidden, "Avatar loading keeps one accessibility owner")
picker.tableView(picker.tableView, didSelectRowAt: path)
}
precondition(selected == keys, "Each distinct label must select its own pubkey")
avatarReplies.removeAll()
picker.update(["users": users, "selected": [], "query": "", "loading": false])
let rebuilt = picker.tableView(picker.tableView, cellForRowAt: IndexPath(row: 0, section: 1))
let rebuiltImage = (rebuilt.contentConfiguration as? UIListContentConfiguration)?.image
precondition(avatarReplies.isEmpty, "A loaded avatar is not fetched again on update")
precondition(rebuiltImage?.pngData() == UIImage(data: avatar!)?.pngData(), "A rebuilt row draws its loaded avatar at once")
picker.update(["users": users, "selected": [users[1]], "query": "", "loading": false])
let chosen = picker.tableView(picker.tableView, cellForRowAt: IndexPath(row: 0, section: 0))
precondition(chosen.accessibilityLabel == "\(labels[1]), Same owner", "Selected identity retains its qualifier")
precondition(chosen.accessibilityTraits.contains(.selected), "Selection is exposed to VoiceOver")
print("Native member picker identity and accessibility passed")
}
}