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:
<CodePlayground />
Produces this nifty playground component:
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:
//...
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:
<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.
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']
.
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.
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.
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