fix(m6): mission detail page can now edit metadata, append scenarios, edit members
The M6 SPA shipped the create wizard but the detail page was read-only —
even though the backend already exposed PUT /missions/{id}, POST
/missions/{id}/scenarios, and PUT /missions/{id}/members. So once a
mission was created you couldn't fix a typo in the client name, add a
scenario you forgot, or change member assignments without curl.
Added three modals on the detail page, gated by `is_admin ||
mission.update`:
- Edit metadata (header button, 3xl modal): name + client + dates +
markdown description, same validation as the wizard step 1.
- Add scenarios (Tests tab): scenario picker matching wizard step 2,
calls POST /missions/{id}/scenarios which appends snapshots at
current_max_position + 1.
- Edit members (Members tab): roster + red/blue toggles, calls
PUT /missions/{id}/members (full-set replace), pre-populated with
the current member set.
The detail page now imports useAuth so `canEdit` is computed once and
shared between the three buttons.
E2E: new "detail page edits metadata, appends scenarios, edits members"
spec exercises the three modals end-to-end. M6 e2e count is now 6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -292,6 +292,90 @@ test.describe('M6 — Missions', () => {
|
||||
await expect(page.getByText('spa-wizard-t3')).toBeVisible();
|
||||
});
|
||||
|
||||
test('SPA — detail page edits metadata, appends scenarios, edits members', async ({
|
||||
page,
|
||||
request,
|
||||
}) => {
|
||||
const auth = await adminAuth(request);
|
||||
|
||||
// Pre-seed: one mission with one initial scenario; a second scenario to
|
||||
// append; and a second user we can assign as a member from the SPA.
|
||||
const initialTestId = await makeTest(request, auth, 'spa-edit-initial-t');
|
||||
const initialScenarioId = await makeScenario(
|
||||
request,
|
||||
auth,
|
||||
'spa-edit-initial-scenario',
|
||||
[initialTestId],
|
||||
);
|
||||
const extraTestId = await makeTest(request, auth, 'spa-edit-appended-t');
|
||||
const extraScenarioId = await makeScenario(
|
||||
request,
|
||||
auth,
|
||||
'spa-edit-appended-scenario',
|
||||
[extraTestId],
|
||||
);
|
||||
const mission = await request
|
||||
.post('/api/v1/missions', {
|
||||
headers: auth,
|
||||
data: {
|
||||
name: 'spa-edit-target',
|
||||
client_target: 'Initial Co.',
|
||||
scenario_template_ids: [initialScenarioId],
|
||||
},
|
||||
})
|
||||
.then((r) => r.json());
|
||||
|
||||
// A second user the admin can add as a member via the modal.
|
||||
const teammateEmail = `spa-edit-mate-${crypto.randomUUID().slice(0, 8)}@metamorph.local`;
|
||||
const inv = await request
|
||||
.post('/api/v1/invitations', {
|
||||
headers: auth,
|
||||
data: { email_hint: teammateEmail },
|
||||
})
|
||||
.then((r) => r.json());
|
||||
await request.post(`/api/v1/invitations/accept/${inv.token}`, {
|
||||
data: { email: teammateEmail, password: 'MatePass1234!' },
|
||||
});
|
||||
|
||||
await loginViaSpa(page, ADMIN_EMAIL, ADMIN_PASSWORD);
|
||||
await page.goto(`/missions/${mission.id}`);
|
||||
await expect(page.getByText('Initial Co.')).toBeVisible();
|
||||
|
||||
// --- Edit metadata --------------------------------------------------
|
||||
await page.getByTestId('mission-edit-meta').click();
|
||||
const metaModal = page.getByTestId('mission-edit-meta-modal');
|
||||
await expect(metaModal).toBeVisible();
|
||||
await metaModal.getByTestId('meta-edit-client').fill('Renamed Co.');
|
||||
await metaModal.getByTestId('meta-edit-save').click();
|
||||
await expect(metaModal).toBeHidden();
|
||||
await expect(page.getByText('Renamed Co.')).toBeVisible();
|
||||
|
||||
// --- Append a scenario ---------------------------------------------
|
||||
await page.getByTestId('mission-add-scenarios').click();
|
||||
const addModal = page.getByTestId('mission-add-scenarios-modal');
|
||||
await expect(addModal).toBeVisible();
|
||||
await addModal.getByTestId(`add-scenario-toggle-${extraScenarioId}`).click();
|
||||
await addModal.getByTestId('add-scenarios-save').click();
|
||||
await expect(addModal).toBeHidden();
|
||||
// Both scenarios now visible in the Tests tab
|
||||
await expect(page.getByText('spa-edit-initial-scenario')).toBeVisible();
|
||||
await expect(page.getByText('spa-edit-appended-scenario')).toBeVisible();
|
||||
await expect(page.getByText('spa-edit-appended-t')).toBeVisible();
|
||||
|
||||
// --- Edit members ---------------------------------------------------
|
||||
await page.getByTestId('mission-tab-members').click();
|
||||
await page.getByTestId('mission-edit-members').click();
|
||||
const memModal = page.getByTestId('mission-edit-members-modal');
|
||||
await expect(memModal).toBeVisible();
|
||||
// The roster row test-ids encode the new user's id; we don't know it here
|
||||
// but the email is unique, so locate the row by email text and toggle red.
|
||||
const teammateRow = memModal.getByText(teammateEmail).locator('..').locator('..');
|
||||
await teammateRow.getByRole('button', { name: /red/i }).click();
|
||||
await memModal.getByTestId('edit-members-save').click();
|
||||
await expect(memModal).toBeHidden();
|
||||
await expect(page.getByText(teammateEmail)).toBeVisible();
|
||||
});
|
||||
|
||||
test('SPA — list page filters by status', async ({ page, request }) => {
|
||||
const auth = await adminAuth(request);
|
||||
// Seed two missions with distinct statuses.
|
||||
|
||||
Reference in New Issue
Block a user