mirror of
https://github.com/igorski/bitmappery.git
synced 2026-06-17 03:34:56 +02:00
Split `definitions/document.ts` into unique files per actor type. Create `model` folder to store the above types, their factories and the state changing actions.
858 lines
38 KiB
TypeScript
858 lines
38 KiB
TypeScript
import { it, afterEach, beforeEach, describe, expect, vi } from "vitest";
|
|
import { mockZCanvas, createMockCanvasElement, createMockZoomableCanvas } from "../../mocks";
|
|
import { type Layer } from "@/model/types/layer";
|
|
import { LayerTypes } from "@/definitions/layer-types";
|
|
import DocumentFactory from "@/model/factories/document-factory";
|
|
import LayerFactory from "@/model/factories/layer-factory";
|
|
import DocumentModule, { createDocumentState, type DocumentState } from "@/store/modules/document-module";
|
|
import LayerRenderer from "@/rendering/actors/layer-renderer";
|
|
|
|
const { getters, mutations, actions } = DocumentModule;
|
|
|
|
mockZCanvas();
|
|
|
|
let mockUpdateFn: ( fnName: string, ...args: any[]) => void;
|
|
vi.mock( "@/model/factories/renderer-factory", () => ({
|
|
flushLayerRenderers: vi.fn(( ...args: any[]) => mockUpdateFn?.( "flushLayerRenderers", ...args )),
|
|
runRendererFn: vi.fn(( ...args: any[]) => mockUpdateFn?.( "runRendererFn", ...args )),
|
|
getRendererForLayer: vi.fn(( ...args: any[]) => mockUpdateFn?.( "getRendererForLayer", ...args )),
|
|
createRendererForLayer: vi.fn(( ...args: any[]) => mockUpdateFn?.( "createRendererForLayer", ...args )),
|
|
}));
|
|
const mockFlushBlendedLayerCache = vi.fn();
|
|
vi.mock( "@/rendering/cache/blended-layer-cache", async ( importOriginal ) => {
|
|
return {
|
|
...await importOriginal(),
|
|
flushBlendedLayerCache: vi.fn(( ...args: any[] ) => mockFlushBlendedLayerCache( ...args )),
|
|
}
|
|
});
|
|
const mockCreateLayerThumbnail = vi.fn();
|
|
const mockFlushThumbnailCache = vi.fn();
|
|
const mockFlushThumbnailForLayer = vi.fn();
|
|
vi.mock( "@/rendering/cache/thumbnail-cache", () => ({
|
|
createLayerThumbnail: vi.fn(( ...args: any[] ) => mockCreateLayerThumbnail( ...args )),
|
|
flushThumbnailCache: vi.fn(( ...args: any[] ) => mockFlushThumbnailCache( ...args )),
|
|
flushThumbnailForLayer: vi.fn(( ...args: any[] ) => mockFlushThumbnailForLayer( ...args )),
|
|
}));
|
|
const mockFlushTileCacheFn = vi.fn();
|
|
vi.mock( "@/rendering/cache/tile-cache", () => ({
|
|
flushTileCache: vi.fn(( ...args: any[] ) => mockFlushTileCacheFn( ...args )),
|
|
}));
|
|
const mockCanvasInstance = createMockZoomableCanvas();
|
|
vi.mock( "@/services/canvas-service", () => ({
|
|
getCanvasInstance: vi.fn(( ...args: any[]) => {
|
|
mockUpdateFn?.( "getCanvasInstance", ...args );
|
|
return mockCanvasInstance;
|
|
}),
|
|
}));
|
|
vi.mock( "@/utils/layer-util", async ( importOriginal ) => ({
|
|
...await importOriginal(),
|
|
resizeLayerContent: vi.fn((...args: any[]) => mockUpdateFn?.( "resizeLayerContent", ...args )),
|
|
cropLayerContent: vi.fn((...args: any[]) => mockUpdateFn?.( "cropLayerContent", ...args )),
|
|
}));
|
|
|
|
describe( "Vuex document module", () => {
|
|
afterEach(() => {
|
|
vi.resetAllMocks();
|
|
});
|
|
|
|
describe( "getters", () => {
|
|
it( "should be able to retrieve all open Documents", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ name: "foo" }),
|
|
DocumentFactory.create({ name: "bar" })
|
|
]
|
|
});
|
|
expect( getters.documents( state, getters, {}, {} )).toEqual( state.documents );
|
|
});
|
|
|
|
it( "should be able to retrieve the active Document", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ name: "foo" }),
|
|
DocumentFactory.create({ name: "bar" })
|
|
],
|
|
activeIndex: 0
|
|
});
|
|
expect( getters.activeDocument( state, getters, {}, {} )).toEqual( state.documents[ 0 ]);
|
|
state.activeIndex = 1;
|
|
expect( getters.activeDocument( state, getters, {}, {} )).toEqual( state.documents[ 1 ]);
|
|
});
|
|
|
|
it( "should be able to retrieve the Layers for the active Document", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({
|
|
name: "foo",
|
|
layers: [
|
|
LayerFactory.create({ name: "foo" }),
|
|
LayerFactory.create({ name: "bar" })
|
|
]
|
|
})
|
|
],
|
|
activeIndex: 0
|
|
});
|
|
expect( getters.layers( state, getters, {}, {} )).toEqual( state.documents[ 0 ].layers );
|
|
});
|
|
|
|
it( "should be able to retrieve the active Layer index", () => {
|
|
const state = createDocumentState({ activeLayerIndex: 2 });
|
|
expect( getters.activeLayerIndex( state, getters, {}, {} )).toEqual( 2 );
|
|
});
|
|
|
|
it( "should be able to retrieve the active Layer for the active Document", () => {
|
|
const state = createDocumentState({ activeLayerIndex: 1 });
|
|
const mockedGetters = {
|
|
layers: [
|
|
LayerFactory.create({ name: "layer1" }),
|
|
LayerFactory.create({ name: "layer2" }),
|
|
LayerFactory.create({ name: "layer3" })
|
|
]
|
|
};
|
|
expect( getters.activeLayer( state, mockedGetters, {}, {} )).toEqual( mockedGetters.layers[ 1 ]);
|
|
});
|
|
|
|
it( "should be able to retrieve the active Layer mask, when set", () => {
|
|
const state = createDocumentState({ maskActive: false });
|
|
const mockedGetters = {
|
|
activeLayer: LayerFactory.create({ name: "layer1" }),
|
|
};
|
|
// null because mask is not active
|
|
expect( getters.activeLayerMask( state, mockedGetters, {}, {} )).toBeUndefined();
|
|
state.maskActive = true;
|
|
// null because layer has no mask drawable
|
|
expect( getters.activeLayerMask( state, mockedGetters, {}, {} )).toBeUndefined();
|
|
mockedGetters.activeLayer.mask = createMockCanvasElement();
|
|
expect( getters.activeLayerMask( state, mockedGetters, {}, {} )).toEqual( mockedGetters.activeLayer.mask );
|
|
});
|
|
|
|
it( "should be able to retrieve the active Layer transform", () => {
|
|
const mockedGetters = { activeLayer: { name: "layer1", transform: [{ rotation: 1 }] } };
|
|
expect( getters.activeLayerTransform( createDocumentState(), mockedGetters, {}, {} )).toEqual( mockedGetters.activeLayer.transform );
|
|
});
|
|
|
|
it( "should know whether the current Document has an active selection", () => {
|
|
const mockedGetters = { activeDocument: DocumentFactory.create({ activeSelection: [] }) };
|
|
expect( getters.hasSelection( createDocumentState(), mockedGetters, {}, {} )).toBe( false );
|
|
|
|
mockedGetters.activeDocument.activeSelection = [ [] ];
|
|
expect( getters.hasSelection( createDocumentState(), mockedGetters, {}, {} )).toBe( false );
|
|
|
|
mockedGetters.activeDocument.activeSelection = [[ { x: 0, y: 0 }] ];
|
|
expect( getters.hasSelection( createDocumentState(), mockedGetters, {}, {} )).toBe( true );
|
|
});
|
|
});
|
|
|
|
describe( "mutations", () => {
|
|
describe( "when setting the active Document", () => {
|
|
it( "should be able to set the active Document index", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ name: "foo", layers: [ LayerFactory.create(), LayerFactory.create() ] }),
|
|
DocumentFactory.create({ name: "bar", layers: [ LayerFactory.create(), LayerFactory.create(), LayerFactory.create()] })
|
|
],
|
|
activeIndex: 0,
|
|
activeLayerIndex: 1,
|
|
});
|
|
mutations.setActiveDocument( state, 1 );
|
|
expect( state.activeIndex ).toEqual( 1 );
|
|
expect( state.activeLayerIndex ).toEqual( 1 );
|
|
});
|
|
|
|
it( "when switching to a Document with less layers than the currently active one, it should select the top layer", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ name: "foo", layers: [ LayerFactory.create(), LayerFactory.create() ] }),
|
|
DocumentFactory.create({ name: "bar", layers: [ LayerFactory.create(), LayerFactory.create(), LayerFactory.create() ] })
|
|
],
|
|
activeIndex: 1,
|
|
activeLayerIndex: 2,
|
|
});
|
|
mutations.setActiveDocument( state, 0 );
|
|
expect( state.activeIndex ).toEqual( 0 );
|
|
expect( state.activeLayerIndex ).toEqual( 1 );
|
|
});
|
|
|
|
it( "should request the invalidate() method on each renderer for the given Document", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ name: "foo" }),
|
|
DocumentFactory.create({ name: "bar" })
|
|
],
|
|
activeIndex: 0
|
|
});
|
|
mockUpdateFn = vi.fn();
|
|
mutations.setActiveDocument( state, 1 );
|
|
expect( mockUpdateFn ).toHaveBeenCalledWith( "runRendererFn", expect.any( Function ), state.documents[ 1 ]);
|
|
});
|
|
});
|
|
|
|
it( "should be able to update the active Document name", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ name: "foo", width: 5, height: 5 }),
|
|
DocumentFactory.create({ name: "bar", width: 10, height: 10 })
|
|
],
|
|
activeIndex: 1,
|
|
});
|
|
const [ document1, document2 ] = state.documents;
|
|
|
|
mutations.setActiveDocumentName( state, "baz" );
|
|
|
|
expect( state.documents ).toEqual([
|
|
document1, {
|
|
...document2,
|
|
name: "baz",
|
|
}
|
|
]);
|
|
});
|
|
|
|
describe( "when setting the active Document size", () => {
|
|
it( "should be able to update the active Document size", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ name: "foo", width: 30, height: 30 }),
|
|
DocumentFactory.create({ name: "bar", width: 50, height: 50 }),
|
|
],
|
|
activeIndex : 1,
|
|
});
|
|
const [ document1, document2 ] = state.documents;
|
|
|
|
const size = { width: 75, height: 40 };
|
|
mutations.setActiveDocumentSize( state, size );
|
|
|
|
expect( state.documents ).toEqual([
|
|
document1,
|
|
{ ...document2, width: size.width, height: size.height },
|
|
]);
|
|
});
|
|
|
|
it( "should update the existing zCanvas dimensions and trigger its associated rescale handler", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ name: "foo", width: 30, height: 30, layers: [ LayerFactory.create({ name: "layer1", width: 30, height: 30 }) ] }),
|
|
DocumentFactory.create({ name: "bar", width: 50, height: 50, layers: [ LayerFactory.create({ name: "layer2", width: 20, height: 10 }), LayerFactory.create({ name: "layer3", width: 15, height: 15 }) ] }),
|
|
],
|
|
activeIndex : 1,
|
|
});
|
|
const size = { width: 75, height: 40 };
|
|
|
|
mutations.setActiveDocumentSize( state, size );
|
|
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 1, "getCanvasInstance" );
|
|
expect( mockCanvasInstance.setDimensions ).toHaveBeenCalledWith( size.width, size.height, true, true );
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 2, "getCanvasInstance" );
|
|
expect( mockCanvasInstance.rescaleFn ).toHaveBeenCalled();
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 3, "getCanvasInstance" );
|
|
expect( mockCanvasInstance.refreshFn ).toHaveBeenCalled();
|
|
});
|
|
|
|
it( "should flush the thumbnail and tile caches", () => {
|
|
const state = createDocumentState({
|
|
documents: [ DocumentFactory.create() ],
|
|
activeIndex : 0,
|
|
});
|
|
|
|
mutations.setActiveDocumentSize( state, { width: 500, height: 500 });
|
|
|
|
expect( mockFlushThumbnailCache ).toHaveBeenCalled();
|
|
expect( mockFlushTileCacheFn ).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it( "should be able to set the active selection for the currently active document", () => {
|
|
const selection = [{ x: 0, y: 0 }, { x: 10, y: 10 }];
|
|
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ name: "foo", activeSelection: [] }),
|
|
DocumentFactory.create({ name: "bar", activeSelection: [] })
|
|
],
|
|
activeIndex: 1
|
|
});
|
|
mutations.setActiveSelection( state, selection );
|
|
|
|
expect( state.documents[ 0 ].activeSelection ).toHaveLength( 0 );
|
|
expect( state.documents[ 1 ].activeSelection ).toEqual( selection );
|
|
});
|
|
|
|
it( "should be able to add a new Document to the list", () => {
|
|
const state = createDocumentState({
|
|
documents: [ DocumentFactory.create({ name: "foo" }) ],
|
|
activeIndex: 0,
|
|
activeLayerIndex: 2,
|
|
});
|
|
mutations.addNewDocument( state, "bar" );
|
|
expect( state.documents ).toHaveLength( 2 );
|
|
expect( state.documents[ 1 ].name ).toEqual( "bar" );
|
|
expect( state.activeIndex ).toEqual( 1 );
|
|
expect( state.activeLayerIndex ).toEqual( 0 );
|
|
});
|
|
|
|
it( "should be able to close the active Document", () => {
|
|
const layer1 = LayerFactory.create({ name: "layer1" });
|
|
const layer2 = LayerFactory.create({ name: "layer2" });
|
|
const layer3 = LayerFactory.create({ name: "layer3" });
|
|
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ name: "foo", layers: [ layer1 ] }),
|
|
DocumentFactory.create({ name: "bar", layers: [ layer2, layer3 ] }),
|
|
],
|
|
activeIndex: 1
|
|
});
|
|
const [ document1 ] = state.documents;
|
|
|
|
mockUpdateFn = vi.fn();
|
|
mutations.closeActiveDocument( state );
|
|
|
|
expect( state.documents ).toEqual([ document1 ]);
|
|
expect( state.activeIndex ).toEqual( 0 );
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 1, "flushLayerRenderers", layer2 );
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 2, "flushLayerRenderers", layer3 );
|
|
});
|
|
|
|
describe( "when adding layers", () => {
|
|
const layerCreateSpy = vi.spyOn( LayerFactory, "create" );
|
|
|
|
afterEach(() => {
|
|
layerCreateSpy.mockClear();
|
|
});
|
|
|
|
it( "should be able to add a Layer to the active Document", () => {
|
|
const state = createDocumentState({
|
|
documents: [ DocumentFactory.create({ name: "foo", width: 1000, height: 1000 }) ],
|
|
activeIndex: 0
|
|
});
|
|
|
|
const layerOpts = { name: "layer1", width: 50, height: 100 };
|
|
mutations.addLayer( state, layerOpts );
|
|
|
|
// assert LayerFactory is invoked with provided opts when calling addLayer()
|
|
expect( layerCreateSpy ).toHaveBeenCalledWith( layerOpts );
|
|
|
|
const addedLayer = state.documents[ 0 ].layers[ 1 ];
|
|
expect( addedLayer.name ).toEqual( "layer1" );
|
|
expect( addedLayer.width ).toEqual( 50 );
|
|
expect( addedLayer.height ).toEqual( 100 );
|
|
});
|
|
|
|
it( "when adding a Layer without specified dimensions, these should default to the Document dimensions", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ name: "foo", width: 1000, height: 1000 })
|
|
],
|
|
activeIndex: 0
|
|
});
|
|
mockUpdateFn = vi.fn((_fn, data) => data );
|
|
|
|
const layerOpts = { name: "layer1" };
|
|
mutations.addLayer( state, layerOpts );
|
|
|
|
const mutatedLayer = state.documents[ 0 ].layers[ 1 ];
|
|
|
|
expect( mutatedLayer.name ).toEqual( layerOpts.name );
|
|
expect( mutatedLayer.width ).toEqual( state.documents[ 0 ].width );
|
|
expect( mutatedLayer.height ).toEqual( state.documents[ 0 ].height );
|
|
});
|
|
|
|
it( "should update the active layer index to the last added layers index", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({
|
|
name: "foo",
|
|
layers: [
|
|
LayerFactory.create({ name: "layer1" }),
|
|
LayerFactory.create({ name: "layer2" })
|
|
]
|
|
})
|
|
],
|
|
activeIndex: 0,
|
|
activeLayerIndex: 1
|
|
});
|
|
mutations.addLayer( state );
|
|
expect( state.activeLayerIndex ).toEqual( 2 );
|
|
});
|
|
|
|
it( "should add new layers at the end of the list (to have them appear on top)", () => {
|
|
const layer1 = LayerFactory.create({ name: "layer1" });
|
|
const layer2 = LayerFactory.create({ name: "layer2" });
|
|
|
|
const state = createDocumentState({
|
|
documents: [ DocumentFactory.create({ name: "foo", layers: [ layer1 ] }) ],
|
|
activeIndex: 0
|
|
});
|
|
mutations.addLayer( state, layer2 );
|
|
|
|
expect( state.documents[ 0 ].layers ).toEqual([ layer1, layer2 ]);
|
|
});
|
|
|
|
it( "should be able to add layers at specific indices in the layer list", () => {
|
|
const layer1 = LayerFactory.create({ name: "layer1" });
|
|
const layer2 = LayerFactory.create({ name: "layer2" });
|
|
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({
|
|
name: "foo",
|
|
layers: [ layer1, layer2 ],
|
|
})
|
|
],
|
|
activeIndex: 0,
|
|
activeLayerIndex: 0,
|
|
});
|
|
const layerToInsert = LayerFactory.create({ name: "layer3" });
|
|
mutations.insertLayerAtIndex( state, { index: 1, layer: layerToInsert });
|
|
|
|
expect( state.documents[ 0 ].layers ).toEqual([ layer1, layerToInsert, layer2 ]);
|
|
expect( state.activeLayerIndex ).toEqual( 1 );
|
|
});
|
|
});
|
|
|
|
describe( "when removing layers", () => {
|
|
let state: DocumentState;
|
|
let layer1: Layer;
|
|
let layer2: Layer;
|
|
let layer3: Layer;
|
|
|
|
beforeEach(() => {
|
|
state = createDocumentState({
|
|
documents: [ DocumentFactory.create({
|
|
name: "foo",
|
|
layers: [
|
|
LayerFactory.create({ name: "layer1" }),
|
|
LayerFactory.create({ name: "layer2" }),
|
|
LayerFactory.create({ name: "layer3" })
|
|
]
|
|
})],
|
|
activeIndex: 0,
|
|
activeLayerIndex: 1,
|
|
});
|
|
([ layer1, layer2, layer3 ] = state.documents[ 0 ].layers );
|
|
});
|
|
|
|
it( "should be able to remove a layer by reference", () => {
|
|
mutations.removeLayer( state, 1 );
|
|
|
|
expect( state.documents[ 0 ].layers ).toEqual([ layer1, layer3 ]);
|
|
});
|
|
|
|
it( "should set the active layer index to the first Layer", () => {
|
|
mutations.removeLayer( state, 1 );
|
|
|
|
expect( state.activeLayerIndex ).toEqual( 0 );
|
|
});
|
|
|
|
it( "should flush the Layer renderers", () => {
|
|
mockUpdateFn = vi.fn();
|
|
|
|
mutations.removeLayer( state, 1 );
|
|
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 1, "flushLayerRenderers", layer2 );
|
|
});
|
|
|
|
it( "should flush the blended layer cache fully", () => {
|
|
mutations.removeLayer( state, 1 );
|
|
|
|
expect( mockFlushBlendedLayerCache ).toHaveBeenCalledWith( true );
|
|
});
|
|
|
|
it( "should flush the thumbnail cache for the layer", () => {
|
|
mutations.removeLayer( state, 1 );
|
|
|
|
expect( mockFlushThumbnailForLayer ).toHaveBeenLastCalledWith( layer2 );
|
|
});
|
|
});
|
|
|
|
it( "should be able to replace all layers in a single operation", () => {
|
|
const state = createDocumentState({
|
|
documents: [ DocumentFactory.create({
|
|
name: "foo",
|
|
layers: [
|
|
LayerFactory.create({ name: "layer1" }),
|
|
LayerFactory.create({ name: "layer2" }),
|
|
LayerFactory.create({ name: "layer3" })
|
|
]
|
|
})],
|
|
activeIndex: 0,
|
|
activeLayerIndex: 1,
|
|
});
|
|
const newLayers = [
|
|
LayerFactory.create({ name: "layer4" }),
|
|
LayerFactory.create({ name: "layer5" }),
|
|
];
|
|
|
|
mutations.replaceLayers( state, newLayers );
|
|
|
|
expect( state.documents[ 0 ].layers ).toEqual( newLayers );
|
|
});
|
|
|
|
it( "should be able to swap the layers in the currently active Document", () => {
|
|
const fooLayer1 = LayerFactory.create({ name: "fooLayer1" });
|
|
const fooLayer2 = LayerFactory.create({ name: "fooLayer2" });
|
|
const barLayer1 = LayerFactory.create({ name: "barLayer1" });
|
|
const barLayer2 = LayerFactory.create({ name: "barLayer2" });
|
|
const barLayer3 = LayerFactory.create({ name: "barLayer3" });
|
|
const barLayer4 = LayerFactory.create({ name: "barLayer4" });
|
|
|
|
const document1 = DocumentFactory.create({ name: "foo", layers: [ fooLayer1, fooLayer2 ] });
|
|
const document2 = DocumentFactory.create({ name: "bar", layers: [ barLayer1, barLayer2, barLayer3, barLayer4 ] });
|
|
|
|
const state = createDocumentState({
|
|
documents: [ document1, document2 ],
|
|
activeIndex: 1
|
|
});
|
|
|
|
mutations.swapLayers( state, { index1: 1, index2: 3 });
|
|
|
|
expect( state.documents ).toEqual([
|
|
{ ...document1, layers: [ fooLayer1, fooLayer2 ] },
|
|
{ ...document2, layers: [ barLayer1, barLayer4, barLayer3, barLayer2 ]},
|
|
]);
|
|
});
|
|
|
|
it( "should be able to reorder all layers in the currently active Document", () => {
|
|
const layers = [
|
|
LayerFactory.create({ name: "layer1" }),
|
|
LayerFactory.create({ name: "layer2" }),
|
|
LayerFactory.create({ name: "layer3" }),
|
|
LayerFactory.create({ name: "layer4" }),
|
|
];
|
|
const orgLayers = [ ...layers ];
|
|
const state = createDocumentState({
|
|
documents: [ DocumentFactory.create({ name: "foo", layers })],
|
|
activeIndex: 0
|
|
});
|
|
mutations.reorderLayers( state, { activeDocument: state.documents[ 0 ], layerIds: [
|
|
layers[ 1 ].id, layers[ 2 ].id, layers[ 0 ].id, layers[ 3 ].id
|
|
] });
|
|
// note we check by reference to ensure all bindings remain
|
|
expect( state.documents[ 0 ].layers ).toEqual([
|
|
orgLayers[ 1 ], orgLayers[ 2 ], orgLayers[ 0 ], orgLayers[ 3 ]
|
|
]);
|
|
expect( mockFlushBlendedLayerCache ).toHaveBeenCalledWith( true );
|
|
});
|
|
|
|
describe( "when setting the active layer content", () => {
|
|
it( "should be able to set the active layer by index", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({
|
|
name: "foo",
|
|
layers: [
|
|
LayerFactory.create({ name: "layer1" }),
|
|
LayerFactory.create({ name: "layer2" })
|
|
]
|
|
})
|
|
],
|
|
activeIndex: 0,
|
|
activeLayerIndex: 0
|
|
});
|
|
mutations.setActiveLayerIndex( state, 1 );
|
|
expect( state.activeLayerIndex ).toEqual( 1 );
|
|
});
|
|
|
|
it( "should be able to set the active layer by reference", () => {
|
|
const state = createDocumentState({
|
|
documents: [ DocumentFactory.create({
|
|
name: "foo",
|
|
layers: [
|
|
LayerFactory.create({ name: "layer1" }),
|
|
LayerFactory.create({ name: "layer2" })
|
|
]
|
|
})],
|
|
activeIndex: 0,
|
|
activeLayerIndex: 0
|
|
});
|
|
mutations.setActiveLayer( state, state.documents[ 0 ].layers[ 1 ] );
|
|
expect( state.activeLayerIndex ).toEqual( 1 );
|
|
});
|
|
|
|
it( "should unset the active layer mask when setting the active layer index", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({
|
|
name: "foo",
|
|
layers: [
|
|
LayerFactory.create({ name: "layer1" }),
|
|
LayerFactory.create({ name: "layer2" })
|
|
]
|
|
})
|
|
],
|
|
activeLayerIndex: 0,
|
|
maskActive: true,
|
|
});
|
|
mutations.setActiveLayerIndex( state, 1 );
|
|
expect( state.maskActive ).toBe( false );
|
|
});
|
|
|
|
it( "should be able to set the active layer mask", () => {
|
|
const state = createDocumentState({
|
|
documents: [ DocumentFactory.create({
|
|
name: "foo",
|
|
layers: [
|
|
LayerFactory.create({ name: "layer1" }),
|
|
LayerFactory.create({ name: "layer2", mask: createMockCanvasElement()})
|
|
]
|
|
})],
|
|
activeIndex: 0,
|
|
activeLayerIndex: 0,
|
|
maskActive: false,
|
|
});
|
|
mutations.setActiveLayerMask( state, 1 );
|
|
expect( state.activeLayerIndex ).toEqual( 1 );
|
|
expect( state.maskActive ).toBe( true );
|
|
});
|
|
});
|
|
|
|
describe( "when updating Layer properties", () => {
|
|
let layer1: Layer;
|
|
let layer2: Layer;
|
|
let state: DocumentState;
|
|
|
|
beforeEach(() => {
|
|
layer1 = LayerFactory.create({ name: "layer1", transform: { rotation: 0 } });
|
|
layer2 = LayerFactory.create({ name: "layer2", transform: { rotation: 0 } });
|
|
|
|
state = createDocumentState({
|
|
documents: [ DocumentFactory.create({
|
|
name: "foo",
|
|
layers: [ layer1, layer2 ]
|
|
})],
|
|
activeIndex: 0
|
|
});
|
|
});
|
|
|
|
it( "should be able to update the options of a specific Layer within the active Document", () => {
|
|
const index = 1;
|
|
const opts = {
|
|
name: "layer2 updated",
|
|
x: 100,
|
|
y: 200,
|
|
width: 100,
|
|
height: 150,
|
|
type: LayerTypes.LAYER_IMAGE
|
|
};
|
|
const layerRenderer = new LayerRenderer( layer2 );
|
|
const cacheEffectsSpy = vi.spyOn( layerRenderer, "cacheEffects" );
|
|
|
|
mockUpdateFn = vi.fn( fn => {
|
|
if ( fn === "getRendererForLayer" ) return layerRenderer;
|
|
return true;
|
|
});
|
|
mutations.updateLayer( state, { index, opts });
|
|
expect( state.documents[ 0 ].layers[ index ] ).toEqual({
|
|
...layer2,
|
|
...opts
|
|
});
|
|
expect( mockUpdateFn ).toHaveBeenCalledWith( "getRendererForLayer", state.documents[ 0 ].layers[ index ] );
|
|
expect( cacheEffectsSpy ).toHaveBeenCalled();
|
|
});
|
|
|
|
it( "should be able to update the source image of a specific layer within the active Document, invoking a filter recache on the renderer", () => {
|
|
const index = 1;
|
|
const opts = {
|
|
name: "layer2 updated",
|
|
source: new Image(),
|
|
type: LayerTypes.LAYER_IMAGE
|
|
};
|
|
const layerRenderer = new LayerRenderer( layer2 );
|
|
const resetAndRecacheSpy = vi.spyOn( layerRenderer, "resetFilterAndRecache" );
|
|
|
|
mockUpdateFn = vi.fn( fn => {
|
|
if ( fn === "getRendererForLayer" ) return layerRenderer;
|
|
return true;
|
|
});
|
|
mutations.updateLayer( state, { index, opts });
|
|
expect( resetAndRecacheSpy ).toHaveBeenCalled();
|
|
});
|
|
|
|
it( "should not flush the blended layer cache when no filter properties were updated", () => {
|
|
const index = 0;
|
|
const opts = { name: "layer1 updated" };
|
|
const layerRenderer = new LayerRenderer( layer1 );
|
|
|
|
mockUpdateFn = vi.fn( fn => {
|
|
if ( fn === "getRendererForLayer" ) return layerRenderer;
|
|
return true;
|
|
});
|
|
mutations.updateLayer( state, { index, opts });
|
|
|
|
expect( mockFlushBlendedLayerCache ).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it( "should flush the blended layer cache fully when filter properties are updated to ensure correct rendering on history state changes", () => {
|
|
const index = 0;
|
|
const opts = { filters: { gamma: 1 } };
|
|
const layerRenderer = new LayerRenderer( layer1 );
|
|
|
|
mockUpdateFn = vi.fn( fn => {
|
|
if ( fn === "getRendererForLayer" ) return layerRenderer;
|
|
return true;
|
|
});
|
|
mutations.updateLayer( state, { index, opts });
|
|
|
|
expect( mockFlushBlendedLayerCache ).toHaveBeenCalledWith( true );
|
|
});
|
|
|
|
describe( "when requesting to also recreate the renderer for the specific Layer", () => {
|
|
it( "should not do anything related to renderer lifecycle when no recreation request was provided", () => {
|
|
const index = 0;
|
|
const opts = { filters: { gamma: 1 } };
|
|
|
|
mutations.updateLayer( state, { index, opts, recreateRenderer: false });
|
|
|
|
const layer = state.documents[ 0 ].layers[ index ]; // the layer after mutation
|
|
|
|
expect( mockCanvasInstance.setLock ).not.toHaveBeenCalled();
|
|
expect( mockUpdateFn ).not.toHaveBeenCalledWith( "flushLayerRenderers", layer );
|
|
expect( mockUpdateFn ).not.toHaveBeenCalledWith( "createRendererForLayer", mockCanvasInstance, layer, true );
|
|
});
|
|
|
|
it( "should lock the canvas, flush the Layers renderer and create a new renderer instance when a recreation request was provided", () => {
|
|
const index = 0;
|
|
const opts = { filters: { gamma: 1 } };
|
|
|
|
mutations.updateLayer( state, { index, opts, recreateRenderer: true });
|
|
|
|
const layer = state.documents[ 0 ].layers[ index ]; // the layer after mutation
|
|
|
|
expect( mockCanvasInstance.setLock ).toHaveBeenCalledWith( true );
|
|
expect( mockUpdateFn ).toHaveBeenCalledWith( "flushLayerRenderers", layer );
|
|
expect( mockUpdateFn ).toHaveBeenCalledWith( "createRendererForLayer", mockCanvasInstance, layer, true );
|
|
});
|
|
});
|
|
|
|
it( "should be able to update the transform of a specific Layer within the active Document", () => {
|
|
const index = 0;
|
|
const transform = { rotation: 1.6 };
|
|
const layerRenderer = new LayerRenderer( layer1 );
|
|
const invalidateBlendCacheSpy = vi.spyOn( layerRenderer, "invalidateBlendCache" );
|
|
|
|
mockUpdateFn = vi.fn( fn => {
|
|
if ( fn === "getRendererForLayer" ) return layerRenderer;
|
|
return true;
|
|
});
|
|
mutations.updateLayerTransform( state, { index, transform });
|
|
expect( state.documents[ 0 ].layers[ index ] ).toEqual({
|
|
...layer1,
|
|
transform: {
|
|
...layer1.transform,
|
|
...transform,
|
|
}
|
|
});
|
|
expect( mockUpdateFn ).toHaveBeenCalledWith( "getRendererForLayer", state.documents[ 0 ].layers[ index ] );
|
|
expect( invalidateBlendCacheSpy ).toHaveBeenCalled();
|
|
expect( mockCreateLayerThumbnail ).toHaveBeenCalledWith( layer1, state.documents[ 0 ], true );
|
|
});
|
|
});
|
|
|
|
it( "should be able to resize active Document content by calling the render util upon each Layer", async () => {
|
|
const layer1 = LayerFactory.create({ name: "layer1" });
|
|
const layer2 = LayerFactory.create({ name: "layer2" });
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ layers: [ layer1, layer2 ]}),
|
|
],
|
|
activeIndex: 0,
|
|
});
|
|
mockUpdateFn = vi.fn();
|
|
const scaleX = 1.1;
|
|
const scaleY = 1.2;
|
|
|
|
await mutations.resizeActiveDocumentContent( state, { scaleX, scaleY });
|
|
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 1, "resizeLayerContent", layer1, scaleX, scaleY );
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 2, "resizeLayerContent", layer2, scaleX, scaleY );
|
|
});
|
|
|
|
it( "should be able to crop active Document content by calling the render util upon each Layer", async () => {
|
|
const layer1 = LayerFactory.create({ name: "layer1" });
|
|
const layer2 = LayerFactory.create({ name: "layer2" });
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({ layers: [ layer1, layer2 ]}),
|
|
],
|
|
activeIndex: 0,
|
|
});
|
|
mockUpdateFn = vi.fn();
|
|
const left = 10;
|
|
const top = 15;
|
|
|
|
await mutations.cropActiveDocumentContent( state, { left, top });
|
|
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 1, "cropLayerContent", layer1, { left, top });
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 2, "getRendererForLayer", layer1 );
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 3, "cropLayerContent", layer2, { left, top });
|
|
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 4, "getRendererForLayer", layer2 );
|
|
});
|
|
|
|
it( "should be able to update the Document Groups", () => {
|
|
const state = createDocumentState({
|
|
documents: [ DocumentFactory.create() ],
|
|
activeIndex: 0,
|
|
});
|
|
|
|
mutations.updateGroups( state, [ 0, 1, 2 ]);
|
|
|
|
expect( state.documents[ 0 ].groups ).toEqual([ 0, 1, 2 ]);
|
|
});
|
|
|
|
it( "should be able to update the Documents metadata using partial updates", () => {
|
|
const state = createDocumentState({
|
|
documents: [
|
|
DocumentFactory.create({
|
|
meta: {
|
|
dpi: 150,
|
|
unit: "px",
|
|
}
|
|
}),
|
|
],
|
|
activeIndex: 0,
|
|
});
|
|
|
|
mutations.updateMeta( state, {
|
|
fps: 10,
|
|
});
|
|
|
|
expect( state.documents[ 0 ].meta ).toEqual({
|
|
dpi: 150,
|
|
fps: 10,
|
|
unit: "px",
|
|
swatches: [],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe( "actions", () => {
|
|
it( "should be able to flush all resources allocated to a Document when closing", () => {
|
|
const state = createDocumentState({
|
|
documents: [ DocumentFactory.create(), DocumentFactory.create() ],
|
|
activeIndex: 0,
|
|
});
|
|
const commit = vi.fn();
|
|
const getters = {
|
|
activeDocument: state.documents[ 0 ],
|
|
t: vi.fn(),
|
|
};
|
|
|
|
// @ts-expect-error Not all constituents of type 'Action<HistoryState, any>' are callable.
|
|
actions.requestDocumentClose({ state, commit, getters });
|
|
|
|
expect( commit ).toHaveBeenCalledTimes( 1 );
|
|
expect( commit ).toHaveBeenCalledWith( "openDialog", expect.any( Object ));
|
|
|
|
// grab the Dialog window request actions and confirm Document close
|
|
const { confirm } = commit.mock.calls[ 0 ][ 1 ];
|
|
confirm();
|
|
|
|
expect( commit ).toHaveBeenCalledTimes( 5 );
|
|
|
|
expect( commit ).toHaveBeenCalledWith( "closeActiveDocument" );
|
|
expect( commit ).toHaveBeenCalledWith( "removeImagesForDocument", getters.activeDocument );
|
|
expect( commit ).toHaveBeenCalledWith( "setActiveDocument", 0 );
|
|
expect( commit ).toHaveBeenCalledWith( "clearHistory", getters.activeDocument.id );
|
|
});
|
|
});
|
|
});
|