Skip to content

Commit

Permalink
fix(figma-importer): Properly handle exposed instances props
Browse files Browse the repository at this point in the history
Issue: https://linear.app/plasmic/issue/PLA-10940

Change-Id: Ib760117a3c3615eae76bfd869cb2a3ce8c45a14c
GitOrigin-RevId: f4499e93587c2fc7db2175bb2e81ead7065d082c
  • Loading branch information
FMota0 authored and Copybara committed Jul 2, 2024
1 parent 225befd commit a3f4a1b
Show file tree
Hide file tree
Showing 2 changed files with 240 additions and 12 deletions.
221 changes: 221 additions & 0 deletions platform/wab/src/wab/client/figma-importer/props.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import { InstanceNode } from "@/wab/client/figma-importer/plugin-types";
import { fromFigmaComponentToTplProps } from "@/wab/client/figma-importer/props";
import { fakeStudioCtx } from "@/wab/client/test/fake-init-ctx";
import { mkComponentVariantGroup, mkVariant } from "@/wab/shared/Variants";
import { hackyCast } from "@/wab/shared/common";
import { ComponentType, mkComponent } from "@/wab/shared/core/components";
import { ParamExportType, mkParam, mkVar } from "@/wab/shared/core/lang";
import { mkTplTagX } from "@/wab/shared/core/tpls";
import { StateParam } from "@/wab/shared/model/classes";
import { typeFactory } from "@/wab/shared/model/model-util";

function createFigmaTestData(getCodeComponentMeta: jest.FunctionLike) {
const child = {
type: "INSTANCE",
name: "ButtonSwap",
componentPropertyReferences: {
mainComponent: "swapChild",
},
children: [],
};

const node: Partial<InstanceNode> = {
componentProperties: {
"Error message#1": {
type: "TEXT",
value: "ERROR_MESSAGE_1",
},
"Filled value#2": {
type: "TEXT",
value: "FILLED_VALUE_2",
},
"value#3": {
type: "TEXT",
value: "VALUE_3",
},
"isDisabled#4": {
type: "BOOLEAN",
value: "false",
},
color: {
type: "VARIANT",
value: "primary",
},
swapChild: {
type: "INSTANCE_SWAP",
value: "FIGMA_INTERNAL_ID1",
},
slotValue: {
type: "TEXT",
value: "SLOT_VALUE",
},
Type: {
type: "VARIANT",
value: "ghost",
},
},
exposedInstances: [
hackyCast<InstanceNode>({
name: "exposedInst",
type: "INSTANCE",
children: [],
componentProperties: {
"Exposed prop 1#12:34": {
type: "TEXT",
value: "exposedProp1",
},
},
}),
],
type: "INSTANCE",
children: [
// @ts-expect-error - child is not a full InstanceNode
child,
],
mainComponent: {
id: "FIGMA_INTERNAL_ID2",
name: "Button",
},
parent: null,
};

const TypeVariantParam = {
variable: mkVar("type"),
} as StateParam;

const component = mkComponent({
name: "Button",
type: ComponentType.Code,
params: [
mkParam({
// We should identify that this prop matches "Error message#1"
name: "errorMessage",
type: typeFactory.text(),
exportType: ParamExportType.External,
paramType: "prop",
}),
mkParam({
name: "filledValue",
type: typeFactory.text(),
exportType: ParamExportType.External,
paramType: "prop",
}),
mkParam({
name: "value",
type: typeFactory.text(),
exportType: ParamExportType.External,
paramType: "prop",
}),
mkParam({
name: "isDisabled",
type: typeFactory.bool(),
exportType: ParamExportType.External,
paramType: "prop",
}),
mkParam({
name: "color",
type: typeFactory.text(),
exportType: ParamExportType.External,
paramType: "prop",
}),
mkParam({
name: "secondaryColor",
type: typeFactory.text(),
exportType: ParamExportType.External,
paramType: "prop",
}),
mkParam({
name: "slotValue",
type: typeFactory.renderable(),
exportType: ParamExportType.External,
paramType: "slot",
}),
TypeVariantParam,
],
variantGroups: [
mkComponentVariantGroup({
// The param is neglible for this test
param: TypeVariantParam,
multi: false,
variants: ["primary", "secondary", "ghost"].map((type) => {
return mkVariant({
name: `btn-${type}`,
});
}),
}),
],
tplTree: mkTplTagX("div"),
});

const { studioCtx } = fakeStudioCtx();
// @ts-expect-error - assign fake function to get code component meta
studioCtx.getCodeComponentMeta = getCodeComponentMeta;

return {
studioCtx,
node,
component,
};
}

describe("Figma importer slot handling", () => {
describe("fromFigmaComponentToTplProps", () => {
it("should directly map props if no transform function is provided", () => {
const getCodeComponentMeta = jest.fn().mockReturnValue({});
const { studioCtx, node, component } =
createFigmaTestData(getCodeComponentMeta);
expect(
fromFigmaComponentToTplProps(studioCtx, component, node as InstanceNode)
).toEqual([
["errorMessage", "ERROR_MESSAGE_1"],
["filledValue", "FILLED_VALUE_2"],
["value", "VALUE_3"],
["color", "primary"],
]);
expect(getCodeComponentMeta).toHaveBeenCalledWith(component);
});

it("should call transform function if provided", () => {
const figmaPropsTransform = jest.fn().mockImplementation((props) => {
return {
...props,
secondaryColor: `derived-${props.color}`,
type: `btn-${props.Type}`,
};
});

const getCodeComponentMeta = jest.fn().mockReturnValue({
figmaPropsTransform,
});
const { studioCtx, node, component } =
createFigmaTestData(getCodeComponentMeta);
expect(
fromFigmaComponentToTplProps(studioCtx, component, node as InstanceNode)
).toEqual([
["errorMessage", "ERROR_MESSAGE_1"],
["filledValue", "FILLED_VALUE_2"],
["value", "VALUE_3"],
["color", "primary"],
["secondaryColor", "derived-primary"],
[
"type",
expect.objectContaining({
variants: [component.variantGroups[0].variants[2]],
}),
],
]);
expect(getCodeComponentMeta).toHaveBeenCalledWith(component);
expect(figmaPropsTransform).toHaveBeenCalledWith({
// All props expect for ones that match slots should be here
"Error message": "ERROR_MESSAGE_1",
"Filled value": "FILLED_VALUE_2",
value: "VALUE_3",
isDisabled: false,
color: "primary",
swapChild: "ButtonSwap",
Type: "ghost",
"Exposed prop 1": "exposedProp1",
});
});
});
});
31 changes: 19 additions & 12 deletions platform/wab/src/wab/client/figma-importer/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import {
InstanceNode,
} from "@/wab/client/figma-importer/plugin-types";
import { StudioCtx } from "@/wab/client/studio-ctx/StudioCtx";
import { hackyCast, isJsonScalar, withoutNils } from "@/wab/shared/common";
import { getParamByVarName, isCodeComponent } from "@/wab/shared/core/components";
import { isSlot } from "@/wab/shared/SlotUtils";
import { isStandaloneVariantGroup } from "@/wab/shared/Variants";
import { toVarName } from "@/wab/shared/codegen/util";
import { isJsonScalar, withoutNils } from "@/wab/shared/common";
import {
getParamByVarName,
isCodeComponent,
} from "@/wab/shared/core/components";
import { Component, VariantsRef } from "@/wab/shared/model/classes";
import { isBoolType, isNumType } from "@/wab/shared/model/model-util";
import { notification } from "antd";
Expand Down Expand Up @@ -37,7 +40,8 @@ function getChildComponentNameFromPropertyKey(

function fixComponentFigmaPropKey(key: string, prop: ComponentProperty) {
// Fix property name, removing the suffix that figma adds to text and boolean properties
if (prop.type === "TEXT" || prop.type === "BOOLEAN") {
if ((prop.type === "TEXT" || prop.type === "BOOLEAN") && key.includes("#")) {
// We only run to remove if it does have a "#" in the key
return key.substring(0, key.lastIndexOf("#"));
}
return key;
Expand Down Expand Up @@ -122,7 +126,15 @@ function fromFigmaNodeToFigmaProps(
): ComponentPropertiesEntries {
const localProps: ComponentPropertiesEntries = Object.entries(
inst.componentProperties ?? {}
);
).map(([key, prop]) => {
// We fix directly in the source, since running the fix functions twice can cause issues
// For example. If we have a key "text#other#id" and we run the fixComponentFigmaPropKey function
// twice, it will transform it to "text", which is not what we want
return [
fixComponentFigmaPropKey(key, prop),
fixComponentFigmaPropValue(key, prop, descendants),
];
});

const exposedInstances: InstanceNode[] = inst.exposedInstances ?? [];

Expand All @@ -144,12 +156,7 @@ function fromFigmaNodeToFigmaProps(
// to the component in Plasmic, so that the user can transform them
includePropsWithoutParam: true,
}
).map(([key, prop]) => {
return [
fixComponentFigmaPropKey(key, prop),
fixComponentFigmaPropValue(key, prop, descendants),
];
});
);
}

function getAllDescendants(inst: InstanceNode): InstanceNode[] {
Expand Down Expand Up @@ -255,10 +262,10 @@ function maybeTransformFigmaProps(

if (isCodeComponent(component)) {
const meta = studioCtx.getCodeComponentMeta(component);
if (meta && hackyCast(meta).figmaPropsTransform) {
if (meta && meta.figmaPropsTransform) {
const transformResult = safeTransformFigmaProps(
component,
hackyCast(meta).figmaPropsTransform,
meta.figmaPropsTransform,
componentProps
);

Expand Down

0 comments on commit a3f4a1b

Please sign in to comment.