UNPKG

@shopware-ag/meteor-component-library

Version:

The meteor component library is a Vue component library developed by Shopware. It is based on the [Meteor Design System](https://shopware.design/).

456 lines (388 loc) 18 kB
import meta, { type MtEntityDataTableMeta } from "./mt-entity-data-table.stories"; import type { StoryObj } from "@storybook/vue3"; import { within, userEvent, waitFor, expect, fn, screen } from "@storybook/test"; import { waitUntil } from "../../../_internal/test-helper"; import EntityCollection from "@shopware-ag/meteor-admin-sdk/es/_internals/data/EntityCollection"; import Criteria from "@shopware-ag/meteor-admin-sdk/es/data/Criteria"; import type Repository from "@shopware-ag/meteor-admin-sdk/es/data/repository"; import repositoryMockData from "./_mocks/repositoryMock"; import productFixtures from "../../table-and-list/mt-data-table/mt-data-table.fixtures.json"; // Define a minimal ApiContext interface locally if direct import is troublesome interface MinimalApiContext { apiPath?: string; apiResourcePath?: string; assetsPath?: string; authToken?: string | null; basePath?: string; host?: string; installationPath?: string; languageId?: string; liveVersionId?: string; port?: number; scheme?: string; schemeAndHttpHost?: string; version?: string; inheritance?: boolean; [key: string]: any; // Allow other properties for flexibility if SDK uses them internally } export default { ...meta, title: "Components/mt-entity-data-table/Interaction tests", tags: ["!autodocs"], } as MtEntityDataTableMeta; type MtEntityDataTableStory = StoryObj<MtEntityDataTableMeta>; const waitForLoadingComplete = async (canvasElement: HTMLElement) => { await waitUntil(() => !canvasElement.querySelector(".mt-skeleton-bar"), 5000); await new Promise((resolve) => setTimeout(resolve, 200)); }; const createMockApiContext = (): MinimalApiContext => ({ apiPath: "/api", apiResourcePath: "/api", assetsPath: "/assets", authToken: null, basePath: "", host: "localhost", installationPath: "", languageId: "2fbb5fe2e29a4d70aa5854ce7ce3e20c", liveVersionId: "0fa91ce3e96a4bc2be4bd9ce752c3425", port: 80, scheme: "http", schemeAndHttpHost: "http://localhost", version: "v3", inheritance: false, }); const emptyRepositoryFactory = (entityName: string) => { const criteria = new Criteria(1, 25); const collection = new EntityCollection( `/api/${entityName.replace(/_/g, "-")}`, entityName, createMockApiContext() as any, // Cast to any if MinimalApiContext is not perfectly matching SDK's internal ApiContext criteria, [], 0, ); return { search: fn().mockResolvedValue(collection), get: fn().mockResolvedValue(null), save: fn().mockResolvedValue({}), delete: fn().mockResolvedValue(undefined), clone: fn().mockResolvedValue({}), create: fn().mockReturnValue({}), changesetGenerator: { generate: fn().mockReturnValue({}) }, syncService: { sync: fn().mockResolvedValue({}) }, schema: { properties: {} }, }; }; // Basic Rendering and Visual States export const VisualTestRenderTable: MtEntityDataTableStory = { name: "Should render the table with initial data", play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); await expect(canvas.getByText(args.title ?? "Products")).toBeInTheDocument(); await expect(canvas.getByText("Fantastic Cotton Sausages")).toBeInTheDocument(); }, }; export const VisualTestRenderFullLayout: MtEntityDataTableStory = { name: 'Should render in full layout when layout="full"', args: { layout: "full", }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); await expect(canvas.getByText(args.title ?? "Products")).toBeInTheDocument(); await expect(canvas.getByText("Fantastic Cotton Sausages")).toBeInTheDocument(); }, }; export const TestRenderEmptyState: MtEntityDataTableStory = { name: "Should render the empty state when repository returns no data", args: { repository: fn(emptyRepositoryFactory) as unknown as typeof Repository, availableFilters: [], title: "Empty Products Table", }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); await expect(canvas.getByText("Currently no items are available yet.")).toBeInTheDocument(); }, }; // Data Operations export const InteractionTestPaginationChangePage: MtEntityDataTableStory = { name: "Should change page and fetch new data on pagination page change", args: { repository: fn(repositoryMockData) as unknown as typeof Repository, }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); // Check if the first item is visible await expect(canvas.getByText("Fantastic Cotton Sausages")).toBeInTheDocument(); // Click on the next page button const nextPageButton = canvas.getByRole("button", { name: /Next page|Go to page 2/ }); await userEvent.click(nextPageButton); await waitForLoadingComplete(canvasElement); // Check if the first item on the second page is visible await expect(canvas.getByText("Practical Wooden Keyboard")).toBeInTheDocument(); }, }; export const InteractionTestPaginationChangeLimit: MtEntityDataTableStory = { name: "Should change item limit and fetch new data on pagination limit change", args: { repository: fn(repositoryMockData) as unknown as typeof Repository, paginationOptions: [5, 10, 25], }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); // Check if 6. item is visible await expect(canvas.getByText("Awesome Concrete Chair")).toBeInTheDocument(); // Change limit to 5 const limitSelectComponent = await canvas.getByLabelText(/Items per page/i); const chevronIcon = within(limitSelectComponent).getByTestId("mt-select__select-indicator"); await userEvent.click(chevronIcon); // Wait for the specific option to appear and then click it const optionToSelect = await waitFor(() => screen.findByTestId("mt-select-option--5")); await userEvent.click(optionToSelect); // Wait for the loading to complete await waitForLoadingComplete(canvasElement); // Check if 5. item is visible await expect(canvas.getByText("Practical Concrete Pizza")).toBeInTheDocument(); // Check if 6. item is not visible await expect(canvas.queryByText("Awesome Concrete Chair")).not.toBeInTheDocument(); }, }; export const InteractionTestSorting: MtEntityDataTableStory = { name: "Should sort data and refetch when a sortable column header is clicked", args: { repository: fn(repositoryMockData) as unknown as typeof Repository, }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); // Click on the specific settings trigger for the 'Manufacturer' column const settingsTriggerManufacturer = canvas.getByTestId( "column-settings-trigger__manufacturer.name", ); await userEvent.click(settingsTriggerManufacturer); // Popover interaction const popoverContent = await waitFor( () => document.querySelector('.mt-floating-ui__content[data-show="true"]'), { timeout: 3000 }, ); const popover = within(popoverContent as HTMLElement); const sortDescButton = await popover.findByText(/Sort descending/i); // This is a label in the HTML await userEvent.click(sortDescButton); // Wait for the loading to complete await waitUntil(() => !document.querySelector(".mt-skeleton-bar")); await waitUntil(() => !canvas.queryByText("McDermott, Walsh and McGlynn")); // Check if the correct items are visible await expect(canvas.getByText("Willms - Waelchi")).toBeInTheDocument(); }, }; export const InteractionTestSearch: MtEntityDataTableStory = { name: "Should filter data via search term and refetch", args: { repository: fn(repositoryMockData) as unknown as typeof Repository, }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); // Search for "Concrete" const searchInput = canvas.getByPlaceholderText(/Search/i); await userEvent.type(searchInput, "Handmade Granite Mouse"); // Wait for the search to complete await waitUntil(() => !canvas.queryByText("Fantastic Cotton Sausages")); await waitForLoadingComplete(canvasElement); // Check if the correct items are visible await waitUntil(() => canvas.getByText("Handmade Granite Mouse")); await expect(canvas.getByText("Handmade Granite Mouse")).toBeInTheDocument(); }, }; // Filtering export const InteractionTestApplyBooleanFilter: MtEntityDataTableStory = { name: "Should apply a boolean filter (e.g., Active) and update/refetch data", args: { repository: fn(repositoryMockData) as unknown as typeof Repository, }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); // Open "Add filter" button which opens the filter dialog const addFilterButton = canvas.getByRole("button", { name: /Add filter/i }); await userEvent.click(addFilterButton); // Open "Active" filter in the opened dialog await waitFor(() => document.querySelector('[role="dialog"]')); const dialogContent = within(document.querySelector('[role="dialog"]') as HTMLElement); const activeFilter = await dialogContent.findByText("Active"); await userEvent.click(activeFilter); // Wait for the loading to complete, e.g. "Manufacturer" filter // is not visible anymore inside the dialog await waitUntil(() => !dialogContent.queryByText("Manufacturer")); // Select "Active" option which has the role "menuitem" const activeOption = await dialogContent.findByRole("menuitem", { name: "Active" }); await userEvent.click(activeOption); // Wait for the loading to complete await waitUntil(() => !canvas.queryByText("Intelligent Plastic Pants")); await waitUntil(() => !document.querySelector(".mt-skeleton-bar")); // Check if the correct items are visible await expect(canvas.getByText("Fantastic Plastic Hat")).toBeInTheDocument(); }, }; export const InteractionTestApplyMultiSelectFilter: MtEntityDataTableStory = { name: "Should apply a multi-select filter (e.g., Manufacturer) and update/refetch data", args: { repository: fn(repositoryMockData) as unknown as typeof Repository, }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); // Open "Add filter" button which opens the filter dialog const addFilterButton = canvas.getByRole("button", { name: /Add filter/i }); await userEvent.click(addFilterButton); // Open "Manufacturer" filter in the opened dialog await waitFor(() => document.querySelector('[role="dialog"]')); const dialogContent = within(document.querySelector('[role="dialog"]') as HTMLElement); const manufacturerFilter = await dialogContent.findByText("Manufacturer"); await userEvent.click(manufacturerFilter); // Select "Gibson - Predovic" option which has the role "menuitem" const manufacturerOption = await dialogContent.findByRole("menuitem", { name: "Gibson - Predovic", }); await userEvent.click(manufacturerOption); // Wait for the loading to complete await waitUntil(() => !canvas.queryByText("Intelligent Plastic Pants")); await waitUntil(() => !document.querySelector(".mt-skeleton-bar")); // Check if the correct items are visible inside the first table row const tableContent = within( document.querySelector(".mt-data-table__table-wrapper") as HTMLElement, ); await expect(tableContent.getByText("Gibson - Predovic")).toBeInTheDocument(); }, }; export const InteractionTestClearFilter: MtEntityDataTableStory = { name: "Should clear the applied filter and update/refetch data", args: { repository: fn(repositoryMockData) as unknown as typeof Repository, }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); // Open "Add filter" button which opens the filter dialog const addFilterButton = canvas.getByRole("button", { name: /Add filter/i }); await userEvent.click(addFilterButton); // Open "Manufacturer" filter in the opened dialog await waitFor(() => document.querySelector('[role="dialog"]')); const dialogContent = within(document.querySelector('[role="dialog"]') as HTMLElement); const manufacturerFilter = await dialogContent.findByText("Manufacturer"); await userEvent.click(manufacturerFilter); // Select "Gibson - Predovic" option which has the role "menuitem" const manufacturerOption = await dialogContent.findByRole("menuitem", { name: "Gibson - Predovic", }); await userEvent.click(manufacturerOption); // Wait for the loading to complete await waitUntil(() => !canvas.queryByText("Intelligent Plastic Pants")); await waitUntil(() => !document.querySelector(".mt-skeleton-bar")); // Check if the correct items are visible inside the first table row const tableContent = within( document.querySelector(".mt-data-table__table-wrapper") as HTMLElement, ); await expect(tableContent.getByText("Gibson - Predovic")).toBeInTheDocument(); // Clear the filter const clearFilterButton = await canvas.findByText("Remove filter"); await userEvent.click(clearFilterButton); // Wait until "Remove filter" button is not visible anymore await waitFor(() => !canvas.queryByText("Remove filter")); // Wait for the loading to complete await waitUntil(() => !canvas.queryByText("Gibson - Predovic")); await waitUntil(() => !document.querySelector(".mt-skeleton-bar")); // Check if the correct items are visible inside the first table row await expect(tableContent.getByText("Fantastic Cotton Sausages")).toBeInTheDocument(); }, }; // Event Emissions export const InteractionTestEmitOpenDetails: MtEntityDataTableStory = { name: 'Should emit "onOpenDetails" with row data on details action', args: { onOpenDetails: fn(), }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); const firstProductNameCell = await waitFor(() => canvas.getByText("Fantastic Cotton Sausages")); await userEvent.click(firstProductNameCell); expect(args.onOpenDetails).toHaveBeenCalledWith( productFixtures.find((product) => product.name === "Fantastic Cotton Sausages"), ); }, }; export const InteractionTestEmitBulkEdit: MtEntityDataTableStory = { name: 'Should emit "onBulkEdit" with selected IDs on bulk edit action', args: { allowRowSelection: true, allowBulkEdit: true, onBulkEdit: fn(), repository: fn(repositoryMockData) as unknown as typeof Repository, }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); const rowCheckboxes = await waitFor(() => canvas.getAllByRole("checkbox")); await userEvent.click(rowCheckboxes[0]); // Wait until "25 items selected" is visible await waitFor(() => canvas.getByText("25 items selected")); // Click on "Edit" button const editButton = await waitFor(() => canvas.getByRole("button", { name: /Edit/i })); await userEvent.click(editButton); // Check if "onBulkEdit" event was emitted with the correct IDs expect(args.onBulkEdit).toHaveBeenCalledTimes(1); expect(args.onBulkEdit).toHaveBeenCalledWith( // Get the first 25 product IDs productFixtures.map((product) => product.id).slice(0, 25), ); }, }; export const InteractionTestEmitBulkDelete: MtEntityDataTableStory = { name: 'Should emit "onBulkDelete" with selected IDs on bulk delete action', args: { allowRowSelection: true, allowBulkDelete: true, onBulkDelete: fn(), repository: fn(repositoryMockData) as unknown as typeof Repository, }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await waitForLoadingComplete(canvasElement); const rowCheckboxes = await waitFor(() => canvas.getAllByRole("checkbox")); await userEvent.click(rowCheckboxes[0]); // Wait until "25 items selected" is visible await waitFor(() => canvas.getByText("25 items selected")); // Click on "Delete" button const deleteButton = await waitFor(() => canvas.getByRole("button", { name: /Delete/i })); await userEvent.click(deleteButton); // Check if "onBulkDelete" event was emitted with the correct IDs expect(args.onBulkDelete).toHaveBeenCalledTimes(1); expect(args.onBulkDelete).toHaveBeenCalledWith( productFixtures.map((product) => product.id).slice(0, 25), ); }, }; export const VisualTestDeleteModal: MtEntityDataTableStory = { name: "Should render the delete modal", play: async ({ canvasElement, args }) => { await waitForLoadingComplete(canvasElement); // Click on context button in first row const firstRow = within( canvasElement.querySelector("table > tbody > tr:nth-child(1)") as HTMLElement, ); const contextButton = await firstRow.findByRole("button", { name: /Context menu/i }); await userEvent.click(contextButton); // Click on "Delete" menuItem in context menu const deleteMenuItem = await screen.findByRole("menuitem", { name: /Delete/i }); await userEvent.click(deleteMenuItem); // Wait for the delete modal to be visible await waitFor(() => screen.getByText("Are you sure you want to delete the selected items?")); // Do nothing anymore because we want to check the visual state of the modal }, };