From 1191f82c643356a3ff5729edc81ef0b501f81edc Mon Sep 17 00:00:00 2001 From: Matt Phillips Date: Wed, 31 Aug 2022 11:21:15 +0100 Subject: [PATCH] feat(SandpackTests): Add SandpackTests component (#562) Co-authored-by: Danilo Woznica --- .codesandbox/tasks.json | 10 +- cypress/integration/Templates.spec.js | 2 + sandpack-client/src/types.ts | 120 +++++ sandpack-react/.storybook/main.css | 26 + sandpack-react/package.json | 1 + sandpack-react/src/Playground.stories.tsx | 82 +++- .../OpenInCodeSandboxButton.tsx | 4 +- sandpack-react/src/common/RunButton.tsx | 4 +- sandpack-react/src/common/Stack.tsx | 4 + .../src/components/CodeEditor/index.tsx | 2 +- .../src/components/Console/Button.tsx | 4 +- .../src/components/Console/Header.tsx | 1 + .../src/components/FileExplorer/index.tsx | 2 +- .../src/components/FileTabs/index.tsx | 2 +- .../src/components/Navigator/index.tsx | 34 +- .../src/components/Preview/RefreshButton.tsx | 4 +- .../src/components/Preview/index.tsx | 45 +- .../src/components/Tests/Describes.tsx | 50 ++ .../src/components/Tests/FormattedError.tsx | 136 ++++++ .../src/components/Tests/Header.tsx | 107 ++++ .../src/components/Tests/RunButton.tsx | 33 ++ .../src/components/Tests/SandpackTests.tsx | 455 ++++++++++++++++++ sandpack-react/src/components/Tests/Specs.tsx | 205 ++++++++ .../src/components/Tests/Summary.tsx | 97 ++++ .../src/components/Tests/Tests.stories.tsx | 276 +++++++++++ sandpack-react/src/components/Tests/Tests.tsx | 80 +++ sandpack-react/src/components/Tests/index.tsx | 1 + sandpack-react/src/components/Tests/style.ts | 39 ++ sandpack-react/src/components/Tests/utils.ts | 85 ++++ sandpack-react/src/components/index.ts | 1 + sandpack-react/src/hooks/index.ts | 1 + sandpack-react/src/hooks/useSandpackClient.ts | 60 +++ .../src/presets/Sandpack.stories.tsx | 18 + sandpack-react/src/presets/Sandpack.test.tsx | 2 +- sandpack-react/src/presets/Sandpack.tsx | 63 ++- .../styles/__snapshots__/styles.test.ts.snap | 1 + sandpack-react/src/styles/index.ts | 2 +- sandpack-react/src/styles/shared.ts | 12 +- .../src/templates/Templates.stories.tsx | 5 +- sandpack-react/src/templates/angular.ts | 2 +- sandpack-react/src/templates/index.tsx | 3 + .../src/templates/react-typescript.ts | 2 +- sandpack-react/src/templates/react.ts | 2 +- sandpack-react/src/templates/solid.ts | 2 +- sandpack-react/src/templates/svelte.ts | 2 +- sandpack-react/src/templates/tests-ts.ts | 40 ++ .../src/templates/vanilla-typescript.ts | 2 +- sandpack-react/src/templates/vanilla.ts | 2 +- sandpack-react/src/templates/vue.ts | 2 +- sandpack-react/src/templates/vue3.ts | 2 +- sandpack-react/src/types.ts | 1 + .../docs/docs/advanced-usage/components.md | 181 +++++-- website/docs/docs/advanced-usage/hooks.md | 20 +- website/docs/docs/intro.md | 6 +- yarn.lock | 5 + 55 files changed, 2190 insertions(+), 160 deletions(-) create mode 100644 sandpack-react/src/components/Tests/Describes.tsx create mode 100644 sandpack-react/src/components/Tests/FormattedError.tsx create mode 100644 sandpack-react/src/components/Tests/Header.tsx create mode 100644 sandpack-react/src/components/Tests/RunButton.tsx create mode 100644 sandpack-react/src/components/Tests/SandpackTests.tsx create mode 100644 sandpack-react/src/components/Tests/Specs.tsx create mode 100644 sandpack-react/src/components/Tests/Summary.tsx create mode 100644 sandpack-react/src/components/Tests/Tests.stories.tsx create mode 100644 sandpack-react/src/components/Tests/Tests.tsx create mode 100644 sandpack-react/src/components/Tests/index.tsx create mode 100644 sandpack-react/src/components/Tests/style.ts create mode 100644 sandpack-react/src/components/Tests/utils.ts create mode 100644 sandpack-react/src/hooks/useSandpackClient.ts create mode 100644 sandpack-react/src/templates/tests-ts.ts diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json index fd00b0e6b..b91a9d0dd 100644 --- a/.codesandbox/tasks.json +++ b/.codesandbox/tasks.json @@ -46,14 +46,18 @@ "command": "yarn workspace @codesandbox/sandpack-react build" }, "dev:website-docs": { - "name": "Website docs", + "name": "Documentation", "command": "yarn dev:docs", "runAtStart": false }, "dev:sandpack-react": { "name": "Storybook", "command": "yarn dev:react", - "runAtStart": true + "runAtStart": true, + "preview": { + "port": 6006, + "pr-link": "direct" + } }, "dev:website-landing": { "name": "Website landing", @@ -66,4 +70,4 @@ "runAtStart": false } } -} \ No newline at end of file +} diff --git a/cypress/integration/Templates.spec.js b/cypress/integration/Templates.spec.js index 409a5eb15..1c5b929c8 100644 --- a/cypress/integration/Templates.spec.js +++ b/cypress/integration/Templates.spec.js @@ -20,6 +20,8 @@ const getIframeBody = () => { describe("Templates", () => { Object.keys(SANDBOX_TEMPLATES).forEach((template) => { + if (template === "test-ts") return null; // tests there is no preview + it(`Should run the ${template} template`, () => { accessPage(template); diff --git a/sandpack-client/src/types.ts b/sandpack-client/src/types.ts index 7d71ba9c2..81d81fc03 100644 --- a/sandpack-client/src/types.ts +++ b/sandpack-client/src/types.ts @@ -169,6 +169,125 @@ export interface NpmRegistry { registryAuthToken?: string; } +type TestStatus = "running" | "pass" | "fail"; + +export type TestError = Error & { + matcherResult?: boolean; + mappedErrors?: Array<{ + fileName: string; + _originalFunctionName: string; + _originalColumnNumber: number; + _originalLineNumber: number; + _originalScriptCode: Array<{ + lineNumber: number; + content: string; + highlight: boolean; + }> | null; + }>; +}; + +export interface Test { + name: string; + blocks: string[]; + status: TestStatus; + path: string; + errors: TestError[]; + duration?: number | undefined; +} + +export type SandboxTestMessage = + | RunAllTests + | RunTests + | ClearJestErrors + | ({ type: "test" } & ( + | InitializedTestsMessage + | TestCountMessage + | TotalTestStartMessage + | TotalTestEndMessage + | AddFileMessage + | RemoveFileMessage + | FileErrorMessage + | DescribeStartMessage + | DescribeEndMessage + | AddTestMessage + | TestStartMessage + | TestEndMessage + )); + +interface InitializedTestsMessage { + event: "initialize_tests"; +} + +interface ClearJestErrors { + type: "action"; + action: "clear-errors"; + source: "jest"; + path: string; +} + +interface TestCountMessage { + event: "test_count"; + count: number; +} + +interface TotalTestStartMessage { + event: "total_test_start"; +} + +interface TotalTestEndMessage { + event: "total_test_end"; +} + +interface AddFileMessage { + event: "add_file"; + path: string; +} + +interface RemoveFileMessage { + event: "remove_file"; + path: string; +} + +interface FileErrorMessage { + event: "file_error"; + path: string; + error: TestError; +} + +interface DescribeStartMessage { + event: "describe_start"; + blockName: string; +} + +interface DescribeEndMessage { + event: "describe_end"; +} + +interface AddTestMessage { + event: "add_test"; + testName: string; + path: string; +} + +interface TestStartMessage { + event: "test_start"; + test: Test; +} + +interface TestEndMessage { + event: "test_end"; + test: Test; +} + +interface RunAllTests { + type: "run-all-tests"; +} + +interface RunTests { + type: "run-tests"; + path: string; +} + export type SandpackMessage = BaseSandpackMessage & ( | { @@ -258,6 +377,7 @@ export type SandpackMessage = BaseSandpackMessage & data: string[]; }>; } + | SandboxTestMessage ); export type Template = diff --git a/sandpack-react/.storybook/main.css b/sandpack-react/.storybook/main.css index ef488d1f1..56bcca3f0 100644 --- a/sandpack-react/.storybook/main.css +++ b/sandpack-react/.storybook/main.css @@ -10,3 +10,29 @@ p { margin: 0; margin-bottom: 0.3em; } + +.playground-grid { + display: grid; + grid-template-columns: 200px 400px 400px; + grid-template-rows: repeat(2, 250px); + gap: 1px; +} + +.playground-grid .sp-file-explorer { + grid-area: 1 / 1 / 3 / 2; +} + +.playground-grid .sp-editor { + grid-area: 1 / 2 / 2 / 3; +} + +.playground-grid .sp-previe { + grid-area: 1 / 3 / 2 / 4; +} + +.playground-grid .sp-consol { + grid-area: 2 / 2 / 3 / 3; +} +.playground-grid .sp-tests { + grid-area: 2 / 3 / 3 / 4; +} diff --git a/sandpack-react/package.json b/sandpack-react/package.json index 9bf60ec34..e729f8c84 100644 --- a/sandpack-react/package.json +++ b/sandpack-react/package.json @@ -51,6 +51,7 @@ "@codesandbox/sandpack-client": "^1.5.4", "@react-hook/intersection-observer": "^3.1.1", "@stitches/core": "^1.2.6", + "clean-set": "^1.1.2", "codesandbox-import-util-types": "^2.2.3", "lodash.isequal": "^4.5.0", "lz-string": "^1.4.4", diff --git a/sandpack-react/src/Playground.stories.tsx b/sandpack-react/src/Playground.stories.tsx index fe6988faa..a8f969d41 100644 --- a/sandpack-react/src/Playground.stories.tsx +++ b/sandpack-react/src/Playground.stories.tsx @@ -12,6 +12,7 @@ import { SandpackLayout, SandpackFileExplorer, SandpackConsole, + SandpackTests, } from "./"; export default { @@ -25,21 +26,22 @@ export const Main = (): JSX.Element => { Editor: true, FileExplorer: true, Console: true, + Tests: true, }, Options: { showTabs: true, showLineNumbers: true, showInlineErrors: true, closableTabs: true, - wrapContent: true, + wrapContent: false, readOnly: false, showReadOnly: true, showNavigator: true, showRefreshButton: true, consoleShowHeader: true, }, - Template: "react" as const, - Theme: "auto", + Template: "exhaustedFilesTests" as const, + Theme: "light", }); const update = (key: any, value: any): void => { @@ -80,6 +82,9 @@ export const Main = (): JSX.Element => { } value={config.Template} > + {Object.keys(SANDBOX_TEMPLATES).map((tem) => ( ))} @@ -135,25 +140,68 @@ export const Main = (): JSX.Element => { - {config.Components.FileExplorer && } - {config.Components.Editor && ( - - )} - {config.Components.Preview && ( - - )} - {config.Components.Console && ( - - )} +
+ {config.Components.FileExplorer && } + {config.Components.Editor && ( + + )} + {config.Components.Preview && ( + + )} + + {config.Components.Console && ( + + )} + {config.Components.Tests && } +
); }; + +const defaultTemplate = SANDBOX_TEMPLATES["react-ts"]; + +const exhaustedFilesTests = { + ...defaultTemplate, + dependencies: { + ...defaultTemplate.dependencies, + "@testing-library/react": "^13.3.0", + "@testing-library/jest-dom": "^5.16.5", + }, + files: { + "/src/index.tsx": SANDBOX_TEMPLATES["react-ts"].files["/index.tsx"], + "/src/App.tsx": `console.log("Hello world");\n\n${SANDBOX_TEMPLATES["react-ts"].files["/App.tsx"].code}`, + "/src/App.test.tsx": `import '@testing-library/jest-dom'; +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders welcome message', () => { + render(); + expect(screen.getByText('Hello World')).toBeInTheDocument(); +});`, + "/src/styles.css": SANDBOX_TEMPLATES["react-ts"].files["/styles.css"], + "/package.json": JSON.stringify({ main: "/src/index.tsx" }), + }, +}; diff --git a/sandpack-react/src/common/OpenInCodeSandboxButton/OpenInCodeSandboxButton.tsx b/sandpack-react/src/common/OpenInCodeSandboxButton/OpenInCodeSandboxButton.tsx index 6cd0bc692..7a5615403 100644 --- a/sandpack-react/src/common/OpenInCodeSandboxButton/OpenInCodeSandboxButton.tsx +++ b/sandpack-react/src/common/OpenInCodeSandboxButton/OpenInCodeSandboxButton.tsx @@ -6,7 +6,7 @@ import { THEME_PREFIX } from "../../styles"; import { buttonClassName, iconStandaloneClassName, - actionButtonClassName, + roundedButtonClassName, } from "../../styles/shared"; import { classNames } from "../../utils/classNames"; @@ -24,7 +24,7 @@ export const OpenInCodeSandboxButton = (): JSX.Element | null => { c("button", "icon-standalone"), buttonClassName, iconStandaloneClassName, - actionButtonClassName + roundedButtonClassName )} > diff --git a/sandpack-react/src/common/RunButton.tsx b/sandpack-react/src/common/RunButton.tsx index 956c8a459..27db5e91e 100644 --- a/sandpack-react/src/common/RunButton.tsx +++ b/sandpack-react/src/common/RunButton.tsx @@ -4,7 +4,7 @@ import * as React from "react"; import { useSandpack } from "../hooks/useSandpack"; import { RunIcon } from "../icons"; import { css, THEME_PREFIX } from "../styles"; -import { actionButtonClassName, buttonClassName } from "../styles/shared"; +import { roundedButtonClassName, buttonClassName } from "../styles/shared"; import { classNames } from "../utils/classNames"; const runButtonClassName = css({ @@ -30,7 +30,7 @@ export const RunButton = ({ className={classNames( c("button"), buttonClassName, - actionButtonClassName, + roundedButtonClassName, runButtonClassName, className )} diff --git a/sandpack-react/src/common/Stack.tsx b/sandpack-react/src/common/Stack.tsx index 8800096b1..8cbc7c098 100644 --- a/sandpack-react/src/common/Stack.tsx +++ b/sandpack-react/src/common/Stack.tsx @@ -15,6 +15,10 @@ export const stackClassName = css({ backgroundColor: "$colors$surface1", transition: "height $transitions$default", gap: 1, // border between components + + [`&:has(.${THEME_PREFIX}-stack)`]: { + backgroundColor: "$colors$surface2", + }, }); /** diff --git a/sandpack-react/src/components/CodeEditor/index.tsx b/sandpack-react/src/components/CodeEditor/index.tsx index aeb784a8a..dec46c8ac 100644 --- a/sandpack-react/src/components/CodeEditor/index.tsx +++ b/sandpack-react/src/components/CodeEditor/index.tsx @@ -104,7 +104,7 @@ export const SandpackCodeEditor = React.forwardRef< }; return ( - + {shouldShowTabs && }
diff --git a/sandpack-react/src/components/Console/Button.tsx b/sandpack-react/src/components/Console/Button.tsx index aacc9d1dc..745e9b595 100644 --- a/sandpack-react/src/components/Console/Button.tsx +++ b/sandpack-react/src/components/Console/Button.tsx @@ -6,7 +6,7 @@ import { css } from "../../styles"; import { buttonClassName, iconStandaloneClassName, - actionButtonClassName, + roundedButtonClassName, } from "../../styles/shared"; import { classNames } from "../../utils/classNames"; @@ -19,7 +19,7 @@ export const Button: React.FC<{ onClick: () => void }> = ({ onClick }) => { c("button", "icon-standalone"), buttonClassName, iconStandaloneClassName, - actionButtonClassName, + roundedButtonClassName, css({ position: "absolute", bottom: "$space$2", diff --git a/sandpack-react/src/components/Console/Header.tsx b/sandpack-react/src/components/Console/Header.tsx index ea90b5fed..0b6776665 100644 --- a/sandpack-react/src/components/Console/Header.tsx +++ b/sandpack-react/src/components/Console/Header.tsx @@ -11,6 +11,7 @@ export const Header: React.FC = () => { css({ borderBottom: "1px solid $colors$surface2", padding: "$space$3 $space$2", + height: "$layout$headerHeight", }) )} > diff --git a/sandpack-react/src/components/FileExplorer/index.tsx b/sandpack-react/src/components/FileExplorer/index.tsx index 70ea9da4a..076631cff 100644 --- a/sandpack-react/src/components/FileExplorer/index.tsx +++ b/sandpack-react/src/components/FileExplorer/index.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { stackClassName } from "../.."; +import { stackClassName } from "../.."; import { useSandpack } from "../../hooks/useSandpack"; import { css, THEME_PREFIX } from "../../styles"; import { classNames } from "../../utils/classNames"; diff --git a/sandpack-react/src/components/FileTabs/index.tsx b/sandpack-react/src/components/FileTabs/index.tsx index 23b78ae21..f556bbe43 100644 --- a/sandpack-react/src/components/FileTabs/index.tsx +++ b/sandpack-react/src/components/FileTabs/index.tsx @@ -47,7 +47,7 @@ const closeButtonClassName = css({ */ export const tabButton = css({ padding: "0 $space$2", - height: "40px", + height: "$layout$headerHeight", whiteSpace: "nowrap", "&:focus": { outline: "none" }, diff --git a/sandpack-react/src/components/Navigator/index.tsx b/sandpack-react/src/components/Navigator/index.tsx index 3b364b05b..05432e48b 100644 --- a/sandpack-react/src/components/Navigator/index.tsx +++ b/sandpack-react/src/components/Navigator/index.tsx @@ -13,9 +13,9 @@ import { splitUrl } from "./utils"; const navigatorClassName = css({ display: "flex", alignItems: "center", - height: "40px", + height: "$layout$headerHeight", borderBottom: "1px solid $colors$surface2", - padding: "$space$2 $space$4", + padding: "$space$3 $space$2", background: "$colors$surface1", }); @@ -49,7 +49,7 @@ const inputClassName = css({ * @category Components */ export interface NavigatorProps { - clientId?: string; + clientId: string; onURLChange?: (newURL: string) => void; } @@ -124,6 +124,16 @@ export const Navigator = ({ dispatch({ type: "urlforward" }); }; + const buttonsClassNames = classNames( + c("button", "icon"), + buttonClassName, + iconClassName, + css({ + minWidth: "$space$6", + justifyContent: "center", + }) + ); + return (