Skip to content

Commit

Permalink
9244: move/copy mod component
Browse files Browse the repository at this point in the history
Drop invalid page editor page object method

Fix CreateModModalBody logic
  • Loading branch information
twschiller committed Oct 7, 2024
1 parent 07137af commit 9c49084
Show file tree
Hide file tree
Showing 16 changed files with 466 additions and 507 deletions.
9 changes: 6 additions & 3 deletions end-to-end-tests/pageObjects/pageEditor/createModModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import { BasePageObject } from "../basePageObject";
import { type UUID } from "@/types/stringTypes";
import { ModifiesModFormState } from "./utils";
import { uuidv4 } from "@/types/helpers";

export class CreateModModal extends BasePageObject {
modIdInput = this.getByTestId("registryId-id-id");
Expand All @@ -27,11 +28,13 @@ export class CreateModModal extends BasePageObject {
/**
* Creates a mod using the Create Mod modal, with the given modId and modName.
* @param modName the modName to use
* @param modUuid the UUID of the mod component from adding the starter brick
* @param modUuid an optional UUID to force the modId to be unique, if not provided a random UUID will be generated
*/
@ModifiesModFormState
async createMod(modName: string, modUuid: UUID): Promise<string> {
const modId = `${modName.split(" ").join("-").toLowerCase()}-${modUuid}`;
async createMod(modName: string, modUuid?: UUID): Promise<string> {
const modId = `${modName.split(" ").join("-").toLowerCase()}-${
modUuid ?? uuidv4()
}`;

await this.modIdInput.fill(modId);
await this.modNameInput.fill(modName);
Expand Down
93 changes: 58 additions & 35 deletions end-to-end-tests/pageObjects/pageEditor/pageEditorPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import { uuidv4 } from "@/types/helpers";

class EditorPane extends BasePageObject {
editTab = this.getByRole("tab", { name: "Edit" });
logsTab = this.getByRole("tab", { name: "Logs" });

runTriggerButton = this.getByRole("button", { name: "Run Trigger" });
autoRunTrigger = this.getSwitchByLabel("Auto-Run");
Expand Down Expand Up @@ -199,16 +198,68 @@ export class PageEditorPage extends BasePageObject {
return { modId };
}

/**
* Create a new mod by moving a mod component to a new mod.
* @param sourceModComponentName the name of the mod component to move
* @param destinationModName the root name of the new mod
*/
@ModifiesModFormState
async saveStandaloneMod(modName: string, modUuid: UUID) {
const modListItem = this.modListingPanel.getModListItemByName(modName);
await modListItem.select();
await modListItem.saveButton.click();
async moveModComponentToNewMod({
sourceModComponentName,
destinationModName,
}: {
sourceModComponentName: string;
destinationModName: string;
}) {
const modListItem = this.modListingPanel.getModListItemByName(
sourceModComponentName,
);
await modListItem.menuButton.click();
await this.getByRole("menuitem", { name: "Move to Mod" }).click();

const moveDialog = this.getByRole("dialog");

await moveDialog.getByRole("combobox").click();
await moveDialog.getByRole("option", { name: /Create new mod.../ }).click();
await moveDialog.getByRole("button", { name: "Move" }).click();

// Create mod modal is shown
const createModModal = new CreateModModal(this.getByRole("dialog"));
const modId = await createModModal.createMod(modName, modUuid);

this.savedPackageModIds.push(modId);
const modId = await createModModal.createMod(destinationModName);
return { modId };
}

/**
* Create a new mod by copying a mod component to a new mod.
* @param sourceModComponentName the name of the mod component to move
* @param destinationModName the root name of the new mod
*/
@ModifiesModFormState
async copyModComponentToNewMod({
sourceModComponentName,
destinationModName,
}: {
sourceModComponentName: string;
destinationModName: string;
}) {
const modListItem = this.modListingPanel.getModListItemByName(
sourceModComponentName,
);
await modListItem.menuButton.click();
await this.getByRole("menuitem", { name: "Copy to Mod" }).click();

const moveDialog = this.getByRole("dialog");

await moveDialog.getByRole("combobox").click();
await moveDialog.getByRole("option", { name: /Create new mod.../ }).click();
await moveDialog.getByRole("button", { name: "Copy" }).click();

// Create mod modal is shown
const createModModal = new CreateModModal(this.getByRole("dialog"));

const modId = await createModModal.createMod(destinationModName);
return { modId };
}

@ModifiesModFormState
Expand Down Expand Up @@ -238,34 +289,6 @@ export class PageEditorPage extends BasePageObject {
await deactivateModModal.deactivateButton.click();
}

@ModifiesModFormState
async createModFromModComponent({
modNameRoot,
modComponentName,
modUuid,
}: {
modNameRoot: string;
modComponentName: string;
modUuid: UUID;
}) {
const modName = `${modNameRoot} ${modUuid}`;

const modListItem =
this.modListingPanel.getModListItemByName(modComponentName);
await modListItem.menuButton.click();
await this.getByRole("menuitem", { name: "Add to mod" }).click();

await this.getByText("Select...Choose a mod").click();
await this.getByRole("option", { name: /Create new mod.../ }).click();
await this.getByRole("button", { name: "Move" }).click();

// Create mod modal is shown
const createModModal = new CreateModModal(this.getByRole("dialog"));
const modId = await createModModal.createMod(modName, modUuid);

return { modName, modId };
}

/**
* This method is meant to be called exactly once after the test is done to clean up any saved mods created during the
* test.
Expand Down
116 changes: 116 additions & 0 deletions end-to-end-tests/tests/pageEditor/moveCopyModComponent.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { test, expect } from "../../fixtures/testBase";
// @ts-expect-error -- https://youtrack.jetbrains.com/issue/AQUA-711/Provide-a-run-configuration-for-Playwright-tests-in-specs-with-fixture-imports-only
import { type Page, test as base } from "@playwright/test";
import { uuidv4 } from "@/types/helpers";

test("Create new mod by moving mod component", async ({
page,
newPageEditorPage,
}) => {
await page.goto("/");
const pageEditorPage = await newPageEditorPage(page.url());

await test.step("Add new Trigger starter brick", async () => {
const { modComponentNameMatcher } =
await pageEditorPage.modListingPanel.addNewMod({
starterBrickName: "Trigger",
});

await expect(
pageEditorPage.brickConfigurationPanel.getByRole("textbox", {
name: "Name",
}),
).toHaveValue(modComponentNameMatcher);
});

const modComponentName = await pageEditorPage.brickConfigurationPanel
.getByRole("textbox", {
name: "Name",
})
.inputValue();

// Since 2.1.4, new mods are created with the name "New Mod" instead of being a standalone mod component
// Use span locator to distinguish from the New Mod button
await expect(
pageEditorPage.locator("span").filter({ hasText: "New Mod" }),
).toBeVisible();

const modName = `Destination Mod ${uuidv4()}`;

await pageEditorPage.moveModComponentToNewMod({
sourceModComponentName: modComponentName,
destinationModName: modName,
});

await expect(pageEditorPage.getByText(modName)).toBeVisible();
await expect(pageEditorPage.getByText(modComponentName)).toBeVisible();

// Should not be visible. Because it's only mod component was moved
await expect(
pageEditorPage.locator("span").filter({ hasText: "New Mod" }),
).toBeHidden();
});

test("Create new mod by copying a mod component", async ({
page,
newPageEditorPage,
}) => {
await page.goto("/");
const pageEditorPage = await newPageEditorPage(page.url());

await test.step("Add new Trigger starter brick", async () => {
const { modComponentNameMatcher } =
await pageEditorPage.modListingPanel.addNewMod({
starterBrickName: "Trigger",
});

await expect(
pageEditorPage.brickConfigurationPanel.getByRole("textbox", {
name: "Name",
}),
).toHaveValue(modComponentNameMatcher);
});

const modComponentName = await pageEditorPage.brickConfigurationPanel
.getByRole("textbox", {
name: "Name",
})
.inputValue();

// Since 2.1.4, new mods are created with the name "New Mod" instead of being a standalone mod component
// Use span locator to distinguish from the New Mod button
await expect(
pageEditorPage.locator("span").filter({ hasText: "New Mod" }),
).toBeVisible();

const modName = `Destination Mod ${uuidv4()}`;

await pageEditorPage.copyModComponentToNewMod({
sourceModComponentName: modComponentName,
destinationModName: modName,
});

// Use span locator to distinguish from the New Mod button
await expect(
pageEditorPage.locator("span").filter({ hasText: "New Mod" }),
).toBeVisible();
await expect(pageEditorPage.getByText(modName)).toBeVisible();
await expect(pageEditorPage.getByText(modComponentName)).toHaveCount(2);
});
2 changes: 1 addition & 1 deletion src/pageEditor/modListingPanel/ActionMenu.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
margin-right: 2px;
}

.removeIcon {
.moveIcon {
margin-left: 3px;
margin-right: -3px;
}
30 changes: 20 additions & 10 deletions src/pageEditor/modListingPanel/DraftModComponentListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,30 +201,40 @@ const DraftModComponentListItem: React.FunctionComponent<
<ModComponentActionMenu
isActive={isActive}
isDirty={isDirty}
labelRoot={`${getLabel(modComponentFormState)}`}
onSave={onSave}
onDelete={onDelete}
labelRoot={getLabel(modComponentFormState)}
onDeactivate={onDeactivate}
onDuplicate={async () => {
dispatch(actions.duplicateActiveModComponent());
dispatch(
actions.duplicateActiveModComponent({
// Pass undefined to duplicate the mod component in the same mod
destinationModMetadata: undefined,
}),
);
}}
onClearChanges={
modComponentFormState.installed ? onClearChanges : undefined
}
onAddToMod={
onMoveToMod={
modComponentFormState.modMetadata
? undefined
: async () => {
dispatch(actions.showAddToModModal());
? async () => {
dispatch(
actions.showMoveCopyToModModal({ moveOrCopy: "move" }),
);
}
: undefined
}
onRemoveFromMod={
onCopyToMod={
modComponentFormState.modMetadata
? async () => {
dispatch(actions.showRemoveFromModModal());
dispatch(
actions.showMoveCopyToModModal({ moveOrCopy: "copy" }),
);
}
: undefined
}
// TODO: https://github.com/pixiebrix/pixiebrix-extension/issues/9242, remove standalone mod component actions
onSave={onSave}
onDelete={onDelete}
/>
</ListGroup.Item>
);
Expand Down
20 changes: 10 additions & 10 deletions src/pageEditor/modListingPanel/ModComponentActionMenu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ export default {
control: "boolean",
defaultValue: false,
},
disabled: {
control: "boolean",
defaultValue: false,
},
},
} as ComponentMeta<typeof ModComponentActionMenu>;

Expand All @@ -43,28 +39,32 @@ const Template: ComponentStory<typeof ModComponentActionMenu> = (args) => (
export const NewModComponent = Template.bind({});
NewModComponent.args = {
onClearChanges: undefined,
onRemoveFromMod: undefined,
onCopyToMod: undefined,
onMoveToMod: undefined,
isDirty: true,
};

export const OldModComponent = Template.bind({});
OldModComponent.args = {
onRemoveFromMod: undefined,
onCopyToMod: undefined,
onMoveToMod: undefined,
};

export const Mod = Template.bind({});
Mod.args = {
onAddToMod: undefined,
onRemoveFromMod: undefined,
onCopyToMod: undefined,
onMoveToMod: undefined,
};

export const NewModComponentInMod = Template.bind({});
NewModComponentInMod.args = {
onClearChanges: undefined,
onAddToMod: undefined,
onCopyToMod: undefined,
onMoveToMod: undefined,
};

export const OldModComponentInMod = Template.bind({});
OldModComponentInMod.args = {
onAddToMod: undefined,
onCopyToMod: undefined,
onMoveToMod: undefined,
};
Loading

0 comments on commit 9c49084

Please sign in to comment.