Polish user actions and runtime controls

This commit is contained in:
2026-04-02 02:51:32 +03:00
parent c04847b21c
commit 3adda67eb9
9 changed files with 556 additions and 85 deletions

View File

@@ -105,6 +105,16 @@ describe('App login gate', () => {
expect(screen.getByRole('button', { name: /new user/i })).toBeInTheDocument();
});
it('can stop the runtime from the dashboard', async () => {
const user = userEvent.setup();
render(<App />);
await loginIntoPanel(user);
await user.click(screen.getByRole('button', { name: /stop/i }));
expect(screen.getAllByText(/^idle$/i).length).toBeGreaterThan(0);
});
it('opens add-user flow in a modal and closes it on escape', async () => {
const user = userEvent.setup();
render(<App />);
@@ -116,7 +126,8 @@ describe('App login gate', () => {
const dialog = screen.getByRole('dialog', { name: /add user/i });
expect(dialog).toBeInTheDocument();
expect(screen.getByPlaceholderText(/night-shift-01/i)).toBeInTheDocument();
expect(String((screen.getByLabelText(/username/i) as HTMLInputElement).value)).toMatch(/^user-/i);
expect(String((screen.getByLabelText(/password/i) as HTMLInputElement).value)).toMatch(/^pw-/i);
expect(screen.getByLabelText(/service/i)).toBeInTheDocument();
expect(screen.queryByLabelText(/port/i)).not.toBeInTheDocument();
expect(within(dialog).getByText(/edge\.example\.net:1080/i)).toBeInTheDocument();
@@ -133,11 +144,11 @@ describe('App login gate', () => {
await loginIntoPanel(user);
await user.click(screen.getByRole('button', { name: /users/i }));
await user.click(screen.getAllByRole('button', { name: /pause/i })[0]);
await user.click(screen.getAllByRole('button', { name: /pause user/i })[0]);
expect(screen.getByText(/^paused$/i)).toBeInTheDocument();
expect(screen.getAllByRole('button', { name: /resume/i })).toHaveLength(1);
expect(screen.getAllByRole('button', { name: /resume user/i })).toHaveLength(1);
await user.click(screen.getByRole('button', { name: /resume/i }));
await user.click(screen.getByRole('button', { name: /resume user/i }));
expect(screen.queryByText(/^paused$/i)).not.toBeInTheDocument();
});
@@ -150,7 +161,7 @@ describe('App login gate', () => {
expect(screen.getByText(/night-shift/i)).toBeInTheDocument();
await user.click(screen.getAllByRole('button', { name: /delete/i })[0]);
await user.click(screen.getAllByRole('button', { name: /^delete user$/i })[0]);
const dialog = screen.getByRole('dialog', { name: /delete user/i });
expect(dialog).toBeInTheDocument();
@@ -190,6 +201,27 @@ describe('App login gate', () => {
expect(screen.getAllByText(/gw\.example\.net:1180/i).length).toBeGreaterThan(0);
});
it('opens edit-user flow with existing credentials instead of generating new ones', async () => {
const user = userEvent.setup();
render(<App />);
await loginIntoPanel(user);
await user.click(screen.getByRole('button', { name: /users/i }));
await user.click(screen.getAllByRole('button', { name: /edit user/i })[0]);
const dialog = screen.getByRole('dialog', { name: /edit user/i });
expect(within(dialog).getByLabelText(/username/i)).toHaveValue('night-shift');
expect(within(dialog).getByLabelText(/password/i)).toHaveValue('kettle!23');
await user.clear(within(dialog).getByLabelText(/username/i));
await user.type(within(dialog).getByLabelText(/username/i), 'night-shift-updated');
await user.click(within(dialog).getByRole('button', { name: /save user/i }));
expect(screen.getByText(/night-shift-updated/i)).toBeInTheDocument();
expect(screen.queryByRole('dialog', { name: /edit user/i })).not.toBeInTheDocument();
});
it('does not overwrite dirty system settings when a websocket patch arrives', async () => {
const user = userEvent.setup();
render(<App />);