Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 62 additions & 39 deletions storybook/src/admin/data-grid/ClickableRows.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,24 @@
import { type GridColDef, StackLink, StackPage, StackSwitch, Toolbar, ToolbarBackButton, ToolbarTitleItem, useStackSwitchApi } from "@comet/admin";
import { Edit } from "@comet/admin-icons";
import { IconButton } from "@mui/material";
import { type GridColDef, StackPage, StackSwitch, Toolbar, ToolbarBackButton, ToolbarTitleItem } from "@comet/admin";
import { DataGrid } from "@mui/x-data-grid";
import { type StoryObj } from "@storybook/react-webpack5";
import { expect, within } from "storybook/test";

import { exampleColumns, exampleRows } from "../../helpers/ExampleDataGrid";
import { stackRouteDecorator } from "../../helpers/storyDecorators";
import { storyRouterDecorator } from "../../story-router.decorator";
import { useEditGridRow } from "./utils/useEditGridRow";

export default {
title: "@comet/admin/data-grid",
title: "@comet/admin/data-grid/Clickable Rows",
decorators: [stackRouteDecorator(), storyRouterDecorator()],
};

export const ClickableRows = () => {
export const Story = () => {
// Grid must be a separate component to make sure the stackSwitchApi context is correct
const Grid = () => {
const stackSwitchApi = useStackSwitchApi();

const columns: GridColDef[] = [
...exampleColumns,
{
field: "actions",
type: "actions",
headerName: "",
width: 52,
renderCell: (params) => (
<IconButton color="primary" component={StackLink} pageName="edit" payload={params.row.id}>
<Edit />
</IconButton>
),
},
];

return (
<DataGrid
rows={exampleRows}
columns={columns}
onRowClick={(params) => {
stackSwitchApi.activatePage("edit", params.row.id);
}}
/>
);
const { handleDataGridRowClick, actionsCell } = useEditGridRow();
const columns: GridColDef[] = [...exampleColumns, actionsCell];
return <DataGrid rows={exampleRows} columns={columns} onRowClick={handleDataGridRowClick} />;
};

return (
Expand All @@ -49,15 +27,60 @@ export const ClickableRows = () => {
<Grid />
</StackPage>
<StackPage name="edit">
{(id) => (
<Toolbar>
<ToolbarBackButton />
<ToolbarTitleItem>
Editing item with id: <code>{id}</code>
</ToolbarTitleItem>
</Toolbar>
)}
{(id) => {
const row = exampleRows.find((row) => row.id.toString() === id);
if (!row) return null;

return (
<Toolbar>
<ToolbarBackButton />
<ToolbarTitleItem>
Editing {row.firstName} {row.lastName} (ID: {id})
</ToolbarTitleItem>
</Toolbar>
);
}}
</StackPage>
</StackSwitch>
);
};

export const TestClickingEditButton: StoryObj<typeof Story> = {
render: Story,
play: async ({ canvas, userEvent, step }) => {
await step("Click on edit button", async () => {
const gridRowsContainer = canvas.getByRole("rowgroup");
const firstRow = within(gridRowsContainer).getAllByRole("row")[0];
const firstCell = within(firstRow).getAllByRole("gridcell")[0];
const firstName = firstCell.textContent;

const editButton = within(firstRow).getByLabelText(/edit/i);
await userEvent.click(editButton);

const editText = await within(document.body).findByText(new RegExp(`Editing ${firstName}`));

expect(editText).toBeInTheDocument();
expect(firstCell).not.toBeInTheDocument();
expect(editButton).not.toBeInTheDocument();
});
},
};

export const TestClickingRow: StoryObj<typeof Story> = {
render: Story,
play: async ({ canvas, userEvent, step }) => {
await step("Click on row (first cell)", async () => {
const gridRowsContainer = canvas.getByRole("rowgroup");
const firstRow = within(gridRowsContainer).getAllByRole("row")[0];
const firstCell = within(firstRow).getAllByRole("gridcell")[0];
const firstName = firstCell.textContent;

await userEvent.click(firstCell);

const editText = await within(document.body).findByText(new RegExp(`Editing ${firstName}`));

expect(editText).toBeInTheDocument();
expect(firstCell).not.toBeInTheDocument();
});
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import { type GridColDef, StackPage, StackSwitch, Toolbar, ToolbarBackButton, ToolbarTitleItem } from "@comet/admin";
import { Favorite } from "@comet/admin-icons";
import { IconButton, InputBase } from "@mui/material";
import { DataGrid } from "@mui/x-data-grid";
import { type StoryObj } from "@storybook/react-webpack5";
import { expect, waitFor, within } from "storybook/test";

import { exampleColumns, exampleRows } from "../../helpers/ExampleDataGrid";
import { stackRouteDecorator } from "../../helpers/storyDecorators";
import { storyRouterDecorator } from "../../story-router.decorator";
import { DummyTextInContentOverflow } from "./utils/DummyTextInContentOverflow";
import { DummyVisibilitySelect } from "./utils/DummyVisibilitySelect";
import { useEditGridRow } from "./utils/useEditGridRow";

export default {
title: "@comet/admin/data-grid/Clickable Rows With Interactive Cells",
decorators: [stackRouteDecorator(), storyRouterDecorator()],
};

export const Story = () => {
// Grid must be a separate component to make sure the stackSwitchApi context is correct
const Grid = () => {
const { handleDataGridRowClick, EditIconButton } = useEditGridRow();

const columns: GridColDef[] = [
...exampleColumns,
{
field: "input",
type: "actions",
headerName: "Input",
renderCell: () => <InputBase aria-label="Text Input" />,
flex: 1,
},
{
field: "description",
type: "actions",
headerName: "Description",
renderCell: () => <DummyTextInContentOverflow />,
flex: 2,
},
{
field: "visible",
type: "actions",
headerName: "Visible",
width: 120,
renderCell: () => <DummyVisibilitySelect />,
},
{
field: "actions",
type: "actions",
headerName: "",
width: 84,
renderCell: (params) => (
<>
<IconButton color="secondary" aria-label="Add to favorites">
<Favorite />
</IconButton>
<EditIconButton id={params.row.id} />
</>
),
},
];
return <DataGrid checkboxSelection rows={exampleRows} columns={columns} onRowClick={handleDataGridRowClick} />;
};

return (
<StackSwitch>
<StackPage name="grid">
<Grid />
</StackPage>
<StackPage name="edit">
{(id) => {
const row = exampleRows.find((row) => row.id.toString() === id);
if (!row) return null;

return (
<Toolbar>
<ToolbarBackButton />
<ToolbarTitleItem>
Editing {row.firstName} {row.lastName} (ID: {id})
</ToolbarTitleItem>
</Toolbar>
);
}}
</StackPage>
</StackSwitch>
);
};

export const TestClickingEditButton: StoryObj<typeof Story> = {
render: Story,
play: async ({ canvas, userEvent, step }) => {
await step("Click on edit button", async () => {
const gridRowsContainer = canvas.getByRole("rowgroup");
const firstRow = within(gridRowsContainer).getAllByRole("row")[0];
const firstNameCell = within(firstRow).getAllByRole("gridcell")[1];
const firstName = firstNameCell.textContent;

const editButton = within(firstRow).getByLabelText(/edit/i);
await userEvent.click(editButton);

const toolbarEditText = await within(document.body).findByText(new RegExp(`Editing ${firstName}`));

expect(toolbarEditText).toBeInTheDocument();
expect(firstNameCell).not.toBeInTheDocument();
expect(editButton).not.toBeInTheDocument();
});
},
};

export const TestClickingRow: StoryObj<typeof Story> = {
render: Story,
play: async ({ canvas, userEvent, step }) => {
await step("Click on row (first cell)", async () => {
const gridRowsContainer = canvas.getByRole("rowgroup");
const firstRow = within(gridRowsContainer).getAllByRole("row")[0];
const firstNameCell = within(firstRow).getAllByRole("gridcell")[1];
const firstName = firstNameCell.textContent;

await userEvent.click(firstNameCell);

const toolbarEditText = await within(document.body).findByText(new RegExp(`Editing ${firstName}`));

expect(toolbarEditText).toBeInTheDocument();
expect(firstNameCell).not.toBeInTheDocument();
});
},
};

export const TestClickingCheckbox: StoryObj<typeof Story> = {
render: Story,
play: async ({ canvas, userEvent, step }) => {
await step("Click on checkbox", async () => {
const gridRowsContainer = canvas.getByRole("rowgroup");
const firstRow = within(gridRowsContainer).getAllByRole("row")[0];

const inputTypeCheckbox = within(firstRow).getByRole("checkbox");
await userEvent.click(inputTypeCheckbox);

expect(firstRow).toBeInTheDocument();
expect(inputTypeCheckbox).toBeChecked();

const toolbarEditText = within(document.body).queryByText(new RegExp(`Editing`));
expect(toolbarEditText).toBeNull();
});
},
};

export const TestClickingFavoriteButton: StoryObj<typeof Story> = {
render: Story,
play: async ({ canvas, userEvent, step }) => {
await step("Click on favorite button", async () => {
const gridRowsContainer = canvas.getByRole("rowgroup");
const firstRow = within(gridRowsContainer).getAllByRole("row")[0];

const favoriteButton = within(firstRow).getByLabelText(/add to favorites/i);
await userEvent.click(favoriteButton);

expect(firstRow).toBeInTheDocument();

const toolbarEditText = within(document.body).queryByText(new RegExp(`Editing`));
expect(toolbarEditText).toBeNull();
});
},
};

export const TestSettingInvisible: StoryObj<typeof Story> = {
render: Story,
play: async ({ canvas, userEvent, step }) => {
const gridRowsContainer = canvas.getByRole("rowgroup");
const firstRow = within(gridRowsContainer).getAllByRole("row")[0];
const selectVisibilityButton = within(firstRow).getByLabelText(/select visibility/i);

await step("Open visibility menu", async () => {
await userEvent.click(selectVisibilityButton);

expect(firstRow).toBeInTheDocument();
const toolbarEditText = within(document.body).queryByText(new RegExp(`Editing`));
expect(toolbarEditText).toBeNull();
});
await step("Click on invisible option", async () => {
const menu = await waitFor(() => within(document.body).getByRole("menu"), { timeout: 4000 });
const invisibleOption = within(menu).getByText(/invisible/i);
await userEvent.click(invisibleOption);

expect(firstRow).toBeInTheDocument();
expect(selectVisibilityButton).toHaveTextContent(/invisible/i);
const toolbarEditText = within(document.body).queryByText(new RegExp(`Editing`));
expect(toolbarEditText).toBeNull();
});
},
};

export const TestOpeningContentOverflow: StoryObj<typeof Story> = {
render: Story,
play: async ({ canvas, userEvent, step }) => {
const startTextOfContentOverflow = "Ornare Inceptos Egestas Bibendum";

await step("Open visibility menu", async () => {
const gridRowsContainer = canvas.getByRole("rowgroup");
const firstRow = within(gridRowsContainer).getAllByRole("row")[0];

const buttonsInRow = within(firstRow).getAllByRole("button");
const descriptionButton = buttonsInRow.find((button) => button.textContent?.includes(startTextOfContentOverflow));

if (!descriptionButton) {
throw new Error("Description button not found in row");
}

await userEvent.click(descriptionButton);

expect(firstRow).toBeInTheDocument();
const toolbarEditText = within(document.body).queryByText(new RegExp(`Editing`));
expect(toolbarEditText).toBeNull();

const dialog = await waitFor(() => within(document.body).getByRole("dialog"), { timeout: 4000 });
expect(dialog).toBeInTheDocument();

const dialogTitleText = within(dialog).queryByText(new RegExp(`${startTextOfContentOverflow}`));
expect(dialogTitleText).toBeInTheDocument();
});
},
};

export const TestInteractingWithInput: StoryObj<typeof Story> = {
render: Story,
play: async ({ canvas, userEvent, step }) => {
const gridRowsContainer = canvas.getByRole("rowgroup");
const firstRow = within(gridRowsContainer).getAllByRole("row")[0];
const inputWrapper = within(firstRow).getByLabelText(/text input/i);
const input = within(inputWrapper).getByRole("textbox");

await step("Click on input", async () => {
await userEvent.click(inputWrapper);
expect(input).toHaveFocus();
expect(firstRow).toBeInTheDocument();

const toolbarEditText = within(document.body).queryByText(new RegExp(`Editing`));
expect(toolbarEditText).toBeNull();
});
await step("Type in input", async () => {
await userEvent.type(input, "Hello");
expect(input).toHaveValue("Hello");
expect(firstRow).toBeInTheDocument();

const toolbarEditText = within(document.body).queryByText(new RegExp(`Editing`));
expect(toolbarEditText).toBeNull();
});
},
};
19 changes: 19 additions & 0 deletions storybook/src/admin/data-grid/utils/DummyTextInContentOverflow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ContentOverflow } from "@comet/admin";
import { Typography } from "@mui/material";

export const DummyTextInContentOverflow = () => {
const paragraphsCount = 5;
return (
<ContentOverflow>
<Typography fontWeight={600} gutterBottom>
Ornare Inceptos Egestas Bibendum
</Typography>
{Array.from({ length: paragraphsCount }).map((_, index) => (
<Typography key={index} variant="body2" gutterBottom={index !== paragraphsCount - 1}>
Curabitur blandit tempus porttitor. Nullam id dolor id nibh ultricies vehicula ut id elit. Cras mattis consectetur purus sit amet
fermentum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
</Typography>
))}
</ContentOverflow>
);
};
Loading