--- phase: 13-user-directory-viewmodel plan: 02 subsystem: viewmodel tags: [wpf, mvvm, user-directory, icollectionview, csharp] requires: - phase: 13-user-directory-viewmodel plan: 01 provides: IGraphUserDirectoryService with includeGuests param, GraphDirectoryUser with UserType provides: - Directory browse mode in UserAccessAuditViewModel with load, filter, sort, cancel - ICollectionView for directory users with member/guest and text filtering - 16 unit tests for directory browse behavior affects: - SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs - SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelDirectoryTests.cs tech-stack: added: [] patterns: - ICollectionView with SortDescription and Filter predicate for directory users - Separate CancellationTokenSource for directory load (independent from base class CTS) - Optional constructor parameter for testability (IGraphUserDirectoryService?) key-files: created: - SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelDirectoryTests.cs modified: - SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs key-decisions: - IGraphUserDirectoryService injected as optional param in test constructor to preserve backward compat - Directory always fetches with includeGuests=true from Graph; member/guest filtering is in-memory via ICollectionView - Separate _directoryCts field for directory load cancellation (not sharing base class _cts) - No App.xaml.cs change needed — DI auto-resolves IGraphUserDirectoryService for UserAccessAuditViewModel metrics: duration: 261s completed: "2026-04-08T14:08:05Z" tasks_completed: 4 tasks_total: 4 tests_added: 16 tests_passing: 24 files_changed: 2 --- # Phase 13 Plan 02: User Directory ViewModel Summary Directory browse mode with paginated Graph load, member/guest toggle filter, text search across 4 fields, and DisplayName-sorted ICollectionView -- all testable without WPF View layer. ## What Was Done ### Task 1: Inject IGraphUserDirectoryService into ViewModel - Added `_graphUserDirectoryService` field to `UserAccessAuditViewModel` - Added required parameter to full (DI) constructor after `brandingService` - Added optional parameter to test constructor for backward compatibility - Verified DI auto-resolves via existing `services.AddTransient()` registration ### Task 2: Add directory browse mode properties and commands - Added 6 observable properties: `IsBrowseMode`, `DirectoryUsers`, `IsLoadingDirectory`, `DirectoryLoadStatus`, `IncludeGuests`, `DirectoryFilterText` - Added `DirectoryUserCount` computed property reflecting filtered view count - Added `DirectoryUsersView` (ICollectionView) with default SortDescription on DisplayName ascending - Added `LoadDirectoryCommand` (IAsyncRelayCommand) and `CancelDirectoryLoadCommand` (RelayCommand) - Initialized CollectionView and commands in both constructors - Added change handlers: `OnIncludeGuestsChanged`, `OnDirectoryFilterTextChanged`, `OnIsLoadingDirectoryChanged` ### Task 3: Implement LoadDirectoryAsync, filter predicate, tenant switch cleanup - `LoadDirectoryAsync`: validates service/profile, creates CTS, calls GetUsersAsync with progress reporting, populates on UI thread, handles cancel/error - `DirectoryFilterPredicate`: filters by IncludeGuests (UserType=="Member") then by text match on DisplayName, UPN, Department, JobTitle - `PopulateDirectory` helper: clears and repopulates collection, refreshes view - `OnTenantSwitched`: cancels directory CTS, clears DirectoryUsers, resets all directory state - Exposed `TestLoadDirectoryAsync()` internal method for test access ### Task 4: Write comprehensive tests (16 tests) - Created `UserAccessAuditViewModelDirectoryTests.cs` with helper factories - Tests cover: defaults, load populates, progress status, no-profile guard, cancellation, member/guest filtering, text filtering (DisplayName, Department, JobTitle), sort order, tenant switch reset, filtered count, search mode regression ## Deviations from Plan None -- plan executed exactly as written. ## Verification - `dotnet build --no-restore -warnaserror`: PASSED (0 warnings, 0 errors) - `dotnet test --filter "FullyQualifiedName~UserAccessAuditViewModel"`: 24/24 PASSED (8 existing + 16 new) ## Commits | Hash | Message | |------|---------| | 4ba4de6 | feat(13-02): add directory browse mode with paginated load, member/guest filter, and sortable ICollectionView |