Changement sur le Readme
Ajout d'une partie spécifique pour des PNJ dans la partie campagne
This commit is contained in:
@@ -301,6 +301,46 @@ export async function getPageById(
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export interface SeededNpc {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export async function seedNpc(
|
||||
request: APIRequestContext,
|
||||
opts: { campaignId: string; name?: string; markdownContent?: string | null },
|
||||
): Promise<SeededNpc> {
|
||||
const name = opts.name ?? `E2E NPC ${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
||||
const res = await request.post('/api/npcs', {
|
||||
data: {
|
||||
campaignId: opts.campaignId,
|
||||
name,
|
||||
markdownContent: opts.markdownContent ?? null,
|
||||
},
|
||||
});
|
||||
expect(res.ok(), `POST /api/npcs -> ${res.status()}`).toBeTruthy();
|
||||
const n = await res.json();
|
||||
return { id: n.id, name };
|
||||
}
|
||||
|
||||
export async function getNpcById(
|
||||
request: APIRequestContext,
|
||||
npcId: string,
|
||||
): Promise<{ id: string; name: string; markdownContent: string | null; campaignId: string; order: number }> {
|
||||
const res = await request.get(`/api/npcs/${npcId}`);
|
||||
expect(res.ok(), `GET /api/npcs/${npcId} -> ${res.status()}`).toBeTruthy();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function getNpcsByCampaign(
|
||||
request: APIRequestContext,
|
||||
campaignId: string,
|
||||
): Promise<Array<{ id: string; name: string }>> {
|
||||
const res = await request.get(`/api/npcs/campaign/${campaignId}`);
|
||||
expect(res.ok(), `GET /api/npcs/campaign -> ${res.status()}`).toBeTruthy();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function getTemplateById(
|
||||
request: APIRequestContext,
|
||||
templateId: string,
|
||||
|
||||
75
web/e2e/tests/campaign/npc-create.spec.ts
Normal file
75
web/e2e/tests/campaign/npc-create.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
seedCampaign,
|
||||
deleteCampaign,
|
||||
getNpcsByCampaign,
|
||||
type SeededCampaign,
|
||||
} from '../../fixtures/api';
|
||||
|
||||
test.describe('NPC creation', () => {
|
||||
let campaign: SeededCampaign;
|
||||
|
||||
test.beforeEach(async ({ request }) => {
|
||||
campaign = await seedCampaign(request);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ request }) => {
|
||||
if (campaign?.id) await deleteCampaign(request, campaign.id);
|
||||
});
|
||||
|
||||
test('creates an NPC and redirects back to the campaign', async ({ page, request }) => {
|
||||
const npcName = `Borin le forgeron ${Date.now()}`;
|
||||
const markdown = '# Borin\n\n**Faction :** Clan Feuillefer\n\nNain barbu au regard perçant.';
|
||||
|
||||
await page.goto(`/campaigns/${campaign.id}/npcs/create`);
|
||||
await expect(page.getByRole('heading', { name: /Nouveau PNJ/i })).toBeVisible();
|
||||
|
||||
await page.getByLabel(/Nom du PNJ/i).fill(npcName);
|
||||
await page.getByLabel(/Fiche \(markdown\)/i).fill(markdown);
|
||||
|
||||
await page.getByRole('button', { name: /^Créer$/i }).click();
|
||||
|
||||
// Retour à la page campagne après création
|
||||
await expect(page).toHaveURL(new RegExp(`/campaigns/${campaign.id}$`));
|
||||
|
||||
// Persistance vérifiée via API
|
||||
const npcs = await getNpcsByCampaign(request, campaign.id);
|
||||
const created = npcs.find((n) => n.name === npcName);
|
||||
expect(created).toBeDefined();
|
||||
});
|
||||
|
||||
test('submit is disabled when name is empty', async ({ page }) => {
|
||||
await page.goto(`/campaigns/${campaign.id}/npcs/create`);
|
||||
|
||||
const submit = page.getByRole('button', { name: /^Créer$/i });
|
||||
await expect(submit).toBeDisabled();
|
||||
|
||||
await page.getByLabel(/Nom du PNJ/i).fill('Elara');
|
||||
await expect(submit).toBeEnabled();
|
||||
|
||||
await page.getByLabel(/Nom du PNJ/i).fill(' ');
|
||||
await expect(submit).toBeDisabled();
|
||||
});
|
||||
|
||||
test('NPC appears in the sidebar PNJ branch', async ({ page, request }) => {
|
||||
const npcName = `Sidebar test ${Date.now()}`;
|
||||
|
||||
await page.goto(`/campaigns/${campaign.id}/npcs/create`);
|
||||
await page.getByLabel(/Nom du PNJ/i).fill(npcName);
|
||||
await page.getByRole('button', { name: /^Créer$/i }).click();
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/campaigns/${campaign.id}$`));
|
||||
|
||||
// Le nœud "PNJ" doit apparaître dans la sidebar avec le nouveau PNJ.
|
||||
// On clique sur le nœud PNJ pour le déplier au cas où il serait fermé,
|
||||
// puis on vérifie que le PNJ est listé.
|
||||
const pnjNode = page.getByRole('button', { name: /^PNJ\b/ }).or(
|
||||
page.locator('.tree-item', { hasText: 'PNJ' }).first(),
|
||||
);
|
||||
await expect(pnjNode.first()).toBeVisible();
|
||||
|
||||
// Vérification fallback via API : la liste contient bien le PNJ créé.
|
||||
const npcs = await getNpcsByCampaign(request, campaign.id);
|
||||
expect(npcs.map((n) => n.name)).toContain(npcName);
|
||||
});
|
||||
});
|
||||
69
web/e2e/tests/campaign/npc-edit.spec.ts
Normal file
69
web/e2e/tests/campaign/npc-edit.spec.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
seedCampaign,
|
||||
seedNpc,
|
||||
deleteCampaign,
|
||||
getNpcById,
|
||||
type SeededCampaign,
|
||||
type SeededNpc,
|
||||
} from '../../fixtures/api';
|
||||
|
||||
test.describe('NPC edit', () => {
|
||||
let campaign: SeededCampaign;
|
||||
let npc: SeededNpc;
|
||||
|
||||
test.beforeEach(async ({ request }) => {
|
||||
campaign = await seedCampaign(request);
|
||||
npc = await seedNpc(request, {
|
||||
campaignId: campaign.id,
|
||||
markdownContent: '# Initial\n\nFiche de départ.',
|
||||
});
|
||||
});
|
||||
|
||||
test.afterEach(async ({ request }) => {
|
||||
if (campaign?.id) await deleteCampaign(request, campaign.id);
|
||||
});
|
||||
|
||||
test('edits name + markdown content and persists via API', async ({ page, request }) => {
|
||||
const newName = `${npc.name} (renommé)`;
|
||||
const newMarkdown = '# Borin réécrit\n\n**Statut :** Disparu\n\nDes traces dans la neige...';
|
||||
|
||||
await page.goto(`/campaigns/${campaign.id}/npcs/${npc.id}/edit`);
|
||||
|
||||
await expect(page.getByRole('heading', { name: /Éditer le PNJ/i })).toBeVisible();
|
||||
await expect(page.getByLabel(/Nom du PNJ/i)).toHaveValue(npc.name);
|
||||
|
||||
await page.getByLabel(/Nom du PNJ/i).fill(newName);
|
||||
await page.getByLabel(/Fiche \(markdown\)/i).fill(newMarkdown);
|
||||
|
||||
await page.getByRole('button', { name: /^Enregistrer$/i }).click();
|
||||
|
||||
// Retour à la campagne après save
|
||||
await expect(page).toHaveURL(new RegExp(`/campaigns/${campaign.id}$`));
|
||||
|
||||
const persisted = await getNpcById(request, npc.id);
|
||||
expect(persisted.name).toBe(newName);
|
||||
expect(persisted.markdownContent).toBe(newMarkdown);
|
||||
});
|
||||
|
||||
test('save button is disabled when name is cleared', async ({ page }) => {
|
||||
await page.goto(`/campaigns/${campaign.id}/npcs/${npc.id}/edit`);
|
||||
|
||||
const nameField = page.getByLabel(/Nom du PNJ/i);
|
||||
const saveBtn = page.getByRole('button', { name: /^Enregistrer$/i });
|
||||
|
||||
await expect(saveBtn).toBeEnabled();
|
||||
await nameField.fill('');
|
||||
await expect(saveBtn).toBeDisabled();
|
||||
await nameField.fill('OK');
|
||||
await expect(saveBtn).toBeEnabled();
|
||||
});
|
||||
|
||||
test('Assistant IA button is visible in edit mode', async ({ page }) => {
|
||||
// Vérifie l'intégration drawer chat IA — symétrique aux PJ.
|
||||
// Note : le drawer lui-même nécessite le Brain Python en route, donc
|
||||
// on ne teste que la présence du bouton trigger.
|
||||
await page.goto(`/campaigns/${campaign.id}/npcs/${npc.id}/edit`);
|
||||
await expect(page.getByRole('button', { name: /Assistant IA/i })).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user