Files
webstudio/apps/builder/app/shared/copy-paste/plugin-instance.test.ts
T
Ivan Starkov 6f3adf1cc4 feat: Add SiteSettings Dialog (#2527)
## Description

Dialogs and saving of Site Settings and Page Settings.

Bugs & Fixes
- [x] - Use page name on swap to set path i.e. `/home`


Next PRs.

- CLI support (generating head title links and metas)
- <img width="582" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/ab348eb8-0be0-49fc-b15e-352f089812db">
- <img width="266" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/6c272cf2-09d2-4f43-af9f-7460d84fa100">
- Not sure why we need upload button at all, anyway in next PRs <img
width="451" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/a62d2a9d-8793-4d9b-a2c2-4992358f5b10">



---


<img width="173" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/554f2310-579d-4326-8ca7-dd84a13a114a">

<img width="386" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/f0cfb8be-49ab-4b7f-baa3-d68938a2125a">


<img width="375" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/10dfd728-de87-4c91-bca0-0216ad7e5f96">


- [x] - Make homePage is not working


## Steps for reproduction

1. click button
2. expect xyz

## Code Review

- [ ] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
5de6)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env.example`
and the `builder/env-check.js` if mandatory
2023-10-30 12:18:35 +02:00

320 lines
8.4 KiB
TypeScript

import { describe, test, expect } from "@jest/globals";
import { enableMapSet } from "immer";
import type {
Instance,
Instances,
Prop,
Props,
DataSource,
DataSources,
Page,
} from "@webstudio-is/sdk";
import type { Project } from "@webstudio-is/project";
import { encodeDataSourceVariable } from "@webstudio-is/react-sdk";
import * as baseComponentMetas from "@webstudio-is/sdk-components-react/metas";
import { registerContainers } from "../sync";
import {
dataSourcesStore,
instancesStore,
pagesStore,
projectStore,
propsStore,
registeredComponentMetasStore,
selectedInstanceSelectorStore,
selectedPageIdStore,
} from "../nano-states";
import { onCopy, onPaste } from "./plugin-instance";
enableMapSet();
registerContainers();
registeredComponentMetasStore.set(new Map(Object.entries(baseComponentMetas)));
projectStore.set({ id: "my-project" } as Project);
pagesStore.set({
meta: {},
homePage: { id: "home-page", rootInstanceId: "body0" } as Page,
pages: [],
});
selectedPageIdStore.set("home-page");
const createInstance = (
id: Instance["id"],
component: string,
children: Instance["children"]
): Instance => {
return { type: "instance", id, component, children };
};
const getIdValuePair = <T extends { id: string }>(item: T) =>
[item.id, item] as const;
const toMap = <T extends { id: string }>(list: T[]) =>
new Map(list.map(getIdValuePair));
const getMapDifference = <Type extends Map<unknown, unknown>>(
left: Type,
right: Type
): Type => {
const leftSet = new Set(left.keys());
const rightSet = new Set(right.keys());
const difference = new Map() as Type;
for (const [key, value] of left) {
if (rightSet.has(key) === false) {
difference.set(key, value);
}
}
for (const [key, value] of right) {
if (leftSet.has(key) === false) {
difference.set(key, value);
}
}
return difference;
};
describe("paste target", () => {
// body0
// box1
// box2
const instances: Instances = toMap([
createInstance("body0", "Body", [
{ type: "id", value: "box1" },
{ type: "id", value: "box2" },
]),
createInstance("box1", "Box", []),
createInstance("box2", "Box", []),
] satisfies Instance[]);
test("is inside selected instance", () => {
instancesStore.set(instances);
selectedInstanceSelectorStore.set(["box1", "body0"]);
const clipboardData = onCopy() ?? "";
selectedInstanceSelectorStore.set(["box2", "body0"]);
onPaste(clipboardData);
const instancesDifference = getMapDifference(
instances,
instancesStore.get()
);
const [newBox1] = instancesDifference.keys();
expect(instancesStore.get().get("box2")).toEqual(
createInstance("box2", "Box", [{ type: "id", value: newBox1 }])
);
expect(instancesDifference).toEqual(
toMap([createInstance(newBox1, "Box", [])])
);
});
test("is after selected instance when same as copied", () => {
instancesStore.set(instances);
selectedInstanceSelectorStore.set(["box1", "body0"]);
onPaste(onCopy() ?? "");
const instancesDifference = getMapDifference(
instances,
instancesStore.get()
);
const [newBox1] = instancesDifference.keys();
expect(instancesStore.get().get("body0")).toEqual(
createInstance("body0", "Body", [
{ type: "id", value: "box1" },
{ type: "id", value: newBox1 },
{ type: "id", value: "box2" },
])
);
expect(instancesDifference).toEqual(
toMap([createInstance(newBox1, "Box", [])])
);
});
});
describe("data sources", () => {
// body0
// box1
// box2
const instances: Instances = toMap([
createInstance("body0", "Body", [{ type: "id", value: "box1" }]),
createInstance("box1", "Box", [{ type: "id", value: "box2" }]),
createInstance("box2", "Box", []),
] satisfies Instance[]);
const dataSources: DataSources = toMap([
{
id: "box1$state",
scopeInstanceId: "box1",
type: "variable",
name: "state",
value: { type: "string", value: "initial" },
},
{
id: "box2$stateInitial",
scopeInstanceId: "box2",
type: "expression",
name: "stateInitial",
code: `$ws$dataSource$box1$state === 'initial'`,
},
] satisfies DataSource[]);
const props: Props = toMap([
{
id: "box1$state",
instanceId: "box1",
type: "dataSource",
name: "state",
value: "box1$state",
},
{
id: "box2$state",
instanceId: "box2",
type: "dataSource",
name: "state",
value: "box1$state",
},
{
id: "box2$show",
instanceId: "box2",
type: "dataSource",
name: "show",
value: "box2$stateInitial",
},
{
id: "box2$onChange",
instanceId: "box2",
type: "action",
name: "onChange",
value: [
{
type: "execute",
args: ["value"],
code: `$ws$dataSource$box1$state = value`,
},
],
},
] satisfies Prop[]);
test("are copy pasted when scoped to copied instances", () => {
instancesStore.set(instances);
propsStore.set(props);
dataSourcesStore.set(dataSources);
selectedInstanceSelectorStore.set(["box1", "body0"]);
const clipboardData = onCopy() ?? "";
selectedInstanceSelectorStore.set(["body0"]);
onPaste(clipboardData);
const instancesDifference = getMapDifference(
instances,
instancesStore.get()
);
const [newBox1, newBox2] = instancesDifference.keys();
const dataSourcesDifference = getMapDifference(
dataSources,
dataSourcesStore.get()
);
const [newDataSource1, newDataSource2] = dataSourcesDifference.keys();
expect(dataSourcesDifference).toEqual(
toMap([
{
...dataSources.get("box1$state"),
id: newDataSource1,
scopeInstanceId: newBox1,
},
{
...dataSources.get("box2$stateInitial"),
id: newDataSource2,
scopeInstanceId: newBox2,
code: `${encodeDataSourceVariable(newDataSource1)} === 'initial'`,
},
])
);
const propsDifference = getMapDifference(props, propsStore.get());
const [newProp1, newProp2, newProp3, newProp4] = propsDifference.keys();
expect(propsDifference).toEqual(
toMap([
{
...props.get("box1$state"),
id: newProp1,
instanceId: newBox1,
value: newDataSource1,
},
{
...props.get("box2$state"),
id: newProp2,
instanceId: newBox2,
value: newDataSource1,
},
{
...props.get("box2$show"),
id: newProp3,
instanceId: newBox2,
value: newDataSource2,
},
{
...props.get("box2$onChange"),
id: newProp4,
instanceId: newBox2,
value: [
{
type: "execute",
args: ["value"],
code: `${encodeDataSourceVariable(newDataSource1)} = value`,
},
],
},
])
);
});
test("are inlined into props when not scoped to copied instances or depends on not scoped data source", () => {
instancesStore.set(instances);
propsStore.set(props);
dataSourcesStore.set(dataSources);
selectedInstanceSelectorStore.set(["box2", "box1", "body0"]);
const clipboardData = onCopy() ?? "";
selectedInstanceSelectorStore.set(["body0"]);
onPaste(clipboardData);
const instancesDifference = getMapDifference(
instances,
instancesStore.get()
);
const [newBox2] = instancesDifference.keys();
const dataSourcesDifference = getMapDifference(
dataSources,
dataSourcesStore.get()
);
expect(dataSourcesDifference).toEqual(new Map());
const propsDifference = getMapDifference(props, propsStore.get());
const [newProp1, newProp2, newProp3] = propsDifference.keys();
expect(propsDifference).toEqual(
toMap([
{
id: newProp1,
instanceId: newBox2,
type: "string",
name: "state",
value: "initial",
},
{
id: newProp2,
instanceId: newBox2,
type: "boolean",
name: "show",
value: true,
},
{
id: newProp3,
instanceId: newBox2,
type: "action",
name: "onChange",
value: [],
},
])
);
});
test.todo(
"preserve data sources not scoped to new instances within same scope"
);
});