fix(settings): build bulk lock drafts only from a readable lock store

The Xtream and M3U management dialogs offer lock toggles, and the
Stalker lock dialog opens, only once the lock store has been read. A
draft built from the empty fail-closed snapshot would otherwise replace
the real locks with nothing on Save if storage recovered in between.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-09-27 15:44:41 +02:00
1 parent ceb0653415
commit ed48e7fd2b
7 files changed
+94 -5

No files matched your search

@@ -47,7 +47,11 @@ describe('GroupsViewComponent', () => {
let fixture: ComponentFixture<GroupsViewComponent>;
let component: GroupsViewComponent;
let dialog: { open: jest.Mock };
let parentalLock: { requestUnlock: jest.Mock };
let parentalLock: {
requestUnlock: jest.Mock;
ensureLocksReadable: jest.Mock;
lockedGroupTitles: jest.Mock;
};
const sportsCenter = createChannel(
'sports-1',
@@ -104,7 +108,11 @@ describe('GroupsViewComponent', () => {
beforeEach(async () => {
localStorage.removeItem(GROUP_CHANNEL_SORT_STORAGE_KEY);
parentalLock = { requestUnlock: jest.fn().mockResolvedValue(true) };
parentalLock = {
requestUnlock: jest.fn().mockResolvedValue(true),
ensureLocksReadable: jest.fn().mockResolvedValue(true),
lockedGroupTitles: jest.fn(() => ['News']),
};
dialog = {
open: jest.fn(),
@@ -537,6 +545,19 @@ describe('GroupsViewComponent', () => {
]);
});
it('opens the manage-groups dialog without lock toggles while the lock store is unreadable', async () => {
fixture.componentRef.setInput('playlistId', 'playlist-a');
fixture.componentRef.setInput('lockedGroupTitles', []);
fixture.detectChanges();
parentalLock.ensureLocksReadable.mockResolvedValue(false);
dialog.open.mockReturnValue({ afterClosed: () => of(undefined) });
await component.openGroupManagement();
const data = dialog.open.mock.calls[0][1].data;
expect(data.lockedGroupTitles).toBeUndefined();
});
it('drops the manage-groups result once another playlist is open', async () => {
const hiddenGroupTitlesChanged = jest.fn();
const lockedGroupTitlesChanged = jest.fn();
@@ -556,7 +556,17 @@ export class GroupsViewComponent {
count,
})
);
const lockedGroupTitles = this.lockedGroupTitles();
// Lock toggles only from a lock store that was read: a draft built
// from the empty fail-closed snapshot would, if storage recovered by
// Save, replace the real locks with nothing. Hidden groups stay
// editable either way.
const lockedGroupTitles =
this.lockedGroupTitles() !== null &&
playlistId &&
(await this.parentalLock.ensureLocksReadable()) &&
this.playlistId() === playlistId
? this.parentalLock.lockedGroupTitles(playlistId)
: null;
const dialogRef = this.dialog.open(GroupManagementDialogComponent, {
data: {
groups,