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:
@ -124,7 +124,7 @@ describe("AgentLimitBadge (render)", () => {
|
||||
const expected = `limité jusqu'à ${formatResetTime(at)}`;
|
||||
expect(screen.getByText(expected)).toBeTruthy();
|
||||
// No countdown / cancel button without an armed resume.
|
||||
expect(screen.queryByLabelText("cancel resume")).toBeNull();
|
||||
expect(screen.queryByLabelText("annuler la reprise")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows plain 'limité' when no reset time is known", () => {
|
||||
@ -145,8 +145,8 @@ describe("AgentLimitBadge (render)", () => {
|
||||
expect(screen.getByText("limité")).toBeTruthy();
|
||||
expect(screen.getByText(/heure inconnue/)).toBeTruthy();
|
||||
// The human-net form: a resume-time input + a "schedule resume" button.
|
||||
expect(screen.getByLabelText("resume time")).toBeTruthy();
|
||||
expect(screen.getByLabelText("schedule resume")).toBeTruthy();
|
||||
expect(screen.getByLabelText("heure de reprise")).toBeTruthy();
|
||||
expect(screen.getByLabelText("programmer la reprise")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does NOT show the resume-time form when a suspected limit has a time", () => {
|
||||
@ -160,7 +160,7 @@ describe("AgentLimitBadge (render)", () => {
|
||||
);
|
||||
expect(screen.getByText(`limité jusqu'à ${formatResetTime(at)}`)).toBeTruthy();
|
||||
expect(screen.queryByText(/heure inconnue/)).toBeNull();
|
||||
expect(screen.queryByLabelText("resume time")).toBeNull();
|
||||
expect(screen.queryByLabelText("heure de reprise")).toBeNull();
|
||||
});
|
||||
|
||||
it("submitting the resume-time form arms a resume at the chosen epoch-ms", () => {
|
||||
@ -172,10 +172,10 @@ describe("AgentLimitBadge (render)", () => {
|
||||
onSetResumeAt={onSetResumeAt}
|
||||
/>,
|
||||
);
|
||||
fireEvent.change(screen.getByLabelText("resume time"), {
|
||||
fireEvent.change(screen.getByLabelText("heure de reprise"), {
|
||||
target: { value: "23:45" },
|
||||
});
|
||||
fireEvent.click(screen.getByLabelText("schedule resume"));
|
||||
fireEvent.click(screen.getByLabelText("programmer la reprise"));
|
||||
expect(onSetResumeAt).toHaveBeenCalledTimes(1);
|
||||
const ms = onSetResumeAt.mock.calls[0][0] as number;
|
||||
const d = new Date(ms);
|
||||
@ -193,7 +193,7 @@ describe("AgentLimitBadge (render)", () => {
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
(screen.getByLabelText("schedule resume") as HTMLButtonElement).disabled,
|
||||
(screen.getByLabelText("programmer la reprise") as HTMLButtonElement).disabled,
|
||||
).toBe(true);
|
||||
expect(onSetResumeAt).not.toHaveBeenCalled();
|
||||
});
|
||||
@ -209,11 +209,11 @@ describe("AgentLimitBadge (render)", () => {
|
||||
/>,
|
||||
);
|
||||
// Countdown label present (≈ "reprise dans 1m 30s").
|
||||
expect(screen.getByLabelText("resume countdown").textContent).toMatch(
|
||||
expect(screen.getByLabelText("compte à rebours de reprise").textContent).toMatch(
|
||||
/reprise dans/,
|
||||
);
|
||||
|
||||
const btn = screen.getByLabelText("cancel resume");
|
||||
const btn = screen.getByLabelText("annuler la reprise");
|
||||
fireEvent.click(btn);
|
||||
expect(onCancelResume).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@ -228,7 +228,7 @@ describe("AgentLimitBadge (render)", () => {
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
(screen.getByLabelText("cancel resume") as HTMLButtonElement).disabled,
|
||||
(screen.getByLabelText("annuler la reprise") as HTMLButtonElement).disabled,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user