Files
webeditor3d/tests/domain/custom-material.command.test.ts

235 lines
6.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { createEditorStore } from "../../src/app/editor-store";
import { createCreateBoxBrushCommand } from "../../src/commands/create-box-brush-command";
import { createCreateCustomMaterialCommand } from "../../src/commands/create-custom-material-command";
import { createSetCustomMaterialTextureCommand } from "../../src/commands/set-custom-material-texture-command";
import { createUpdateCustomMaterialCommand } from "../../src/commands/update-custom-material-command";
import {
createProjectAssetStorageKey,
type ImageAssetRecord
} from "../../src/assets/project-assets";
import {
BOX_FACE_IDS,
type WhiteboxFaceId
} from "../../src/document/brushes";
import { createCustomMaterialDef } from "../../src/materials/starter-material-library";
function createImageAsset(id: string): ImageAssetRecord {
return {
id,
kind: "image",
sourceName: `${id}.png`,
mimeType: "image/png",
storageKey: createProjectAssetStorageKey(id),
byteLength: 128,
metadata: {
kind: "image",
width: 4,
height: 4,
hasAlpha: false,
warnings: []
}
};
}
describe("custom material commands", () => {
it("creates a custom material and applies it to the selected face with undo/redo", () => {
const store = createEditorStore();
const material = createCustomMaterialDef({
id: "material-custom-face",
name: "Blue Test",
albedoColorHex: "#3366ff"
});
store.executeCommand(createCreateBoxBrushCommand());
const brush = Object.values(store.getState().document.brushes)[0];
store.executeCommand(
createCreateCustomMaterialCommand({
material,
applyTo: {
kind: "face",
brushId: brush.id,
faceId: "posZ"
}
})
);
expect(store.getState().document.materials[material.id]).toMatchObject({
kind: "custom",
name: "Blue Test",
albedoColorHex: "#3366ff"
});
expect(
store.getState().document.brushes[brush.id].faces.posZ.materialId
).toBe(material.id);
expect(store.getState().selection).toEqual({
kind: "brushFace",
brushId: brush.id,
faceId: "posZ"
});
expect(store.undo()).toBe(true);
expect(store.getState().document.materials[material.id]).toBeUndefined();
expect(
store.getState().document.brushes[brush.id].faces.posZ.materialId
).toBeNull();
expect(store.redo()).toBe(true);
expect(store.getState().document.materials[material.id]).toBeDefined();
expect(
store.getState().document.brushes[brush.id].faces.posZ.materialId
).toBe(material.id);
});
it("creates a custom material and applies it to all selected solid faces", () => {
const store = createEditorStore();
const material = createCustomMaterialDef({
id: "material-custom-solid",
name: "Solid Test"
});
store.executeCommand(createCreateBoxBrushCommand());
const brush = Object.values(store.getState().document.brushes)[0];
store.executeCommand(
createCreateCustomMaterialCommand({
material,
applyTo: {
kind: "brush",
brushId: brush.id
}
})
);
for (const faceId of BOX_FACE_IDS) {
expect(
store.getState().document.brushes[brush.id].faces[
faceId as WhiteboxFaceId
].materialId
).toBe(material.id);
}
expect(store.undo()).toBe(true);
for (const faceId of BOX_FACE_IDS) {
expect(
store.getState().document.brushes[brush.id].faces[
faceId as WhiteboxFaceId
].materialId
).toBeNull();
}
});
it("updates custom material scalar settings and restores them on undo", () => {
const store = createEditorStore();
const material = createCustomMaterialDef({
id: "material-custom-update",
roughness: 0.8,
metallic: 0
});
store.executeCommand(
createCreateCustomMaterialCommand({
material
})
);
store.executeCommand(
createUpdateCustomMaterialCommand({
materialId: material.id,
update: {
opacity: 0.45,
roughness: 0.2,
metallic: 0.9,
normalStrength: 0.35
}
})
);
expect(store.getState().document.materials[material.id]).toMatchObject({
opacity: 0.45,
roughness: 0.2,
metallic: 0.9,
normalStrength: 0.35
});
expect(store.undo()).toBe(true);
expect(store.getState().document.materials[material.id]).toMatchObject({
opacity: 1,
roughness: 0.8,
metallic: 0,
normalStrength: 1
});
});
it("imports and assigns image texture assets to custom material slots", () => {
const store = createEditorStore();
const material = createCustomMaterialDef({
id: "material-custom-texture"
});
const imageAsset = createImageAsset("asset-material-albedo");
store.executeCommand(
createCreateCustomMaterialCommand({
material
})
);
store.executeCommand(
createSetCustomMaterialTextureCommand({
materialId: material.id,
slot: "albedo",
assetId: imageAsset.id,
asset: imageAsset
})
);
const assignedMaterial = store.getState().document.materials[material.id];
expect(store.getState().document.assets[imageAsset.id]).toEqual(imageAsset);
expect(assignedMaterial).toMatchObject({
kind: "custom",
textures: {
albedo: {
assetId: imageAsset.id
}
}
});
expect(store.undo()).toBe(true);
expect(store.getState().document.assets[imageAsset.id]).toBeUndefined();
expect(store.getState().document.materials[material.id]).toMatchObject({
textures: {
albedo: null
}
});
expect(store.redo()).toBe(true);
expect(store.getState().document.assets[imageAsset.id]).toEqual(imageAsset);
expect(store.getState().document.materials[material.id]).toMatchObject({
textures: {
albedo: {
assetId: imageAsset.id
}
}
});
});
it("keeps starter materials read-only", () => {
const store = createEditorStore();
expect(() =>
store.executeCommand(
createUpdateCustomMaterialCommand({
materialId: "starter-amber-grid",
update: {
roughness: 0.4
}
})
)
).toThrow("Starter materials are read-only.");
});
});