fix(settings): close the M3U group editor on any relock

The group management dialog lists every group name, locked ones
included, even when it opened without lock toggles (unreadable lock
store). It now closes on any relock, and the groups view re-checks the
lock state before opening it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-09-27 16:15:02 +02:00
1 parent b078526bef
commit 0f5276c581
5 files changed
+38 -6

No files matched your search

+3 -1
View File
@@ -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
@@ -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);
});
});
@@ -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);
}
});
@@ -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();
@@ -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,