Add password-remember + warning on save

This commit is contained in:
Jack Kingsman
2026-03-19 20:09:35 -07:00
parent 5b166c4b66
commit d05312c157
7 changed files with 282 additions and 23 deletions
+33 -4
View File
@@ -7,6 +7,10 @@ describe('RepeaterLogin', () => {
repeaterName: 'TestRepeater',
loading: false,
error: null as string | null,
password: '',
onPasswordChange: vi.fn(),
rememberPassword: false,
onRememberPasswordChange: vi.fn(),
onLogin: vi.fn(),
onLoginAsGuest: vi.fn(),
};
@@ -26,20 +30,45 @@ describe('RepeaterLogin', () => {
render(<RepeaterLogin {...defaultProps} />);
expect(screen.getByPlaceholderText('Repeater password...')).toBeInTheDocument();
expect(screen.getByText('Remember password')).toBeInTheDocument();
expect(screen.getByText('Login with Password')).toBeInTheDocument();
expect(screen.getByText('Login as Guest / ACLs')).toBeInTheDocument();
});
it('calls onLogin with trimmed password on submit', () => {
render(<RepeaterLogin {...defaultProps} />);
const input = screen.getByPlaceholderText('Repeater password...');
fireEvent.change(input, { target: { value: ' secret ' } });
render(<RepeaterLogin {...defaultProps} password=" secret " />);
fireEvent.submit(screen.getByText('Login with Password').closest('form')!);
expect(defaultProps.onLogin).toHaveBeenCalledWith('secret');
});
it('propagates password changes', () => {
render(<RepeaterLogin {...defaultProps} />);
const input = screen.getByPlaceholderText('Repeater password...');
fireEvent.change(input, { target: { value: 'new secret' } });
expect(defaultProps.onPasswordChange).toHaveBeenCalledWith('new secret');
});
it('toggles remember password checkbox', () => {
render(<RepeaterLogin {...defaultProps} />);
fireEvent.click(screen.getByLabelText('Remember password'));
expect(defaultProps.onRememberPasswordChange).toHaveBeenCalledWith(true);
});
it('shows storage warning when remember password is enabled', () => {
render(<RepeaterLogin {...defaultProps} rememberPassword={true} />);
expect(
screen.getByText(
/Passwords are stored unencrypted in local browser storage for this domain\./
)
).toBeInTheDocument();
});
it('calls onLoginAsGuest when guest button clicked', () => {
render(<RepeaterLogin {...defaultProps} />);
+8
View File
@@ -1 +1,9 @@
import '@testing-library/jest-dom';
class ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
}
globalThis.ResizeObserver = ResizeObserver;
@@ -0,0 +1,77 @@
import { act, renderHook } from '@testing-library/react';
import { beforeEach, describe, expect, it } from 'vitest';
import { useRememberedServerPassword } from '../hooks/useRememberedServerPassword';
describe('useRememberedServerPassword', () => {
beforeEach(() => {
localStorage.clear();
});
it('loads remembered passwords from localStorage', () => {
localStorage.setItem(
'remoteterm-server-password:repeater:abc123',
JSON.stringify({ password: 'stored-secret' })
);
const { result } = renderHook(() => useRememberedServerPassword('repeater', 'abc123'));
expect(result.current.password).toBe('stored-secret');
expect(result.current.rememberPassword).toBe(true);
});
it('stores passwords after login when remember is enabled', () => {
const { result } = renderHook(() => useRememberedServerPassword('room', 'room-key'));
act(() => {
result.current.setRememberPassword(true);
});
act(() => {
result.current.persistAfterLogin(' hello ');
});
expect(localStorage.getItem('remoteterm-server-password:room:room-key')).toBe(
JSON.stringify({ password: 'hello' })
);
expect(result.current.password).toBe('hello');
});
it('clears stored passwords when login is done with remember disabled', () => {
localStorage.setItem(
'remoteterm-server-password:repeater:abc123',
JSON.stringify({ password: 'stored-secret' })
);
const { result } = renderHook(() => useRememberedServerPassword('repeater', 'abc123'));
act(() => {
result.current.setRememberPassword(false);
});
act(() => {
result.current.persistAfterLogin('new-secret');
});
expect(localStorage.getItem('remoteterm-server-password:repeater:abc123')).toBeNull();
expect(result.current.password).toBe('');
});
it('preserves remembered passwords on guest login when remember stays enabled', () => {
localStorage.setItem(
'remoteterm-server-password:room:room-key',
JSON.stringify({ password: 'stored-secret' })
);
const { result } = renderHook(() => useRememberedServerPassword('room', 'room-key'));
act(() => {
result.current.persistAfterLogin('');
});
expect(localStorage.getItem('remoteterm-server-password:room:room-key')).toBe(
JSON.stringify({ password: 'stored-secret' })
);
expect(result.current.password).toBe('stored-secret');
});
});