feat(kanban): requester input empty + keyboard nav (issue 0088)
CardForm: drop pre-fill of requester from logged user; Enter inside the Autocomplete no longer submits the form (Mantine handles dropdown selection; arrows + Enter pick option without closing modal). Submit remains via "Crear" button or Ctrl+Enter from description. Adds data-field="requester" and data-test="add-card" selectors for stable e2e queries. Tests: - vitest component test (CardForm.test.tsx): empty input, Enter does not submit, submit only via button. Dropdown arrow nav covered by e2e (jsdom portal handling is brittle). - Playwright e2e (requester-input.spec.ts) using new browser capability group (pw_kanban_login, pw_keyboard_sequence) from registry. - seed_e2e_user CLI to create deterministic test user against operations.db (bcrypt via standard backend hash). Setup additions (frontend/): - vitest + @testing-library + jsdom devDeps - @playwright/test devDep + playwright.config.ts - src/test/setup.ts polyfills jsdom for Mantine (matchMedia, visualViewport, document.fonts, ResizeObserver) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
import { CardForm } from "./CardForm";
|
||||
|
||||
function renderForm(overrides: Partial<Parameters<typeof CardForm>[0]> = {}) {
|
||||
const onSubmit = vi.fn();
|
||||
const onCancel = vi.fn();
|
||||
render(
|
||||
<MantineProvider>
|
||||
<CardForm
|
||||
requesterOptions={["Alice", "Anna", "Bob", "Enmanuel"]}
|
||||
onSubmit={onSubmit}
|
||||
onCancel={onCancel}
|
||||
{...overrides}
|
||||
/>
|
||||
</MantineProvider>
|
||||
);
|
||||
return { onSubmit, onCancel };
|
||||
}
|
||||
|
||||
describe("CardForm — requester input (issue 0088)", () => {
|
||||
it("solicitante entra vacio cuando initial.requester no se pasa", () => {
|
||||
renderForm();
|
||||
const requesterInput = (document.querySelector('input[data-field="requester"]') as HTMLInputElement) as HTMLInputElement;
|
||||
expect(requesterInput.value).toBe("");
|
||||
});
|
||||
|
||||
it("Enter dentro del requester NO dispara onSubmit (dropdown cerrado o abierto)", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { onSubmit } = renderForm();
|
||||
|
||||
// Necesita un titulo valido para que un eventual submit no se ignore por el guard.
|
||||
const title = screen.getByLabelText(/Tarea/i);
|
||||
await user.type(title, "Mi tarea");
|
||||
|
||||
const requester = (document.querySelector('input[data-field="requester"]') as HTMLInputElement);
|
||||
await user.click(requester);
|
||||
await user.keyboard("{Enter}");
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
|
||||
await user.type(requester, "An");
|
||||
await user.keyboard("{Enter}");
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Navegacion ArrowDown + Enter del dropdown la maneja Mantine internamente.
|
||||
// Validar eso en jsdom es fragil (portals + virtual focus). Cubierto en e2e
|
||||
// Playwright donde corre browser real.
|
||||
|
||||
it("submit solo via boton Crear", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { onSubmit } = renderForm({ submitLabel: "Crear" });
|
||||
|
||||
const title = screen.getByLabelText(/Tarea/i);
|
||||
await user.type(title, "Mi tarea");
|
||||
const requester = (document.querySelector('input[data-field="requester"]') as HTMLInputElement);
|
||||
await user.type(requester, "Anna");
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /Crear/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(onSubmit.mock.calls[0][0]).toMatchObject({
|
||||
title: "Mi tarea",
|
||||
requester: "Anna",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,11 @@ import { Autocomplete, Button, Group, Select, Stack, TagsInput, Textarea } from
|
||||
import { FormEvent, KeyboardEvent, useState } from "react";
|
||||
import type { User } from "../types";
|
||||
|
||||
// CardForm: Solicitante (Autocomplete) intencionadamente NO hace submit en Enter.
|
||||
// Enter dentro del Autocomplete deja que Mantine seleccione el item resaltado del
|
||||
// dropdown sin cerrar el formulario. Submit solo via boton "Crear" o Ctrl+Enter
|
||||
// en descripcion. Ver issue 0088.
|
||||
|
||||
export interface CardFormValues {
|
||||
requester: string;
|
||||
title: string;
|
||||
@@ -48,12 +53,6 @@ export function CardForm({
|
||||
});
|
||||
};
|
||||
|
||||
const enterSubmit = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
submit();
|
||||
}
|
||||
};
|
||||
const textareaEnter = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
@@ -89,9 +88,12 @@ export function CardForm({
|
||||
data={requesterOptions}
|
||||
tabIndex={2}
|
||||
autoComplete="off"
|
||||
onKeyDown={enterSubmit}
|
||||
data-field="requester"
|
||||
placeholder="Empieza a escribir y elige uno existente"
|
||||
limit={10}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
<Textarea
|
||||
label="Descripcion"
|
||||
|
||||
@@ -455,6 +455,7 @@ function KanbanColumnImpl({
|
||||
onClick={() => onAddCard(column.id)}
|
||||
mt="xs"
|
||||
fullWidth
|
||||
data-test="add-card"
|
||||
>
|
||||
Anadir tarjeta
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user