|
@@ -8,7 +8,9 @@ import {
|
|
|
actionBringForward,
|
|
|
actionBringToFront,
|
|
|
actionSendToBack,
|
|
|
+ actionDuplicateSelection,
|
|
|
} from "../actions";
|
|
|
+import { AppState } from "../types";
|
|
|
import { API } from "./helpers/api";
|
|
|
|
|
|
// Unmount ReactDOM from root
|
|
@@ -22,20 +24,49 @@ beforeEach(() => {
|
|
|
const { h } = window;
|
|
|
|
|
|
const populateElements = (
|
|
|
- elements: { id: string; isDeleted?: boolean; isSelected?: boolean }[],
|
|
|
+ elements: {
|
|
|
+ id: string;
|
|
|
+ isDeleted?: boolean;
|
|
|
+ isSelected?: boolean;
|
|
|
+ groupIds?: string[];
|
|
|
+ y?: number;
|
|
|
+ x?: number;
|
|
|
+ width?: number;
|
|
|
+ height?: number;
|
|
|
+ }[],
|
|
|
) => {
|
|
|
const selectedElementIds: any = {};
|
|
|
|
|
|
- h.elements = elements.map(({ id, isDeleted = false, isSelected = false }) => {
|
|
|
- const element = API.createElement({ type: "rectangle", id, isDeleted });
|
|
|
- if (isSelected) {
|
|
|
- selectedElementIds[element.id] = true;
|
|
|
- }
|
|
|
- return element;
|
|
|
- });
|
|
|
+ h.elements = elements.map(
|
|
|
+ ({
|
|
|
+ id,
|
|
|
+ isDeleted = false,
|
|
|
+ isSelected = false,
|
|
|
+ groupIds = [],
|
|
|
+ y = 100,
|
|
|
+ x = 100,
|
|
|
+ width = 100,
|
|
|
+ height = 100,
|
|
|
+ }) => {
|
|
|
+ const element = API.createElement({
|
|
|
+ type: "rectangle",
|
|
|
+ id,
|
|
|
+ isDeleted,
|
|
|
+ x,
|
|
|
+ y,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ });
|
|
|
+ // @ts-ignore
|
|
|
+ element.groupIds = groupIds;
|
|
|
+ if (isSelected) {
|
|
|
+ selectedElementIds[element.id] = true;
|
|
|
+ }
|
|
|
+ return element;
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
h.setState({
|
|
|
- ...h.state,
|
|
|
selectedElementIds,
|
|
|
});
|
|
|
|
|
@@ -50,12 +81,24 @@ type Actions =
|
|
|
|
|
|
const assertZindex = ({
|
|
|
elements,
|
|
|
+ appState,
|
|
|
operations,
|
|
|
}: {
|
|
|
- elements: { id: string; isDeleted?: true; isSelected?: true }[];
|
|
|
+ elements: {
|
|
|
+ id: string;
|
|
|
+ isDeleted?: true;
|
|
|
+ isSelected?: true;
|
|
|
+ groupIds?: string[];
|
|
|
+ }[];
|
|
|
+ appState?: Partial<AppState>;
|
|
|
operations: [Actions, string[]][];
|
|
|
}) => {
|
|
|
const selectedElementIds = populateElements(elements);
|
|
|
+
|
|
|
+ h.setState({
|
|
|
+ editingGroupId: appState?.editingGroupId || null,
|
|
|
+ });
|
|
|
+
|
|
|
operations.forEach(([action, expected]) => {
|
|
|
h.app.actionManager.executeAction(action);
|
|
|
expect(h.elements.map((element) => element.id)).toEqual(expected);
|
|
@@ -64,9 +107,11 @@ const assertZindex = ({
|
|
|
};
|
|
|
|
|
|
describe("z-index manipulation", () => {
|
|
|
- it("send back", () => {
|
|
|
+ beforeEach(() => {
|
|
|
render(<App />);
|
|
|
+ });
|
|
|
|
|
|
+ it("send back", () => {
|
|
|
assertZindex({
|
|
|
elements: [
|
|
|
{ id: "A" },
|
|
@@ -75,9 +120,21 @@ describe("z-index manipulation", () => {
|
|
|
{ id: "D", isSelected: true },
|
|
|
],
|
|
|
operations: [
|
|
|
- [actionSendBackward, ["B", "C", "D", "A"]],
|
|
|
+ [actionSendBackward, ["D", "A", "B", "C"]],
|
|
|
+ // noop
|
|
|
+ [actionSendBackward, ["D", "A", "B", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", isSelected: true },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ { id: "C", isSelected: true },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
// noop
|
|
|
- [actionSendBackward, ["B", "C", "D", "A"]],
|
|
|
+ [actionSendBackward, ["A", "B", "C"]],
|
|
|
],
|
|
|
});
|
|
|
|
|
@@ -88,7 +145,7 @@ describe("z-index manipulation", () => {
|
|
|
{ id: "C", isDeleted: true },
|
|
|
{ id: "D", isSelected: true },
|
|
|
],
|
|
|
- operations: [[actionSendBackward, ["A", "C", "D", "B"]]],
|
|
|
+ operations: [[actionSendBackward, ["A", "D", "B", "C"]]],
|
|
|
});
|
|
|
|
|
|
assertZindex({
|
|
@@ -98,8 +155,13 @@ describe("z-index manipulation", () => {
|
|
|
{ id: "C", isDeleted: true },
|
|
|
{ id: "D", isSelected: true },
|
|
|
{ id: "E", isSelected: true },
|
|
|
+ { id: "F" },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionSendBackward, ["D", "E", "A", "B", "C", "F"]],
|
|
|
+ // noop
|
|
|
+ [actionSendBackward, ["D", "E", "A", "B", "C", "F"]],
|
|
|
],
|
|
|
- operations: [[actionSendBackward, ["B", "C", "D", "E", "A"]]],
|
|
|
});
|
|
|
|
|
|
assertZindex({
|
|
@@ -113,14 +175,242 @@ describe("z-index manipulation", () => {
|
|
|
{ id: "G", isSelected: true },
|
|
|
],
|
|
|
operations: [
|
|
|
- [actionSendBackward, ["A", "C", "D", "E", "B", "G", "F"]],
|
|
|
- [actionSendBackward, ["C", "D", "E", "A", "G", "B", "F"]],
|
|
|
+ [actionSendBackward, ["A", "E", "B", "C", "D", "G", "F"]],
|
|
|
+ [actionSendBackward, ["E", "A", "G", "B", "C", "D", "F"]],
|
|
|
+ [actionSendBackward, ["E", "G", "A", "B", "C", "D", "F"]],
|
|
|
+ // noop
|
|
|
+ [actionSendBackward, ["E", "G", "A", "B", "C", "D", "F"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B" },
|
|
|
+ { id: "C", isDeleted: true },
|
|
|
+ { id: "D", isSelected: true },
|
|
|
+ { id: "E", isDeleted: true },
|
|
|
+ { id: "F", isSelected: true },
|
|
|
+ { id: "G" },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionSendBackward, ["A", "D", "E", "F", "B", "C", "G"]],
|
|
|
+ [actionSendBackward, ["D", "E", "F", "A", "B", "C", "G"]],
|
|
|
+ // noop
|
|
|
+ [actionSendBackward, ["D", "E", "F", "A", "B", "C", "G"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // grouped elements should be atomic
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g1"] },
|
|
|
+ { id: "C", groupIds: ["g1"] },
|
|
|
+ { id: "D", isDeleted: true },
|
|
|
+ { id: "E", isDeleted: true },
|
|
|
+ { id: "F", isSelected: true },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionSendBackward, ["A", "F", "B", "C", "D", "E"]],
|
|
|
+ [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
|
|
|
+ // noop
|
|
|
+ [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "C", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "D", groupIds: ["g1"] },
|
|
|
+ { id: "E", isDeleted: true },
|
|
|
+ { id: "F", isSelected: true },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionSendBackward, ["A", "F", "B", "C", "D", "E"]],
|
|
|
+ [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
|
|
|
+ // noop
|
|
|
+ [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g1"] },
|
|
|
+ { id: "C", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "D", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "E", isDeleted: true },
|
|
|
+ { id: "F", isSelected: true },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionSendBackward, ["A", "F", "B", "C", "D", "E"]],
|
|
|
+ [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
|
|
|
+ // noop
|
|
|
+ [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B1", groupIds: ["g1"] },
|
|
|
+ { id: "C1", groupIds: ["g1"] },
|
|
|
+ { id: "D2", groupIds: ["g2"], isSelected: true },
|
|
|
+ { id: "E2", groupIds: ["g2"], isSelected: true },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: null,
|
|
|
+ },
|
|
|
+ operations: [[actionSendBackward, ["A", "D2", "E2", "B1", "C1"]]],
|
|
|
+ });
|
|
|
+
|
|
|
+ // in-group siblings
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g1"] },
|
|
|
+ { id: "C", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "D", groupIds: ["g2", "g1"], isSelected: true },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionSendBackward, ["A", "B", "D", "C"]],
|
|
|
+ // noop (prevented)
|
|
|
+ [actionSendBackward, ["A", "B", "D", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "C", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "D", groupIds: ["g1"], isSelected: true },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g1",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionSendBackward, ["A", "D", "B", "C"]],
|
|
|
+ // noop (prevented)
|
|
|
+ [actionSendBackward, ["A", "D", "B", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g1"] },
|
|
|
+ { id: "C", groupIds: ["g2", "g1"], isSelected: true },
|
|
|
+ { id: "D", groupIds: ["g2", "g1"], isDeleted: true },
|
|
|
+ { id: "E", groupIds: ["g2", "g1"], isSelected: true },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g1",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionSendBackward, ["A", "C", "D", "E", "B"]],
|
|
|
+ // noop (prevented)
|
|
|
+ [actionSendBackward, ["A", "C", "D", "E", "B"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g1"] },
|
|
|
+ { id: "C", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "D", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "E", groupIds: ["g3", "g1"], isSelected: true },
|
|
|
+ { id: "F", groupIds: ["g3", "g1"], isSelected: true },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g1",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionSendBackward, ["A", "B", "E", "F", "C", "D"]],
|
|
|
+ [actionSendBackward, ["A", "E", "F", "B", "C", "D"]],
|
|
|
+ // noop (prevented)
|
|
|
+ [actionSendBackward, ["A", "E", "F", "B", "C", "D"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // invalid z-indexes across groups (legacy) → allow to sort to next sibling
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", groupIds: ["g1"] },
|
|
|
+ { id: "B", groupIds: ["g2"] },
|
|
|
+ { id: "C", groupIds: ["g1"] },
|
|
|
+ { id: "D", groupIds: ["g2"], isSelected: true },
|
|
|
+ { id: "E", groupIds: ["g2"], isSelected: true },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionSendBackward, ["A", "D", "E", "B", "C"]],
|
|
|
+ // noop
|
|
|
+ [actionSendBackward, ["A", "D", "E", "B", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // invalid z-indexes across groups (legacy) → allow to sort to next sibling
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", groupIds: ["g1"] },
|
|
|
+ { id: "B", groupIds: ["g2"] },
|
|
|
+ { id: "C", groupIds: ["g1"] },
|
|
|
+ { id: "D", groupIds: ["g2"], isSelected: true },
|
|
|
+ { id: "F" },
|
|
|
+ { id: "G", groupIds: ["g2"], isSelected: true },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionSendBackward, ["A", "D", "G", "B", "C", "F"]],
|
|
|
+ // noop
|
|
|
+ [actionSendBackward, ["A", "D", "G", "B", "C", "F"]],
|
|
|
],
|
|
|
});
|
|
|
});
|
|
|
|
|
|
it("bring forward", () => {
|
|
|
- render(<App />);
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ { id: "C", isSelected: true },
|
|
|
+ { id: "D", isDeleted: true },
|
|
|
+ { id: "E" },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionBringForward, ["A", "D", "E", "B", "C"]],
|
|
|
+ // noop
|
|
|
+ [actionBringForward, ["A", "D", "E", "B", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", isSelected: true },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ { id: "C", isSelected: true },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ // noop
|
|
|
+ [actionBringForward, ["A", "B", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
|
|
|
assertZindex({
|
|
|
elements: [
|
|
@@ -133,17 +423,165 @@ describe("z-index manipulation", () => {
|
|
|
{ id: "G" },
|
|
|
],
|
|
|
operations: [
|
|
|
- [actionBringForward, ["D", "A", "B", "C", "G", "E", "F"]],
|
|
|
- [actionBringForward, ["D", "G", "A", "B", "C", "E", "F"]],
|
|
|
+ [actionBringForward, ["B", "C", "D", "A", "F", "G", "E"]],
|
|
|
+ [actionBringForward, ["B", "C", "D", "F", "G", "A", "E"]],
|
|
|
+ // noop
|
|
|
+ [actionBringForward, ["B", "C", "D", "F", "G", "A", "E"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // grouped elements should be atomic
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", isSelected: true },
|
|
|
+ { id: "B", isDeleted: true },
|
|
|
+ { id: "C", isDeleted: true },
|
|
|
+ { id: "D", groupIds: ["g1"] },
|
|
|
+ { id: "E", groupIds: ["g1"] },
|
|
|
+ { id: "F" },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionBringForward, ["B", "C", "D", "E", "A", "F"]],
|
|
|
+ [actionBringForward, ["B", "C", "D", "E", "F", "A"]],
|
|
|
+ // noop
|
|
|
+ [actionBringForward, ["B", "C", "D", "E", "F", "A"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ { id: "C", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "D", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "E", groupIds: ["g1"] },
|
|
|
+ { id: "F" },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionBringForward, ["A", "C", "D", "E", "B", "F"]],
|
|
|
+ [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
|
|
|
+ // noop
|
|
|
+ [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ { id: "C", groupIds: ["g1"] },
|
|
|
+ { id: "D", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "E", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "F" },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionBringForward, ["A", "C", "D", "E", "B", "F"]],
|
|
|
+ [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
|
|
|
+ // noop
|
|
|
+ [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // in-group siblings
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g2", "g1"], isSelected: true },
|
|
|
+ { id: "C", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "D", groupIds: ["g1"] },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionBringForward, ["A", "C", "B", "D"]],
|
|
|
+ // noop (prevented)
|
|
|
+ [actionBringForward, ["A", "C", "B", "D"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "C", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "D" },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g1",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionBringForward, ["B", "C", "A", "D"]],
|
|
|
+ // noop (prevented)
|
|
|
+ [actionBringForward, ["B", "C", "A", "D"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", groupIds: ["g2", "g1"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g2", "g1"], isSelected: true },
|
|
|
+ { id: "C", groupIds: ["g1"] },
|
|
|
+ { id: "D" },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g1",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionBringForward, ["C", "A", "B", "D"]],
|
|
|
+ // noop (prevented)
|
|
|
+ [actionBringForward, ["C", "A", "B", "D"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // invalid z-indexes across groups (legacy) → allow to sort to next sibling
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", groupIds: ["g2"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g2"], isSelected: true },
|
|
|
+ { id: "C", groupIds: ["g1"] },
|
|
|
+ { id: "D", groupIds: ["g2"] },
|
|
|
+ { id: "E", groupIds: ["g1"] },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionBringForward, ["C", "D", "A", "B", "E"]],
|
|
|
+ // noop
|
|
|
+ [actionBringForward, ["C", "D", "A", "B", "E"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // invalid z-indexes across groups (legacy) → allow to sort to next sibling
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", groupIds: ["g2"], isSelected: true },
|
|
|
+ { id: "B" },
|
|
|
+ { id: "C", groupIds: ["g2"], isSelected: true },
|
|
|
+ { id: "D", groupIds: ["g1"] },
|
|
|
+ { id: "E", groupIds: ["g2"] },
|
|
|
+ { id: "F", groupIds: ["g1"] },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionBringForward, ["B", "D", "E", "A", "C", "F"]],
|
|
|
+ // noop
|
|
|
+ [actionBringForward, ["B", "D", "E", "A", "C", "F"]],
|
|
|
],
|
|
|
});
|
|
|
});
|
|
|
|
|
|
it("bring to front", () => {
|
|
|
- render(<App />);
|
|
|
-
|
|
|
assertZindex({
|
|
|
elements: [
|
|
|
+ { id: "0" },
|
|
|
{ id: "A", isSelected: true },
|
|
|
{ id: "B", isDeleted: true },
|
|
|
{ id: "C", isDeleted: true },
|
|
@@ -152,13 +590,130 @@ describe("z-index manipulation", () => {
|
|
|
{ id: "F", isDeleted: true },
|
|
|
{ id: "G" },
|
|
|
],
|
|
|
- operations: [[actionBringToFront, ["D", "G", "A", "B", "C", "E", "F"]]],
|
|
|
+ operations: [
|
|
|
+ [actionBringToFront, ["0", "B", "C", "D", "F", "G", "A", "E"]],
|
|
|
+ // noop
|
|
|
+ [actionBringToFront, ["0", "B", "C", "D", "F", "G", "A", "E"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", isSelected: true },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ { id: "C", isSelected: true },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ // noop
|
|
|
+ [actionBringToFront, ["A", "B", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ { id: "C", isSelected: true },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ // noop
|
|
|
+ [actionBringToFront, ["A", "B", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", isSelected: true },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ { id: "C" },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionBringToFront, ["C", "A", "B"]],
|
|
|
+ // noop
|
|
|
+ [actionBringToFront, ["C", "A", "B"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // in-group sorting
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g1"] },
|
|
|
+ { id: "C", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "D", groupIds: ["g1"] },
|
|
|
+ { id: "E", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "F", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "G", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "H", groupIds: ["g3", "g1"] },
|
|
|
+ { id: "I", groupIds: ["g3", "g1"] },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g1",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionBringToFront, ["A", "B", "D", "F", "G", "H", "I", "C", "E"]],
|
|
|
+ // noop (prevented)
|
|
|
+ [actionBringToFront, ["A", "B", "D", "F", "G", "H", "I", "C", "E"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g2", "g1"], isSelected: true },
|
|
|
+ { id: "D", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "C", groupIds: ["g1"] },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionBringToFront, ["A", "D", "B", "C"]],
|
|
|
+ // noop (prevented)
|
|
|
+ [actionBringToFront, ["A", "D", "B", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // invalid z-indexes across groups (legacy) → allow to sort to next sibling
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", groupIds: ["g2", "g3"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g1", "g3"] },
|
|
|
+ { id: "C", groupIds: ["g2", "g3"] },
|
|
|
+ { id: "D", groupIds: ["g1", "g3"] },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionBringToFront, ["B", "C", "A", "D"]],
|
|
|
+ // noop
|
|
|
+ [actionBringToFront, ["B", "C", "A", "D"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // invalid z-indexes across groups (legacy) → allow to sort to next sibling
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", groupIds: ["g2"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g1"] },
|
|
|
+ { id: "C", groupIds: ["g2"] },
|
|
|
+ { id: "D", groupIds: ["g1"] },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionBringToFront, ["B", "C", "A", "D"]],
|
|
|
+ // noop
|
|
|
+ [actionBringToFront, ["B", "C", "A", "D"]],
|
|
|
+ ],
|
|
|
});
|
|
|
});
|
|
|
|
|
|
it("send to back", () => {
|
|
|
- render(<App />);
|
|
|
-
|
|
|
assertZindex({
|
|
|
elements: [
|
|
|
{ id: "A" },
|
|
@@ -168,8 +723,334 @@ describe("z-index manipulation", () => {
|
|
|
{ id: "E", isSelected: true },
|
|
|
{ id: "F", isDeleted: true },
|
|
|
{ id: "G" },
|
|
|
+ { id: "H", isSelected: true },
|
|
|
+ { id: "I" },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionSendToBack, ["E", "H", "A", "B", "C", "D", "F", "G", "I"]],
|
|
|
+ // noop
|
|
|
+ [actionSendToBack, ["E", "H", "A", "B", "C", "D", "F", "G", "I"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", isSelected: true },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ { id: "C", isSelected: true },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ // noop
|
|
|
+ [actionSendToBack, ["A", "B", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", isSelected: true },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ { id: "C" },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ // noop
|
|
|
+ [actionSendToBack, ["A", "B", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ { id: "C", isSelected: true },
|
|
|
+ ],
|
|
|
+ operations: [
|
|
|
+ [actionSendToBack, ["B", "C", "A"]],
|
|
|
+ // noop
|
|
|
+ [actionSendToBack, ["B", "C", "A"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // in-group sorting
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "C", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "D", groupIds: ["g3", "g1"] },
|
|
|
+ { id: "E", groupIds: ["g3", "g1"] },
|
|
|
+ { id: "F", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "G", groupIds: ["g1"] },
|
|
|
+ { id: "H", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "I", groupIds: ["g1"] },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g1",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionSendToBack, ["A", "F", "H", "B", "C", "D", "E", "G", "I"]],
|
|
|
+ // noop (prevented)
|
|
|
+ [actionSendToBack, ["A", "F", "H", "B", "C", "D", "E", "G", "I"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B", groupIds: ["g1"] },
|
|
|
+ { id: "C", groupIds: ["g2", "g1"] },
|
|
|
+ { id: "D", groupIds: ["g2", "g1"], isSelected: true },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionSendToBack, ["A", "B", "D", "C"]],
|
|
|
+ // noop (prevented)
|
|
|
+ [actionSendToBack, ["A", "B", "D", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // invalid z-indexes across groups (legacy) → allow to sort to next sibling
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", groupIds: ["g1", "g3"] },
|
|
|
+ { id: "B", groupIds: ["g2", "g3"] },
|
|
|
+ { id: "C", groupIds: ["g1", "g3"] },
|
|
|
+ { id: "D", groupIds: ["g2", "g3"], isSelected: true },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionSendToBack, ["A", "D", "B", "C"]],
|
|
|
+ // noop
|
|
|
+ [actionSendToBack, ["A", "D", "B", "C"]],
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ // invalid z-indexes across groups (legacy) → allow to sort to next sibling
|
|
|
+ assertZindex({
|
|
|
+ elements: [
|
|
|
+ { id: "A", groupIds: ["g1"] },
|
|
|
+ { id: "B", groupIds: ["g2"] },
|
|
|
+ { id: "C", groupIds: ["g1"] },
|
|
|
+ { id: "D", groupIds: ["g2"], isSelected: true },
|
|
|
+ ],
|
|
|
+ appState: {
|
|
|
+ editingGroupId: "g2",
|
|
|
+ },
|
|
|
+ operations: [
|
|
|
+ [actionSendToBack, ["A", "D", "B", "C"]],
|
|
|
+ // noop
|
|
|
+ [actionSendToBack, ["A", "D", "B", "C"]],
|
|
|
],
|
|
|
- operations: [[actionSendToBack, ["D", "E", "A", "B", "C", "F", "G"]]],
|
|
|
});
|
|
|
});
|
|
|
+
|
|
|
+ it("duplicating elements should retain zindex integrity", () => {
|
|
|
+ populateElements([
|
|
|
+ { id: "A", isSelected: true },
|
|
|
+ { id: "B", isSelected: true },
|
|
|
+ ]);
|
|
|
+ h.app.actionManager.executeAction(actionDuplicateSelection);
|
|
|
+ expect(h.elements).toMatchObject([
|
|
|
+ { id: "A" },
|
|
|
+ { id: "A_copy" },
|
|
|
+ { id: "B" },
|
|
|
+ { id: "B_copy" },
|
|
|
+ ]);
|
|
|
+
|
|
|
+ populateElements([
|
|
|
+ { id: "A", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g1"], isSelected: true },
|
|
|
+ ]);
|
|
|
+ h.setState({
|
|
|
+ selectedGroupIds: { g1: true },
|
|
|
+ });
|
|
|
+ h.app.actionManager.executeAction(actionDuplicateSelection);
|
|
|
+ expect(h.elements).toMatchObject([
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B" },
|
|
|
+ {
|
|
|
+ id: "A_copy",
|
|
|
+
|
|
|
+ groupIds: [expect.stringMatching(/.{3,}/)],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: "B_copy",
|
|
|
+
|
|
|
+ groupIds: [expect.stringMatching(/.{3,}/)],
|
|
|
+ },
|
|
|
+ ]);
|
|
|
+
|
|
|
+ populateElements([
|
|
|
+ { id: "A", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "C" },
|
|
|
+ ]);
|
|
|
+ h.setState({
|
|
|
+ selectedGroupIds: { g1: true },
|
|
|
+ });
|
|
|
+ h.app.actionManager.executeAction(actionDuplicateSelection);
|
|
|
+ expect(h.elements).toMatchObject([
|
|
|
+ { id: "A" },
|
|
|
+ { id: "B" },
|
|
|
+ {
|
|
|
+ id: "A_copy",
|
|
|
+
|
|
|
+ groupIds: [expect.stringMatching(/.{3,}/)],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: "B_copy",
|
|
|
+
|
|
|
+ groupIds: [expect.stringMatching(/.{3,}/)],
|
|
|
+ },
|
|
|
+ { id: "C" },
|
|
|
+ ]);
|
|
|
+
|
|
|
+ populateElements([
|
|
|
+ { id: "A", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "C", isSelected: true },
|
|
|
+ ]);
|
|
|
+ h.setState({
|
|
|
+ selectedGroupIds: { g1: true },
|
|
|
+ });
|
|
|
+ h.app.actionManager.executeAction(actionDuplicateSelection);
|
|
|
+ expect(h.elements.map((element) => element.id)).toEqual([
|
|
|
+ "A",
|
|
|
+ "B",
|
|
|
+ "A_copy",
|
|
|
+ "B_copy",
|
|
|
+ "C",
|
|
|
+ "C_copy",
|
|
|
+ ]);
|
|
|
+
|
|
|
+ populateElements([
|
|
|
+ { id: "A", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g1"], isSelected: true },
|
|
|
+ { id: "C", groupIds: ["g2"], isSelected: true },
|
|
|
+ { id: "D", groupIds: ["g2"], isSelected: true },
|
|
|
+ ]);
|
|
|
+ h.setState({
|
|
|
+ selectedGroupIds: { g1: true, g2: true },
|
|
|
+ });
|
|
|
+ h.app.actionManager.executeAction(actionDuplicateSelection);
|
|
|
+ expect(h.elements.map((element) => element.id)).toEqual([
|
|
|
+ "A",
|
|
|
+ "B",
|
|
|
+ "A_copy",
|
|
|
+ "B_copy",
|
|
|
+ "C",
|
|
|
+ "D",
|
|
|
+ "C_copy",
|
|
|
+ "D_copy",
|
|
|
+ ]);
|
|
|
+
|
|
|
+ populateElements([
|
|
|
+ { id: "A", groupIds: ["g1", "g2"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g1", "g2"], isSelected: true },
|
|
|
+ { id: "C", groupIds: ["g2"], isSelected: true },
|
|
|
+ ]);
|
|
|
+ h.setState({
|
|
|
+ selectedGroupIds: { g1: true },
|
|
|
+ });
|
|
|
+ h.app.actionManager.executeAction(actionDuplicateSelection);
|
|
|
+ expect(h.elements.map((element) => element.id)).toEqual([
|
|
|
+ "A",
|
|
|
+ "B",
|
|
|
+ "A_copy",
|
|
|
+ "B_copy",
|
|
|
+ "C",
|
|
|
+ "C_copy",
|
|
|
+ ]);
|
|
|
+
|
|
|
+ populateElements([
|
|
|
+ { id: "A", groupIds: ["g1", "g2"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g1", "g2"], isSelected: true },
|
|
|
+ { id: "C", groupIds: ["g2"], isSelected: true },
|
|
|
+ ]);
|
|
|
+ h.setState({
|
|
|
+ selectedGroupIds: { g2: true },
|
|
|
+ });
|
|
|
+ h.app.actionManager.executeAction(actionDuplicateSelection);
|
|
|
+ expect(h.elements.map((element) => element.id)).toEqual([
|
|
|
+ "A",
|
|
|
+ "B",
|
|
|
+ "C",
|
|
|
+ "A_copy",
|
|
|
+ "B_copy",
|
|
|
+ "C_copy",
|
|
|
+ ]);
|
|
|
+
|
|
|
+ populateElements([
|
|
|
+ { id: "A", groupIds: ["g1", "g2"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g1", "g2"], isSelected: true },
|
|
|
+ { id: "C", groupIds: ["g2"], isSelected: true },
|
|
|
+ { id: "D", groupIds: ["g3", "g4"], isSelected: true },
|
|
|
+ { id: "E", groupIds: ["g3", "g4"], isSelected: true },
|
|
|
+ { id: "F", groupIds: ["g4"], isSelected: true },
|
|
|
+ ]);
|
|
|
+ h.setState({
|
|
|
+ selectedGroupIds: { g2: true, g4: true },
|
|
|
+ });
|
|
|
+ h.app.actionManager.executeAction(actionDuplicateSelection);
|
|
|
+ expect(h.elements.map((element) => element.id)).toEqual([
|
|
|
+ "A",
|
|
|
+ "B",
|
|
|
+ "C",
|
|
|
+ "A_copy",
|
|
|
+ "B_copy",
|
|
|
+ "C_copy",
|
|
|
+ "D",
|
|
|
+ "E",
|
|
|
+ "F",
|
|
|
+ "D_copy",
|
|
|
+ "E_copy",
|
|
|
+ "F_copy",
|
|
|
+ ]);
|
|
|
+
|
|
|
+ populateElements([
|
|
|
+ { id: "A", groupIds: ["g1", "g2"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g1", "g2"] },
|
|
|
+ { id: "C", groupIds: ["g2"] },
|
|
|
+ ]);
|
|
|
+ h.app.actionManager.executeAction(actionDuplicateSelection);
|
|
|
+ expect(h.elements.map((element) => element.id)).toEqual([
|
|
|
+ "A",
|
|
|
+ "A_copy",
|
|
|
+ "B",
|
|
|
+ "C",
|
|
|
+ ]);
|
|
|
+
|
|
|
+ populateElements([
|
|
|
+ { id: "A", groupIds: ["g1", "g2"] },
|
|
|
+ { id: "B", groupIds: ["g1", "g2"], isSelected: true },
|
|
|
+ { id: "C", groupIds: ["g2"] },
|
|
|
+ ]);
|
|
|
+ h.app.actionManager.executeAction(actionDuplicateSelection);
|
|
|
+ expect(h.elements.map((element) => element.id)).toEqual([
|
|
|
+ "A",
|
|
|
+ "B",
|
|
|
+ "B_copy",
|
|
|
+ "C",
|
|
|
+ ]);
|
|
|
+
|
|
|
+ populateElements([
|
|
|
+ { id: "A", groupIds: ["g1", "g2"], isSelected: true },
|
|
|
+ { id: "B", groupIds: ["g1", "g2"], isSelected: true },
|
|
|
+ { id: "C", groupIds: ["g2"], isSelected: true },
|
|
|
+ ]);
|
|
|
+ h.app.actionManager.executeAction(actionDuplicateSelection);
|
|
|
+ expect(h.elements.map((element) => element.id)).toEqual([
|
|
|
+ "A",
|
|
|
+ "A_copy",
|
|
|
+ "B",
|
|
|
+ "B_copy",
|
|
|
+ "C",
|
|
|
+ "C_copy",
|
|
|
+ ]);
|
|
|
+ });
|
|
|
});
|