diff --git a/apps/vr-tests-react-components/package.json b/apps/vr-tests-react-components/package.json index 61614353572ef..d94c76f2ef254 100644 --- a/apps/vr-tests-react-components/package.json +++ b/apps/vr-tests-react-components/package.json @@ -49,6 +49,7 @@ "@fluentui/react-progress": "*", "@fluentui/react-provider": "*", "@fluentui/react-radio": "*", + "@fluentui/react-rating-preview": "*", "@fluentui/react-search-preview": "*", "@fluentui/react-select": "*", "@fluentui/react-shared-contexts": "*", diff --git a/apps/vr-tests-react-components/src/stories/Rating.stories.tsx b/apps/vr-tests-react-components/src/stories/Rating.stories.tsx new file mode 100644 index 0000000000000..e5fef1f5696b6 --- /dev/null +++ b/apps/vr-tests-react-components/src/stories/Rating.stories.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; +import { Steps, StoryWright } from 'storywright'; +import { storiesOf } from '@storybook/react'; +import { CircleRegular, CircleFilled, SquareRegular, SquareFilled } from '@fluentui/react-icons'; +import { Rating } from '@fluentui/react-rating-preview'; +import { TestWrapperDecoratorFixedWidth } from '../utilities/TestWrapperDecorator'; + +storiesOf('Rating Converged', module) + .addDecorator(TestWrapperDecoratorFixedWidth) + .addDecorator(story => ( + + {story()} + + )) + .addStory('Neutral Rating with half star', () => , { + includeHighContrast: true, + includeDarkMode: true, + }) + .addStory('Brand Rating with half star', () => , { + includeHighContrast: true, + includeDarkMode: true, + }) + .addStory('Marigold Rating with half star', () => , { + includeHighContrast: true, + includeDarkMode: true, + }); + +storiesOf('Rating Converged', module) + .addDecorator(TestWrapperDecoratorFixedWidth) + .addDecorator(story => ( + {story()} + )) + .addStory('Rating size small', () => , {}) + .addStory('Rating size medium', () => , {}) + .addStory('Rating size large', () => , {}) + .addStory('Rating size extra-large', () => , {}); + +storiesOf('Rating Converged', module) + .addDecorator(TestWrapperDecoratorFixedWidth) + .addDecorator(story => ( + {story()} + )) + .addStory( + 'Rating with custom circle icons', + () => } iconOutline={} defaultValue={3.5} />, + {}, + ) + .addStory( + 'Rating with custom square icons', + () => } iconOutline={} defaultValue={3.5} />, + {}, + ); diff --git a/apps/vr-tests-react-components/src/stories/RatingDisplay.stories.tsx b/apps/vr-tests-react-components/src/stories/RatingDisplay.stories.tsx new file mode 100644 index 0000000000000..ccfd4dda9b8df --- /dev/null +++ b/apps/vr-tests-react-components/src/stories/RatingDisplay.stories.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { storiesOf } from '@storybook/react'; +import { CircleFilled, SquareFilled } from '@fluentui/react-icons'; +import { RatingDisplay } from '@fluentui/react-rating-preview'; +import { TestWrapperDecoratorFixedWidth } from '../utilities/TestWrapperDecorator'; +import { Steps, StoryWright } from 'storywright'; + +storiesOf('RatingDisplay Converged', module) + .addDecorator(TestWrapperDecoratorFixedWidth) + .addDecorator(story => ( + {story()} + )) + .addStory('no value', () => ) + .addStory('size small with value and count', () => ) + .addStory('size medium with value and count', () => ) + .addStory('size large with value and count', () => ) + .addStory('size extra-large with value and count', () => ) + .addStory('custom circle icons', () => } value={3} />) + .addStory('custom square icons', () => } value={3} />) + .addStory('rounded up', () => ) + .addStory('rounded down', () => ) + .addStory('Neutral with half value', () => , { + includeHighContrast: true, + includeDarkMode: true, + }) + .addStory('Brand with half value', () => , { + includeHighContrast: true, + includeDarkMode: true, + }) + .addStory('Marigold with half value', () => , { + includeHighContrast: true, + includeDarkMode: true, + }) + .addStory('Compact', () => , {}); diff --git a/change/@fluentui-react-rating-preview-53ee592c-ae0d-4b3b-9828-2c08ee70057e.json b/change/@fluentui-react-rating-preview-53ee592c-ae0d-4b3b-9828-2c08ee70057e.json new file mode 100644 index 0000000000000..60c0507244e1e --- /dev/null +++ b/change/@fluentui-react-rating-preview-53ee592c-ae0d-4b3b-9828-2c08ee70057e.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: add accesibility props to Rating and RatingDisplay", + "packageName": "@fluentui/react-rating-preview", + "email": "ololubek@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-rating-preview/src/components/Rating/Rating.test.tsx b/packages/react-components/react-rating-preview/src/components/Rating/Rating.test.tsx index 1d7da749a3300..8c4b6bb5017c9 100644 --- a/packages/react-components/react-rating-preview/src/components/Rating/Rating.test.tsx +++ b/packages/react-components/react-rating-preview/src/components/Rating/Rating.test.tsx @@ -1,15 +1,56 @@ import * as React from 'react'; import { render } from '@testing-library/react'; -// import { isConformant } from '../../testing/isConformant'; +import { isConformant } from '../../testing/isConformant'; import { Rating } from './Rating'; describe('Rating', () => { - // isConformant({ - // Component: Rating, - // displayName: 'Rating', - // }); - it('renders a default state', () => { - const result = render(Default RatingItem); - expect(result.container).toMatchSnapshot(); + isConformant({ + Component: Rating, + displayName: 'Rating', + // TODO re-enable consistent-callback-args once https://github.com/microsoft/fluentui/pull/30376 is merged + disabledTests: ['consistent-callback-args'], + }); + it('respects a default value', () => { + const { getAllByRole } = render(); + const checkedItems = getAllByRole('radio').filter(item => (item as HTMLInputElement).checked); + expect(checkedItems[0].getAttribute('value')).toBe('3'); + expect(checkedItems.length).toEqual(1); + }); + it('only sets the selected rating item to checked', () => { + const { getAllByRole } = render(); + const checkedItems = getAllByRole('radio').filter(item => (item as HTMLInputElement).checked); + expect(checkedItems[0].getAttribute('value')).toBe('3'); + expect(checkedItems.length).toEqual(1); + }); + it('renders the correct number of items', () => { + const { getAllByRole } = render(); + const items = getAllByRole('radio'); + expect(items.length).toEqual(10); + }); + it('calle onChange when a rating is clicked', () => { + const onChange = jest.fn(); + const { getAllByRole } = render(); + const items = getAllByRole('radio'); + items[0].click(); + expect(onChange).toHaveBeenCalledTimes(1); + }); + it('does not call onChange when a rating is clicked and the value is the same', () => { + const onChange = jest.fn(); + const { getAllByRole } = render(); + const items = getAllByRole('radio'); + items[2].click(); + expect(onChange).toHaveBeenCalledTimes(0); + }); + it('calls onChange with the correct value when a rating is clicked', () => { + const onChange = jest.fn(); + const { getAllByRole } = render(); + const items = getAllByRole('radio'); + items[3].click(); + items[2].click(); + items[1].click(); + expect(onChange).toHaveBeenCalledTimes(3); + expect(onChange.mock.calls[0][1].value).toBe(4); + expect(onChange.mock.calls[1][1].value).toBe(3); + expect(onChange.mock.calls[2][1].value).toBe(2); }); }); diff --git a/packages/react-components/react-rating-preview/src/components/Rating/__snapshots__/Rating.test.tsx.snap b/packages/react-components/react-rating-preview/src/components/Rating/__snapshots__/Rating.test.tsx.snap deleted file mode 100644 index 4cae3c2a39910..0000000000000 --- a/packages/react-components/react-rating-preview/src/components/Rating/__snapshots__/Rating.test.tsx.snap +++ /dev/null @@ -1,12 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Rating renders a default state 1`] = ` -
-
- Default RatingItem -
-
-`; diff --git a/packages/react-components/react-rating-preview/src/components/RatingDisplay/RatingDisplay.test.tsx b/packages/react-components/react-rating-preview/src/components/RatingDisplay/RatingDisplay.test.tsx index 2a1f88f7cb36b..9fdcea6235e6a 100644 --- a/packages/react-components/react-rating-preview/src/components/RatingDisplay/RatingDisplay.test.tsx +++ b/packages/react-components/react-rating-preview/src/components/RatingDisplay/RatingDisplay.test.tsx @@ -1,18 +1,41 @@ import * as React from 'react'; import { render } from '@testing-library/react'; -//import { isConformant } from '../../testing/isConformant'; +import { isConformant } from '../../testing/isConformant'; import { RatingDisplay } from './RatingDisplay'; describe('RatingDisplay', () => { - // isConformant({ - // Component: RatingDisplay, - // displayName: 'RatingDisplay', - // }); - - // TODO add more tests here, and create visual regression tests in /apps/vr-tests - - it('renders a default state', () => { - const result = render(Default RatingDisplay); - expect(result.container).toMatchSnapshot(); + isConformant({ + Component: RatingDisplay, + displayName: 'RatingDisplay', + requiredProps: { count: 1160, value: 4.5 }, + }); + it('renders the correct number of items', () => { + const { getByRole } = render(); + expect(getByRole('img').children.length).toEqual(10); + }); + it('renders the valueText slot when a value is provided', () => { + const { getByText } = render(); + const valueText = getByText('3'); + expect(valueText).toBeDefined(); + expect(valueText.classList.contains('fui-RatingDisplay__valueText')).toBeTruthy(); + }); + it('does not render the valueText slot when a value is not provided', () => { + const { container } = render(); + expect(container?.querySelector('.fui-RatingDisplay__valueText')).toBeNull(); + }); + it('renders the countText slot when a count is provided', () => { + const { getByText } = render(); + const countText = getByText('1,160'); + expect(countText).toBeDefined(); + expect(countText.classList.contains('fui-RatingDisplay__countText')).toBeTruthy(); + }); + it('does not render the countText slot when a count is not provided', () => { + const { container } = render(); + expect(container?.querySelector('.fui-RatingDisplay__countText')).toBeNull(); + }); + it('renders only one item when compact is true', () => { + const { getByRole } = render(); + const items = getByRole('img'); + expect(items.getElementsByClassName('fui-RatingItem').length).toEqual(1); }); }); diff --git a/packages/react-components/react-rating-preview/src/components/RatingDisplay/__snapshots__/RatingDisplay.test.tsx.snap b/packages/react-components/react-rating-preview/src/components/RatingDisplay/__snapshots__/RatingDisplay.test.tsx.snap deleted file mode 100644 index 47f6019ebf1ba..0000000000000 --- a/packages/react-components/react-rating-preview/src/components/RatingDisplay/__snapshots__/RatingDisplay.test.tsx.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RatingDisplay renders a default state 1`] = ` -
- -
-`; diff --git a/packages/react-components/react-rating-preview/src/components/RatingItem/RatingItem.test.tsx b/packages/react-components/react-rating-preview/src/components/RatingItem/RatingItem.test.tsx index 28f019552d428..d51bb8926ada1 100644 --- a/packages/react-components/react-rating-preview/src/components/RatingItem/RatingItem.test.tsx +++ b/packages/react-components/react-rating-preview/src/components/RatingItem/RatingItem.test.tsx @@ -1,10 +1,112 @@ import * as React from 'react'; import { render } from '@testing-library/react'; +import { isConformant } from '../../testing/isConformant'; import { RatingItem } from './RatingItem'; +import { RatingItemProvider } from '../../contexts/RatingItemContext'; +import { RatingItemContextValue } from './RatingItem.types'; +import { StarFilled, StarRegular } from '@fluentui/react-icons'; describe('RatingItem', () => { - it('renders a default state', () => { - const result = render(Default RatingItem); - expect(result.container).toMatchSnapshot(); + isConformant({ + Component: RatingItem, + displayName: 'RatingItem', + // Need to disable this test because certain slots are not rendered without specific context values + disabledTests: ['component-has-static-classnames-object'], + }); + it('does not render input elements when interactive is false', () => { + const contextValues: RatingItemContextValue = { + value: 3, + interactive: false, + iconFilled: , + iconOutline: , + color: 'neutral', + step: 1, + size: 'medium', + }; + const { queryAllByRole } = render( + + + , + ); + const inputs = queryAllByRole('radio'); + expect(inputs.length).toEqual(0); + }); + it('renders only the full value input when step is 1 and interactive is true', () => { + const contextValues: RatingItemContextValue = { + value: 3, + interactive: true, + iconFilled: , + iconOutline: , + color: 'neutral', + step: 1, + size: 'medium', + }; + const { getByRole } = render( + + + , + ); + const input = getByRole('radio') as HTMLInputElement; + expect(input.value).toEqual('2'); + }); + it('renders both the full value input and the half value input when step is 0.5 and interactive is true', () => { + const contextValues: RatingItemContextValue = { + value: 3, + interactive: true, + iconFilled: , + iconOutline: , + color: 'neutral', + step: 0.5, + size: 'medium', + }; + const { getAllByRole } = render( + + + , + ); + const inputs = getAllByRole('radio') as HTMLInputElement[]; + expect(inputs.length).toEqual(2); + expect(inputs[0].value).toEqual('1.5'); + expect(inputs[1].value).toEqual('2'); + }); + it('sets the half value input to checked when the value is 2.5', () => { + const contextValues: RatingItemContextValue = { + value: 2.5, + interactive: true, + iconFilled: , + iconOutline: , + color: 'neutral', + step: 0.5, + size: 'medium', + }; + const { queryAllByRole } = render( + + + , + ); + const inputs = queryAllByRole('radio') as HTMLInputElement[]; + const checkedInputs = inputs.filter(input => input.checked); + expect(checkedInputs.length).toEqual(1); + expect(checkedInputs[0].value).toEqual(contextValues.value?.toString()); + }); + it('sets the full value input to checked when the value is 3', () => { + const contextValues: RatingItemContextValue = { + value: 3, + interactive: true, + iconFilled: , + iconOutline: , + color: 'neutral', + step: 1, + size: 'medium', + }; + const { queryAllByRole } = render( + + + , + ); + const inputs = queryAllByRole('radio') as HTMLInputElement[]; + const checkedInputs = inputs.filter(input => input.checked); + expect(checkedInputs.length).toEqual(1); + expect(checkedInputs[0].value).toEqual(contextValues.value?.toString()); }); }); diff --git a/packages/react-components/react-rating-preview/src/components/RatingItem/__snapshots__/RatingItem.test.tsx.snap b/packages/react-components/react-rating-preview/src/components/RatingItem/__snapshots__/RatingItem.test.tsx.snap deleted file mode 100644 index 22912565ed2bc..0000000000000 --- a/packages/react-components/react-rating-preview/src/components/RatingItem/__snapshots__/RatingItem.test.tsx.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RatingItem renders a default state 1`] = ` -
- - - -
-`;