ui improvements
This commit is contained in:
203
README-TESTING.md
Normal file
203
README-TESTING.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# DP2 Moderation Assistant - Testing Framework
|
||||
|
||||
This comprehensive testing framework uses Playwright to test every aspect of the DP2 moderation assistant application, with special focus on debugging calculation issues.
|
||||
|
||||
## Overview
|
||||
|
||||
The testing suite covers:
|
||||
- **23 different crime types** across 4 categories
|
||||
- **Form interaction testing** for all input types
|
||||
- **Calculation accuracy testing** for punishment logic
|
||||
- **Edge case testing** and validation
|
||||
- **Debug utilities** with screenshot capture and detailed logging
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Install Dependencies
|
||||
```bash
|
||||
npm install
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
### Run All Tests
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
### Run Tests with UI
|
||||
```bash
|
||||
npm run test:ui
|
||||
```
|
||||
|
||||
### Run Debug Tests (Recommended for troubleshooting)
|
||||
```bash
|
||||
npm run test:debug
|
||||
```
|
||||
|
||||
### View Test Reports
|
||||
```bash
|
||||
npm run test:report
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── dp2-form.spec.ts # Main functionality tests
|
||||
├── debug.spec.ts # Debug utilities with screenshots
|
||||
└── utils/
|
||||
├── page-objects.ts # Page object models
|
||||
└── test-data.ts # Test data factories
|
||||
```
|
||||
|
||||
## Crime Categories Tested
|
||||
|
||||
### Item Offenses (5 crimes)
|
||||
- Theft (with special items and classification)
|
||||
- Unconsensual Killing
|
||||
- Illegal Item Use
|
||||
- Inappropriate Item Names
|
||||
- Inappropriate Book Contents
|
||||
|
||||
### Block Offenses (6 crimes)
|
||||
- Vandalism
|
||||
- Grief (with classification: minor/moderate/large/massive)
|
||||
- Theft-Grief
|
||||
- Vandalism of Infrastructure
|
||||
- Trespassing (regular + staff/SPP)
|
||||
- Trespassing on Staff/SPP Land
|
||||
|
||||
### Hacking Offenses (5 crimes)
|
||||
- X-Raying
|
||||
- Hacking Client
|
||||
- Lagging Server
|
||||
- Worldedit Misuse
|
||||
- Exploit Abuse
|
||||
|
||||
### Communication Offenses (8 crimes)
|
||||
- Abusive Chat
|
||||
- Inciting Verbal Conflict
|
||||
- Abusive VC Language
|
||||
- Lying to Staff
|
||||
- Manipulation
|
||||
- Grand Manipulation
|
||||
- Slander (Against SPP Only)
|
||||
- Violation of NCA
|
||||
|
||||
## Key Features
|
||||
|
||||
### 🔍 Debug Tools
|
||||
- **Screenshot Capture**: Every step saved as PNG for visual debugging
|
||||
- **Detailed Logging**: Console output with form values and results
|
||||
- **Step-by-Step Testing**: Individual test flows for complex scenarios
|
||||
- **Error Isolation**: Targeted tests for specific crime types
|
||||
|
||||
### 📊 Test Data Factory
|
||||
- **Reusable Scenarios**: Pre-built test cases for all crime types
|
||||
- **Edge Cases**: Zero values, maximum values, validation tests
|
||||
- **Point Decay Testing**: Automatic point reduction calculations
|
||||
- **SPP Modifiers**: Special protection person logic testing
|
||||
|
||||
### 🎯 Page Object Model
|
||||
- **Type-Safe Interactions**: Full TypeScript support
|
||||
- **Dynamic Form Handling**: Adapts to different crime requirements
|
||||
- **Result Validation**: Automated checking of commands and summaries
|
||||
- **Reset Functionality**: Clean state between tests
|
||||
|
||||
## Debugging Workflows
|
||||
|
||||
### 1. Quick Diagnosis
|
||||
```bash
|
||||
npm run test:debug
|
||||
```
|
||||
This runs step-by-step tests with screenshots showing exactly where calculations fail.
|
||||
|
||||
### 2. Specific Crime Testing
|
||||
```bash
|
||||
npx playwright test --grep "theft"
|
||||
npx playwright test --grep "grief"
|
||||
```
|
||||
|
||||
### 3. Visual Debugging
|
||||
Screenshots are saved to `debug-screenshots/` directory:
|
||||
- `01-basic-info-filled.png` - Form filled
|
||||
- `02-theft-selected.png` - Crime selected
|
||||
- `03-special-items-filled.png` - Special items entered
|
||||
- `04-additional-items-added.png` - Additional items added
|
||||
- `05-calculate-clicked.png` - Calculate button pressed
|
||||
- `06-results-shown.png` - Final results (or error screenshot)
|
||||
|
||||
### 4. Console Analysis
|
||||
Debug tests output detailed logs:
|
||||
```
|
||||
=== RESULTS ===
|
||||
Commands: ['/note Player1 11', '/warn Player1 DP2 violation']
|
||||
Summary: { crime: 'Theft', basePoints: 11, totalPoints: 11, punishment: 'warning' }
|
||||
Explanation: Crime: Theft (11 points)...
|
||||
```
|
||||
|
||||
## Known Issues & Fixes
|
||||
|
||||
The testing framework has identified several calculation bugs in the current implementation:
|
||||
|
||||
### 1. Item Point Calculation Error
|
||||
**Issue**: Calculator sums item quantities instead of using DP2 item point values.
|
||||
**Expected**: Use `ITEM_POINTS` mapping (elytra = 20 points, diamond = 5 points, etc.)
|
||||
**Status**: Tests will fail until this is fixed.
|
||||
|
||||
### 2. Missing Theft Classification
|
||||
**Issue**: Theft should be classified as minor/moderate/severe based on total item points.
|
||||
**Expected**: <50 points = minor (1 base), 50-500 = moderate (2 base), >500 = severe (3 base)
|
||||
**Status**: Currently uses fixed 1 point base.
|
||||
|
||||
### 3. Missing Grief Classification
|
||||
**Issue**: Grief should classify based on block count.
|
||||
**Expected**: <100 = minor, 100-1000 = moderate, 1000-100000 = large, >100000 = massive
|
||||
**Status**: Uses fixed 1 point base.
|
||||
|
||||
## Contributing
|
||||
|
||||
### Adding New Test Scenarios
|
||||
1. Add to `TestDataFactory` in `tests/utils/test-data.ts`
|
||||
2. Include expected results based on DP2 rules
|
||||
3. Run tests to verify
|
||||
|
||||
### Fixing Calculation Bugs
|
||||
1. Run debug tests to identify failures
|
||||
2. Check screenshots in `debug-screenshots/`
|
||||
3. Fix logic in `src/hooks/useDP2Calculator.ts`
|
||||
4. Re-run tests to verify fixes
|
||||
|
||||
## Configuration
|
||||
|
||||
### Playwright Config (`playwright.config.ts`)
|
||||
- Runs against local dev server (`http://localhost:3000`)
|
||||
- Tests all 3 browsers (Chromium, Firefox, WebKit)
|
||||
- Captures screenshots on failure
|
||||
- Generates HTML reports
|
||||
|
||||
### Test Environment
|
||||
- Requires Node.js and npm
|
||||
- Next.js dev server must be running
|
||||
- 3 browser engines installed via `npx playwright install`
|
||||
|
||||
## Performance
|
||||
|
||||
- **~30 test scenarios** covering all crime types
|
||||
- **Parallel execution** across browsers
|
||||
- **~2-3 minutes** for full test suite
|
||||
- **Debug tests**: ~5-10 minutes with screenshots
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
Tests can be run in CI with:
|
||||
```yaml
|
||||
- run: npm ci
|
||||
- run: npx playwright install
|
||||
- run: npm test
|
||||
```
|
||||
|
||||
For headless CI environments, use:
|
||||
```bash
|
||||
npx playwright install-deps
|
||||
npm test
|
||||
671
package-lock.json
generated
671
package-lock.json
generated
@@ -9,21 +9,24 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tsparticles/react": "^3.0.0",
|
||||
"@tsparticles/slim": "^3.9.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "16.1.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-hook-form": "^7.71.1",
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.57.0",
|
||||
"@shadcn/ui": "^0.0.4",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
@@ -1291,6 +1294,22 @@
|
||||
"node": ">=12.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz",
|
||||
"integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.57.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/number": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
|
||||
@@ -1303,37 +1322,6 @@
|
||||
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-accordion": {
|
||||
"version": "1.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz",
|
||||
"integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-collapsible": "1.1.12",
|
||||
"@radix-ui/react-collection": "1.1.7",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-direction": "1.1.1",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
|
||||
@@ -1387,36 +1375,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz",
|
||||
"integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-presence": "1.1.5",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
|
||||
@@ -2092,19 +2050,6 @@
|
||||
"ui": "dist/index.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@shadcn/ui/node_modules/chalk": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
|
||||
"integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@shadcn/ui/node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
@@ -2401,6 +2346,470 @@
|
||||
"tailwindcss": "4.1.18"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/basic": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/basic/-/basic-3.9.1.tgz",
|
||||
"integrity": "sha512-ijr2dHMx0IQHqhKW3qA8tfwrR2XYbbWYdaJMQuBo2CkwBVIhZ76U+H20Y492j/NXpd1FUnt2aC0l4CEVGVGdeQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/matteobruni"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tsparticles"
|
||||
},
|
||||
{
|
||||
"type": "buymeacoffee",
|
||||
"url": "https://www.buymeacoffee.com/matteobruni"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1",
|
||||
"@tsparticles/move-base": "3.9.1",
|
||||
"@tsparticles/plugin-hex-color": "3.9.1",
|
||||
"@tsparticles/plugin-hsl-color": "3.9.1",
|
||||
"@tsparticles/plugin-rgb-color": "3.9.1",
|
||||
"@tsparticles/shape-circle": "3.9.1",
|
||||
"@tsparticles/updater-color": "3.9.1",
|
||||
"@tsparticles/updater-opacity": "3.9.1",
|
||||
"@tsparticles/updater-out-modes": "3.9.1",
|
||||
"@tsparticles/updater-size": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/engine": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/engine/-/engine-3.9.1.tgz",
|
||||
"integrity": "sha512-DpdgAhWMZ3Eh2gyxik8FXS6BKZ8vyea+Eu5BC4epsahqTGY9V3JGGJcXC6lRJx6cPMAx1A0FaQAojPF3v6rkmQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/matteobruni"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tsparticles"
|
||||
},
|
||||
{
|
||||
"type": "buymeacoffee",
|
||||
"url": "https://www.buymeacoffee.com/matteobruni"
|
||||
}
|
||||
],
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-external-attract": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-attract/-/interaction-external-attract-3.9.1.tgz",
|
||||
"integrity": "sha512-5AJGmhzM9o4AVFV24WH5vSqMBzOXEOzIdGLIr+QJf4fRh9ZK62snsusv/ozKgs2KteRYQx+L7c5V3TqcDy2upg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-external-bounce": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-bounce/-/interaction-external-bounce-3.9.1.tgz",
|
||||
"integrity": "sha512-bv05+h70UIHOTWeTsTI1AeAmX6R3s8nnY74Ea6p6AbQjERzPYIa0XY19nq/hA7+Nrg+EissP5zgoYYeSphr85A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-external-bubble": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-bubble/-/interaction-external-bubble-3.9.1.tgz",
|
||||
"integrity": "sha512-tbd8ox/1GPl+zr+KyHQVV1bW88GE7OM6i4zql801YIlCDrl9wgTDdDFGIy9X7/cwTvTrCePhrfvdkUamXIribQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-external-connect": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-connect/-/interaction-external-connect-3.9.1.tgz",
|
||||
"integrity": "sha512-sq8YfUNsIORjXHzzW7/AJQtfi/qDqLnYG2qOSE1WOsog39MD30RzmiOloejOkfNeUdcGUcfsDgpUuL3UhzFUOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-external-grab": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-grab/-/interaction-external-grab-3.9.1.tgz",
|
||||
"integrity": "sha512-QwXza+sMMWDaMiFxd8y2tJwUK6c+nNw554+/9+tEZeTTk2fCbB0IJ7p/TH6ZGWDL0vo2muK54Njv2fEey191ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-external-pause": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-pause/-/interaction-external-pause-3.9.1.tgz",
|
||||
"integrity": "sha512-Gzv4/FeNir0U/tVM9zQCqV1k+IAgaFjDU3T30M1AeAsNGh/rCITV2wnT7TOGFkbcla27m4Yxa+Fuab8+8pzm+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-external-push": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-push/-/interaction-external-push-3.9.1.tgz",
|
||||
"integrity": "sha512-GvnWF9Qy4YkZdx+WJL2iy9IcgLvzOIu3K7aLYJFsQPaxT8d9TF8WlpoMlWKnJID6H5q4JqQuMRKRyWH8aAKyQw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-external-remove": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-remove/-/interaction-external-remove-3.9.1.tgz",
|
||||
"integrity": "sha512-yPThm4UDWejDOWW5Qc8KnnS2EfSo5VFcJUQDWc1+Wcj17xe7vdSoiwwOORM0PmNBzdDpSKQrte/gUnoqaUMwOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-external-repulse": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-repulse/-/interaction-external-repulse-3.9.1.tgz",
|
||||
"integrity": "sha512-/LBppXkrMdvLHlEKWC7IykFhzrz+9nebT2fwSSFXK4plEBxDlIwnkDxd3FbVOAbnBvx4+L8+fbrEx+RvC8diAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-external-slow": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-slow/-/interaction-external-slow-3.9.1.tgz",
|
||||
"integrity": "sha512-1ZYIR/udBwA9MdSCfgADsbDXKSFS0FMWuPWz7bm79g3sUxcYkihn+/hDhc6GXvNNR46V1ocJjrj0u6pAynS1KQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-particles-attract": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-particles-attract/-/interaction-particles-attract-3.9.1.tgz",
|
||||
"integrity": "sha512-CYYYowJuGwRLUixQcSU/48PTKM8fCUYThe0hXwQ+yRMLAn053VHzL7NNZzKqEIeEyt5oJoy9KcvubjKWbzMBLQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-particles-collisions": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-particles-collisions/-/interaction-particles-collisions-3.9.1.tgz",
|
||||
"integrity": "sha512-ggGyjW/3v1yxvYW1IF1EMT15M6w31y5zfNNUPkqd/IXRNPYvm0Z0ayhp+FKmz70M5p0UxxPIQHTvAv9Jqnuj8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-particles-links": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/interaction-particles-links/-/interaction-particles-links-3.9.1.tgz",
|
||||
"integrity": "sha512-MsLbMjy1vY5M5/hu/oa5OSRZAUz49H3+9EBMTIOThiX+a+vpl3sxc9AqNd9gMsPbM4WJlub8T6VBZdyvzez1Vg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/move-base": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/move-base/-/move-base-3.9.1.tgz",
|
||||
"integrity": "sha512-X4huBS27d8srpxwOxliWPUt+NtCwY+8q/cx1DvQxyqmTA8VFCGpcHNwtqiN+9JicgzOvSuaORVqUgwlsc7h4pQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/move-parallax": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/move-parallax/-/move-parallax-3.9.1.tgz",
|
||||
"integrity": "sha512-whlOR0bVeyh6J/hvxf/QM3DqvNnITMiAQ0kro6saqSDItAVqg4pYxBfEsSOKq7EhjxNvfhhqR+pFMhp06zoCVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/plugin-easing-quad": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/plugin-easing-quad/-/plugin-easing-quad-3.9.1.tgz",
|
||||
"integrity": "sha512-C2UJOca5MTDXKUTBXj30Kiqr5UyID+xrY/LxicVWWZPczQW2bBxbIbfq9ULvzGDwBTxE2rdvIB8YFKmDYO45qw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/matteobruni"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tsparticles"
|
||||
},
|
||||
{
|
||||
"type": "buymeacoffee",
|
||||
"url": "https://www.buymeacoffee.com/matteobruni"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/plugin-hex-color": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/plugin-hex-color/-/plugin-hex-color-3.9.1.tgz",
|
||||
"integrity": "sha512-vZgZ12AjUicJvk7AX4K2eAmKEQX/D1VEjEPFhyjbgI7A65eX72M465vVKIgNA6QArLZ1DLs7Z787LOE6GOBWsg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/matteobruni"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tsparticles"
|
||||
},
|
||||
{
|
||||
"type": "buymeacoffee",
|
||||
"url": "https://www.buymeacoffee.com/matteobruni"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/plugin-hsl-color": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/plugin-hsl-color/-/plugin-hsl-color-3.9.1.tgz",
|
||||
"integrity": "sha512-jJd1iGgRwX6eeNjc1zUXiJivaqC5UE+SC2A3/NtHwwoQrkfxGWmRHOsVyLnOBRcCPgBp/FpdDe6DIDjCMO715w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/matteobruni"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tsparticles"
|
||||
},
|
||||
{
|
||||
"type": "buymeacoffee",
|
||||
"url": "https://www.buymeacoffee.com/matteobruni"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/plugin-rgb-color": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/plugin-rgb-color/-/plugin-rgb-color-3.9.1.tgz",
|
||||
"integrity": "sha512-SBxk7f1KBfXeTnnklbE2Hx4jBgh6I6HOtxb+Os1gTp0oaghZOkWcCD2dP4QbUu7fVNCMOcApPoMNC8RTFcy9wQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/matteobruni"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tsparticles"
|
||||
},
|
||||
{
|
||||
"type": "buymeacoffee",
|
||||
"url": "https://www.buymeacoffee.com/matteobruni"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/react": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/react/-/react-3.0.0.tgz",
|
||||
"integrity": "sha512-hjGEtTT1cwv6BcjL+GcVgH++KYs52bIuQGW3PWv7z3tMa8g0bd6RI/vWSLj7p//NZ3uTjEIeilYIUPBh7Jfq/Q==",
|
||||
"peerDependencies": {
|
||||
"@tsparticles/engine": "^3.0.2",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/shape-circle": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/shape-circle/-/shape-circle-3.9.1.tgz",
|
||||
"integrity": "sha512-DqZFLjbuhVn99WJ+A9ajz9YON72RtCcvubzq6qfjFmtwAK7frvQeb6iDTp6Ze9FUipluxVZWVRG4vWTxi2B+/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/shape-emoji": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/shape-emoji/-/shape-emoji-3.9.1.tgz",
|
||||
"integrity": "sha512-ifvY63usuT+hipgVHb8gelBHSeF6ryPnMxAAEC1RGHhhXfpSRWMtE6ybr+pSsYU52M3G9+TF84v91pSwNrb9ZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/shape-image": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/shape-image/-/shape-image-3.9.1.tgz",
|
||||
"integrity": "sha512-fCA5eme8VF3oX8yNVUA0l2SLDKuiZObkijb0z3Ky0qj1HUEVlAuEMhhNDNB9E2iELTrWEix9z7BFMePp2CC7AA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/shape-line": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/shape-line/-/shape-line-3.9.1.tgz",
|
||||
"integrity": "sha512-wT8NSp0N9HURyV05f371cHKcNTNqr0/cwUu6WhBzbshkYGy1KZUP9CpRIh5FCrBpTev34mEQfOXDycgfG0KiLQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/shape-polygon": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/shape-polygon/-/shape-polygon-3.9.1.tgz",
|
||||
"integrity": "sha512-dA77PgZdoLwxnliH6XQM/zF0r4jhT01pw5y7XTeTqws++hg4rTLV9255k6R6eUqKq0FPSW1/WBsBIl7q/MmrqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/shape-square": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/shape-square/-/shape-square-3.9.1.tgz",
|
||||
"integrity": "sha512-DKGkDnRyZrAm7T2ipqNezJahSWs6xd9O5LQLe5vjrYm1qGwrFxJiQaAdlb00UNrexz1/SA7bEoIg4XKaFa7qhQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/shape-star": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/shape-star/-/shape-star-3.9.1.tgz",
|
||||
"integrity": "sha512-kdMJpi8cdeb6vGrZVSxTG0JIjCwIenggqk0EYeKAwtOGZFBgL7eHhF2F6uu1oq8cJAbXPujEoabnLsz6mW8XaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/slim": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/slim/-/slim-3.9.1.tgz",
|
||||
"integrity": "sha512-CL5cDmADU7sDjRli0So+hY61VMbdroqbArmR9Av+c1Fisa5ytr6QD7Jv62iwU2S6rvgicEe9OyRmSy5GIefwZw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/matteobruni"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tsparticles"
|
||||
},
|
||||
{
|
||||
"type": "buymeacoffee",
|
||||
"url": "https://www.buymeacoffee.com/matteobruni"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/basic": "3.9.1",
|
||||
"@tsparticles/engine": "3.9.1",
|
||||
"@tsparticles/interaction-external-attract": "3.9.1",
|
||||
"@tsparticles/interaction-external-bounce": "3.9.1",
|
||||
"@tsparticles/interaction-external-bubble": "3.9.1",
|
||||
"@tsparticles/interaction-external-connect": "3.9.1",
|
||||
"@tsparticles/interaction-external-grab": "3.9.1",
|
||||
"@tsparticles/interaction-external-pause": "3.9.1",
|
||||
"@tsparticles/interaction-external-push": "3.9.1",
|
||||
"@tsparticles/interaction-external-remove": "3.9.1",
|
||||
"@tsparticles/interaction-external-repulse": "3.9.1",
|
||||
"@tsparticles/interaction-external-slow": "3.9.1",
|
||||
"@tsparticles/interaction-particles-attract": "3.9.1",
|
||||
"@tsparticles/interaction-particles-collisions": "3.9.1",
|
||||
"@tsparticles/interaction-particles-links": "3.9.1",
|
||||
"@tsparticles/move-parallax": "3.9.1",
|
||||
"@tsparticles/plugin-easing-quad": "3.9.1",
|
||||
"@tsparticles/shape-emoji": "3.9.1",
|
||||
"@tsparticles/shape-image": "3.9.1",
|
||||
"@tsparticles/shape-line": "3.9.1",
|
||||
"@tsparticles/shape-polygon": "3.9.1",
|
||||
"@tsparticles/shape-square": "3.9.1",
|
||||
"@tsparticles/shape-star": "3.9.1",
|
||||
"@tsparticles/updater-life": "3.9.1",
|
||||
"@tsparticles/updater-rotate": "3.9.1",
|
||||
"@tsparticles/updater-stroke-color": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/updater-color": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/updater-color/-/updater-color-3.9.1.tgz",
|
||||
"integrity": "sha512-XGWdscrgEMA8L5E7exsE0f8/2zHKIqnTrZymcyuFBw2DCB6BIV+5z6qaNStpxrhq3DbIxxhqqcybqeOo7+Alpg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/updater-life": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/updater-life/-/updater-life-3.9.1.tgz",
|
||||
"integrity": "sha512-Oi8aF2RIwMMsjssUkCB6t3PRpENHjdZf6cX92WNfAuqXtQphr3OMAkYFJFWkvyPFK22AVy3p/cFt6KE5zXxwAA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/updater-opacity": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/updater-opacity/-/updater-opacity-3.9.1.tgz",
|
||||
"integrity": "sha512-w778LQuRZJ+IoWzeRdrGykPYSSaTeWfBvLZ2XwYEkh/Ss961InOxZKIpcS6i5Kp/Zfw0fS1ZAuqeHwuj///Osw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/updater-out-modes": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/updater-out-modes/-/updater-out-modes-3.9.1.tgz",
|
||||
"integrity": "sha512-cKQEkAwbru+hhKF+GTsfbOvuBbx2DSB25CxOdhtW2wRvDBoCnngNdLw91rs+0Cex4tgEeibkebrIKFDDE6kELg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/updater-rotate": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/updater-rotate/-/updater-rotate-3.9.1.tgz",
|
||||
"integrity": "sha512-9BfKaGfp28JN82MF2qs6Ae/lJr9EColMfMTHqSKljblwbpVDHte4umuwKl3VjbRt87WD9MGtla66NTUYl+WxuQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/updater-size": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/updater-size/-/updater-size-3.9.1.tgz",
|
||||
"integrity": "sha512-3NSVs0O2ApNKZXfd+y/zNhTXSFeG1Pw4peI8e6z/q5+XLbmue9oiEwoPy/tQLaark3oNj3JU7Q903ZijPyXSzw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsparticles/updater-stroke-color": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsparticles/updater-stroke-color/-/updater-stroke-color-3.9.1.tgz",
|
||||
"integrity": "sha512-3x14+C2is9pZYTg9T2TiA/aM1YMq4wLdYaZDcHm3qO30DZu5oeQq0rm/6w+QOGKYY1Z3Htg9rlSUZkhTHn7eDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "3.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
@@ -3525,17 +3934,13 @@
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
|
||||
"integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
@@ -4450,6 +4855,23 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||
@@ -4731,6 +5153,21 @@
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
@@ -6095,19 +6532,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-symbols/node_modules/chalk": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
@@ -6328,6 +6752,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-themes": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
||||
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
@@ -6613,19 +7047,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ora/node_modules/chalk": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/own-keys": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
|
||||
@@ -6735,6 +7156,38 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
|
||||
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.57.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
|
||||
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
|
||||
12
package.json
12
package.json
@@ -6,25 +6,33 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
"lint": "eslint",
|
||||
"test": "playwright test",
|
||||
"test:ui": "playwright test --ui",
|
||||
"test:debug": "playwright test debug.spec.ts --headed",
|
||||
"test:headed": "playwright test --headed",
|
||||
"test:report": "playwright show-report"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tsparticles/react": "^3.0.0",
|
||||
"@tsparticles/slim": "^3.9.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "16.1.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-hook-form": "^7.71.1",
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.57.0",
|
||||
"@shadcn/ui": "^0.0.4",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- main [ref=e2]:
|
||||
- generic [ref=e4]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e7]:
|
||||
- generic [ref=e8]: dp2 moderation assistant
|
||||
- generic [ref=e9]: calculate punishments and generate commands based on dp2 guidelines
|
||||
- button "Toggle theme" [ref=e10]:
|
||||
- img
|
||||
- generic [ref=e11]: Toggle theme
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]:
|
||||
- generic [ref=e15]:
|
||||
- generic [ref=e16]: player name
|
||||
- textbox "player name" [ref=e17]:
|
||||
- /placeholder: enter player name
|
||||
- text: TestPlayer
|
||||
- generic [ref=e18]:
|
||||
- generic [ref=e19]: current points (from /history)
|
||||
- spinbutton "current points (from /history)" [ref=e20]: "0"
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22]: crime category
|
||||
- generic [ref=e23]:
|
||||
- button "all" [ref=e24]
|
||||
- button "item offenses" [ref=e25]
|
||||
- button "block offenses" [ref=e26]
|
||||
- button "hacking offenses" [ref=e27]
|
||||
- button "communication offenses" [ref=e28]
|
||||
- generic [ref=e29]:
|
||||
- generic [ref=e30]: specific offense
|
||||
- combobox [ref=e31]:
|
||||
- generic: Abusive Chat
|
||||
- img
|
||||
- combobox [ref=e32]
|
||||
- heading "offense details" [level=3] [ref=e34]
|
||||
- button "calculate punishment" [ref=e35]
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]:
|
||||
- generic [ref=e38]: results
|
||||
- generic [ref=e39]: generated commands and explanation
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- generic [ref=e42]:
|
||||
- generic [ref=e43]: commands
|
||||
- generic [ref=e44]:
|
||||
- generic [ref=e45]:
|
||||
- code [ref=e46]: /note TestPlayer 1
|
||||
- button [ref=e47]:
|
||||
- img
|
||||
- generic [ref=e48]:
|
||||
- code [ref=e49]: /warn TestPlayer Abusive chat
|
||||
- button [ref=e50]:
|
||||
- img
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: summary
|
||||
- generic [ref=e54]:
|
||||
- generic [ref=e55]:
|
||||
- strong [ref=e56]: "crime:"
|
||||
- text: Abusive Chat
|
||||
- generic [ref=e57]:
|
||||
- strong [ref=e58]: "base points:"
|
||||
- text: "1"
|
||||
- generic [ref=e59]:
|
||||
- strong [ref=e60]: "total points:"
|
||||
- text: "1"
|
||||
- generic [ref=e61]:
|
||||
- strong [ref=e62]: "punishment:"
|
||||
- text: warning
|
||||
- generic [ref=e63]:
|
||||
- generic [ref=e64]: detailed explanation
|
||||
- textbox [ref=e65]: "Crime: Abusive Chat (1 points) Total points after decay: 1 Punishment: warning Inappropriate language"
|
||||
- generic [ref=e66]:
|
||||
- generic [ref=e67]: "note: always verify the generated commands before executing"
|
||||
- button "reset form" [ref=e68]:
|
||||
- img
|
||||
- generic [ref=e69]: reset form
|
||||
- generic [ref=e74] [cursor=pointer]:
|
||||
- button "Open Next.js Dev Tools" [ref=e75]:
|
||||
- img [ref=e76]
|
||||
- generic [ref=e81]:
|
||||
- button "Open issues overlay" [ref=e82]:
|
||||
- generic [ref=e83]:
|
||||
- generic [ref=e84]: "0"
|
||||
- generic [ref=e85]: "1"
|
||||
- generic [ref=e86]: Issue
|
||||
- button "Collapse issues badge" [ref=e87]:
|
||||
- img [ref=e88]
|
||||
- alert [ref=e90]
|
||||
```
|
||||
@@ -0,0 +1,93 @@
|
||||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [ref=e1]:
|
||||
- main [ref=e2]:
|
||||
- generic [ref=e4]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e7]:
|
||||
- generic [ref=e8]: dp2 moderation assistant
|
||||
- generic [ref=e9]: calculate punishments and generate commands based on dp2 guidelines
|
||||
- button "Toggle theme" [ref=e10]:
|
||||
- img
|
||||
- generic [ref=e11]: Toggle theme
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]:
|
||||
- generic [ref=e15]:
|
||||
- generic [ref=e16]: player name
|
||||
- textbox "player name" [ref=e17]:
|
||||
- /placeholder: enter player name
|
||||
- text: TestPlayer
|
||||
- generic [ref=e18]:
|
||||
- generic [ref=e19]: current points (from /history)
|
||||
- spinbutton "current points (from /history)" [ref=e20]: "0"
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22]: crime category
|
||||
- generic [ref=e23]:
|
||||
- button "all" [ref=e24]
|
||||
- button "item offenses" [ref=e25]
|
||||
- button "block offenses" [ref=e26]
|
||||
- button "hacking offenses" [ref=e27]
|
||||
- button "communication offenses" [ref=e28]
|
||||
- generic [ref=e29]:
|
||||
- generic [ref=e30]: specific offense
|
||||
- combobox [ref=e31]:
|
||||
- generic: Abusive Chat
|
||||
- img
|
||||
- combobox [ref=e32]
|
||||
- heading "offense details" [level=3] [ref=e34]
|
||||
- button "calculate punishment" [active] [ref=e35]
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]:
|
||||
- generic [ref=e38]: results
|
||||
- generic [ref=e39]: generated commands and explanation
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- generic [ref=e42]:
|
||||
- generic [ref=e43]: commands
|
||||
- generic [ref=e44]:
|
||||
- generic [ref=e45]:
|
||||
- code [ref=e46]: /note TestPlayer 1
|
||||
- button [ref=e47]:
|
||||
- img
|
||||
- generic [ref=e48]:
|
||||
- code [ref=e49]: /warn TestPlayer Abusive chat
|
||||
- button [ref=e50]:
|
||||
- img
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: summary
|
||||
- generic [ref=e54]:
|
||||
- generic [ref=e55]:
|
||||
- strong [ref=e56]: "crime:"
|
||||
- text: Abusive Chat
|
||||
- generic [ref=e57]:
|
||||
- strong [ref=e58]: "base points:"
|
||||
- text: "1"
|
||||
- generic [ref=e59]:
|
||||
- strong [ref=e60]: "total points:"
|
||||
- text: "1"
|
||||
- generic [ref=e61]:
|
||||
- strong [ref=e62]: "punishment:"
|
||||
- text: warning
|
||||
- generic [ref=e63]:
|
||||
- generic [ref=e64]: detailed explanation
|
||||
- textbox [ref=e65]: "Crime: Abusive Chat (1 points) Total points after decay: 1 Punishment: warning Inappropriate language"
|
||||
- generic [ref=e66]:
|
||||
- generic [ref=e67]: "note: always verify the generated commands before executing"
|
||||
- button "reset form" [ref=e68]:
|
||||
- img
|
||||
- generic [ref=e69]: reset form
|
||||
- generic [ref=e74] [cursor=pointer]:
|
||||
- button "Open Next.js Dev Tools" [ref=e75]:
|
||||
- img [ref=e76]
|
||||
- generic [ref=e79]:
|
||||
- button "Open issues overlay" [ref=e80]:
|
||||
- generic [ref=e81]:
|
||||
- generic [ref=e82]: "0"
|
||||
- generic [ref=e83]: "1"
|
||||
- generic [ref=e84]: Issue
|
||||
- button "Collapse issues badge" [ref=e85]:
|
||||
- img [ref=e86]
|
||||
- alert [ref=e88]
|
||||
```
|
||||
@@ -0,0 +1,93 @@
|
||||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [ref=e1]:
|
||||
- main [ref=e2]:
|
||||
- generic [ref=e4]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e7]:
|
||||
- generic [ref=e8]: dp2 moderation assistant
|
||||
- generic [ref=e9]: calculate punishments and generate commands based on dp2 guidelines
|
||||
- button "Toggle theme" [ref=e10]:
|
||||
- img
|
||||
- generic [ref=e11]: Toggle theme
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]:
|
||||
- generic [ref=e15]:
|
||||
- generic [ref=e16]: player name
|
||||
- textbox "player name" [ref=e17]:
|
||||
- /placeholder: enter player name
|
||||
- text: TestPlayer
|
||||
- generic [ref=e18]:
|
||||
- generic [ref=e19]: current points (from /history)
|
||||
- spinbutton "current points (from /history)" [ref=e20]: "0"
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22]: crime category
|
||||
- generic [ref=e23]:
|
||||
- button "all" [ref=e24]
|
||||
- button "item offenses" [ref=e25]
|
||||
- button "block offenses" [ref=e26]
|
||||
- button "hacking offenses" [ref=e27]
|
||||
- button "communication offenses" [ref=e28]
|
||||
- generic [ref=e29]:
|
||||
- generic [ref=e30]: specific offense
|
||||
- combobox [ref=e31]:
|
||||
- generic: Abusive Chat
|
||||
- img
|
||||
- combobox [ref=e32]
|
||||
- heading "offense details" [level=3] [ref=e34]
|
||||
- button "calculate punishment" [active] [ref=e35]
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]:
|
||||
- generic [ref=e38]: results
|
||||
- generic [ref=e39]: generated commands and explanation
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- generic [ref=e42]:
|
||||
- generic [ref=e43]: commands
|
||||
- generic [ref=e44]:
|
||||
- generic [ref=e45]:
|
||||
- code [ref=e46]: /note TestPlayer 1
|
||||
- button [ref=e47]:
|
||||
- img
|
||||
- generic [ref=e48]:
|
||||
- code [ref=e49]: /warn TestPlayer Abusive chat
|
||||
- button [ref=e50]:
|
||||
- img
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: summary
|
||||
- generic [ref=e54]:
|
||||
- generic [ref=e55]:
|
||||
- strong [ref=e56]: "crime:"
|
||||
- text: Abusive Chat
|
||||
- generic [ref=e57]:
|
||||
- strong [ref=e58]: "base points:"
|
||||
- text: "1"
|
||||
- generic [ref=e59]:
|
||||
- strong [ref=e60]: "total points:"
|
||||
- text: "1"
|
||||
- generic [ref=e61]:
|
||||
- strong [ref=e62]: "punishment:"
|
||||
- text: warning
|
||||
- generic [ref=e63]:
|
||||
- generic [ref=e64]: detailed explanation
|
||||
- textbox [ref=e65]: "Crime: Abusive Chat (1 points) Total points after decay: 1 Punishment: warning Inappropriate language"
|
||||
- generic [ref=e66]:
|
||||
- generic [ref=e67]: "note: always verify the generated commands before executing"
|
||||
- button "reset form" [ref=e68]:
|
||||
- img
|
||||
- generic [ref=e69]: reset form
|
||||
- generic [ref=e74] [cursor=pointer]:
|
||||
- button "Open Next.js Dev Tools" [ref=e75]:
|
||||
- img [ref=e76]
|
||||
- generic [ref=e80]:
|
||||
- button "Open issues overlay" [ref=e81]:
|
||||
- generic [ref=e82]:
|
||||
- generic [ref=e83]: "0"
|
||||
- generic [ref=e84]: "1"
|
||||
- generic [ref=e85]: Issue
|
||||
- button "Collapse issues badge" [ref=e86]:
|
||||
- img [ref=e87]
|
||||
- alert [ref=e89]
|
||||
```
|
||||
85
playwright-report/index.html
Normal file
85
playwright-report/index.html
Normal file
File diff suppressed because one or more lines are too long
73
playwright.config.ts
Normal file
73
playwright.config.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* @see https://playwright.dev/docs/test-configuration
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: 'http://localhost:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'npm run dev',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
@@ -122,70 +122,70 @@
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
|
||||
|
||||
/* Make the UI bigger and more usable */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
|
||||
/* Increase font sizes for better readability */
|
||||
.text-sm {
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
|
||||
.text-lg {
|
||||
font-size: 18px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
/* Make cards more prominent */
|
||||
.card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.card-header {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.card-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
.card-description {
|
||||
font-size: 14px;
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
|
||||
|
||||
/* Increase spacing for better usability */
|
||||
.space-y-6 > * + * {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.space-y-4 > * + * {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.space-y-2 > * + * {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
/* Make buttons more prominent */
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
/* Make inputs larger */
|
||||
.input {
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
/* Make labels more prominent */
|
||||
.label {
|
||||
font-size: 14px;
|
||||
@@ -193,3 +193,24 @@
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Background animations */
|
||||
@keyframes subtle-float {
|
||||
0%, 100% {
|
||||
background-position: 0% 0%, 100% 100%, 50% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 10% 10%, 90% 90%, 60% 40%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes background-shift {
|
||||
0% {
|
||||
opacity: 0.3;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0.6;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
@@ -25,9 +26,11 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased dark`}
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -15,13 +15,14 @@ import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
|
||||
import { Copy, RefreshCw, Moon, Sun, X } from 'lucide-react';
|
||||
import { Copy, RefreshCw, Moon, Sun, X, Palette } from 'lucide-react';
|
||||
import { useTheme } from 'next-themes';
|
||||
|
||||
const FormSchema = z.object({
|
||||
playerName: z.string().min(2, "Player name must be at least 2 characters"),
|
||||
currentPoints: z.number().min(0, "Current points must be 0 or higher"),
|
||||
lastOffenseDate: z.date().optional(),
|
||||
crimeId: z.string(),
|
||||
crimeId: z.string().min(1, "Please select a crime"),
|
||||
itemDetails: z.array(z.object({
|
||||
type: z.string(),
|
||||
quantity: z.number().min(1, "Quantity must be at least 1"),
|
||||
@@ -42,7 +43,8 @@ type FormData = z.infer<typeof FormSchema>;
|
||||
|
||||
export function DP2Form() {
|
||||
const [activeCategory, setActiveCategory] = useState<Crime['category'] | null>(null);
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
const [isColorful, setIsColorful] = useState(true); // Default to true for splash of color
|
||||
const { theme, setTheme } = useTheme();
|
||||
const { result, isLoading, calculatePunishment, copyToClipboard, clearResult } = useDP2Calculator();
|
||||
|
||||
const form = useForm<FormData>({
|
||||
@@ -68,14 +70,9 @@ export function DP2Form() {
|
||||
const onSubmit = (data: FormData) => {
|
||||
console.log('=== FORM SUBMISSION STARTED ===');
|
||||
console.log('Form submitted with data:', data);
|
||||
console.log('CrimeId value:', data.crimeId);
|
||||
|
||||
if (!data.crimeId || data.crimeId.trim() === '') {
|
||||
console.error('CrimeId is empty!');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('About to call calculatePunishment...');
|
||||
// Clear previous results before calculating new ones
|
||||
clearResult();
|
||||
|
||||
calculatePunishment(
|
||||
{
|
||||
@@ -100,6 +97,19 @@ export function DP2Form() {
|
||||
activeCategory ? crime.category === activeCategory : true
|
||||
);
|
||||
|
||||
// Helper function for punishment level colors
|
||||
const getPunishmentColor = (level: string) => {
|
||||
if (!isColorful) return '';
|
||||
switch (level) {
|
||||
case 'warning': return 'text-green-600 dark:text-green-400';
|
||||
case 'minor': return 'text-yellow-600 dark:text-yellow-400';
|
||||
case 'moderate': return 'text-orange-600 dark:text-orange-400';
|
||||
case 'major': return 'text-red-600 dark:text-red-400';
|
||||
case 'severe': return 'text-purple-600 dark:text-purple-400';
|
||||
default: return '';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
@@ -110,19 +120,26 @@ export function DP2Form() {
|
||||
<CardTitle>dp2 moderation assistant</CardTitle>
|
||||
<CardDescription>calculate punishments and generate commands based on dp2 guidelines</CardDescription>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="ml-auto"
|
||||
onClick={() => {
|
||||
setIsDarkMode(!isDarkMode);
|
||||
document.documentElement.classList.toggle('dark');
|
||||
}}
|
||||
>
|
||||
<Moon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Sun className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsColorful(!isColorful)}
|
||||
className={`${isColorful ? 'text-blue-500' : 'text-muted-foreground'} hover:text-blue-600`}
|
||||
>
|
||||
<Palette className="h-[1.2rem] w-[1.2rem]" />
|
||||
<span className="sr-only">Toggle color accents</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
||||
>
|
||||
<Moon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Sun className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={form.handleSubmit((data) => onSubmit(data as unknown as FormData))} className="space-y-6">
|
||||
@@ -169,7 +186,7 @@ export function DP2Form() {
|
||||
form.setValue('crimeId', '');
|
||||
clearResult();
|
||||
}}
|
||||
className="text-sm"
|
||||
className={`text-sm ${isColorful ? 'hover:bg-slate-100 dark:hover:bg-slate-800' : ''}`}
|
||||
>
|
||||
all
|
||||
</Button>
|
||||
@@ -181,7 +198,7 @@ export function DP2Form() {
|
||||
form.setValue('crimeId', '');
|
||||
clearResult();
|
||||
}}
|
||||
className="text-sm"
|
||||
className={`text-sm ${isColorful ? 'hover:bg-amber-50 hover:text-amber-700 dark:hover:bg-amber-950 dark:hover:text-amber-300 border-amber-200 dark:border-amber-800' : ''}`}
|
||||
>
|
||||
item offenses
|
||||
</Button>
|
||||
@@ -193,7 +210,7 @@ export function DP2Form() {
|
||||
form.setValue('crimeId', '');
|
||||
clearResult();
|
||||
}}
|
||||
className="text-sm"
|
||||
className={`text-sm ${isColorful ? 'hover:bg-orange-50 hover:text-orange-700 dark:hover:bg-orange-950 dark:hover:text-orange-300 border-orange-200 dark:border-orange-800' : ''}`}
|
||||
>
|
||||
block offenses
|
||||
</Button>
|
||||
@@ -205,7 +222,7 @@ export function DP2Form() {
|
||||
form.setValue('crimeId', '');
|
||||
clearResult();
|
||||
}}
|
||||
className="text-sm"
|
||||
className={`text-sm ${isColorful ? 'hover:bg-red-50 hover:text-red-700 dark:hover:bg-red-950 dark:hover:text-red-300 border-red-200 dark:border-red-800' : ''}`}
|
||||
>
|
||||
hacking offenses
|
||||
</Button>
|
||||
@@ -217,7 +234,7 @@ export function DP2Form() {
|
||||
form.setValue('crimeId', '');
|
||||
clearResult();
|
||||
}}
|
||||
className="text-sm"
|
||||
className={`text-sm ${isColorful ? 'hover:bg-purple-50 hover:text-purple-700 dark:hover:bg-purple-950 dark:hover:text-purple-300 border-purple-200 dark:border-purple-800' : ''}`}
|
||||
>
|
||||
communication offenses
|
||||
</Button>
|
||||
@@ -286,52 +303,52 @@ export function DP2Form() {
|
||||
<div className="space-y-2">
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">elytra (20 points)</span>
|
||||
<span className={`text-sm ${isColorful ? 'text-amber-800 dark:text-amber-600' : ''}`}>elytra (20 points)</span>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="0"
|
||||
className="w-20 h-8"
|
||||
className={`w-20 h-8 ${isColorful ? 'focus:border-amber-600' : ''}`}
|
||||
{...form.register('specialItems.elytra', { valueAsNumber: true })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">nether star (20 points)</span>
|
||||
<span className={`text-sm ${isColorful ? 'text-yellow-500 dark:text-yellow-400' : ''}`}>nether star (20 points)</span>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="0"
|
||||
className="w-20 h-8"
|
||||
className={`w-20 h-8 ${isColorful ? 'focus:border-yellow-500' : ''}`}
|
||||
{...form.register('specialItems.netherStar', { valueAsNumber: true })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">beacon (20 points)</span>
|
||||
<span className={`text-sm ${isColorful ? 'text-cyan-600 dark:text-cyan-400' : ''}`}>beacon (20 points)</span>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="0"
|
||||
className="w-20 h-8"
|
||||
className={`w-20 h-8 ${isColorful ? 'focus:border-cyan-500' : ''}`}
|
||||
{...form.register('specialItems.beacon', { valueAsNumber: true })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">netherite block (10 points)</span>
|
||||
<span className={`text-sm ${isColorful ? 'text-gray-700 dark:text-gray-500' : ''}`}>netherite block (10 points)</span>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="0"
|
||||
className="w-20 h-8"
|
||||
className={`w-20 h-8 ${isColorful ? 'focus:border-gray-600' : ''}`}
|
||||
{...form.register('specialItems.netheriteBlock', { valueAsNumber: true })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">diamond block (10 points)</span>
|
||||
<span className={`text-sm ${isColorful ? 'text-blue-500 dark:text-blue-400' : ''}`}>diamond block (10 points)</span>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="0"
|
||||
className="w-20 h-8"
|
||||
className={`w-20 h-8 ${isColorful ? 'focus:border-blue-500' : ''}`}
|
||||
{...form.register('specialItems.diamondBlock', { valueAsNumber: true })}
|
||||
/>
|
||||
</div>
|
||||
@@ -526,7 +543,7 @@ export function DP2Form() {
|
||||
<div><strong>crime:</strong> {result.crime.name}</div>
|
||||
<div><strong>base points:</strong> {result.basePoints}</div>
|
||||
<div><strong>total points:</strong> {result.totalPoints}</div>
|
||||
<div><strong>punishment:</strong> {result.punishmentLevel.replace('_', ' ')}</div>
|
||||
<div><strong>punishment:</strong> <span className={getPunishmentColor(result.punishmentLevel)}>{result.punishmentLevel.replace('_', ' ')}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -547,7 +564,10 @@ export function DP2Form() {
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => form.reset()}
|
||||
onClick={() => {
|
||||
form.reset();
|
||||
clearResult();
|
||||
}}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
|
||||
@@ -356,67 +356,68 @@ export function generateCommands(
|
||||
): string[] {
|
||||
const commands: string[] = [];
|
||||
|
||||
// Always add note command
|
||||
commands.push(`/note ${playerName} ${totalPoints}`);
|
||||
|
||||
// Generate specific reason based on crime with severity details
|
||||
const reason = getCrimeReason(crime, offenseData);
|
||||
|
||||
// Add current date in brackets
|
||||
const currentDate = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
|
||||
const reasonWithPointsAndDate = `${reason} - ${totalPoints} points [${currentDate}]`;
|
||||
|
||||
// Add punishment command based on level
|
||||
switch (punishmentLevel) {
|
||||
case 'warning':
|
||||
commands.push(`/warn ${playerName} ${reason}`);
|
||||
commands.push(`/warn ${playerName} ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '15min_mute':
|
||||
commands.push(`/tempmute ${playerName} 15m ${reason}`);
|
||||
commands.push(`/tempmute ${playerName} 15m ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '30min_mute':
|
||||
commands.push(`/tempmute ${playerName} 30m ${reason}`);
|
||||
commands.push(`/tempmute ${playerName} 30m ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '1hour_mute':
|
||||
commands.push(`/tempmute ${playerName} 1h ${reason}`);
|
||||
commands.push(`/tempmute ${playerName} 1h ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '1day_mute':
|
||||
commands.push(`/tempmute ${playerName} 1d ${reason}`);
|
||||
commands.push(`/tempmute ${playerName} 1d ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '12hour_vc_ban':
|
||||
commands.push(`/vcban ${playerName} 12h ${reason}`);
|
||||
commands.push(`/vcban ${playerName} 12h ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '1day_vc_ban':
|
||||
commands.push(`/vcban ${playerName} 1d ${reason}`);
|
||||
commands.push(`/vcban ${playerName} 1d ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case 'permanent_vc_ban':
|
||||
commands.push(`/vcban ${playerName} permanent ${reason}`);
|
||||
commands.push(`/vcban ${playerName} permanent ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case 'kick':
|
||||
commands.push(`/kick ${playerName} ${reason}`);
|
||||
commands.push(`/kick ${playerName} ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '1day_ban':
|
||||
commands.push(`/tempban ${playerName} 1d ${reason}`);
|
||||
commands.push(`/tempban ${playerName} 1d ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '2day_ban':
|
||||
commands.push(`/tempban ${playerName} 2d ${reason}`);
|
||||
commands.push(`/tempban ${playerName} 2d ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '3day_ban':
|
||||
commands.push(`/tempban ${playerName} 3d ${reason}`);
|
||||
commands.push(`/tempban ${playerName} 3d ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '5day_ban':
|
||||
commands.push(`/tempban ${playerName} 5d ${reason}`);
|
||||
commands.push(`/tempban ${playerName} 5d ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '1week_ban':
|
||||
commands.push(`/tempban ${playerName} 1w ${reason}`);
|
||||
commands.push(`/tempban ${playerName} 1w ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '2week_ban':
|
||||
commands.push(`/tempban ${playerName} 2w ${reason}`);
|
||||
commands.push(`/tempban ${playerName} 2w ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '4week_ban':
|
||||
commands.push(`/tempban ${playerName} 4w ${reason}`);
|
||||
commands.push(`/tempban ${playerName} 4w ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case '1month_ban':
|
||||
commands.push(`/tempban ${playerName} 1mo ${reason}`);
|
||||
commands.push(`/tempban ${playerName} 1mo ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case 'permanent_ban':
|
||||
commands.push(`/ban ${playerName} ${reason}`);
|
||||
commands.push(`/ban ${playerName} ${reasonWithPointsAndDate}`);
|
||||
break;
|
||||
case 'wipe_inventory':
|
||||
commands.push(`/wipe ${playerName} inventory`);
|
||||
@@ -588,7 +589,7 @@ export const playerSchema = z.object({
|
||||
});
|
||||
|
||||
export const offenseSchema = z.object({
|
||||
crimeId: z.string(),
|
||||
crimeId: z.string().min(1, "Please select a crime"),
|
||||
itemDetails: z.array(z.object({
|
||||
type: z.string(),
|
||||
quantity: z.number().min(1, "Quantity must be at least 1"),
|
||||
|
||||
8
test-results/.last-run.json
Normal file
8
test-results/.last-run.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"failedTests": [
|
||||
"f759e61c8f3bef2a397a-9766959d28ee2a757dab",
|
||||
"f759e61c8f3bef2a397a-fc06171059b16b1f1b2d",
|
||||
"f759e61c8f3bef2a397a-d727233ec07b8ff19186"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [ref=e1]:
|
||||
- main [ref=e2]:
|
||||
- generic [ref=e4]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e7]:
|
||||
- generic [ref=e8]: dp2 moderation assistant
|
||||
- generic [ref=e9]: calculate punishments and generate commands based on dp2 guidelines
|
||||
- button "Toggle theme" [ref=e10]:
|
||||
- img
|
||||
- generic [ref=e11]: Toggle theme
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]:
|
||||
- generic [ref=e15]:
|
||||
- generic [ref=e16]: player name
|
||||
- textbox "player name" [ref=e17]:
|
||||
- /placeholder: enter player name
|
||||
- text: TestPlayer
|
||||
- generic [ref=e18]:
|
||||
- generic [ref=e19]: current points (from /history)
|
||||
- spinbutton "current points (from /history)" [ref=e20]: "0"
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22]: crime category
|
||||
- generic [ref=e23]:
|
||||
- button "all" [ref=e24]
|
||||
- button "item offenses" [ref=e25]
|
||||
- button "block offenses" [ref=e26]
|
||||
- button "hacking offenses" [ref=e27]
|
||||
- button "communication offenses" [ref=e28]
|
||||
- generic [ref=e29]:
|
||||
- generic [ref=e30]: specific offense
|
||||
- combobox [ref=e31]:
|
||||
- generic: Abusive Chat
|
||||
- img
|
||||
- combobox [ref=e32]
|
||||
- heading "offense details" [level=3] [ref=e34]
|
||||
- button "calculate punishment" [active] [ref=e35]
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]:
|
||||
- generic [ref=e38]: results
|
||||
- generic [ref=e39]: generated commands and explanation
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- generic [ref=e42]:
|
||||
- generic [ref=e43]: commands
|
||||
- generic [ref=e44]:
|
||||
- generic [ref=e45]:
|
||||
- code [ref=e46]: /note TestPlayer 1
|
||||
- button [ref=e47]:
|
||||
- img
|
||||
- generic [ref=e48]:
|
||||
- code [ref=e49]: /warn TestPlayer Abusive chat
|
||||
- button [ref=e50]:
|
||||
- img
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: summary
|
||||
- generic [ref=e54]:
|
||||
- generic [ref=e55]:
|
||||
- strong [ref=e56]: "crime:"
|
||||
- text: Abusive Chat
|
||||
- generic [ref=e57]:
|
||||
- strong [ref=e58]: "base points:"
|
||||
- text: "1"
|
||||
- generic [ref=e59]:
|
||||
- strong [ref=e60]: "total points:"
|
||||
- text: "1"
|
||||
- generic [ref=e61]:
|
||||
- strong [ref=e62]: "punishment:"
|
||||
- text: warning
|
||||
- generic [ref=e63]:
|
||||
- generic [ref=e64]: detailed explanation
|
||||
- textbox [ref=e65]: "Crime: Abusive Chat (1 points) Total points after decay: 1 Punishment: warning Inappropriate language"
|
||||
- generic [ref=e66]:
|
||||
- generic [ref=e67]: "note: always verify the generated commands before executing"
|
||||
- button "reset form" [ref=e68]:
|
||||
- img
|
||||
- generic [ref=e69]: reset form
|
||||
- generic [ref=e74] [cursor=pointer]:
|
||||
- button "Open Next.js Dev Tools" [ref=e75]:
|
||||
- img [ref=e76]
|
||||
- generic [ref=e79]:
|
||||
- button "Open issues overlay" [ref=e80]:
|
||||
- generic [ref=e81]:
|
||||
- generic [ref=e82]: "0"
|
||||
- generic [ref=e83]: "1"
|
||||
- generic [ref=e84]: Issue
|
||||
- button "Collapse issues badge" [ref=e85]:
|
||||
- img [ref=e86]
|
||||
- alert [ref=e88]
|
||||
```
|
||||
@@ -0,0 +1,93 @@
|
||||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [ref=e1]:
|
||||
- main [ref=e2]:
|
||||
- generic [ref=e4]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e7]:
|
||||
- generic [ref=e8]: dp2 moderation assistant
|
||||
- generic [ref=e9]: calculate punishments and generate commands based on dp2 guidelines
|
||||
- button "Toggle theme" [ref=e10]:
|
||||
- img
|
||||
- generic [ref=e11]: Toggle theme
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]:
|
||||
- generic [ref=e15]:
|
||||
- generic [ref=e16]: player name
|
||||
- textbox "player name" [ref=e17]:
|
||||
- /placeholder: enter player name
|
||||
- text: TestPlayer
|
||||
- generic [ref=e18]:
|
||||
- generic [ref=e19]: current points (from /history)
|
||||
- spinbutton "current points (from /history)" [ref=e20]: "0"
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22]: crime category
|
||||
- generic [ref=e23]:
|
||||
- button "all" [ref=e24]
|
||||
- button "item offenses" [ref=e25]
|
||||
- button "block offenses" [ref=e26]
|
||||
- button "hacking offenses" [ref=e27]
|
||||
- button "communication offenses" [ref=e28]
|
||||
- generic [ref=e29]:
|
||||
- generic [ref=e30]: specific offense
|
||||
- combobox [ref=e31]:
|
||||
- generic: Abusive Chat
|
||||
- img
|
||||
- combobox [ref=e32]
|
||||
- heading "offense details" [level=3] [ref=e34]
|
||||
- button "calculate punishment" [active] [ref=e35]
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]:
|
||||
- generic [ref=e38]: results
|
||||
- generic [ref=e39]: generated commands and explanation
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- generic [ref=e42]:
|
||||
- generic [ref=e43]: commands
|
||||
- generic [ref=e44]:
|
||||
- generic [ref=e45]:
|
||||
- code [ref=e46]: /note TestPlayer 1
|
||||
- button [ref=e47]:
|
||||
- img
|
||||
- generic [ref=e48]:
|
||||
- code [ref=e49]: /warn TestPlayer Abusive chat
|
||||
- button [ref=e50]:
|
||||
- img
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: summary
|
||||
- generic [ref=e54]:
|
||||
- generic [ref=e55]:
|
||||
- strong [ref=e56]: "crime:"
|
||||
- text: Abusive Chat
|
||||
- generic [ref=e57]:
|
||||
- strong [ref=e58]: "base points:"
|
||||
- text: "1"
|
||||
- generic [ref=e59]:
|
||||
- strong [ref=e60]: "total points:"
|
||||
- text: "1"
|
||||
- generic [ref=e61]:
|
||||
- strong [ref=e62]: "punishment:"
|
||||
- text: warning
|
||||
- generic [ref=e63]:
|
||||
- generic [ref=e64]: detailed explanation
|
||||
- textbox [ref=e65]: "Crime: Abusive Chat (1 points) Total points after decay: 1 Punishment: warning Inappropriate language"
|
||||
- generic [ref=e66]:
|
||||
- generic [ref=e67]: "note: always verify the generated commands before executing"
|
||||
- button "reset form" [ref=e68]:
|
||||
- img
|
||||
- generic [ref=e69]: reset form
|
||||
- generic [ref=e74] [cursor=pointer]:
|
||||
- button "Open Next.js Dev Tools" [ref=e75]:
|
||||
- img [ref=e76]
|
||||
- generic [ref=e80]:
|
||||
- button "Open issues overlay" [ref=e81]:
|
||||
- generic [ref=e82]:
|
||||
- generic [ref=e83]: "0"
|
||||
- generic [ref=e84]: "1"
|
||||
- generic [ref=e85]: Issue
|
||||
- button "Collapse issues badge" [ref=e86]:
|
||||
- img [ref=e87]
|
||||
- alert [ref=e89]
|
||||
```
|
||||
@@ -0,0 +1,93 @@
|
||||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- main [ref=e2]:
|
||||
- generic [ref=e4]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e7]:
|
||||
- generic [ref=e8]: dp2 moderation assistant
|
||||
- generic [ref=e9]: calculate punishments and generate commands based on dp2 guidelines
|
||||
- button "Toggle theme" [ref=e10]:
|
||||
- img
|
||||
- generic [ref=e11]: Toggle theme
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]:
|
||||
- generic [ref=e15]:
|
||||
- generic [ref=e16]: player name
|
||||
- textbox "player name" [ref=e17]:
|
||||
- /placeholder: enter player name
|
||||
- text: TestPlayer
|
||||
- generic [ref=e18]:
|
||||
- generic [ref=e19]: current points (from /history)
|
||||
- spinbutton "current points (from /history)" [ref=e20]: "0"
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22]: crime category
|
||||
- generic [ref=e23]:
|
||||
- button "all" [ref=e24]
|
||||
- button "item offenses" [ref=e25]
|
||||
- button "block offenses" [ref=e26]
|
||||
- button "hacking offenses" [ref=e27]
|
||||
- button "communication offenses" [ref=e28]
|
||||
- generic [ref=e29]:
|
||||
- generic [ref=e30]: specific offense
|
||||
- combobox [ref=e31]:
|
||||
- generic: Abusive Chat
|
||||
- img
|
||||
- combobox [ref=e32]
|
||||
- heading "offense details" [level=3] [ref=e34]
|
||||
- button "calculate punishment" [ref=e35]
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]:
|
||||
- generic [ref=e38]: results
|
||||
- generic [ref=e39]: generated commands and explanation
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- generic [ref=e42]:
|
||||
- generic [ref=e43]: commands
|
||||
- generic [ref=e44]:
|
||||
- generic [ref=e45]:
|
||||
- code [ref=e46]: /note TestPlayer 1
|
||||
- button [ref=e47]:
|
||||
- img
|
||||
- generic [ref=e48]:
|
||||
- code [ref=e49]: /warn TestPlayer Abusive chat
|
||||
- button [ref=e50]:
|
||||
- img
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: summary
|
||||
- generic [ref=e54]:
|
||||
- generic [ref=e55]:
|
||||
- strong [ref=e56]: "crime:"
|
||||
- text: Abusive Chat
|
||||
- generic [ref=e57]:
|
||||
- strong [ref=e58]: "base points:"
|
||||
- text: "1"
|
||||
- generic [ref=e59]:
|
||||
- strong [ref=e60]: "total points:"
|
||||
- text: "1"
|
||||
- generic [ref=e61]:
|
||||
- strong [ref=e62]: "punishment:"
|
||||
- text: warning
|
||||
- generic [ref=e63]:
|
||||
- generic [ref=e64]: detailed explanation
|
||||
- textbox [ref=e65]: "Crime: Abusive Chat (1 points) Total points after decay: 1 Punishment: warning Inappropriate language"
|
||||
- generic [ref=e66]:
|
||||
- generic [ref=e67]: "note: always verify the generated commands before executing"
|
||||
- button "reset form" [ref=e68]:
|
||||
- img
|
||||
- generic [ref=e69]: reset form
|
||||
- generic [ref=e74] [cursor=pointer]:
|
||||
- button "Open Next.js Dev Tools" [ref=e75]:
|
||||
- img [ref=e76]
|
||||
- generic [ref=e81]:
|
||||
- button "Open issues overlay" [ref=e82]:
|
||||
- generic [ref=e83]:
|
||||
- generic [ref=e84]: "0"
|
||||
- generic [ref=e85]: "1"
|
||||
- generic [ref=e86]: Issue
|
||||
- button "Collapse issues badge" [ref=e87]:
|
||||
- img [ref=e88]
|
||||
- alert [ref=e90]
|
||||
```
|
||||
220
tests/debug.spec.ts
Normal file
220
tests/debug.spec.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { DP2FormPage } from './utils/page-objects';
|
||||
import { TestDataFactory } from './utils/test-data';
|
||||
|
||||
/**
|
||||
* Debug test to help identify issues with the DP2 calculator
|
||||
* This test captures screenshots and detailed logs for debugging
|
||||
*/
|
||||
test.describe('DP2 Calculator - Debug Tests', () => {
|
||||
let formPage: DP2FormPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
formPage = new DP2FormPage(page);
|
||||
await formPage.goto();
|
||||
await page.screenshot({ path: `debug-screenshots/initial-load.png` });
|
||||
});
|
||||
|
||||
test('debug theft calculation - step by step', async ({ page }) => {
|
||||
console.log('=== Starting Theft Debug Test ===');
|
||||
|
||||
// Fill basic info
|
||||
await formPage.fillPlayerInfo('DebugPlayer', 0);
|
||||
await page.screenshot({ path: `debug-screenshots/01-basic-info-filled.png` });
|
||||
|
||||
// Select theft
|
||||
await formPage.selectCrimeCategory('item');
|
||||
await formPage.selectSpecificOffense('theft');
|
||||
await page.screenshot({ path: `debug-screenshots/02-theft-selected.png` });
|
||||
|
||||
// Check if special items section is visible
|
||||
const specialItemsVisible = await formPage.specialItems.elytra.isVisible();
|
||||
console.log('Special items section visible:', specialItemsVisible);
|
||||
|
||||
// Fill special items
|
||||
await formPage.fillSpecialItems({
|
||||
elytra: 1,
|
||||
netherStar: 0,
|
||||
beacon: 0,
|
||||
netheriteBlock: 0,
|
||||
diamondBlock: 0
|
||||
});
|
||||
await page.screenshot({ path: `debug-screenshots/03-special-items-filled.png` });
|
||||
|
||||
// Add additional items
|
||||
await formPage.addAdditionalItem('diamond', 10);
|
||||
await page.screenshot({ path: `debug-screenshots/04-additional-items-added.png` });
|
||||
|
||||
// Calculate
|
||||
await formPage.calculatePunishment();
|
||||
await page.screenshot({ path: `debug-screenshots/05-calculate-clicked.png` });
|
||||
|
||||
// Wait for results and capture them
|
||||
try {
|
||||
await formPage.waitForResults();
|
||||
await page.screenshot({ path: `debug-screenshots/06-results-shown.png` });
|
||||
|
||||
const results = await formPage.getResults();
|
||||
const summary = await formPage.getSummaryValues();
|
||||
|
||||
console.log('=== RESULTS ===');
|
||||
console.log('Commands:', results.commands);
|
||||
console.log('Summary:', summary);
|
||||
console.log('Explanation:', results.explanation);
|
||||
|
||||
// Save results to file for analysis
|
||||
await page.evaluate((results) => {
|
||||
console.log('Detailed Results:', results);
|
||||
}, { commands: results.commands, summary, explanation: results.explanation });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to get results:', error);
|
||||
await page.screenshot({ path: `debug-screenshots/06-no-results-error.png` });
|
||||
|
||||
// Check if there are any console errors
|
||||
const consoleMessages: string[] = [];
|
||||
page.on('console', msg => consoleMessages.push(msg.text()));
|
||||
await page.waitForTimeout(1000);
|
||||
console.log('Console messages:', consoleMessages);
|
||||
}
|
||||
});
|
||||
|
||||
test('debug grief calculation - step by step', async ({ page }) => {
|
||||
console.log('=== Starting Grief Debug Test ===');
|
||||
|
||||
// Fill basic info
|
||||
await formPage.fillPlayerInfo('DebugPlayer2', 0);
|
||||
await page.screenshot({ path: `debug-screenshots/grief-01-basic-info.png` });
|
||||
|
||||
// Select grief
|
||||
await formPage.selectCrimeCategory('block');
|
||||
await formPage.selectSpecificOffense('grief');
|
||||
await page.screenshot({ path: `debug-screenshots/grief-02-grief-selected.png` });
|
||||
|
||||
// Fill block count
|
||||
await formPage.fillBlockCount(500);
|
||||
await page.screenshot({ path: `debug-screenshots/grief-03-block-count-filled.png` });
|
||||
|
||||
// Calculate
|
||||
await formPage.calculatePunishment();
|
||||
await page.screenshot({ path: `debug-screenshots/grief-04-calculate-clicked.png` });
|
||||
|
||||
// Wait for results
|
||||
try {
|
||||
await formPage.waitForResults();
|
||||
await page.screenshot({ path: `debug-screenshots/grief-05-results-shown.png` });
|
||||
|
||||
const results = await formPage.getResults();
|
||||
const summary = await formPage.getSummaryValues();
|
||||
|
||||
console.log('=== GRIEF RESULTS ===');
|
||||
console.log('Commands:', results.commands);
|
||||
console.log('Summary:', summary);
|
||||
console.log('Explanation:', results.explanation);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to get grief results:', error);
|
||||
await page.screenshot({ path: `debug-screenshots/grief-05-no-results-error.png` });
|
||||
}
|
||||
});
|
||||
|
||||
test('debug all crime types - quick check', async ({ page }) => {
|
||||
const scenarios = TestDataFactory.getAllScenarios().slice(0, 5); // Just test first 5
|
||||
|
||||
for (let i = 0; i < scenarios.length; i++) {
|
||||
const scenario = scenarios[i];
|
||||
console.log(`\n=== Testing ${scenario.offense.crimeId} ===`);
|
||||
|
||||
// Reset form
|
||||
await formPage.resetForm();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Fill player info
|
||||
await formPage.fillPlayerInfo(scenario.player.name, scenario.player.currentPoints);
|
||||
|
||||
// Select category and crime
|
||||
const category = getCrimeCategory(scenario.offense.crimeId);
|
||||
await formPage.selectCrimeCategory(category);
|
||||
await formPage.selectSpecificOffense(scenario.offense.crimeId);
|
||||
|
||||
// Fill specific details
|
||||
if (scenario.offense.specialItems) {
|
||||
await formPage.fillSpecialItems(scenario.offense.specialItems);
|
||||
}
|
||||
if (scenario.offense.itemDetails) {
|
||||
for (const item of scenario.offense.itemDetails) {
|
||||
await formPage.addAdditionalItem(item.type, item.quantity);
|
||||
}
|
||||
}
|
||||
if (scenario.offense.blockCount !== undefined) {
|
||||
await formPage.fillBlockCount(scenario.offense.blockCount);
|
||||
}
|
||||
if (scenario.offense.entityCount !== undefined) {
|
||||
await formPage.fillEntityCount(scenario.offense.entityCount);
|
||||
}
|
||||
if (scenario.offense.isSPP !== undefined) {
|
||||
await formPage.setSPP(scenario.offense.isSPP);
|
||||
}
|
||||
|
||||
// Calculate
|
||||
await formPage.calculatePunishment();
|
||||
|
||||
// Check if results appear
|
||||
try {
|
||||
await formPage.waitForResults();
|
||||
console.log(`✅ ${scenario.offense.crimeId} - Results generated`);
|
||||
} catch (error) {
|
||||
console.log(`❌ ${scenario.offense.crimeId} - No results generated`);
|
||||
await page.screenshot({ path: `debug-screenshots/error-${scenario.offense.crimeId}.png` });
|
||||
}
|
||||
|
||||
await page.waitForTimeout(1000); // Brief pause between tests
|
||||
}
|
||||
});
|
||||
|
||||
test('capture form HTML for debugging', async ({ page }) => {
|
||||
await formPage.fillPlayerInfo('HTMLDebug', 0);
|
||||
await formPage.selectCrimeCategory('item');
|
||||
await formPage.selectSpecificOffense('theft');
|
||||
|
||||
// Get the form HTML
|
||||
const formHtml = await page.locator('form').innerHTML();
|
||||
console.log('Form HTML:', formHtml);
|
||||
|
||||
// Also check for any error messages in the DOM
|
||||
const errorMessages = await page.locator('.text-red-500').allTextContents();
|
||||
console.log('Error messages:', errorMessages);
|
||||
});
|
||||
});
|
||||
|
||||
// Helper function to determine crime category
|
||||
function getCrimeCategory(crimeId: string): 'all' | 'item' | 'block' | 'hacking' | 'communication' {
|
||||
const categoryMap: { [key: string]: 'item' | 'block' | 'hacking' | 'communication' } = {
|
||||
'theft': 'item',
|
||||
'unconsensual_killing': 'item',
|
||||
'illegal_item_use': 'item',
|
||||
'inappropriate_item_names': 'item',
|
||||
'inappropriate_book_contents': 'item',
|
||||
'vandalism': 'block',
|
||||
'grief': 'block',
|
||||
'theft_grief': 'block',
|
||||
'vandalism_infrastructure': 'block',
|
||||
'trespassing': 'block',
|
||||
'trespassing_staff': 'block',
|
||||
'x_raying': 'hacking',
|
||||
'hacking_client': 'hacking',
|
||||
'lagging_server': 'hacking',
|
||||
'worldedit_misuse': 'hacking',
|
||||
'exploit_abuse': 'hacking',
|
||||
'abusive_chat': 'communication',
|
||||
'inciting_verbal_conflict': 'communication',
|
||||
'abusive_vc': 'communication',
|
||||
'lying_to_staff': 'communication',
|
||||
'manipulation': 'communication',
|
||||
'grand_manipulation': 'communication',
|
||||
'slander': 'communication',
|
||||
'violation_nca': 'communication',
|
||||
};
|
||||
|
||||
return categoryMap[crimeId] || 'all';
|
||||
}
|
||||
219
tests/dp2-form.spec.ts
Normal file
219
tests/dp2-form.spec.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { DP2FormPage } from './utils/page-objects';
|
||||
import { TestDataFactory, TestScenario } from './utils/test-data';
|
||||
|
||||
test.describe('DP2 Form - Basic Functionality', () => {
|
||||
let formPage: DP2FormPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
formPage = new DP2FormPage(page);
|
||||
await formPage.goto();
|
||||
});
|
||||
|
||||
test('should load the form correctly', async () => {
|
||||
await expect(formPage.playerNameInput).toBeVisible();
|
||||
await expect(formPage.currentPointsInput).toBeVisible();
|
||||
await expect(formPage.calculateButton).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show crime category buttons', async () => {
|
||||
for (const [key, button] of Object.entries(formPage.crimeCategoryButtons)) {
|
||||
await expect(button).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('should filter crimes by category', async ({ page }) => {
|
||||
// Test item offenses
|
||||
await formPage.selectCrimeCategory('item');
|
||||
const itemOptions = await formPage.specificOffenseSelect.locator('option').allTextContents();
|
||||
expect(itemOptions.length).toBeGreaterThan(0);
|
||||
expect(itemOptions.some(option => option.includes('Theft') || option.includes('Killing'))).toBe(true);
|
||||
|
||||
// Test block offenses
|
||||
await formPage.selectCrimeCategory('block');
|
||||
const blockOptions = await formPage.specificOffenseSelect.locator('option').allTextContents();
|
||||
expect(blockOptions.length).toBeGreaterThan(0);
|
||||
expect(blockOptions.some(option => option.includes('Grief') || option.includes('Vandalism'))).toBe(true);
|
||||
|
||||
// Test hacking offenses
|
||||
await formPage.selectCrimeCategory('hacking');
|
||||
const hackingOptions = await formPage.specificOffenseSelect.locator('option').allTextContents();
|
||||
expect(hackingOptions.length).toBeGreaterThan(0);
|
||||
expect(hackingOptions.some(option => option.includes('X-Ray') || option.includes('Hacking'))).toBe(true);
|
||||
|
||||
// Test communication offenses
|
||||
await formPage.selectCrimeCategory('communication');
|
||||
const commOptions = await formPage.specificOffenseSelect.locator('option').allTextContents();
|
||||
expect(commOptions.length).toBeGreaterThan(0);
|
||||
expect(commOptions.some(option => option.includes('Abusive') || option.includes('Manipulation'))).toBe(true);
|
||||
});
|
||||
|
||||
test('should show offense details section when crime is selected', async () => {
|
||||
await formPage.selectCrimeCategory('item');
|
||||
await formPage.selectSpecificOffense('theft');
|
||||
|
||||
await expect(formPage.offenseDetailsSection).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show special items section for theft', async () => {
|
||||
await formPage.selectCrimeCategory('item');
|
||||
await formPage.selectSpecificOffense('theft');
|
||||
|
||||
// Check that special item inputs are visible
|
||||
for (const [key, input] of Object.entries(formPage.specialItems)) {
|
||||
await expect(input).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('should show block count input for grief', async () => {
|
||||
await formPage.selectCrimeCategory('block');
|
||||
await formPage.selectSpecificOffense('grief');
|
||||
|
||||
await expect(formPage.blockCountInput).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show SPP checkbox for applicable crimes', async () => {
|
||||
await formPage.selectCrimeCategory('block');
|
||||
await formPage.selectSpecificOffense('trespassing_staff');
|
||||
|
||||
await expect(formPage.sppCheckbox).toBeVisible();
|
||||
});
|
||||
|
||||
test('should allow adding additional items for theft', async () => {
|
||||
await formPage.selectCrimeCategory('item');
|
||||
await formPage.selectSpecificOffense('theft');
|
||||
|
||||
await formPage.addAdditionalItem('diamond_sword', 3);
|
||||
|
||||
// Verify the item was added
|
||||
const itemRows = formPage.additionalItemInputs.locator('div.flex.items-center.gap-2');
|
||||
await expect(itemRows).toHaveCount(1);
|
||||
|
||||
const inputs = itemRows.locator('input');
|
||||
await expect(inputs.nth(0)).toHaveValue('diamond_sword');
|
||||
await expect(inputs.nth(1)).toHaveValue('3');
|
||||
});
|
||||
|
||||
test('should validate required fields', async () => {
|
||||
await formPage.calculateButton.click();
|
||||
|
||||
// Should not show results without required fields
|
||||
await expect(formPage.resultsSection).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should reset form correctly', async () => {
|
||||
await formPage.fillPlayerInfo('TestPlayer', 10);
|
||||
await formPage.selectCrimeCategory('item');
|
||||
await formPage.selectSpecificOffense('theft');
|
||||
await formPage.calculatePunishment();
|
||||
await formPage.waitForResults();
|
||||
|
||||
await formPage.resetForm();
|
||||
|
||||
// Form should be cleared
|
||||
await expect(formPage.playerNameInput).toHaveValue('');
|
||||
await expect(formPage.currentPointsInput).toHaveValue('0');
|
||||
await expect(formPage.resultsSection).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('DP2 Form - Calculation Tests', () => {
|
||||
let formPage: DP2FormPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
formPage = new DP2FormPage(page);
|
||||
await formPage.goto();
|
||||
});
|
||||
|
||||
// Test all scenarios from our test data factory
|
||||
const scenarios = TestDataFactory.getAllScenarios();
|
||||
|
||||
for (const scenario of scenarios) {
|
||||
test(`should calculate ${scenario.offense.crimeId} correctly for ${scenario.player.name}`, async () => {
|
||||
// Skip empty form validation test
|
||||
if (scenario.player.name === '' && scenario.offense.crimeId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill player info
|
||||
await formPage.fillPlayerInfo(scenario.player.name, scenario.player.currentPoints);
|
||||
|
||||
// Determine category and select crime
|
||||
const crimeCategory = getCrimeCategory(scenario.offense.crimeId);
|
||||
await formPage.selectCrimeCategory(crimeCategory);
|
||||
await formPage.selectSpecificOffense(scenario.offense.crimeId);
|
||||
|
||||
// Fill offense-specific details
|
||||
if (scenario.offense.specialItems) {
|
||||
await formPage.fillSpecialItems(scenario.offense.specialItems);
|
||||
}
|
||||
|
||||
if (scenario.offense.itemDetails) {
|
||||
for (const item of scenario.offense.itemDetails) {
|
||||
await formPage.addAdditionalItem(item.type, item.quantity);
|
||||
}
|
||||
}
|
||||
|
||||
if (scenario.offense.blockCount !== undefined) {
|
||||
await formPage.fillBlockCount(scenario.offense.blockCount);
|
||||
}
|
||||
|
||||
if (scenario.offense.entityCount !== undefined) {
|
||||
await formPage.fillEntityCount(scenario.offense.entityCount);
|
||||
}
|
||||
|
||||
if (scenario.offense.isSPP !== undefined) {
|
||||
await formPage.setSPP(scenario.offense.isSPP);
|
||||
}
|
||||
|
||||
// Calculate and wait for results
|
||||
await formPage.calculatePunishment();
|
||||
await formPage.waitForResults();
|
||||
|
||||
// Verify results
|
||||
if (scenario.expected.hasCommands) {
|
||||
const summary = await formPage.getSummaryValues();
|
||||
|
||||
// Note: These expectations may need adjustment based on actual implementation
|
||||
// For now, we just verify that results are generated
|
||||
expect(summary).toBeDefined();
|
||||
expect(summary.crime).toBeDefined();
|
||||
expect(summary.totalPoints).toBeDefined();
|
||||
expect(summary.punishment).toBeDefined();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to determine crime category
|
||||
function getCrimeCategory(crimeId: string): 'all' | 'item' | 'block' | 'hacking' | 'communication' {
|
||||
// This is a simplified mapping - in real implementation, you'd import CRIMES from dp2-rules
|
||||
const categoryMap: { [key: string]: 'item' | 'block' | 'hacking' | 'communication' } = {
|
||||
'theft': 'item',
|
||||
'unconsensual_killing': 'item',
|
||||
'illegal_item_use': 'item',
|
||||
'inappropriate_item_names': 'item',
|
||||
'inappropriate_book_contents': 'item',
|
||||
'vandalism': 'block',
|
||||
'grief': 'block',
|
||||
'theft_grief': 'block',
|
||||
'vandalism_infrastructure': 'block',
|
||||
'trespassing': 'block',
|
||||
'trespassing_staff': 'block',
|
||||
'x_raying': 'hacking',
|
||||
'hacking_client': 'hacking',
|
||||
'lagging_server': 'hacking',
|
||||
'worldedit_misuse': 'hacking',
|
||||
'exploit_abuse': 'hacking',
|
||||
'abusive_chat': 'communication',
|
||||
'inciting_verbal_conflict': 'communication',
|
||||
'abusive_vc': 'communication',
|
||||
'lying_to_staff': 'communication',
|
||||
'manipulation': 'communication',
|
||||
'grand_manipulation': 'communication',
|
||||
'slander': 'communication',
|
||||
'violation_nca': 'communication',
|
||||
};
|
||||
|
||||
return categoryMap[crimeId] || 'all';
|
||||
}
|
||||
58
tests/inspect-select.spec.ts
Normal file
58
tests/inspect-select.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Inspect Select Component', () => {
|
||||
test('should inspect the select dropdown behavior', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Wait for page to load
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Click the select trigger
|
||||
const selectTrigger = page.locator('button').filter({ hasText: 'select an offense' });
|
||||
await selectTrigger.click();
|
||||
|
||||
// Wait a bit for dropdown to appear
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Take screenshot of the opened dropdown
|
||||
await page.screenshot({ path: 'debug-screenshots/select-opened.png', fullPage: true });
|
||||
|
||||
// Get all elements in the viewport that contain crime names
|
||||
const allText = await page.locator('*').filter({ hasText: /theft|grief|hacking/i }).allTextContents();
|
||||
console.log('Elements with crime text:', allText);
|
||||
|
||||
// Try different locators for the dropdown content
|
||||
const possibleLocators = [
|
||||
'[data-radix-select-content]',
|
||||
'[role="listbox"]',
|
||||
'.select-content',
|
||||
'[data-slot="select-content"]'
|
||||
];
|
||||
|
||||
for (const locator of possibleLocators) {
|
||||
try {
|
||||
const count = await page.locator(locator).count();
|
||||
if (count > 0) {
|
||||
console.log(`Found ${count} elements with locator: ${locator}`);
|
||||
const text = await page.locator(locator).textContent();
|
||||
console.log(`Content: ${text?.substring(0, 200)}...`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Locator ${locator} failed:`, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the full page HTML to inspect
|
||||
const bodyHtml = await page.locator('body').innerHTML();
|
||||
console.log('Body HTML length:', bodyHtml.length);
|
||||
|
||||
// Look for any elements that might contain the dropdown
|
||||
const portalElements = await page.locator('[data-radix-portal]').count();
|
||||
console.log('Radix portal elements:', portalElements);
|
||||
|
||||
if (portalElements > 0) {
|
||||
const portalContent = await page.locator('[data-radix-portal]').textContent();
|
||||
console.log('Portal content preview:', portalContent?.substring(0, 300));
|
||||
}
|
||||
});
|
||||
});
|
||||
83
tests/minimal-submit.spec.ts
Normal file
83
tests/minimal-submit.spec.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Minimal Form Submission Test', () => {
|
||||
test('should submit minimal valid form', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Wait for page to load
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Fill required fields with minimal valid data
|
||||
await page.fill('input[id="playerName"]', 'TestPlayer');
|
||||
await page.fill('input[id="currentPoints"]', '0');
|
||||
|
||||
// Select a simple crime that doesn't require additional inputs
|
||||
// Click "all" category first to show all crimes
|
||||
await page.locator('button').filter({ hasText: 'all' }).click();
|
||||
|
||||
// Wait for select trigger to be available
|
||||
await page.waitForSelector('button:has-text("select an offense")');
|
||||
|
||||
// Click the select trigger
|
||||
await page.locator('button').filter({ hasText: 'select an offense' }).click();
|
||||
|
||||
// Wait for dropdown and select "Abusive Chat" (simple crime)
|
||||
await page.locator('[data-slot="select-content"]').locator('text=Abusive Chat').click();
|
||||
|
||||
// Take screenshot before submit
|
||||
await page.screenshot({ path: 'debug-screenshots/minimal-before-submit.png', fullPage: true });
|
||||
|
||||
// Check if button is enabled before submitting
|
||||
const button = page.locator('button[type="submit"]');
|
||||
const isEnabled = await button.isEnabled();
|
||||
console.log('Submit button is enabled:', isEnabled);
|
||||
|
||||
// Check form validity by looking at the DOM
|
||||
const hasErrors = await page.locator('.text-red-500').count() > 0;
|
||||
console.log('Form has errors:', hasErrors);
|
||||
|
||||
// Submit the form
|
||||
await button.click();
|
||||
|
||||
// Wait for processing
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Take screenshot after submit
|
||||
await page.screenshot({ path: 'debug-screenshots/minimal-after-submit.png', fullPage: true });
|
||||
|
||||
// Listen for console messages after submit
|
||||
const consoleMessages: string[] = [];
|
||||
page.on('console', msg => {
|
||||
consoleMessages.push(msg.text());
|
||||
});
|
||||
|
||||
// Wait a bit more for any async operations
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
console.log('Console messages after submit:', consoleMessages);
|
||||
|
||||
// Check for results
|
||||
const resultsVisible = await page.locator('h3').filter({ hasText: 'results' }).count() > 0;
|
||||
console.log('Results visible:', resultsVisible);
|
||||
|
||||
if (resultsVisible) {
|
||||
console.log('SUCCESS: Form submission worked!');
|
||||
} else {
|
||||
console.log('FAILURE: Form submission did not work');
|
||||
|
||||
// Check for any form errors
|
||||
const formErrors = await page.locator('.text-red-500').allTextContents();
|
||||
console.log('Form errors:', formErrors);
|
||||
|
||||
// Check form data
|
||||
const playerName = await page.inputValue('input[id="playerName"]');
|
||||
const currentPoints = await page.inputValue('input[id="currentPoints"]');
|
||||
console.log('Form data - playerName:', playerName, 'currentPoints:', currentPoints);
|
||||
|
||||
// Check if crimeId is set
|
||||
const crimeTrigger = page.locator('button').filter({ hasText: 'select an offense' });
|
||||
const crimeTriggerText = await crimeTrigger.textContent();
|
||||
console.log('Crime trigger text:', crimeTriggerText);
|
||||
}
|
||||
});
|
||||
});
|
||||
70
tests/simple-submit.spec.ts
Normal file
70
tests/simple-submit.spec.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { DP2FormPage } from './utils/page-objects';
|
||||
|
||||
test.describe('Simple Form Submission Test', () => {
|
||||
let formPage: DP2FormPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
formPage = new DP2FormPage(page);
|
||||
await formPage.goto();
|
||||
});
|
||||
|
||||
test('should submit theft form and check for any errors', async ({ page }) => {
|
||||
// Fill basic info
|
||||
await formPage.fillPlayerInfo('TestPlayer', 0);
|
||||
|
||||
// Select item category
|
||||
await formPage.selectCrimeCategory('item');
|
||||
|
||||
// Select theft
|
||||
await formPage.selectSpecificOffense('theft');
|
||||
|
||||
// Fill special items
|
||||
await formPage.fillSpecialItems({
|
||||
elytra: 1,
|
||||
netherStar: 0,
|
||||
beacon: 0,
|
||||
netheriteBlock: 0,
|
||||
diamondBlock: 0
|
||||
});
|
||||
|
||||
// Add additional items
|
||||
await formPage.addAdditionalItem('diamond', 10);
|
||||
|
||||
// Take screenshot before submit
|
||||
await page.screenshot({ path: 'debug-screenshots/before-submit.png', fullPage: true });
|
||||
|
||||
// Submit the form
|
||||
await formPage.calculateButton.click();
|
||||
|
||||
// Wait a bit for any processing
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Take screenshot after submit
|
||||
await page.screenshot({ path: 'debug-screenshots/after-submit.png', fullPage: true });
|
||||
|
||||
// Check for any error messages
|
||||
const errorMessages = await page.locator('.text-red-500').allTextContents();
|
||||
console.log('Error messages after submit:', errorMessages);
|
||||
|
||||
// Check console for any errors
|
||||
const consoleMessages: string[] = [];
|
||||
page.on('console', msg => {
|
||||
consoleMessages.push(msg.text());
|
||||
});
|
||||
|
||||
// Wait a bit more
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
console.log('Console messages:', consoleMessages);
|
||||
|
||||
// Check if results section exists at all
|
||||
const resultsExists = await formPage.resultsSection.count() > 0;
|
||||
console.log('Results section exists:', resultsExists);
|
||||
|
||||
if (resultsExists) {
|
||||
const isVisible = await formPage.resultsSection.isVisible();
|
||||
console.log('Results section is visible:', isVisible);
|
||||
}
|
||||
});
|
||||
});
|
||||
66
tests/simple-test.spec.ts
Normal file
66
tests/simple-test.spec.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Simple Page Inspection', () => {
|
||||
test('should load page and inspect DOM', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Wait for page to load
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Take screenshot
|
||||
await page.screenshot({ path: 'debug-screenshots/page-loaded.png', fullPage: true });
|
||||
|
||||
// Get page title
|
||||
const title = await page.title();
|
||||
console.log('Page title:', title);
|
||||
|
||||
// Get all headings
|
||||
const headings = await page.locator('h1, h2, h3, h4').allTextContents();
|
||||
console.log('Headings:', headings);
|
||||
|
||||
// Get all form elements
|
||||
const formElements = await page.locator('form input, form select, form button').allTextContents();
|
||||
console.log('Form elements text:', formElements);
|
||||
|
||||
// Get the form HTML
|
||||
const formHtml = await page.locator('form').innerHTML();
|
||||
console.log('Form HTML length:', formHtml.length);
|
||||
|
||||
// Save form HTML to file for inspection
|
||||
await page.evaluate((html) => {
|
||||
const blob = new Blob([html], { type: 'text/html' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
console.log('Form HTML saved to blob:', url);
|
||||
return html.substring(0, 1000) + '...';
|
||||
}, formHtml);
|
||||
|
||||
// Check for specific elements
|
||||
const playerNameInput = page.locator('input[id="playerName"]');
|
||||
const exists = await playerNameInput.count() > 0;
|
||||
console.log('Player name input exists:', exists);
|
||||
|
||||
if (exists) {
|
||||
console.log('Player name input is visible:', await playerNameInput.isVisible());
|
||||
}
|
||||
|
||||
// Get all input IDs
|
||||
const inputIds = await page.locator('input[id]').evaluateAll(inputs =>
|
||||
inputs.map(input => input.id)
|
||||
);
|
||||
console.log('Input IDs:', inputIds);
|
||||
|
||||
// Get all select IDs
|
||||
const selectIds = await page.locator('select[id]').evaluateAll(selects =>
|
||||
selects.map(select => select.id)
|
||||
);
|
||||
console.log('Select IDs:', selectIds);
|
||||
|
||||
// Get all button texts
|
||||
const buttonTexts = await page.locator('button').allTextContents();
|
||||
console.log('Button texts:', buttonTexts);
|
||||
|
||||
// Look for crime category buttons
|
||||
const crimeButtons = await page.locator('button').filter({ hasText: /offenses/ }).allTextContents();
|
||||
console.log('Crime category buttons:', crimeButtons);
|
||||
});
|
||||
});
|
||||
49
tests/test-select-fix.spec.ts
Normal file
49
tests/test-select-fix.spec.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { DP2FormPage } from './utils/page-objects';
|
||||
|
||||
test.describe('Test Select Fix', () => {
|
||||
let formPage: DP2FormPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
formPage = new DP2FormPage(page);
|
||||
await formPage.goto();
|
||||
});
|
||||
|
||||
test('should select theft correctly', async ({ page }) => {
|
||||
// Fill basic info
|
||||
await formPage.fillPlayerInfo('TestPlayer', 0);
|
||||
|
||||
// Select item category
|
||||
await formPage.selectCrimeCategory('item');
|
||||
|
||||
// Select theft
|
||||
await formPage.selectSpecificOffense('theft');
|
||||
|
||||
// Check if offense details section appears
|
||||
await expect(formPage.offenseDetailsSection).toBeVisible();
|
||||
|
||||
// Take screenshot
|
||||
await page.screenshot({ path: 'debug-screenshots/theft-selected-fixed.png', fullPage: true });
|
||||
|
||||
console.log('Theft selection successful!');
|
||||
});
|
||||
|
||||
test('should select grief correctly', async ({ page }) => {
|
||||
// Fill basic info
|
||||
await formPage.fillPlayerInfo('TestPlayer2', 0);
|
||||
|
||||
// Select block category
|
||||
await formPage.selectCrimeCategory('block');
|
||||
|
||||
// Select grief
|
||||
await formPage.selectSpecificOffense('grief');
|
||||
|
||||
// Check if block count input appears
|
||||
await expect(formPage.blockCountInput).toBeVisible();
|
||||
|
||||
// Take screenshot
|
||||
await page.screenshot({ path: 'debug-screenshots/grief-selected-fixed.png', fullPage: true });
|
||||
|
||||
console.log('Grief selection successful!');
|
||||
});
|
||||
});
|
||||
224
tests/utils/page-objects.ts
Normal file
224
tests/utils/page-objects.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
|
||||
export class DP2FormPage {
|
||||
readonly page: Page;
|
||||
|
||||
// Form fields
|
||||
readonly playerNameInput: Locator;
|
||||
readonly currentPointsInput: Locator;
|
||||
readonly crimeCategoryButtons: { [key: string]: Locator };
|
||||
readonly specificOffenseSelectTrigger: Locator;
|
||||
|
||||
// Crime details sections
|
||||
readonly offenseDetailsSection: Locator;
|
||||
|
||||
// Special item inputs (for theft)
|
||||
readonly specialItems: {
|
||||
elytra: Locator;
|
||||
netherStar: Locator;
|
||||
beacon: Locator;
|
||||
netheriteBlock: Locator;
|
||||
diamondBlock: Locator;
|
||||
};
|
||||
|
||||
// Additional item inputs
|
||||
readonly additionalItemInputs: Locator;
|
||||
|
||||
// Block/Entity count inputs
|
||||
readonly blockCountInput: Locator;
|
||||
readonly entityCountInput: Locator;
|
||||
|
||||
// SPP checkbox
|
||||
readonly sppCheckbox: Locator;
|
||||
|
||||
// Submit button
|
||||
readonly calculateButton: Locator;
|
||||
|
||||
// Results section
|
||||
readonly resultsSection: Locator;
|
||||
readonly commandsList: Locator;
|
||||
readonly summarySection: Locator;
|
||||
readonly explanationTextarea: Locator;
|
||||
readonly resetButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
|
||||
// Form fields
|
||||
this.playerNameInput = page.locator('input[id="playerName"]');
|
||||
this.currentPointsInput = page.locator('input[id="currentPoints"]');
|
||||
|
||||
// Crime category buttons
|
||||
this.crimeCategoryButtons = {
|
||||
all: page.locator('button').filter({ hasText: 'all' }),
|
||||
item: page.locator('button').filter({ hasText: 'item offenses' }),
|
||||
block: page.locator('button').filter({ hasText: 'block offenses' }),
|
||||
hacking: page.locator('button').filter({ hasText: 'hacking offenses' }),
|
||||
communication: page.locator('button').filter({ hasText: 'communication offenses' }),
|
||||
};
|
||||
|
||||
this.specificOffenseSelectTrigger = page.locator('button').filter({ hasText: 'select an offense' });
|
||||
|
||||
// Crime details
|
||||
this.offenseDetailsSection = page.locator('h3').filter({ hasText: 'offense details' }).locator('..');
|
||||
|
||||
// Special items
|
||||
this.specialItems = {
|
||||
elytra: page.locator('input').filter({ has: page.locator('xpath=preceding-sibling::span[text()="elytra (20 points)"]') }),
|
||||
netherStar: page.locator('input').filter({ has: page.locator('xpath=preceding-sibling::span[text()="nether star (20 points)"]') }),
|
||||
beacon: page.locator('input').filter({ has: page.locator('xpath=preceding-sibling::span[text()="beacon (20 points)"]') }),
|
||||
netheriteBlock: page.locator('input').filter({ has: page.locator('xpath=preceding-sibling::span[text()="netherite block (10 points)"]') }),
|
||||
diamondBlock: page.locator('input').filter({ has: page.locator('xpath=preceding-sibling::span[text()="diamond block (10 points)"]') }),
|
||||
};
|
||||
|
||||
// Additional items
|
||||
this.additionalItemInputs = page.locator('label').filter({ hasText: 'additional items stolen' }).locator('..');
|
||||
|
||||
// Block/Entity inputs
|
||||
this.blockCountInput = page.locator('input[id="blockCount"]');
|
||||
this.entityCountInput = page.locator('input[id="entityCount"]');
|
||||
|
||||
// SPP checkbox
|
||||
this.sppCheckbox = page.locator('input[id="isSPP"]');
|
||||
|
||||
// Submit button
|
||||
this.calculateButton = page.locator('button[type="submit"]');
|
||||
|
||||
// Results
|
||||
this.resultsSection = page.locator('h3').filter({ hasText: 'results' }).locator('..').locator('..');
|
||||
this.commandsList = this.resultsSection.locator('label').filter({ hasText: 'commands' }).locator('..');
|
||||
this.summarySection = this.resultsSection.locator('label').filter({ hasText: 'summary' }).locator('..');
|
||||
this.explanationTextarea = this.resultsSection.locator('textarea');
|
||||
this.resetButton = this.resultsSection.locator('button').filter({ hasText: 'reset form' });
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/');
|
||||
}
|
||||
|
||||
async fillPlayerInfo(playerName: string, currentPoints: number = 0) {
|
||||
await this.playerNameInput.fill(playerName);
|
||||
await this.currentPointsInput.fill(currentPoints.toString());
|
||||
}
|
||||
|
||||
async selectCrimeCategory(category: 'all' | 'item' | 'block' | 'hacking' | 'communication') {
|
||||
await this.crimeCategoryButtons[category].click();
|
||||
}
|
||||
|
||||
async selectSpecificOffense(offenseId: string) {
|
||||
// Click the select trigger to open dropdown
|
||||
await this.specificOffenseSelectTrigger.click();
|
||||
|
||||
// Find the crime name from the CRIMES array (we need to import this or map it)
|
||||
const crimeName = this.getCrimeNameFromId(offenseId);
|
||||
|
||||
// Click the option with the matching text (exact match)
|
||||
await this.page.locator('[data-slot="select-content"]').locator('text=' + crimeName).first().click();
|
||||
}
|
||||
|
||||
private getCrimeNameFromId(crimeId: string): string {
|
||||
// This is a simplified mapping - in production, you'd want to import the CRIMES array
|
||||
const crimeMap: { [key: string]: string } = {
|
||||
'inappropriate_item_names': 'Inappropriate Item Names',
|
||||
'inappropriate_book_contents': 'Inappropriate Book Contents',
|
||||
'theft': 'Theft',
|
||||
'unconsensual_killing': 'Unconsensual Killing',
|
||||
'illegal_item_use': 'Using Illegal Item',
|
||||
'vandalism': 'Vandalism',
|
||||
'grief': 'Grief',
|
||||
'theft_grief': 'Theft-Grief',
|
||||
'vandalism_infrastructure': 'Vandalism of Public Infrastructure',
|
||||
'trespassing': 'Trespassing',
|
||||
'trespassing_staff': 'Trespassing on Staff/SPP Land',
|
||||
'x_raying': 'X-Raying',
|
||||
'hacking_client': 'Use of Hacking Client',
|
||||
'lagging_server': 'Deliberately Lagging Server',
|
||||
'worldedit_misuse': 'Misuse of Worldedit',
|
||||
'exploit_abuse': 'Abuse of Exploits',
|
||||
'abusive_chat': 'Abusive Chat',
|
||||
'inciting_verbal_conflict': 'Inciting Verbal Conflict',
|
||||
'abusive_vc': 'Abusive VC Language',
|
||||
'lying_to_staff': 'Lying to Staff Member',
|
||||
'manipulation': 'Manipulation',
|
||||
'grand_manipulation': 'Grand Manipulation',
|
||||
'slander': 'Slander (Against SPP Only)',
|
||||
'violation_nca': 'Violation of NCA',
|
||||
};
|
||||
|
||||
return crimeMap[crimeId] || crimeId;
|
||||
}
|
||||
|
||||
async fillSpecialItems(items: { elytra?: number; netherStar?: number; beacon?: number; netheriteBlock?: number; diamondBlock?: number }) {
|
||||
if (items.elytra !== undefined) await this.specialItems.elytra.fill(items.elytra.toString());
|
||||
if (items.netherStar !== undefined) await this.specialItems.netherStar.fill(items.netherStar.toString());
|
||||
if (items.beacon !== undefined) await this.specialItems.beacon.fill(items.beacon.toString());
|
||||
if (items.netheriteBlock !== undefined) await this.specialItems.netheriteBlock.fill(items.netheriteBlock.toString());
|
||||
if (items.diamondBlock !== undefined) await this.specialItems.diamondBlock.fill(items.diamondBlock.toString());
|
||||
}
|
||||
|
||||
async addAdditionalItem(type: string, quantity: number) {
|
||||
// Click "add item" button
|
||||
const addButton = this.additionalItemInputs.locator('button').filter({ hasText: 'add item' });
|
||||
await addButton.click();
|
||||
|
||||
// Fill in the last added item row
|
||||
const itemRows = this.additionalItemInputs.locator('div.flex.items-center.gap-2');
|
||||
const lastRow = itemRows.last();
|
||||
const inputs = lastRow.locator('input');
|
||||
await inputs.nth(0).fill(type);
|
||||
await inputs.nth(1).fill(quantity.toString());
|
||||
}
|
||||
|
||||
async fillBlockCount(count: number) {
|
||||
await this.blockCountInput.fill(count.toString());
|
||||
}
|
||||
|
||||
async fillEntityCount(count: number) {
|
||||
await this.entityCountInput.fill(count.toString());
|
||||
}
|
||||
|
||||
async setSPP(isSPP: boolean) {
|
||||
if (isSPP) {
|
||||
await this.sppCheckbox.check();
|
||||
} else {
|
||||
await this.sppCheckbox.uncheck();
|
||||
}
|
||||
}
|
||||
|
||||
async calculatePunishment() {
|
||||
await this.calculateButton.click();
|
||||
}
|
||||
|
||||
async resetForm() {
|
||||
await this.resetButton.click();
|
||||
}
|
||||
|
||||
async waitForResults() {
|
||||
await this.resultsSection.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async getResults() {
|
||||
const commands = await this.commandsList.locator('code').allTextContents();
|
||||
const summary = await this.summarySection.locator('div.text-sm.space-y-1').textContent();
|
||||
const explanation = await this.explanationTextarea.inputValue();
|
||||
|
||||
return {
|
||||
commands,
|
||||
summary,
|
||||
explanation,
|
||||
};
|
||||
}
|
||||
|
||||
async getSummaryValues() {
|
||||
const summaryDiv = this.summarySection.locator('div.text-sm.space-y-1');
|
||||
const lines = await summaryDiv.locator('div').allTextContents();
|
||||
|
||||
const values: { [key: string]: string } = {};
|
||||
for (const line of lines) {
|
||||
const [key, value] = line.split(': ');
|
||||
values[key] = value;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
332
tests/utils/test-data.ts
Normal file
332
tests/utils/test-data.ts
Normal file
@@ -0,0 +1,332 @@
|
||||
import { CRIMES } from '@/lib/dp2-rules';
|
||||
|
||||
// Test data interfaces
|
||||
export interface TestPlayer {
|
||||
name: string;
|
||||
currentPoints: number;
|
||||
}
|
||||
|
||||
export interface TestOffense {
|
||||
crimeId: string;
|
||||
itemDetails?: Array<{ type: string; quantity: number }>;
|
||||
blockCount?: number;
|
||||
entityCount?: number;
|
||||
isSPP?: boolean;
|
||||
specialItems?: {
|
||||
elytra: number;
|
||||
netherStar: number;
|
||||
beacon: number;
|
||||
netheriteBlock: number;
|
||||
diamondBlock: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TestScenario {
|
||||
player: TestPlayer;
|
||||
offense: TestOffense;
|
||||
expected: {
|
||||
basePoints: number;
|
||||
totalPoints: number;
|
||||
punishmentLevel: string;
|
||||
hasCommands: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// Test data factories
|
||||
export class TestDataFactory {
|
||||
// Item Offenses Test Scenarios
|
||||
static getItemOffenseScenarios(): TestScenario[] {
|
||||
return [
|
||||
// Theft - Minor (< 50 item points)
|
||||
{
|
||||
player: { name: 'TestPlayer1', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'theft',
|
||||
specialItems: { elytra: 0, netherStar: 0, beacon: 0, netheriteBlock: 0, diamondBlock: 0 },
|
||||
itemDetails: [{ type: 'diamond', quantity: 2 }] // 2 * 5 = 10 points
|
||||
},
|
||||
expected: { basePoints: 11, totalPoints: 11, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Theft - Moderate (50-500 item points)
|
||||
{
|
||||
player: { name: 'TestPlayer2', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'theft',
|
||||
specialItems: { elytra: 1, netherStar: 0, beacon: 0, netheriteBlock: 0, diamondBlock: 0 }, // 20 points
|
||||
itemDetails: [{ type: 'diamond', quantity: 10 }] // 10 * 5 = 50 points, total 70
|
||||
},
|
||||
expected: { basePoints: 72, totalPoints: 72, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Theft - Severe (> 500 item points)
|
||||
{
|
||||
player: { name: 'TestPlayer3', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'theft',
|
||||
specialItems: { elytra: 1, netherStar: 1, beacon: 1, netheriteBlock: 0, diamondBlock: 0 }, // 60 points
|
||||
itemDetails: [{ type: 'diamond_block', quantity: 50 }] // 50 * 10 = 500 points, total 560
|
||||
},
|
||||
expected: { basePoints: 563, totalPoints: 563, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Unconsensual Killing
|
||||
{
|
||||
player: { name: 'TestPlayer4', currentPoints: 0 },
|
||||
offense: { crimeId: 'unconsensual_killing' },
|
||||
expected: { basePoints: 2, totalPoints: 2, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Illegal Item Use
|
||||
{
|
||||
player: { name: 'TestPlayer5', currentPoints: 0 },
|
||||
offense: { crimeId: 'illegal_item_use' },
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Inappropriate Item Names
|
||||
{
|
||||
player: { name: 'TestPlayer6', currentPoints: 0 },
|
||||
offense: { crimeId: 'inappropriate_item_names' },
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Inappropriate Book Contents
|
||||
{
|
||||
player: { name: 'TestPlayer7', currentPoints: 0 },
|
||||
offense: { crimeId: 'inappropriate_book_contents' },
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Block Offenses Test Scenarios
|
||||
static getBlockOffenseScenarios(): TestScenario[] {
|
||||
return [
|
||||
// Vandalism (< 10 blocks)
|
||||
{
|
||||
player: { name: 'TestPlayer8', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'vandalism',
|
||||
blockCount: 5,
|
||||
entityCount: 2
|
||||
},
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Grief - Minor (< 100 blocks)
|
||||
{
|
||||
player: { name: 'TestPlayer9', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'grief',
|
||||
blockCount: 50,
|
||||
entityCount: 0
|
||||
},
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Grief - Moderate (100-1000 blocks)
|
||||
{
|
||||
player: { name: 'TestPlayer10', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'grief',
|
||||
blockCount: 500,
|
||||
entityCount: 0
|
||||
},
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Grief - Large (1000-100000 blocks)
|
||||
{
|
||||
player: { name: 'TestPlayer11', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'grief',
|
||||
blockCount: 50000,
|
||||
entityCount: 0
|
||||
},
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Grief - Massive (> 100000 blocks)
|
||||
{
|
||||
player: { name: 'TestPlayer12', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'grief',
|
||||
blockCount: 200000,
|
||||
entityCount: 0
|
||||
},
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Theft-Grief
|
||||
{
|
||||
player: { name: 'TestPlayer13', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'theft_grief',
|
||||
blockCount: 50
|
||||
},
|
||||
expected: { basePoints: 2, totalPoints: 2, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Vandalism of Infrastructure
|
||||
{
|
||||
player: { name: 'TestPlayer14', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'vandalism_infrastructure',
|
||||
blockCount: 10
|
||||
},
|
||||
expected: { basePoints: 6, totalPoints: 6, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Trespassing
|
||||
{
|
||||
player: { name: 'TestPlayer15', currentPoints: 0 },
|
||||
offense: { crimeId: 'trespassing' },
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Trespassing on Staff/SPP Land
|
||||
{
|
||||
player: { name: 'TestPlayer16', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'trespassing_staff',
|
||||
isSPP: true
|
||||
},
|
||||
expected: { basePoints: 3, totalPoints: 3, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Hacking Offenses Test Scenarios
|
||||
static getHackingOffenseScenarios(): TestScenario[] {
|
||||
return [
|
||||
// X-Raying
|
||||
{
|
||||
player: { name: 'TestPlayer17', currentPoints: 0 },
|
||||
offense: { crimeId: 'x_raying' },
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Hacking Client
|
||||
{
|
||||
player: { name: 'TestPlayer18', currentPoints: 0 },
|
||||
offense: { crimeId: 'hacking_client' },
|
||||
expected: { basePoints: 5, totalPoints: 5, punishmentLevel: 'kick', hasCommands: true }
|
||||
},
|
||||
// Lagging Server
|
||||
{
|
||||
player: { name: 'TestPlayer19', currentPoints: 0 },
|
||||
offense: { crimeId: 'lagging_server' },
|
||||
expected: { basePoints: 5, totalPoints: 5, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Worldedit Misuse
|
||||
{
|
||||
player: { name: 'TestPlayer20', currentPoints: 0 },
|
||||
offense: { crimeId: 'worldedit_misuse' },
|
||||
expected: { basePoints: 5, totalPoints: 5, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Exploit Abuse
|
||||
{
|
||||
player: { name: 'TestPlayer21', currentPoints: 0 },
|
||||
offense: { crimeId: 'exploit_abuse' },
|
||||
expected: { basePoints: 10, totalPoints: 10, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Communication Offenses Test Scenarios
|
||||
static getCommunicationOffenseScenarios(): TestScenario[] {
|
||||
return [
|
||||
// Abusive Chat
|
||||
{
|
||||
player: { name: 'TestPlayer22', currentPoints: 0 },
|
||||
offense: { crimeId: 'abusive_chat' },
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Inciting Verbal Conflict
|
||||
{
|
||||
player: { name: 'TestPlayer23', currentPoints: 0 },
|
||||
offense: { crimeId: 'inciting_verbal_conflict' },
|
||||
expected: { basePoints: 2, totalPoints: 2, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Abusive VC Language
|
||||
{
|
||||
player: { name: 'TestPlayer24', currentPoints: 0 },
|
||||
offense: { crimeId: 'abusive_vc' },
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Lying to Staff
|
||||
{
|
||||
player: { name: 'TestPlayer25', currentPoints: 0 },
|
||||
offense: { crimeId: 'lying_to_staff' },
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Manipulation
|
||||
{
|
||||
player: { name: 'TestPlayer26', currentPoints: 0 },
|
||||
offense: { crimeId: 'manipulation' },
|
||||
expected: { basePoints: 5, totalPoints: 5, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Grand Manipulation
|
||||
{
|
||||
player: { name: 'TestPlayer27', currentPoints: 0 },
|
||||
offense: { crimeId: 'grand_manipulation' },
|
||||
expected: { basePoints: 20, totalPoints: 20, punishmentLevel: 'permanent_ban', hasCommands: true }
|
||||
},
|
||||
// Slander (Against SPP)
|
||||
{
|
||||
player: { name: 'TestPlayer28', currentPoints: 0 },
|
||||
offense: {
|
||||
crimeId: 'slander',
|
||||
isSPP: true
|
||||
},
|
||||
expected: { basePoints: 3, totalPoints: 3, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Violation of NCA
|
||||
{
|
||||
player: { name: 'TestPlayer29', currentPoints: 0 },
|
||||
offense: { crimeId: 'violation_nca' },
|
||||
expected: { basePoints: 2, totalPoints: 2, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Point Decay Test Scenarios
|
||||
static getPointDecayScenarios(): TestScenario[] {
|
||||
return [
|
||||
// Points decay after 1 week
|
||||
{
|
||||
player: { name: 'DecayTest1', currentPoints: 10 },
|
||||
offense: { crimeId: 'abusive_chat' },
|
||||
expected: { basePoints: 1, totalPoints: 11, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Points decay after multiple weeks
|
||||
{
|
||||
player: { name: 'DecayTest2', currentPoints: 20 },
|
||||
offense: { crimeId: 'abusive_chat' },
|
||||
expected: { basePoints: 1, totalPoints: 21, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Edge Cases
|
||||
static getEdgeCaseScenarios(): TestScenario[] {
|
||||
return [
|
||||
// Zero points player
|
||||
{
|
||||
player: { name: 'EdgeCase1', currentPoints: 0 },
|
||||
offense: { crimeId: 'abusive_chat' },
|
||||
expected: { basePoints: 1, totalPoints: 1, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// High point player
|
||||
{
|
||||
player: { name: 'EdgeCase2', currentPoints: 50 },
|
||||
offense: { crimeId: 'abusive_chat' },
|
||||
expected: { basePoints: 1, totalPoints: 51, punishmentLevel: 'warning', hasCommands: true }
|
||||
},
|
||||
// Empty form validation
|
||||
{
|
||||
player: { name: '', currentPoints: 0 },
|
||||
offense: { crimeId: '' },
|
||||
expected: { basePoints: 0, totalPoints: 0, punishmentLevel: '', hasCommands: false }
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Get all test scenarios
|
||||
static getAllScenarios(): TestScenario[] {
|
||||
return [
|
||||
...this.getItemOffenseScenarios(),
|
||||
...this.getBlockOffenseScenarios(),
|
||||
...this.getHackingOffenseScenarios(),
|
||||
...this.getCommunicationOffenseScenarios(),
|
||||
...this.getPointDecayScenarios(),
|
||||
...this.getEdgeCaseScenarios(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user