235 lines
6.3 KiB
TypeScript
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.");
|
||
|
|
});
|
||
|
|
});
|