feat(adminless-groups): admin promotion UI when last admin leaves group - WPB-25289#4829
feat(adminless-groups): admin promotion UI when last admin leaves group - WPB-25289#4829David-Henner wants to merge 30 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Implements the “last admin leaving a group” flow behind the preventAdminlessGroups feature: when the final admin tries to leave, they must either promote another eligible participant as admin (via a new SwiftUI selection sheet) or delete the group (with a delete-only variant when no candidates exist).
Changes:
- Adds
LastAdminLeaveAlertplus anAdminSelectionView/AdminSelectionViewModelpromotion sheet, and wires the flow intoConversationActionController’s leave action. - Extends accessibility identifiers/locators for UI testing and improves participant cell identification (admin vs member).
- Adds UI test coverage for the promotion-and-leave path and unit tests for the view model filtering/promotion state.
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| WireUI/Sources/WireLocators/Locators.swift | Adds locators for admin/member participant cells, last-admin leave alert actions, and the admin selection page. |
| wire-ios/WireUITests/Pages/ConversationDetailsPage.swift | Adds helpers to tap “Promote new admin” and to query admin/member cells by name. |
| wire-ios/WireUITests/Pages/AdminSelectionPage.swift | Introduces a page object for the admin selection sheet (user selection + promote). |
| wire-ios/WireUITests/Helper/WireUITestCase.swift | Adds an override hook to inject additional developer flags before app launch. |
| wire-ios/WireUITests/AdminPromotionTests.swift | Adds UI test that promotes a new admin and verifies the owner leaves the group. |
| wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UIAlertAction+Convenience.swift | Extends the convenience initializer to accept an accessibility hint. |
| wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/Sections/ParticipantsSectionController.swift | Sets participant cell accessibility identifiers based on section role (admin vs member). |
| wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/ConversationActions/LastAdminLeaveAlert.swift | Adds alert factory for promote-or-delete and delete-only variants with locators. |
| wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/ConversationActions/ConversationActionController+Leave.swift | Adds last-admin detection, candidate filtering, admin selection presentation, promotion + leave behavior. |
| wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/ConversationActions/ConversationActionController.swift | Routes .leave action through the new requestLeave(for:) flow. |
| wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/AdminSelection/AdminSelectionViewModel.swift | Adds view model for searching, selecting, and promoting candidates (with promotion state). |
| wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/AdminSelection/AdminSelectionView.swift | Adds SwiftUI sheet UI with search, candidate rows, and promote action. |
| wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserImageViewRepresentable.swift | Bridges UserImageView into SwiftUI for candidate rows. |
| wire-ios/Wire-iOS/Resources/Localization/Base.lproj/Localizable.strings | Adds new localized strings for the admin selection and last-admin leave alerts. |
| wire-ios/Wire-iOS/Resources/Localization/Base.lproj/Accessibility.strings | Adds VoiceOver strings for admin selection UI. |
| wire-ios/Wire-iOS/Generated/Strings+Generated.swift | Updates generated string accessors for the newly added localization keys. |
| wire-ios/Wire-iOS UnitTests/UserInterface/GroupDetails/AdminSelectionViewModelTests.swift | Adds Swift Testing unit tests for filtering, canPromote, and promotion state changes. |
| wire-ios-utilities/Source/DeveloperFlag.swift | Adds preventAdminlessGroups developer flag. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @discardableResult | ||
| func selectUser(named name: String) -> Self { | ||
| let predicate = NSPredicate(format: "label == %@", name) | ||
| app.staticTexts | ||
| .matching(identifier: Locators.AdminSelectionPage.userCell.rawValue) | ||
| .matching(predicate) | ||
| .firstMatch | ||
| .tap() | ||
| return self |
| // Self user is no longer a participant | ||
| XCTAssertFalse( | ||
| conversationDetailsPage.userCell(named: owner.name).exists, | ||
| "Owner should not appear in participant list after leaving" | ||
| ) |
Test Results164 tests 163 ✅ 1m 2s ⏱️ Results for commit ec0239b. ♻️ This comment has been updated with latest results. Summary: workflow run #27965030996 |
netbe
left a comment
There was a problem hiding this comment.
looks good, left some comments about design
…ction/AdminSelectionViewModel.swift Co-authored-by: Sam Wyndham <samwyndham@users.noreply.github.com>
79ff8ae to
1ff5255
Compare
|



When the last admin of a group tries to leave and the
preventAdminlessGroupsfeature is enabled, the user is presented with a choice: promote another participant as admin before leaving, or delete the group. If no eligible candidates exist, only the delete option is offered.ScreenRecording_06-09-2026.18-10-40_1.MP4
Changes
Added:
AdminSelectionView— SwiftUI sheet for searching and selecting a new admin, with full VoiceOver accessibility supportAdminSelectionViewModel—@MainActorObservableObject owning search filtering, selection, andPromotionState(.idle / .inProgress / .succeeded / .failed)LastAdminLeaveAlert—UIAlertControllerfactory for the "promote or delete" and "delete only" alert variantsUserImageViewRepresentable—UIViewRepresentablebridge forUserImageViewin SwiftUIAdminPromotionTests— UI test: last admin promotes a new admin and leaves the groupAdminSelectionPage— page object forAdminSelectionViewAdminSelectionViewModelTests— Swift Testing unit tests forfilteredCandidates,canPromote, andpromoteModified:
ConversationActionController+Leave—requestLeavecheckspreventAdminlessGroupsflag and last-admin status before showing the new alert;performAdminPromotioncallsupdateRolethen leaves the groupParticipantsSectionController—configure(user:isE2EICertified:conversation:showSeparator:)now takesconversationRoleand setsadminCell/memberCellaccessibility identifier internallyUIAlertAction+Convenience— extended theaccessibilityIdentifierconvenience init to also acceptaccessibilityHintConversationDetailsPage— addedtapPromoteNewAdmin(),adminCell(named:),memberCell(named:)WireUITestCase— addedadditionalDeveloperFlags()override hook for injecting flags before app launchLocators— addedLastAdminLeaveAlert,AdminSelectionPage,ConversationDetailsPage.adminCell/.memberCelllocatorsDeveloperFlag— addedpreventAdminlessGroupsdeveloper flagArchitecture note
Aligning with architecture guidelines (e.g. UseCases, repositories) was out of scope and not worth committing to — the legacy code already had the interfaces in place to integrate the feature directly.
Testing
preventAdminlessGroupsdeveloper flag (Settings → Developer)AdminPromotionTestsUI test suiteChecklist
[WPB-XXX].UI accessibility checklist
If your PR includes UI changes, please utilize this checklist: