mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 21:10:38 +03:00
R: Setup testing environment and mocks
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run
This commit is contained in:
parent
32f83c4122
commit
1c904f965c
1
.github/workflows/frontend.yml
vendored
1
.github/workflows/frontend.yml
vendored
|
@ -41,6 +41,7 @@ jobs:
|
|||
- name: Run CI
|
||||
run: |
|
||||
npm run lint
|
||||
npm run lint:playwright
|
||||
npm test
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
|
|
25
.vscode/settings.json
vendored
25
.vscode/settings.json
vendored
|
@ -4,7 +4,30 @@
|
|||
".pytest_cache/": true
|
||||
},
|
||||
"typescript.tsdk": "rsconcept/frontend/node_modules/typescript/lib",
|
||||
"eslint.workingDirectories": ["rsconcept/frontend"],
|
||||
"eslint.probe": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
],
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
],
|
||||
"eslint.workingDirectories": [
|
||||
{
|
||||
"directory": "./",
|
||||
"overrideConfigFile": "eslint.config.js",
|
||||
"changeProcessCWD": true
|
||||
},
|
||||
{
|
||||
"directory": "./",
|
||||
"pattern": "tests/**",
|
||||
"overrideConfigFile": "eslint.playwright.config.js"
|
||||
}
|
||||
],
|
||||
"isort.args": [
|
||||
"--line-length",
|
||||
"100",
|
||||
|
|
|
@ -66,6 +66,7 @@ This readme file is used mostly to document project dependencies and conventions
|
|||
- eslint-plugin-simple-import-sort
|
||||
- eslint-plugin-react-hooks
|
||||
- eslint-plugin-tsdoc
|
||||
- eslint-plugin-playwright
|
||||
- babel-plugin-react-compiler
|
||||
- vite
|
||||
- jest
|
||||
|
|
|
@ -77,18 +77,6 @@ class AuthSerializer(serializers.Serializer):
|
|||
}
|
||||
|
||||
|
||||
class UserInfoSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: User data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = models.User
|
||||
fields = [
|
||||
'id',
|
||||
'first_name',
|
||||
'last_name',
|
||||
]
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: User data. '''
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
# Frontend Developer guidelines
|
||||
|
||||
Styling conventions
|
||||
|
||||
- static > conditional static > props. All dynamic styling should go in styles props
|
||||
- dimensions = rectangle + outer layout
|
||||
|
||||
<details>
|
||||
<summary>clsx className grouping and order</summary>
|
||||
<pre>
|
||||
- layer: z-position
|
||||
- outer layout: fixed bottom-1/2 left-0 -translate-x-1/2
|
||||
- rectangle: mt-3 min-w-fit min-w-10 flex-grow shrink-0
|
||||
- inner layout: px-3 py-2 flex flex-col gap-3 justify-between items-center
|
||||
- overflow behavior: overflow-scroll overscroll-contain
|
||||
- border: borer-2 outline-none shadow-md
|
||||
- text: text-start text-sm font-semibold whitespace-nowrap bg-prim-200 fg-app-100
|
||||
- behavior modifiers: select-none disabled:cursor-auto
|
||||
- transitions:
|
||||
</pre>
|
||||
</details>
|
|
@ -11,7 +11,16 @@ export default [
|
|||
...typescriptPlugin.configs.recommendedTypeChecked,
|
||||
...typescriptPlugin.configs.stylisticTypeChecked,
|
||||
{
|
||||
ignores: ['**/parser.ts', '**/node_modules/**', '**/public/**', '**/dist/**', 'eslint.config.js']
|
||||
ignores: [
|
||||
'**/parser.ts',
|
||||
'**/node_modules/**',
|
||||
'**/public/**',
|
||||
'**/dist/**',
|
||||
'eslint.config.js',
|
||||
'playwright.config.ts',
|
||||
'eslint.playwright.config.js',
|
||||
'tests/**'
|
||||
]
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
|
@ -20,7 +29,7 @@ export default [
|
|||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
globals: { ...globals.browser, ...globals.es2020, ...globals.jest },
|
||||
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||
project: ['./tsconfig.json', './tsconfig.vite.json'],
|
||||
projectService: true
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +45,8 @@ export default [
|
|||
settings: { react: { version: 'detect' } },
|
||||
rules: {
|
||||
'react-compiler/react-compiler': 'error',
|
||||
'react-refresh/only-export-components': ['off', { allowConstantExport: true }],
|
||||
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'warn',
|
||||
{
|
||||
|
@ -55,8 +66,8 @@ export default [
|
|||
}
|
||||
],
|
||||
|
||||
'react-refresh/only-export-components': ['off', { allowConstantExport: true }],
|
||||
|
||||
'simple-import-sort/exports': 'error',
|
||||
'import/no-duplicates': 'warn',
|
||||
'simple-import-sort/imports': [
|
||||
'warn',
|
||||
{
|
||||
|
@ -82,8 +93,6 @@ export default [
|
|||
]
|
||||
}
|
||||
],
|
||||
'simple-import-sort/exports': 'error',
|
||||
'import/no-duplicates': 'warn',
|
||||
|
||||
...reactHooksPlugin.configs.recommended.rules
|
||||
}
|
||||
|
|
40
rsconcept/frontend/eslint.playwright.config.js
Normal file
40
rsconcept/frontend/eslint.playwright.config.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import globals from 'globals';
|
||||
import typescriptParser from '@typescript-eslint/parser';
|
||||
import playwright from 'eslint-plugin-playwright';
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
||||
|
||||
export default [
|
||||
{
|
||||
...playwright.configs['flat/recommended'],
|
||||
|
||||
languageOptions: {
|
||||
parser: typescriptParser,
|
||||
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.es2020
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
plugins: {
|
||||
'playwright': playwright,
|
||||
'simple-import-sort': simpleImportSort,
|
||||
'import': importPlugin
|
||||
},
|
||||
|
||||
rules: {
|
||||
...playwright.configs['flat/recommended'].rules,
|
||||
|
||||
'simple-import-sort/exports': 'error',
|
||||
'import/no-duplicates': 'warn',
|
||||
'simple-import-sort/imports': 'warn'
|
||||
},
|
||||
|
||||
files: ['tests/**/*.ts']
|
||||
}
|
||||
];
|
49
rsconcept/frontend/package-lock.json
generated
49
rsconcept/frontend/package-lock.json
generated
|
@ -53,6 +53,7 @@
|
|||
"babel-plugin-react-compiler": "^19.0.0-beta-21e868a-20250216",
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-playwright": "^2.2.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react-compiler": "^19.0.0-beta-21e868a-20250216",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
|
@ -5738,6 +5739,54 @@
|
|||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-playwright": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.2.0.tgz",
|
||||
"integrity": "sha512-qSQpAw7RcSzE3zPp8FMGkthaCWovHZ/BsXtpmnGax9vQLIovlh1bsZHEa2+j2lv9DWhnyeLM/qZmp7ffQZfQvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"examples"
|
||||
],
|
||||
"dependencies": {
|
||||
"globals": "^13.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8.40.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-playwright/node_modules/globals": {
|
||||
"version": "13.24.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
|
||||
"integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"type-fest": "^0.20.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-playwright/node_modules/type-fest": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
||||
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react": {
|
||||
"version": "7.37.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz",
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"teste2e": "playwright test",
|
||||
"dev": "vite --host",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
|
||||
"lint": "eslint . --config eslint.config.js --report-unused-disable-directives --max-warnings 0",
|
||||
"lint:playwright": "eslint . --config eslint.playwright.config.js --report-unused-disable-directives --max-warnings 0",
|
||||
"lintFix": "eslint . --report-unused-disable-directives --max-warnings 0 --fix",
|
||||
"preview": "vite preview --port 3000"
|
||||
},
|
||||
|
@ -59,6 +60,7 @@
|
|||
"babel-plugin-react-compiler": "^19.0.0-beta-21e868a-20250216",
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-playwright": "^2.2.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react-compiler": "^19.0.0-beta-21e868a-20250216",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
|
|
|
@ -6,9 +6,8 @@ test('should load the homepage and display login button', async ({ page }) => {
|
|||
await authAnonymous(page);
|
||||
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('.h-full > .mr-1');
|
||||
await expect(page).toHaveTitle('Концепт Портал');
|
||||
|
||||
await page.click('.h-full > .mr-1');
|
||||
await page.locator('.h-full > .mr-1').click();
|
||||
await expect(page.getByText('Логин или email')).toBeVisible();
|
||||
});
|
||||
|
|
|
@ -1,9 +1,43 @@
|
|||
import { type Page } from '@playwright/test';
|
||||
|
||||
import { type ICurrentUser } from '../../src/features/auth/backend/types';
|
||||
import { BACKEND_URL } from '../constants';
|
||||
|
||||
const dataAnonymousAuth: ICurrentUser = {
|
||||
id: null,
|
||||
username: '',
|
||||
is_staff: false,
|
||||
editor: []
|
||||
};
|
||||
|
||||
const dataAdminAuth: ICurrentUser = {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
is_staff: true,
|
||||
editor: []
|
||||
};
|
||||
|
||||
const dataUserAuth: ICurrentUser = {
|
||||
id: 2,
|
||||
username: 'user',
|
||||
is_staff: false,
|
||||
editor: [2]
|
||||
};
|
||||
|
||||
export async function authAnonymous(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/auth`, async route => {
|
||||
await route.fulfill({ json: { id: null } });
|
||||
await route.fulfill({ json: dataAnonymousAuth });
|
||||
});
|
||||
}
|
||||
|
||||
export async function authAdmin(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/auth`, async route => {
|
||||
await route.fulfill({ json: dataAdminAuth });
|
||||
});
|
||||
}
|
||||
|
||||
export async function authUser(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/auth`, async route => {
|
||||
await route.fulfill({ json: dataUserAuth });
|
||||
});
|
||||
}
|
||||
|
|
72
rsconcept/frontend/tests/mocks/users.ts
Normal file
72
rsconcept/frontend/tests/mocks/users.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
type IUpdateProfileDTO,
|
||||
type IUserInfo,
|
||||
type IUserProfile,
|
||||
type IUserSignupDTO
|
||||
} from '../../src/features/users/backend/types';
|
||||
import { BACKEND_URL } from '../constants';
|
||||
|
||||
const dataActiveUsers: IUserInfo[] = [
|
||||
{
|
||||
id: 1,
|
||||
first_name: 'Admin',
|
||||
last_name: 'User'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
first_name: 'User',
|
||||
last_name: 'User'
|
||||
}
|
||||
];
|
||||
|
||||
let dataUserProfile: IUserProfile = {
|
||||
id: 1,
|
||||
username: 'user',
|
||||
email: 'user@example.com',
|
||||
first_name: 'User',
|
||||
last_name: 'User'
|
||||
};
|
||||
|
||||
export async function setupUsers(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/active-users`, async route => {
|
||||
await route.fulfill({ json: dataActiveUsers });
|
||||
});
|
||||
}
|
||||
|
||||
export async function setupUserProfile(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/profile`, async route => {
|
||||
await route.fulfill({ json: dataUserProfile });
|
||||
});
|
||||
}
|
||||
|
||||
export async function setupUserSignup(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/signup`, async route => {
|
||||
const data = route.request().postDataJSON() as IUserSignupDTO;
|
||||
const newID = dataActiveUsers.length + 1;
|
||||
dataActiveUsers.push({
|
||||
id: newID,
|
||||
first_name: data.first_name,
|
||||
last_name: data.last_name
|
||||
});
|
||||
dataUserProfile = {
|
||||
id: newID,
|
||||
username: data.username,
|
||||
email: data.email,
|
||||
first_name: data.first_name,
|
||||
last_name: data.last_name
|
||||
};
|
||||
await route.fulfill({ json: dataUserProfile });
|
||||
});
|
||||
}
|
||||
|
||||
export async function setupUserProfileUpdate(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/profile`, async route => {
|
||||
dataUserProfile = {
|
||||
...dataUserProfile,
|
||||
...(route.request().postDataJSON() as IUpdateProfileDTO)
|
||||
};
|
||||
await route.fulfill({ json: dataUserProfile });
|
||||
});
|
||||
}
|
|
@ -28,5 +28,5 @@
|
|||
},
|
||||
"types": ["vite/client"],
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
"references": [{ "path": "./tsconfig.vite.json" }]
|
||||
}
|
||||
|
|
21
rsconcept/frontend/tsconfig.playwright.json
Normal file
21
rsconcept/frontend/tsconfig.playwright.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "dom"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"isolatedModules": false,
|
||||
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"types": ["playwright"]
|
||||
},
|
||||
"include": ["playwright.config.ts", "tests/**/*.ts"]
|
||||
}
|
|
@ -11,5 +11,5 @@
|
|||
"@/*": ["*"]
|
||||
}
|
||||
},
|
||||
"include": ["vite.config.ts", "package.json", "playwright.config.ts", "tests", "src"]
|
||||
"include": ["vite.config.ts", "package.json"]
|
||||
}
|
|
@ -21,6 +21,7 @@ function LintBackend() {
|
|||
function LintFrontend() {
|
||||
Set-Location $frontend
|
||||
& npm run lint
|
||||
& npm run lint:playwright
|
||||
}
|
||||
|
||||
RunLinters
|
Loading…
Reference in New Issue
Block a user