feat(domain,application,infrastructure,backend,web-server,app-tauri,frontend): lot utilisateur #325 #326 — icônes projet avec watermark CLI custom et localisation FR/EN (QA verte)
Lot en un seul commit: les deux features partagent les mêmes contrats (domain/settings.rs, DTO, commandes Tauri/HTTP, web-server) et les mêmes surfaces UI — la séparation par fichiers était impossible sans chirurgie de hunks risquant des états intermédiaires non compilables (convention lot des batches #310-#313 et #315). #325 — icône configurable par projet, watermark CLI custom: - domaine: ProjectIconSet (devicon, simple-icons, lucide, idea), ProjectIconRef validé (nom ASCII 1..=64, -_. internes), champ icon optionnel sur ProjectSettings; sérialisation camelCase et try_from strict - application: UpdateProjectSettings.execute accepte un patch theme_color OU icon (absent = inchangé, null = défaut produit), tests in-dependants avec store mémoire - backend/app-tauri/web-server: ProjectIconRefDto, UpdateProjectSettingsRequestDto avec icon optionnel (désérialisation additive), commandes update_project_settings alignées Tauri/HTTP, tests DTO et HTTP (défaut null, set, clear, nom invalide rejeté) - frontend: catalogue projectIcons (recherche EN), ProjectIconPicker en grille multi-lignes dans Paramètres projet, affichage de l'icône dans ProjectTabs et watermark grand format à faible opacité en fond de CLI custom; ports ProjectSettings.icon, Partial<ProjectSettings>, adapters tauri/mock/http et tests alignés #326 — localisation FR/EN et traduction des textes IdeA: - domaine: IdeaLanguage (fr | en), champ language optionnel sur IdeaUserSettings (absence = défaut produit, série additive validée) - backend/web-server: UserSettingsDto.language, update_user_settings accepte language, tests HTTP alignés; fixtures structured_launch_d3 mises à jour - frontend: i18n/ (catalogue FR/EN, I18nProvider, scan anti-régression des sources visibles), GeneralSettings avec sélecteur de langue dans les paramètres, setupI18n dans vitest; migration de l'ensemble des textes hardcodés vers le catalogue sur toutes les surfaces (agents, layout, projets, tickets, sprints, web, plugins, permissions, git, templates, skills, mémoire, model servers, embedder, first-run, devices, scheduled tasks, workstate, dialogs), FR conservé par défaut; recherche d'icônes #325 en anglais conformément au ticket QA verte sur feature/batch-325-326-project-icons-localisation. Aucun push.
This commit is contained in:
@ -55,7 +55,7 @@ async function waitForMemoryIdle() {
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
(
|
||||
screen.getByRole("button", { name: "create memory" }) as HTMLButtonElement
|
||||
screen.getByRole("button", { name: "créer une mémoire" }) as HTMLButtonElement
|
||||
).disabled,
|
||||
).toBe(false);
|
||||
});
|
||||
@ -69,21 +69,21 @@ async function createMemory(
|
||||
type: "user" | "feedback" | "project" | "reference" = "project",
|
||||
) {
|
||||
await waitForMemoryIdle();
|
||||
fireEvent.click(screen.getByRole("button", { name: "create memory" }));
|
||||
await waitFor(() => expect(screen.getByLabelText("memory name")).toBeTruthy());
|
||||
fireEvent.change(screen.getByLabelText("memory name"), {
|
||||
fireEvent.click(screen.getByRole("button", { name: "créer une mémoire" }));
|
||||
await waitFor(() => expect(screen.getByLabelText("nom de la mémoire")).toBeTruthy());
|
||||
fireEvent.change(screen.getByLabelText("nom de la mémoire"), {
|
||||
target: { value: name },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText("memory description"), {
|
||||
fireEvent.change(screen.getByLabelText("description de la mémoire"), {
|
||||
target: { value: description },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText("memory type"), {
|
||||
fireEvent.change(screen.getByLabelText("type de mémoire"), {
|
||||
target: { value: type },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText("memory content"), {
|
||||
fireEvent.change(screen.getByLabelText("contenu de la mémoire"), {
|
||||
target: { value: content },
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "Save note" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Enregistrer la note" }));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -94,7 +94,7 @@ describe("MemoryPanel (with MockMemoryGateway)", () => {
|
||||
it("shows the empty state initially", async () => {
|
||||
renderMemoryPanel();
|
||||
await waitForMemoryIdle();
|
||||
expect(screen.getByText("No memory notes yet.")).toBeTruthy();
|
||||
expect(screen.getByText("Aucune note de mémoire.")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders the index (title + hook + type) on mount", async () => {
|
||||
@ -141,27 +141,27 @@ describe("MemoryPanel (with MockMemoryGateway)", () => {
|
||||
it("Save is disabled until description and content are filled", async () => {
|
||||
renderMemoryPanel();
|
||||
await waitForMemoryIdle();
|
||||
fireEvent.click(screen.getByRole("button", { name: "create memory" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "créer une mémoire" }));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole("button", { name: "Save note" })).toBeTruthy(),
|
||||
expect(screen.getByRole("button", { name: "Enregistrer la note" })).toBeTruthy(),
|
||||
);
|
||||
const save = screen.getByRole("button", {
|
||||
name: "Save note",
|
||||
name: "Enregistrer la note",
|
||||
}) as HTMLButtonElement;
|
||||
expect(save.disabled).toBe(true);
|
||||
|
||||
// Filling everything enables Save.
|
||||
fireEvent.change(screen.getByLabelText("memory name"), {
|
||||
fireEvent.change(screen.getByLabelText("nom de la mémoire"), {
|
||||
target: { value: "X" },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText("memory description"), {
|
||||
fireEvent.change(screen.getByLabelText("description de la mémoire"), {
|
||||
target: { value: "h" },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText("memory content"), {
|
||||
fireEvent.change(screen.getByLabelText("contenu de la mémoire"), {
|
||||
target: { value: "c" },
|
||||
});
|
||||
expect(
|
||||
(screen.getByRole("button", { name: "Save note" }) as HTMLButtonElement)
|
||||
(screen.getByRole("button", { name: "Enregistrer la note" }) as HTMLButtonElement)
|
||||
.disabled,
|
||||
).toBe(false);
|
||||
});
|
||||
@ -172,24 +172,24 @@ describe("MemoryPanel (with MockMemoryGateway)", () => {
|
||||
await screen.findByText("Editable Note");
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "edit memory Editable Note" }),
|
||||
screen.getByRole("button", { name: "modifier la mémoire Editable Note" }),
|
||||
);
|
||||
|
||||
// Slug shown read-only — there's no editable "memory name" input in edit-mode.
|
||||
await waitFor(() => expect(screen.getByText("editable-note")).toBeTruthy());
|
||||
expect(screen.queryByLabelText("memory name")).toBeNull();
|
||||
expect(screen.queryByLabelText("nom de la mémoire")).toBeNull();
|
||||
|
||||
// Description / type / content are editable.
|
||||
fireEvent.change(screen.getByLabelText("memory description"), {
|
||||
fireEvent.change(screen.getByLabelText("description de la mémoire"), {
|
||||
target: { value: "new hook" },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText("memory type"), {
|
||||
fireEvent.change(screen.getByLabelText("type de mémoire"), {
|
||||
target: { value: "feedback" },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText("memory content"), {
|
||||
fireEvent.change(screen.getByLabelText("contenu de la mémoire"), {
|
||||
target: { value: "new body" },
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "Save note" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Enregistrer la note" }));
|
||||
|
||||
await waitFor(async () => {
|
||||
const note = await memory.getMemory(PROJECT_ID, "editable-note");
|
||||
@ -205,7 +205,7 @@ describe("MemoryPanel (with MockMemoryGateway)", () => {
|
||||
await screen.findByText("To Delete");
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "delete memory To Delete" }),
|
||||
screen.getByRole("button", { name: "supprimer la mémoire To Delete" }),
|
||||
);
|
||||
await waitFor(() => expect(screen.queryByText("To Delete")).toBeNull());
|
||||
});
|
||||
@ -231,7 +231,7 @@ describe("MemoryPanel (with MockMemoryGateway)", () => {
|
||||
renderMemoryPanel(memory);
|
||||
|
||||
fireEvent.click(
|
||||
await screen.findByRole("button", { name: "edit memory Source Note" }),
|
||||
await screen.findByRole("button", { name: "modifier la mémoire Source Note" }),
|
||||
);
|
||||
|
||||
// The resolved link to the existing note appears; the broken one does not.
|
||||
|
||||
Reference in New Issue
Block a user