From 08bee8e138cc7091131cb7811e337a2cebd1b787 Mon Sep 17 00:00:00 2001 From: Marc Bucchieri Date: Mon, 29 Jun 2026 12:02:31 -0700 Subject: [PATCH 1/3] improve behavior and test coverage of export fields and sortable field list --- .../components/ExportForm/ExportForm.test.tsx | 318 ++++++++++++++++++ .../components/ExportForm/ExportForm.tsx | 37 +- .../SortableFieldList.module.scss | 20 +- .../SortableFieldList.test.tsx | 239 +++++++++++++ .../SortableFieldList/SortableFieldList.tsx | 35 +- src/features/exports/types/ExportForm.ts | 1 + .../InstancesExportForm/constants.ts | 106 +++--- 7 files changed, 695 insertions(+), 61 deletions(-) create mode 100644 src/features/exports/components/ExportForm/ExportForm.test.tsx create mode 100644 src/features/exports/components/SortableFieldList/SortableFieldList.test.tsx diff --git a/src/features/exports/components/ExportForm/ExportForm.test.tsx b/src/features/exports/components/ExportForm/ExportForm.test.tsx new file mode 100644 index 0000000000..1c91a370b8 --- /dev/null +++ b/src/features/exports/components/ExportForm/ExportForm.test.tsx @@ -0,0 +1,318 @@ +import { INPUT_DATE_FORMAT } from "@/constants"; +import { renderWithProviders } from "@/tests/render"; +import { screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import moment from "moment"; +import { describe, expect, it, vi } from "vitest"; +import ExportForm from "./ExportForm"; +import type { + ExportFieldGroup, + ExportFormValues, +} from "../../types/ExportForm"; + +const FIELD_GROUPS: readonly ExportFieldGroup[] = [ + { + title: "Primary Identity", + key: "primary-identity", + fields: [ + { id: "hostname", label: "Hostname" }, + { id: "title", label: "Instance name" }, + ], + }, + { + title: "Compliance", + key: "compliance", + fields: [ + { id: "securely_patched", label: "Securely patched" }, + { id: "time_to_patch_days", label: "Time to patch (days)" }, + ], + }, +]; + +const INITIAL_VALUES: ExportFormValues = { + name: "", + selectedFieldIds: [], + retainUntil: moment().add(3, "years").format(INPUT_DATE_FORMAT), +}; + +const renderForm = ( + overrides: Partial<{ + initialValues: ExportFormValues; + onGenerate: (args: { + values: ExportFormValues; + fieldsToExport: { id: string; label: string; groupTitle?: string }[]; + }) => Promise; + }> = {}, +) => { + const onGenerate = + overrides.onGenerate ?? vi.fn().mockResolvedValue(undefined); + + renderWithProviders( + , + ); + + return { onGenerate }; +}; + +const openAttributeGroup = async ( + user: ReturnType, + name: RegExp, +) => { + await user.click(screen.getByRole("tab", { name })); +}; + +describe("ExportForm", () => { + it("keeps Next disabled until an export name and at least one attribute are selected", async () => { + const user = userEvent.setup(); + renderForm(); + + const nextButton = screen.getByRole("button", { name: "Next" }); + + expect(nextButton).toHaveAttribute("aria-disabled", "true"); + + await user.type( + screen.getByRole("textbox", { name: "Export name" }), + "My export", + ); + + expect(nextButton).toHaveAttribute("aria-disabled", "true"); + + await openAttributeGroup(user, /primary identity/i); + await user.click(screen.getByRole("checkbox", { name: "Hostname" })); + + expect(nextButton).not.toHaveAttribute("aria-disabled", "true"); + }); + + it("filters attributes by search text and shows the empty state when nothing matches", async () => { + const user = userEvent.setup(); + renderForm(); + + const searchInput = screen.getByRole("searchbox", { + name: "Search attributes", + }); + + await user.type(searchInput, "securely"); + + expect( + screen.getByRole("tab", { name: /compliance/i }), + ).toBeInTheDocument(); + expect( + screen.queryByRole("tab", { name: /primary identity/i }), + ).not.toBeInTheDocument(); + + await user.clear(searchInput); + await user.type(searchInput, "does not exist"); + + expect( + screen.getByText("No attributes match your search."), + ).toBeInTheDocument(); + }); + + it("auto-expands matching groups so fields are visible without clicking the group header", async () => { + const user = userEvent.setup(); + renderForm(); + + expect( + screen.queryByRole("checkbox", { name: "Hostname" }), + ).not.toBeInTheDocument(); + + await user.type( + screen.getByRole("searchbox", { name: "Search attributes" }), + "host", + ); + + expect( + screen.getByRole("checkbox", { name: "Hostname" }), + ).toBeInTheDocument(); + }); + + it("auto-expands groups matched by group title so all their fields are visible", async () => { + const user = userEvent.setup(); + renderForm(); + + await user.type( + screen.getByRole("searchbox", { name: "Search attributes" }), + "compliance", + ); + + expect( + screen.getByRole("checkbox", { name: "Securely patched" }), + ).toBeInTheDocument(); + expect( + screen.getByRole("checkbox", { name: "Time to patch (days)" }), + ).toBeInTheDocument(); + }); + + it("selects and deselects all fields in a group from the group checkbox", async () => { + const user = userEvent.setup(); + const onGenerate = vi.fn().mockResolvedValue(undefined); + + renderForm({ onGenerate }); + + const exportName = screen.getByRole("textbox", { name: "Export name" }); + const nextButton = screen.getByRole("button", { name: "Next" }); + const groupSelectAll = screen.getByRole("checkbox", { + name: "Primary Identity", + }); + + await user.type(exportName, "Group selection export"); + await user.click(groupSelectAll); + + expect(nextButton).not.toHaveAttribute("aria-disabled", "true"); + + await user.click(nextButton); + await user.click(screen.getByRole("button", { name: "Generate TSV" })); + + await waitFor(() => { + expect(onGenerate).toHaveBeenCalledWith({ + values: expect.objectContaining({ + selectedFieldIds: ["hostname", "title"], + }), + fieldsToExport: [ + { id: "hostname", label: "Hostname", groupTitle: "Primary Identity" }, + { + id: "title", + label: "Instance name", + groupTitle: "Primary Identity", + }, + ], + }); + }); + + await user.click(screen.getByRole("button", { name: "Back" })); + await user.click( + screen.getByRole("checkbox", { name: "Primary Identity" }), + ); + + expect(nextButton).toHaveAttribute("aria-disabled", "true"); + }); + + it("moves to the reorder step on the first submit and returns to step 0 with Back", async () => { + const user = userEvent.setup(); + const { onGenerate } = renderForm(); + + await user.type( + screen.getByRole("textbox", { name: "Export name" }), + "Ordered export", + ); + await openAttributeGroup(user, /primary identity/i); + await user.click(screen.getByRole("checkbox", { name: "Hostname" })); + await user.click(screen.getByRole("button", { name: "Next" })); + + expect(onGenerate).not.toHaveBeenCalled(); + expect( + screen.getByRole("button", { name: "Generate TSV" }), + ).toBeInTheDocument(); + + await user.click(screen.getByRole("button", { name: "Back" })); + + expect(screen.getByRole("button", { name: "Next" })).toBeInTheDocument(); + expect( + screen.getByRole("textbox", { name: "Export name" }), + ).toBeInTheDocument(); + }); + + it("submits selected fields and form values on the second step", async () => { + const user = userEvent.setup(); + const onGenerate = vi.fn().mockResolvedValue(undefined); + + renderForm({ onGenerate }); + + await user.type( + screen.getByRole("textbox", { name: "Export name" }), + "Compliance export", + ); + await openAttributeGroup(user, /primary identity/i); + await user.click(screen.getByRole("checkbox", { name: "Hostname" })); + await openAttributeGroup(user, /compliance/i); + await user.click( + screen.getByRole("checkbox", { name: "Securely patched" }), + ); + + await user.click(screen.getByRole("button", { name: "Next" })); + await user.click(screen.getByRole("button", { name: "Generate TSV" })); + + await waitFor(() => { + expect(onGenerate).toHaveBeenCalledWith({ + values: expect.objectContaining({ + name: "Compliance export", + selectedFieldIds: ["hostname", "securely_patched"], + retainUntil: INITIAL_VALUES.retainUntil, + }), + fieldsToExport: [ + { id: "hostname", label: "Hostname", groupTitle: "Primary Identity" }, + { + id: "securely_patched", + label: "Securely patched", + groupTitle: "Compliance", + }, + ], + }); + }); + }); + + it("shows group badges and reset button on step 2", async () => { + const user = userEvent.setup(); + renderForm(); + + await user.type( + screen.getByRole("textbox", { name: "Export name" }), + "Badge test", + ); + await openAttributeGroup(user, /primary identity/i); + await user.click(screen.getByRole("checkbox", { name: "Hostname" })); + await openAttributeGroup(user, /compliance/i); + await user.click( + screen.getByRole("checkbox", { name: "Securely patched" }), + ); + await user.click(screen.getByRole("button", { name: "Next" })); + + expect(screen.getByText("Primary Identity")).toBeInTheDocument(); + expect(screen.getByText("Compliance")).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: /reset to default order/i }), + ).toBeInTheDocument(); + }); + + it("reset to default order restores group-declaration sequence after manual reorder", async () => { + const user = userEvent.setup(); + const onGenerate = vi.fn().mockResolvedValue(undefined); + + renderForm({ onGenerate }); + + await user.type( + screen.getByRole("textbox", { name: "Export name" }), + "Reset test", + ); + await openAttributeGroup(user, /primary identity/i); + await user.click(screen.getByRole("checkbox", { name: "Hostname" })); + await user.click(screen.getByRole("checkbox", { name: "Instance name" })); + await user.click(screen.getByRole("button", { name: "Next" })); + + await user.click( + screen.getByRole("button", { name: /move hostname down/i }), + ); + + await user.click( + screen.getByRole("button", { name: /reset to default order/i }), + ); + + await user.click(screen.getByRole("button", { name: "Generate TSV" })); + + await waitFor(() => { + expect(onGenerate).toHaveBeenCalledWith( + expect.objectContaining({ + fieldsToExport: [ + expect.objectContaining({ id: "hostname" }), + expect.objectContaining({ id: "title" }), + ], + }), + ); + }); + }); +}); diff --git a/src/features/exports/components/ExportForm/ExportForm.tsx b/src/features/exports/components/ExportForm/ExportForm.tsx index a3ce1de0d1..7b638be9b1 100644 --- a/src/features/exports/components/ExportForm/ExportForm.tsx +++ b/src/features/exports/components/ExportForm/ExportForm.tsx @@ -52,7 +52,9 @@ const ExportForm: FC = ({ validationSchema: VALIDATION_SCHEMA, onSubmit: async (values) => { const selectedFields = fieldGroups - .flatMap((group) => group.fields) + .flatMap((group) => + group.fields.map((field) => ({ ...field, groupTitle: group.title })), + ) .filter((field) => values.selectedFieldIds.includes(field.id)); if (step === 0) { @@ -165,6 +167,31 @@ const ExportForm: FC = ({ const selectedFieldIdsError = getFormikError(formik, "selectedFieldIds"); + const renderFieldGroups = () => { + if (!filteredFieldGroups.length) { + return ( +

No attributes match your search.

+ ); + } + + if (attributeSearch.trim()) { + return filteredFieldGroups.map((group) => { + const section = accordionSections.find((s) => s.key === group.key); + if (!section) return null; + return ( + + ); + }); + } + + return ; + }; + const stepContent = step === 0 ? ( <> @@ -206,13 +233,7 @@ const ExportForm: FC = ({ {selectedFieldIdsError}

)} - {filteredFieldGroups.length ? ( - - ) : ( -

- No attributes match your search. -

- )} + {renderFieldGroups()} ) : ( diff --git a/src/features/exports/components/SortableFieldList/SortableFieldList.module.scss b/src/features/exports/components/SortableFieldList/SortableFieldList.module.scss index bc47fc53d8..5a0b54d4ed 100644 --- a/src/features/exports/components/SortableFieldList/SortableFieldList.module.scss +++ b/src/features/exports/components/SortableFieldList/SortableFieldList.module.scss @@ -5,16 +5,34 @@ padding-top: $spv--small; } -.selectedColumnsIntro { +.selectedColumnsHeader { + align-items: flex-start; + display: flex; + justify-content: space-between; margin-bottom: $spv--medium; } +.selectedColumnsIntro { + margin-bottom: 0; +} + .nameCell { align-items: center; display: flex; gap: $sph--small; } +.groupBadge { + background: var(--vf-color-background-alt, #f5f5f5); + border: 1px solid var(--vf-color-border-default, #d9d9d9); + border-radius: 2px; + color: var(--vf-color-text-muted, #666); + font-size: 0.688rem; + margin-left: auto; + padding: 0 $sph--x-small; + white-space: nowrap; +} + .dragHandle { cursor: grab; flex-shrink: 0; diff --git a/src/features/exports/components/SortableFieldList/SortableFieldList.test.tsx b/src/features/exports/components/SortableFieldList/SortableFieldList.test.tsx new file mode 100644 index 0000000000..261f5bf253 --- /dev/null +++ b/src/features/exports/components/SortableFieldList/SortableFieldList.test.tsx @@ -0,0 +1,239 @@ +import { renderWithProviders } from "@/tests/render"; +import { screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { useState } from "react"; +import { describe, expect, it, vi } from "vitest"; +import SortableFieldList from "./SortableFieldList"; +import type { ExportField } from "../../types/ExportForm"; + +const FIELDS: ExportField[] = [ + { id: "hostname", label: "Hostname", groupTitle: "Primary Identity" }, + { id: "status", label: "Status", groupTitle: "Primary Identity" }, + { + id: "securely_patched", + label: "Securely patched", + groupTitle: "Compliance", + }, +]; + +describe("SortableFieldList", () => { + it("renders all field labels", () => { + renderWithProviders( + , + ); + + expect(screen.getByText("Hostname")).toBeInTheDocument(); + expect(screen.getByText("Status")).toBeInTheDocument(); + expect(screen.getByText("Securely patched")).toBeInTheDocument(); + }); + + it("renders group badges for each field", () => { + renderWithProviders( + , + ); + + const primaryBadges = screen.getAllByText("Primary Identity"); + expect(primaryBadges).toHaveLength(2); + expect(screen.getByText("Compliance")).toBeInTheDocument(); + }); + + it("does not render a group badge when groupTitle is absent", () => { + const fields: ExportField[] = [{ id: "hostname", label: "Hostname" }]; + + renderWithProviders( + , + ); + + expect(screen.queryByText("Primary Identity")).not.toBeInTheDocument(); + }); + + it("shows the Reset to default order button", () => { + renderWithProviders( + , + ); + + expect( + screen.getByRole("button", { name: /reset to default order/i }), + ).toBeInTheDocument(); + }); + + it("calls onOrderChange with original fields when Reset is clicked", async () => { + const user = userEvent.setup(); + const onOrderChange = vi.fn(); + + renderWithProviders( + , + ); + + await user.click( + screen.getByRole("button", { name: /move hostname down/i }), + ); + onOrderChange.mockClear(); + + await user.click( + screen.getByRole("button", { name: /reset to default order/i }), + ); + + expect(onOrderChange).toHaveBeenCalledWith(FIELDS); + }); + + it("restores the displayed row order after Reset is clicked", async () => { + const user = userEvent.setup(); + + renderWithProviders( + , + ); + + await user.click( + screen.getByRole("button", { name: /move hostname down/i }), + ); + + expect( + screen.getByRole("spinbutton", { name: /order for status/i }), + ).toHaveValue(1); + expect( + screen.getByRole("spinbutton", { name: /order for hostname/i }), + ).toHaveValue(2); + + await user.click( + screen.getByRole("button", { name: /reset to default order/i }), + ); + + expect( + screen.getByRole("spinbutton", { name: /order for hostname/i }), + ).toHaveValue(1); + expect( + screen.getByRole("spinbutton", { name: /order for status/i }), + ).toHaveValue(2); + }); + + it("moves a field up using the arrow button", async () => { + const user = userEvent.setup(); + const onOrderChange = vi.fn(); + + renderWithProviders( + , + ); + + await user.click(screen.getByRole("button", { name: /move status up/i })); + + expect(onOrderChange).toHaveBeenCalledWith([ + expect.objectContaining({ id: "status" }), + expect.objectContaining({ id: "hostname" }), + expect.objectContaining({ id: "securely_patched" }), + ]); + }); + + it("moves a field down using the arrow button", async () => { + const user = userEvent.setup(); + const onOrderChange = vi.fn(); + + renderWithProviders( + , + ); + + await user.click( + screen.getByRole("button", { name: /move hostname down/i }), + ); + + expect(onOrderChange).toHaveBeenCalledWith([ + expect.objectContaining({ id: "status" }), + expect.objectContaining({ id: "hostname" }), + expect.objectContaining({ id: "securely_patched" }), + ]); + }); + + it("disables the Up button for the first row and the Down button for the last row", () => { + renderWithProviders( + , + ); + + expect( + screen.getByRole("button", { name: /move hostname up/i }), + ).toHaveAttribute("aria-disabled", "true"); + expect( + screen.getByRole("button", { name: /move securely patched down/i }), + ).toHaveAttribute("aria-disabled", "true"); + }); + + it("reset restores original order even when parent state is kept in sync with each move", async () => { + const user = userEvent.setup(); + + // Mirror ExportForm's usage: parent updates fields on every onOrderChange call + const ControlledWrapper = () => { + const [fields, setFields] = useState(FIELDS); + return ; + }; + + renderWithProviders(); + + await user.click( + screen.getByRole("button", { name: /move hostname down/i }), + ); + + expect( + screen.getByRole("spinbutton", { name: /order for status/i }), + ).toHaveValue(1); + + await user.click( + screen.getByRole("button", { name: /reset to default order/i }), + ); + + expect( + screen.getByRole("spinbutton", { name: /order for hostname/i }), + ).toHaveValue(1); + expect( + screen.getByRole("spinbutton", { name: /order for status/i }), + ).toHaveValue(2); + }); + + it("renders the reorder instructions", () => { + renderWithProviders( + , + ); + + expect( + screen.getByText(/review and reorder the columns for your export/i), + ).toBeInTheDocument(); + }); + + it("moves a field to a specific position via the order input", async () => { + const user = userEvent.setup(); + const onOrderChange = vi.fn(); + + renderWithProviders( + , + ); + + const hostnameInput = screen.getByRole("spinbutton", { + name: /order for hostname/i, + }); + await user.clear(hostnameInput); + await user.type(hostnameInput, "3"); + await user.keyboard("{Enter}"); + + expect(onOrderChange).toHaveBeenCalledWith([ + expect.objectContaining({ id: "status" }), + expect.objectContaining({ id: "securely_patched" }), + expect.objectContaining({ id: "hostname" }), + ]); + }); + + it("ignores an out-of-range value entered in the order input", async () => { + const user = userEvent.setup(); + const onOrderChange = vi.fn(); + + renderWithProviders( + , + ); + + const hostnameInput = screen.getByRole("spinbutton", { + name: /order for hostname/i, + }); + await user.clear(hostnameInput); + await user.type(hostnameInput, "99"); + await user.keyboard("{Enter}"); + + expect(onOrderChange).not.toHaveBeenCalled(); + }); +}); diff --git a/src/features/exports/components/SortableFieldList/SortableFieldList.tsx b/src/features/exports/components/SortableFieldList/SortableFieldList.tsx index 1aee381dcf..6853539a86 100644 --- a/src/features/exports/components/SortableFieldList/SortableFieldList.tsx +++ b/src/features/exports/components/SortableFieldList/SortableFieldList.tsx @@ -28,6 +28,7 @@ interface ReorderRowData extends Record { index: number; fieldId: string; label: string; + groupTitle: string | undefined; currentOrder: string; } @@ -73,6 +74,10 @@ const SortableFieldList: FC = ({ const orderedFieldsRef = useRef(orderedFields); const draggingFieldIdRef = useRef(draggingFieldId); + // Capture the field list as it exists on first render so Reset can always + // return to the group-declaration order, regardless of how many moves the + // parent state has accumulated since then. + const initialFieldsRef = useRef(fields); // Stable per-row ref callbacks, cached by fieldId, so the row's ref doesn't // churn (null then re-set) on every render. @@ -95,6 +100,12 @@ const SortableFieldList: FC = ({ }); usePendingFieldScroll({ orderedFields, pendingScrollRef, rowRefsMap }); + const handleResetOrder = useCallback(() => { + setOrderedFields([...initialFieldsRef.current]); + setOrderDrafts({}); + onOrderChange([...initialFieldsRef.current]); + }, [onOrderChange]); + const triggerMoveEffect = useCallback((fieldId: string) => { if (justMovedTimerRef.current) clearTimeout(justMovedTimerRef.current); setJustMovedFieldId(fieldId); @@ -338,11 +349,14 @@ const SortableFieldList: FC = ({ Header: "Attribute name", accessor: "fieldId", Cell: ({ row }: CellProps) => { - const { label } = row.original; + const { label, groupTitle } = row.original; return (
{label} + {groupTitle && ( + {groupTitle} + )}
); }, @@ -421,6 +435,7 @@ const SortableFieldList: FC = ({ index, fieldId: field.id, label: field.label, + groupTitle: field.groupTitle, currentOrder: orderDrafts[index] ?? String(index + 1), })), [orderDrafts, orderedFields], @@ -484,10 +499,20 @@ const SortableFieldList: FC = ({ return (
-

- Review and reorder the columns for your export. Drag rows or use the - controls to change the order. -

+
+

+ Review and reorder the columns for your export. Drag rows or use the + controls to change the order. +

+ +
Date: Tue, 30 Jun 2026 08:45:15 -0700 Subject: [PATCH 2/3] address design feedback --- .../SortableFieldList.module.scss | 20 ++++++++----------- .../SortableFieldList/SortableFieldList.tsx | 19 ++++++++++++++---- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/features/exports/components/SortableFieldList/SortableFieldList.module.scss b/src/features/exports/components/SortableFieldList/SortableFieldList.module.scss index 5a0b54d4ed..5cc4d50b66 100644 --- a/src/features/exports/components/SortableFieldList/SortableFieldList.module.scss +++ b/src/features/exports/components/SortableFieldList/SortableFieldList.module.scss @@ -6,33 +6,29 @@ } .selectedColumnsHeader { - align-items: flex-start; + align-items: baseline; display: flex; + gap: $sph--large; justify-content: space-between; margin-bottom: $spv--medium; } .selectedColumnsIntro { + flex: 1; margin-bottom: 0; } +.resetButton { + flex-shrink: 0; + white-space: nowrap; +} + .nameCell { align-items: center; display: flex; gap: $sph--small; } -.groupBadge { - background: var(--vf-color-background-alt, #f5f5f5); - border: 1px solid var(--vf-color-border-default, #d9d9d9); - border-radius: 2px; - color: var(--vf-color-text-muted, #666); - font-size: 0.688rem; - margin-left: auto; - padding: 0 $sph--x-small; - white-space: nowrap; -} - .dragHandle { cursor: grab; flex-shrink: 0; diff --git a/src/features/exports/components/SortableFieldList/SortableFieldList.tsx b/src/features/exports/components/SortableFieldList/SortableFieldList.tsx index 6853539a86..b12ecdebc4 100644 --- a/src/features/exports/components/SortableFieldList/SortableFieldList.tsx +++ b/src/features/exports/components/SortableFieldList/SortableFieldList.tsx @@ -1,4 +1,10 @@ -import { Button, Icon, Input, ModularTable } from "@canonical/react-components"; +import { + Button, + Chip, + Icon, + Input, + ModularTable, +} from "@canonical/react-components"; import classNames from "classnames"; import { useCallback, @@ -355,7 +361,12 @@ const SortableFieldList: FC = ({ {label} {groupTitle && ( - {groupTitle} + )}
); @@ -506,11 +517,11 @@ const SortableFieldList: FC = ({

Date: Tue, 30 Jun 2026 09:33:58 -0700 Subject: [PATCH 3/3] fix failing test --- .../ActivitiesExportForm/ActivitiesExportForm.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/features/activities/components/ActivitiesExportForm/ActivitiesExportForm.test.tsx b/src/features/activities/components/ActivitiesExportForm/ActivitiesExportForm.test.tsx index 48c0cac2d3..948c21f16b 100644 --- a/src/features/activities/components/ActivitiesExportForm/ActivitiesExportForm.test.tsx +++ b/src/features/activities/components/ActivitiesExportForm/ActivitiesExportForm.test.tsx @@ -74,7 +74,6 @@ describe("ActivitiesExportForm", () => { "creator", ); - await openAttributeGroup(user, /audit & time/i); expect( screen.getByRole("checkbox", { name: "Creator" }), ).toBeInTheDocument();