Skip to content

Commit

Permalink
feat(prop-view): override props
Browse files Browse the repository at this point in the history
  • Loading branch information
lynette-li committed Aug 3, 2020
1 parent 63fc4d0 commit 07ac738
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 44 deletions.
3 changes: 3 additions & 0 deletions src/hook/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { HOOK_NAME } from "../shared/constants";
import { getBricks, getBrickByUid, getBrickInfo } from "./traverse";
import { inspectElement, dismissInspections } from "./inspector";
import { emit } from "./emit";
import { overrideProps } from "./overrideProps";

function injectHook(): void {
if (Object.prototype.hasOwnProperty.call(window, HOOK_NAME)) {
Expand All @@ -15,6 +16,8 @@ function injectHook(): void {
inspectBrick: (uid: number) => inspectElement(getBrickByUid(uid)),
dismissInspections,
emit,
overrideProps: (uid: number, propertyName: string, value: any) =>
overrideProps(getBrickByUid(uid), propertyName, value),
};

Object.defineProperty(hook, "pageHasBricks", {
Expand Down
17 changes: 17 additions & 0 deletions src/hook/overrideProps.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { overrideProps } from "./overrideProps";
import { BrickElement } from "../shared/interfaces";

describe("overrideProps", () => {
it("should work", () => {
overrideProps(undefined, "showCard", false);
const element =
{
tagName: "YOUR.awesome-brick",
} as BrickElement;
overrideProps(element, "showCard", false);
expect(element).toEqual({
tagName: "YOUR.awesome-brick",
showCard: false,
});
});
});
11 changes: 11 additions & 0 deletions src/hook/overrideProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BrickElement } from "../shared/interfaces";

export function overrideProps(
element: BrickElement,
propertyName: string,
value: any
): void {
if (element) {
element[propertyName] = value;
}
}
59 changes: 54 additions & 5 deletions src/panel/components/PropList.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import { shallow } from "enzyme";
import { Icon } from "@blueprintjs/core";
import { Icon, TextArea } from "@blueprintjs/core";
import { PropList, PropItem } from "./PropList";
import { PROP_DEHYDRATED } from "../../shared/constants";

Expand Down Expand Up @@ -76,10 +76,14 @@ describe("PropItem", () => {
const wrapper = shallow(<PropItem propName="quality" propValue="good" />);
expect(wrapper.hasClass("expanded")).toBe(false);
expect(wrapper.find(Icon).prop("icon")).toBe("blank");
wrapper.find(".prop-item-label").invoke("onClick")(null);
wrapper.find('span[data-testid="prop-name-wrapper"]').invoke("onClick")(
null
);
expect(wrapper.hasClass("expanded")).toBe(true);
expect(wrapper.find(Icon).prop("icon")).toBe("blank");
wrapper.find(".prop-item-label").invoke("onClick")(null);
wrapper.find('span[data-testid="prop-name-wrapper"]').invoke("onClick")(
null
);
expect(wrapper.hasClass("expanded")).toBe(false);
expect(wrapper.find(Icon).prop("icon")).toBe("blank");
});
Expand All @@ -88,7 +92,9 @@ describe("PropItem", () => {
const wrapper = shallow(<PropItem propName="quality" propValue={[1]} />);
expect(wrapper.find(PropList).length).toBe(0);
expect(wrapper.find(Icon).prop("icon")).toBe("caret-right");
wrapper.find(".prop-item-label").invoke("onClick")(null);
wrapper.find('span[data-testid="prop-name-wrapper"]').invoke("onClick")(
null
);
expect(wrapper.find(PropList).prop("list")).toEqual([1]);
expect(wrapper.find(Icon).prop("icon")).toBe("caret-down");
});
Expand All @@ -102,8 +108,51 @@ describe("PropItem", () => {
it("should work for standalone with complex property value", () => {
const wrapper = shallow(<PropItem propValue={[1]} standalone />);
expect(wrapper.find(Icon).prop("icon")).toBe("caret-right");
wrapper.find(".prop-item-label").invoke("onClick")(null);
wrapper.find('span[data-testid="prop-name-wrapper"]').invoke("onClick")(
null
);
expect(wrapper.find(PropList).prop("list")).toEqual([1]);
expect(wrapper.find(Icon).prop("icon")).toBe("caret-down");
wrapper.find(".prop-value").invoke("onDoubleClick")({} as any);
expect(wrapper.find(TextArea).length).toBe(0);
});

it("should work when editable", () => {
const wrapper = shallow(
<PropItem propName="quality" propValue="good" editable />
);
expect(wrapper.find(TextArea).length).toBe(0);
wrapper.find(".prop-value").invoke("onDoubleClick")({} as any);
expect(wrapper.find(TextArea).length).toBe(1);
wrapper.find(TextArea).invoke("onChange")({
target: {
value: '"bad"',
},
} as any);
expect(wrapper.find(TextArea).prop("value")).toBe('"bad"');
wrapper.find(TextArea).invoke("onBlur")({} as any);
expect(wrapper.find(".bp3-intent-danger").length).toBe(0);
expect(wrapper.find(TextArea).length).toBe(0);

wrapper.find(".prop-value").invoke("onDoubleClick")({} as any);
wrapper.find(TextArea).invoke("onChange")({
target: {
value: "{a:errorJsonStringValue",
},
} as any);
expect(wrapper.find(TextArea).prop("value")).toBe(
"{a:errorJsonStringValue"
);
wrapper.find(TextArea).invoke("onBlur")({} as any);
expect(wrapper.find(".bp3-intent-danger").length).toBe(1);
expect(wrapper.find(TextArea).length).toBe(1);
wrapper.find(TextArea).invoke("onChange")({
target: {
value: undefined,
},
} as any);
expect(wrapper.find(TextArea).prop("value")).toBe(undefined);
wrapper.find(TextArea).invoke("onBlur")({} as any);
expect(wrapper.find(".bp3-intent-danger").length).toBe(0);
});
});
119 changes: 95 additions & 24 deletions src/panel/components/PropList.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,44 @@
import React from "react";
import classNames from "classnames";
import { Icon } from "@blueprintjs/core";
import { Icon, TextArea } from "@blueprintjs/core";
import { PROP_DEHYDRATED } from "../../shared/constants";
import { VariableDisplay } from "./VariableDisplay";
import { isDehydrated, isObject } from "../libs/utils";

interface PropListProps {
list: any[] | Record<string, any>;
editable?: boolean;
overrideProps?: (propName: string, propValue: string) => void;
}

export function PropList({ list }: PropListProps): React.ReactElement {
export function PropList({
list,
editable,
overrideProps,
}: PropListProps): React.ReactElement {
const value = isDehydrated(list)
? list[PROP_DEHYDRATED].children || {}
: list;
return (
<ul className="prop-list">
{Array.isArray(value)
? value.map((item, index) => (
<PropItem key={index} propName={String(index)} propValue={item} />
<PropItem
key={index}
propName={String(index)}
propValue={item}
overrideProps={overrideProps}
editable={editable}
/>
))
: Object.entries(value).map((entry) => (
<PropItem key={entry[0]} propName={entry[0]} propValue={entry[1]} />
<PropItem
key={entry[0]}
propName={entry[0]}
propValue={entry[1]}
overrideProps={overrideProps}
editable={editable}
/>
))}
</ul>
);
Expand All @@ -30,19 +48,54 @@ interface PropItemProps {
propValue: any;
propName?: string;
standalone?: boolean;
editable?: boolean;
overrideProps?: (propName: string, propValue: string) => void;
}

export function PropItem({
propValue,
propName,
standalone,
editable,
overrideProps,
}: PropItemProps): React.ReactElement {
const [expanded, setExpanded] = React.useState(false);
const [editing, setEditing] = React.useState(false);
const [changeValue, setChangeValue] = React.useState(
JSON.stringify(propValue, null, 2)
);
const [error, setError] = React.useState(false);

const handleClick = React.useCallback(() => {
setExpanded(!expanded);
}, [expanded]);

const handleDoubleClick = React.useCallback(() => {
if (editable) {
setEditing(true);
}
}, [editable]);

const handleBlur = () => {
try {
let result;
if (changeValue === "" || changeValue === undefined) {
result = undefined;
} else {
result = JSON.parse(changeValue);
}
overrideProps?.(propName, changeValue);
setEditing(false);
setError(false);
} catch (error) {
setError(true);
}
};

const handleChange = (v) => {
setChangeValue(v.target?.value);
};

const hasChildren = isObject(propValue);

return React.createElement(
Expand All @@ -51,29 +104,47 @@ export function PropItem({
className: classNames("prop-item", { expanded }),
},
<>
<div
className="bp3-text-overflow-ellipsis prop-item-label"
onClick={handleClick}
>
{(!standalone || hasChildren) && (
<Icon
icon={
hasChildren ? (expanded ? "caret-down" : "caret-right") : "blank"
}
iconSize={16}
<div className="bp3-text-overflow-ellipsis prop-item-label">
<span onClick={handleClick} data-testid="prop-name-wrapper">
{(!standalone || hasChildren) && (
<Icon
icon={
hasChildren && !editing
? expanded
? "caret-down"
: "caret-right"
: "blank"
}
iconSize={16}
/>
)}
{!standalone && (
<>
<span className="prop-name">{propName}</span>
<span className="prop-punctuation">:</span>{" "}
</>
)}
</span>
{editing && editable ? (
<TextArea
growVertically={true}
onChange={handleChange}
onBlur={handleBlur}
value={changeValue}
className={`prop-editable ${error ? "bp3-intent-danger" : ""}`}
autoFocus
/>
) : (
<span className="prop-value" onDoubleClick={handleDoubleClick}>
<VariableDisplay value={propValue} expanded={expanded} />
</span>
)}
{!standalone && (
<>
<span className="prop-name">{propName}</span>
<span className="prop-punctuation">:</span>{" "}
</>
)}
<span className="prop-value">
<VariableDisplay value={propValue} expanded={expanded} />
</span>
</div>
{hasChildren && expanded && <PropList list={propValue} />}
{hasChildren && expanded && !editing && (
<div onDoubleClick={handleDoubleClick}>
<PropList list={propValue} />
</div>
)}
</>
);
}
25 changes: 24 additions & 1 deletion src/panel/components/PropView.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ describe("PropView", () => {
expect(wrapper.find(PropList).at(0).prop("list")).toEqual({
quality: "good",
});
expect(wrapper.find(PropList).at(0).prop("editable")).toBe(true);
expect(wrapper.find(PropList).at(1).prop("list")).toEqual([
["click", { action: "console.log" }],
]);

expect(wrapper.find(PropList).at(1).prop("editable")).toBe(false);
(useSelectedBrickContext as jest.Mock).mockReset();
});

Expand All @@ -75,4 +76,26 @@ describe("PropView", () => {
expect(document.execCommand).toBeCalledWith("copy");
expect(document.removeEventListener).toHaveBeenCalled();
});

it("should work when overrideProps", () => {
const selectedBrick = {
uid: 1,
};
(useSelectedBrickContext as jest.Mock).mockReturnValue({ selectedBrick });
const wrapper = mount(<PropView />);
expect(mockEval.mock.calls[0][0]).toBe(
"window.__BRICK_NEXT_DEVTOOLS_HOOK__.getBrickInfo(1)"
);
const mockEvalOverrideProps = jest.fn();
(window as any).chrome = {
devtools: {
inspectedWindow: {
eval: mockEvalOverrideProps,
},
},
};
wrapper.find(PropList).at(0).invoke("overrideProps")("quality", "bad");
expect(mockEvalOverrideProps).toHaveBeenCalled();
(useSelectedBrickContext as jest.Mock).mockReset();
});
});
Loading

0 comments on commit 07ac738

Please sign in to comment.