Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ModComponentRef instead of flat extensionId and blueprintId in panel entries #8756

Merged
merged 13 commits into from
Jul 8, 2024
15 changes: 7 additions & 8 deletions src/bricks/transformers/ephemeralForm/formTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { type BrickConfig } from "@/bricks/types";
import { type FormDefinition } from "@/platform/forms/formTypes";
import { isExpression } from "@/utils/expressionUtils";
import type { PlatformCapability } from "@/platform/capabilities";
import { mapMessageContextToModComponentRef } from "@/utils/modUtils";

export const TEMPORARY_FORM_SCHEMA: Schema = {
type: "object",
Expand Down Expand Up @@ -135,15 +136,13 @@ export class FormTransformer extends TransformerABC {
controller.abort();
});

if (logger.context.extensionId == null) {
throw new Error(`${this.name} must be run in a mod context`);
}

try {
return await platform.form(formDefinition, controller, {
componentId: logger.context.extensionId,
modId: logger.context.blueprintId,
});
// `mapMessageContextToModComponentRef` throws if there's no mod component or starter brick in the context
return await platform.form(
formDefinition,
controller,
mapMessageContextToModComponentRef(logger.context),
);
} finally {
controller.abort();
}
Expand Down
87 changes: 33 additions & 54 deletions src/bricks/transformers/temporaryInfo/DisplayTemporaryInfo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import { uuidv4 } from "@/types/helpers";
import ConsoleLogger from "@/utils/ConsoleLogger";
import { tick } from "@/starterBricks/starterBrickTestUtils";
import pDefer from "p-defer";
import { registryIdFactory } from "@/testUtils/factories/stringFactories";
import { type RendererErrorPayload } from "@/types/rendererTypes";
import {
MergeStrategies,
Expand All @@ -60,16 +59,23 @@ import { unary } from "lodash";
import { toExpression } from "@/utils/expressionUtils";
import { showModal } from "@/contentScript/modalDom";
import { isLoadedInIframe } from "@/utils/iframeUtils";
import { modComponentRefFactory } from "@/testUtils/factories/modComponentFactories";

jest.mock("@/contentScript/modalDom");
jest.mock("@/contentScript/sidebarController");
jest.mock("@/platform/panels/panelController");

jest.mock("@/utils/iframeUtils");

const displayTemporaryInfoBlock = new DisplayTemporaryInfo();
const renderer = new DocumentRenderer();

function reduceOptionsFactory() {
return {
...testOptions("v3"),
logger: new ConsoleLogger(modComponentRefFactory()),
};
}

describe("DisplayTemporaryInfo", () => {
beforeEach(() => {
jest.mocked(isLoadedInIframe).mockReturnValue(false);
Expand All @@ -92,8 +98,7 @@ describe("DisplayTemporaryInfo", () => {
});

test("it returns run payload for sidebar panel", async () => {
const extensionId = uuidv4();
const blueprintId = registryIdFactory();
const modComponentRef = modComponentRefFactory();

const config = getExampleBrickConfig(renderer.id);
const pipeline = {
Expand All @@ -106,13 +111,12 @@ describe("DisplayTemporaryInfo", () => {

await reducePipeline(pipeline, simpleInput({}), {
...testOptions("v3"),
logger: new ConsoleLogger({ extensionId, blueprintId }),
logger: new ConsoleLogger(modComponentRef),
});

// Show function will be called with a "loading" payload
expect(showTemporarySidebarPanel).toHaveBeenCalledExactlyOnceWith({
blueprintId,
extensionId,
modComponentRef,
nonce: expect.toBeString(),
heading: expect.toBeString(),
payload: expect.objectContaining({
Expand All @@ -122,8 +126,7 @@ describe("DisplayTemporaryInfo", () => {

// Panel will be updated when the real payload is ready
expect(updatePanelDefinition).toHaveBeenCalledExactlyOnceWith({
blueprintId,
extensionId,
modComponentRef,
nonce: expect.toBeString(),
heading: expect.toBeString(),
payload: expect.objectContaining({
Expand Down Expand Up @@ -160,7 +163,7 @@ describe("DisplayTemporaryInfo", () => {
payload = entry.payload;
});

await reducePipeline(pipeline, simpleInput({}), testOptions("v3"));
await reducePipeline(pipeline, simpleInput({}), reduceOptionsFactory());

expect(isRendererErrorPayload(payload)).toBe(true);
const error = payload as RendererErrorPayload;
Expand All @@ -179,13 +182,11 @@ describe("DisplayTemporaryInfo", () => {
},
};

const extensionId = uuidv4();
const modComponentRef = modComponentRefFactory();

const options = {
...testOptions("v3"),
logger: new ConsoleLogger({
extensionId,
}),
logger: new ConsoleLogger(modComponentRef),
};

await reducePipeline(pipeline, simpleInput({}), options);
Expand All @@ -195,10 +196,10 @@ describe("DisplayTemporaryInfo", () => {

expect(waitForTemporaryPanel).toHaveBeenCalledWith({
nonce: expect.toBeString(),
extensionId,
extensionId: modComponentRef.extensionId,
location: "modal",
entry: expect.objectContaining({
extensionId,
modComponentRef,
heading: "Test Temp Panel",
nonce: expect.toBeString(),
payload: expect.toBeObject(),
Expand All @@ -207,6 +208,7 @@ describe("DisplayTemporaryInfo", () => {
});

test("it errors from frame", async () => {
const modComponentRef = modComponentRefFactory();
jest.mocked(isLoadedInIframe).mockReturnValue(true);

const config = getExampleBrickConfig(renderer.id);
Expand All @@ -220,13 +222,9 @@ describe("DisplayTemporaryInfo", () => {
},
};

const extensionId = uuidv4();

const options = {
...testOptions("v3"),
logger: new ConsoleLogger({
extensionId,
}),
logger: new ConsoleLogger(modComponentRef),
};

await expect(
Expand All @@ -246,17 +244,8 @@ describe("DisplayTemporaryInfo", () => {
},
};

const extensionId = uuidv4();

const options = {
...testOptions("v3"),
logger: new ConsoleLogger({
extensionId,
}),
};

await expect(
reducePipeline(pipeline, simpleInput({}), options),
reducePipeline(pipeline, simpleInput({}), reduceOptionsFactory()),
).rejects.toThrow("Target must be an element for popover");
});

Expand All @@ -274,17 +263,13 @@ describe("DisplayTemporaryInfo", () => {
},
};

const extensionId = uuidv4();
const root = document.querySelector<HTMLElement>("#target");

const options = {
...testOptions("v3"),
logger: new ConsoleLogger({
extensionId,
}),
};

await reducePipeline(pipeline, { ...simpleInput({}), root }, options);
await reducePipeline(
pipeline,
{ ...simpleInput({}), root },
reduceOptionsFactory(),
);

expect(showModal).not.toHaveBeenCalled();
expect(showTemporarySidebarPanel).not.toHaveBeenCalled();
Expand Down Expand Up @@ -314,16 +299,7 @@ describe("DisplayTemporaryInfo", () => {
},
};

const extensionId = uuidv4();

const options = {
...testOptions("v3"),
logger: new ConsoleLogger({
extensionId,
}),
};

void reducePipeline(pipeline, simpleInput({}), options);
void reducePipeline(pipeline, simpleInput({}), reduceOptionsFactory());

await tick();

Expand All @@ -338,7 +314,7 @@ describe("DisplayTemporaryInfo", () => {
deferredPromise.resolve();
});

test("body receives updated mod variable on re-render", async () => {
test("body receives updated public mod variable on re-render", async () => {
document.body.innerHTML = '<div><div id="target"></div></div>';

const deferredPromise = pDefer<any>();
Expand All @@ -365,9 +341,12 @@ describe("DisplayTemporaryInfo", () => {

const options = {
...testOptions("v3"),
logger: new ConsoleLogger({
extensionId,
}),
logger: new ConsoleLogger(
modComponentRefFactory({
extensionId,
blueprintId: null,
}),
),
};

void reducePipeline(pipeline, simpleInput({}), options);
Expand Down
14 changes: 4 additions & 10 deletions src/bricks/transformers/temporaryInfo/DisplayTemporaryInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ import { type JsonObject } from "type-fest";
import { TransformerABC } from "@/types/bricks/transformerTypes";
import { type Schema } from "@/types/schemaTypes";
import { type Location } from "@/types/starterBrickTypes";
import { assumeNotNullish_UNSAFE } from "@/utils/nullishUtils";
import type {
RefreshTrigger,
TemporaryPanelEntryMetadata,
} from "@/platform/panels/panelTypes";
import { mapMessageContextToModComponentRef } from "@/utils/modUtils";

class DisplayTemporaryInfo extends TransformerABC {
static BRICK_ID = validateRegistryId("@pixiebrix/display");
Expand Down Expand Up @@ -101,9 +101,7 @@ class DisplayTemporaryInfo extends TransformerABC {
isRootAware: boolean;
}>,
{
logger: {
context: { extensionId, blueprintId },
},
logger: { context },
root = document,
platform,
runRendererPipeline,
Expand All @@ -113,18 +111,14 @@ class DisplayTemporaryInfo extends TransformerABC {
expectContext("contentScript");

const target = isRootAware ? root : document;
assumeNotNullish_UNSAFE(extensionId);
// XXX: blueprintId can actually be nullish if not running on the context of a mod. But assume it's non-nullish
// for passing to the panel for now. The panel can gracefully handle nullish blueprintId.
assumeNotNullish_UNSAFE(blueprintId);

// Counter for tracking branch execution
let counter = 0;

const panelEntryMetadata: TemporaryPanelEntryMetadata = {
heading: title,
extensionId,
blueprintId,
// Throws if there's no mod component or starter brick in the context
modComponentRef: mapMessageContextToModComponentRef(context),
};

const getPayload = async () => {
Expand Down
10 changes: 2 additions & 8 deletions src/bricks/transformers/temporaryInfo/EphemeralPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,7 @@ const EphemeralPanel: React.FC = () => {
<PanelBody
isRootPanel={false}
payload={entry.payload}
context={{
extensionId: entry.extensionId,
blueprintId: entry.blueprintId,
}}
context={entry.modComponentRef}
onAction={(action) => {
resolveTemporaryPanel(target, panelNonce, action);
}}
Expand Down Expand Up @@ -219,10 +216,7 @@ const EphemeralPanel: React.FC = () => {
<PanelBody
isRootPanel={false}
payload={entry.payload}
context={{
extensionId: entry.extensionId,
blueprintId: entry.blueprintId,
}}
context={entry.modComponentRef}
onAction={(action) => {
resolveTemporaryPanel(target, panelNonce, action);
}}
Expand Down
4 changes: 2 additions & 2 deletions src/contentScript/contentScriptPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ class ContentScriptPlatform extends PlatformBase {
override get panels(): PlatformProtocol["panels"] {
return {
isContainerVisible: async () => sidebarController.isSidePanelOpen(),
unregisterExtensionPoint: sidebarController.removeExtensionPoint,
removeComponents: sidebarController.removeExtensions,
unregisterExtensionPoint: sidebarController.removeStarterBrick,
removeComponents: sidebarController.removeModComponents,
reservePanels: sidebarController.reservePanels,
updateHeading: sidebarController.updateHeading,
upsertPanel: sidebarController.upsertPanel,
Expand Down
14 changes: 4 additions & 10 deletions src/contentScript/ephemeralForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ import {
import { uuidv4 } from "@/types/helpers";
import { isLoadedInIframe } from "@/utils/iframeUtils";
import { BusinessError } from "@/errors/businessErrors";
import type { UUID } from "@/types/stringTypes";
import type { RegistryId } from "@/types/registryTypes";
import { getThisFrame } from "webext-messenger";
import { expectContext } from "@/utils/expectContext";
import { showModal } from "@/contentScript/modalDom";
import type { Target } from "@/types/messengerTypes";
import type { ModComponentRef } from "@/types/modComponentTypes";

// The modes for createFrameSource are different from the location argument for FormTransformer. The mode for the frame
// just determines the layout container of the form
Expand All @@ -55,10 +54,7 @@ export async function createFrameSource(
export async function ephemeralForm(
definition: FormDefinition,
controller: AbortController,
{
componentId: extensionId,
modId: blueprintId,
}: { componentId: UUID; modId: RegistryId },
modComponentRef: ModComponentRef,
): Promise<unknown> {
expectContext("contentScript");

Expand All @@ -75,21 +71,19 @@ export async function ephemeralForm(
// Pre-registering the form also allows the sidebar to know a form will be shown in computing the default
// tab to show during sidebar initialization.
const formPromise = registerForm({
extensionId,
nonce: formNonce,
modComponentRef,
definition,
blueprintId,
});

if (definition.location === "sidebar") {
// Ensure the sidebar is visible (which may also be showing persistent panels)
await showSidebar();

await showSidebarForm({
extensionId,
blueprintId,
nonce: formNonce,
form: definition,
modComponentRef,
});

// Two-way binding between sidebar and form. Listen for the user (or an action) closing the sidebar
Expand Down
Loading
Loading