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:
@ -16,6 +16,7 @@ import type { GraphCommit } from "@/domain";
|
||||
import { useGateways } from "@/app/di";
|
||||
import { Spinner, cn } from "@/shared";
|
||||
import { computeGraphRows, type GraphLink, type GraphRow } from "./graphLayout";
|
||||
import { useI18n } from "@/i18n";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hook
|
||||
@ -136,14 +137,15 @@ function GraphColumn({ row, laneCount }: GraphColumnProps) {
|
||||
// Relative date helper
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function relativeDate(timestamp: number): string {
|
||||
function relativeDate(timestamp: number, language: "fr" | "en"): string {
|
||||
const secs = Math.floor(Date.now() / 1000) - timestamp;
|
||||
if (secs < 60) return "just now";
|
||||
if (secs < 3600) return `${Math.floor(secs / 60)}m ago`;
|
||||
if (secs < 86400) return `${Math.floor(secs / 3600)}h ago`;
|
||||
if (secs < 2592000) return `${Math.floor(secs / 86400)}d ago`;
|
||||
if (secs < 31536000) return `${Math.floor(secs / 2592000)}mo ago`;
|
||||
return `${Math.floor(secs / 31536000)}y ago`;
|
||||
const rtf = new Intl.RelativeTimeFormat(language, { numeric: "auto", style: "narrow" });
|
||||
if (secs < 60) return rtf.format(0, "second");
|
||||
if (secs < 3600) return rtf.format(-Math.floor(secs / 60), "minute");
|
||||
if (secs < 86400) return rtf.format(-Math.floor(secs / 3600), "hour");
|
||||
if (secs < 2592000) return rtf.format(-Math.floor(secs / 86400), "day");
|
||||
if (secs < 31536000) return rtf.format(-Math.floor(secs / 2592000), "month");
|
||||
return rtf.format(-Math.floor(secs / 31536000), "year");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -156,18 +158,19 @@ interface CommitDetailProps {
|
||||
}
|
||||
|
||||
function CommitDetail({ commit, onClose }: CommitDetailProps) {
|
||||
const { tr } = useI18n();
|
||||
const isTag = (ref: string) => ref.startsWith("tag:");
|
||||
|
||||
return (
|
||||
<aside
|
||||
aria-label="commit detail"
|
||||
aria-label={tr("détail du commit", "commit detail")}
|
||||
className="flex w-80 shrink-0 flex-col gap-3 overflow-y-auto border-l border-border bg-surface p-4"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<h3 className="text-sm font-semibold text-content">{commit.summary}</h3>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="close commit detail"
|
||||
aria-label={tr("fermer le détail du commit", "close commit detail")}
|
||||
onClick={onClose}
|
||||
className="shrink-0 text-muted hover:text-content"
|
||||
>
|
||||
@ -183,18 +186,18 @@ function CommitDetail({ commit, onClose }: CommitDetailProps) {
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="font-medium text-faint">Author</dt>
|
||||
<dt className="font-medium text-faint">{tr("Auteur", "Author")}</dt>
|
||||
<dd className="text-content">{commit.author}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="font-medium text-faint">Date</dt>
|
||||
<dt className="font-medium text-faint">{tr("Date", "Date")}</dt>
|
||||
<dd className="text-content">
|
||||
{new Date(commit.timestamp * 1000).toLocaleString()}
|
||||
</dd>
|
||||
</div>
|
||||
{commit.parents.length > 0 && (
|
||||
<div>
|
||||
<dt className="font-medium text-faint">Parents</dt>
|
||||
<dt className="font-medium text-faint">{tr("Parents", "Parents")}</dt>
|
||||
<dd className="flex flex-col gap-0.5">
|
||||
{commit.parents.map((p) => (
|
||||
<code key={p} className="text-content">
|
||||
@ -236,6 +239,7 @@ export interface GitGraphViewProps {
|
||||
}
|
||||
|
||||
export function GitGraphView({ projectId }: GitGraphViewProps) {
|
||||
const { tr, language } = useI18n();
|
||||
const vm = useGitGraph(projectId);
|
||||
const [selectedHash, setSelectedHash] = useState<string | null>(null);
|
||||
|
||||
@ -250,21 +254,21 @@ export function GitGraphView({ projectId }: GitGraphViewProps) {
|
||||
const isTag = (ref: string) => ref.startsWith("tag:");
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 overflow-hidden" aria-label="git graph">
|
||||
<div className="flex flex-1 overflow-hidden" aria-label={tr("graphe Git", "git graph")}>
|
||||
{/* ── Graph + commit list ── */}
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
{/* Toolbar */}
|
||||
<div className="flex shrink-0 items-center gap-2 border-b border-border bg-surface px-3 py-2">
|
||||
<span className="text-sm font-semibold text-content">Git Graph</span>
|
||||
<span className="text-sm font-semibold text-content">{tr("Graphe Git", "Git graph")}</span>
|
||||
{vm.busy && <Spinner size={14} />}
|
||||
<button
|
||||
type="button"
|
||||
aria-label="refresh git graph"
|
||||
aria-label={tr("rafraîchir le graphe Git", "refresh git graph")}
|
||||
onClick={() => void vm.refresh()}
|
||||
disabled={vm.busy}
|
||||
className="ml-auto text-xs text-muted hover:text-content disabled:opacity-40"
|
||||
>
|
||||
Refresh
|
||||
{tr("Rafraîchir", "Refresh")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -279,9 +283,9 @@ export function GitGraphView({ projectId }: GitGraphViewProps) {
|
||||
)}
|
||||
|
||||
{/* Rows */}
|
||||
<div className="flex-1 overflow-y-auto" role="list" aria-label="commits">
|
||||
<div className="flex-1 overflow-y-auto" role="list" aria-label={tr("commits", "commits")}>
|
||||
{vm.rows.length === 0 && !vm.busy && (
|
||||
<p className="p-4 text-sm text-muted">No commits found.</p>
|
||||
<p className="p-4 text-sm text-muted">{tr("Aucun commit trouvé.", "No commits found.")}</p>
|
||||
)}
|
||||
{vm.rows.map((row) => {
|
||||
const isSelected = row.commit.hash === selectedHash;
|
||||
@ -291,7 +295,7 @@ export function GitGraphView({ projectId }: GitGraphViewProps) {
|
||||
<div
|
||||
key={row.commit.hash}
|
||||
role="listitem"
|
||||
aria-label={`commit ${row.commit.hash.slice(0, 8)}: ${row.commit.summary}`}
|
||||
aria-label={tr(`commit ${row.commit.hash.slice(0, 8)} : ${row.commit.summary}`, `commit ${row.commit.hash.slice(0, 8)}: ${row.commit.summary}`)}
|
||||
onClick={() =>
|
||||
setSelectedHash(isSelected ? null : row.commit.hash)
|
||||
}
|
||||
@ -353,7 +357,7 @@ export function GitGraphView({ projectId }: GitGraphViewProps) {
|
||||
{row.commit.author}
|
||||
</span>
|
||||
<span className="shrink-0 text-xs text-faint">
|
||||
{relativeDate(row.commit.timestamp)}
|
||||
{relativeDate(row.commit.timestamp, language)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -13,6 +13,7 @@ import { useState } from "react";
|
||||
import { Button, Input, Panel, Spinner, cn } from "@/shared";
|
||||
import { useGit } from "./useGit";
|
||||
import { buildFileTree, type GitTreeNode } from "./gitTree";
|
||||
import { useI18n } from "@/i18n";
|
||||
|
||||
/**
|
||||
* Recursively renders a changed-files tree. Directories are static rows; file
|
||||
@ -31,6 +32,7 @@ function FileTree({
|
||||
onAction: (path: string) => void;
|
||||
busy: boolean;
|
||||
}) {
|
||||
const { tr } = useI18n();
|
||||
return (
|
||||
<ul className="flex flex-col gap-0.5">
|
||||
{nodes.map((node) =>
|
||||
@ -74,12 +76,12 @@ function FileTree({
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
aria-label={`${action} ${node.path}`}
|
||||
aria-label={tr(`${action === "unstage" ? "désindexer" : "indexer"} ${node.path}`, `${action} ${node.path}`)}
|
||||
disabled={busy}
|
||||
onClick={() => onAction(node.path)}
|
||||
className="shrink-0 text-muted hover:text-content"
|
||||
>
|
||||
{action === "unstage" ? "Unstage" : "Stage"}
|
||||
{action === "unstage" ? tr("Désindexer", "Unstage") : tr("Indexer", "Stage")}
|
||||
</Button>
|
||||
</li>
|
||||
),
|
||||
@ -94,6 +96,7 @@ export interface GitPanelProps {
|
||||
}
|
||||
|
||||
export function GitPanel({ projectId }: GitPanelProps) {
|
||||
const { tr } = useI18n();
|
||||
const vm = useGit(projectId);
|
||||
const [commitMessage, setCommitMessage] = useState("");
|
||||
const [newBranch, setNewBranch] = useState("");
|
||||
@ -133,22 +136,22 @@ export function GitPanel({ projectId }: GitPanelProps) {
|
||||
)}
|
||||
|
||||
{/* ── Changes ── */}
|
||||
<section aria-label="Changes" className="border-b border-border p-4">
|
||||
<section aria-label={tr("Modifications", "Changes")} className="border-b border-border p-4">
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wide text-faint">
|
||||
Changes
|
||||
{tr("Modifications", "Changes")}
|
||||
</h4>
|
||||
{vm.busy && <Spinner size={12} />}
|
||||
</div>
|
||||
|
||||
{vm.files.length === 0 ? (
|
||||
<p className="text-sm text-muted">No changes.</p>
|
||||
<p className="text-sm text-muted">{tr("Aucune modification.", "No changes.")}</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Staged — shown as a file tree. */}
|
||||
{staged.length > 0 && (
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-medium text-muted">Staged</p>
|
||||
<p className="mb-1 text-xs font-medium text-muted">{tr("Indexées", "Staged")}</p>
|
||||
<FileTree
|
||||
nodes={buildFileTree(staged)}
|
||||
depth={0}
|
||||
@ -162,7 +165,7 @@ export function GitPanel({ projectId }: GitPanelProps) {
|
||||
{/* Unstaged — shown as a file tree. */}
|
||||
{unstaged.length > 0 && (
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-medium text-muted">Unstaged</p>
|
||||
<p className="mb-1 text-xs font-medium text-muted">{tr("Non indexées", "Unstaged")}</p>
|
||||
<FileTree
|
||||
nodes={buildFileTree(unstaged)}
|
||||
depth={0}
|
||||
@ -177,14 +180,14 @@ export function GitPanel({ projectId }: GitPanelProps) {
|
||||
</section>
|
||||
|
||||
{/* ── Commit ── */}
|
||||
<section aria-label="Commit" className="border-b border-border p-4">
|
||||
<section aria-label={tr("Commit", "Commit")} className="border-b border-border p-4">
|
||||
<h4 className="mb-2 text-xs font-semibold uppercase tracking-wide text-faint">
|
||||
Commit
|
||||
</h4>
|
||||
<form onSubmit={handleCommit} className="flex flex-col gap-2">
|
||||
<Input
|
||||
aria-label="commit message"
|
||||
placeholder="Commit message"
|
||||
aria-label={tr("message de commit", "commit message")}
|
||||
placeholder={tr("Message de commit", "Commit message")}
|
||||
value={commitMessage}
|
||||
onChange={(e) => setCommitMessage(e.target.value)}
|
||||
/>
|
||||
@ -201,14 +204,14 @@ export function GitPanel({ projectId }: GitPanelProps) {
|
||||
</section>
|
||||
|
||||
{/* ── Branches ── */}
|
||||
<section aria-label="Branches" className="border-b border-border p-4">
|
||||
<section aria-label={tr("Branches", "Branches")} className="border-b border-border p-4">
|
||||
<h4 className="mb-2 text-xs font-semibold uppercase tracking-wide text-faint">
|
||||
Branches
|
||||
</h4>
|
||||
<p className="mb-2 text-sm text-content">
|
||||
Current:{" "}
|
||||
{tr("Courante :", "Current:")}{" "}
|
||||
<span className="font-medium">
|
||||
{vm.branches.current ?? "(none)"}
|
||||
{vm.branches.current ?? tr("(aucune)", "(none)")}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
@ -236,12 +239,12 @@ export function GitPanel({ projectId }: GitPanelProps) {
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
aria-label={`checkout ${b}`}
|
||||
aria-label={tr(`basculer sur ${b}`, `checkout ${b}`)}
|
||||
disabled={vm.busy}
|
||||
onClick={() => void handleCheckout(b)}
|
||||
className="shrink-0 text-muted hover:text-content"
|
||||
>
|
||||
Checkout
|
||||
{tr("Basculer", "Checkout")}
|
||||
</Button>
|
||||
)}
|
||||
</li>
|
||||
@ -252,8 +255,8 @@ export function GitPanel({ projectId }: GitPanelProps) {
|
||||
|
||||
<form onSubmit={handleNewBranch} className="flex items-center gap-2">
|
||||
<Input
|
||||
aria-label="new branch name"
|
||||
placeholder="New branch"
|
||||
aria-label={tr("nom de la nouvelle branche", "new branch name")}
|
||||
placeholder={tr("Nouvelle branche", "New branch")}
|
||||
value={newBranch}
|
||||
onChange={(e) => setNewBranch(e.target.value)}
|
||||
className="flex-1"
|
||||
@ -263,18 +266,18 @@ export function GitPanel({ projectId }: GitPanelProps) {
|
||||
variant="ghost"
|
||||
disabled={!newBranch.trim() || vm.busy}
|
||||
>
|
||||
Create & checkout
|
||||
{tr("Créer et basculer", "Create & checkout")}
|
||||
</Button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
{/* ── Log ── */}
|
||||
<section aria-label="Log" className="p-4">
|
||||
<section aria-label={tr("Historique", "Log")} className="p-4">
|
||||
<h4 className="mb-2 text-xs font-semibold uppercase tracking-wide text-faint">
|
||||
Log
|
||||
{tr("Historique", "Log")}
|
||||
</h4>
|
||||
{vm.log.length === 0 ? (
|
||||
<p className="text-sm text-muted">No commits yet.</p>
|
||||
<p className="text-sm text-muted">{tr("Aucun commit.", "No commits yet.")}</p>
|
||||
) : (
|
||||
<ul className="flex flex-col divide-y divide-border">
|
||||
{vm.log.map((c) => (
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*
|
||||
* Covers:
|
||||
* - status shows staged/unstaged groupings
|
||||
* - stage moves a file from Unstaged to Staged
|
||||
* - indexer moves a file from Unstaged to Staged
|
||||
* - commit with empty message is blocked (button disabled)
|
||||
* - commit with message but nothing staged is blocked (button disabled)
|
||||
* - commit moves staged files out and adds an entry to the log
|
||||
@ -41,7 +41,7 @@ function renderPanel(git: MockGitGateway = new MockGitGateway()) {
|
||||
/** Waits until the "Changes" section is rendered (panel stabilised). */
|
||||
async function waitForPanel() {
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("region", { name: "Changes" })).toBeTruthy();
|
||||
expect(screen.getByRole("region", { name: "Modifications" })).toBeTruthy();
|
||||
});
|
||||
}
|
||||
|
||||
@ -59,15 +59,15 @@ describe("GitPanel (with MockGitGateway)", () => {
|
||||
renderPanel(git);
|
||||
await waitForPanel();
|
||||
|
||||
await screen.findByText("Staged");
|
||||
await screen.findByText("Unstaged");
|
||||
// src/main.rs is staged → Unstage button exists
|
||||
await screen.findByText("Indexées");
|
||||
await screen.findByText("Non indexées");
|
||||
// src/main.rs is staged → Unindexer button exists
|
||||
expect(
|
||||
screen.getByRole("button", { name: "unstage src/main.rs" }),
|
||||
screen.getByRole("button", { name: "désindexer src/main.rs" }),
|
||||
).toBeTruthy();
|
||||
// README.md is unstaged → Stage button exists
|
||||
expect(
|
||||
screen.getByRole("button", { name: "stage README.md" }),
|
||||
screen.getByRole("button", { name: "indexer README.md" }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
@ -77,19 +77,19 @@ describe("GitPanel (with MockGitGateway)", () => {
|
||||
|
||||
// Stage README.md
|
||||
const stageBtn = await screen.findByRole("button", {
|
||||
name: "stage README.md",
|
||||
name: "indexer README.md",
|
||||
});
|
||||
fireEvent.click(stageBtn);
|
||||
|
||||
// After staging, an Unstage button should appear for README.md
|
||||
// After staging, an Unindexer button should appear for README.md
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("button", { name: "unstage README.md" }),
|
||||
screen.getByRole("button", { name: "désindexer README.md" }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("clicking Unstage moves a file back to Unstaged", async () => {
|
||||
it("clicking Unindexer moves a file back to Unstaged", async () => {
|
||||
const git = new MockGitGateway();
|
||||
await git.init(PROJECT_ID);
|
||||
await git.stage(PROJECT_ID, "src/main.rs");
|
||||
@ -98,13 +98,13 @@ describe("GitPanel (with MockGitGateway)", () => {
|
||||
await waitForPanel();
|
||||
|
||||
const unstageBtn = await screen.findByRole("button", {
|
||||
name: "unstage src/main.rs",
|
||||
name: "désindexer src/main.rs",
|
||||
});
|
||||
fireEvent.click(unstageBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("button", { name: "stage src/main.rs" }),
|
||||
screen.getByRole("button", { name: "indexer src/main.rs" }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -121,7 +121,7 @@ describe("GitPanel (with MockGitGateway)", () => {
|
||||
renderPanel();
|
||||
await waitForPanel();
|
||||
|
||||
fireEvent.change(screen.getByLabelText("commit message"), {
|
||||
fireEvent.change(screen.getByLabelText("message de commit"), {
|
||||
target: { value: "my message" },
|
||||
});
|
||||
|
||||
@ -138,7 +138,7 @@ describe("GitPanel (with MockGitGateway)", () => {
|
||||
renderPanel(git);
|
||||
await waitForPanel();
|
||||
|
||||
fireEvent.change(screen.getByLabelText("commit message"), {
|
||||
fireEvent.change(screen.getByLabelText("message de commit"), {
|
||||
target: { value: "initial commit" },
|
||||
});
|
||||
|
||||
@ -148,7 +148,7 @@ describe("GitPanel (with MockGitGateway)", () => {
|
||||
|
||||
// After commit, no files should remain (both were staged).
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("No changes.")).toBeTruthy();
|
||||
expect(screen.getByText("Aucune modification.")).toBeTruthy();
|
||||
});
|
||||
|
||||
// The commit should appear in the log.
|
||||
@ -168,9 +168,9 @@ describe("GitPanel (with MockGitGateway)", () => {
|
||||
expect(branchesSection).toBeTruthy();
|
||||
|
||||
// Create & checkout a new branch via the form.
|
||||
const branchInput = screen.getByLabelText("new branch name");
|
||||
const branchInput = screen.getByLabelText("nom de la nouvelle branche");
|
||||
fireEvent.change(branchInput, { target: { value: "feature/new-thing" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: /create.*checkout/i }));
|
||||
fireEvent.click(screen.getByRole("button", { name: /créer.*basculer/i }));
|
||||
|
||||
// After checkout, the new branch should appear (may show in current + list).
|
||||
await waitFor(() => {
|
||||
@ -181,7 +181,7 @@ describe("GitPanel (with MockGitGateway)", () => {
|
||||
// A checkout button for "main" should now appear (since it's no longer current).
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("button", { name: "checkout main" }),
|
||||
screen.getByRole("button", { name: "basculer sur main" }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -190,7 +190,7 @@ describe("GitPanel (with MockGitGateway)", () => {
|
||||
renderPanel();
|
||||
await waitForPanel();
|
||||
|
||||
expect(screen.getByText("No commits yet.")).toBeTruthy();
|
||||
expect(screen.getByText("Aucun commit.")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@ -213,7 +213,7 @@ describe("MockGitGateway (unit)", () => {
|
||||
expect(Array.isArray(files)).toBe(true);
|
||||
});
|
||||
|
||||
it("stage toggles a file to staged=true", async () => {
|
||||
it("indexer toggles a file to staged=true", async () => {
|
||||
const gw = new MockGitGateway();
|
||||
const before = await gw.status("p");
|
||||
const target = before[0].path;
|
||||
@ -223,7 +223,7 @@ describe("MockGitGateway (unit)", () => {
|
||||
expect(file.staged).toBe(true);
|
||||
});
|
||||
|
||||
it("unstage toggles a file back to staged=false", async () => {
|
||||
it("désindexer toggles a file back to staged=false", async () => {
|
||||
const gw = new MockGitGateway();
|
||||
const files = await gw.status("p");
|
||||
const target = files[0].path;
|
||||
|
||||
@ -114,7 +114,7 @@ describe("GitGraphView", () => {
|
||||
await waitForCommits();
|
||||
|
||||
// The detail panel should not be visible yet.
|
||||
expect(screen.queryByRole("complementary", { name: "commit detail" })).toBeNull();
|
||||
expect(screen.queryByRole("complementary", { name: "détail du commit" })).toBeNull();
|
||||
|
||||
// Click the first commit row.
|
||||
const items = screen.getAllByRole("listitem");
|
||||
@ -122,7 +122,7 @@ describe("GitGraphView", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("complementary", { name: "commit detail" }),
|
||||
screen.getByRole("complementary", { name: "détail du commit" }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -147,7 +147,7 @@ describe("GitGraphView", () => {
|
||||
|
||||
// Author should appear in a <dd> (detail panel's definition list).
|
||||
await waitFor(() => {
|
||||
const detail = screen.getByRole("complementary", { name: "commit detail" });
|
||||
const detail = screen.getByRole("complementary", { name: "détail du commit" });
|
||||
expect(detail.textContent).toContain("Alice");
|
||||
});
|
||||
});
|
||||
@ -162,7 +162,7 @@ describe("GitGraphView", () => {
|
||||
fireEvent.click(mergeRow);
|
||||
|
||||
await waitFor(() => {
|
||||
const detail = screen.getByRole("complementary", { name: "commit detail" });
|
||||
const detail = screen.getByRole("complementary", { name: "détail du commit" });
|
||||
// The merge commit has two parents displayed as short hashes.
|
||||
expect(detail.textContent).toContain("bbbbbbbb");
|
||||
expect(detail.textContent).toContain("dddddddd");
|
||||
@ -178,16 +178,16 @@ describe("GitGraphView", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("complementary", { name: "commit detail" }),
|
||||
screen.getByRole("complementary", { name: "détail du commit" }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
// Click the close button.
|
||||
fireEvent.click(screen.getByRole("button", { name: "close commit detail" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "fermer le détail du commit" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole("complementary", { name: "commit detail" }),
|
||||
screen.queryByRole("complementary", { name: "détail du commit" }),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
@ -201,7 +201,7 @@ describe("GitGraphView", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("complementary", { name: "commit detail" }),
|
||||
screen.getByRole("complementary", { name: "détail du commit" }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
@ -210,7 +210,7 @@ describe("GitGraphView", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole("complementary", { name: "commit detail" }),
|
||||
screen.queryByRole("complementary", { name: "détail du commit" }),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user