diff --git a/docs/architecture/parental-lock.md b/docs/architecture/parental-lock.md index d72483d08..e54d6fcad 100644 --- a/docs/architecture/parental-lock.md +++ b/docs/architecture/parental-lock.md @@ -386,7 +386,9 @@ so a cancelled or refused PIN leaves it showing the real state. read (`ensureLocksReadable`): a draft taken from the empty fail-closed snapshot would erase the real locks on Save if storage recovered in between. The M3U and Xtream dialogs then open without lock toggles; the - Stalker dialog does not open. Bulk actions stay in the dialog, and while the session is locked + Stalker dialog does not open. Every one of them closes on a relock, the + M3U one also when it opened without toggles, since it still lists every + group name. Bulk actions stay in the dialog, and while the session is locked a locked category is not in the rail, so unlocking always goes through the "N locked · Enter PIN to show" row or the dialog. The M3U dialog hands its lock list back to `ChannelListContainerComponent`, which diff --git a/libs/ui/components/src/lib/channel-list-container/groups-view/group-management-dialog/group-management-dialog.component.spec.ts b/libs/ui/components/src/lib/channel-list-container/groups-view/group-management-dialog/group-management-dialog.component.spec.ts index 03306b7f8..20141d6c6 100644 --- a/libs/ui/components/src/lib/channel-list-container/groups-view/group-management-dialog/group-management-dialog.component.spec.ts +++ b/libs/ui/components/src/lib/channel-list-container/groups-view/group-management-dialog/group-management-dialog.component.spec.ts @@ -55,13 +55,21 @@ describe('GroupManagementDialogComponent', () => { expect(dialogRef.close).toHaveBeenCalledWith(undefined); }); - it('keeps the plain hide/show editor open when the lock feature is off', async () => { + it('keeps the plain hide/show editor open while nothing relocks', async () => { const fixture = await create({ groups, hiddenGroupTitles: ['News'] }); - - parentalLock.active.set(true); fixture.detectChanges(); expect(dialogRef.close).not.toHaveBeenCalled(); expect(fixture.componentInstance.showLocks).toBe(false); }); + + it('closes the editor without lock toggles too on a relock: it lists every group', async () => { + // Opened without toggles because the lock store was unreadable. + const fixture = await create({ groups, hiddenGroupTitles: ['News'] }); + + parentalLock.active.set(true); + fixture.detectChanges(); + + expect(dialogRef.close).toHaveBeenCalledWith(undefined); + }); }); diff --git a/libs/ui/components/src/lib/channel-list-container/groups-view/group-management-dialog/group-management-dialog.component.ts b/libs/ui/components/src/lib/channel-list-container/groups-view/group-management-dialog/group-management-dialog.component.ts index 710ab30ca..58748137d 100644 --- a/libs/ui/components/src/lib/channel-list-container/groups-view/group-management-dialog/group-management-dialog.component.ts +++ b/libs/ui/components/src/lib/channel-list-container/groups-view/group-management-dialog/group-management-dialog.component.ts @@ -105,9 +105,10 @@ export class GroupManagementDialogComponent { constructor() { // A relock while the editor is open: the locked group names it - // lists and the lock list it can rewrite are behind the PIN. + // lists (with or without lock toggles — every group is listed) and + // the lock list it can rewrite are behind the PIN. effect(() => { - if (this.showLocks && this.parentalLock.active()) { + if (this.parentalLock.active()) { this.dialogRef.close(undefined); } }); diff --git a/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.spec.ts b/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.spec.ts index d2ed2b48c..64ffcee83 100644 --- a/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.spec.ts +++ b/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.spec.ts @@ -51,6 +51,7 @@ describe('GroupsViewComponent', () => { requestUnlock: jest.Mock; ensureLocksReadable: jest.Mock; lockedGroupTitles: jest.Mock; + active: jest.Mock; }; const sportsCenter = createChannel( @@ -112,6 +113,7 @@ describe('GroupsViewComponent', () => { requestUnlock: jest.fn().mockResolvedValue(true), ensureLocksReadable: jest.fn().mockResolvedValue(true), lockedGroupTitles: jest.fn(() => ['News']), + active: jest.fn(() => false), }; dialog = { @@ -558,6 +560,20 @@ describe('GroupsViewComponent', () => { expect(data.lockedGroupTitles).toBeUndefined(); }); + it('opens no manage-groups dialog when the session relocked while the store was read', async () => { + fixture.componentRef.setInput('playlistId', 'playlist-a'); + fixture.componentRef.setInput('lockedGroupTitles', []); + fixture.detectChanges(); + parentalLock.ensureLocksReadable.mockImplementation(async () => { + parentalLock.active.mockReturnValue(true); + return true; + }); + + await component.openGroupManagement(); + + expect(dialog.open).not.toHaveBeenCalled(); + }); + it('drops the manage-groups result once another playlist is open', async () => { const hiddenGroupTitlesChanged = jest.fn(); const lockedGroupTitlesChanged = jest.fn(); diff --git a/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.ts b/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.ts index 0266868ce..731ba2dfd 100644 --- a/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.ts +++ b/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.ts @@ -567,6 +567,11 @@ export class GroupsViewComponent { this.playlistId() === playlistId ? this.parentalLock.lockedGroupTitles(playlistId) : null; + // Relocked while the store was being read: the dialog lists every + // group, locked ones included. + if (this.parentalLock.active() || this.playlistId() !== playlistId) { + return; + } const dialogRef = this.dialog.open(GroupManagementDialogComponent, { data: { groups,