fix(settings): close lock editors on relock and clear withheld details opened from All

The Xtream, Stalker and M3U category editors are gated by the PIN only when
they open; an idle relock left them on screen listing locked names with a
lock-rewriting Save. Each now closes itself when the session relocks.

ParentalLockEnforcementService also judges the selected Xtream/Stalker
item by its own category: a detail opened from All, recently added or
search has no selected category to vanish with, so it stayed open after a
relock.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5.1 committed 2026-09-26 00:25:01 +02:00
1 parent 45661e311e
commit bc1b0eef41
9 files changed
+438 -21

No files matched your search

@@ -0,0 +1,67 @@
import { signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { ParentalLockService } from '@iptvnator/services';
import {
GroupManagementDialogComponent,
GroupManagementDialogData,
} from './group-management-dialog.component';
describe('GroupManagementDialogComponent', () => {
const dialogRef = { close: jest.fn() };
const parentalLock = { active: signal(false) };
async function create(
data: GroupManagementDialogData
): Promise<ComponentFixture<GroupManagementDialogComponent>> {
await TestBed.configureTestingModule({
imports: [
GroupManagementDialogComponent,
TranslateModule.forRoot(),
],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{ provide: MAT_DIALOG_DATA, useValue: data },
{ provide: ParentalLockService, useValue: parentalLock },
],
}).compileComponents();
const fixture = TestBed.createComponent(GroupManagementDialogComponent);
fixture.detectChanges();
return fixture;
}
const groups = [
{ key: 'News', count: 3 },
{ key: 'Adult', count: 2 },
];
beforeEach(() => {
jest.clearAllMocks();
parentalLock.active.set(false);
});
it('closes without a result when the session relocks while locks are shown', async () => {
const fixture = await create({
groups,
hiddenGroupTitles: [],
lockedGroupTitles: ['Adult'],
});
expect(dialogRef.close).not.toHaveBeenCalled();
parentalLock.active.set(true);
fixture.detectChanges();
expect(dialogRef.close).toHaveBeenCalledWith(undefined);
});
it('keeps the plain hide/show editor open when the lock feature is off', 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);
});
});
@@ -3,6 +3,7 @@ import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
signal,
} from '@angular/core';
@@ -16,6 +17,7 @@ import {
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslatePipe } from '@ngx-translate/core';
import { ParentalLockService } from '@iptvnator/services';
import { foldSearchText } from '@iptvnator/shared/interfaces';
export interface GroupManagementDialogGroup {
@@ -64,6 +66,7 @@ export class GroupManagementDialogComponent {
>
);
readonly data = inject<GroupManagementDialogData>(MAT_DIALOG_DATA);
private readonly parentalLock = inject(ParentalLockService);
readonly showLocks = this.data.lockedGroupTitles !== undefined;
readonly searchTerm = signal('');
@@ -100,6 +103,16 @@ export class GroupManagementDialogComponent {
this.groups().every((group) => group.selected)
);
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.
effect(() => {
if (this.showLocks && this.parentalLock.active()) {
this.dialogRef.close(undefined);
}
});
}
clearSearch(): void {
this.searchTerm.set('');
}