ui improvements

This commit is contained in:
2026-01-22 07:50:20 -05:00
parent bd7225c84e
commit 0eb82da629
25 changed files with 2942 additions and 188 deletions

224
tests/utils/page-objects.ts Normal file
View 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
View 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(),
];
}
}