mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-04-05 06:57:24 +02: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
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -6,3 +6,7 @@
|
|||
/coverage
|
||||
/playwright-report
|
||||
/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>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,17 @@
|
|||
* Feature: zones-geojson-export
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import * as fc from "fast-check";
|
||||
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;
|
||||
}
|
||||
|
|
@ -17,11 +21,13 @@ declare global {
|
|||
describe("zones GeoJSON export - Property-Based Tests", () => {
|
||||
beforeEach(() => {
|
||||
// Mock getCoordinates function
|
||||
globalThis.getCoordinates = vi.fn((x: number, y: number, decimals: number) => {
|
||||
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) => {
|
||||
|
|
@ -44,35 +50,54 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
fc.assert(
|
||||
fc.property(
|
||||
// Generate random zones with varying properties
|
||||
fc.array(
|
||||
fc
|
||||
.array(
|
||||
fc.record({
|
||||
i: fc.integer({ min: 0, max: 1000 }),
|
||||
name: fc.string({ minLength: 1, maxLength: 50 }),
|
||||
type: fc.oneof(fc.constant("Unknown"), fc.constant("Territory"), fc.constant("Climate")),
|
||||
type: fc.oneof(
|
||||
fc.constant("Unknown"),
|
||||
fc.constant("Territory"),
|
||||
fc.constant("Climate"),
|
||||
),
|
||||
color: fc.oneof(
|
||||
fc.constant("#ff0000"),
|
||||
fc.constant("#00ff00"),
|
||||
fc.constant("url(#hatch1)")
|
||||
fc.constant("url(#hatch1)"),
|
||||
),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), { minLength: 0, maxLength: 10 }),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), {
|
||||
minLength: 0,
|
||||
maxLength: 10,
|
||||
}),
|
||||
hidden: fc.boolean(),
|
||||
}),
|
||||
{ minLength: 0, maxLength: 20 }
|
||||
).map(zones => {
|
||||
{ minLength: 0, maxLength: 20 },
|
||||
)
|
||||
.map((zones) => {
|
||||
// Ensure unique zone IDs
|
||||
return zones.map((zone, index) => ({ ...zone, i: index }));
|
||||
}),
|
||||
(zones) => {
|
||||
// Setup mock pack data
|
||||
const mockCells = {
|
||||
v: Array(101).fill(null).map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101).fill(null).map(() => [0, 1, 2]), // Neighbors
|
||||
v: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Neighbors
|
||||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: Array(3).fill(null).map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
v: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
p: Array(3)
|
||||
.fill(null)
|
||||
.map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
v: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
};
|
||||
|
||||
globalThis.pack = {
|
||||
|
|
@ -197,9 +222,9 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
expect(feature.geometry).toHaveProperty("type");
|
||||
expect(feature.geometry).toHaveProperty("coordinates");
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -215,35 +240,54 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
fc.assert(
|
||||
fc.property(
|
||||
// Generate random zones with mixed visibility and cell counts
|
||||
fc.array(
|
||||
fc
|
||||
.array(
|
||||
fc.record({
|
||||
i: fc.integer({ min: 0, max: 1000 }),
|
||||
name: fc.string({ minLength: 1, maxLength: 50 }),
|
||||
type: fc.oneof(fc.constant("Unknown"), fc.constant("Territory"), fc.constant("Climate")),
|
||||
type: fc.oneof(
|
||||
fc.constant("Unknown"),
|
||||
fc.constant("Territory"),
|
||||
fc.constant("Climate"),
|
||||
),
|
||||
color: fc.oneof(
|
||||
fc.constant("#ff0000"),
|
||||
fc.constant("#00ff00"),
|
||||
fc.constant("url(#hatch1)")
|
||||
fc.constant("url(#hatch1)"),
|
||||
),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), { minLength: 0, maxLength: 10 }),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), {
|
||||
minLength: 0,
|
||||
maxLength: 10,
|
||||
}),
|
||||
hidden: fc.boolean(), // Mix of hidden and visible zones
|
||||
}),
|
||||
{ minLength: 0, maxLength: 20 }
|
||||
).map(zones => {
|
||||
{ minLength: 0, maxLength: 20 },
|
||||
)
|
||||
.map((zones) => {
|
||||
// Ensure unique zone IDs
|
||||
return zones.map((zone, index) => ({ ...zone, i: index }));
|
||||
}),
|
||||
(zones) => {
|
||||
// Setup mock pack data
|
||||
const mockCells = {
|
||||
v: Array(101).fill(null).map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101).fill(null).map(() => [0, 1, 2]), // Neighbors
|
||||
v: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Neighbors
|
||||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: Array(3).fill(null).map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
v: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
p: Array(3)
|
||||
.fill(null)
|
||||
.map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
v: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
};
|
||||
|
||||
globalThis.pack = {
|
||||
|
|
@ -354,14 +398,14 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
const result = saveGeoJsonZones();
|
||||
|
||||
// Calculate expected visible zones (not hidden AND has cells)
|
||||
const expectedVisibleZones = zones.filter(
|
||||
zone => !zone.hidden && zone.cells && zone.cells.length > 0
|
||||
const _expectedVisibleZones = zones.filter(
|
||||
(zone) => !zone.hidden && zone.cells && zone.cells.length > 0,
|
||||
);
|
||||
|
||||
// Verify that all exported features correspond to visible zones only
|
||||
for (const feature of result.features) {
|
||||
const zoneId = feature.properties.id;
|
||||
const originalZone = zones.find(z => z.i === zoneId);
|
||||
const originalZone = zones.find((z) => z.i === zoneId);
|
||||
|
||||
// Verify the zone exists
|
||||
expect(originalZone).toBeDefined();
|
||||
|
|
@ -377,22 +421,26 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
}
|
||||
|
||||
// Verify no hidden zones are in the export
|
||||
const exportedZoneIds = new Set(result.features.map(f => f.properties.id));
|
||||
const hiddenZones = zones.filter(z => z.hidden === true);
|
||||
const exportedZoneIds = new Set(
|
||||
result.features.map((f) => f.properties.id),
|
||||
);
|
||||
const hiddenZones = zones.filter((z) => z.hidden === true);
|
||||
|
||||
for (const hiddenZone of hiddenZones) {
|
||||
expect(exportedZoneIds.has(hiddenZone.i)).toBe(false);
|
||||
}
|
||||
|
||||
// Verify no zones with empty cells are in the export
|
||||
const emptyZones = zones.filter(z => !z.cells || z.cells.length === 0);
|
||||
const emptyZones = zones.filter(
|
||||
(z) => !z.cells || z.cells.length === 0,
|
||||
);
|
||||
|
||||
for (const emptyZone of emptyZones) {
|
||||
expect(exportedZoneIds.has(emptyZone.i)).toBe(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -408,35 +456,54 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
fc.assert(
|
||||
fc.property(
|
||||
// Generate random zones with varying properties
|
||||
fc.array(
|
||||
fc
|
||||
.array(
|
||||
fc.record({
|
||||
i: fc.integer({ min: 0, max: 1000 }),
|
||||
name: fc.string({ minLength: 1, maxLength: 50 }),
|
||||
type: fc.oneof(fc.constant("Unknown"), fc.constant("Territory"), fc.constant("Climate")),
|
||||
type: fc.oneof(
|
||||
fc.constant("Unknown"),
|
||||
fc.constant("Territory"),
|
||||
fc.constant("Climate"),
|
||||
),
|
||||
color: fc.oneof(
|
||||
fc.constant("#ff0000"),
|
||||
fc.constant("#00ff00"),
|
||||
fc.constant("url(#hatch1)")
|
||||
fc.constant("url(#hatch1)"),
|
||||
),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), { minLength: 1, maxLength: 10 }),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), {
|
||||
minLength: 1,
|
||||
maxLength: 10,
|
||||
}),
|
||||
hidden: fc.constant(false), // Only visible zones
|
||||
}),
|
||||
{ minLength: 1, maxLength: 20 }
|
||||
).map(zones => {
|
||||
{ minLength: 1, maxLength: 20 },
|
||||
)
|
||||
.map((zones) => {
|
||||
// Ensure unique zone IDs
|
||||
return zones.map((zone, index) => ({ ...zone, i: index }));
|
||||
}),
|
||||
(zones) => {
|
||||
// Setup mock pack data
|
||||
const mockCells = {
|
||||
v: Array(101).fill(null).map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101).fill(null).map(() => [0, 1, 2]), // Neighbors
|
||||
v: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Neighbors
|
||||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: Array(3).fill(null).map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
v: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
p: Array(3)
|
||||
.fill(null)
|
||||
.map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
v: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
};
|
||||
|
||||
globalThis.pack = {
|
||||
|
|
@ -561,7 +628,9 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
expect(Array.isArray(feature.geometry.coordinates)).toBe(true);
|
||||
|
||||
// Verify at least one coordinate ring exists
|
||||
expect(feature.geometry.coordinates.length).toBeGreaterThanOrEqual(1);
|
||||
expect(feature.geometry.coordinates.length).toBeGreaterThanOrEqual(
|
||||
1,
|
||||
);
|
||||
|
||||
// Verify the first element is a coordinate ring (array of coordinates)
|
||||
const firstRing = feature.geometry.coordinates[0];
|
||||
|
|
@ -576,9 +645,9 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
expect(typeof coord[1]).toBe("number"); // latitude
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -594,35 +663,54 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
fc.assert(
|
||||
fc.property(
|
||||
// Generate random zones with varying properties
|
||||
fc.array(
|
||||
fc
|
||||
.array(
|
||||
fc.record({
|
||||
i: fc.integer({ min: 0, max: 1000 }),
|
||||
name: fc.string({ minLength: 1, maxLength: 50 }),
|
||||
type: fc.oneof(fc.constant("Unknown"), fc.constant("Territory"), fc.constant("Climate")),
|
||||
type: fc.oneof(
|
||||
fc.constant("Unknown"),
|
||||
fc.constant("Territory"),
|
||||
fc.constant("Climate"),
|
||||
),
|
||||
color: fc.oneof(
|
||||
fc.constant("#ff0000"),
|
||||
fc.constant("#00ff00"),
|
||||
fc.constant("url(#hatch1)")
|
||||
fc.constant("url(#hatch1)"),
|
||||
),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), { minLength: 1, maxLength: 10 }),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), {
|
||||
minLength: 1,
|
||||
maxLength: 10,
|
||||
}),
|
||||
hidden: fc.constant(false), // Only visible zones
|
||||
}),
|
||||
{ minLength: 1, maxLength: 20 }
|
||||
).map(zones => {
|
||||
{ minLength: 1, maxLength: 20 },
|
||||
)
|
||||
.map((zones) => {
|
||||
// Ensure unique zone IDs
|
||||
return zones.map((zone, index) => ({ ...zone, i: index }));
|
||||
}),
|
||||
(zones) => {
|
||||
// Setup mock pack data
|
||||
const mockCells = {
|
||||
v: Array(101).fill(null).map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101).fill(null).map(() => [0, 1, 2]), // Neighbors
|
||||
v: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Neighbors
|
||||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: Array(3).fill(null).map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
v: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
p: Array(3)
|
||||
.fill(null)
|
||||
.map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
v: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
};
|
||||
|
||||
globalThis.pack = {
|
||||
|
|
@ -759,9 +847,9 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
expect(firstCoord[1]).toBe(lastCoord[1]); // latitude
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -777,35 +865,54 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
fc.assert(
|
||||
fc.property(
|
||||
// Generate random zones with all required properties
|
||||
fc.array(
|
||||
fc
|
||||
.array(
|
||||
fc.record({
|
||||
i: fc.integer({ min: 0, max: 1000 }),
|
||||
name: fc.string({ minLength: 1, maxLength: 50 }),
|
||||
type: fc.oneof(fc.constant("Unknown"), fc.constant("Territory"), fc.constant("Climate")),
|
||||
type: fc.oneof(
|
||||
fc.constant("Unknown"),
|
||||
fc.constant("Territory"),
|
||||
fc.constant("Climate"),
|
||||
),
|
||||
color: fc.oneof(
|
||||
fc.constant("#ff0000"),
|
||||
fc.constant("#00ff00"),
|
||||
fc.constant("url(#hatch1)")
|
||||
fc.constant("url(#hatch1)"),
|
||||
),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), { minLength: 1, maxLength: 10 }),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), {
|
||||
minLength: 1,
|
||||
maxLength: 10,
|
||||
}),
|
||||
hidden: fc.constant(false), // Only visible zones
|
||||
}),
|
||||
{ minLength: 1, maxLength: 20 }
|
||||
).map(zones => {
|
||||
{ minLength: 1, maxLength: 20 },
|
||||
)
|
||||
.map((zones) => {
|
||||
// Ensure unique zone IDs
|
||||
return zones.map((zone, index) => ({ ...zone, i: index }));
|
||||
}),
|
||||
(zones) => {
|
||||
// Setup mock pack data
|
||||
const mockCells = {
|
||||
v: Array(101).fill(null).map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101).fill(null).map(() => [0, 1, 2]), // Neighbors
|
||||
v: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Neighbors
|
||||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: Array(3).fill(null).map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
v: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
p: Array(3)
|
||||
.fill(null)
|
||||
.map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
v: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
};
|
||||
|
||||
globalThis.pack = {
|
||||
|
|
@ -934,16 +1041,18 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
expect(Array.isArray(feature.properties.cells)).toBe(true);
|
||||
|
||||
// Verify values match input zones
|
||||
const matchingZone = zones.find(z => z.i === feature.properties.id);
|
||||
const matchingZone = zones.find(
|
||||
(z) => z.i === feature.properties.id,
|
||||
);
|
||||
expect(matchingZone).toBeDefined();
|
||||
expect(feature.properties.name).toBe(matchingZone.name);
|
||||
expect(feature.properties.type).toBe(matchingZone.type);
|
||||
expect(feature.properties.color).toBe(matchingZone.color);
|
||||
expect(feature.properties.cells).toEqual(matchingZone.cells);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -959,21 +1068,30 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
fc.assert(
|
||||
fc.property(
|
||||
// Generate random zones with varying properties
|
||||
fc.array(
|
||||
fc
|
||||
.array(
|
||||
fc.record({
|
||||
i: fc.integer({ min: 0, max: 1000 }),
|
||||
name: fc.string({ minLength: 1, maxLength: 50 }),
|
||||
type: fc.oneof(fc.constant("Unknown"), fc.constant("Territory"), fc.constant("Climate")),
|
||||
type: fc.oneof(
|
||||
fc.constant("Unknown"),
|
||||
fc.constant("Territory"),
|
||||
fc.constant("Climate"),
|
||||
),
|
||||
color: fc.oneof(
|
||||
fc.constant("#ff0000"),
|
||||
fc.constant("#00ff00"),
|
||||
fc.constant("url(#hatch1)")
|
||||
fc.constant("url(#hatch1)"),
|
||||
),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), { minLength: 1, maxLength: 10 }),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), {
|
||||
minLength: 1,
|
||||
maxLength: 10,
|
||||
}),
|
||||
hidden: fc.constant(false), // Only visible zones
|
||||
}),
|
||||
{ minLength: 1, maxLength: 20 }
|
||||
).map(zones => {
|
||||
{ minLength: 1, maxLength: 20 },
|
||||
)
|
||||
.map((zones) => {
|
||||
// Ensure unique zone IDs
|
||||
return zones.map((zone, index) => ({ ...zone, i: index }));
|
||||
}),
|
||||
|
|
@ -981,23 +1099,34 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
fc.array(
|
||||
fc.tuple(
|
||||
fc.float({ min: -1000, max: 1000 }),
|
||||
fc.float({ min: -1000, max: 1000 })
|
||||
fc.float({ min: -1000, max: 1000 }),
|
||||
),
|
||||
{ minLength: 3, maxLength: 10 }
|
||||
{ minLength: 3, maxLength: 10 },
|
||||
),
|
||||
(zones, vertexCoords) => {
|
||||
// Setup mock pack data with random vertex coordinates
|
||||
const mockCells = {
|
||||
v: Array(101).fill(null).map((_, i) =>
|
||||
Array.from({ length: Math.min(vertexCoords.length, 10) }, (_, j) => j)
|
||||
v: Array(101)
|
||||
.fill(null)
|
||||
.map((_, _i) =>
|
||||
Array.from(
|
||||
{ length: Math.min(vertexCoords.length, 10) },
|
||||
(_, j) => j,
|
||||
),
|
||||
c: Array(101).fill(null).map(() => [0, 1, 2]),
|
||||
),
|
||||
c: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: vertexCoords,
|
||||
c: Array(vertexCoords.length).fill(null).map(() => [0, 1, 2]),
|
||||
v: Array(vertexCoords.length).fill(null).map(() => [0, 1, 2]),
|
||||
c: Array(vertexCoords.length)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
v: Array(vertexCoords.length)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
};
|
||||
|
||||
globalThis.pack = {
|
||||
|
|
@ -1110,8 +1239,8 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
// Helper function to count decimal places
|
||||
const countDecimals = (num: number): number => {
|
||||
const str = num.toString();
|
||||
if (!str.includes('.')) return 0;
|
||||
return str.split('.')[1].length;
|
||||
if (!str.includes(".")) return 0;
|
||||
return str.split(".")[1].length;
|
||||
};
|
||||
|
||||
// Verify all coordinates have at most 4 decimal places
|
||||
|
|
@ -1163,9 +1292,9 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -1181,36 +1310,55 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
fc.assert(
|
||||
fc.property(
|
||||
// Generate random zones with multiple cells to test merging
|
||||
fc.array(
|
||||
fc
|
||||
.array(
|
||||
fc.record({
|
||||
i: fc.integer({ min: 0, max: 1000 }),
|
||||
name: fc.string({ minLength: 1, maxLength: 50 }),
|
||||
type: fc.oneof(fc.constant("Unknown"), fc.constant("Territory"), fc.constant("Climate")),
|
||||
type: fc.oneof(
|
||||
fc.constant("Unknown"),
|
||||
fc.constant("Territory"),
|
||||
fc.constant("Climate"),
|
||||
),
|
||||
color: fc.oneof(
|
||||
fc.constant("#ff0000"),
|
||||
fc.constant("#00ff00"),
|
||||
fc.constant("url(#hatch1)")
|
||||
fc.constant("url(#hatch1)"),
|
||||
),
|
||||
// Generate zones with multiple cells (2-10 cells per zone)
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), { minLength: 2, maxLength: 10 }),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), {
|
||||
minLength: 2,
|
||||
maxLength: 10,
|
||||
}),
|
||||
hidden: fc.constant(false), // Only visible zones
|
||||
}),
|
||||
{ minLength: 1, maxLength: 20 }
|
||||
).map(zones => {
|
||||
{ minLength: 1, maxLength: 20 },
|
||||
)
|
||||
.map((zones) => {
|
||||
// Ensure unique zone IDs
|
||||
return zones.map((zone, index) => ({ ...zone, i: index }));
|
||||
}),
|
||||
(zones) => {
|
||||
// Setup mock pack data
|
||||
const mockCells = {
|
||||
v: Array(101).fill(null).map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101).fill(null).map(() => [0, 1, 2]), // Neighbors
|
||||
v: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Simple triangular cells
|
||||
c: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]), // Neighbors
|
||||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: Array(3).fill(null).map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
v: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
p: Array(3)
|
||||
.fill(null)
|
||||
.map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
v: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
};
|
||||
|
||||
globalThis.pack = {
|
||||
|
|
@ -1328,7 +1476,10 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
|
||||
for (const feature of result.features) {
|
||||
const zoneId = feature.properties.id;
|
||||
zoneIdToFeatureCount.set(zoneId, (zoneIdToFeatureCount.get(zoneId) || 0) + 1);
|
||||
zoneIdToFeatureCount.set(
|
||||
zoneId,
|
||||
(zoneIdToFeatureCount.get(zoneId) || 0) + 1,
|
||||
);
|
||||
}
|
||||
|
||||
// Verify each zone produces exactly ONE feature
|
||||
|
|
@ -1348,7 +1499,9 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
|
||||
// Verify the geometry structure is a Polygon (array of rings)
|
||||
expect(Array.isArray(feature.geometry.coordinates)).toBe(true);
|
||||
expect(feature.geometry.coordinates.length).toBeGreaterThanOrEqual(1);
|
||||
expect(feature.geometry.coordinates.length).toBeGreaterThanOrEqual(
|
||||
1,
|
||||
);
|
||||
|
||||
// Verify the first element is a coordinate ring (not nested arrays like MultiPolygon)
|
||||
const firstRing = feature.geometry.coordinates[0];
|
||||
|
|
@ -1365,10 +1518,14 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
}
|
||||
|
||||
// Additional verification: zones with multiple cells should still produce single Polygon
|
||||
const multiCellZones = zones.filter(z => !z.hidden && z.cells && z.cells.length > 1);
|
||||
const multiCellZones = zones.filter(
|
||||
(z) => !z.hidden && z.cells && z.cells.length > 1,
|
||||
);
|
||||
|
||||
for (const zone of multiCellZones) {
|
||||
const features = result.features.filter(f => f.properties.id === zone.i);
|
||||
const features = result.features.filter(
|
||||
(f) => f.properties.id === zone.i,
|
||||
);
|
||||
|
||||
// Should have exactly one feature
|
||||
expect(features.length).toBe(1);
|
||||
|
|
@ -1383,9 +1540,9 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
expect(zone.cells.length).toBeGreaterThan(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -1401,31 +1558,46 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
fc.assert(
|
||||
fc.property(
|
||||
// Generate random zones
|
||||
fc.array(
|
||||
fc
|
||||
.array(
|
||||
fc.record({
|
||||
i: fc.integer({ min: 0, max: 1000 }),
|
||||
name: fc.string({ minLength: 1, maxLength: 50 }),
|
||||
type: fc.string({ minLength: 1, maxLength: 20 }),
|
||||
color: fc.string({ minLength: 1, maxLength: 20 }),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), { minLength: 1, maxLength: 10 }),
|
||||
cells: fc.array(fc.integer({ min: 0, max: 100 }), {
|
||||
minLength: 1,
|
||||
maxLength: 10,
|
||||
}),
|
||||
hidden: fc.constant(false),
|
||||
}),
|
||||
{ minLength: 1, maxLength: 10 }
|
||||
).map(zones => {
|
||||
{ minLength: 1, maxLength: 10 },
|
||||
)
|
||||
.map((zones) => {
|
||||
// Ensure unique zone IDs
|
||||
return zones.map((zone, index) => ({ ...zone, i: index }));
|
||||
}),
|
||||
(zones) => {
|
||||
// Setup mock pack data
|
||||
const mockCells = {
|
||||
v: Array(101).fill(null).map(() => [0, 1, 2]),
|
||||
c: Array(101).fill(null).map(() => [0, 1, 2]),
|
||||
v: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
c: Array(101)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
};
|
||||
|
||||
const mockVertices = {
|
||||
p: Array(3).fill(null).map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
v: Array(3).fill(null).map(() => [0, 1, 2]),
|
||||
p: Array(3)
|
||||
.fill(null)
|
||||
.map((_, i) => [i * 10, i * 10]),
|
||||
c: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
v: Array(3)
|
||||
.fill(null)
|
||||
.map(() => [0, 1, 2]),
|
||||
};
|
||||
|
||||
globalThis.pack = {
|
||||
|
|
@ -1544,7 +1716,8 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
expect(globalThis.downloadFile).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Get the call arguments
|
||||
const [data, fileName, mimeType] = vi.mocked(globalThis.downloadFile).mock.calls[0];
|
||||
const [data, fileName, mimeType] = vi.mocked(globalThis.downloadFile)
|
||||
.mock.calls[0];
|
||||
|
||||
// Verify filename pattern
|
||||
expect(fileName).toMatch(/.*_Zones_.*\.geojson$/);
|
||||
|
|
@ -1559,9 +1732,9 @@ describe("zones GeoJSON export - Property-Based Tests", () => {
|
|||
const parsedData = JSON.parse(data);
|
||||
expect(parsedData).toHaveProperty("type", "FeatureCollection");
|
||||
expect(parsedData).toHaveProperty("features");
|
||||
}
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
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