mirror of
https://github.com/Xevion/banner.git
synced 2025-12-06 21:14:26 -06:00
feat: add ESLint configuration and testing infrastructure
Add comprehensive ESLint setup with React and TypeScript support, create basic integration tests for the shutdown utilities, and enhance the Justfile with a new check command that runs all validation steps (cargo check, clippy, tests, and linting).
This commit is contained in:
9
Justfile
9
Justfile
@@ -1,5 +1,14 @@
|
|||||||
default_services := "bot,web,scraper"
|
default_services := "bot,web,scraper"
|
||||||
|
|
||||||
|
default:
|
||||||
|
just --list
|
||||||
|
|
||||||
|
check:
|
||||||
|
cargo check
|
||||||
|
cargo clippy
|
||||||
|
cargo nextest run
|
||||||
|
pnpm run -C web lint
|
||||||
|
|
||||||
# Auto-reloading frontend server
|
# Auto-reloading frontend server
|
||||||
frontend:
|
frontend:
|
||||||
pnpm run -C web dev
|
pnpm run -C web dev
|
||||||
|
|||||||
33
tests/basic_test.rs
Normal file
33
tests/basic_test.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
use banner::utils::shutdown::join_tasks;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_join_tasks_success() {
|
||||||
|
// Create some tasks that complete successfully
|
||||||
|
let handles: Vec<JoinHandle<()>> = vec![
|
||||||
|
tokio::spawn(async { tokio::time::sleep(tokio::time::Duration::from_millis(10)).await }),
|
||||||
|
tokio::spawn(async { tokio::time::sleep(tokio::time::Duration::from_millis(20)).await }),
|
||||||
|
tokio::spawn(async { /* immediate completion */ }),
|
||||||
|
];
|
||||||
|
|
||||||
|
// All tasks should complete successfully
|
||||||
|
let result = join_tasks(handles).await;
|
||||||
|
assert!(result.is_ok(), "Expected all tasks to complete successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_join_tasks_with_panic() {
|
||||||
|
// Create some tasks, including one that panics
|
||||||
|
let handles: Vec<JoinHandle<()>> = vec![
|
||||||
|
tokio::spawn(async { tokio::time::sleep(tokio::time::Duration::from_millis(10)).await }),
|
||||||
|
tokio::spawn(async { panic!("intentional test panic") }),
|
||||||
|
tokio::spawn(async { /* immediate completion */ }),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Should return an error because one task panicked
|
||||||
|
let result = join_tasks(handles).await;
|
||||||
|
assert!(result.is_err(), "Expected an error when a task panics");
|
||||||
|
|
||||||
|
let error_msg = result.unwrap_err().to_string();
|
||||||
|
assert!(error_msg.contains("1 task(s) panicked"), "Error message should mention panicked tasks");
|
||||||
|
}
|
||||||
63
web/eslint.config.js
Normal file
63
web/eslint.config.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import react from 'eslint-plugin-react';
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
// Ignore generated files and build outputs
|
||||||
|
{
|
||||||
|
ignores: ['dist', 'node_modules', 'src/routeTree.gen.ts', '*.config.js'],
|
||||||
|
},
|
||||||
|
// Base configs
|
||||||
|
js.configs.recommended,
|
||||||
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
|
// React plugin configuration
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
plugins: {
|
||||||
|
react,
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: true,
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: '19.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// React rules
|
||||||
|
...react.configs.recommended.rules,
|
||||||
|
...react.configs['jsx-runtime'].rules,
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
|
||||||
|
// React Refresh
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
|
||||||
|
// TypeScript overrides
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
|
||||||
|
// Disable prop-types since we're using TypeScript
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
"start": "vite --port 3000",
|
"start": "vite --port 3000",
|
||||||
"build": "vite build && tsc",
|
"build": "vite build && tsc",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"test": "vitest run"
|
"test": "vitest run",
|
||||||
|
"lint": "tsc && eslint . --ext .ts,.tsx"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/themes": "^3.2.1",
|
"@radix-ui/themes": "^3.2.1",
|
||||||
@@ -23,14 +24,20 @@
|
|||||||
"recharts": "^3.2.0"
|
"recharts": "^3.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.0",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/react": "^16.2.0",
|
||||||
"@types/node": "^24.3.3",
|
"@types/node": "^24.3.3",
|
||||||
"@types/react": "^19.0.8",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^19.0.3",
|
"@types/react-dom": "^19.0.3",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"eslint": "^9.39.0",
|
||||||
|
"eslint-plugin-react": "^7.37.5",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
|
"typescript-eslint": "^8.46.2",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.5",
|
||||||
"vitest": "^3.0.5",
|
"vitest": "^3.0.5",
|
||||||
"web-vitals": "^4.2.4"
|
"web-vitals": "^4.2.4"
|
||||||
|
|||||||
2129
web/pnpm-lock.yaml
generated
2129
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,7 @@ export class BannerApiClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return (await response.json()) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getHealth(): Promise<HealthResponse> {
|
async getHealth(): Promise<HealthResponse> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const reportWebVitals = (onPerfEntry?: () => void) => {
|
const reportWebVitals = (onPerfEntry?: () => void) => {
|
||||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||||
import('web-vitals').then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => {
|
void import('web-vitals').then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => {
|
||||||
onCLS(onPerfEntry)
|
onCLS(onPerfEntry)
|
||||||
onINP(onPerfEntry)
|
onINP(onPerfEntry)
|
||||||
onFCP(onPerfEntry)
|
onFCP(onPerfEntry)
|
||||||
|
|||||||
@@ -237,11 +237,11 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Schedule the next request after the current one completes
|
// Schedule the next request after the current one completes
|
||||||
timeoutId = setTimeout(fetchData, REFRESH_INTERVAL);
|
timeoutId = setTimeout(() => void fetchData(), REFRESH_INTERVAL);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start the first request immediately
|
// Start the first request immediately
|
||||||
fetchData();
|
void fetchData();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (timeoutId) {
|
if (timeoutId) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
|
|||||||
Reference in New Issue
Block a user