feat(tickets): fenêtre dédiée de gestion des tickets — liens via popup + assistant IA flottant (#17)
Lot C du sprint UI rework : fenêtre dédiée de gestion des tickets, appuyée sur la primitive FloatingWindow (#16) et la popup TicketPicker (#18). - TicketDetail : gestion des liens de ticket via la popup TicketPicker, assistant IA en fenêtre flottante - FloatingWindow : ajustements pour l'usage fenêtre dédiée Tests : tsc --noEmit clean, suite complète 530/530, suites tickets + FloatingWindow 50/50. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -645,47 +645,96 @@ describe("TicketsView", () => {
|
||||
);
|
||||
|
||||
const detail = await screen.findByRole("dialog", { name: `ticket ${t.ref}` });
|
||||
expect(within(detail).getByText("Assistant IA")).toBeTruthy();
|
||||
|
||||
const profileSelect = await within(detail).findByLabelText(
|
||||
// The assistant is opened from the prominent bottom-right floating button;
|
||||
// it is not inline anymore (#17).
|
||||
expect(within(detail).queryByLabelText("profil de l'assistant")).toBeNull();
|
||||
fireEvent.click(within(detail).getByLabelText("ouvrir l'assistant IA"));
|
||||
|
||||
// It opens in its own nested window.
|
||||
const assistant = await screen.findByRole("dialog", {
|
||||
name: `Assistant IA — ${t.ref}`,
|
||||
});
|
||||
|
||||
const profileSelect = await within(assistant).findByLabelText(
|
||||
"profil de l'assistant",
|
||||
);
|
||||
expect(within(detail).getByText("Ouvrir la conversation")).toBeTruthy();
|
||||
expect(within(assistant).getByText("Ouvrir la conversation")).toBeTruthy();
|
||||
|
||||
fireEvent.change(profileSelect, { target: { value: "qa-assistant" } });
|
||||
fireEvent.click(within(detail).getByText("Ouvrir la conversation"));
|
||||
fireEvent.click(within(assistant).getByText("Ouvrir la conversation"));
|
||||
|
||||
expect(
|
||||
await within(detail).findByLabelText("message à l'assistant"),
|
||||
await within(assistant).findByLabelText("message à l'assistant"),
|
||||
).toBeTruthy();
|
||||
|
||||
fireEvent.change(within(detail).getByLabelText("message à l'assistant"), {
|
||||
fireEvent.change(within(assistant).getByLabelText("message à l'assistant"), {
|
||||
target: { value: "Crée un plan de correction" },
|
||||
});
|
||||
fireEvent.click(within(detail).getByText("Envoyer"));
|
||||
fireEvent.click(within(assistant).getByText("Envoyer"));
|
||||
|
||||
expect(await within(detail).findByText("Crée un plan de correction")).toBeTruthy();
|
||||
expect(
|
||||
await within(detail).findByText(
|
||||
await within(assistant).findByText("Crée un plan de correction"),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
await within(assistant).findByText(
|
||||
"Assistant: reçu « Crée un plan de correction ».",
|
||||
),
|
||||
).toBeTruthy();
|
||||
|
||||
fireEvent.click(within(detail).getByText("Fermer la conversation"));
|
||||
fireEvent.click(within(assistant).getByText("Fermer la conversation"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
within(detail).queryByLabelText("message à l'assistant"),
|
||||
within(assistant).queryByLabelText("message à l'assistant"),
|
||||
).toBeNull(),
|
||||
);
|
||||
expect(within(detail).getByText("Ouvrir la conversation")).toBeTruthy();
|
||||
expect(within(assistant).getByText("Ouvrir la conversation")).toBeTruthy();
|
||||
expect(
|
||||
within(detail).queryByText("Crée un plan de correction"),
|
||||
within(assistant).queryByText("Crée un plan de correction"),
|
||||
).toBeNull();
|
||||
expect(
|
||||
within(detail).queryByText("Assistant: reçu « Crée un plan de correction »."),
|
||||
within(assistant).queryByText(
|
||||
"Assistant: reçu « Crée un plan de correction ».",
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("adds a link via the TicketPicker popup instead of a manual #id (#17)", async () => {
|
||||
const a = await ticket.create(PROJECT_ID, { title: "Source ticket" });
|
||||
const b = await ticket.create(PROJECT_ID, { title: "Target ticket" });
|
||||
const gateways = { ticket, system, agent } as unknown as Gateways;
|
||||
|
||||
render(
|
||||
<DIProvider gateways={gateways}>
|
||||
<TicketDetail
|
||||
projectId={PROJECT_ID}
|
||||
ticketRef={a.ref}
|
||||
onClose={() => {}}
|
||||
onOpenRef={() => {}}
|
||||
/>
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
const detail = await screen.findByRole("dialog", { name: `ticket ${a.ref}` });
|
||||
// The old manual "#id" input is gone.
|
||||
expect(within(detail).queryByLabelText("link target ref")).toBeNull();
|
||||
|
||||
// Choose the link kind, then open the picker and select the target ticket.
|
||||
fireEvent.change(within(detail).getByLabelText("link kind"), {
|
||||
target: { value: "blocks" },
|
||||
});
|
||||
fireEvent.click(within(detail).getByLabelText("add link"));
|
||||
|
||||
// The picker excludes the ticket itself; the target is offered.
|
||||
expect(screen.queryByLabelText(`select ticket ${a.ref}`)).toBeNull();
|
||||
fireEvent.click(await screen.findByLabelText(`select ticket ${b.ref}`));
|
||||
|
||||
await waitFor(async () => {
|
||||
const fresh = await ticket.read(PROJECT_ID, a.ref);
|
||||
expect(fresh.links).toEqual([{ targetRef: b.ref, kind: "blocks" }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("SprintManager (#11)", () => {
|
||||
|
||||
Reference in New Issue
Block a user