Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions orchestrator/app/components/EnvironmentsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ function cancelEdit() {
editingId.value = null;
}

watch(open, (isOpen) => {
if (isOpen) cancelEdit();
});

async function handleSave(data: Partial<EnvironmentInfo>) {
if (editingId.value) {
await updateEnvironment(editingId.value, data);
Expand Down
4 changes: 4 additions & 0 deletions orchestrator/app/components/ListEditorModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ function cancelEdit() {
viewing.value = null;
}

watch(open, (isOpen) => {
if (isOpen) cancelEdit();
});

async function handleSave() {
if (!editForm.name.trim() || !editForm.content.trim()) return;
const body = { name: editForm.name, content: editForm.content };
Expand Down
5 changes: 4 additions & 1 deletion orchestrator/app/components/UsersModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ async function refresh() {
}

watch(open, (v) => {
if (v) refresh();
if (v) {
cancelCreate();
refresh();
}
});

function startCreate() {
Expand Down
49 changes: 49 additions & 0 deletions tests/ui/environments-modal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,55 @@ test.describe('Environments Modal', () => {
await expect(page.locator('[role="dialog"]')).toBeHidden({ timeout: 10_000 });
});

for (const closeMethod of ['Close', 'Escape', 'outside click'] as const) {
test(`reopening shows the list after dismissing an editor with ${closeMethod}`, async ({ page, request }) => {
const api = new ApiClient(request);
const envName = `UIEnvReopen-${closeMethod}-${Date.now()}`;
const { body: created } = await api.createEnvironment({ name: envName });

try {
await goToDashboard(page);
await openEnvironmentsModal(page);
const dialog = page.getByRole('dialog');
const nameInput = dialog.locator('input[placeholder="My environment"]');
const envRow = dialog.locator('.rounded-lg').filter({ hasText: envName });

for (const mode of ['edit', 'create', 'view'] as const) {
await test.step(`dismiss and reopen from ${mode} mode`, async () => {
if (mode === 'edit') {
await envRow.getByRole('button', { name: 'Edit', exact: true }).click();
await expect(nameInput).toHaveValue(envName);
} else if (mode === 'create') {
await dialog.getByRole('button', { name: 'New', exact: true }).click();
await expect(nameInput).toHaveValue('');
} else {
await dialog.getByRole('button', { name: 'View', exact: true }).first().click();
await expect(nameInput).toBeDisabled();
}

if (mode !== 'view') await nameInput.fill('Unsaved draft');

if (closeMethod === 'Close') {
await dialog.getByRole('button', { name: 'Close', exact: true }).first().click();
} else if (closeMethod === 'Escape') {
await page.keyboard.press('Escape');
} else {
await page.mouse.click(10, 10);
}
await expect(dialog).toBeHidden();

await openEnvironmentsModal(page);
await expect(dialog.getByRole('button', { name: 'New', exact: true })).toBeVisible();
await expect(envRow).toBeVisible();
await expect(nameInput).toBeHidden();
});
}
} finally {
await api.deleteEnvironment(created.id);
}
});
}

test('shows pre-created environments', async ({ page, request }) => {
// Create an environment via API first
const api = new ApiClient(request);
Expand Down
98 changes: 98 additions & 0 deletions tests/ui/list-editor-modal-reopen.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { test, expect } from '@playwright/test';
import { goToDashboard } from '../helpers/ui-helpers';
import { ApiClient } from '../helpers/api-client';

const resources = [
{
title: 'Capabilities',
namePlaceholder: 'Capability name',
initialContent: '',
content: '---\ndescription: Modal reopen test\n---\n\nOriginal content.',
create: 'createCapability',
remove: 'deleteCapability',
},
{
title: 'Instructions',
namePlaceholder: 'Entry name',
initialContent: '',
content: '# Original\n\nOriginal content.',
create: 'createInstruction',
remove: 'deleteInstruction',
},
{
title: 'Init Scripts',
namePlaceholder: 'Script name',
initialContent: '#!/bin/bash\n',
content: '#!/bin/bash\necho original',
create: 'createInitScript',
remove: 'deleteInitScript',
},
] as const;

for (const resource of resources) {
test.describe(`${resource.title} modal reopening`, () => {
for (const closeMethod of ['Close', 'Escape', 'outside click'] as const) {
test(`reopening shows the list after dismissing an editor with ${closeMethod}`, async ({ page, request }) => {
const api = new ApiClient(request);
const name = `UIReopen-${resource.title}-${closeMethod}-${Date.now()}`;
const { body: created } = await api[resource.create]({ name, content: resource.content });

try {
await goToDashboard(page);
const openButton = page.getByRole('button', { name: resource.title, exact: true });
await openButton.click();
const dialog = page.getByRole('dialog');
const nameInput = dialog.getByPlaceholder(resource.namePlaceholder, { exact: true });
const contentInput = dialog.locator('textarea');
const itemRow = dialog.locator('.rounded-lg').filter({ hasText: name });

for (const mode of ['edit', 'create', 'view'] as const) {
await test.step(`dismiss and reopen from ${mode} mode`, async () => {
if (mode === 'edit') {
await itemRow.getByRole('button', { name: 'Edit', exact: true }).click();
await expect(nameInput).toHaveValue(name);
await expect(contentInput).toHaveValue(resource.content);
} else if (mode === 'create') {
await dialog.getByRole('button', { name: 'New', exact: true }).click();
await expect(nameInput).toHaveValue('');
await expect(contentInput).toHaveValue(resource.initialContent);
} else {
await dialog.getByRole('button', { name: 'View', exact: true }).first().click();
await expect(nameInput).toBeDisabled();
await expect(contentInput).toBeDisabled();
}

if (mode !== 'view') {
await nameInput.fill('Unsaved draft');
await contentInput.fill('Unsaved content');
}

if (closeMethod === 'Close') {
await dialog.getByRole('button', { name: 'Close', exact: true }).first().click();
} else if (closeMethod === 'Escape') {
await page.keyboard.press('Escape');
} else {
await page.mouse.click(10, 10);
}
await expect(dialog).toBeHidden();

await openButton.click();
await expect(dialog.getByRole('button', { name: 'New', exact: true })).toBeVisible();
await expect(itemRow).toBeVisible();
await expect(nameInput).toBeHidden();
await expect(contentInput).toBeHidden();
});
}

await dialog.getByRole('button', { name: 'New', exact: true }).click();
await expect(nameInput).toBeEnabled();
await expect(nameInput).toHaveValue('');
await expect(contentInput).toBeEnabled();
await expect(contentInput).toHaveValue(resource.initialContent);
} finally {
await api[resource.remove](created.id);
}
});
}
});
}
30 changes: 30 additions & 0 deletions tests/ui/users-modal.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,40 @@
import { test, expect } from '@playwright/test';
import { createTestUser, deleteTestUser, type CreatedUser } from '../helpers/test-users';
import { goToDashboard, selectSidebarTab } from '../helpers/ui-helpers';

/**
* Admin-only Users modal. Runs with the global admin storage state (default).
*/
test.describe('Users modal (admin)', () => {
for (const closeMethod of ['Close', 'Escape', 'outside click'] as const) {
test(`reopening shows the list after dismissing the new user form with ${closeMethod}`, async ({ page }) => {
await goToDashboard(page);
await selectSidebarTab(page, 'System');
const openButton = page.getByRole('button', { name: 'Users', exact: true });
await openButton.click();
const dialog = page.getByRole('dialog');
await dialog.getByRole('button', { name: 'New', exact: true }).click();
await dialog.locator('input[type="email"]').fill('unsaved@example.test');

if (closeMethod === 'Close') {
await dialog.getByRole('button', { name: 'Close', exact: true }).click();
} else if (closeMethod === 'Escape') {
await page.keyboard.press('Escape');
} else {
await page.mouse.click(10, 10);
}
await expect(dialog).toBeHidden();

await openButton.click();
await expect(dialog.getByRole('button', { name: 'New', exact: true })).toBeVisible();
await expect(dialog.locator('[data-user-row]').first()).toBeVisible();
await expect(dialog.locator('input[type="email"]')).toBeHidden();

await dialog.getByRole('button', { name: 'New', exact: true }).click();
await expect(dialog.locator('input[type="email"]')).toHaveValue('');
});
}

test('admin opens Users modal from the System tab', async ({ page }) => {
await page.goto('/');
await page.waitForSelector('h1:has-text("Agentor")', { timeout: 15_000 });
Expand Down