Compare commits

..

No commits in common. "main" and "feature/landing-tests" have entirely different histories.

42 changed files with 276 additions and 1346 deletions

View File

@ -1,5 +0,0 @@
{
"i18n-ally.localesPaths": [
"locales"
]
}

32
Jenkinsfile vendored
View File

@ -1,7 +1,7 @@
pipeline {
agent {
docker {
image 'node:22'
image 'node:20'
}
}
@ -30,21 +30,25 @@ pipeline {
}
}
stage('eslint') {
steps {
sh 'npm run eslint'
}
}
stage('checks') {
parallel {
stage('eslint') {
steps {
sh 'npm run eslint'
}
}
stage('test') {
steps {
sh 'npm run test'
}
}
stage('test') {
steps {
sh 'npm run test'
}
}
stage('build') {
steps {
sh 'npm run build'
stage('build') {
steps {
sh 'npm run build'
}
}
}
}

View File

@ -19,18 +19,9 @@ module.exports = {
'dry-wash.arm': '/arm/*',
},
features: {
'dry-wash': {
'dry-wash-pl': {
// add your features here in the format [featureName]: { value: string }
"order-view-status-polling": {
"on": true,
"value": "3000",
"key": "order-view-status-polling"
},
"car-img-upload": {
"on": true,
"value": "true",
"key": "car-img-upload"
}
'order-view-status-polling': { value: '3000' }
},
},
config: {

View File

@ -6,7 +6,7 @@ test.beforeEach('check server is up', async ({ page }) => {
const makeOrderText = page.getByText('Сделать заказ', { exact: true });
await expect(makeOrderText).toBeVisible();
} catch (error) {
console.error('server not up', error);
console.error('server not up');
test.skip();
}
});

View File

@ -50,12 +50,6 @@
"dry-wash.order-view.details.location": "Where",
"dry-wash.order-view.details.datetime-range": "When",
"dry-wash.order-view.details.alert": "The operator will contact you about the payment at the specified phone number",
"dry-wash.order-view.upload-car-image.field.label": "Upload a photo of your car, and our service will quickly calculate the pre-order price!",
"dry-wash.order-view.upload-car-image.field.help": "Allowed formats: .jpg, .png. Maximum size: 5MB",
"dry-wash.order-view.upload-car-image.file-input.placeholder": "Upload a file",
"dry-wash.order-view.upload-car-image.file-input.button": "Upload",
"dry-wash.order-view.upload-car-image-query.success.title": "The car image is successfully uploaded",
"dry-wash.order-view.upload-car-image-query.error.title": "Failed to upload the car image",
"dry-wash.arm.master.add": "Add",
"dry-wash.arm.order.title": "Orders",
"dry-wash.arm.order.table.empty": "Table empty",

View File

@ -105,12 +105,6 @@
"dry-wash.order-view.details.location": "Где",
"dry-wash.order-view.details.datetime-range": "Когда",
"dry-wash.order-view.details.alert": "С вами свяжется оператор насчет оплаты по указанному номеру телефона",
"dry-wash.order-view.upload-car-image.field.label": "Загрузите фото вашего автомобиля, и наш сервис быстро рассчитает предварительную стоимость заказа!",
"dry-wash.order-view.upload-car-image.field.help": "Допустимые форматы: .jpg, .png. Максимальный размер: 5МБ",
"dry-wash.order-view.upload-car-image.file-input.placeholder": "Загрузите файл",
"dry-wash.order-view.upload-car-image.file-input.button": "Загрузить",
"dry-wash.order-view.upload-car-image-query.success.title": "Изображение автомобиля успешно загружено",
"dry-wash.order-view.upload-car-image-query.error.title": "Не удалось загрузить изображение автомобиля",
"dry-wash.notFound.title": "Страница не найдена",
"dry-wash.notFound.description": "К сожалению, запрашиваемая вами страница не существует.",
"dry-wash.notFound.button.back": "Вернуться на главную",

24
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "dry-wash",
"version": "0.9.1",
"version": "0.7.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "dry-wash",
"version": "0.9.1",
"version": "0.7.2",
"license": "ISC",
"dependencies": {
"@babel/core": "^7.26.7",
@ -68,7 +68,8 @@
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz",
"integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
@ -3829,6 +3830,7 @@
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
"integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@adobe/css-tools": "^4.4.0",
"aria-query": "^5.0.0",
@ -3849,6 +3851,7 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
@ -3864,6 +3867,7 @@
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@ -3876,13 +3880,15 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
"integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/@testing-library/jest-dom/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
@ -6080,7 +6086,8 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/cssesc": {
"version": "3.0.0",
@ -8816,6 +8823,7 @@
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
@ -11405,7 +11413,8 @@
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
@ -11602,6 +11611,7 @@
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
@ -13165,6 +13175,7 @@
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
"dev": true,
"license": "MIT",
"dependencies": {
"indent-string": "^4.0.0",
"strip-indent": "^3.0.0"
@ -13982,6 +13993,7 @@
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"min-indent": "^1.0.0"
},

View File

@ -1,16 +1,16 @@
{
"name": "dry-wash",
"version": "0.9.1",
"version": "0.7.2",
"description": "<a id=\"readme-top\"></a>",
"main": "./src/index.tsx",
"scripts": {
"test": "jest -u",
"test": "jest",
"start": "brojs server --port=8099 --with-open-browser",
"build": "npm run clean && brojs build --dev",
"build:prod": "npm run clean && brojs build",
"clean": "rimraf dist",
"eslint": "npx eslint src",
"eslint:fix": "npx eslint src --fix",
"eslint": "npx eslint .",
"eslint:fix": "npx eslint . --fix",
"preversion": "npm run eslint"
},
"keywords": [],

View File

@ -1,18 +1,15 @@
import { getFeatures } from "@brojs/cli";
const features = getFeatures('dry-wash');
const features = getFeatures('dry-wash-pl');
export const FEATURE = {
orderViewStatusPolling: {
isOn: Boolean(features?.['order-view-status-polling']),
isOn: Boolean(features['order-view-status-polling']),
getValue: () => {
const interval = parseInt(features?.['order-view-status-polling']?.value);
const interval = parseInt(features['order-view-status-polling'].value);
if (!Number.isNaN(interval)) {
return interval;
}
}
},
carImageUpload: {
isOn: Boolean(features?.['car-img-upload'])
}
};

View File

@ -1,4 +1,4 @@
import { GetOrder, CreateOrder, UploadCarImage } from "../../models/api";
import { GetOrder, CreateOrder } from "../../models/api";
import { api } from "./api";
import { extractBodyFromResponse, extractErrorMessageFromResponse } from "./utils";
@ -19,13 +19,5 @@ export const landingApi = api.injectEndpoints({
transformResponse: extractBodyFromResponse<CreateOrder.Response>,
transformErrorResponse: extractErrorMessageFromResponse,
}),
uploadCarImage: mutation<UploadCarImage.Response, UploadCarImage.Params>({
query: ({ orderId, body }) => ({
url: `/order/${orderId}/upload-car-img`,
body,
method: 'POST'
}),
transformErrorResponse: extractErrorMessageFromResponse,
}),
})
});

View File

@ -1,6 +1,6 @@
import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import { BaseResponse } from '../../models/api';
import { BaseResponse } from "../../models/api";
export const extractBodyFromResponse = <Body>(response: BaseResponse<Body>) => {
if (response.success) {
@ -8,15 +8,8 @@ export const extractBodyFromResponse = <Body>(response: BaseResponse<Body>) => {
}
};
export const extractErrorMessageFromResponse = ({
data,
}: FetchBaseQueryError) => {
if (
typeof data === 'object' &&
data !== null &&
'error' in data &&
typeof data.error === 'string'
) {
return data.error;
export const extractErrorMessageFromResponse = ({ data }: FetchBaseQueryError) => {
if (typeof data === 'object' && 'message' in data && typeof data.message === 'string') {
return data.message;
}
};

View File

@ -7,9 +7,7 @@ export const store = configureStore({
[api.reducerPath]: api.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false
}).concat(api.middleware),
getDefaultMiddleware().concat(api.middleware),
});
export type RootState = ReturnType<typeof store.getState>;

View File

@ -1,7 +1,6 @@
import React from 'react';
import { Box, Button, Text } from '@chakra-ui/react';
import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons';
import dayjs from 'dayjs';
interface DateNavigatorProps {
currentDate: Date;
@ -20,7 +19,7 @@ const DateNavigator = ({
<ArrowBackIcon />
</Button>
<Text mx='4' fontSize='lg' fontWeight='bold'>
{dayjs(currentDate).format('DD.MM.YYYY')}
{currentDate.toLocaleDateString()}
</Text>
<Button onClick={onNextDate}>
<ArrowForwardIcon />

View File

@ -33,22 +33,22 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
render() {
const { hasError } = this.state;
//TODO: добавить анимацию после залива 404 страницы
//TODO: может сделать обертку для хука, чтоб язык менялся без перезагрузки
if (hasError) {
return (
<Center minH='100vh' data-testid='error-boundary'>
<Center minH='100vh'>
<VStack spacing={4} textAlign='center'>
<Heading as='h1' size='2xl' data-testid='error-title'>
<Heading as='h1' size='2xl'>
{i18next.t('~:dry-wash.errorBoundary.title')}
</Heading>
<Text fontSize='lg' data-testid='error-description'>
<Text fontSize='lg'>
{i18next.t('~:dry-wash.errorBoundary.description')}
</Text>
<Button
colorScheme='teal'
size='lg'
variant='outline'
data-testid='error-reload-button'
onClick={() => window.location.reload()}
>
{i18next.t('~:dry-wash.errorBoundary.button.reload')}

View File

@ -1,51 +0,0 @@
import * as React from 'react';
import { describe, expect, it, jest } from '@jest/globals';
import { render, screen, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { useEffect } from 'react';
import ErrorBoundary from '../ErrorBoundary';
import { store } from '../../../__data__/store';
const ProblematicComponent = () => {
useEffect(() => {
throw new Error('Test Error');
}, []);
return <div>Этот текст не должен появиться</div>;
};
jest.mock('@brojs/cli', () => {
return {
getNavigationValue: () => '/auth/login',
getConfigValue: () => '/api',
};
});
describe('ErrorBoundary', () => {
it('должен отобразить запасной UI при ошибке', async () => {
// Подавляем вывод ошибки в консоль во время теста
const consoleSpy = jest.spyOn(console, 'error');
consoleSpy.mockImplementation(() => {});
const { container } = render(
<Provider store={store}>
<ErrorBoundary>
<BrowserRouter>
<ProblematicComponent />
</BrowserRouter>
</ErrorBoundary>
</Provider>,
);
const button = await waitFor(() =>
screen.getByTestId('error-reload-button'),
);
expect(button).not.toBeNull();
expect(container).toMatchSnapshot();
// Восстанавливаем console.error после теста
consoleSpy.mockRestore();
});
});

View File

@ -1,28 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ErrorBoundary должен отобразить запасной UI при ошибке 1`] = `
<div>
<div
class="css-1o0ed15"
data-testid="error-boundary"
>
<div
class="chakra-stack css-zefqyp"
>
<h1
class="chakra-heading css-0"
data-testid="error-title"
/>
<p
class="chakra-text css-1ezsviu"
data-testid="error-description"
/>
<button
class="chakra-button css-4xx2wk"
data-testid="error-reload-button"
type="button"
/>
</div>
</div>
</div>
`;

View File

@ -15,13 +15,13 @@ import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs';
import OrderItem from '../OrderItem';
import { OrderProps } from '../OrderItem/OrderItem';
import DateNavigator from '../DateNavigator';
import {
useGetMastersQuery,
useGetOrdersQuery,
} from '../../__data__/service/api';
import useShowToast from '../../hooks/useShowToast';
import { OrderArm } from '../../models/api';
const TABLE_HEADERS = [
'carNumber' as const,
@ -70,6 +70,7 @@ const Orders = () => {
<Heading size='lg' mb='5'>
{t('title')}
</Heading>
<DateNavigator
currentDate={currentDate}
onPreviousDate={() =>
@ -111,7 +112,7 @@ const Orders = () => {
allMasters={masters}
key={index}
{...order}
status={order.status as OrderArm['status']}
status={order.status as OrderProps['status']}
/>
))}
</Tbody>

View File

@ -5,7 +5,6 @@ export const PageSpinner: FC = () => {
return (
<Flex w='full' h='100vh' justifyContent='center' alignItems='center'>
<Spinner
data-testid='spinner'
thickness='5px'
speed='0.65s'
emptyColor='gray.200'

View File

@ -47,7 +47,6 @@ const Sidebar = () => {
w='100%'
colorScheme={isActive(URLs.armMaster.url) ? 'green' : 'blue'}
variant={isActive(URLs.armMaster.url) ? 'solid' : 'ghost'}
data-testid='master-button'
>
{t('master')}
</Button>

View File

@ -1,102 +0,0 @@
import React, { FC, memo, useRef } from 'react';
import { Controller, useForm } from 'react-hook-form';
import {
Button,
FormControl,
FormErrorMessage,
FormHelperText,
FormLabel,
HStack,
Input,
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { landingApi } from '../../../__data__/service/landing.api';
import { UploadCarImage } from '../../../models/api';
import { useHandleUploadCarImageResponse } from './helper';
type FormValues = {
carImg: File & {
fileName: string;
};
};
type CarImageFormProps = {
orderId: UploadCarImage.Params['orderId'];
};
export const CarImageForm: FC<CarImageFormProps> = memo(function CarImageForm({
orderId,
}) {
const {
handleSubmit,
control,
formState: { errors, isSubmitting },
} = useForm<FormValues>({ shouldFocusError: true });
const [uploadCarImage, uploadCarImageMutation] =
landingApi.useUploadCarImageMutation();
useHandleUploadCarImageResponse(uploadCarImageMutation);
const onSubmit = (formData: FormValues) => {
const body = new FormData();
body.append('file', formData.carImg);
uploadCarImage({ orderId, body });
};
const fileInputRef = useRef(null);
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.order-view.upload-car-image',
});
return (
<form>
<FormControl>
<FormLabel htmlFor='carImg'>{t('field.label')}</FormLabel>
<Controller
control={control}
name='carImg'
render={({ field: { value, onChange, ...field } }) => {
return (
<HStack gap={0}>
<Input
{...field}
ref={fileInputRef}
accept='image/png,image/jpeg'
value={value?.fileName}
onChange={(event) => {
onChange(event.target.files[0]);
handleSubmit(onSubmit)();
}}
type='file'
hidden
/>
<Input
placeholder={t('file-input.placeholder')}
value={value?.name || ''}
readOnly
borderRightRadius={0}
/>
<Button
onClick={() => {
fileInputRef.current.click();
}}
isLoading={isSubmitting || uploadCarImageMutation.isLoading}
colorScheme='primary'
paddingInline={8}
borderLeftRadius={0}
>
{t('file-input.button')}
</Button>
</HStack>
);
}}
/>
<FormErrorMessage>{errors.carImg?.message}</FormErrorMessage>
<FormHelperText>{t('field.help')}</FormHelperText>
</FormControl>
</form>
);
});

View File

@ -1,35 +0,0 @@
import { useEffect } from "react";
import { useToast } from "@chakra-ui/react";
import { useTranslation } from "react-i18next";
import { isErrorMessage } from "../../../models/api";
export const useHandleUploadCarImageResponse = (query: {
isSuccess: boolean;
isError: boolean;
error?: unknown;
}) => {
const toast = useToast();
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.order-view.upload-car-image-query',
});
useEffect(() => {
if (query.isError) {
toast({
status: 'error',
title: t('error.title'),
description: isErrorMessage(query.error) ? query.error : undefined,
});
}
}, [query.isError]);
useEffect(() => {
if (query.isSuccess) {
toast({
status: 'success',
title: t('success.title'),
});
}
}, [query.isSuccess]);
};

View File

@ -1 +0,0 @@
export { CarImageForm } from './car-img-form';

View File

@ -5,13 +5,11 @@ import {
Heading,
HStack,
UnorderedList,
VStack,
ListItem,
Text,
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs';
import localizedFormat from "dayjs/plugin/localizedFormat";
dayjs.extend(localizedFormat);
import { Order } from '../../../models/landing';
import { formatDatetime } from '../../../lib';
@ -43,7 +41,7 @@ export const OrderDetails: FC<OrderDetailsProps> = ({
location,
startWashTime,
endWashTime,
created
...props
}) => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.order-view.details',
@ -53,7 +51,7 @@ export const OrderDetails: FC<OrderDetailsProps> = ({
});
return (
<>
<VStack p={4} alignItems='flex-start' gap={4} {...props}>
<HStack
width='full'
flexWrap='wrap'
@ -61,7 +59,7 @@ export const OrderDetails: FC<OrderDetailsProps> = ({
gap={2}
>
<Heading as='h2' size='lg'>
{t('title', { number: orderNumber })} ({dayjs(created).format('LLLL')})
{t('title', { number: orderNumber })}
</Heading>
<OrderStatus value={status} />
</HStack>
@ -107,7 +105,7 @@ export const OrderDetails: FC<OrderDetailsProps> = ({
<AlertIcon />
{t('alert')}
</Alert>
</>
</VStack>
);
};

View File

@ -9,7 +9,7 @@ export const isErrorMessage = (error: unknown): error is ErrorMessage => typeof
type ErrorResponse = {
success: false;
error: ErrorMessage;
message: ErrorMessage;
};
export type BaseResponse<Body> = SuccessResponse<Body> | ErrorResponse;

View File

@ -21,17 +21,6 @@ export namespace CreateOrder {
};
}
export namespace UploadCarImage {
export type Response = void;
export type Params = {
orderId: Order.Id;
/**
* @example { file: File }
*/
body: FormData;
};
}
type GetArrItemType<ArrType> =
ArrType extends Array<infer ItemType> ? ItemType : never;

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Страница заказов должна корректно отображать список заказов после загрузки данных 1`] = `
exports[`Arm Page render 1`] = `
<div>
<div
class="css-1yeiifd"
@ -32,7 +32,6 @@ exports[`Страница заказов должна корректно ото
/>
<a
class="chakra-button css-1kg18wp"
data-testid="master-button"
href="/auth/login"
>
Мастера
@ -75,7 +74,173 @@ exports[`Страница заказов должна корректно ото
<p
class="chakra-text css-52ukzg"
>
23.02.2025
15.02.2025
</p>
<button
class="chakra-button css-ez23ye"
type="button"
>
<svg
class="chakra-icon css-onkibi"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"
fill="currentColor"
/>
</svg>
</button>
</div>
<table
class="chakra-table css-5605sr"
>
<thead
class="css-0"
>
<tr
class="css-0"
>
<th
class="css-1szkfps"
>
Номер машины
</th>
<th
class="css-1szkfps"
>
Дата заказа
</th>
<th
class="css-1szkfps"
>
Статус
</th>
<th
class="css-1szkfps"
>
Мастер
</th>
<th
class="css-1szkfps"
>
Телефон
</th>
<th
class="css-1szkfps"
>
Расположение
</th>
</tr>
</thead>
<tbody
class="css-0"
>
<tr
class="css-0"
>
<td
class="css-1v9gmks"
colspan="6"
>
<div
class="chakra-spinner css-1j92705"
>
<span
class="css-8b45rq"
>
Loading...
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<span
hidden=""
id="__chakra_env"
/>
</div>
`;
exports[`Arm Page render 2`] = `
<div>
<div
class="css-1yeiifd"
>
<div
class="css-13owfwq"
>
<h2
class="chakra-heading css-173d1bl"
>
Сухой мастер
</h2>
<div
class="chakra-stack css-1cggwyz"
>
<hr
aria-orientation="horizontal"
class="chakra-divider css-svjswr"
/>
<a
class="chakra-button css-1kg18wp"
href="/auth/login"
>
Заказы
</a>
<hr
aria-orientation="horizontal"
class="chakra-divider css-svjswr"
/>
<a
class="chakra-button css-1kg18wp"
href="/auth/login"
>
Мастера
</a>
<hr
aria-orientation="horizontal"
class="chakra-divider css-svjswr"
/>
</div>
</div>
<div
class="css-jiwy8d"
>
<div
class="css-1glkkdp"
>
<h2
class="chakra-heading css-1xer3cv"
>
Заказы
</h2>
<div
class="css-1u3smh"
>
<button
class="chakra-button css-ez23ye"
type="button"
>
<svg
class="chakra-icon css-onkibi"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"
fill="currentColor"
/>
</svg>
</button>
<p
class="chakra-text css-52ukzg"
>
15.02.2025
</p>
<button
class="chakra-button css-ez23ye"

View File

@ -1,643 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Master Page should display master list and show details when master button is clicked 1`] = `
<div>
<div
class="css-1yeiifd"
>
<div
class="css-13owfwq"
>
<h2
class="chakra-heading css-173d1bl"
>
Сухой мастер
</h2>
<div
class="chakra-stack css-1cggwyz"
>
<hr
aria-orientation="horizontal"
class="chakra-divider css-svjswr"
/>
<a
class="chakra-button css-18yoix2"
href="/order"
>
Заказы
</a>
<hr
aria-orientation="horizontal"
class="chakra-divider css-svjswr"
/>
<a
class="chakra-button css-1kg18wp"
data-testid="master-button"
href="/master"
>
Мастера
</a>
<hr
aria-orientation="horizontal"
class="chakra-divider css-svjswr"
/>
</div>
</div>
<div
class="css-jiwy8d"
>
<div
class="css-1glkkdp"
>
<div
class="css-sd3fvu"
>
<h2
class="chakra-heading css-1jb3vzl"
>
Мастера
</h2>
<button
class="chakra-button css-h211ee"
type="button"
>
+
Добавить
</button>
</div>
<table
class="chakra-table css-5605sr"
>
<thead
class="css-0"
>
<tr
class="css-0"
>
<th
class="css-1szkfps"
>
Имя
</th>
<th
class="css-1szkfps"
>
Актуальная занятость
</th>
<th
class="css-1szkfps"
>
Телефон
</th>
<th
class="css-1szkfps"
>
Действия
</th>
</tr>
</thead>
<tbody
class="css-0"
>
<tr
class="css-0"
>
<td
class="css-zgoslk"
>
<div
class="chakra-editable css-vtl58r"
>
<div
class="chakra-stack css-c2wmld"
>
<span
class="chakra-editable__preview css-1gasyng"
>
Иван Иванов
</span>
<input
class="chakra-editable__input chakra-input css-2lpiar"
hidden=""
value="Иван Иванов"
/>
<div
class="css-1l4w6pd"
>
<button
aria-label="Edit"
class="chakra-button css-1pqvhxt"
type="button"
>
<svg
aria-hidden="true"
class="chakra-icon css-onkibi"
focusable="false"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
>
<path
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
/>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</g>
</svg>
</button>
</div>
</div>
</div>
</td>
<td
class="css-zgoslk"
>
<p
class="chakra-text css-q9k0mw"
>
Свободен
</p>
</td>
<td
class="css-zgoslk"
>
<div
class="chakra-editable css-vtl58r"
>
<div
class="chakra-stack css-c2wmld"
>
<span
class="chakra-editable__preview css-1gasyng"
>
+7 900 123 45 67
</span>
<input
class="chakra-editable__input chakra-input css-2lpiar"
hidden=""
value="+7 900 123 45 67"
/>
<div
class="css-1l4w6pd"
>
<button
aria-label="Edit"
class="chakra-button css-1pqvhxt"
type="button"
>
<svg
aria-hidden="true"
class="chakra-icon css-onkibi"
focusable="false"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
>
<path
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
/>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</g>
</svg>
</button>
</div>
</div>
</div>
</td>
<td
class="css-zgoslk"
>
<button
aria-controls="menu-list-:r2:"
aria-expanded="false"
aria-haspopup="menu"
class="chakra-button chakra-menu__menu-button css-13sr8jm"
id="menu-button-:r2:"
type="button"
>
<svg
aria-hidden="true"
class="chakra-icon css-onkibi"
focusable="false"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
>
<path
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
/>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</g>
</svg>
</button>
<div
class="css-ktd6ms"
style="visibility: hidden; position: absolute; min-width: max-content; inset: 0 auto auto 0;"
>
<div
aria-orientation="vertical"
class="chakra-menu__menu-list css-s5t7bz"
id="menu-list-:r2:"
role="menu"
style="transform-origin: var(--popper-transform-origin); opacity: 0; visibility: hidden; transform: scale(0.8) translateZ(0);"
tabindex="-1"
>
<button
aria-disabled="false"
class="chakra-menu__menuitem css-y7jzs3"
data-index="0"
id="menu-list-:r2:-menuitem-:r3:"
role="menuitem"
tabindex="-1"
type="button"
>
Удалить мастера
</button>
</div>
</div>
</td>
</tr>
<tr
class="css-0"
>
<td
class="css-zgoslk"
>
<div
class="chakra-editable css-vtl58r"
>
<div
class="chakra-stack css-c2wmld"
>
<span
class="chakra-editable__preview css-1gasyng"
>
Олег Макаров
</span>
<input
class="chakra-editable__input chakra-input css-2lpiar"
hidden=""
value="Олег Макаров"
/>
<div
class="css-1l4w6pd"
>
<button
aria-label="Edit"
class="chakra-button css-1pqvhxt"
type="button"
>
<svg
aria-hidden="true"
class="chakra-icon css-onkibi"
focusable="false"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
>
<path
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
/>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</g>
</svg>
</button>
</div>
</div>
</div>
</td>
<td
class="css-zgoslk"
>
<p
class="chakra-text css-q9k0mw"
>
Свободен
</p>
</td>
<td
class="css-zgoslk"
>
<div
class="chakra-editable css-vtl58r"
>
<div
class="chakra-stack css-c2wmld"
>
<span
class="chakra-editable__preview css-1gasyng"
>
79001234567
</span>
<input
class="chakra-editable__input chakra-input css-2lpiar"
hidden=""
value="79001234567"
/>
<div
class="css-1l4w6pd"
>
<button
aria-label="Edit"
class="chakra-button css-1pqvhxt"
type="button"
>
<svg
aria-hidden="true"
class="chakra-icon css-onkibi"
focusable="false"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
>
<path
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
/>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</g>
</svg>
</button>
</div>
</div>
</div>
</td>
<td
class="css-zgoslk"
>
<button
aria-controls="menu-list-:r5:"
aria-expanded="false"
aria-haspopup="menu"
class="chakra-button chakra-menu__menu-button css-13sr8jm"
id="menu-button-:r5:"
type="button"
>
<svg
aria-hidden="true"
class="chakra-icon css-onkibi"
focusable="false"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
>
<path
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
/>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</g>
</svg>
</button>
<div
class="css-ktd6ms"
style="visibility: hidden; position: absolute; min-width: max-content; inset: 0 auto auto 0;"
>
<div
aria-orientation="vertical"
class="chakra-menu__menu-list css-s5t7bz"
id="menu-list-:r5:"
role="menu"
style="transform-origin: var(--popper-transform-origin); opacity: 0; visibility: hidden; transform: scale(0.8) translateZ(0);"
tabindex="-1"
>
<button
aria-disabled="false"
class="chakra-menu__menuitem css-y7jzs3"
data-index="0"
id="menu-list-:r5:-menuitem-:r6:"
role="menuitem"
tabindex="-1"
type="button"
>
Удалить мастера
</button>
</div>
</div>
</td>
</tr>
<tr
class="css-0"
>
<td
class="css-zgoslk"
>
<div
class="chakra-editable css-vtl58r"
>
<div
class="chakra-stack css-c2wmld"
>
<span
class="chakra-editable__preview css-1gasyng"
>
Иван Галкин
</span>
<input
class="chakra-editable__input chakra-input css-2lpiar"
hidden=""
value="Иван Галкин"
/>
<div
class="css-1l4w6pd"
>
<button
aria-label="Edit"
class="chakra-button css-1pqvhxt"
type="button"
>
<svg
aria-hidden="true"
class="chakra-icon css-onkibi"
focusable="false"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
>
<path
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
/>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</g>
</svg>
</button>
</div>
</div>
</div>
</td>
<td
class="css-zgoslk"
>
<div
class="chakra-stack css-1f0wxn3"
>
<span
class="chakra-badge css-1g1qw76"
/>
<span
class="chakra-badge css-1g1qw76"
/>
</div>
</td>
<td
class="css-zgoslk"
>
<div
class="chakra-editable css-vtl58r"
>
<div
class="chakra-stack css-c2wmld"
>
<span
class="chakra-editable__preview css-1gasyng"
>
+7 900 123 45 67
</span>
<input
class="chakra-editable__input chakra-input css-2lpiar"
hidden=""
value="+7 900 123 45 67"
/>
<div
class="css-1l4w6pd"
>
<button
aria-label="Edit"
class="chakra-button css-1pqvhxt"
type="button"
>
<svg
aria-hidden="true"
class="chakra-icon css-onkibi"
focusable="false"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
>
<path
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
/>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</g>
</svg>
</button>
</div>
</div>
</div>
</td>
<td
class="css-zgoslk"
>
<button
aria-controls="menu-list-:r8:"
aria-expanded="false"
aria-haspopup="menu"
class="chakra-button chakra-menu__menu-button css-13sr8jm"
id="menu-button-:r8:"
type="button"
>
<svg
aria-hidden="true"
class="chakra-icon css-onkibi"
focusable="false"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
>
<path
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
/>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</g>
</svg>
</button>
<div
class="css-ktd6ms"
style="visibility: hidden; position: absolute; min-width: max-content; inset: 0 auto auto 0;"
>
<div
aria-orientation="vertical"
class="chakra-menu__menu-list css-s5t7bz"
id="menu-list-:r8:"
role="menu"
style="transform-origin: var(--popper-transform-origin); opacity: 0; visibility: hidden; transform: scale(0.8) translateZ(0);"
tabindex="-1"
>
<button
aria-disabled="false"
class="chakra-menu__menuitem css-y7jzs3"
data-index="0"
id="menu-list-:r8:-menuitem-:r9:"
role="menuitem"
tabindex="-1"
type="button"
>
Удалить мастера
</button>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<span
hidden=""
id="__chakra_env"
/>
</div>
`;

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Страница просмотра заказа отображает детали заказа после успешной загрузки 1`] = `
exports[`Order View page, initial load shows order details 1`] = `
<div>
<div
class="chakra-container css-3n6qh3"
@ -16,6 +16,7 @@ exports[`Страница просмотра заказа отображает
</h2>
<div
class="chakra-stack css-1n38vgh"
created="2025-01-19T14:04:02.985Z"
data-testid="order-details"
>
<div
@ -25,9 +26,6 @@ exports[`Страница просмотра заказа отображает
class="chakra-heading css-1jb3vzl"
>
Заказ №{{number}}
(
Sunday, January 19, 2025 5:04 PM
)
</h2>
<span
class="css-6jfsiv"
@ -128,7 +126,7 @@ exports[`Страница просмотра заказа отображает
</div>
`;
exports[`Страница просмотра заказа отображает индикатор загрузки деталей заказа 1`] = `
exports[`Order View page, initial load shows order details loading 1`] = `
<div>
<div
class="chakra-container css-3n6qh3"
@ -173,7 +171,7 @@ exports[`Страница просмотра заказа отображает
</div>
`;
exports[`Страница просмотра заказа отображает ошибку при некорректном ID заказа 1`] = `
exports[`Order View page, initial load shows order error 1`] = `
<div>
<div
class="chakra-container css-3n6qh3"

View File

@ -1,4 +1,4 @@
import * as React from 'react';
import React from 'react';
import {
describe,
it,
@ -98,12 +98,16 @@ jest.mock('@brojs/cli', () => {
};
});
describe('Страница заказов', () => {
describe('Arm Page', () => {
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it('должна корректно отображать список заказов после загрузки данных', async () => {
it('render ', async () => {
server.events.on('request:start', ({ request }) => {
console.log('Outgoing:', request.method, request.url);
});
const { container } = render(
<Provider store={store}>
<ChakraProvider theme={chakraTheme}>
@ -116,6 +120,8 @@ describe('Страница заказов', () => {
</Provider>,
);
expect(container).toMatchSnapshot();
await waitFor(() => screen.getByText('A123BC'));
expect(container).toMatchSnapshot();

View File

@ -1,133 +0,0 @@
import * as React from 'react';
import {
describe,
it,
expect,
jest,
beforeAll,
afterEach,
afterAll,
} from '@jest/globals';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { ChakraProvider, theme as chakraTheme } from '@chakra-ui/react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import ErrorBoundary from '../../components/ErrorBoundary';
import { store } from '../../__data__/store';
import Page from '../arm';
const server = setupServer(
http.get('/api/arm/masters', () => {
return HttpResponse.json({
success: true,
body: [
{
id: '4545423234',
name: 'Иван Иванов',
phone: '+7 900 123 45 67',
},
{
name: 'Олег Макаров',
phone: '79001234567',
id: '23423442',
},
{
id: '345354234',
name: 'Иван Галкин',
schedule: [
{
id: 'order1',
startWashTime: '2024-11-24T10:30:00.000Z',
endWashTime: '2024-11-24T16:30:00.000Z',
},
{
id: 'order2',
startWashTime: '2024-11-24T11:30:00.000Z',
endWashTime: '2024-11-24T17:30:00.000Z',
},
],
phone: '+7 900 123 45 67',
},
],
});
}),
http.post('/api/arm/orders', () => {
return HttpResponse.json({
success: true,
body: [
{
id: 'order1',
carNumber: 'A123BC',
startWashTime: '2024-11-24T10:30:00.000Z',
endWashTime: '2024-11-24T16:30:00.000Z',
orderDate: '2024-11-24T08:41:46.366Z',
status: 'pending',
phone: '79001234563',
location: 'Казань, ул. Баумана, 1',
master: {
name: 'Олег Макаров',
phone: '79001234567',
id: '23423442',
},
notes: '',
},
{
id: 'order2',
carNumber: 'A245BC',
startWashTime: '2024-11-24T11:30:00.000Z',
endWashTime: '2024-11-24T17:30:00.000Z',
orderDate: '2024-11-24T07:40:46.366Z',
status: 'progress',
phone: '79001234567',
location: 'Казань, ул. Баумана, 43',
master: [],
notes: '',
},
],
});
}),
);
jest.mock('@brojs/cli', () => {
return {
getNavigationValue: (key: string) =>
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('../../../bro.config').navigations[key],
getConfigValue: () => '/api',
};
});
describe('Master Page', () => {
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it('should display master list and show details when master button is clicked', async () => {
const { container } = render(
<Provider store={store}>
<ChakraProvider theme={chakraTheme}>
<ErrorBoundary>
<BrowserRouter>
<Page mockUser={{ name: 'ilnaz' }} />
</BrowserRouter>
</ErrorBoundary>
</ChakraProvider>
</Provider>,
);
const button = await waitFor(() => screen.getByTestId('master-button'));
fireEvent.click(button);
// Проверяем отображение всех мастеров
await waitFor(() => {
expect(screen.getByText('Иван Иванов')).toBeInTheDocument();
expect(screen.getByText('Олег Макаров')).toBeInTheDocument();
expect(screen.getByText('Иван Галкин')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
});

View File

@ -11,12 +11,12 @@ jest.mock('react-router-dom', () => ({
useParams: jest.fn(),
}));
describe('Страница просмотра заказа', () => {
describe('Order View page, initial load', () => {
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('отображает индикатор загрузки деталей заказа', () => {
test('shows order details loading', () => {
(useParams as jest.Mock).mockReturnValue({ orderId: 'id1' });
const { container } = render(
@ -33,7 +33,7 @@ describe('Страница просмотра заказа', () => {
expect(container).toMatchSnapshot();
});
test('отображает детали заказа после успешной загрузки', async () => {
test('shows order details', async () => {
(useParams as jest.Mock).mockReturnValue({ orderId: 'id1' });
const { container } = render(
@ -52,7 +52,7 @@ describe('Страница просмотра заказа', () => {
expect(container).toMatchSnapshot();
});
test('отображает ошибку при некорректном ID заказа', async () => {
test('shows order error', async () => {
(useParams as jest.Mock).mockReturnValue({ orderId: null });
const { container } = render(

View File

@ -1,63 +0,0 @@
import * as React from 'react';
import {
describe,
it,
jest,
beforeAll,
afterEach,
afterAll,
} from '@jest/globals';
import { render, screen, waitFor } from '@testing-library/react';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { BrowserRouter } from 'react-router-dom';
import { ChakraProvider, theme as chakraTheme } from '@chakra-ui/react';
import { Provider } from 'react-redux';
import ErrorBoundary from '../../components/ErrorBoundary';
import { store } from '../../__data__/store';
import Page from '../arm';
const server = setupServer(
http.post('/api/arm/orders', () => {
return HttpResponse.json({
success: true,
body: [],
});
}),
http.get('/api/arm/masters', () => {
return HttpResponse.json({
success: true,
body: [],
});
}),
);
jest.mock('@brojs/cli', () => {
return {
getNavigationValue: () => '/auth/login',
getConfigValue: () => '/api',
};
});
describe('order page', () => {
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it('получение пустого списка', async () => {
render(
<Provider store={store}>
<ChakraProvider theme={chakraTheme}>
<ErrorBoundary>
<BrowserRouter>
<Page mockUser={{ name: 'ilnaz' }} />
</BrowserRouter>
</ErrorBoundary>
</ChakraProvider>
</Provider>,
);
await waitFor(() => screen.getByText('Список пуст'));
});
});

View File

@ -1,98 +0,0 @@
import * as React from 'react';
import {
describe,
it,
jest,
beforeAll,
afterEach,
afterAll,
expect,
} from '@jest/globals';
import { render, screen, waitFor } from '@testing-library/react';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { BrowserRouter } from 'react-router-dom';
import { ChakraProvider, theme as chakraTheme } from '@chakra-ui/react';
import { Provider } from 'react-redux';
import ErrorBoundary from '../../components/ErrorBoundary';
import { store } from '../../__data__/store';
import Page from '../arm';
import { PageSpinner } from '../../components';
const server = setupServer(
http.post('/api/arm/orders', () => {
return HttpResponse.json({}, { status: 500 });
}),
http.get('/api/arm/masters', () => {
return HttpResponse.json({
success: true,
body: [
{
id: '4545423234',
name: 'Иван Иванов',
phone: '+7 900 123 45 67',
},
{
name: 'Олег Макаров',
phone: '79001234567',
id: '23423442',
},
{
id: '345354234',
name: 'Иван Галкин',
schedule: [
{
id: 'order1',
startWashTime: '2024-11-24T10:30:00.000Z',
endWashTime: '2024-11-24T16:30:00.000Z',
},
{
id: 'order2',
startWashTime: '2024-11-24T11:30:00.000Z',
endWashTime: '2024-11-24T17:30:00.000Z',
},
],
phone: '+7 900 123 45 67',
},
],
});
}),
);
jest.mock('@brojs/cli', () => {
return {
getNavigationValue: () => '/auth/login',
getConfigValue: () => '/api',
};
});
describe('order page', () => {
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it('обработка ошибки при загрузке данных', async () => {
render(
<Provider store={store}>
<ChakraProvider theme={chakraTheme}>
<ErrorBoundary>
<BrowserRouter>
<Page mockUser={{ name: 'ilnaz' }} />
</BrowserRouter>
</ErrorBoundary>
</ChakraProvider>
</Provider>,
);
await waitFor(() => screen.getByText('Ошибка при загрузке данных'));
});
});
describe('Routers', () => {
it('отображает PageSpinner ', async () => {
render(<PageSpinner />);
expect(await screen.findByTestId('spinner')).toBeTruthy();
});
});

View File

@ -21,7 +21,6 @@ import { Order } from '../../models/landing';
import { landingApi } from '../../__data__/service/landing.api';
import { isErrorMessage } from '../../models/api';
import { FEATURE } from '../../__data__/features';
import { CarImageForm } from '../../components/order-view/car-img';
const Page: FC = () => {
const { t } = useTranslation('~', {
@ -70,30 +69,24 @@ const Page: FC = () => {
<>
<>
{isSuccess && (
<VStack p={4} alignItems='flex-start' gap={4} data-testid='order-details'>
<OrderDetails
orderNumber={order.orderNumber}
status={order.status}
phone={order.phone}
carNumber={order.carNumber}
carBody={order.carBody}
carColor={order.carColor}
location={order.location}
startWashTime={order.startWashTime}
endWashTime={order.endWashTime}
created={order.created}
/>
{FEATURE.carImageUpload.isOn && <CarImageForm orderId={orderId} />}
</VStack>
<OrderDetails
orderNumber={order.orderNumber}
status={order.status}
phone={order.phone}
carNumber={order.carNumber}
carBody={order.carBody}
carColor={order.carColor}
location={order.location}
startWashTime={order.startWashTime}
endWashTime={order.endWashTime}
created={order.created}
data-testid='order-details'
/>
)}
</>
<>
{isError && (
<Alert
status='error'
alignItems='flex-start'
data-testid='error'
>
<Alert status='error' alignItems='flex-start' data-testid='error'>
<AlertIcon />
<Box>
<AlertTitle>

View File

@ -2,7 +2,7 @@
/* eslint-disable @typescript-eslint/no-require-imports */
const router = require('express').Router();
const STUBS = { masters: 'success', orders: 'success', orderCreate: 'success', orderView: 'success-pending', orderCarImg: 'success' };
const STUBS = { masters: 'success', orders: 'success', orderCreate: 'success', orderView: 'success-pending' };
router.get('/set/:name/:value', (req, res) => {
const { name, value } = req.params;
@ -15,34 +15,28 @@ router.get('/set/:name/:value', (req, res) => {
router.get('/', (req, res) => {
res.send(`<div>
<fieldset>
<legend>Мастера</legend>
<legend>Мастера</legend>
${generateRadioInput('masters', 'success')}
${generateRadioInput('masters', 'error')}
${generateRadioInput('masters', 'empty')}
</fieldset>
<fieldset>
<legend>Заказы</legend>
<legend>Заказы</legend>
${generateRadioInput('orders', 'success')}
${generateRadioInput('orders', 'error')}
${generateRadioInput('orders', 'empty')}
</fieldset>
<fieldset>
<legend>Лендинг - Сделать заказ</legend>
<legend>Лендинг - Сделать заказ</legend>
${generateRadioInput('orderCreate', 'success')}
${generateRadioInput('orderCreate', 'error')}
</fieldset>
<fieldset>
<legend>Лендинг - Детали заказа</legend>
<legend>Лендинг - Детали заказа</legend>
${generateRadioInput('orderView', 'success-pending')}
${generateRadioInput('orderView', 'success-working')}
${generateRadioInput('orderView', 'error')}
</fieldset>
<fieldset>
<legend>Лендинг - Детали заказа, фото машины</legend>
${generateRadioInput('orderCarImg', 'success')}
${generateRadioInput('orderCarImg', 'error-file-type')}
${generateRadioInput('orderCarImg', 'error-file-size')}
</fieldset>
</div>`);
});

View File

@ -98,22 +98,6 @@ router.post('/order/create', (req, res) => {
);
});
router.post('/order/:orderId/upload-car-img', (req, res) => {
const { orderId } = req.params;
const stubName = `${orderId}-${STUBS.orderCarImg}`;
try {
res
.status(/error/.test(stubName) ? 500 : 200)
.send(require(`../json/landing-order-car-image-upload/${stubName}.json`));
} catch (e) {
console.error(e);
res
.status(500)
.send(commonError);
}
});
router.use('/admin', require('./admin'));
module.exports = router;

View File

@ -1,4 +0,0 @@
{
"success": false,
"error": "Invalid car image file size. Limit is 5MB"
}

View File

@ -1,4 +0,0 @@
{
"success": false,
"error": "Invalid car image file type. Allowed types: jpg, png"
}

View File

@ -1,3 +0,0 @@
{
"success": true
}

View File

@ -1,4 +1,4 @@
{
"success": false,
"error": "Не удалось создать заказ"
"message": "Не удалось создать заказ"
}

View File

@ -1,4 +1,4 @@
{
"success": false,
"error": "Не удалось загрузить детали заказа"
"message": "Не удалось загрузить детали заказа"
}

View File

@ -21,5 +21,5 @@
"**/*.test.ts",
"**/*.test.tsx",
"node_modules/@types/jest"
]
}
]
}