diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..0e980c7 --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,3 @@ +BACKEND_BASE_URL="http://localhost:3000" +PUBLIC_API_BASE_URL="/api" +PUBLIC_ENABLE_MOCKING=false diff --git a/frontend/.env.mock b/frontend/.env.mock new file mode 100644 index 0000000..aa50534 --- /dev/null +++ b/frontend/.env.mock @@ -0,0 +1,2 @@ +PUBLIC_API_BASE_URL="/" +PUBLIC_ENABLE_MOCKING=true diff --git a/frontend/__mocks__/browser/data/expenses/items.ts b/frontend/__mocks__/browser/data/expenses/items.ts new file mode 100644 index 0000000..000991e --- /dev/null +++ b/frontend/__mocks__/browser/data/expenses/items.ts @@ -0,0 +1,72 @@ +export const items1 = [ + { + id: '1', + expenseListId: '1', + title: 'Счет за интернет', + description: 'Ежемесячная плата за интернет', + date: '2024-04-01', + amount: 500, + currency: 'RUB', + count: 1, + createdAt: '2024-04-01T12:00:00Z', + updatedAt: '2024-04-01T12:00:00Z', + }, + { + id: '2', + expenseListId: '1', + title: 'Покупка продуктов', + description: 'Продукты на неделю', + date: '2024-04-02', + amount: 3000, + currency: 'RUB', + count: 1, + createdAt: '2024-04-02T12:00:00Z', + updatedAt: '2024-04-02T12:00:00Z', + }, +]; + +export const items2 = []; + +export const items3 = null; + +export const items4 = [ + { + id: '3', + expenseListId: '4', + title: 'Бензин', + description: null, + date: '2024-04-03', + amount: 1500, + currency: 'RUB', + count: null, + createdAt: '2024-04-03T12:00:00Z', + updatedAt: '2024-04-03T12:00:00Z', + }, +]; + +export const items5 = [ + { + id: '4', + expenseListId: '5', + title: 'Ноутбук', + description: 'Покупка нового ноутбука', + date: '2024-04-04', + amount: 50000, + currency: 'RUB', + count: 1, + createdAt: '2024-04-04T12:00:00Z', + updatedAt: '2024-04-04T12:00:00Z', + }, + { + id: '5', + expenseListId: '5', + title: 'Мышка', + description: 'Механическая мышка', + date: '2024-04-05', + amount: 3000, + currency: 'RUB', + count: 1, + createdAt: '2024-04-05T12:00:00Z', + updatedAt: '2024-04-05T12:00:00Z', + }, +]; diff --git a/frontend/__mocks__/browser/data/expenses/lists.ts b/frontend/__mocks__/browser/data/expenses/lists.ts new file mode 100644 index 0000000..13318fe --- /dev/null +++ b/frontend/__mocks__/browser/data/expenses/lists.ts @@ -0,0 +1,47 @@ +import { + items1, + items2, + items3, + items4, + items5, +} from './items'; + +export const list1 = { + id: '1', + title: 'Расходы на быт', + items: items1, + createdAt: '2024-03-28T10:00:00Z', + updatedAt: '2024-04-02T12:00:00Z', +}; + +export const list2 = { + id: '2', + title: 'Дополнительные расходы', + items: items2, + createdAt: '2024-04-01T10:00:00Z', + updatedAt: '2024-04-01T10:00:00Z', +}; + +export const list3 = { + id: '3', + title: 'Кредиты', + items: items3, + createdAt: '2024-04-01T11:00:00Z', + updatedAt: '2024-04-01T11:00:00Z', +}; + +export const list4 = { + id: '4', + title: 'Автомобиль', + items: items4, + createdAt: '2024-04-03T10:00:00Z', + updatedAt: '2024-04-03T10:00:00Z', +}; + +export const list5 = { + id: '5', + title: 'Техника', + items: items5, + createdAt: '2024-04-04T10:00:00Z', + updatedAt: '2024-04-05T10:00:00Z', +}; diff --git a/frontend/__mocks__/browser/handlers/auth/login/post.ts b/frontend/__mocks__/browser/handlers/auth/login/post.ts new file mode 100644 index 0000000..391ec3a --- /dev/null +++ b/frontend/__mocks__/browser/handlers/auth/login/post.ts @@ -0,0 +1,9 @@ +import { http, HttpResponse } from 'msw'; + +import type { paths } from '@/shared/api/schema'; + +type LoginResponse = paths['/auth/login']['post']['responses']['200']['content']['application/json']; +export const loginHandler = http.post( + '/auth/login', + () => HttpResponse.json({ accessToken: '123' }), +); diff --git a/frontend/__mocks__/browser/handlers/expense/list/:id/get.ts b/frontend/__mocks__/browser/handlers/expense/list/:id/get.ts new file mode 100644 index 0000000..f476608 --- /dev/null +++ b/frontend/__mocks__/browser/handlers/expense/list/:id/get.ts @@ -0,0 +1,12 @@ +import { http, HttpResponse } from 'msw'; + +import type { paths } from '@/shared/api/schema'; + +type ExpensesListItemsResponse = paths['/expenses/list/{id}']['get']['responses']['200']['content']['application/json']; +export const getExpenseItemListById = (id: string) => http.get( + `/expenses/list/${id}`, + + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + async () => HttpResponse.json((await import('@mocks/browser/data/expenses/lists'))[`list${id}`]), +); diff --git a/frontend/__mocks__/browser/handlers/expense/list/get.ts b/frontend/__mocks__/browser/handlers/expense/list/get.ts new file mode 100644 index 0000000..adffdb0 --- /dev/null +++ b/frontend/__mocks__/browser/handlers/expense/list/get.ts @@ -0,0 +1,25 @@ +import { http, HttpResponse } from 'msw'; + +import { + list1, + list2, + list3, + list4, + list5, +} from '@mocks/browser/data/expenses/lists'; + +import type { paths } from '@/shared/api/schema'; + +const expenseLists = [ + list1, + list2, + list3, + list4, + list5, +]; + +type ExpensesListResponse = paths['/expenses/list']['get']['responses']['200']['content']['application/json']; +export const getExpenseList = http.get( + '/expenses/list', + () => HttpResponse.json(expenseLists), +); diff --git a/frontend/__mocks__/browser/handlers/index.ts b/frontend/__mocks__/browser/handlers/index.ts new file mode 100644 index 0000000..9fb765b --- /dev/null +++ b/frontend/__mocks__/browser/handlers/index.ts @@ -0,0 +1,3 @@ +export { getExpenseList } from './expense/list/get'; +export { getExpenseItemListById } from './expense/list/:id/get'; +export { loginHandler } from './auth/login/post'; diff --git a/frontend/__mocks__/browser/msw.ts b/frontend/__mocks__/browser/msw.ts new file mode 100644 index 0000000..1452c8a --- /dev/null +++ b/frontend/__mocks__/browser/msw.ts @@ -0,0 +1,17 @@ +import { setupWorker } from 'msw/browser'; + +import { + getExpenseList, + getExpenseItemListById, + loginHandler, +} from './handlers'; + +export const worker = setupWorker( + loginHandler, + getExpenseList, + getExpenseItemListById('1'), + getExpenseItemListById('2'), + getExpenseItemListById('3'), + getExpenseItemListById('4'), + getExpenseItemListById('5'), +); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ec87a72..575c96f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,9 +15,9 @@ "react-router": "7.8.1" }, "devDependencies": { - "@rsbuild/core": "1.4.15", + "@rsbuild/core": "1.5.3", "@rsbuild/plugin-eslint": "1.1.2", - "@rsbuild/plugin-react": "1.3.5", + "@rsbuild/plugin-react": "1.4.0", "@rsbuild/plugin-type-check": "1.2.4", "@swc/core": "1.13.5", "@swc/jest": "0.2.39", @@ -29,7 +29,7 @@ "@types/jest": "30.0.0", "@types/react": "19.1.9", "@types/react-dom": "19.1.7", - "@typescript-eslint/parser": "8.39.0", + "@typescript-eslint/parser": "8.42.0", "eslint": "9.34.0", "eslint-config-ksv741": "0.2.0", "identity-obj-proxy": "3.0.0", @@ -2518,62 +2518,62 @@ } }, "node_modules/@module-federation/error-codes": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.17.1.tgz", - "integrity": "sha512-n6Elm4qKSjwAPxLUGtwnl7qt4y1dxB8OpSgVvXBIzqI9p27a3ZXshLPLnumlpPg1Qudaj8sLnSnFtt9yGpt5yQ==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.18.0.tgz", + "integrity": "sha512-Woonm8ehyVIUPXChmbu80Zj6uJkC0dD9SJUZ/wOPtO8iiz/m+dkrOugAuKgoiR6qH4F+yorWila954tBz4uKsQ==", "dev": true, "license": "MIT" }, "node_modules/@module-federation/runtime": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.17.1.tgz", - "integrity": "sha512-vKEN32MvUbpeuB/s6UXfkHDZ9N5jFyDDJnj83UTJ8n4N1jHIJu9VZ6Yi4/Ac8cfdvU8UIK9bIbfVXWbUYZUDsw==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.18.0.tgz", + "integrity": "sha512-+C4YtoSztM7nHwNyZl6dQKGUVJdsPrUdaf3HIKReg/GQbrt9uvOlUWo2NXMZ8vDAnf/QRrpSYAwXHmWDn9Obaw==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/error-codes": "0.17.1", - "@module-federation/runtime-core": "0.17.1", - "@module-federation/sdk": "0.17.1" + "@module-federation/error-codes": "0.18.0", + "@module-federation/runtime-core": "0.18.0", + "@module-federation/sdk": "0.18.0" } }, "node_modules/@module-federation/runtime-core": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.17.1.tgz", - "integrity": "sha512-LCtIFuKgWPQ3E+13OyrVpuTPOWBMI/Ggwsq1Q874YeT8Px28b8tJRCj09DjyRFyhpSPyV/uG80T6iXPAUoLIfQ==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.18.0.tgz", + "integrity": "sha512-ZyYhrDyVAhUzriOsVfgL6vwd+5ebYm595Y13KeMf6TKDRoUHBMTLGQ8WM4TDj8JNsy7LigncK8C03fn97of0QQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/error-codes": "0.17.1", - "@module-federation/sdk": "0.17.1" + "@module-federation/error-codes": "0.18.0", + "@module-federation/sdk": "0.18.0" } }, "node_modules/@module-federation/runtime-tools": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.17.1.tgz", - "integrity": "sha512-4kr6zTFFwGywJx6whBtxsc84V+COAuuBpEdEbPZN//YLXhNB0iz2IGsy9r9wDl+06h84bD+3dQ05l9euRLgXzQ==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.18.0.tgz", + "integrity": "sha512-fSga9o4t1UfXNV/Kh6qFvRyZpPp3EHSPRISNeyT8ZoTpzDNiYzhtw0BPUSSD8m6C6XQh2s/11rI4g80UY+d+hA==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime": "0.17.1", - "@module-federation/webpack-bundler-runtime": "0.17.1" + "@module-federation/runtime": "0.18.0", + "@module-federation/webpack-bundler-runtime": "0.18.0" } }, "node_modules/@module-federation/sdk": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.17.1.tgz", - "integrity": "sha512-nlUcN6UTEi+3HWF+k8wPy7gH0yUOmCT+xNatihkIVR9REAnr7BUvHFGlPJmx7WEbLPL46+zJUbtQHvLzXwFhng==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.18.0.tgz", + "integrity": "sha512-Lo/Feq73tO2unjmpRfyyoUkTVoejhItXOk/h5C+4cistnHbTV8XHrW/13fD5e1Iu60heVdAhhelJd6F898Ve9A==", "dev": true, "license": "MIT" }, "node_modules/@module-federation/webpack-bundler-runtime": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.17.1.tgz", - "integrity": "sha512-Swspdgf4PzcbvS9SNKFlBzfq8h/Qxwqjq/xRSqw1pqAZWondZQzwTTqPXhgrg0bFlz7qWjBS/6a8KuH/gRvGaQ==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.18.0.tgz", + "integrity": "sha512-TEvErbF+YQ+6IFimhUYKK3a5wapD90d90sLsNpcu2kB3QGT7t4nIluE25duXuZDVUKLz86tEPrza/oaaCWTpvQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime": "0.17.1", - "@module-federation/sdk": "0.17.1" + "@module-federation/runtime": "0.18.0", + "@module-federation/sdk": "0.18.0" } }, "node_modules/@mswjs/interceptors": { @@ -2771,23 +2771,23 @@ } }, "node_modules/@rsbuild/core": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-1.4.15.tgz", - "integrity": "sha512-KoSTtKjzQUQwamcbeCp63Ne9kL7io1WI4+skTJe2chfLz6wsp/Gfg8aKkfs1DuyG1p+zxFDcYpwTWMsNtxqqiw==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-1.5.3.tgz", + "integrity": "sha512-asJYYmpMmYVEPgqR2hoPlW5pFtMYf7+dq9/u8vP0uY+Xbi9aUoguskc783qSi1yu9KvZwDbwpbfCuoBAt8anIA==", "dev": true, "license": "MIT", "dependencies": { - "@rspack/core": "1.4.11", + "@rspack/core": "1.5.2", "@rspack/lite-tapable": "~1.0.1", "@swc/helpers": "^0.5.17", - "core-js": "~3.45.0", + "core-js": "~3.45.1", "jiti": "^2.5.1" }, "bin": { "rsbuild": "bin/rsbuild.js" }, "engines": { - "node": ">=16.10.0" + "node": ">=18.12.0" } }, "node_modules/@rsbuild/plugin-eslint": { @@ -2810,13 +2810,13 @@ } }, "node_modules/@rsbuild/plugin-react": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@rsbuild/plugin-react/-/plugin-react-1.3.5.tgz", - "integrity": "sha512-L/GoHgJV4j+EQbI4KOhe5EscM0OHgnSat1eR0Nt5P3JZxpJV2ryO5Yfx5jElPWOkYZZCuk+EWhHWDQ4CkeC5BQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rsbuild/plugin-react/-/plugin-react-1.4.0.tgz", + "integrity": "sha512-YhhOUOonJBjnKpUf7E4iXKidldPWAGmYBRtDjQgcSmW4tbW0DasFpNCqLn5870Q2Ly6oCU06sLv+8G597I36+w==", "dev": true, "license": "MIT", "dependencies": { - "@rspack/plugin-react-refresh": "~1.4.3", + "@rspack/plugin-react-refresh": "^1.5.0", "react-refresh": "^0.17.0" }, "peerDependencies": { @@ -2858,28 +2858,28 @@ } }, "node_modules/@rspack/binding": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.4.11.tgz", - "integrity": "sha512-maGl/zRwnl0QVwkBCkgjn5PH20L9HdlRIdkYhEsfTepy5x2QZ0ti/0T49djjTJQrqb+S1i6wWQymMMMMMsxx6Q==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.5.2.tgz", + "integrity": "sha512-NKiBcsxmAzFDYRnK2ZHWbTtDFVT5/704eK4OfpgsDXPMkaMnBKijMKNgP5pbe18X4rUlz+8HnGm4+Xllo9EESw==", "dev": true, "license": "MIT", "optionalDependencies": { - "@rspack/binding-darwin-arm64": "1.4.11", - "@rspack/binding-darwin-x64": "1.4.11", - "@rspack/binding-linux-arm64-gnu": "1.4.11", - "@rspack/binding-linux-arm64-musl": "1.4.11", - "@rspack/binding-linux-x64-gnu": "1.4.11", - "@rspack/binding-linux-x64-musl": "1.4.11", - "@rspack/binding-wasm32-wasi": "1.4.11", - "@rspack/binding-win32-arm64-msvc": "1.4.11", - "@rspack/binding-win32-ia32-msvc": "1.4.11", - "@rspack/binding-win32-x64-msvc": "1.4.11" + "@rspack/binding-darwin-arm64": "1.5.2", + "@rspack/binding-darwin-x64": "1.5.2", + "@rspack/binding-linux-arm64-gnu": "1.5.2", + "@rspack/binding-linux-arm64-musl": "1.5.2", + "@rspack/binding-linux-x64-gnu": "1.5.2", + "@rspack/binding-linux-x64-musl": "1.5.2", + "@rspack/binding-wasm32-wasi": "1.5.2", + "@rspack/binding-win32-arm64-msvc": "1.5.2", + "@rspack/binding-win32-ia32-msvc": "1.5.2", + "@rspack/binding-win32-x64-msvc": "1.5.2" } }, "node_modules/@rspack/binding-darwin-arm64": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.4.11.tgz", - "integrity": "sha512-PrmBVhR8MC269jo6uQ+BMy1uwIDx0HAJYLQRQur8gXiehWabUBCRg/d4U9KR7rLzdaSScRyc5JWXR52T7/4MfA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.5.2.tgz", + "integrity": "sha512-aO76T6VQvAFt1LJNRA5aPOJ+szeTLlzC5wubsnxgWWjG53goP+Te35kFjDIDe+9VhKE/XqRId6iNAymaEsN+Uw==", "cpu": [ "arm64" ], @@ -2891,9 +2891,9 @@ ] }, "node_modules/@rspack/binding-darwin-x64": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.4.11.tgz", - "integrity": "sha512-YIV8Wzy+JY0SoSsVtN4wxFXOjzxxVPnVXNswrrfqVUTPr9jqGOFYUWCGpbt8lcCgfuBFm6zN8HpOsKm1xUNsVA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.5.2.tgz", + "integrity": "sha512-XNSmUOwdGs2PEdCKTFCC0/vu/7U9nMhAlbHJKlmdt0V4iPvFyaNWxkNdFqzLc05jlJOfgDdwbwRb91y9IcIIFQ==", "cpu": [ "x64" ], @@ -2905,9 +2905,9 @@ ] }, "node_modules/@rspack/binding-linux-arm64-gnu": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.4.11.tgz", - "integrity": "sha512-ms6uwECUIcu+6e82C5HJhRMHnfsI+l33v7XQezntzRPN0+sG3EpikEoT7SGbgt4vDwaWLR7wS20suN4qd5r3GA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.5.2.tgz", + "integrity": "sha512-rNxRfgC5khlrhyEP6y93+45uQ4TI7CdtWqh5PKsaR6lPepG1rH4L8VE+etejSdhzXH6wQ76Rw4wzb96Hx+5vuQ==", "cpu": [ "arm64" ], @@ -2919,9 +2919,9 @@ ] }, "node_modules/@rspack/binding-linux-arm64-musl": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.4.11.tgz", - "integrity": "sha512-9evq0DOdxMN/H8VM8ZmyY9NSuBgILNVV6ydBfVPMHPx4r1E7JZGpWeKDegZcS5Erw3sS9kVSIxyX78L5PDzzKw==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.5.2.tgz", + "integrity": "sha512-kTFX+KsGgArWC5q+jJWz0K/8rfVqZOn1ojv1xpCCcz/ogWRC/qhDGSOva6Wandh157BiR93Vfoe1gMvgjpLe5g==", "cpu": [ "arm64" ], @@ -2933,9 +2933,9 @@ ] }, "node_modules/@rspack/binding-linux-x64-gnu": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.4.11.tgz", - "integrity": "sha512-bHYFLxPPYBOSaHdQbEoCYGMQ1gOrEWj7Mro/DLfSHZi1a0okcQ2Q1y0i1DczReim3ZhLGNrK7k1IpFXCRbAobQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.5.2.tgz", + "integrity": "sha512-Lh/6WZGq30lDV6RteQQu7Phw0RH2Z1f4kGR+MsplJ6X4JpnziDow+9oxKdu6FvFHWxHByncpveVeInusQPmL7Q==", "cpu": [ "x64" ], @@ -2947,9 +2947,9 @@ ] }, "node_modules/@rspack/binding-linux-x64-musl": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.4.11.tgz", - "integrity": "sha512-wrm4E7q2k4+cwT6Uhp6hIQ3eUe/YoaUttj6j5TqHYZX6YeLrNPtD9+ne6lQQ17BV8wmm6NZsmoFIJ5xIptpRhQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.5.2.tgz", + "integrity": "sha512-CsLC/SIOIFs6CBmusSAF0FECB62+J36alMdwl7j6TgN6nX3UQQapnL1aVWuQaxU6un/1Vpim0V/EZbUYIdJQ4g==", "cpu": [ "x64" ], @@ -2961,9 +2961,9 @@ ] }, "node_modules/@rspack/binding-wasm32-wasi": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.4.11.tgz", - "integrity": "sha512-hiYxHZjaZ17wQtXyLCK0IdtOvMWreGVTiGsaHCxyeT+SldDG+r16bXNjmlqfZsjlfl1mkAqKz1dg+mMX28OTqw==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.5.2.tgz", + "integrity": "sha512-cuVbGr1b4q0Z6AtEraI3becZraPMMgZtZPRaIsVLeDXCmxup/maSAR3T6UaGf4Q2SNcFfjw4neGz5UJxPK8uvA==", "cpu": [ "wasm32" ], @@ -2975,9 +2975,9 @@ } }, "node_modules/@rspack/binding-win32-arm64-msvc": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.4.11.tgz", - "integrity": "sha512-+HF/mnjmTr8PC1dccRt1bkrD2tPDGeqvXC1BBLYd/Klq1VbtIcnrhfmvQM6KaXbiLcY9VWKzcZPOTmnyZ8TaHQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.5.2.tgz", + "integrity": "sha512-4vJQdzRTSuvmvL3vrOPuiA7f9v9frNc2RFWDxqg+GYt0YAjDStssp+lkVbRYyXnTYVJkARSuO6N+BOiI+kLdsQ==", "cpu": [ "arm64" ], @@ -2989,9 +2989,9 @@ ] }, "node_modules/@rspack/binding-win32-ia32-msvc": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.4.11.tgz", - "integrity": "sha512-EU2fQGwrRfwFd/tcOInlD0jy6gNQE4Q3Ayj0Is+cX77sbhPPyyOz0kZDEaQ4qaN2VU8w4Hu/rrD7c0GAKLFvCw==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.5.2.tgz", + "integrity": "sha512-zPbu3lx/NrNxdjZzTIjwD0mILUOpfhuPdUdXIFiOAO8RiWSeQpYOvyI061s/+bNOmr4A+Z0uM0dEoOClfkhUFg==", "cpu": [ "ia32" ], @@ -3003,9 +3003,9 @@ ] }, "node_modules/@rspack/binding-win32-x64-msvc": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.4.11.tgz", - "integrity": "sha512-1Nc5ZzWqfvE+iJc47qtHFzYYnHsC3awavXrCo74GdGip1vxtksM3G30BlvAQHHVtEmULotWqPbjZpflw/Xk9Ag==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.5.2.tgz", + "integrity": "sha512-duLNUTshX38xhC10/W9tpkPca7rOifP2begZjdb1ikw7C4AI0I7VnBnYt8qPSxGISoclmhOBxU/LuAhS8jMMlg==", "cpu": [ "x64" ], @@ -3017,18 +3017,18 @@ ] }, "node_modules/@rspack/core": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.4.11.tgz", - "integrity": "sha512-JtKnL6p7Kc/YgWQJF3Woo4OccbgKGyT/4187W4dyex8BMkdQcbqCNIdi6dFk02hwQzxpOOxRSBI4hlGRbz7oYQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.5.2.tgz", + "integrity": "sha512-ifjHqLczC81d1xjXPXCzxTFKNOFsEzuuLN44cMnyzQ/GWi4B48fyX7JHndWE7Lxd54cW1O9Ik7AdBN3Gq891EA==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime-tools": "0.17.1", - "@rspack/binding": "1.4.11", + "@module-federation/runtime-tools": "0.18.0", + "@rspack/binding": "1.5.2", "@rspack/lite-tapable": "1.0.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.12.0" }, "peerDependencies": { "@swc/helpers": ">=0.5.1" @@ -3050,9 +3050,9 @@ } }, "node_modules/@rspack/plugin-react-refresh": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.4.3.tgz", - "integrity": "sha512-wZx4vWgy5oMEvgyNGd/oUKcdnKaccYWHCRkOqTdAPJC3WcytxhTX+Kady8ERurSBiLyQpoMiU3Iyd+F1Y2Arbw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.5.0.tgz", + "integrity": "sha512-pYOmc1mrK8Ui/7VWUgjKt9YqrxFn4woURTgGpFYWwsFvJxmWm05zog4fUbChvErbaBHkx1aA+KHxIvM/6tFODg==", "dev": true, "license": "MIT", "dependencies": { @@ -3104,14 +3104,14 @@ } }, "node_modules/@stylistic/eslint-plugin": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.2.0.tgz", - "integrity": "sha512-RCEdbREv9EBiToUBQTlRhVYKG093I6ZnnQ990j08eJ6uRZh71DXkOnoxtTLfDQ6utVCVQzrhZFHZP0zfrfOIjA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.3.1.tgz", + "integrity": "sha512-Ykums1VYonM0TgkD0VteVq9mrlO2FhF48MDJnPyv3MktIB2ydtuhlO0AfWm7xnW1kyf5bjOqA6xc7JjviuVTxg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/types": "^8.37.0", + "@typescript-eslint/types": "^8.41.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "estraverse": "^5.3.0", @@ -3813,17 +3813,57 @@ "dev": true, "license": "MIT" }, - "node_modules/@typescript-eslint/parser": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.0.tgz", - "integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.42.0.tgz", + "integrity": "sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/type-utils": "8.42.0", + "@typescript-eslint/utils": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.42.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.42.0.tgz", + "integrity": "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", "debug": "^4.3.4" }, "engines": { @@ -3839,14 +3879,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz", - "integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.42.0.tgz", + "integrity": "sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.39.0", - "@typescript-eslint/types": "^8.39.0", + "@typescript-eslint/tsconfig-utils": "^8.42.0", + "@typescript-eslint/types": "^8.42.0", "debug": "^4.3.4" }, "engines": { @@ -3861,14 +3901,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz", - "integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.42.0.tgz", + "integrity": "sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0" + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3879,9 +3919,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz", - "integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.42.0.tgz", + "integrity": "sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==", "dev": true, "license": "MIT", "engines": { @@ -3895,10 +3935,35 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.42.0.tgz", + "integrity": "sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/utils": "8.42.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/@typescript-eslint/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz", - "integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.42.0.tgz", + "integrity": "sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==", "dev": true, "license": "MIT", "engines": { @@ -3910,16 +3975,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz", - "integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.42.0.tgz", + "integrity": "sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.39.0", - "@typescript-eslint/tsconfig-utils": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0", + "@typescript-eslint/project-service": "8.42.0", + "@typescript-eslint/tsconfig-utils": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3978,16 +4043,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.42.0.tgz", + "integrity": "sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0" + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4002,13 +4067,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz", - "integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.42.0.tgz", + "integrity": "sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/types": "8.42.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -5308,9 +5373,9 @@ } }, "node_modules/core-js": { - "version": "3.45.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.0.tgz", - "integrity": "sha512-c2KZL9lP4DjkN3hk/an4pWn5b5ZefhRJnAc42n6LJ19kSnbeRbdQZE5dSeE2LBol1OwJD3X1BQvFTAsa8ReeDA==", + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", + "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5968,66 +6033,6 @@ } } }, - "node_modules/eslint-config-ksv741/node_modules/@typescript-eslint/scope-manager": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", - "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-config-ksv741/node_modules/@typescript-eslint/types": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", - "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-config-ksv741/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", - "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.37.0", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-config-ksv741/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/eslint-config-ksv741/node_modules/globals": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", @@ -6041,241 +6046,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-config-ksv741/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint-config-ksv741/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/eslint-config-ksv741/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-config-ksv741/node_modules/typescript-eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz", - "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.37.0", - "@typescript-eslint/parser": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/utils": "8.37.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/eslint-config-ksv741/node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", - "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/type-utils": "8.37.0", - "@typescript-eslint/utils": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.37.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/eslint-config-ksv741/node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", - "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/utils": "8.37.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/eslint-config-ksv741/node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", - "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/eslint-config-ksv741/node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", - "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.37.0", - "@typescript-eslint/tsconfig-utils": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/eslint-config-ksv741/node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", - "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.37.0", - "@typescript-eslint/types": "^8.37.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/eslint-config-ksv741/node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", - "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/eslint-config-ksv741/node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", - "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, "node_modules/eslint-import-context": { "version": "0.1.9", "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", @@ -12872,6 +12642,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.42.0.tgz", + "integrity": "sha512-ozR/rQn+aQXQxh1YgbCzQWDFrsi9mcg+1PM3l/z5o1+20P7suOIaNg515bpr/OYt6FObz/NHcBstydDLHWeEKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/utils": "8.42.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 828adcb..edf086b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,11 +6,12 @@ "scripts": { "build": "rsbuild build", "dev": "rsbuild dev", + "dev:mock": "rsbuild dev --env-mode mock", "preview": "rsbuild preview", "lint": "eslint", "test:unit": "jest", "typecheck": "tsc --noEmit", - "openapi:gen": "npx openapi-typescript http://localhost:3000/docs/swagger.json -o src/shared/api/schema.ts" + "openapi:gen": "openapi-typescript http://localhost:3000/docs/swagger.json -o src/shared/api/schema.ts" }, "dependencies": { "@tanstack/react-query": "5.85.5", @@ -20,9 +21,9 @@ "react-router": "7.8.1" }, "devDependencies": { - "@rsbuild/core": "1.4.15", + "@rsbuild/core": "1.5.3", "@rsbuild/plugin-eslint": "1.1.2", - "@rsbuild/plugin-react": "1.3.5", + "@rsbuild/plugin-react": "1.4.0", "@rsbuild/plugin-type-check": "1.2.4", "@swc/core": "1.13.5", "@swc/jest": "0.2.39", diff --git a/frontend/public/mockServiceWorker.js b/frontend/public/mockServiceWorker.js new file mode 100644 index 0000000..723b071 --- /dev/null +++ b/frontend/public/mockServiceWorker.js @@ -0,0 +1,344 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + */ + +const PACKAGE_VERSION = '2.10.5' +const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +addEventListener('install', function () { + self.skipWaiting() +}) + +addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +addEventListener('message', async function (event) { + const clientId = Reflect.get(event.source || {}, 'id') + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +addEventListener('fetch', function (event) { + // Bypass navigation requests. + if (event.request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if ( + event.request.cache === 'only-if-cached' && + event.request.mode !== 'same-origin' + ) { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +/** + * @param {FetchEvent} event + * @param {string} requestId + */ +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const requestCloneForEvents = event.request.clone() + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Clone the response so both the client and the library could consume it. + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + headers: Object.fromEntries(responseClone.headers.entries()), + body: responseClone.body, + }, + }, + }, + responseClone.body ? [serializedRequest.body, responseClone.body] : [], + ) + } + + return response +} + +/** + * Resolve the main client for the given event. + * Client that issues a request doesn't necessarily equal the client + * that registered the worker. It's with the latter the worker should + * communicate with during the response resolving phase. + * @param {FetchEvent} event + * @returns {Promise} + */ +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @returns {Promise} + */ +async function getResponse(event, client, requestId) { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = event.request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const serializedRequest = await serializeRequest(event.request) + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + ...serializedRequest, + }, + }, + [serializedRequest.body], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [ + channel.port2, + ...transferrables.filter(Boolean), + ]) + }) +} + +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.arrayBuffer(), + keepalive: request.keepalive, + } +} diff --git a/frontend/rsbuild.config.ts b/frontend/rsbuild.config.ts index db268c2..1c235df 100644 --- a/frontend/rsbuild.config.ts +++ b/frontend/rsbuild.config.ts @@ -20,10 +20,12 @@ export default defineConfig({ }, server: { proxy: { - '/api': { - target: 'http://localhost:3000', + [`${process.env.PUBLIC_API_BASE_URL}`]: { + target: process.env.BACKEND_BASE_URL, changeOrigin: true, - pathRewrite: { '^/api': '' }, + pathRewrite: { + [`^${process.env.PUBLIC_API_BASE_URL}`]: '', + }, }, }, }, diff --git a/frontend/src/features/expenses/items/AddExpenseItemButton/ui/AddExpenseItemButton.test.tsx b/frontend/src/features/expenses/items/AddExpenseItemButton/ui/AddExpenseItemButton.test.tsx index c1a16ec..ba0407e 100644 --- a/frontend/src/features/expenses/items/AddExpenseItemButton/ui/AddExpenseItemButton.test.tsx +++ b/frontend/src/features/expenses/items/AddExpenseItemButton/ui/AddExpenseItemButton.test.tsx @@ -45,25 +45,25 @@ const submitHandler = (requestSpy: jest.Mock) => http.post< AddExpenseItemBody, AddExpenseItemSuccessResponse >( - 'http://localhost:3000/expenses/list/123/items', - async ({ request }) => { - const body = await request.json(); - requestSpy(body); + '/expenses/list/123/items', + async ({ request }) => { + const body = await request.json(); + requestSpy(body); - return HttpResponse.json({ - id: '51a44dc0-597b-49b9-85bb-e0bcd42db3f6', - title: 'Mock item', - amount: 12, - count: 12, - updatedAt: '2025-08-29T07:57:40.503Z', - expenseListId: '10cc093c-3875-4d33-a40a-df526266e262', - createdAt: '2025-08-29T07:57:40.503Z', - currency: 'RUB', - date: '2025-08-22T00:00:03.000Z', - description: 'Mock description', - }); - }, - ); + return HttpResponse.json({ + id: '51a44dc0-597b-49b9-85bb-e0bcd42db3f6', + title: 'Mock item', + amount: 12, + count: 12, + updatedAt: '2025-08-29T07:57:40.503Z', + expenseListId: '10cc093c-3875-4d33-a40a-df526266e262', + createdAt: '2025-08-29T07:57:40.503Z', + currency: 'RUB', + date: '2025-08-22T00:00:03.000Z', + description: 'Mock description', + }); + }, +); describe('Test AddExpenseItemButton', () => { test('should correct render button', () => { diff --git a/frontend/src/features/expenses/items/DeleteExpenseItemButton/ui/DeleteExpenseItemButton.test.tsx b/frontend/src/features/expenses/items/DeleteExpenseItemButton/ui/DeleteExpenseItemButton.test.tsx index 1bc8f3e..0fa2f40 100644 --- a/frontend/src/features/expenses/items/DeleteExpenseItemButton/ui/DeleteExpenseItemButton.test.tsx +++ b/frontend/src/features/expenses/items/DeleteExpenseItemButton/ui/DeleteExpenseItemButton.test.tsx @@ -45,7 +45,7 @@ const openModal = async () => { }; }; -const submitHandler = http.delete(`http://localhost:3000/expenses/list/${deletedItem.expenseListId}/items/${deletedItem.id}`, () => HttpResponse.json(deletedItem)); +const submitHandler = http.delete(`/expenses/list/${deletedItem.expenseListId}/items/${deletedItem.id}`, () => HttpResponse.json(deletedItem)); describe('Test DeleteExpenseItemButton', () => { test('should correct render button', () => { diff --git a/frontend/src/features/expenses/items/EditExpenseItemButton/ui/EditExpenseItemButton.test.tsx b/frontend/src/features/expenses/items/EditExpenseItemButton/ui/EditExpenseItemButton.test.tsx index 74ddd5b..03366e2 100644 --- a/frontend/src/features/expenses/items/EditExpenseItemButton/ui/EditExpenseItemButton.test.tsx +++ b/frontend/src/features/expenses/items/EditExpenseItemButton/ui/EditExpenseItemButton.test.tsx @@ -50,18 +50,18 @@ const openModal = async () => { }; const submitHandler = (requestSpy: jest.Mock) => http.patch< - EditExpenseItemPath, - EditExpenseItemBody, - EditExpenseItemSuccessResponse - >( - `http://localhost:3000/expenses/list/${editingItem.expenseListId}/items/${editingItem.id}`, - async ({ request }) => { - const body = await request.json(); - requestSpy(body); + EditExpenseItemPath, + EditExpenseItemBody, + EditExpenseItemSuccessResponse +>( + `/expenses/list/${editingItem.expenseListId}/items/${editingItem.id}`, + async ({ request }) => { + const body = await request.json(); + requestSpy(body); - return HttpResponse.json(editingItem); - }, - ); + return HttpResponse.json(editingItem); + }, +); const fillForm = async (data: { title: string; description: string; date: string; amount: string; count: string }) => { const titleInputElem = screen.getByLabelText('Название'); diff --git a/frontend/src/features/expenses/list/AddExpensesListButton/ui/AddExpensesListButton.test.tsx b/frontend/src/features/expenses/list/AddExpensesListButton/ui/AddExpensesListButton.test.tsx index ff0d172..2e3d422 100644 --- a/frontend/src/features/expenses/list/AddExpensesListButton/ui/AddExpensesListButton.test.tsx +++ b/frontend/src/features/expenses/list/AddExpensesListButton/ui/AddExpensesListButton.test.tsx @@ -37,20 +37,20 @@ const fillForm = async (data: { title: string }) => { }; const submitHandler = (requestSpy: jest.Mock) => http.post( - 'http://localhost:3000/expenses/list', - async ({ request }) => { - const body = await request.json(); - requestSpy(body); + '/expenses/list', + async ({ request }) => { + const body = await request.json(); + requestSpy(body); - return HttpResponse.json({ - id: '10cc093c-3875-4d33-a40a-df526266e262', - title: 'Mock list', - items: [], - updatedAt: '2025-08-29T07:57:40.503Z', - createdAt: '2025-08-29T07:57:40.503Z', - }); - }, - ); + return HttpResponse.json({ + id: '10cc093c-3875-4d33-a40a-df526266e262', + title: 'Mock list', + items: [], + updatedAt: '2025-08-29T07:57:40.503Z', + createdAt: '2025-08-29T07:57:40.503Z', + }); + }, +); describe('Test AddExpensesListButton', () => { test('should correct render button', () => { diff --git a/frontend/src/features/expenses/list/DeleteExpensesListButton/ui/DeleteExpensesListButton.test.tsx b/frontend/src/features/expenses/list/DeleteExpensesListButton/ui/DeleteExpensesListButton.test.tsx index b4cf25f..33d4505 100644 --- a/frontend/src/features/expenses/list/DeleteExpensesListButton/ui/DeleteExpensesListButton.test.tsx +++ b/frontend/src/features/expenses/list/DeleteExpensesListButton/ui/DeleteExpensesListButton.test.tsx @@ -40,7 +40,7 @@ const openModal = async () => { }; }; -const submitHandler = http.delete(`http://localhost:3000/expenses/list/${deletingList.id}`, () => HttpResponse.json(deletingList)); +const submitHandler = http.delete(`/expenses/list/${deletingList.id}`, () => HttpResponse.json(deletingList)); describe('Test DeleteExpensesListButton', () => { test('should correct render button', () => { diff --git a/frontend/src/features/expenses/list/EditExpensesListButton/ui/EditExpensesListButton.test.tsx b/frontend/src/features/expenses/list/EditExpensesListButton/ui/EditExpensesListButton.test.tsx index c6a4124..8e1dd99 100644 --- a/frontend/src/features/expenses/list/EditExpensesListButton/ui/EditExpensesListButton.test.tsx +++ b/frontend/src/features/expenses/list/EditExpensesListButton/ui/EditExpensesListButton.test.tsx @@ -45,18 +45,18 @@ const openModal = async () => { }; const submitHandler = (requestSpy: jest.Mock) => http.patch< - EditExpenseListPath, - EditExpenseListBody, - EditExpenseListSuccessResponse - >( - `http://localhost:3000/expenses/list/${editingList.id}`, - async ({ request }) => { - const body = await request.json(); - requestSpy(body); + EditExpenseListPath, + EditExpenseListBody, + EditExpenseListSuccessResponse +>( + `/expenses/list/${editingList.id}`, + async ({ request }) => { + const body = await request.json(); + requestSpy(body); - return HttpResponse.json(editingList); - }, - ); + return HttpResponse.json(editingList); + }, +); const fillForm = async (data: { title: string }) => { const titleInputElem = screen.getByLabelText('Название'); diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index e31917a..1367bc4 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -4,11 +4,24 @@ import ReactDOM from 'react-dom/client'; import { App } from '@/app/App'; const rootEl = document.getElementById('root'); + +async function enableMocking() { + if (process.env.PUBLIC_ENABLE_MOCKING !== 'true') { + return; + } + + const { worker } = await import('@mocks/browser/msw'); + + return worker.start(); +} + if (rootEl) { const root = ReactDOM.createRoot(rootEl); - root.render( - - - , - ); + enableMocking().then(() => { + root.render( + + + , + ); + }); } diff --git a/frontend/src/shared/api/client.ts b/frontend/src/shared/api/client.ts index c513ef9..a7b4ef6 100644 --- a/frontend/src/shared/api/client.ts +++ b/frontend/src/shared/api/client.ts @@ -4,7 +4,7 @@ import { PAGES } from '@/shared/router'; import type { paths } from './schema'; -const BASE_URL = 'http://localhost:3000'; +const BASE_URL = process.env.PUBLIC_API_BASE_URL; export const api = axios.create({ baseURL: BASE_URL,