Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { composeStories } from '@storybook/react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { axe } from 'jest-axe';
import type { ReactNode } from 'react';
import { FormProvider, useForm } from 'react-hook-form';

import AdminABACRoomAttributesForm, { type AdminABACRoomAttributesFormFormData } from './AdminABACRoomAttributesForm';
import * as stories from './AdminABACRoomAttributesForm.stories';

const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName || 'Story', Story]);

const appRoot = mockAppRoot()
.withTranslations('en', 'core', {
Name: 'Name',
Values: 'Values',
Add_Value: 'Add Value',
Cancel: 'Cancel',
Save: 'Save',
Required_field: '{{field}} is required',
})
.build();

const FormProviderWrapper = ({
children,
defaultValues,
}: {
children: ReactNode;
defaultValues?: Partial<AdminABACRoomAttributesFormFormData>;
}) => {
const methods = useForm<AdminABACRoomAttributesFormFormData>({
defaultValues: {
name: '',
attributeValues: [{ value: '' }],
lockedAttributes: [],
...defaultValues,
},
mode: 'onChange',
});

return <FormProvider {...methods}>{children}</FormProvider>;
};

describe('AdminABACRoomAttributesForm', () => {
const defaultProps = {
onSave: jest.fn(),
onCancel: jest.fn(),
description: 'Create an attribute that can later be assigned to rooms.',
};

beforeEach(() => {
jest.clearAllMocks();
});

test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => {
const { baseElement } = render(<Story />, { wrapper: appRoot });

expect(baseElement).toMatchSnapshot();
});

test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => {
const { container } = render(<Story />, { wrapper: appRoot });

const results = await axe(container);
expect(results).toHaveNoViolations();
});

it('should show validation errors for required fields', async () => {
render(
<FormProviderWrapper>
<AdminABACRoomAttributesForm {...defaultProps} />
</FormProviderWrapper>,
{ wrapper: appRoot },
);

const saveButton = screen.getByRole('button', { name: 'Save' });
await userEvent.click(saveButton);

await waitFor(() => {
expect(screen.getByText('Name is required')).toBeInTheDocument();
});
});

it('should show validation error for empty attribute values', async () => {
render(
<FormProviderWrapper>
<AdminABACRoomAttributesForm {...defaultProps} />
</FormProviderWrapper>,
{ wrapper: appRoot },
);

const nameInput = screen.getByLabelText('Name*');
await userEvent.type(nameInput, 'Test Attribute');

const saveButton = screen.getByRole('button', { name: 'Save' });
await userEvent.click(saveButton);

await waitFor(() => {
expect(screen.getByText('Values is required')).toBeInTheDocument();
});
});

it('should add new attribute value when Add Value button is clicked', async () => {
const defaultValues = {
name: 'Test Attribute',
attributeValues: [{ value: 'Value 1' }],
};

render(
<FormProviderWrapper defaultValues={defaultValues}>
<AdminABACRoomAttributesForm {...defaultProps} />
</FormProviderWrapper>,
{ wrapper: appRoot },
);

const addButton = screen.getByRole('button', { name: 'Add Value' });
await userEvent.click(addButton);

const valueInputs = screen.getAllByLabelText('Values*');
expect(valueInputs).toHaveLength(2);
});

it('should remove attribute value when trash button is clicked', async () => {
const defaultValues = {
name: 'Test Attribute',
attributeValues: [{ value: 'Value 1' }, { value: 'Value 2' }],
};

render(
<FormProviderWrapper defaultValues={defaultValues}>
<AdminABACRoomAttributesForm {...defaultProps} />
</FormProviderWrapper>,
{ wrapper: appRoot },
);

const trashButtons = screen.getAllByRole('button', { name: 'Remove' });
expect(screen.getByDisplayValue('Value 1')).toBeInTheDocument();
expect(screen.getByDisplayValue('Value 2')).toBeInTheDocument();

await userEvent.click(trashButtons[0]);

expect(screen.getByDisplayValue('Value 1')).toBeInTheDocument();
expect(screen.queryByDisplayValue('Value 2')).not.toBeInTheDocument();
});

it('should remove locked attribute when trash button is clicked', async () => {
const defaultValues = {
name: 'Room Type',
lockedAttributes: [{ value: 'Locked Value 1' }, { value: 'Locked Value 2' }],
};

render(
<FormProviderWrapper defaultValues={defaultValues}>
<AdminABACRoomAttributesForm {...defaultProps} />
</FormProviderWrapper>,
{ wrapper: appRoot },
);

const trashButtons = screen.queryAllByRole('button', { name: 'Remove' });

expect(screen.getByDisplayValue('Locked Value 1')).toBeInTheDocument();
expect(screen.getByDisplayValue('Locked Value 2')).toBeInTheDocument();

await userEvent.click(trashButtons[0]);

expect(screen.getByDisplayValue('Locked Value 1')).toBeInTheDocument();
expect(screen.queryByDisplayValue('Locked Value 2')).not.toBeInTheDocument();
});

it('should disable Add Value button when there are empty values', async () => {
render(
<FormProviderWrapper>
<AdminABACRoomAttributesForm {...defaultProps} />
</FormProviderWrapper>,
{ wrapper: appRoot },
);

const addButton = screen.getByRole('button', { name: 'Add Value' });
expect(addButton).toBeDisabled();
});

it('should enable Add Value button when all values are filled', async () => {
const defaultValues = {
name: 'Test Attribute',
attributeValues: [{ value: 'Value 1' }],
};

render(
<FormProviderWrapper defaultValues={defaultValues}>
<AdminABACRoomAttributesForm {...defaultProps} />
</FormProviderWrapper>,
{ wrapper: appRoot },
);

const addButton = screen.getByRole('button', { name: 'Add Value' });
expect(addButton).not.toBeDisabled();
});

it('should call onSave with correct data when form is submitted', async () => {
const defaultValues = {
name: 'Test Attribute',
attributeValues: [{ value: 'Value 1' }, { value: 'Value 2' }],
};

render(
<FormProviderWrapper defaultValues={defaultValues}>
<AdminABACRoomAttributesForm {...defaultProps} />
</FormProviderWrapper>,
{ wrapper: appRoot },
);

const trashButtons = screen.queryAllByRole('button', { name: 'Remove' });
await userEvent.click(trashButtons[0]);

const saveButton = screen.getByRole('button', { name: 'Save' });
await userEvent.click(saveButton);

await waitFor(() => {
expect(defaultProps.onSave).toHaveBeenCalled();
});
});

it('should call onCancel when Cancel button is clicked', async () => {
render(
<FormProviderWrapper>
<AdminABACRoomAttributesForm {...defaultProps} />
</FormProviderWrapper>,
{ wrapper: appRoot },
);

const cancelButton = screen.getByRole('button', { name: 'Cancel' });
await userEvent.click(cancelButton);

expect(defaultProps.onCancel).toHaveBeenCalled();
});

it('should handle mixed locked and regular attributes correctly', async () => {
const defaultValues = {
name: 'Room Type',
lockedAttributes: [{ value: 'Locked Value' }],
attributeValues: [{ value: 'Regular Value' }],
};

render(
<FormProviderWrapper defaultValues={defaultValues}>
<AdminABACRoomAttributesForm {...defaultProps} />
</FormProviderWrapper>,
{ wrapper: appRoot },
);

expect(screen.getByDisplayValue('Locked Value')).toBeDisabled();
expect(screen.getByDisplayValue('Regular Value')).not.toBeDisabled();

const trashButtons = screen.getAllByRole('button', { name: 'Remove' });
expect(trashButtons).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Contextualbar } from '@rocket.chat/fuselage';
import { mockAppRoot } from '@rocket.chat/mock-providers';
import type { Meta, StoryFn } from '@storybook/react';
import { FormProvider, useForm } from 'react-hook-form';

import AdminABACRoomAttributesForm, { type AdminABACRoomAttributesFormFormData } from './AdminABACRoomAttributesForm';

export default {
component: AdminABACRoomAttributesForm,
parameters: {
layout: 'padded',
},
args: {
description: 'Create an attribute that can later be assigned to rooms.',
},
decorators: [mockAppRoot().buildStoryDecorator()],
} satisfies Meta<typeof AdminABACRoomAttributesForm>;

const Template: StoryFn<typeof AdminABACRoomAttributesForm> = (args) => <AdminABACRoomAttributesForm {...args} />;

export const NewAttribute = Template.bind({});

NewAttribute.decorators = [
(fn) => {
const methods = useForm<AdminABACRoomAttributesFormFormData>({
defaultValues: {
name: '',
attributeValues: [{ value: '' }],
lockedAttributes: [],
},
mode: 'onChange',
});

return (
<FormProvider {...methods}>
<Contextualbar width='400px' p={16}>
{fn()}
</Contextualbar>
</FormProvider>
);
},
];

export const WithLockedAttributes = Template.bind({});

WithLockedAttributes.args = {
description: 'Attribute values cannot be edited, but can be added or deleted.',
};

WithLockedAttributes.decorators = [
(fn) => {
const methods = useForm<AdminABACRoomAttributesFormFormData>({
defaultValues: {
name: 'Room Type',
lockedAttributes: [{ value: 'Locked Value 1' }, { value: 'Locked Value 2' }, { value: 'Locked Value 3' }],
},
mode: 'onChange',
});

return (
<FormProvider {...methods}>
<Contextualbar width='400px' p={16}>
{fn()}
</Contextualbar>
</FormProvider>
);
},
];
Loading
Loading