Refactor App component and update terrain-related tests

This commit is contained in:
2026-05-13 13:59:03 +02:00
parent 6f64954ee0
commit d702cdfdf9
3 changed files with 101 additions and 50 deletions

View File

@@ -2816,12 +2816,11 @@ export function App({
terrainList terrainList
); );
const selectedPath = getSelectedPath(editorState.selection, pathList); const selectedPath = getSelectedPath(editorState.selection, pathList);
const splineCorridorJunctionCandidates = const splineCorridorJunctionCandidates = detectSplineCorridorJunctionCandidates({
detectSplineCorridorJunctionCandidates({ paths: pathList,
paths: pathList, junctions: editorState.document.splineCorridorJunctions,
junctions: editorState.document.splineCorridorJunctions, terrains: terrainList.filter((terrain) => terrain.enabled)
terrains: terrainList.filter((terrain) => terrain.enabled) });
});
const selectedPathJunctionCandidates = const selectedPathJunctionCandidates =
selectedPath === null selectedPath === null
? [] ? []
@@ -9651,7 +9650,10 @@ export function App({
const handleTerrainPaintLayerChange = (value: string) => { const handleTerrainPaintLayerChange = (value: string) => {
setActiveTerrainPaintLayerIndex( setActiveTerrainPaintLayerIndex(
clampTerrainPaintLayerIndex(Number(value), selectedTerrain?.layers.length) clampTerrainPaintLayerIndex(
Number(value),
selectedTerrain?.layers.length
)
); );
}; };
@@ -9992,9 +9994,7 @@ export function App({
const handleDeleteSplineCorridorJunction = (junctionId: string) => { const handleDeleteSplineCorridorJunction = (junctionId: string) => {
try { try {
store.executeCommand( store.executeCommand(createDeleteSplineCorridorJunctionCommand(junctionId));
createDeleteSplineCorridorJunctionCommand(junctionId)
);
setStatusMessage("Deleted spline corridor junction."); setStatusMessage("Deleted spline corridor junction.");
} catch (error) { } catch (error) {
setStatusMessage(getErrorMessage(error)); setStatusMessage(getErrorMessage(error));
@@ -21491,10 +21491,7 @@ export function App({
<div className="terrain-layer-list"> <div className="terrain-layer-list">
{selectedTerrain.layers.map((layer, layerIndex) => ( {selectedTerrain.layers.map((layer, layerIndex) => (
<div key={layerIndex} className="form-field"> <div key={layerIndex} className="form-field">
<label <label className="label" htmlFor={`terrain-layer-material-${layerIndex}`}>
className="label"
htmlFor={`terrain-layer-material-${layerIndex}`}
>
{getTerrainLayerLabel(layerIndex)} {getTerrainLayerLabel(layerIndex)}
</label> </label>
<div className="inline-actions"> <div className="inline-actions">
@@ -21521,9 +21518,7 @@ export function App({
type="button" type="button"
data-testid={`terrain-layer-remove-${layerIndex}`} data-testid={`terrain-layer-remove-${layerIndex}`}
disabled={layerIndex === 0} disabled={layerIndex === 0}
onClick={() => onClick={() => handleDeleteTerrainLayer(layerIndex)}
handleDeleteTerrainLayer(layerIndex)
}
> >
Remove Remove
</button> </button>
@@ -22493,9 +22488,10 @@ export function App({
handlePathRoadChange( handlePathRoadChange(
{ {
...selectedPath.road, ...selectedPath.road,
heightOffset: normalizeScenePathRoadHeightOffset( heightOffset:
Number(event.currentTarget.value) normalizeScenePathRoadHeightOffset(
) Number(event.currentTarget.value)
)
}, },
"Updated Path road height offset." "Updated Path road height offset."
) )
@@ -22549,8 +22545,7 @@ export function App({
</label> </label>
{(["left", "right"] as const).map((side) => { {(["left", "right"] as const).map((side) => {
const edge = selectedPath.road.edges[side]; const edge = selectedPath.road.edges[side];
const edgeLabel = const edgeLabel = side === "left" ? "Left Edge" : "Right Edge";
side === "left" ? "Left Edge" : "Right Edge";
return ( return (
<div className="form-section" key={side}> <div className="form-section" key={side}>
@@ -22722,7 +22717,10 @@ export function App({
{selectedPathJunctionCandidates {selectedPathJunctionCandidates
.slice(0, 6) .slice(0, 6)
.map((candidate, candidateIndex) => ( .map((candidate, candidateIndex) => (
<div className="compact-card" key={candidate.id}> <div
className="compact-card"
key={candidate.id}
>
<div className="compact-card__header"> <div className="compact-card__header">
<strong> <strong>
Candidate {candidateIndex + 1} Candidate {candidateIndex + 1}
@@ -23339,8 +23337,8 @@ export function App({
Edit authored point positions directly. Keep at least{" "} Edit authored point positions directly. Keep at least{" "}
{MIN_SCENE_PATH_POINT_COUNT} points. <code>Shift+W</code>{" "} {MIN_SCENE_PATH_POINT_COUNT} points. <code>Shift+W</code>{" "}
adds a point after the selected control point, or appends adds a point after the selected control point, or appends
to the end when the whole path is selected. When a point to the end when the whole path is selected. When a point is
is selected, <code>Shift+D</code> adds one and starts grab selected, <code>Shift+D</code> adds one and starts grab
mode. mode.
</div> </div>
{selectedPathPointIndex === null ? null : ( {selectedPathPointIndex === null ? null : (

View File

@@ -100,12 +100,12 @@ describe("terrain grid resizing", () => {
cellSize: 0.5 cellSize: 0.5
}); });
expect( expect(sampleTerrainHeightAtLocalPosition(resizedTerrain, 2.5, 3.5)).toBeCloseTo(
sampleTerrainHeightAtLocalPosition(resizedTerrain, 2.5, 3.5) sampleTerrainHeightAtLocalPosition(terrain, 2.5, 3.5) ?? 0
).toBeCloseTo(sampleTerrainHeightAtLocalPosition(terrain, 2.5, 3.5) ?? 0); );
expect( expect(sampleTerrainHeightAtLocalPosition(resizedTerrain, 6, 1)).toBeCloseTo(
sampleTerrainHeightAtLocalPosition(resizedTerrain, 6, 1) sampleTerrainHeightAtLocalPosition(terrain, 6, 1) ?? 0
).toBeCloseTo(sampleTerrainHeightAtLocalPosition(terrain, 6, 1) ?? 0); );
}); });
it("preserves paint weights at representative local positions when Cell Size changes", () => { it("preserves paint weights at representative local positions when Cell Size changes", () => {
@@ -123,11 +123,7 @@ describe("terrain grid resizing", () => {
cellSize: 0.5 cellSize: 0.5
}); });
const originalOffset = getTerrainPaintWeightSampleOffset(terrain, 4, 2); const originalOffset = getTerrainPaintWeightSampleOffset(terrain, 4, 2);
const resizedOffset = getTerrainPaintWeightSampleOffset( const resizedOffset = getTerrainPaintWeightSampleOffset(resizedTerrain, 8, 4);
resizedTerrain,
8,
4
);
expect(resizedTerrain.paintWeights[resizedOffset]).toBeCloseTo( expect(resizedTerrain.paintWeights[resizedOffset]).toBeCloseTo(
terrain.paintWeights[originalOffset] ?? 0 terrain.paintWeights[originalOffset] ?? 0
@@ -296,7 +292,20 @@ describe("terrain material layers", () => {
id: "terrain-add-layer", id: "terrain-add-layer",
sampleCountX: 2, sampleCountX: 2,
sampleCountZ: 2, sampleCountZ: 2,
paintWeights: [0.2, 0.3, 0.1, 0.4, 0.1, 0, 0, 0.5, 0, 0.25, 0.25, 0.25] paintWeights: [
0.2,
0.3,
0.1,
0.4,
0.1,
0,
0,
0.5,
0,
0.25,
0.25,
0.25
]
}); });
const nextTerrain = createTerrainWithAddedLayer(terrain, "road-material"); const nextTerrain = createTerrainWithAddedLayer(terrain, "road-material");
@@ -313,12 +322,18 @@ describe("terrain material layers", () => {
expect(nextTerrain.layers).toHaveLength(5); expect(nextTerrain.layers).toHaveLength(5);
expect(nextTerrain.layers[4]).toEqual({ materialId: "road-material" }); expect(nextTerrain.layers[4]).toEqual({ materialId: "road-material" });
expect( expect(nextTerrain.paintWeights.slice(firstSampleOffset, firstSampleOffset + 4)).toEqual([
nextTerrain.paintWeights.slice(firstSampleOffset, firstSampleOffset + 4) 0.2,
).toEqual([0.2, 0.3, 0.1, 0]); 0.3,
expect( 0.1,
nextTerrain.paintWeights.slice(secondSampleOffset, secondSampleOffset + 4) 0
).toEqual([0.4, 0.1, 0, 0]); ]);
expect(nextTerrain.paintWeights.slice(secondSampleOffset, secondSampleOffset + 4)).toEqual([
0.4,
0.1,
0,
0
]);
}); });
it("removes non-base layers by compacting explicit weights", () => { it("removes non-base layers by compacting explicit weights", () => {
@@ -327,7 +342,20 @@ describe("terrain material layers", () => {
id: "terrain-remove-layer", id: "terrain-remove-layer",
sampleCountX: 2, sampleCountX: 2,
sampleCountZ: 2, sampleCountZ: 2,
paintWeights: [0.2, 0.3, 0.1, 0.4, 0.1, 0, 0, 0.5, 0, 0.25, 0.25, 0.25] paintWeights: [
0.2,
0.3,
0.1,
0.4,
0.1,
0,
0,
0.5,
0,
0.25,
0.25,
0.25
]
}), }),
"fifth-material" "fifth-material"
); );
@@ -336,7 +364,18 @@ describe("terrain material layers", () => {
expect(nextTerrain.layers).toHaveLength(4); expect(nextTerrain.layers).toHaveLength(4);
expect(nextTerrain.paintWeights).toEqual([ expect(nextTerrain.paintWeights).toEqual([
0.2, 0.1, 0, 0.4, 0, 0, 0, 0, 0, 0.25, 0.25, 0 0.2,
0.1,
0,
0.4,
0,
0,
0,
0,
0,
0.25,
0.25,
0
]); ]);
}); });
}); });
@@ -407,13 +446,28 @@ describe("terrain foliage masks", () => {
expect(clonedTerrain.foliageMasks[foliageLayerId]).not.toBe(mask); expect(clonedTerrain.foliageMasks[foliageLayerId]).not.toBe(mask);
expect(getTerrainFoliageMaskValueAtSample(mask!, 1, 1)).toBe(0.5); expect(getTerrainFoliageMaskValueAtSample(mask!, 1, 1)).toBe(0.5);
expect( expect(
sampleTerrainFoliageMaskAtWorldPosition(terrain, foliageLayerId, 11, 21) sampleTerrainFoliageMaskAtWorldPosition(
terrain,
foliageLayerId,
11,
21
)
).toBeCloseTo(0.375); ).toBeCloseTo(0.375);
expect( expect(
sampleTerrainFoliageMaskAtWorldPosition(terrain, "missing-layer", 11, 21) sampleTerrainFoliageMaskAtWorldPosition(
terrain,
"missing-layer",
11,
21
)
).toBe(0); ).toBe(0);
expect( expect(
sampleTerrainFoliageMaskAtWorldPosition(terrain, foliageLayerId, 99, 99) sampleTerrainFoliageMaskAtWorldPosition(
terrain,
foliageLayerId,
99,
99
)
).toBeNull(); ).toBeNull();
}); });
}); });

View File

@@ -173,8 +173,7 @@ describe("Terrain foundation", () => {
fireEvent.click(screen.getByTestId("terrain-grid-apply")); fireEvent.click(screen.getByTestId("terrain-grid-apply"));
await waitFor(() => { await waitFor(() => {
const updatedTerrain = const updatedTerrain = store.getState().document.terrains[createdTerrain.id];
store.getState().document.terrains[createdTerrain.id];
expect(updatedTerrain?.sampleCountX).toBe(5); expect(updatedTerrain?.sampleCountX).toBe(5);
expect(updatedTerrain?.sampleCountZ).toBe(5); expect(updatedTerrain?.sampleCountZ).toBe(5);