Compare commits

..

No commits in common. "main" and "file-upload" have entirely different histories.

21 changed files with 62 additions and 145 deletions

View File

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

32
Jenkinsfile vendored
View File

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

View File

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

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "dry-wash", "name": "dry-wash",
"version": "0.9.1", "version": "0.8.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "dry-wash", "name": "dry-wash",
"version": "0.9.1", "version": "0.8.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@babel/core": "^7.26.7", "@babel/core": "^7.26.7",

View File

@ -1,6 +1,6 @@
{ {
"name": "dry-wash", "name": "dry-wash",
"version": "0.9.1", "version": "0.8.0",
"description": "<a id=\"readme-top\"></a>", "description": "<a id=\"readme-top\"></a>",
"main": "./src/index.tsx", "main": "./src/index.tsx",
"scripts": { "scripts": {
@ -9,8 +9,8 @@
"build": "npm run clean && brojs build --dev", "build": "npm run clean && brojs build --dev",
"build:prod": "npm run clean && brojs build", "build:prod": "npm run clean && brojs build",
"clean": "rimraf dist", "clean": "rimraf dist",
"eslint": "npx eslint src", "eslint": "npx eslint .",
"eslint:fix": "npx eslint src --fix", "eslint:fix": "npx eslint . --fix",
"preversion": "npm run eslint" "preversion": "npm run eslint"
}, },
"keywords": [], "keywords": [],

View File

@ -11,8 +11,5 @@ export const FEATURE = {
return interval; return interval;
} }
} }
},
carImageUpload: {
isOn: Boolean(features?.['car-img-upload'])
} }
}; };

View File

@ -13,7 +13,6 @@ export const extractErrorMessageFromResponse = ({
}: FetchBaseQueryError) => { }: FetchBaseQueryError) => {
if ( if (
typeof data === 'object' && typeof data === 'object' &&
data !== null &&
'error' in data && 'error' in data &&
typeof data.error === 'string' typeof data.error === 'string'
) { ) {

View File

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

View File

@ -28,6 +28,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
componentDidCatch(error: Error, errorInfo: ErrorInfo): void { componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
console.error('Error caught by ErrorBoundary:', error, errorInfo); console.error('Error caught by ErrorBoundary:', error, errorInfo);
console.error('4545');
this.setState({ error, errorInfo }); this.setState({ error, errorInfo });
} }

View File

@ -24,10 +24,6 @@ jest.mock('@brojs/cli', () => {
describe('ErrorBoundary', () => { describe('ErrorBoundary', () => {
it('должен отобразить запасной UI при ошибке', async () => { it('должен отобразить запасной UI при ошибке', async () => {
// Подавляем вывод ошибки в консоль во время теста
const consoleSpy = jest.spyOn(console, 'error');
consoleSpy.mockImplementation(() => {});
const { container } = render( const { container } = render(
<Provider store={store}> <Provider store={store}>
<ErrorBoundary> <ErrorBoundary>
@ -43,9 +39,7 @@ describe('ErrorBoundary', () => {
); );
expect(button).not.toBeNull(); expect(button).not.toBeNull();
expect(container).toMatchSnapshot();
// Восстанавливаем console.error после теста expect(container).toMatchSnapshot();
consoleSpy.mockRestore();
}); });
}); });

View File

@ -64,7 +64,7 @@ export const CarImageForm: FC<CarImageFormProps> = memo(function CarImageForm({
<Input <Input
{...field} {...field}
ref={fileInputRef} ref={fileInputRef}
accept='image/png,image/jpeg' accept='.jpg,.png'
value={value?.fileName} value={value?.fileName}
onChange={(event) => { onChange={(event) => {
onChange(event.target.files[0]); onChange(event.target.files[0]);

View File

@ -11,7 +11,7 @@ export const useHandleUploadCarImageResponse = (query: {
}) => { }) => {
const toast = useToast(); const toast = useToast();
const { t } = useTranslation('~', { const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.order-view.upload-car-image-query', keyPrefix: 'dry-wash.order-create.upload-car-image-query',
}); });
useEffect(() => { useEffect(() => {

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Master Page should display master list and show details when master button is clicked 1`] = ` exports[`Master Page render 1`] = `
<div> <div>
<div <div
class="css-1yeiifd" class="css-1yeiifd"

View File

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

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Страница заказов должна корректно отображать список заказов после загрузки данных 1`] = ` exports[`order page получение списка заказов 1`] = `
<div> <div>
<div <div
class="css-1yeiifd" class="css-1yeiifd"
@ -75,7 +75,7 @@ exports[`Страница заказов должна корректно ото
<p <p
class="chakra-text css-52ukzg" class="chakra-text css-52ukzg"
> >
23.02.2025 16.02.2025
</p> </p>
<button <button
class="chakra-button css-ez23ye" class="chakra-button css-ez23ye"

View File

@ -54,41 +54,6 @@ const server = setupServer(
], ],
}); });
}), }),
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', () => { jest.mock('@brojs/cli', () => {
@ -105,7 +70,11 @@ describe('Master Page', () => {
afterEach(() => server.resetHandlers()); afterEach(() => server.resetHandlers());
afterAll(() => server.close()); afterAll(() => server.close());
it('should display master list and show details when master button is clicked', async () => { it('render ', async () => {
server.events.on('request:start', ({ request }) => {
console.log('Outgoing:', request.method, request.url);
});
const { container } = render( const { container } = render(
<Provider store={store}> <Provider store={store}>
<ChakraProvider theme={chakraTheme}> <ChakraProvider theme={chakraTheme}>
@ -119,14 +88,10 @@ describe('Master Page', () => {
); );
const button = await waitFor(() => screen.getByTestId('master-button')); const button = await waitFor(() => screen.getByTestId('master-button'));
fireEvent.click(button); fireEvent.click(button);
// Проверяем отображение всех мастеров await waitFor(() => screen.getByText('Иван Иванов'));
await waitFor(() => {
expect(screen.getByText('Иван Иванов')).toBeInTheDocument();
expect(screen.getByText('Олег Макаров')).toBeInTheDocument();
expect(screen.getByText('Иван Галкин')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

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

View File

@ -46,6 +46,10 @@ describe('order page', () => {
afterAll(() => server.close()); afterAll(() => server.close());
it('получение пустого списка', async () => { it('получение пустого списка', async () => {
server.events.on('request:start', ({ request }) => {
console.log('Outgoing:', request.method, request.url);
});
render( render(
<Provider store={store}> <Provider store={store}>
<ChakraProvider theme={chakraTheme}> <ChakraProvider theme={chakraTheme}>

View File

@ -21,43 +21,9 @@ import Page from '../arm';
import { PageSpinner } from '../../components'; import { PageSpinner } from '../../components';
const server = setupServer( const server = setupServer(
http.post('/api/arm/orders', () => { http.get('/api/arm/orders', () => {
return HttpResponse.json({}, { status: 500 }); 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', () => { jest.mock('@brojs/cli', () => {

View File

@ -98,12 +98,16 @@ jest.mock('@brojs/cli', () => {
}; };
}); });
describe('Страница заказов', () => { describe('order page', () => {
beforeAll(() => server.listen()); beforeAll(() => server.listen());
afterEach(() => server.resetHandlers()); afterEach(() => server.resetHandlers());
afterAll(() => server.close()); afterAll(() => server.close());
it('должна корректно отображать список заказов после загрузки данных', async () => { it('получение списка заказов ', async () => {
server.events.on('request:start', ({ request }) => {
console.log('Outgoing:', request.method, request.url);
});
const { container } = render( const { container } = render(
<Provider store={store}> <Provider store={store}>
<ChakraProvider theme={chakraTheme}> <ChakraProvider theme={chakraTheme}>

View File

@ -70,7 +70,7 @@ const Page: FC = () => {
<> <>
<> <>
{isSuccess && ( {isSuccess && (
<VStack p={4} alignItems='flex-start' gap={4} data-testid='order-details'> <VStack p={4} alignItems='flex-start' gap={4}>
<OrderDetails <OrderDetails
orderNumber={order.orderNumber} orderNumber={order.orderNumber}
status={order.status} status={order.status}
@ -82,8 +82,9 @@ const Page: FC = () => {
startWashTime={order.startWashTime} startWashTime={order.startWashTime}
endWashTime={order.endWashTime} endWashTime={order.endWashTime}
created={order.created} created={order.created}
data-testid='order-details'
/> />
{FEATURE.carImageUpload.isOn && <CarImageForm orderId={orderId} />} <CarImageForm orderId={orderId} />
</VStack> </VStack>
)} )}
</> </>