diff --git a/package-lock.json b/package-lock.json index f2143866..6011ae37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "@swc/core": "^1.15.11", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^14.6.1", "@testplane/storybook": "^1.7.4", "@testplane/testing-library": "^1.0.2", "@types/jest": "^30.0.0", @@ -13533,6 +13534,23 @@ "ts-dedent": "^2.2.0" } }, + "node_modules/@storybook/testing-library/node_modules/@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@storybook/theming": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.5.16.tgz", @@ -14218,15 +14236,13 @@ } }, "node_modules/@testing-library/user-event": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", - "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5" - }, + "license": "MIT", "engines": { - "node": ">=10", + "node": ">=12", "npm": ">=6" }, "peerDependencies": { @@ -38346,21 +38362,6 @@ } } }, - "node_modules/storybook/node_modules/@testing-library/user-event": { - "version": "14.6.1", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", - "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" - } - }, "node_modules/storybook/node_modules/@vitest/spy": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", @@ -52998,6 +52999,17 @@ "@testing-library/dom": "^8.3.0", "@testing-library/user-event": "^13.2.1", "ts-dedent": "^2.2.0" + }, + "dependencies": { + "@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + } + } } }, "@storybook/theming": { @@ -53399,13 +53411,11 @@ } }, "@testing-library/user-event": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", - "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", "dev": true, - "requires": { - "@babel/runtime": "^7.12.5" - } + "requires": {} }, "@testplane/devtools": { "version": "8.32.5", @@ -71266,14 +71276,6 @@ "ws": "^8.18.0" }, "dependencies": { - "@testing-library/user-event": { - "version": "14.6.1", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", - "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", - "dev": true, - "peer": true, - "requires": {} - }, "@vitest/spy": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", diff --git a/package.json b/package.json index a1089e00..dbe74a45 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@swc/core": "^1.15.11", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^14.6.1", "@testplane/storybook": "^1.7.4", "@testplane/testing-library": "^1.0.2", "@types/jest": "^30.0.0", diff --git a/src/entities/Article/ui/ArticleList/ArticleList.tsx b/src/entities/Article/ui/ArticleList/ArticleList.tsx index a88bab17..ab44511e 100644 --- a/src/entities/Article/ui/ArticleList/ArticleList.tsx +++ b/src/entities/Article/ui/ArticleList/ArticleList.tsx @@ -15,6 +15,7 @@ interface ArticleListProps { isLoading?: boolean; view?: ArticleView; target?: HTMLAttributeAnchorTarget; + virtualized?: boolean; } const CARD_WIDTH = 250; @@ -36,6 +37,7 @@ export const ArticleList = ({ isLoading, view = ArticleView.LIST, target, + virtualized, }: ArticleListProps) => { const { t } = useTranslation('article'); const [containerWidth, setContainerWidth] = useState(0); @@ -132,18 +134,30 @@ export const ArticleList = ({ cls[view], ])} > - + {virtualized ? ( + + ) : ( + articles.map((item) => ( + + )) + )} {isLoading && getSkeletons(view)} ); diff --git a/src/entities/Profile/ui/ProfileCard/ProfileCard.tsx b/src/entities/Profile/ui/ProfileCard/ProfileCard.tsx index 0a7aea9e..2f6ba9c1 100644 --- a/src/entities/Profile/ui/ProfileCard/ProfileCard.tsx +++ b/src/entities/Profile/ui/ProfileCard/ProfileCard.tsx @@ -103,12 +103,14 @@ export const ProfileCard = ({ placeholder={t('Your name')} readOnly={readonly} onChange={onChangeFirstname} + data-testid="ProfileCard.firstname" /> ({ - getArticleRecommendationsList: build.query({ + getArticleRecommendationsList: build.query({ query: (limit) => ({ url: 'articles', params: { diff --git a/src/features/ArticleRecommendationsList/ui/ArticleRecommendationsList/ArticleRecommendationsList.tsx b/src/features/ArticleRecommendationsList/ui/ArticleRecommendationsList/ArticleRecommendationsList.tsx index f32c27cd..2a5b4f00 100644 --- a/src/features/ArticleRecommendationsList/ui/ArticleRecommendationsList/ArticleRecommendationsList.tsx +++ b/src/features/ArticleRecommendationsList/ui/ArticleRecommendationsList/ArticleRecommendationsList.tsx @@ -26,11 +26,11 @@ export const ArticleRecommendationsList = memo( } = getArticleRecommendationsList(getRandomNumber()); if (isLoading) { - ; + return ; } - if (error) { - ; + if (error || !articles) { + return ; } return ( @@ -41,6 +41,7 @@ export const ArticleRecommendationsList = memo( articles={articles} isLoading={isLoading} target="_blank" + virtualized={false} /> ); diff --git a/src/features/EditableProfileCard/ui/EditableProfileCard/EditableProfileCard.test.tsx b/src/features/EditableProfileCard/ui/EditableProfileCard/EditableProfileCard.test.tsx new file mode 100644 index 00000000..3459404b --- /dev/null +++ b/src/features/EditableProfileCard/ui/EditableProfileCard/EditableProfileCard.test.tsx @@ -0,0 +1,130 @@ +import { componentRender } from 'shared/lib/tests/componentRouter/componentRouter'; +import { Profile } from 'entities/Profile'; +import { Currency } from 'entities/Currency'; +import { Country } from 'entities/Country'; +import { userEvent } from '@testing-library/user-event'; +import { screen } from '@testing-library/react'; +import { $api } from 'shared/api/api'; +import { profileReducer } from '../../model/slices/profileSlice'; +import { EditableProfileCard } from './EditableProfileCard'; + +const profile: Profile = { + id: '1', + firstname: 'admin', + lastname: 'admin', + age: 22, + currency: Currency.EUR, + country: Country.Belarus, + city: 'Minsk', + username: 'adminchik', +}; + +const options = { + initialState: { + profile: { + readonly: true, + data: profile, + form: profile, + }, + user: { + authData: { + id: '1', + username: 'admin', + }, + }, + }, + asyncReducers: { + profile: profileReducer, + }, +}; + +describe('features/EditableProfileCard', () => { + beforeEach(() => componentRender(, options)); + + test('Should be Edit mode form', async () => { + await userEvent.click( + screen.getByTestId('EditableProfileCardHeader.EditButton'), + ); + expect(screen.getByTestId('EditableProfileCardHeader.CancelButton')); + }); + + test('Reset data in the form when click cancel button', async () => { + await userEvent.click( + screen.getByTestId('EditableProfileCardHeader.EditButton'), + ); + + // Clear the data in inputs + await userEvent.clear(screen.getByTestId('ProfileCard.firstname')); + await userEvent.clear(screen.getByTestId('ProfileCard.lastname')); + await userEvent.clear(screen.getByTestId('ProfileCard.age')); + + // Type the another data in inputs + await userEvent.type( + screen.getByTestId('ProfileCard.firstname'), + 'user', + ); + await userEvent.type( + screen.getByTestId('ProfileCard.lastname'), + 'user', + ); + await userEvent.type(screen.getByTestId('ProfileCard.age'), '20'); + + // Check the another data in inputs + expect(screen.getByTestId('ProfileCard.firstname')).toHaveValue('user'); + expect(screen.getByTestId('ProfileCard.lastname')).toHaveValue('user'); + expect(screen.getByTestId('ProfileCard.age')).toHaveValue('20'); + + // Click the cancel button for reset another data in inputs + await userEvent.click( + screen.getByTestId('EditableProfileCardHeader.CancelButton'), + ); + + // And check the initialdatas in inputs + expect(screen.getByTestId('ProfileCard.firstname')).toHaveValue( + profile.firstname, + ); + expect(screen.getByTestId('ProfileCard.lastname')).toHaveValue( + profile.lastname, + ); + expect(screen.getByTestId('ProfileCard.age')).toHaveValue( + String(profile.age), + ); + }); + + test('Should be display error', async () => { + await userEvent.click( + screen.getByTestId('EditableProfileCardHeader.EditButton'), + ); + + await userEvent.clear(screen.getByTestId('ProfileCard.firstname')); + + expect(screen.getByTestId('ProfileCard.firstname')).toHaveValue(''); + + await userEvent.click( + screen.getByTestId('EditableProfileCardHeader.SaveButton'), + ); + + expect( + screen.getByTestId('EditableProfileCard.Error.Paragraph'), + ).toBeInTheDocument(); + }); + + test('Put request method for success data', async () => { + const mockPutrequest = jest.spyOn($api, 'put'); + + await userEvent.click( + screen.getByTestId('EditableProfileCardHeader.EditButton'), + ); + + await userEvent.type( + screen.getByTestId('ProfileCard.firstname'), + 'user', + ); + + await userEvent.click( + screen.getByTestId('EditableProfileCardHeader.SaveButton'), + ); + + expect(mockPutrequest).toHaveBeenCalled(); + }); +}); diff --git a/src/features/EditableProfileCard/ui/EditableProfileCard/EditableProfileCard.tsx b/src/features/EditableProfileCard/ui/EditableProfileCard/EditableProfileCard.tsx index 62aadad9..48fe738d 100644 --- a/src/features/EditableProfileCard/ui/EditableProfileCard/EditableProfileCard.tsx +++ b/src/features/EditableProfileCard/ui/EditableProfileCard/EditableProfileCard.tsx @@ -140,6 +140,7 @@ export const EditableProfileCard = memo( key={error} theme={TextTheme.ERROR} text={validateErrorTranslates[error]} + data-testid="EditableProfileCard.Error" /> ))} {t('Edit')} @@ -59,12 +60,14 @@ export const EditableProfileCardHeader = ({ diff --git a/src/shared/lib/hooks/useInitialEffect/useInitialEffect.ts b/src/shared/lib/hooks/useInitialEffect/useInitialEffect.ts index fa18ba5c..5576d966 100644 --- a/src/shared/lib/hooks/useInitialEffect/useInitialEffect.ts +++ b/src/shared/lib/hooks/useInitialEffect/useInitialEffect.ts @@ -8,7 +8,7 @@ import { useEffect } from 'react'; */ export function useInitialEffect(callback: () => void) { useEffect(() => { - if (__PROJECT__ !== 'storybook') { + if (__PROJECT__ !== 'storybook' && __PROJECT__ !== 'jest') { callback(); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/shared/lib/tests/componentRouter/componentRouter.tsx b/src/shared/lib/tests/componentRouter/componentRouter.tsx index c5f8f75d..9ac8b2fb 100644 --- a/src/shared/lib/tests/componentRouter/componentRouter.tsx +++ b/src/shared/lib/tests/componentRouter/componentRouter.tsx @@ -1,3 +1,4 @@ +import { ReducersMapObject } from '@reduxjs/toolkit'; import { render } from '@testing-library/react'; import { StateSchema, StoreProvider } from 'app/providers/StoreProvider'; import { ReactNode } from 'react'; @@ -8,16 +9,20 @@ import i18nForTests from 'shared/config/i18n/i18nForTests'; export interface componentRenderOptions { route?: string; initialState?: DeepPartial; + asyncReducers?: DeepPartial>; } export function componentRender( component: ReactNode, options: componentRenderOptions = {}, ) { - const { route = '/', initialState } = options; + const { route = '/', initialState, asyncReducers } = options; return render( - + {component} diff --git a/src/shared/ui/Text/Text.tsx b/src/shared/ui/Text/Text.tsx index f6fa8df8..b3cc5c03 100644 --- a/src/shared/ui/Text/Text.tsx +++ b/src/shared/ui/Text/Text.tsx @@ -26,6 +26,7 @@ interface TextProps { theme?: TextTheme; align?: TextAlign; size?: TextSize; + 'data-testid'?: string; } type HeaderTagType = 'h2' | 'h3' | 'h4'; @@ -44,6 +45,7 @@ export const Text = memo( theme = TextTheme.PRIMARY, align = TextAlign.LEFT, size = TextSize.M, + 'data-testid': dataTestId = 'Text', }: TextProps) => { const mods: Mods = { [cls[theme]]: true, @@ -55,8 +57,22 @@ export const Text = memo( return (
- {title && {title}} - {text &&

{text}

} + {title && ( + + {title} + + )} + {text && ( +

+ {text} +

+ )}
); }, diff --git a/testplane/ArticleList.stories.tsx-screens/24c982a/linux-chrome/plain.png b/testplane/ArticleList.stories.tsx-screens/24c982a/linux-chrome/plain.png index 6409c75f..55964ba5 100644 Binary files a/testplane/ArticleList.stories.tsx-screens/24c982a/linux-chrome/plain.png and b/testplane/ArticleList.stories.tsx-screens/24c982a/linux-chrome/plain.png differ diff --git a/testplane/ArticleList.stories.tsx-screens/975fb7c/linux-chrome/plain.png b/testplane/ArticleList.stories.tsx-screens/975fb7c/linux-chrome/plain.png index 0502c5d0..8d489795 100644 Binary files a/testplane/ArticleList.stories.tsx-screens/975fb7c/linux-chrome/plain.png and b/testplane/ArticleList.stories.tsx-screens/975fb7c/linux-chrome/plain.png differ diff --git a/testplane/ArticleList.stories.tsx-screens/b39f666/linux-chrome/plain.png b/testplane/ArticleList.stories.tsx-screens/b39f666/linux-chrome/plain.png index c3e2e849..9c5b645d 100644 Binary files a/testplane/ArticleList.stories.tsx-screens/b39f666/linux-chrome/plain.png and b/testplane/ArticleList.stories.tsx-screens/b39f666/linux-chrome/plain.png differ diff --git a/testplane/ArticleList.stories.tsx-screens/c87465e/linux-chrome/plain.png b/testplane/ArticleList.stories.tsx-screens/c87465e/linux-chrome/plain.png index 0502c5d0..9417b46d 100644 Binary files a/testplane/ArticleList.stories.tsx-screens/c87465e/linux-chrome/plain.png and b/testplane/ArticleList.stories.tsx-screens/c87465e/linux-chrome/plain.png differ diff --git a/testplane/ArticleList.stories.tsx-screens/d909d51/linux-chrome/plain.png b/testplane/ArticleList.stories.tsx-screens/d909d51/linux-chrome/plain.png index 6409c75f..1ce3bd53 100644 Binary files a/testplane/ArticleList.stories.tsx-screens/d909d51/linux-chrome/plain.png and b/testplane/ArticleList.stories.tsx-screens/d909d51/linux-chrome/plain.png differ diff --git a/testplane/Avatar.stories.tsx-screens/d76533d/linux-chrome/plain.png b/testplane/Avatar.stories.tsx-screens/d76533d/linux-chrome/plain.png index 29b6b18f..e0cb84e4 100644 Binary files a/testplane/Avatar.stories.tsx-screens/d76533d/linux-chrome/plain.png and b/testplane/Avatar.stories.tsx-screens/d76533d/linux-chrome/plain.png differ