Adding Imports and a Test Runner to Sandpack

Adding Imports and a Test Runner to Sandpack

In the previous article, we started work on building a nice <CodePlayground /> component for our NextJS MDX blog from scratch, using Sandpack and TailwindCSS.

We started out with a pretty blank canvas, and were able to build out our Playground well enough so that markdown like this:

MDX
<CodePlayground />

Produces this nifty playground component:

Code Playground
export default function App() {
  return <h1>Hello world</h1>
}

There were, however, 2 features I didn't get to implementing:

  • A Test Runner and
  • Module Importing

Import modules to code examples in Sandpack

Sandpack makes this pretty dead simple.

In our <CodePlayground /> component, we simply need to ensure the <SandpackProvider /> supports the customSetup prop, like so:

src/components/CodePlayground.jsx
//...

export function CodePlayground_05({ files, customSetup }) {
  const [mode, setMode] = useState('result')
  const isPreview = mode === 'result'

  const previewProps = {
    mode,
    setMode,
    isPreview,
  }

  return (
    <SandpackProvider
      template="react"
      theme={theme}
      files={files}
      customSetup={customSetup}
    >
      <SandpackLayout className="!-mx-4 !block !rounded-none sm:!mx-0 sm:!rounded-lg">
        <TitleBar />
        <SandpackCodeEditor showTabs />
        <Console {...previewProps} />
        <Preview {...previewProps} />
      </SandpackLayout>
    </SandpackProvider>
  )
}

We can then pass a custom setup property in our Markdown, like this:

MDX
<CodePlayground_05 files={{
  '/App.js': `import React from 'react';
import { times } from 'lodash';

export default function App() {
  return (
    <ul>
      {times(5, n => (
        <li key={n}>{n}</li>
      ))}
    </ul>
  );
}

`
  }}
  customSetup={{
    dependencies: {
      lodash: 'latest'
    }
  }}
/>

That small change will let us import times from lodash, and use it in our code example.

Code Playground
import React from 'react';
import { times } from 'lodash';

export default function App() {
  return (
    <ul>
      {times(5, n => (
        <li key={n}>{n}</li>
      ))}
    </ul>
  );
}

Pretty slick, eh? 👍

Adding a Test Runner to our Sandpack code playground

Sandpack also makes adding a test runner super simple.

To start, we're gonna update our main <Playground /> component to support 3 modes instead of 2: ['result', 'console', 'tests'].

/src/components/CodePlayground.jsx
export function CodePlayground_06({ files, customSetup }) {
  const [mode, setMode] = useState('result')
- const isPreview = mode === 'result';

  const previewProps = {
    mode,
    setMode,
-   isPreview,
  }

  return (
    <SandpackProvider
      template="react"
      theme={theme}
      files={files}
      customSetup={customSetup}
    >
      <SandpackLayout className="!-mx-4 !block !rounded-none sm:!mx-0 sm:!rounded-lg">
        <TitleBar />
        <SandpackCodeEditor showTabs />
        <Actions {...previewProps} />
        <Preview {...previewProps} />
      </SandpackLayout>
    </SandpackProvider>
  )
}

Next, we'll update our <Preview /> component to add the <SandpackTests /> renderer.

src/components/CodePlayground.jsx
import React, { useState, useEffect } from 'react'
import {
  SandpackProvider,
  SandpackLayout,
  SandpackCodeEditor,
  SandpackPreview,
  SandpackConsole,
  SandpackTests,
  UnstyledOpenInCodeSandboxButton,
  useSandpack,
  useSandpackNavigation,
} from '@codesandbox/sandpack-react'

// ...

function Preview({ mode }) {
  return (
    <>
      <div className="rounded-b-lg bg-zinc-900 p-4">
        <div
          className={clsx(
            mode === 'result' ? 'block' : 'hidden',
            'overflow-hidden rounded bg-white p-1'
          )}
        >
          <SandpackPreview
            showOpenInCodeSandbox={false}
            showRefreshButton={false}
          />
        </div>

        <div
          className={clsx(
            mode === 'console' ? 'block' : 'hidden',
            'min-h-[160px] overflow-hidden rounded'
          )}
        >
          <SandpackConsole
            standalone
            resetOnPreviewRestart
            showHeader={false}
          />
        </div>

        <div
          className={clsx(
            mode === 'tests' ? 'block' : 'hidden',
            'min-h-[160px] overflow-hidden rounded'
          )}
        >
          <SandpackTests />
        </div>
      </div>
    </>
  )
}

And lastly, we'll update our <Actions /> (formerly <Console />) component to add a button for Tests.

src/components/CodePlayground.jsx
function Actions({ mode, setMode }) {
  const [reloading, setReloading] = useState(false)
  const { sandpack, listen } = useSandpack()
  const { refresh } = useSandpackNavigation()
  const activeClass = 'border-b border-amber-500'

  useEffect(() => {
    //...
  }, [listen])

  return (
    <div className="flex items-center justify-between border border-zinc-700 bg-zinc-900 px-3">
      <div>
        <button
          className={clsx('mr-6 py-3', mode === 'result' ? activeClass : null)}
          onClick={() => setMode('result')}
        >
          Preview
        </button>
        <button
          className={clsx('mr-6 py-3', mode === 'console' ? activeClass : null)}
          onClick={() => setMode('console')}
        >
          Console
        </button>
        <button
          className={clsx('py-3', mode === 'tests' ? activeClass : null)}
          onClick={() => setMode('tests')}
        >
          Tests
        </button>
      </div>
      <div>
        <button
          onClick={() => {
            setReloading(true)
            refresh()
          }}
          disabled={sandpack?.status === 'idle'}
        >
          <RefreshIcon
            className={clsx(
              'h-5 w-5 text-zinc-400',
              reloading && 'animate-spin',
              sandpack?.status === 'idle' && 'text-zinc-600'
            )}
          />
        </button>
      </div>
    </div>
  )
}

Et voila! We have a functioning Tests panel in our <CodePlayground />. 🦄

Actually running tests in our CodePlayground

Sandpack makes it pretty simple to run tests in the playground; you just need to add test files to your sandpack instance.

I'm not sure what the 1 skipped thing is all about, but I'll look into it.

In the meantime, happy hacking! 😎

If you enjoyed this article, please consider following me on Twitter

Subscribe to the Newsletter

Subscribe for exclusive tips, strategies, and resources to launch, grow, & build your SaaS team.

Share this article on: