Correction de plusieurs anomalies : problème de switch entre 2 templates (par exemple si on était sur un template 1 et qu'on voulait passer directement au 2, ce dernier ne chargeait pas) ; correction du soucis d'apparition de la sidebar à gauche qui disparaissait sans explication ; problème de redirection : lorsqu'on terminait de créer un PJ / PNJ ; on arrivait sur l'accueil de la campagne au lieu de voir le résultat de la création. Problème de redirection également lors du clique sur un PNJ / PJ sur le coté : on arrivait sur l'édition au lieu de la présentation. Correction de la première lettre stylisée : tout est au même style comme ça plus de probleme de lecture. Nouveautées : stylisation des modales (notamment suppression, warning.....) avec en prime l'ajout d'un warning lors du changement de système pour avertir que les fiches persos ne sont pas conservées. Ajout d'une option pour créer un game system directement à la création d'une campagne afin de faciliter la mise en place de cette dernière. Ajout d'un bouton pour créer un nouveau template directement lorsqu'on créer une page : ça permet de créer un template et de revenir sur la page qu'on était en train de créer sans perdre le titre. Passage en bêta 0.8.4
410 lines
13 KiB
TypeScript
410 lines
13 KiB
TypeScript
import { APIRequestContext, expect } from '@playwright/test';
|
|
|
|
export interface SeededLore {
|
|
id: string;
|
|
name: string;
|
|
rootFolderId: string;
|
|
rootFolderName: string;
|
|
}
|
|
|
|
/**
|
|
* Seed un Lore + un dossier racine via l'API backend.
|
|
* Les noms sont uniques (timestamp + random) pour éviter les collisions en parallèle.
|
|
*/
|
|
export async function seedLoreWithFolder(request: APIRequestContext): Promise<SeededLore> {
|
|
const suffix = `${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
const loreName = `E2E Lore ${suffix}`;
|
|
const folderName = `E2E Folder ${suffix}`;
|
|
|
|
const loreRes = await request.post('/api/lores', {
|
|
data: { name: loreName, description: 'Seeded by Playwright' },
|
|
});
|
|
expect(loreRes.ok(), `POST /api/lores -> ${loreRes.status()}`).toBeTruthy();
|
|
const lore = await loreRes.json();
|
|
|
|
const folderRes = await request.post('/api/lore-nodes', {
|
|
data: { loreId: lore.id, name: folderName, icon: 'folder', description: '' },
|
|
});
|
|
expect(folderRes.ok(), `POST /api/lore-nodes -> ${folderRes.status()}`).toBeTruthy();
|
|
const folder = await folderRes.json();
|
|
|
|
return { id: lore.id, name: loreName, rootFolderId: folder.id, rootFolderName: folderName };
|
|
}
|
|
|
|
/** Cleanup best-effort — n'échoue pas si déjà supprimé. */
|
|
export async function deleteLore(request: APIRequestContext, loreId: string): Promise<void> {
|
|
await request.delete(`/api/lores/${loreId}`).catch(() => undefined);
|
|
}
|
|
|
|
export async function getLoreById(
|
|
request: APIRequestContext,
|
|
loreId: string,
|
|
): Promise<{ id: string; name: string; description: string }> {
|
|
const res = await request.get(`/api/lores/${loreId}`);
|
|
expect(res.ok(), `GET /api/lores/${loreId} -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|
|
|
|
export async function getArcsForCampaign(
|
|
request: APIRequestContext,
|
|
campaignId: string,
|
|
): Promise<Array<{ id: string; name: string; campaignId: string }>> {
|
|
const res = await request.get(`/api/arcs?campaignId=${campaignId}`);
|
|
expect(res.ok(), `GET /api/arcs -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|
|
|
|
export async function getChaptersForArc(
|
|
request: APIRequestContext,
|
|
arcId: string,
|
|
): Promise<Array<{ id: string; name: string; arcId: string }>> {
|
|
const res = await request.get(`/api/chapters?arcId=${arcId}`);
|
|
expect(res.ok(), `GET /api/chapters -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|
|
|
|
export async function getScenesForChapter(
|
|
request: APIRequestContext,
|
|
chapterId: string,
|
|
): Promise<Array<{ id: string; name: string; chapterId: string }>> {
|
|
const res = await request.get(`/api/scenes?chapterId=${chapterId}`);
|
|
expect(res.ok(), `GET /api/scenes -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|
|
|
|
export async function getTemplatesForLore(
|
|
request: APIRequestContext,
|
|
loreId: string,
|
|
): Promise<Array<{ id: string; name: string }>> {
|
|
const res = await request.get(`/api/templates?loreId=${loreId}`);
|
|
expect(res.ok(), `GET /api/templates -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|
|
|
|
export interface SeededTemplate {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
export async function seedTemplate(
|
|
request: APIRequestContext,
|
|
opts: { loreId: string; defaultNodeId: string; name?: string; fieldNames?: string[] },
|
|
): Promise<SeededTemplate> {
|
|
const templateName = opts.name ?? `E2E Template ${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
const fields = (opts.fieldNames ?? ['Nom', 'Description']).map((name) => ({ name, type: 'TEXT' }));
|
|
|
|
const res = await request.post('/api/templates', {
|
|
data: {
|
|
loreId: opts.loreId,
|
|
name: templateName,
|
|
description: 'Seeded by Playwright',
|
|
defaultNodeId: opts.defaultNodeId,
|
|
fields,
|
|
},
|
|
});
|
|
expect(res.ok(), `POST /api/templates -> ${res.status()}`).toBeTruthy();
|
|
const tpl = await res.json();
|
|
return { id: tpl.id, name: templateName };
|
|
}
|
|
|
|
export async function deleteCampaign(request: APIRequestContext, campaignId: string): Promise<void> {
|
|
await request.delete(`/api/campaigns/${campaignId}`).catch(() => undefined);
|
|
}
|
|
|
|
export interface SeededCampaign {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
export async function seedCampaign(
|
|
request: APIRequestContext,
|
|
opts: { name?: string; loreId?: string | null; playerCount?: number } = {},
|
|
): Promise<SeededCampaign> {
|
|
const name = opts.name ?? `E2E Campaign ${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
const res = await request.post('/api/campaigns', {
|
|
data: {
|
|
name,
|
|
description: 'Seeded by Playwright',
|
|
playerCount: opts.playerCount ?? 4,
|
|
loreId: opts.loreId ?? null,
|
|
gameSystemId: null,
|
|
},
|
|
});
|
|
expect(res.ok(), `POST /api/campaigns -> ${res.status()}`).toBeTruthy();
|
|
const c = await res.json();
|
|
return { id: c.id, name };
|
|
}
|
|
|
|
export interface SeededArc {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
export async function seedArc(
|
|
request: APIRequestContext,
|
|
opts: { campaignId: string; name?: string; order?: number },
|
|
): Promise<SeededArc> {
|
|
const name = opts.name ?? `E2E Arc ${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
const res = await request.post('/api/arcs', {
|
|
data: {
|
|
campaignId: opts.campaignId,
|
|
name,
|
|
description: '',
|
|
order: opts.order ?? 1,
|
|
},
|
|
});
|
|
expect(res.ok(), `POST /api/arcs -> ${res.status()}`).toBeTruthy();
|
|
const a = await res.json();
|
|
return { id: a.id, name };
|
|
}
|
|
|
|
export interface SeededChapter {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
export async function seedChapter(
|
|
request: APIRequestContext,
|
|
opts: { arcId: string; name?: string; order?: number },
|
|
): Promise<SeededChapter> {
|
|
const name = opts.name ?? `E2E Chapter ${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
const res = await request.post('/api/chapters', {
|
|
data: { arcId: opts.arcId, name, description: '', order: opts.order ?? 1 },
|
|
});
|
|
expect(res.ok(), `POST /api/chapters -> ${res.status()}`).toBeTruthy();
|
|
const c = await res.json();
|
|
return { id: c.id, name };
|
|
}
|
|
|
|
export async function getChapterById(
|
|
request: APIRequestContext,
|
|
chapterId: string,
|
|
): Promise<{
|
|
id: string;
|
|
name: string;
|
|
description?: string;
|
|
gmNotes?: string | null;
|
|
playerObjectives?: string | null;
|
|
narrativeStakes?: string | null;
|
|
}> {
|
|
const res = await request.get(`/api/chapters/${chapterId}`);
|
|
expect(res.ok(), `GET /api/chapters/${chapterId} -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|
|
|
|
export interface SeededScene {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
export async function seedScene(
|
|
request: APIRequestContext,
|
|
opts: { chapterId: string; name?: string; order?: number },
|
|
): Promise<SeededScene> {
|
|
const name = opts.name ?? `E2E Scene ${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
const res = await request.post('/api/scenes', {
|
|
data: { chapterId: opts.chapterId, name, description: '', order: opts.order ?? 1 },
|
|
});
|
|
expect(res.ok(), `POST /api/scenes -> ${res.status()}`).toBeTruthy();
|
|
const s = await res.json();
|
|
return { id: s.id, name };
|
|
}
|
|
|
|
export async function getSceneById(
|
|
request: APIRequestContext,
|
|
sceneId: string,
|
|
): Promise<{
|
|
id: string;
|
|
name: string;
|
|
description?: string;
|
|
location?: string | null;
|
|
timing?: string | null;
|
|
atmosphere?: string | null;
|
|
playerNarration?: string | null;
|
|
gmSecretNotes?: string | null;
|
|
choicesConsequences?: string | null;
|
|
combatDifficulty?: string | null;
|
|
enemies?: string | null;
|
|
branches?: Array<{ label: string; targetSceneId: string; condition?: string }>;
|
|
}> {
|
|
const res = await request.get(`/api/scenes/${sceneId}`);
|
|
expect(res.ok(), `GET /api/scenes/${sceneId} -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|
|
|
|
export async function getArcById(
|
|
request: APIRequestContext,
|
|
arcId: string,
|
|
): Promise<{
|
|
id: string;
|
|
name: string;
|
|
description?: string;
|
|
themes?: string | null;
|
|
stakes?: string | null;
|
|
gmNotes?: string | null;
|
|
rewards?: string | null;
|
|
resolution?: string | null;
|
|
}> {
|
|
const res = await request.get(`/api/arcs/${arcId}`);
|
|
expect(res.ok(), `GET /api/arcs/${arcId} -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|
|
|
|
export async function getCampaigns(
|
|
request: APIRequestContext,
|
|
): Promise<Array<{ id: string; name: string; loreId: string | null }>> {
|
|
const res = await request.get('/api/campaigns');
|
|
expect(res.ok(), `GET /api/campaigns -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|
|
|
|
export async function getPagesForLore(
|
|
request: APIRequestContext,
|
|
loreId: string,
|
|
): Promise<Array<{ id: string; title: string; nodeId: string; templateId: string }>> {
|
|
const res = await request.get(`/api/pages?loreId=${loreId}`);
|
|
expect(res.ok(), `GET /api/pages -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|
|
|
|
export interface SeededPage {
|
|
id: string;
|
|
title: string;
|
|
}
|
|
|
|
export async function seedPage(
|
|
request: APIRequestContext,
|
|
opts: { loreId: string; nodeId: string; templateId: string; title?: string },
|
|
): Promise<SeededPage> {
|
|
const title = opts.title ?? `E2E Page ${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
const res = await request.post('/api/pages', {
|
|
data: { loreId: opts.loreId, nodeId: opts.nodeId, templateId: opts.templateId, title },
|
|
});
|
|
expect(res.ok(), `POST /api/pages -> ${res.status()}`).toBeTruthy();
|
|
const page = await res.json();
|
|
return { id: page.id, title };
|
|
}
|
|
|
|
export async function getPageById(
|
|
request: APIRequestContext,
|
|
pageId: string,
|
|
): Promise<{
|
|
id: string;
|
|
title: string;
|
|
nodeId: string;
|
|
values?: Record<string, string>;
|
|
tags?: string[];
|
|
notes?: string;
|
|
}> {
|
|
const res = await request.get(`/api/pages/${pageId}`);
|
|
expect(res.ok(), `GET /api/pages/${pageId} -> ${res.status()}`).toBeTruthy();
|
|
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,
|
|
): Promise<{
|
|
id: string;
|
|
name: string;
|
|
description?: string;
|
|
defaultNodeId?: string | null;
|
|
fields: Array<{ name: string; type: string }>;
|
|
}> {
|
|
const res = await request.get(`/api/templates/${templateId}`);
|
|
expect(res.ok(), `GET /api/templates/${templateId} -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|
|
|
|
// ─────────────── GameSystem ───────────────
|
|
|
|
export interface SeededGameSystem {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
export async function seedGameSystem(
|
|
request: APIRequestContext,
|
|
opts: { name?: string; description?: string; author?: string; rulesMarkdown?: string } = {},
|
|
): Promise<SeededGameSystem> {
|
|
const name = opts.name ?? `E2E GameSystem ${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
const res = await request.post('/api/game-systems', {
|
|
data: {
|
|
name,
|
|
description: opts.description ?? null,
|
|
author: opts.author ?? null,
|
|
rulesMarkdown: opts.rulesMarkdown ?? null,
|
|
characterTemplate: [],
|
|
npcTemplate: [],
|
|
isPublic: false,
|
|
},
|
|
});
|
|
expect(res.ok(), `POST /api/game-systems -> ${res.status()}`).toBeTruthy();
|
|
const gs = await res.json();
|
|
return { id: gs.id, name };
|
|
}
|
|
|
|
export async function deleteGameSystem(
|
|
request: APIRequestContext,
|
|
id: string,
|
|
): Promise<void> {
|
|
// Best-effort : ignore 404 si déjà supprimé par le test (ex: delete spec).
|
|
await request.delete(`/api/game-systems/${id}`);
|
|
}
|
|
|
|
export async function getGameSystemById(
|
|
request: APIRequestContext,
|
|
id: string,
|
|
): Promise<{
|
|
id: string;
|
|
name: string;
|
|
description: string | null;
|
|
author: string | null;
|
|
rulesMarkdown: string | null;
|
|
isPublic: boolean;
|
|
}> {
|
|
const res = await request.get(`/api/game-systems/${id}`);
|
|
expect(res.ok(), `GET /api/game-systems/${id} -> ${res.status()}`).toBeTruthy();
|
|
return res.json();
|
|
}
|