Skip to content

Commit

Permalink
#9313: fix delete draft mod component (#9320)
Browse files Browse the repository at this point in the history
  • Loading branch information
twschiller authored Oct 22, 2024
1 parent c81c880 commit ea50eb5
Show file tree
Hide file tree
Showing 28 changed files with 581 additions and 734 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,4 @@
*/

export const deactivateMod = jest.fn();
export const deactivateModComponents = jest.fn();
export const removeModComponentsFromAllTabs = jest.fn();
export const removeModDataAndInterfaceFromAllTabs = jest.fn();
2 changes: 1 addition & 1 deletion src/activation/useActivateMod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import useActivateMod from "./useActivateMod";
import { uuidv4, validateRegistryId } from "@/types/helpers";
import { type StarterBrickDefinitionLike } from "@/starterBricks/types";
import { type ContextMenuDefinition } from "@/starterBricks/contextMenu/contextMenuTypes";
import { deactivateMod } from "@/store/deactivateUtils";
import { deactivateMod } from "@/store/deactivateModHelpers";
import { type ModDefinition } from "@/types/modDefinitionTypes";
import modComponentSlice from "@/store/modComponents/modComponentSlice";
import { type InnerDefinitions } from "@/types/registryTypes";
Expand Down
2 changes: 1 addition & 1 deletion src/activation/useActivateMod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { useDispatch, useSelector } from "react-redux";
import modComponentSlice from "@/store/modComponents/modComponentSlice";
import reportEvent from "@/telemetry/reportEvent";
import { getErrorMessage } from "@/errors/errorHelpers";
import { deactivateMod } from "@/store/deactivateUtils";
import { deactivateMod } from "@/store/deactivateModHelpers";
import { ensurePermissionsFromUserGesture } from "@/permissions/permissionsUtils";
import { checkModDefinitionPermissions } from "@/modDefinitions/modDefinitionPermissionsHelpers";
import { useCreateDatabaseMutation } from "@/data/service/api";
Expand Down
4 changes: 2 additions & 2 deletions src/background/utils/deactivateMod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type ReduxSliceState = {
};

/**
* Returns the Redux state that excludes the mod. NOTE: does not persist the state or remove the mod UI from
* Returns the Redux state that excludes the mod. NOTE: does not persist the state, or remove the mod UI from
* existing tabs.
*
* @param modInstance the active mod to deactivate
Expand All @@ -48,7 +48,7 @@ function deactivateMod(
),
editorState: editorSlice.reducer(
editorState,
editorSlice.actions.removeMod(modInstance),
editorSlice.actions.removeModById(modId),
),
};
}
Expand Down
4 changes: 2 additions & 2 deletions src/extensionConsole/pages/mods/ModsPageActions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import React from "react";
import { useHistory } from "react-router";
import { deactivateMod } from "@/store/deactivateUtils";
import { deactivateMod } from "@/store/deactivateModHelpers";
import { render, screen } from "@/extensionConsole/testHelpers";
import ModsPageActions from "@/extensionConsole/pages/mods/ModsPageActions";
import { modViewItemFactory } from "@/testUtils/factories/modViewItemFactory";
Expand All @@ -41,7 +41,7 @@ jest.mock("react-router", () => {
};
});

jest.mock("@/store/deactivateUtils", () => ({
jest.mock("@/store/deactivateModHelpers", () => ({
deactivateMod: jest.fn(),
}));

Expand Down
2 changes: 1 addition & 1 deletion src/extensionConsole/pages/mods/ModsPageActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { useDispatch } from "react-redux";
import { useHistory } from "react-router";
import reportEvent from "@/telemetry/reportEvent";
import { Events } from "@/telemetry/events";
import { deactivateMod } from "@/store/deactivateUtils";
import { deactivateMod } from "@/store/deactivateModHelpers";
import useUserAction from "@/hooks/useUserAction";
import { useDeletePackageMutation } from "@/data/service/api";
import { useModals } from "@/components/ConfirmationModal";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { renderHook } from "@/extensionConsole/testHelpers";
import useReactivateMod from "./useReactivateMod";
import { actions as modComponentActions } from "@/store/modComponents/modComponentSlice";
import { deactivateMod } from "@/store/deactivateUtils";
import { deactivateMod } from "@/store/deactivateModHelpers";
import { type ModComponentsRootState } from "@/store/modComponents/modComponentTypes";
import { defaultModDefinitionFactory } from "@/testUtils/factories/modDefinitionFactories";
import { selectActivatedModComponents } from "@/store/modComponents/modComponentSelectors";
Expand Down
2 changes: 1 addition & 1 deletion src/extensionConsole/pages/mods/utils/useReactivateMod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { type ModDefinition } from "@/types/modDefinitionTypes";
import { useDispatch, useSelector } from "react-redux";
import { useCallback } from "react";
import { actions as modComponentActions } from "@/store/modComponents/modComponentSlice";
import { deactivateMod } from "@/store/deactivateUtils";
import { deactivateMod } from "@/store/deactivateModHelpers";
import { selectModInstanceMap } from "@/store/modComponents/modInstanceSelectors";
import { assertNotNullish } from "@/utils/nullishUtils";

Expand Down
2 changes: 1 addition & 1 deletion src/pageEditor/hooks/useClearModChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function useClearModChanges(): (modId: RegistryId) => Promise<void> {
);

dispatch(actions.clearMetadataAndOptionsChangesForMod(modId));
dispatch(actions.restoreDeletedModComponentFormStatesForMod(modId));
dispatch(actions.clearDeletedModComponentFormStatesForMod(modId));
dispatch(actions.setActiveModId(modId));
},
[
Expand Down
4 changes: 1 addition & 3 deletions src/pageEditor/hooks/useClearModComponentChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import { selectSessionId } from "@/pageEditor/store/session/sessionSelectors";
import reportEvent from "@/telemetry/reportEvent";
import { Events } from "@/telemetry/events";
import { type UUID } from "@/types/stringTypes";
import { useAllModDefinitions } from "@/modDefinitions/modDefinitionHooks";

import { selectActivatedModComponents } from "@/store/modComponents/modComponentSelectors";

Expand All @@ -44,7 +43,6 @@ function useClearModComponentChanges(): (
const dispatch = useDispatch();
const sessionId = useSelector(selectSessionId);
const activatedModComponents = useSelector(selectActivatedModComponents);
const { data: mods } = useAllModDefinitions();
const { showConfirmation } = useModals();

return useCallback(
Expand Down Expand Up @@ -84,7 +82,7 @@ function useClearModComponentChanges(): (
dispatch(actions.adapterError({ uuid: modComponentId, error }));
}
},
[dispatch, mods, sessionId, activatedModComponents, showConfirmation],
[dispatch, sessionId, activatedModComponents, showConfirmation],
);
}

Expand Down
24 changes: 7 additions & 17 deletions src/pageEditor/hooks/useCreateModFromModComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { actions as editorActions } from "@/pageEditor/store/editor/editorSlice"
import useUpsertModComponentFormState from "@/pageEditor/hooks/useUpsertModComponentFormState";
import { mapModDefinitionUpsertResponseToModMetadata } from "@/pageEditor/utils";
import { selectKeepLocalCopyOnCreateMod } from "@/pageEditor/store/editor/editorSelectors";
import { useRemoveModComponentFromStorage } from "@/pageEditor/hooks/useRemoveModComponentFromStorage";
import useDeleteDraftModComponent from "@/pageEditor/hooks/useDeleteDraftModComponent";
import useBuildAndValidateMod from "@/pageEditor/hooks/useBuildAndValidateMod";
import { BusinessError } from "@/errors/businessErrors";
import { type Nullishable } from "@/utils/nullishUtils";
Expand All @@ -48,7 +48,7 @@ function useCreateModFromModComponent(
const keepLocalCopy = useSelector(selectKeepLocalCopyOnCreateMod);
const [createMod] = useCreateModDefinitionMutation();
const upsertModComponentFormState = useUpsertModComponentFormState();
const removeModComponentFromStorage = useRemoveModComponentFromStorage();
const deleteDraftModComponent = useDeleteDraftModComponent();
const { buildAndValidateMod } = useBuildAndValidateMod();

const createModFromComponent = useCallback(
Expand Down Expand Up @@ -106,20 +106,10 @@ function useCreateModFromModComponent(
});

if (!keepLocalCopy) {
console.debug(
"createModFromComponent - removing standalone component",
{
activeModComponent,
},
);

const removePromises: Array<Promise<unknown>> = [
removeModComponentFromStorage({
modComponentId: activeModComponent.uuid,
}),
];

await Promise.all(removePromises);
// Delete the mod component from the source mod
await deleteDraftModComponent({
modComponentId: activeModComponent.uuid,
});
}

// Check the new component availability, so it's added to available components if needed
Expand All @@ -143,7 +133,7 @@ function useCreateModFromModComponent(
dispatch,
upsertModComponentFormState,
keepLocalCopy,
removeModComponentFromStorage,
deleteDraftModComponent,
],
);

Expand Down
105 changes: 59 additions & 46 deletions src/pageEditor/hooks/useDeactivateMod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,64 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { useCallback } from "react";
import React, { useCallback } from "react";
import { type RegistryId } from "@/types/registryTypes";
import {
DEACTIVATE_MOD_MODAL_PROPS,
DELETE_UNSAVED_MOD_MODAL_PROPS,
useRemoveModComponentFromStorage,
} from "@/pageEditor/hooks/useRemoveModComponentFromStorage";
import { useDispatch, useSelector } from "react-redux";
import { selectModComponentFormStates } from "@/pageEditor/store/editor/editorSelectors";
import { uniq } from "lodash";
import { useModals } from "@/components/ConfirmationModal";
import { actions } from "@/pageEditor/store/editor/editorSlice";
import { clearLog } from "@/background/messenger/api";
import { selectModInstanceMap } from "@/store/modComponents/modInstanceSelectors";
import {
type ConfirmationModalProps,
useModals,
} from "@/components/ConfirmationModal";
import { actions as editorActions } from "@/pageEditor/store/editor/editorSlice";
import { actions as modComponentActions } from "@/store/modComponents/modComponentSlice";
import { isInnerDefinitionRegistryId } from "@/types/helpers";
import { removeModDataAndInterfaceFromAllTabs } from "@/store/deactivateModHelpers";
import { selectModInstanceMap } from "@/store/modComponents/modInstanceSelectors";
import { selectGetDraftModComponentIdsForMod } from "@/pageEditor/store/editor/selectGetCleanComponentsAndDirtyFormStatesForMod";

type Config = {
modId: RegistryId;
shouldShowConfirmation?: boolean;
};

const DEACTIVATE_MOD_MODAL_PROPS: ConfirmationModalProps = {
title: "Deactivate Mod?",
message: (
<>
Any unsaved changes will be lost. You can reactivate or delete mods from
the{" "}
<a href="/options.html" target="_blank">
PixieBrix Extension Console
</a>
.
</>
),
submitCaption: "Deactivate",
};

const DELETE_UNSAVED_MOD_MODAL_PROPS: ConfirmationModalProps = {
title: "Delete Mod?",
message: (
<>
This action cannot be undone. If you&apos;d like to deactivate this mod
instead, save the mod first.
</>
),
submitCaption: "Delete",
};

/**
* This hook provides a callback function to deactivate a mod and remove it from the Page Editor. Note that in the case
* of unsaved mods, the mod will be deleted instead of deactivated.
* Hook providing a callback function to deactivate a mod and remove it from the Page Editor. NOTE: if the mod is
* an unsaved draft, the mod will be deleted completely instead of deactivated.
*
* @see useDeleteDraftModComponent
*/
function useDeactivateMod(): (useDeactivateConfig: Config) => Promise<void> {
const dispatch = useDispatch();
const removeModComponentFromStorage = useRemoveModComponentFromStorage();
const modInstanceMap = useSelector(selectModInstanceMap);
const modComponentFormStates = useSelector(selectModComponentFormStates);
const { showConfirmation } = useModals();
const modInstanceMap = useSelector(selectModInstanceMap);
const getDraftModComponentIds = useSelector(
selectGetDraftModComponentIdsForMod,
);

return useCallback(
async ({ modId, shouldShowConfirmation = true }) => {
Expand All @@ -62,38 +89,24 @@ function useDeactivateMod(): (useDeactivateConfig: Config) => Promise<void> {
}
}

const modInstance = modInstanceMap.get(modId);

const formComponentIds = modComponentFormStates
.filter((x) => x.modMetadata.id === modId)
.map((x) => x.uuid);

const modComponentIds = uniq([
...(modInstance?.modComponentIds ?? []),
...formComponentIds,
]);

await Promise.all(
modComponentIds.map(async (modComponentId) =>
removeModComponentFromStorage({
modComponentId,
}),
),
);

void clearLog({
// Remove from Page Editor
dispatch(editorActions.removeModById(modId));
await removeModDataAndInterfaceFromAllTabs(
modId,
});
getDraftModComponentIds(modId),
);

dispatch(actions.removeModData(modId));
// Remove the activated mod instance from all tabs, if one exists
const modInstance = modInstanceMap.get(modId);
if (modInstance) {
dispatch(modComponentActions.removeModById(modId));
await removeModDataAndInterfaceFromAllTabs(
modId,
modInstance.modComponentIds,
);
}
},
[
dispatch,
modComponentFormStates,
modInstanceMap,
removeModComponentFromStorage,
showConfirmation,
],
[dispatch, showConfirmation, modInstanceMap, getDraftModComponentIds],
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@
*/

import { renderHook } from "@/pageEditor/testHelpers";
import { removeModComponentsFromAllTabs } from "@/store/deactivateUtils";
import { useRemoveModComponentFromStorage } from "./useRemoveModComponentFromStorage";
import useDeleteDraftModComponent from "./useDeleteDraftModComponent";
import { actions as editorActions } from "@/pageEditor/store/editor/editorSlice";
import { actions as modComponentActions } from "@/store/modComponents/modComponentSlice";
import { removeDraftModComponents } from "@/contentScript/messenger/api";

import { autoUUIDSequence } from "@/testUtils/factories/stringFactories";
Expand All @@ -28,19 +26,19 @@ beforeEach(() => {
jest.resetAllMocks();
});

test("useRemoveModComponentFromStorage", async () => {
test("useDeleteModComponent", async () => {
const modComponentId = autoUUIDSequence();

const {
result: { current: removeModComponent },
result: { current: deleteDraftModComponent },
getReduxStore,
} = renderHook(() => useRemoveModComponentFromStorage(), {
setupRedux(dispatch, { store }) {
} = renderHook(() => useDeleteDraftModComponent(), {
setupRedux(_dispatch, { store }) {
jest.spyOn(store, "dispatch");
},
});

await removeModComponent({
await deleteDraftModComponent({
modComponentId,
});

Expand All @@ -49,12 +47,9 @@ test("useRemoveModComponentFromStorage", async () => {
expect(dispatch).toHaveBeenCalledWith(
editorActions.removeModComponentFormState(modComponentId),
);
expect(dispatch).toHaveBeenCalledWith(
modComponentActions.removeModComponent({ modComponentId }),
);

expect(removeDraftModComponents).toHaveBeenCalledWith(
expect.any(Object),
modComponentId,
);
expect(removeModComponentsFromAllTabs).toHaveBeenCalledWith([modComponentId]);
});
Loading

0 comments on commit ea50eb5

Please sign in to comment.