feat(applications): make bulk selection an explicit mode
This commit is contained in:
@@ -164,6 +164,9 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
// Presentation only: not an account preference, saved-view field or API input.
|
||||
const [density, setDensity] = useState<ContentDensity>("comfortable");
|
||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||
const [selectionMode, setSelectionMode] = useState(false);
|
||||
const selectionGeneration = useRef(0);
|
||||
const showSelection = mode === "trash" || selectionMode;
|
||||
const [search, setSearch] = useState(routeQuery.q);
|
||||
const debouncedSearch = useDebouncedValue(search, 250);
|
||||
const [includeDeleted, setIncludeDeleted] = useState(() => mode === "trash" || routeQuery.includeDeleted);
|
||||
@@ -185,10 +188,13 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
|
||||
// Selection belongs to the current Table page, never to an invisible view.
|
||||
useEffect(() => {
|
||||
selectionGeneration.current += 1;
|
||||
setSelectionMode(false);
|
||||
setSelectedIds([]);
|
||||
setBulkStatusAnchor(null);
|
||||
setBoardCount(null);
|
||||
retainedSelectionAfterReloadRef.current = null;
|
||||
}, [boardView]);
|
||||
}, [boardView, mode]);
|
||||
|
||||
const viewDestination = (view: "table" | "board") => {
|
||||
const query = new URLSearchParams(location.search);
|
||||
@@ -197,6 +203,8 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
};
|
||||
|
||||
const updateListRoute = useCallback((updates: Record<string, string | null>) => {
|
||||
selectionGeneration.current += 1;
|
||||
retainedSelectionAfterReloadRef.current = null;
|
||||
setSelectedIds([]);
|
||||
const next = new URLSearchParams(location.search);
|
||||
Object.entries(updates).forEach(([key, value]) => {
|
||||
@@ -409,8 +417,10 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
|
||||
const runBulkAction = async (action: "delete" | "restore" | "status", value?: string) => {
|
||||
if (selectedIds.length === 0) return;
|
||||
const selection = selectionGeneration.current;
|
||||
const selectedJobs = jobs.filter((job) => selectedIdSet.has(job.id));
|
||||
if (action === "delete" && !(await confirmDelete(selectedJobs))) return;
|
||||
if (selection !== selectionGeneration.current) return;
|
||||
const results = await Promise.allSettled(selectedIds.map((id) => {
|
||||
if (action === "delete") return api.delete(`/jobapplications/${id}`);
|
||||
if (action === "restore") return api.post(`/jobapplications/${id}/restore`);
|
||||
@@ -420,7 +430,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
if (failedIds.length) toast(t("jobTableBulkActionFailed"), "error");
|
||||
else toast(t("jobTableUpdatedJobs", { count: selectedIds.length }), "success");
|
||||
if (failedIds.length !== selectedIds.length) {
|
||||
retainedSelectionAfterReloadRef.current = failedIds;
|
||||
if (selection === selectionGeneration.current) retainedSelectionAfterReloadRef.current = failedIds;
|
||||
setReloadToken((token) => token + 1);
|
||||
}
|
||||
};
|
||||
@@ -456,7 +466,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
}), [buildWorkflowActionSignal, filteredJobs, selectedIdSet, language]);
|
||||
|
||||
const statusOptions = ["Waiting", "Interview", "Offer", "Rejected", "Ghosted"] as const;
|
||||
const visibleDesktopColumns = 6 + Number(columns.status) + Number(columns.dateApplied) + Number(columns.daysSince) + Number(columns.jobUrl);
|
||||
const visibleDesktopColumns = 5 + Number(showSelection) + Number(columns.status) + Number(columns.dateApplied) + Number(columns.daysSince) + Number(columns.jobUrl);
|
||||
const selectedCompanyIsLoading = companyFilterId !== "All" && !companies.some((company) => company.id === companyFilterId);
|
||||
const matchingCount = boardView ? boardCount
|
||||
: jobsResource.loading || jobsResource.refreshing || jobsResource.error ? null : total;
|
||||
@@ -632,7 +642,17 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{!boardView && selectedIds.length > 0 ? (
|
||||
{!boardView && mode === "jobs" && (filteredJobs.length > 0 || selectionMode) ? (
|
||||
<Button size="small" variant={selectionMode ? "contained" : "outlined"} aria-pressed={selectionMode} sx={{ mt: 1.5, minHeight: isMobile ? 44 : undefined }}
|
||||
onClick={() => {
|
||||
selectionGeneration.current += 1;
|
||||
retainedSelectionAfterReloadRef.current = null;
|
||||
setSelectedIds([]);
|
||||
setBulkStatusAnchor(null);
|
||||
setSelectionMode(!selectionMode);
|
||||
}}>{t("jobTableSelectionMode")}</Button>
|
||||
) : null}
|
||||
{!boardView && showSelection && selectedIds.length > 0 ? (
|
||||
<Paper variant="outlined" sx={{ mt: 1.5, p: 1.5, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 1.5, flexWrap: "wrap", borderColor: "primary.main", borderRadius: 2.5, boxShadow: "none" }}>
|
||||
<Typography sx={{ fontWeight: 700 }}>{t("jobTableSelected", { count: selectedIds.length })}</Typography>
|
||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", width: { xs: "100%", sm: "auto" }, "& .MuiButton-root": { minHeight: isMobile ? 44 : undefined } }}>
|
||||
@@ -685,9 +705,9 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
>
|
||||
{isMobile ? (
|
||||
<Stack spacing={0}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 1, px: 0.5 }}>
|
||||
{showSelection ? <Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 1, px: 0.5 }}>
|
||||
<FormControlLabel control={<Checkbox checked={selectedAllOnPage} indeterminate={selectedIds.length > 0 && !selectedAllOnPage} onChange={(e) => toggleSelectAll(e.target.checked)} />} label={t("jobTableSelectAll")} sx={{ mr: 0 }} />
|
||||
</Box>
|
||||
</Box> : null}
|
||||
|
||||
{jobsResource.loading ? <Typography sx={{ py: 2, textAlign: "center" }}>{t("loading")}</Typography> : null}
|
||||
{!jobsResource.loading && !jobsResource.error && rowModels.map(({ job, toneName, actionSignals, tags, appliedDateLabel, isSelected }) => {
|
||||
@@ -722,14 +742,14 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
<Stack spacing="var(--app-list-gap)">
|
||||
<Box sx={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 1, flexWrap: "wrap" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "flex-start", gap: 1, minWidth: 0, flex: "1 1 180px" }}>
|
||||
<Checkbox inputProps={{ "aria-label": t("jobTableSelectJob", { title: job.jobTitle }) }} checked={isSelected} onChange={(e) => toggleSelected(job.id, e.target.checked)} sx={{ mt: -0.5, ml: -1 }} />
|
||||
{showSelection ? <Checkbox inputProps={{ "aria-label": t("jobTableSelectJob", { title: job.jobTitle }) }} checked={isSelected} onChange={(e) => toggleSelected(job.id, e.target.checked)} sx={{ mt: -0.5, ml: -1 }} /> : null}
|
||||
<Box sx={{ minWidth: 0 }}>
|
||||
<Typography variant="body2" sx={{ color: "text.secondary", overflowWrap: "anywhere" }}>
|
||||
{job.company?.name ?? t("jobTableCompany")}
|
||||
</Typography>
|
||||
<Typography sx={{ fontWeight: 700, fontSize: 16, lineHeight: 1.35, overflowWrap: "anywhere" }}>
|
||||
{job.jobTitle}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: "text.secondary", overflowWrap: "anywhere" }}>
|
||||
{job.company?.name ?? t("jobTableCompany")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
{columns.status ? <Chip label={statusLabel(t, job.status)} size="small" color={toneName as any} variant="outlined" sx={{ fontWeight: 700, maxWidth: "100%", height: "auto", minHeight: 24, "& .MuiChip-label": { whiteSpace: "normal", overflowWrap: "anywhere", py: 0.5 } }} /> : null}
|
||||
@@ -809,9 +829,9 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
<Table aria-label={t(mode === "trash" ? "trash" : "jobApplications")} size="small" sx={{ minWidth: 980 }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell padding="checkbox"><Checkbox inputProps={{ "aria-label": t("jobTableSelectAll") }} checked={selectedAllOnPage} indeterminate={selectedIds.length > 0 && !selectedAllOnPage} onChange={(e) => toggleSelectAll(e.target.checked)} /></TableCell>
|
||||
<TableCell sortDirection={sortBy === "company" ? sortDir : false}><TableSortLabel active={sortBy === "company"} direction={sortBy === "company" ? sortDir : "asc"} onClick={() => requestSort("company")}>{t("jobTableCompany")}</TableSortLabel></TableCell>
|
||||
{showSelection ? <TableCell padding="checkbox"><Checkbox inputProps={{ "aria-label": t("jobTableSelectAll") }} checked={selectedAllOnPage} indeterminate={selectedIds.length > 0 && !selectedAllOnPage} onChange={(e) => toggleSelectAll(e.target.checked)} /></TableCell> : null}
|
||||
<TableCell sortDirection={sortBy === "jobTitle" ? sortDir : false}><TableSortLabel active={sortBy === "jobTitle"} direction={sortBy === "jobTitle" ? sortDir : "asc"} onClick={() => requestSort("jobTitle")}>{t("jobTableRole")}</TableSortLabel></TableCell>
|
||||
<TableCell sortDirection={sortBy === "company" ? sortDir : false}><TableSortLabel active={sortBy === "company"} direction={sortBy === "company" ? sortDir : "asc"} onClick={() => requestSort("company")}>{t("jobTableCompany")}</TableSortLabel></TableCell>
|
||||
<TableCell sortDirection={sortBy === "location" ? sortDir : false}><TableSortLabel active={sortBy === "location"} direction={sortBy === "location" ? sortDir : "asc"} onClick={() => requestSort("location")}>{t("jobTableLocation")}</TableSortLabel></TableCell>
|
||||
{columns.status ? <TableCell sortDirection={sortBy === "status" ? sortDir : false}><TableSortLabel active={sortBy === "status"} direction={sortBy === "status" ? sortDir : "asc"} onClick={() => requestSort("status")}>{t("jobTableStatus")}</TableSortLabel></TableCell> : null}
|
||||
{columns.dateApplied ? <TableCell sortDirection={sortBy === "dateApplied" ? sortDir : false}><TableSortLabel active={sortBy === "dateApplied"} direction={sortBy === "dateApplied" ? sortDir : "asc"} onClick={() => requestSort("dateApplied")}>{t("jobTableDateApplied")}</TableSortLabel></TableCell> : null}
|
||||
@@ -848,8 +868,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
"&:focus-visible": { outline: "3px solid", outlineColor: "primary.main", outlineOffset: -3 },
|
||||
}}
|
||||
>
|
||||
<TableCell padding="checkbox"><Checkbox inputProps={{ "aria-label": t("jobTableSelectJob", { title: job.jobTitle }) }} checked={isSelected} onChange={(e) => toggleSelected(job.id, e.target.checked)} /></TableCell>
|
||||
<TableCell sx={{ minWidth: 140, color: "text.secondary", fontWeight: 600 }}>{job.company?.name ?? ""}</TableCell>
|
||||
{showSelection ? <TableCell padding="checkbox"><Checkbox inputProps={{ "aria-label": t("jobTableSelectJob", { title: job.jobTitle }) }} checked={isSelected} onChange={(e) => toggleSelected(job.id, e.target.checked)} /></TableCell> : null}
|
||||
<TableCell sx={{ minWidth: 260 }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }}>
|
||||
<Typography component="span" sx={{ fontWeight: 700, overflowWrap: "anywhere" }}>{job.jobTitle}</Typography>
|
||||
@@ -869,6 +888,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
))}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell sx={{ minWidth: 140, color: "text.secondary", fontWeight: 600 }}>{job.company?.name ?? ""}</TableCell>
|
||||
<TableCell sx={{ minWidth: 130 }}>{job.location || "—"}</TableCell>
|
||||
{columns.status ? <TableCell><Chip label={statusLabel(t, job.status)} size="small" color={toneName as any} variant="outlined" sx={{ fontWeight: 600 }} /></TableCell> : null}
|
||||
{columns.dateApplied ? <TableCell>{appliedDateLabel}</TableCell> : null}
|
||||
|
||||
@@ -2826,6 +2826,7 @@ export const translations = {
|
||||
jobTableInterviewStage: "Interview stage",
|
||||
jobTableShowDeleted: "Show deleted",
|
||||
jobTableColumns: "Columns",
|
||||
jobTableSelectionMode: "Select applications",
|
||||
jobTableSelectAll: "Select all",
|
||||
jobTableSelected: "{count} selected",
|
||||
jobTableRestoreSelected: "Restore selected",
|
||||
@@ -2856,7 +2857,7 @@ export const translations = {
|
||||
jobTableFollowUp: "Follow up",
|
||||
jobTableCvMissing: "CV missing",
|
||||
jobTableCvReady: "CV ready",
|
||||
jobTablePackageWork: "Build package",
|
||||
jobTablePackageWork: "Prepare application",
|
||||
jobTablePackageMissingCvAndNotes: "Tailored CV and job notes still need a working draft.",
|
||||
jobTablePackageMissingNotes: "Job notes still need a working draft.",
|
||||
jobTableOpenListing: "Open listing",
|
||||
@@ -6050,6 +6051,7 @@ export const translations = {
|
||||
jobTableInterviewStage: "Intervjustadium",
|
||||
jobTableShowDeleted: "Vis slettede",
|
||||
jobTableColumns: "Kolonner",
|
||||
jobTableSelectionMode: "Velg søknader",
|
||||
jobTableSelectAll: "Velg alle",
|
||||
jobTableSelected: "{count} valgt",
|
||||
jobTableRestoreSelected: "Gjenopprett valgte",
|
||||
@@ -6080,7 +6082,7 @@ export const translations = {
|
||||
jobTableFollowUp: "Følg opp",
|
||||
jobTableCvMissing: "CV mangler",
|
||||
jobTableCvReady: "CV klar",
|
||||
jobTablePackageWork: "Bygg pakke",
|
||||
jobTablePackageWork: "Klargjør søknaden",
|
||||
jobTablePackageMissingCvAndNotes: "Tilpasset CV og jobbnotater trenger fortsatt et arbeidsutkast.",
|
||||
jobTablePackageMissingNotes: "Jobbnotater trenger fortsatt et arbeidsutkast.",
|
||||
jobTableOpenListing: "Åpne stilling",
|
||||
|
||||
@@ -34,9 +34,34 @@ beforeEach(() => {
|
||||
mockedApi.get.mockImplementation((url: string) => Promise.resolve({ data: url === "/companies" ? [job.company] : url === "/jobapplications" ? { items: [job], total: 31, page: 1, pageSize: 15 } : [] } as any));
|
||||
});
|
||||
|
||||
test("ordinary applications hide checkboxes until explicit selection mode and discard selection on exit", async () => {
|
||||
show("jobs", "?q=platform");
|
||||
const row = await screen.findByRole("row", { name: /platform engineer/i });
|
||||
const readsBefore = calls().length;
|
||||
expect(within(row).queryByRole("checkbox")).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("checkbox", { name: t.jobTableSelectAll })).not.toBeInTheDocument();
|
||||
const selection = screen.getByRole("button", { name: t.jobTableSelectionMode });
|
||||
expect(within(row).getAllByRole("cell")[0]).toHaveTextContent(job.jobTitle);
|
||||
expect(within(row).getAllByRole("cell")[1]).toHaveTextContent(job.company.name);
|
||||
fireEvent.click(selection);
|
||||
expect(selection).toHaveAttribute("aria-pressed", "true");
|
||||
fireEvent.click(within(row).getByRole("checkbox"));
|
||||
expect(screen.getByRole("button", { name: t.jobTableDeleteSelected })).toBeVisible();
|
||||
fireEvent.click(selection);
|
||||
expect(selection).toHaveAttribute("aria-pressed", "false");
|
||||
expect(within(row).queryByRole("checkbox")).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: t.jobTableDeleteSelected })).not.toBeInTheDocument();
|
||||
fireEvent.click(selection);
|
||||
expect(within(row).getByRole("checkbox")).not.toBeChecked();
|
||||
expect(calls()).toHaveLength(readsBefore);
|
||||
expect(mockedApi.delete).not.toHaveBeenCalled();
|
||||
expect(screen.getByLabelText("Current route")).toHaveTextContent("/jobs?q=platform");
|
||||
});
|
||||
|
||||
test("row status/edit/delete controls retain selection and do not open the workspace", async () => {
|
||||
show();
|
||||
const row = await screen.findByRole("row", { name: /platform engineer/i });
|
||||
fireEvent.click(screen.getByRole("button", { name: t.jobTableSelectionMode }));
|
||||
fireEvent.click(within(row).getByRole("checkbox"));
|
||||
fireEvent.click(within(row).getByRole("button", { name: `${t.jobTableEdit}: ${job.jobTitle}` }));
|
||||
expect(screen.getByRole("dialog", { name: "Edit job" })).toBeVisible();
|
||||
@@ -62,6 +87,11 @@ test.each(["en", "nb"] as const)("%s mobile row actions remain direct, named and
|
||||
const copy = translations[language];
|
||||
show("jobs", "?q=platform");
|
||||
const row = await screen.findByRole("link", { name: copy.jobTableOpenJob.replace("{title}", job.jobTitle).replace("{company}", job.company.name) });
|
||||
expect(within(row).queryByRole("checkbox")).not.toBeInTheDocument();
|
||||
const roleTitle = within(row).getByText(job.jobTitle);
|
||||
const employer = within(row).getByText(job.company.name);
|
||||
expect(roleTitle.compareDocumentPosition(employer) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole("button", { name: copy.jobTableSelectionMode }));
|
||||
fireEvent.click(within(row).getByRole("checkbox"));
|
||||
fireEvent.click(within(row).getByRole("button", { name: copy.jobTableEdit }));
|
||||
expect(screen.getByRole("dialog", { name: "Edit job" })).toBeVisible();
|
||||
@@ -118,6 +148,7 @@ async function compact() {
|
||||
test("density preserves selected rows, query, page, custom status, columns and listing link without refetch", async () => {
|
||||
show("jobs", "?q=platform&page=2&sortBy=company&sortDir=asc");
|
||||
const row = await screen.findByRole("row", { name: /open platform engineer/i });
|
||||
fireEvent.click(screen.getByRole("button", { name: t.jobTableSelectionMode }));
|
||||
const columns = within(row).getAllByRole("cell").length;
|
||||
fireEvent.click(within(row).getByRole("checkbox"));
|
||||
const before = calls().length;
|
||||
@@ -185,6 +216,7 @@ test("partial bulk failure refreshes successful rows and retains only failed vis
|
||||
show();
|
||||
const first = await screen.findByRole("row", { name: /open platform engineer/i });
|
||||
const second = screen.getByRole("row", { name: /open second role/i });
|
||||
fireEvent.click(screen.getByRole("button", { name: t.jobTableSelectionMode }));
|
||||
fireEvent.click(screen.getByRole("checkbox", { name: t.jobTableSelectAll }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Offer" }));
|
||||
await waitFor(() => expect(mockedApi.patch).toHaveBeenCalledTimes(2));
|
||||
@@ -193,6 +225,28 @@ test("partial bulk failure refreshes successful rows and retains only failed vis
|
||||
expect(calls().length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
test("a completed bulk request cannot restore a discarded selection", async () => {
|
||||
const secondJob = { ...job, id: 43, jobTitle: "Second role" };
|
||||
mockedApi.get.mockImplementation((url: string) => Promise.resolve({ data: url === "/companies" ? [job.company] : url === "/jobapplications" ? { items: [job, secondJob], total: 2, page: 1, pageSize: 15 } : [] } as any));
|
||||
let completeFirst!: () => void;
|
||||
mockedApi.patch.mockImplementation((url: string) => url.includes("/43/")
|
||||
? Promise.reject(new Error("Synthetic failure"))
|
||||
: new Promise(resolve => { completeFirst = () => resolve({ status: 204 } as any); }));
|
||||
show();
|
||||
await screen.findByRole("row", { name: /open second role/i });
|
||||
const selection = screen.getByRole("button", { name: t.jobTableSelectionMode });
|
||||
fireEvent.click(selection);
|
||||
fireEvent.click(screen.getByRole("checkbox", { name: t.jobTableSelectAll }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Offer" }));
|
||||
fireEvent.click(selection);
|
||||
fireEvent.click(selection);
|
||||
await act(async () => { completeFirst(); });
|
||||
await waitFor(() => expect(calls().length).toBeGreaterThan(1));
|
||||
const second = await screen.findByRole("row", { name: /open second role/i });
|
||||
expect(within(second).getByRole("checkbox")).not.toBeChecked();
|
||||
expect(screen.queryByRole("button", { name: t.jobTableDeleteSelected })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("saved view create/apply/delete stays scoped to the signed-in account", async () => {
|
||||
setAuthUserKey("account-a");
|
||||
show("jobs", "?q=platform&status=Interview");
|
||||
@@ -217,6 +271,7 @@ test("saved view create/apply/delete stays scoped to the signed-in account", asy
|
||||
test("changing the result set clears selection before an old row can be bulk-updated", async () => {
|
||||
show();
|
||||
const row = await screen.findByRole("row", { name: /platform engineer/i });
|
||||
fireEvent.click(screen.getByRole("button", { name: t.jobTableSelectionMode }));
|
||||
fireEvent.click(within(row).getByRole("checkbox"));
|
||||
expect(screen.getByText(t.jobTableSelected.replace("{count}", "1"))).toBeVisible();
|
||||
fireEvent.change(screen.getByRole("textbox", { name: t.jobTableSearch }), { target: { value: "different" } });
|
||||
|
||||
@@ -59,6 +59,7 @@ test("Table/Board switches preserve query, complete membership, page, density an
|
||||
await screen.findByText("16–26 of 26");
|
||||
expect(reads("/jobapplications/board")).toHaveLength(0);
|
||||
const pageIds = ids("[data-job-row-id]", "data-job-row-id");
|
||||
fireEvent.click(screen.getByRole("button", { name: t.jobTableSelectionMode }));
|
||||
fireEvent.click(screen.getAllByRole("checkbox", { name: /Select Engineer/ })[0]);
|
||||
fireEvent.mouseDown(screen.getByRole("combobox", { name: t.listDensity }));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "Compact" }));
|
||||
@@ -75,7 +76,8 @@ test("Table/Board switches preserve query, complete membership, page, density an
|
||||
await screen.findByText("16–26 of 26");
|
||||
expect(ids("[data-job-row-id]", "data-job-row-id")).toEqual(pageIds);
|
||||
expect(screen.getByRole("combobox", { name: t.listDensity })).toHaveTextContent("Compact");
|
||||
expect(screen.getAllByRole("checkbox", { name: /Select Engineer/ }).every(el => !(el as HTMLInputElement).checked)).toBe(true);
|
||||
expect(screen.queryByRole("checkbox", { name: /Select Engineer/ })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: t.jobTableSelectionMode })).toHaveAttribute("aria-pressed", "false");
|
||||
expect(reads("/jobapplications/board")).toHaveLength(1);
|
||||
fireEvent.click(screen.getByText("History back"));
|
||||
await waitFor(() => expect(document.querySelectorAll("[data-kanban-card-id]")).toHaveLength(26));
|
||||
|
||||
Reference in New Issue
Block a user