mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-03-25 00:27:24 +01:00
Task 2: Apply automated Biome linter fixes - No auto-fixes available (223 errors remain)
This commit is contained in:
parent
7dbfc542b3
commit
45afc24aef
11 changed files with 1065 additions and 404 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -5,4 +5,8 @@
|
|||
/dist
|
||||
/coverage
|
||||
/playwright-report
|
||||
/test-results
|
||||
/test-results
|
||||
|
||||
# TypeScript error cataloging
|
||||
error-catalog.json
|
||||
error-report.txt
|
||||
44
package-lock.json
generated
44
package-lock.json
generated
|
|
@ -25,6 +25,7 @@
|
|||
"@vitest/browser-playwright": "^4.0.18",
|
||||
"fast-check": "^4.5.3",
|
||||
"playwright": "^1.57.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vitest": "^4.0.18"
|
||||
|
|
@ -2092,6 +2093,19 @@
|
|||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.13.6",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
|
||||
"integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
|
|
@ -2314,6 +2328,16 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/robust-predicates": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
|
||||
|
|
@ -2483,6 +2507,26 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.27.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@
|
|||
"test:browser": "vitest --config=vitest.browser.config.ts",
|
||||
"test:e2e": "playwright test",
|
||||
"lint": "biome check --write",
|
||||
"format": "biome format --write"
|
||||
"format": "biome format --write",
|
||||
"catalog-errors": "tsx scripts/catalog-errors.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.3.13",
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
"@vitest/browser-playwright": "^4.0.18",
|
||||
"fast-check": "^4.5.3",
|
||||
"playwright": "^1.57.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vitest": "^4.0.18"
|
||||
|
|
|
|||
78
scripts/README.md
Normal file
78
scripts/README.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# TypeScript Error Cataloging Scripts
|
||||
|
||||
## Overview
|
||||
|
||||
This directory contains scripts for analyzing and cataloging TypeScript errors in the Fantasy Map Generator codebase.
|
||||
|
||||
## catalog-errors.ts
|
||||
|
||||
A script that runs `tsc --noEmit` to capture TypeScript errors, parses them into structured JSON format, categorizes them, and generates reports.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
npm run catalog-errors
|
||||
```
|
||||
|
||||
### Output Files
|
||||
|
||||
The script generates two files in the project root:
|
||||
|
||||
1. **error-catalog.json** - Structured JSON catalog containing:
|
||||
- Timestamp of analysis
|
||||
- Total error count
|
||||
- Array of all errors with file, line, column, code, message, category, and severity
|
||||
- Errors grouped by category
|
||||
- Errors grouped by file
|
||||
|
||||
2. **error-report.txt** - Human-readable report containing:
|
||||
- Summary statistics
|
||||
- Error counts by category
|
||||
- Error counts by file (sorted by count)
|
||||
- Detailed error listings organized by category
|
||||
|
||||
### Error Categories
|
||||
|
||||
The script categorizes errors into the following types:
|
||||
|
||||
- **implicit-any**: Variables or parameters without explicit type annotations (TS7006, TS7031, TS7034)
|
||||
- **node-protocol**: Missing 'node:' prefix for Node.js built-in module imports
|
||||
- **type-conversion**: Type assignment and conversion issues
|
||||
- **unused-parameter**: Parameters not used in function bodies (TS6133)
|
||||
- **dynamic-import**: Dynamic namespace import access issues
|
||||
- **type-compatibility**: Type compatibility mismatches (TS2345, TS2322)
|
||||
- **global-conflict**: Global variable type declaration conflicts
|
||||
- **other**: Uncategorized errors
|
||||
|
||||
### Example Output
|
||||
|
||||
```
|
||||
================================================================================
|
||||
Summary
|
||||
================================================================================
|
||||
Total Errors: 223
|
||||
|
||||
By Category:
|
||||
implicit-any: 180
|
||||
type-conversion: 5
|
||||
type-compatibility: 3
|
||||
other: 35
|
||||
|
||||
Top 5 Files by Error Count:
|
||||
src/modules/states-generator.ts: 40
|
||||
src/modules/provinces-generator.ts: 39
|
||||
src/modules/zones-generator.ts: 32
|
||||
src/modules/burgs-generator.ts: 18
|
||||
src/modules/religions-generator.ts: 17
|
||||
```
|
||||
|
||||
## Integration with Cleanup Process
|
||||
|
||||
This cataloging infrastructure supports the systematic TypeScript cleanup effort by:
|
||||
|
||||
1. Providing baseline error counts before cleanup begins
|
||||
2. Enabling progress tracking as errors are resolved
|
||||
3. Identifying error patterns and priorities
|
||||
4. Supporting automated validation after fixes are applied
|
||||
|
||||
Run the script periodically during cleanup to track progress and verify that fixes are reducing the error count without introducing new issues.
|
||||
279
scripts/catalog-errors.ts
Normal file
279
scripts/catalog-errors.ts
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* TypeScript Error Cataloging Script
|
||||
*
|
||||
* This script runs `tsc --noEmit` to capture TypeScript errors,
|
||||
* parses them into structured JSON format, categorizes them,
|
||||
* and generates a report showing counts by category and file.
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
enum ErrorCategory {
|
||||
ImplicitAny = 'implicit-any',
|
||||
NodeProtocol = 'node-protocol',
|
||||
TypeConversion = 'type-conversion',
|
||||
UnusedParameter = 'unused-parameter',
|
||||
DynamicImport = 'dynamic-import',
|
||||
TypeCompatibility = 'type-compatibility',
|
||||
GlobalConflict = 'global-conflict',
|
||||
Other = 'other'
|
||||
}
|
||||
|
||||
interface TypeScriptError {
|
||||
file: string;
|
||||
line: number;
|
||||
column: number;
|
||||
code: string;
|
||||
message: string;
|
||||
category: ErrorCategory;
|
||||
severity: 'error' | 'warning';
|
||||
}
|
||||
|
||||
interface ErrorCatalog {
|
||||
timestamp: string;
|
||||
totalErrors: number;
|
||||
errors: TypeScriptError[];
|
||||
errorsByCategory: Record<ErrorCategory, TypeScriptError[]>;
|
||||
errorsByFile: Record<string, TypeScriptError[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Categorize a TypeScript error based on its code and message
|
||||
*/
|
||||
function categorizeError(code: string, message: string): ErrorCategory {
|
||||
// Implicit any types
|
||||
if (code === 'TS7006' || code === 'TS7031' || code === 'TS7034') {
|
||||
return ErrorCategory.ImplicitAny;
|
||||
}
|
||||
|
||||
// Node.js import protocol issues
|
||||
if (message.includes("'node:'") || message.includes('node protocol')) {
|
||||
return ErrorCategory.NodeProtocol;
|
||||
}
|
||||
|
||||
// Type conversion issues
|
||||
if (message.includes('Type') && (message.includes('is not assignable to') || message.includes('conversion'))) {
|
||||
return ErrorCategory.TypeConversion;
|
||||
}
|
||||
|
||||
// Unused parameters
|
||||
if (code === 'TS6133' && message.includes('parameter')) {
|
||||
return ErrorCategory.UnusedParameter;
|
||||
}
|
||||
|
||||
// Dynamic import/namespace access
|
||||
if (message.includes('dynamic') || message.includes('namespace')) {
|
||||
return ErrorCategory.DynamicImport;
|
||||
}
|
||||
|
||||
// Type compatibility
|
||||
if (code === 'TS2345' || code === 'TS2322') {
|
||||
return ErrorCategory.TypeCompatibility;
|
||||
}
|
||||
|
||||
// Global variable conflicts
|
||||
if (message.includes('global') || message.includes('duplicate')) {
|
||||
return ErrorCategory.GlobalConflict;
|
||||
}
|
||||
|
||||
return ErrorCategory.Other;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse TypeScript compiler output into structured errors
|
||||
*/
|
||||
function parseTypeScriptErrors(output: string): TypeScriptError[] {
|
||||
const errors: TypeScriptError[] = [];
|
||||
const lines = output.split('\n');
|
||||
|
||||
// TypeScript error format: file(line,column): error TSxxxx: message
|
||||
const errorPattern = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.+)$/;
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(errorPattern);
|
||||
if (match) {
|
||||
const [, file, lineNum, column, severity, code, message] = match;
|
||||
errors.push({
|
||||
file: file.trim(),
|
||||
line: parseInt(lineNum, 10),
|
||||
column: parseInt(column, 10),
|
||||
code,
|
||||
message: message.trim(),
|
||||
category: categorizeError(code, message),
|
||||
severity: severity as 'error' | 'warning'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group errors by category
|
||||
*/
|
||||
function groupByCategory(errors: TypeScriptError[]): Record<ErrorCategory, TypeScriptError[]> {
|
||||
const grouped: Record<ErrorCategory, TypeScriptError[]> = {
|
||||
[ErrorCategory.ImplicitAny]: [],
|
||||
[ErrorCategory.NodeProtocol]: [],
|
||||
[ErrorCategory.TypeConversion]: [],
|
||||
[ErrorCategory.UnusedParameter]: [],
|
||||
[ErrorCategory.DynamicImport]: [],
|
||||
[ErrorCategory.TypeCompatibility]: [],
|
||||
[ErrorCategory.GlobalConflict]: [],
|
||||
[ErrorCategory.Other]: []
|
||||
};
|
||||
|
||||
for (const error of errors) {
|
||||
grouped[error.category].push(error);
|
||||
}
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group errors by file
|
||||
*/
|
||||
function groupByFile(errors: TypeScriptError[]): Record<string, TypeScriptError[]> {
|
||||
const grouped: Record<string, TypeScriptError[]> = {};
|
||||
|
||||
for (const error of errors) {
|
||||
if (!grouped[error.file]) {
|
||||
grouped[error.file] = [];
|
||||
}
|
||||
grouped[error.file].push(error);
|
||||
}
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a human-readable report
|
||||
*/
|
||||
function generateReport(catalog: ErrorCatalog): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push('='.repeat(80));
|
||||
lines.push('TypeScript Error Catalog Report');
|
||||
lines.push('='.repeat(80));
|
||||
lines.push('');
|
||||
lines.push(`Generated: ${catalog.timestamp}`);
|
||||
lines.push(`Total Errors: ${catalog.totalErrors}`);
|
||||
lines.push('');
|
||||
|
||||
// Errors by category
|
||||
lines.push('-'.repeat(80));
|
||||
lines.push('Errors by Category');
|
||||
lines.push('-'.repeat(80));
|
||||
lines.push('');
|
||||
|
||||
for (const [category, errors] of Object.entries(catalog.errorsByCategory)) {
|
||||
if (errors.length > 0) {
|
||||
lines.push(`${category}: ${errors.length} errors`);
|
||||
}
|
||||
}
|
||||
lines.push('');
|
||||
|
||||
// Errors by file
|
||||
lines.push('-'.repeat(80));
|
||||
lines.push('Errors by File');
|
||||
lines.push('-'.repeat(80));
|
||||
lines.push('');
|
||||
|
||||
const sortedFiles = Object.entries(catalog.errorsByFile)
|
||||
.sort((a, b) => b[1].length - a[1].length);
|
||||
|
||||
for (const [file, errors] of sortedFiles) {
|
||||
lines.push(`${file}: ${errors.length} errors`);
|
||||
}
|
||||
lines.push('');
|
||||
|
||||
// Detailed error list by category
|
||||
lines.push('-'.repeat(80));
|
||||
lines.push('Detailed Errors by Category');
|
||||
lines.push('-'.repeat(80));
|
||||
lines.push('');
|
||||
|
||||
for (const [category, errors] of Object.entries(catalog.errorsByCategory)) {
|
||||
if (errors.length > 0) {
|
||||
lines.push('');
|
||||
lines.push(`## ${category} (${errors.length} errors)`);
|
||||
lines.push('');
|
||||
|
||||
for (const error of errors) {
|
||||
lines.push(` ${error.file}(${error.line},${error.column}): ${error.code}`);
|
||||
lines.push(` ${error.message}`);
|
||||
lines.push('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Main execution
|
||||
*/
|
||||
function main() {
|
||||
console.log('Running TypeScript compiler to capture errors...');
|
||||
|
||||
let output = '';
|
||||
try {
|
||||
// Run tsc --noEmit and capture output
|
||||
execSync('npx tsc --noEmit', { encoding: 'utf-8', stdio: 'pipe' });
|
||||
console.log('No TypeScript errors found!');
|
||||
output = '';
|
||||
} catch (error: any) {
|
||||
// tsc exits with non-zero code when errors exist
|
||||
output = error.stdout || error.stderr || '';
|
||||
}
|
||||
|
||||
console.log('Parsing errors...');
|
||||
const errors = parseTypeScriptErrors(output);
|
||||
|
||||
console.log('Categorizing errors...');
|
||||
const errorsByCategory = groupByCategory(errors);
|
||||
const errorsByFile = groupByFile(errors);
|
||||
|
||||
const catalog: ErrorCatalog = {
|
||||
timestamp: new Date().toISOString(),
|
||||
totalErrors: errors.length,
|
||||
errors,
|
||||
errorsByCategory,
|
||||
errorsByFile
|
||||
};
|
||||
|
||||
// Write JSON catalog
|
||||
const jsonPath = resolve(process.cwd(), 'error-catalog.json');
|
||||
writeFileSync(jsonPath, JSON.stringify(catalog, null, 2));
|
||||
console.log(`\nJSON catalog written to: ${jsonPath}`);
|
||||
|
||||
// Write human-readable report
|
||||
const reportPath = resolve(process.cwd(), 'error-report.txt');
|
||||
const report = generateReport(catalog);
|
||||
writeFileSync(reportPath, report);
|
||||
console.log(`Report written to: ${reportPath}`);
|
||||
|
||||
// Print summary to console
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('Summary');
|
||||
console.log('='.repeat(80));
|
||||
console.log(`Total Errors: ${catalog.totalErrors}`);
|
||||
console.log('\nBy Category:');
|
||||
for (const [category, errors] of Object.entries(errorsByCategory)) {
|
||||
if (errors.length > 0) {
|
||||
console.log(` ${category}: ${errors.length}`);
|
||||
}
|
||||
}
|
||||
console.log('\nTop 5 Files by Error Count:');
|
||||
const topFiles = Object.entries(errorsByFile)
|
||||
.sort((a, b) => b[1].length - a[1].length)
|
||||
.slice(0, 5);
|
||||
for (const [file, errors] of topFiles) {
|
||||
console.log(` ${file}: ${errors.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -8558,6 +8558,6 @@
|
|||
<script defer src="modules/io/save.js?v=1.111.0"></script>
|
||||
<script defer src="modules/io/load.js?v=1.111.0"></script>
|
||||
<script defer src="modules/io/cloud.js?v=1.106.0"></script>
|
||||
<script defer src="modules/io/export.js?v=1.108.14"></script>
|
||||
<script defer src="modules/io/export.js?v=1.112.2"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,96 +0,0 @@
|
|||
/**
|
||||
* UI Integration tests for zones GeoJSON export button
|
||||
* Feature: zones-geojson-export
|
||||
*
|
||||
* These tests verify the zones export button is correctly integrated into the UI
|
||||
* Validates: Requirements 4.1, 4.2, 4.3, 4.4
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll } from "vitest";
|
||||
import { readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
describe("zones GeoJSON export - UI Integration Tests", () => {
|
||||
let htmlContent: string;
|
||||
|
||||
beforeAll(() => {
|
||||
// Read the index.html file
|
||||
const htmlPath = join(__dirname, "../../index.html");
|
||||
htmlContent = readFileSync(htmlPath, "utf-8");
|
||||
});
|
||||
|
||||
/**
|
||||
* Test 4.2.1: Button exists in correct location
|
||||
* Validates: Requirement 4.1, 4.4
|
||||
* The zones button should be in the "Export to GeoJSON" section after the markers button
|
||||
*/
|
||||
it("should have zones button in correct location after markers button", () => {
|
||||
// Find the GeoJSON export section
|
||||
expect(htmlContent).toContain("Export to GeoJSON");
|
||||
|
||||
// Find the markers button
|
||||
const markersButtonPattern = /<button[^>]*onclick="saveGeoJsonMarkers\(\)"[^>]*>markers<\/button>/;
|
||||
const markersMatch = htmlContent.match(markersButtonPattern);
|
||||
expect(markersMatch).toBeTruthy();
|
||||
|
||||
// Find the zones button
|
||||
const zonesButtonPattern = /<button[^>]*onclick="saveGeoJsonZones\(\)"[^>]*>zones<\/button>/;
|
||||
const zonesMatch = htmlContent.match(zonesButtonPattern);
|
||||
expect(zonesMatch).toBeTruthy();
|
||||
|
||||
// Verify zones button comes after markers button in the HTML
|
||||
const markersIndex = htmlContent.indexOf(markersMatch![0]);
|
||||
const zonesIndex = htmlContent.indexOf(zonesMatch![0]);
|
||||
expect(zonesIndex).toBeGreaterThan(markersIndex);
|
||||
});
|
||||
|
||||
/**
|
||||
* Test 4.2.2: Button has correct tooltip
|
||||
* Validates: Requirement 4.2
|
||||
* The zones button should have a data-tip attribute with the correct tooltip text
|
||||
*/
|
||||
it("should have correct tooltip on zones button", () => {
|
||||
// Find the zones button with data-tip attribute
|
||||
const zonesButtonPattern = /<button[^>]*onclick="saveGeoJsonZones\(\)"[^>]*data-tip="([^"]*)"[^>]*>zones<\/button>/;
|
||||
const match = htmlContent.match(zonesButtonPattern);
|
||||
|
||||
expect(match).toBeTruthy();
|
||||
expect(match![1]).toBe("Download zones data in GeoJSON format");
|
||||
});
|
||||
|
||||
/**
|
||||
* Test 4.2.3: Button has correct onclick handler
|
||||
* Validates: Requirement 4.3
|
||||
* The zones button should have onclick="saveGeoJsonZones()"
|
||||
*/
|
||||
it("should have correct onclick handler", () => {
|
||||
// Find the zones button with onclick attribute
|
||||
const zonesButtonPattern = /<button[^>]*onclick="(saveGeoJsonZones\(\))"[^>]*>zones<\/button>/;
|
||||
const match = htmlContent.match(zonesButtonPattern);
|
||||
|
||||
expect(match).toBeTruthy();
|
||||
expect(match![1]).toBe("saveGeoJsonZones()");
|
||||
});
|
||||
|
||||
/**
|
||||
* Test 4.2.4: Button is in the GeoJSON export section
|
||||
* Validates: Requirement 4.1
|
||||
* The zones button should be in the same div as other GeoJSON export buttons
|
||||
*/
|
||||
it("should be in the GeoJSON export section with other export buttons", () => {
|
||||
// Find the GeoJSON export section
|
||||
const geojsonSectionPattern = /<div[^>]*>Export to GeoJSON<\/div>\s*<div>([\s\S]*?)<\/div>/;
|
||||
const match = htmlContent.match(geojsonSectionPattern);
|
||||
|
||||
expect(match).toBeTruthy();
|
||||
|
||||
const exportButtonsSection = match![1];
|
||||
|
||||
// Verify all expected buttons are in the section
|
||||
expect(exportButtonsSection).toContain('onclick="saveGeoJsonCells()"');
|
||||
expect(exportButtonsSection).toContain('onclick="saveGeoJsonRoutes()"');
|
||||
expect(exportButtonsSection).toContain('onclick="saveGeoJsonRivers()"');
|
||||
expect(exportButtonsSection).toContain('onclick="saveGeoJsonMarkers()"');
|
||||
expect(exportButtonsSection).toContain('onclick="saveGeoJsonZones()"');
|
||||
});
|
||||
});
|
||||
|
|
@ -3,12 +3,16 @@
|
|||
* Feature: zones-geojson-export
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
// Mock global functions and objects
|
||||
declare global {
|
||||
var pack: any;
|
||||
var getCoordinates: (x: number, y: number, decimals: number) => [number, number];
|
||||
var getCoordinates: (
|
||||
x: number,
|
||||
y: number,
|
||||
decimals: number,
|
||||
) => [number, number];
|
||||
var getFileName: (dataType: string) => string;
|
||||
var downloadFile: (data: string, fileName: string, mimeType: string) => void;
|
||||
}
|
||||
|
|
@ -16,11 +20,13 @@ declare global {
|
|||
describe("zones GeoJSON export - Edge Case Unit Tests", () => {
|
||||
beforeEach(() => {
|
||||
// Mock getCoordinates function
|
||||
globalThis.getCoordinates = vi.fn((x: number, y: number, decimals: number) => {
|
||||
const lon = Number((x / 10).toFixed(decimals));
|
||||
const lat = Number((y / 10).toFixed(decimals));
|
||||
return [lon, lat];
|
||||
});
|
||||
globalThis.getCoordinates = vi.fn(
|
||||
(x: number, y: number, decimals: number) => {
|
||||
const lon = Number((x / 10).toFixed(decimals));
|
||||
const lat = Number((y / 10).toFixed(decimals));
|
||||
return [lon, lat];
|
||||
},
|
||||
);
|
||||
|
||||
// Mock getFileName function
|
||||
globalThis.getFileName = vi.fn((dataType: string) => {
|
||||
|
|
@ -40,19 +46,55 @@ describe("zones GeoJSON export - Edge Case Unit Tests", () => {
|
|||
it("should generate empty FeatureCollection when all zones are hidden", () => {
|
||||
// Setup: All zones are hidden
|
||||
const zones = [
|
||||
{ i: 0, name: "Zone 1", type: "Territory", color: "#ff0000", cells: [0, 1], hidden: true },
|
||||
{ i: 1, name: "Zone 2", type: "Climate", color: "#00ff00", cells: [2, 3], hidden: true },
|
||||
{
|
||||
i: 0,
|
||||
name: "Zone 1",
|
||||
type: "Territory",
|
||||
color: "#ff0000",
|
||||
cells: [0, 1],
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
i: 1,
|
||||
name: "Zone 2",
|
||||
type: "Climate",
|
||||
color: "#00ff00",
|
||||
cells: [2, 3],
|
||||
hidden: true,
|
||||
},
|
||||
];
|
||||
|
||||
const mockCells = {
|
||||
v: [[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2]],
|
||||
c: [[1, 2, 3], [0, 2, 3], [0, 1, 3], [0, 1, 2]],
|
||||
v: [
|
||||
[0, 1, 2],
|
||||
[0, 1, 2],
|
||||
[0, 1, 2],
|
||||
[0, 1, 2],
|
||||
],
|
||||
c: [
|
||||
[1, 2, 3],
|
||||
[0, 2, 3],
|
||||
[0, 1, 3],
|
||||
[0, 1, 2],
|
||||
],
|
||||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: [[0, 0], [10, 0], [5, 10]],
|
||||
c: [[0, 1], [0, 1], [0, 1]],
|
||||
v: [[1, 2], [0, 2], [0, 1]],
|
||||
p: [
|
||||
[0, 0],
|
||||
[10, 0],
|
||||
[5, 10],
|
||||
],
|
||||
c: [
|
||||
[0, 1],
|
||||
[0, 1],
|
||||
[0, 1],
|
||||
],
|
||||
v: [
|
||||
[1, 2],
|
||||
[0, 2],
|
||||
[0, 1],
|
||||
],
|
||||
};
|
||||
|
||||
globalThis.pack = { zones, cells: mockCells, vertices: mockVertices };
|
||||
|
|
@ -167,8 +209,22 @@ describe("zones GeoJSON export - Edge Case Unit Tests", () => {
|
|||
it("should generate empty FeatureCollection when all zones have no cells", () => {
|
||||
// Setup: All zones have empty cells arrays
|
||||
const zones = [
|
||||
{ i: 0, name: "Zone 1", type: "Territory", color: "#ff0000", cells: [], hidden: false },
|
||||
{ i: 1, name: "Zone 2", type: "Climate", color: "#00ff00", cells: [], hidden: false },
|
||||
{
|
||||
i: 0,
|
||||
name: "Zone 1",
|
||||
type: "Territory",
|
||||
color: "#ff0000",
|
||||
cells: [],
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
i: 1,
|
||||
name: "Zone 2",
|
||||
type: "Climate",
|
||||
color: "#00ff00",
|
||||
cells: [],
|
||||
hidden: false,
|
||||
},
|
||||
];
|
||||
|
||||
const mockCells = {
|
||||
|
|
@ -177,9 +233,17 @@ describe("zones GeoJSON export - Edge Case Unit Tests", () => {
|
|||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: [[0, 0], [10, 0], [5, 10]],
|
||||
p: [
|
||||
[0, 0],
|
||||
[10, 0],
|
||||
[5, 10],
|
||||
],
|
||||
c: [[0], [0], [0]],
|
||||
v: [[1, 2], [0, 2], [0, 1]],
|
||||
v: [
|
||||
[1, 2],
|
||||
[0, 2],
|
||||
[0, 1],
|
||||
],
|
||||
};
|
||||
|
||||
globalThis.pack = { zones, cells: mockCells, vertices: mockVertices };
|
||||
|
|
@ -301,18 +365,43 @@ describe("zones GeoJSON export - Edge Case Unit Tests", () => {
|
|||
it("should export single visible zone with correct GeoJSON structure and properties", () => {
|
||||
// Setup: One visible zone with cells that have boundaries
|
||||
const zones = [
|
||||
{ i: 0, name: "Test Zone", type: "Territory", color: "#ff0000", cells: [0, 1], hidden: false },
|
||||
{
|
||||
i: 0,
|
||||
name: "Test Zone",
|
||||
type: "Territory",
|
||||
color: "#ff0000",
|
||||
cells: [0, 1],
|
||||
hidden: false,
|
||||
},
|
||||
];
|
||||
|
||||
const mockCells = {
|
||||
v: [[0, 1, 2], [0, 1, 2]],
|
||||
c: [[1, 2, 3], [0, 2, 3]], // Cell 0 has neighbors 1,2,3 where 2,3 are outside the zone
|
||||
v: [
|
||||
[0, 1, 2],
|
||||
[0, 1, 2],
|
||||
],
|
||||
c: [
|
||||
[1, 2, 3],
|
||||
[0, 2, 3],
|
||||
], // Cell 0 has neighbors 1,2,3 where 2,3 are outside the zone
|
||||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: [[0, 0], [10, 0], [5, 10]],
|
||||
c: [[0, 1, 2], [0, 1, 3], [0, 1, 2]], // Vertices connected to cells including outside cells
|
||||
v: [[1, 2], [0, 2], [0, 1]],
|
||||
p: [
|
||||
[0, 0],
|
||||
[10, 0],
|
||||
[5, 10],
|
||||
],
|
||||
c: [
|
||||
[0, 1, 2],
|
||||
[0, 1, 3],
|
||||
[0, 1, 2],
|
||||
], // Vertices connected to cells including outside cells
|
||||
v: [
|
||||
[1, 2],
|
||||
[0, 2],
|
||||
[0, 1],
|
||||
],
|
||||
};
|
||||
|
||||
globalThis.pack = { zones, cells: mockCells, vertices: mockVertices };
|
||||
|
|
@ -450,20 +539,67 @@ describe("zones GeoJSON export - Edge Case Unit Tests", () => {
|
|||
it("should export multiple visible zones with correct feature count", () => {
|
||||
// Setup: Multiple visible zones with one hidden
|
||||
const zones = [
|
||||
{ i: 0, name: "Zone 1", type: "Territory", color: "#ff0000", cells: [0, 1], hidden: false },
|
||||
{ i: 1, name: "Zone 2", type: "Climate", color: "#00ff00", cells: [2, 3], hidden: true },
|
||||
{ i: 2, name: "Zone 3", type: "Unknown", color: "#0000ff", cells: [4, 5], hidden: false },
|
||||
{
|
||||
i: 0,
|
||||
name: "Zone 1",
|
||||
type: "Territory",
|
||||
color: "#ff0000",
|
||||
cells: [0, 1],
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
i: 1,
|
||||
name: "Zone 2",
|
||||
type: "Climate",
|
||||
color: "#00ff00",
|
||||
cells: [2, 3],
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
i: 2,
|
||||
name: "Zone 3",
|
||||
type: "Unknown",
|
||||
color: "#0000ff",
|
||||
cells: [4, 5],
|
||||
hidden: false,
|
||||
},
|
||||
];
|
||||
|
||||
const mockCells = {
|
||||
v: [[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2]],
|
||||
c: [[1, 2, 3], [0, 2, 3], [0, 1, 3], [0, 1, 2], [5, 2, 3], [4, 2, 3]],
|
||||
v: [
|
||||
[0, 1, 2],
|
||||
[0, 1, 2],
|
||||
[0, 1, 2],
|
||||
[0, 1, 2],
|
||||
[0, 1, 2],
|
||||
[0, 1, 2],
|
||||
],
|
||||
c: [
|
||||
[1, 2, 3],
|
||||
[0, 2, 3],
|
||||
[0, 1, 3],
|
||||
[0, 1, 2],
|
||||
[5, 2, 3],
|
||||
[4, 2, 3],
|
||||
],
|
||||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: [[0, 0], [10, 0], [5, 10]],
|
||||
c: [[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]],
|
||||
v: [[1, 2], [0, 2], [0, 1]],
|
||||
p: [
|
||||
[0, 0],
|
||||
[10, 0],
|
||||
[5, 10],
|
||||
],
|
||||
c: [
|
||||
[0, 1, 2, 3, 4, 5],
|
||||
[0, 1, 2, 3, 4, 5],
|
||||
[0, 1, 2, 3, 4, 5],
|
||||
],
|
||||
v: [
|
||||
[1, 2],
|
||||
[0, 2],
|
||||
[0, 1],
|
||||
],
|
||||
};
|
||||
|
||||
globalThis.pack = { zones, cells: mockCells, vertices: mockVertices };
|
||||
|
|
@ -590,7 +726,9 @@ describe("zones GeoJSON export - Edge Case Unit Tests", () => {
|
|||
expect(feature2?.properties.cells).toEqual([4, 5]);
|
||||
|
||||
// Verify hidden zone is not exported
|
||||
const hiddenFeature = result.features.find((f: any) => f.properties.id === 1);
|
||||
const hiddenFeature = result.features.find(
|
||||
(f: any) => f.properties.id === 1,
|
||||
);
|
||||
expect(hiddenFeature).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -318,7 +318,7 @@ class ProvinceModule {
|
|||
if (singleIsle) return "Island";
|
||||
if (isleGroup) return "Islands";
|
||||
if (colony) return "Colony";
|
||||
return rw(this.forms["Wild"]);
|
||||
return rw(this.forms.Wild);
|
||||
})();
|
||||
|
||||
const fullName = `${name} ${formName}`;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,50 @@
|
|||
import type { CurveFactory } from "d3";
|
||||
import * as d3 from "d3";
|
||||
import { color, line, range } from "d3";
|
||||
import {
|
||||
color,
|
||||
curveBasis,
|
||||
curveBasisClosed,
|
||||
curveBasisOpen,
|
||||
curveBundle,
|
||||
curveCardinal,
|
||||
curveCardinalClosed,
|
||||
curveCardinalOpen,
|
||||
curveCatmullRom,
|
||||
curveCatmullRomClosed,
|
||||
curveCatmullRomOpen,
|
||||
curveLinear,
|
||||
curveLinearClosed,
|
||||
curveMonotoneX,
|
||||
curveMonotoneY,
|
||||
curveNatural,
|
||||
curveStep,
|
||||
curveStepAfter,
|
||||
curveStepBefore,
|
||||
line,
|
||||
range,
|
||||
} from "d3";
|
||||
import { round } from "../utils";
|
||||
|
||||
const curveFactories: Record<string, CurveFactory> = {
|
||||
curveBasis,
|
||||
curveBasisClosed,
|
||||
curveBasisOpen,
|
||||
curveBundle,
|
||||
curveCardinal,
|
||||
curveCardinalClosed,
|
||||
curveCardinalOpen,
|
||||
curveCatmullRom,
|
||||
curveCatmullRomClosed,
|
||||
curveCatmullRomOpen,
|
||||
curveLinear,
|
||||
curveLinearClosed,
|
||||
curveMonotoneX,
|
||||
curveMonotoneY,
|
||||
curveNatural,
|
||||
curveStep,
|
||||
curveStepAfter,
|
||||
curveStepBefore,
|
||||
};
|
||||
|
||||
declare global {
|
||||
var drawHeightmap: () => void;
|
||||
}
|
||||
|
|
@ -28,10 +70,8 @@ const heightmapRenderer = (): void => {
|
|||
if (renderOceanCells) {
|
||||
const skip = +ocean.attr("skip") + 1 || 1;
|
||||
const relax = +ocean.attr("relax") || 0;
|
||||
// TODO: Improve for treeshaking
|
||||
const curveType: keyof typeof d3 = (ocean.attr("curve") ||
|
||||
"curveBasisClosed") as keyof typeof d3;
|
||||
const lineGen = line().curve(d3[curveType] as CurveFactory);
|
||||
const curveType = ocean.attr("curve") || "curveBasisClosed";
|
||||
const lineGen = line().curve(curveFactories[curveType] || curveBasisClosed);
|
||||
|
||||
let currentLayer = 0;
|
||||
for (const i of heights) {
|
||||
|
|
@ -59,9 +99,8 @@ const heightmapRenderer = (): void => {
|
|||
{
|
||||
const skip = +land.attr("skip") + 1 || 1;
|
||||
const relax = +land.attr("relax") || 0;
|
||||
const curveType: keyof typeof d3 = (land.attr("curve") ||
|
||||
"curveBasisClosed") as keyof typeof d3;
|
||||
const lineGen = line().curve(d3[curveType] as CurveFactory);
|
||||
const curveType = land.attr("curve") || "curveBasisClosed";
|
||||
const lineGen = line().curve(curveFactories[curveType] || curveBasisClosed);
|
||||
|
||||
let currentLayer = 20;
|
||||
for (const i of heights) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue