mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-08 21:15:18 +09:00
## 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
320 lines
8.4 KiB
TypeScript
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"
|
|
);
|
|
});
|