diff --git a/cypress/e2e/slices/00-create.cy.js b/cypress/e2e/slices/00-create.cy.js index 588be449db..4bcda216f2 100644 --- a/cypress/e2e/slices/00-create.cy.js +++ b/cypress/e2e/slices/00-create.cy.js @@ -34,6 +34,22 @@ describe("Create Slices", () => { cy.getInputByLabel("Variation ID*").type("bar"); cy.get("#variation-add").submit(); + + cy.contains("button", "Simulate Slice").should("have.attr", "disabled"); + cy.contains("button", "Simulate Slice").realHover(); + cy.get("#simulator-button-tooltip").should("be.visible"); + cy.get("#simulator-button-tooltip").should( + "contain", + "Save your work in order to simulate" + ); + cy.contains("button", "Update screenshot").should("have.attr", "disabled"); + cy.contains("button", "Update screenshot").realHover(); + cy.get("#update-screenshot-button-tooltip").should("be.visible"); + cy.get("#update-screenshot-button-tooltip").should( + "contain", + "Save your work in order to update the screenshot" + ); + cy.location("pathname", { timeout: 20000 }).should( "eq", `/${lib}/${editedSliceName}/bar` @@ -47,6 +63,12 @@ describe("Create Slices", () => { cy.contains("Save to File System").click(); + cy.contains("button", "Simulate Slice").should("not.have.attr", "disabled"); + cy.contains("button", "Update screenshot").should( + "not.have.attr", + "disabled" + ); + // simulator simulatorPage.setup(); @@ -88,4 +110,30 @@ describe("Create Slices", () => { .its("content.text") .should("equal", "🎉"); }); + + it("allows drag n drop to the top position", () => { + // inspired by https://github.com/atlassian/react-beautiful-dnd/blob/master/cypress/integration/reorder.spec.js + // could not get it to work with mouse events + + // TODO: use faster fixtures + cy.createSlice(lib, sliceId, sliceName); + + cy.get('ul[data-cy="slice-non-repeatable-zone"] > li') + .eq(1) + .contains("Description"); + + cy.get("[data-rbd-draggable-id='list-item-description'] button") + .first() + .focus() + .trigger("keydown", { keyCode: 32 }); + cy.get("[data-rbd-draggable-id='list-item-description'] button") + .first() + .trigger("keydown", { keyCode: 38, force: true }) + .wait(1 * 1000) + .trigger("keydown", { keyCode: 32, force: true }); + + cy.get('ul[data-cy="slice-non-repeatable-zone"] > li') + .eq(0) + .contains("Description"); + }); }); diff --git a/cypress/e2e/user-flows/scenario_custom_screenshots.cy.js b/cypress/e2e/user-flows/scenario_custom_screenshots.cy.js index 1babcd9469..67c98656da 100644 --- a/cypress/e2e/user-flows/scenario_custom_screenshots.cy.js +++ b/cypress/e2e/user-flows/scenario_custom_screenshots.cy.js @@ -3,7 +3,7 @@ import { SliceCard } from "../../pages/slices/sliceCard"; import { SlicePage } from "../../pages/slices/slicePage"; import { Menu } from "../../pages/Menu"; -describe("I am an existing SM user and I want to upload screenshots on variations of an existing Slice", () => { +describe.skip("I am an existing SM user and I want to upload screenshots on variations of an existing Slice", () => { const random = Date.now(); const slice = { diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index 69cca5cd40..65a4e5e553 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -20,3 +20,5 @@ import "./commands"; // require('./commands') import "./assertions"; + +import "cypress-real-events"; diff --git a/package-lock.json b/package-lock.json index e695c5c797..4cf3d18688 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "dependencies": { "cypress": "^12.1.0", "cypress-localstorage-commands": "^1.7.0", + "cypress-real-events": "^1.7.6", "cypress-wait-until": "^1.7.2", "start-server-and-test": "^1.14.0" }, @@ -5008,6 +5009,14 @@ "cypress": ">=2.1.0" } }, + "node_modules/cypress-real-events": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/cypress-real-events/-/cypress-real-events-1.7.6.tgz", + "integrity": "sha512-yP6GnRrbm6HK5q4DH6Nnupz37nOfZu/xn1xFYqsE2o4G73giPWQOdu6375QYpwfU1cvHNCgyD2bQ2hPH9D7NMw==", + "peerDependencies": { + "cypress": "^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x" + } + }, "node_modules/cypress-wait-until": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-1.7.2.tgz", @@ -17445,6 +17454,12 @@ "integrity": "sha512-4v4FrDRPimMStWmiGikyN7OmO2rH0MLI7DMk76dBpb9/sJqCDwa7fAHTUDx6avUEDmO7TFQFgaT3OQTr1HqasQ==", "requires": {} }, + "cypress-real-events": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/cypress-real-events/-/cypress-real-events-1.7.6.tgz", + "integrity": "sha512-yP6GnRrbm6HK5q4DH6Nnupz37nOfZu/xn1xFYqsE2o4G73giPWQOdu6375QYpwfU1cvHNCgyD2bQ2hPH9D7NMw==", + "requires": {} + }, "cypress-wait-until": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-1.7.2.tgz", diff --git a/package.json b/package.json index a1f1d770a3..ec7baacdc3 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "dependencies": { "cypress": "^12.1.0", "cypress-localstorage-commands": "^1.7.0", + "cypress-real-events": "^1.7.6", "cypress-wait-until": "^1.7.2", "start-server-and-test": "^1.14.0" }, diff --git a/packages/client/package-lock.json b/packages/client/package-lock.json index a73e6c66b8..ab99200025 100644 --- a/packages/client/package-lock.json +++ b/packages/client/package-lock.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/client", - "version": "1.1.16", + "version": "1.1.17-dev-070.11", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/client/package.json b/packages/client/package.json index 0b477d67eb..9bff668e03 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/client", - "version": "1.1.16", + "version": "1.1.17-dev-070.11", "main": "./build/index.js", "engines": { "node": ">=14" @@ -23,7 +23,7 @@ }, "dependencies": { "@prismicio/types-internal": "^1.5.3", - "@slicemachine/core": "1.1.16", + "@slicemachine/core": "1.1.17-dev-070.11", "axios": "^0.27.2", "form-data": "^3.0.0", "fp-ts": "^2.11.5", diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index 9251df2568..87087bc35d 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/core", - "version": "1.1.16", + "version": "1.1.17-dev-070.11", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/core/package.json b/packages/core/package.json index 15795df3e5..a6faf90efa 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/core", - "version": "1.1.16", + "version": "1.1.17-dev-070.11", "private": false, "main": "./build/index.js", "engines": { diff --git a/packages/init/package-lock.json b/packages/init/package-lock.json index 413abde691..44126045d2 100644 --- a/packages/init/package-lock.json +++ b/packages/init/package-lock.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/init", - "version": "1.1.16", + "version": "1.1.17-dev-070.11", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/init/package.json b/packages/init/package.json index 45cbe6dad0..0b97203425 100644 --- a/packages/init/package.json +++ b/packages/init/package.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/init", - "version": "1.1.16", + "version": "1.1.17-dev-070.11", "description": "The easiest way to add Slice Machine to your project", "license": "MIT", "bin": { @@ -21,8 +21,8 @@ "dependencies": { "@hapi/hapi": "^20.2.0", "@prismicio/types-internal": "^1.5.3", - "@slicemachine/client": "1.1.16", - "@slicemachine/core": "1.1.16", + "@slicemachine/client": "1.1.17-dev-070.11", + "@slicemachine/core": "1.1.17-dev-070.11", "adm-zip": "^0.5.9", "analytics-node": "^6.0.0", "axios": "^0.24.0", diff --git a/packages/slice-machine/.eslintrc.js b/packages/slice-machine/.eslintrc.js index 0bfb2f8733..9e33661a34 100644 --- a/packages/slice-machine/.eslintrc.js +++ b/packages/slice-machine/.eslintrc.js @@ -23,4 +23,8 @@ module.exports = { "eslint-config-prettier", ], ignorePatterns: ["build", "templates", "**/tests/**", "helpers/**"], + rules: { + "@typescript-eslint/prefer-nullish-coalescing": "warn", + "@typescript-eslint/strict-boolean-expressions": "warn", + }, }; diff --git a/packages/slice-machine/components/AuthInstructions/index.js b/packages/slice-machine/components/AuthInstructions/index.js index 46d12db3ec..2b6c7b09c7 100644 --- a/packages/slice-machine/components/AuthInstructions/index.js +++ b/packages/slice-machine/components/AuthInstructions/index.js @@ -22,7 +22,7 @@ const AuthInstructions = () => ( borderBottom: (t) => `1px solid ${t.colors?.borders}`, }} > - Login to Prismic + Log in to Prismic )} > diff --git a/packages/slice-machine/components/ChangesEmptyPage/AuthErrorPage.tsx b/packages/slice-machine/components/ChangesEmptyPage/AuthErrorPage.tsx index fba9bd7a2e..f1b021d3b5 100644 --- a/packages/slice-machine/components/ChangesEmptyPage/AuthErrorPage.tsx +++ b/packages/slice-machine/components/ChangesEmptyPage/AuthErrorPage.tsx @@ -25,10 +25,10 @@ export const AuthErrorPage = () => { Could not access your repository - You need to login to Prismic in order to push your changes. + You need to log in to Prismic in order to push your changes. diff --git a/packages/slice-machine/components/FormFields/Array.js b/packages/slice-machine/components/FormFields/Array.js index 316ed87e7d..f7ea13454f 100644 --- a/packages/slice-machine/components/FormFields/Array.js +++ b/packages/slice-machine/components/FormFields/Array.js @@ -31,7 +31,7 @@ const FormFieldArray = ({ refs.current[len - 1].focus(); } setPrevLen(len); - }, [refs.current.length]); + }, [refs.current.length, focusOnNewEntry, prevLen]); return ( diff --git a/packages/slice-machine/components/FormFields/CheckboxControl.js b/packages/slice-machine/components/FormFields/CheckboxControl.js index 538c481701..c1335ebb4a 100644 --- a/packages/slice-machine/components/FormFields/CheckboxControl.js +++ b/packages/slice-machine/components/FormFields/CheckboxControl.js @@ -1,21 +1,26 @@ import { useState, useEffect } from "react"; -import { useFormikContext } from "formik"; import { FormFieldCheckbox } from "./"; +/** + * This components allows to set/unset the value of an arbitrary field via checking/unchecking the box + * + * field: the controlled Formik field that we want to manipulate + * label: a function of text displayed next to the checkbox. Can be either a string or a function + * controlledValue: the value of the field being controlled + * setControlledValue: function to update the value of the controlled value in Formik + * buildControlledValue: function to build the new value based on the current controlled value and the state of the checkbox + * + */ const CheckboxControl = ({ field, - helpers, label, defaultValue, onChange, - getFieldControl, - setControlFromField, + controlledValue, + setControlledValue, + buildControlledValue, }) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { values } = useFormikContext(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call - const fieldControl = getFieldControl(values); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment const [isChecked, setCheck] = useState( // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access @@ -24,13 +29,15 @@ const CheckboxControl = ({ useEffect(() => { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - helpers.setValue( - setControlFromField - ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-call - setControlFromField(fieldControl, isChecked) - : fieldControl - ); - }, [isChecked, fieldControl]); + buildControlledValue + ? // eslint-disable-next-line @typescript-eslint/no-unsafe-call + setControlledValue(buildControlledValue(controlledValue)) + : // eslint-disable-next-line @typescript-eslint/no-unsafe-call + setControlledValue(buildControlledValue); + // Adding the missing dependency to this hook triggers an infinite loop + // We decided to leave it for now, waiting for a bigger refactor + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isChecked, controlledValue, buildControlledValue]); return ( setCheck(value) && onChange && onChange(value)} label={ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call - typeof label === "function" ? label(fieldControl, isChecked) : label + typeof label === "function" ? label(controlledValue, isChecked) : label } /> ); diff --git a/packages/slice-machine/components/LoginModal/index.tsx b/packages/slice-machine/components/LoginModal/index.tsx index 13ea3c5638..929836aa7e 100644 --- a/packages/slice-machine/components/LoginModal/index.tsx +++ b/packages/slice-machine/components/LoginModal/index.tsx @@ -77,7 +77,7 @@ const LoginModal: React.FunctionComponent = () => { closeModals(); } catch (e) { stopLoadingLogin(); - openToaster("Logging fail", ToasterType.ERROR); + openToaster("Login failed", ToasterType.ERROR); } }; @@ -157,7 +157,7 @@ const LoginModal: React.FunctionComponent = () => { {isLoginLoading ? ( ) : ( - <>Signin to Prismic + <>Log in to Prismic )} diff --git a/packages/slice-machine/components/Simulator/components/SetupModal/CodeBlock.tsx b/packages/slice-machine/components/Simulator/components/SetupModal/CodeBlock.tsx index 276ce207a5..3e16755250 100644 --- a/packages/slice-machine/components/Simulator/components/SetupModal/CodeBlock.tsx +++ b/packages/slice-machine/components/Simulator/components/SetupModal/CodeBlock.tsx @@ -5,33 +5,36 @@ import { MdCheck, MdContentCopy } from "react-icons/md"; import CodeBlock, { Language } from "../../../CodeBlock"; import { IconType } from "react-icons"; +import VersionBadgeList from "./VersionBadgeList"; + +type Code = { + text: string; + version: string; +}; const CodeBlockWithCopy: React.FC<{ - children: string; + code: Code | Code[]; customCopyText?: string; fileName: string; FileIcon: IconType; lang?: Language; fullHeightCode?: boolean; -}> = ({ - children, - customCopyText, - fileName, - FileIcon, - lang, - fullHeightCode, -}) => { +}> = ({ customCopyText, fileName, FileIcon, lang, fullHeightCode, code }) => { + const codeList = Array.isArray(code) ? code : [code]; const { theme } = useThemeUI(); const [isCopied, setIsCopied] = useState(false); + const [currentCode, setCurrentCode] = useState(codeList[0]); const copy = (): void => { - children && - navigator.clipboard.writeText(customCopyText || children).then(() => { - setIsCopied(true); - setTimeout(() => { - setIsCopied(false); - }, 1200); - }); + currentCode && + navigator.clipboard + .writeText(customCopyText || currentCode.text) + .then(() => { + setIsCopied(true); + setTimeout(() => { + setIsCopied(false); + }, 1200); + }); }; return ( @@ -62,43 +65,58 @@ const CodeBlockWithCopy: React.FC<{ fontSize: "12px", }} Header={() => ( - - - {fileName} - - - + + {" "} + {fileName} + + + + {codeList.length > 1 ? ( + c.version)} + selectedVersion={currentCode.version} + setSelectedVersion={(newVersion) => { + setCurrentCode( + (code) => + codeList.find((c) => c.version === newVersion) || code + ); + }} + /> + ) : null} + )} > - {children} + {currentCode.text} ); diff --git a/packages/slice-machine/components/Simulator/components/SetupModal/VersionBadgeList.tsx b/packages/slice-machine/components/Simulator/components/SetupModal/VersionBadgeList.tsx new file mode 100644 index 0000000000..a6b13da13d --- /dev/null +++ b/packages/slice-machine/components/Simulator/components/SetupModal/VersionBadgeList.tsx @@ -0,0 +1,61 @@ +import { Flex } from "@components/Flex"; +import { FC, ReactNode } from "react"; +import { Button, ThemeUIStyleObject } from "theme-ui"; + +export const VersionBadge: FC<{ + children: ReactNode; + isSelected: boolean; + sx?: ThemeUIStyleObject; + onClick: () => void; +}> = ({ children, isSelected, sx, onClick }) => ( + +); + +const VersionBadgeList: FC<{ + versions: string[]; + selectedVersion: string; + setSelectedVersion: (s: string) => void; +}> = ({ versions, selectedVersion, setSelectedVersion }) => ( + button:not(:last-child)": { + mr: "12px", + }, + }} + > + {versions.map((version) => ( + setSelectedVersion(version)} + > + {version} + + ))} + +); + +export default VersionBadgeList; diff --git a/packages/slice-machine/components/Simulator/components/SetupModal/index.tsx b/packages/slice-machine/components/Simulator/components/SetupModal/index.tsx index cd5ddb9e82..033ccc1e29 100644 --- a/packages/slice-machine/components/Simulator/components/SetupModal/index.tsx +++ b/packages/slice-machine/components/Simulator/components/SetupModal/index.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import Card from "@components/Card"; import SliceMachineModal from "@components/SliceMachineModal"; import { Box, Flex, Heading, Text } from "theme-ui"; @@ -88,7 +89,30 @@ const SetupModal: React.FC<{ isOpen: boolean }> = ({ isOpen }) => { linkToTroubleshootingDocs: getLinkToTroubleshootingDocs(state), })); - const setupData = getStepperConfigurationByFramework(framework); + const setupData = useMemo( + () => getStepperConfigurationByFramework(framework), + [framework] + ); + + const Steps = useMemo( + () => + setupData.steps.map((Step, i) => { + return ( + + + + ); + }), + [setupData, linkToStorybookDocs, linkToTroubleshootingDocs] + ); return ( @@ -129,23 +153,7 @@ const SetupModal: React.FC<{ isOpen: boolean }> = ({ isOpen }) => { ))} {isSimulatorAvailableForFramework ? ( - <> - {setupData.steps.map((Step, i) => { - return ( - - - - ); - })} - + Steps ) : ( { return ( - - Run this command to install the simulator package via npm: - - {npm} - - - Alternatively, you can use yarn: - - - {yarn} - + code={[ + { + text: yarn, + version: "yarn", + }, + { + text: npm, + version: "npm", + }, + ]} + /> ); }; @@ -89,9 +85,11 @@ export const CreatePage = - {code} - + code={{ + text: code, + version: "js", + }} + /> ); }; @@ -115,9 +113,11 @@ export const UpdateSmJson = fileName="sm.json" lang="json" customCopyText={`"localSliceSimulatorURL": "http://localhost:3000/slice-simulator"`} - > - {code} - + code={{ + text: code, + version: "JSON", + }} + /> ); }; diff --git a/packages/slice-machine/components/Simulator/components/SetupModal/steps/nuxt.tsx b/packages/slice-machine/components/Simulator/components/SetupModal/steps/nuxt.tsx index ca33413334..b82132d335 100644 --- a/packages/slice-machine/components/Simulator/components/SetupModal/steps/nuxt.tsx +++ b/packages/slice-machine/components/Simulator/components/SetupModal/steps/nuxt.tsx @@ -57,9 +57,14 @@ const UpdateNuxtConfig: React.FunctionComponent = () => { In your nuxt.config.js file, you need to add at the beginning the following line: - - import smConfig from "./sm.json"; - + Inside of the export statement, add these two properties: @@ -67,9 +72,11 @@ const UpdateNuxtConfig: React.FunctionComponent = () => { fileName="nuxt.config.js" FileIcon={AiOutlineFileText} fullHeightCode - > - {NuxtConfigInstructions} - + code={{ + text: NuxtConfigInstructions, + version: "js", + }} + /> ); }; diff --git a/packages/slice-machine/components/Simulator/components/SetupModal/steps/previousNuxt.tsx b/packages/slice-machine/components/Simulator/components/SetupModal/steps/previousNuxt.tsx index 4eb7c0d471..2bb5273fde 100644 --- a/packages/slice-machine/components/Simulator/components/SetupModal/steps/previousNuxt.tsx +++ b/packages/slice-machine/components/Simulator/components/SetupModal/steps/previousNuxt.tsx @@ -57,15 +57,19 @@ const UpdateNuxtConfig: React.FunctionComponent = () => { In your nuxt.config.js file, you need to add at the beginning the following line: - - import smConfig from "./sm.json" - + Inside of the export statement, add these two properties - - {NuxtConfigInstructions} - + ); }; diff --git a/packages/slice-machine/components/Simulator/index.tsx b/packages/slice-machine/components/Simulator/index.tsx index d59fd98d7a..ba2cb46242 100644 --- a/packages/slice-machine/components/Simulator/index.tsx +++ b/packages/slice-machine/components/Simulator/index.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { SharedSliceEditor } from "@prismicio/editor-fields"; import { defaultSharedSliceContent } from "@src/utils/editor"; @@ -52,8 +52,6 @@ export enum UiState { SUCCESS = "SUCCESS", } -const MemoedSetupModal = memo(SetupModal); - const Simulator: ComponentWithSliceProps = ({ slice, variation }) => { const { checkSimulatorSetup, connectToSimulatorIframe, saveSliceMock } = useSliceMachineActions(); @@ -187,7 +185,7 @@ const Simulator: ComponentWithSliceProps = ({ slice, variation }) => { return ( - +
- {({ errors, values, setFieldValue }) => ( + {({ errors, values, setFieldValue, isValid }) => ( ) => { setFieldValue("id", e.target.value); - setFieldValue("actionType", ActionType.UPDATE); }} />