mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(import): mask the password again when an add form is cleared
Clear erased the password but left the visibility toggle on, so the next password typed in the Xtream or Stalker form showed in plain text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0ea9ecdb27
commit
6df5a148f6
6 files changed
+70
No files matched your search
+18
@@ -1140,6 +1140,24 @@ describe('StalkerPortalImportComponent form', () => {
|
||||
expect(toggle.getAttribute('aria-pressed')).toBe('true');
|
||||
});
|
||||
|
||||
it('masks the password again when the form is cleared', () => {
|
||||
const { fixture, root, component } = render();
|
||||
const password = root.querySelector('#password') as HTMLInputElement;
|
||||
const toggle = password
|
||||
.closest('mat-form-field')
|
||||
?.querySelector('button') as HTMLButtonElement;
|
||||
|
||||
toggle.click();
|
||||
fixture.detectChanges();
|
||||
expect(password.type).toBe('text');
|
||||
|
||||
component.clearForm();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(password.type).toBe('password');
|
||||
expect(toggle.getAttribute('aria-pressed')).toBe('false');
|
||||
});
|
||||
|
||||
it('shows a refusal inline under the portal URL and scrolls it into view', async () => {
|
||||
const scrollIntoView = jest.fn();
|
||||
Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', {
|
||||
|
||||
+5
@@ -149,6 +149,9 @@ export class StalkerPortalImportComponent {
|
||||
readonly feedback = signal<StalkerImportFeedback | null>(null);
|
||||
private readonly feedbackMessage =
|
||||
viewChild<ElementRef<HTMLElement>>('feedbackMessage');
|
||||
private readonly passwordToggle = viewChild(
|
||||
PasswordVisibilityToggleDirective
|
||||
);
|
||||
|
||||
/** Whether the device IDs are being generated from the MAC. */
|
||||
readonly derivesDeviceIds = signal(false);
|
||||
@@ -347,6 +350,8 @@ export class StalkerPortalImportComponent {
|
||||
this.invalidatePendingDerivation();
|
||||
this.derivesDeviceIds.set(false);
|
||||
this.feedback.set(null);
|
||||
// A cleared form is a fresh entry: the next password starts masked.
|
||||
this.passwordToggle()?.hide();
|
||||
this.form.controls.deviceId1.enable();
|
||||
this.form.controls.deviceId2.enable();
|
||||
this.form.reset({
|
||||
|
||||
+18
@@ -260,6 +260,24 @@ describe('XtreamCodeImportComponent form', () => {
|
||||
expect(toggle.getAttribute('aria-pressed')).toBe('false');
|
||||
});
|
||||
|
||||
it('masks the password again when the form is cleared', () => {
|
||||
const { fixture, root, component } = render();
|
||||
const password = root.querySelector('#password') as HTMLInputElement;
|
||||
const toggle = password
|
||||
.closest('mat-form-field')
|
||||
?.querySelector('button') as HTMLButtonElement;
|
||||
|
||||
toggle.click();
|
||||
fixture.detectChanges();
|
||||
expect(password.type).toBe('text');
|
||||
|
||||
component.clearForm();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(password.type).toBe('password');
|
||||
expect(toggle.getAttribute('aria-pressed')).toBe('false');
|
||||
});
|
||||
|
||||
it('shows a neutral server hint until the URL is invalid', () => {
|
||||
const { fixture, root, component } = render();
|
||||
|
||||
|
||||
+6
@@ -3,6 +3,7 @@ import {
|
||||
EventEmitter,
|
||||
Output,
|
||||
inject,
|
||||
viewChild,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
@@ -93,6 +94,9 @@ export class XtreamCodeImportComponent {
|
||||
|
||||
readonly store = inject(Store);
|
||||
readonly connectionTest = createXtreamConnectionTestState(this.form);
|
||||
private readonly passwordToggle = viewChild(
|
||||
PasswordVisibilityToggleDirective
|
||||
);
|
||||
|
||||
get isTestingConnection(): boolean {
|
||||
return this.connectionTest.testing();
|
||||
@@ -103,6 +107,8 @@ export class XtreamCodeImportComponent {
|
||||
}
|
||||
|
||||
clearForm(): void {
|
||||
// A cleared form is a fresh entry: the next password starts masked.
|
||||
this.passwordToggle()?.hide();
|
||||
this.form.reset({
|
||||
_id: createRandomId(),
|
||||
title: '',
|
||||
|
||||
+18
@@ -56,6 +56,24 @@ describe('PasswordVisibilityToggleDirective', () => {
|
||||
expect(button.getAttribute('aria-pressed')).toBe('false');
|
||||
});
|
||||
|
||||
it('masks again on hide()', () => {
|
||||
const fixture = TestBed.createComponent(HostComponent);
|
||||
fixture.detectChanges();
|
||||
const root = fixture.nativeElement as HTMLElement;
|
||||
const button = root.querySelector('button') as HTMLButtonElement;
|
||||
button.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.debugElement
|
||||
.query((node) => node.name === 'button')
|
||||
.injector.get(PasswordVisibilityToggleDirective)
|
||||
.hide();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(root.querySelector('input')?.type).toBe('password');
|
||||
expect(button.getAttribute('aria-pressed')).toBe('false');
|
||||
});
|
||||
|
||||
it('does not submit the surrounding form', () => {
|
||||
const form = document.createElement('form');
|
||||
const submit = jest.fn((event: Event) => event.preventDefault());
|
||||
|
||||
+5
@@ -29,4 +29,9 @@ export class PasswordVisibilityToggleDirective {
|
||||
toggle(): void {
|
||||
this.visible.update((visible) => !visible);
|
||||
}
|
||||
|
||||
/** Masks the field again, e.g. when its form is cleared for a new entry. */
|
||||
hide(): void {
|
||||
this.visible.set(false);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user