diff --git a/README.md b/README.md index 8e70ae0..a985857 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,20 @@ import { Sandpack } from 'codesandbox-sandpack-vue3'; [Read more](https://sandpack.codesandbox.io/) +## online demo + +[demo](https://stackblitz.com/edit/vitejs-vite-s6jdds) + +[ssg demo](https://stackblitz.com/edit/vitejs-vite-ha17a9) + +*Here's an example of a react version for comparison* + +https://stackblitz.com/edit/vitejs-vite-axyaxx + +## releases + +https://github.com/jerrywu001/sandpack-vue3/releases + ## Sandpack Themes A list of themes to customize your Sandpack components. @@ -30,16 +44,6 @@ For full documentation, visit [https://sandpack.codesandbox.io/docs/](https://sa **This project removes [devtools component && useSandpackLint hook]** -## online demo - -[demo](https://stackblitz.com/edit/vitejs-vite-s6jdds) - -[ssg demo](https://stackblitz.com/edit/vitejs-vite-ha17a9) - -*Here's an example of a react version for comparison* - -https://stackblitz.com/edit/vitejs-vite-axyaxx - ## SSG/SSR @@ -69,6 +73,8 @@ app.use(SanpackPlugin()); - [Sandpack](https://sandpack.codesandbox.io/docs/api/react/#sandpack) - [SandpackLayout](https://sandpack.codesandbox.io/docs/api/react/interfaces/SandpackLayoutProps) +- [SandpackConsole](https://sandpack.codesandbox.io/docs/advanced-usage/components#console) +- [SandpackTests](https://sandpack.codesandbox.io/docs/advanced-usage/components#tests) - [CodeEditor](https://sandpack.codesandbox.io/docs/api/react/#codeeditor) - [SandpackProvider](https://sandpack.codesandbox.io/docs/api/react/classes/SandpackProvider) - [ErrorOverlay](https://sandpack.codesandbox.io/docs/api/react/#erroroverlay) diff --git a/global.d.ts b/global.d.ts index 7ffc7d7..9769cb4 100644 --- a/global.d.ts +++ b/global.d.ts @@ -2,6 +2,8 @@ import '@vue/runtime-core'; import { Sandpack, SandpackLayout, + SandpackConsole, + SandpackTests, CodeEditor, SandpackProvider, ErrorOverlay, @@ -18,6 +20,9 @@ declare module '@vue/runtime-core' { export interface GlobalComponents { Sandpack: typeof Sandpack; SandpackLayout: typeof SandpackLayout; + SandpackConsole: typeof SandpackConsole; + SandpackTests: typeof SandpackTests; + SandpackLayout: typeof SandpackLayout; SandpackProvider: typeof SandpackProvider; ErrorOverlay: typeof ErrorOverlay; LoadingOverlay: typeof LoadingOverlay; diff --git a/package.json b/package.json index 6bcffd7..ce115ae 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "@storybook/addon-links": "~6.4.22", "@storybook/builder-vite": "0.1.37", "@storybook/vue3": "~6.4.22", - "@swc/core": "^1.2.245", + "@swc/core": "^1.2.246", "@testing-library/jest-dom": "^5.16.5", "@testing-library/user-event": "^14.4.3", "@testing-library/vue": "^6.6.1", @@ -122,7 +122,7 @@ "husky": "^8.0.1", "jsdom": "^20.0.0", "lint-staged": "^13.0.3", - "npm": "^8.19.0", + "npm": "^8.19.1", "postcss": "^8.4.16", "postcss-cli": "^10.0.0", "postcss-loader": "^7.0.1", diff --git a/playground/src/pages/index.vue b/playground/src/pages/index.vue index 0431e11..57fc088 100644 --- a/playground/src/pages/index.vue +++ b/playground/src/pages/index.vue @@ -3,12 +3,11 @@ import isEqual from 'lodash.isequal'; import { ref } from 'vue'; import { githubLight, aquaBlue } from '@codesandbox/sandpack-themes'; import { - // Sandpack, + Sandpack, // SandpackLayout, // SandpackTranspiledCode, // SandpackProvider, // SandpackCodeEditor, - SandpackConsole, SandpackPredefinedTemplate, SandpackTheme, } from 'codesandbox-sandpack-vue3'; @@ -70,22 +69,34 @@ const toggleReadOnly = () => { showReadOnly: true, readOnly: readOnly, wrapContent: wrapContent, + showConsoleButton: true, + showConsole: false, }" />

+
+ +
+
+ - - - +
+
+ + + +
+
+ - -
- - - -
- - - -
- - - +
+

showConsoleButton: false, showConsole: false

+ + +
+ +

showConsoleButton: true, showConsole: true

+ + +
+ +

showConsoleButton: false, showConsole: true

+ + +
+ +

showConsoleButton: true, showConsole: false

+ +
); diff --git a/playground/stories/Tests.stories.tsx b/playground/stories/Tests.stories.tsx index edfb13c..0f55a7e 100644 --- a/playground/stories/Tests.stories.tsx +++ b/playground/stories/Tests.stories.tsx @@ -126,134 +126,134 @@ export const Main = () => { }; export const VerboseMode = () => ( - - - - - - + + + + + + ); export const OneTestFile = () => ( - - - - - - + + + + + + ); export const FileError = () => ( - - - - - - + + + + + + ); export const ExtendedExpect = () => ( - - - - - - + + + + + + ); export const SlowTest = () => ( - - - - - - + + + + + + ); export const NoTests = () => ( - - - - - - + + + + + + ); export const OnCompleteHandler = () => ( - - - - console.log(specs)} /> - - + + + + console.log(specs)} /> + + ); diff --git a/playground/stories/main.stories.tsx b/playground/stories/main.stories.tsx index 17dce0b..40d8830 100644 --- a/playground/stories/main.stories.tsx +++ b/playground/stories/main.stories.tsx @@ -9,6 +9,7 @@ import { SandpackPreview, SandpackProvider, SandpackConsole, + Sandpack, } from 'codesandbox-sandpack-vue3'; import { computed, ComputedRef, reactive } from 'vue'; @@ -18,7 +19,13 @@ export default { export const Main = () => { const config = reactive({ - Components: { Preview: true, Editor: true, FileExplorer: true, Console: true, Tests: true }, + Components: { + Preview: true, + Editor: true, + FileExplorer: true, + Console: true, + Tests: true, + }, Options: { showTabs: true, showLineNumbers: true, @@ -30,6 +37,8 @@ export const Main = () => { showNavigator: true, showRefreshButton: true, consoleShowHeader: true, + showConsoleButton: true, + showConsole: true, }, Template: 'exhaustedFilesTests' as const, Theme: 'light', @@ -127,41 +136,70 @@ export const Main = () => { })} - - -
- {config.Components.FileExplorer && } - {config.Components.Editor && ( - - )} - {config.Components.Preview && ( - - )} +
+ + +
+ {config.Components.FileExplorer && } + {config.Components.Editor && ( + + )} + {config.Components.Preview && ( + + )} + + {config.Components.Console && ( + + )} + {config.Components.Tests && } +
+
+
- {config.Components.Console && ( - - )} - {config.Components.Tests && } -
- - +
+ + +
); }; diff --git a/src/components/console/SandpackConsole.tsx b/src/components/console/SandpackConsole.tsx index cc63501..24de796 100644 --- a/src/components/console/SandpackConsole.tsx +++ b/src/components/console/SandpackConsole.tsx @@ -1,3 +1,4 @@ +import { nextTick } from 'process'; import { defineComponent, DefineComponent, Fragment, PropType, ref, watch } from 'vue'; import { SandpackStack } from '../../common'; import { css, THEME_PREFIX } from '../../styles'; @@ -72,9 +73,13 @@ export const SandpackConsole = defineComponent({ props.onLogsChange(logs.value); } - if (wrapperRef.value) { - wrapperRef.value.scrollTop = wrapperRef.value.scrollHeight; - } + nextTick(() => { + setTimeout(() => { + if (wrapperRef.value) { + wrapperRef.value.scrollTop = wrapperRef.value.scrollHeight * 2; + } + }); + }); }, ); diff --git a/src/components/console/useSandpackConsole.ts b/src/components/console/useSandpackConsole.ts index 38ab88a..4eb892a 100644 --- a/src/components/console/useSandpackConsole.ts +++ b/src/components/console/useSandpackConsole.ts @@ -1,5 +1,5 @@ import { UnsubscribeFunction } from '@codesandbox/sandpack-client'; -import { computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue'; +import { computed, onBeforeUnmount, onUnmounted, Ref, ref, watch } from 'vue'; import { useSandpack } from '../../contexts/sandpackContext'; import { CLEAR_LOG, @@ -26,6 +26,8 @@ export const useSandpackConsole = (props?: { const maxMessageCount = computed(() => props?.maxMessageCount ?? MAX_MESSAGE_COUNT); const listenMsg = () => { + if (unsubscribe) unsubscribe(); + unsubscribe = listen((message) => { if (message.type === 'console' && message.codesandbox) { if (message.log.find(({ method }) => method === 'clear')) { @@ -74,12 +76,9 @@ export const useSandpackConsole = (props?: { () => { listenMsg(); }, + { immediate: true }, ); - onMounted(() => { - listenMsg(); - }); - onBeforeUnmount(() => { if (unsubscribe) unsubscribe(); }); diff --git a/src/components/console/utils/fromConsoleToString.test.ts b/src/components/console/utils/fromConsoleToString.test.ts deleted file mode 100644 index 0fee37c..0000000 --- a/src/components/console/utils/fromConsoleToString.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -/* eslint-disable max-len */ -/* eslint-disable @typescript-eslint/no-empty-function */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import jsdom from 'jsdom'; - -import type { Message } from './fromConsoleToString'; -import { fromConsoleToString } from './fromConsoleToString'; - -global.window = new jsdom.JSDOM().window; -global.document = window.document; -global.HTMLElement = window.HTMLElement; - -const references = [ - null, // the first item is the log itself - { - '@t': 'Function', - data: { name: '', body: '', proto: 'Function' }, - }, - [{ '@r': 1 }], - 'foo', - [123, { '@r': 3 }], - // self-pointing - [{ '@r': 6 }], - { foo: { '@r': 5 } }, -]; - -const cases: Array<[Message, string]> = [ - /** - * Primitives - */ - ['Lorem ipsum', '"Lorem ipsum"'], - [123, '123'], - [true, 'true'], - [{ data: {} }, '{ data: {} }'], - [[], '[]'], - [{ '@t': '[[undefined]]', data: '' }, 'undefined'], - [Symbol('foo'), 'Symbol(foo)'], - [null, 'null'], - [Infinity, 'Infinity'], - [ - { constructor: { name: 'CustomThing' } }, - 'CustomThing { constructor: { name: "CustomThing" } }', - ], - - /** - * Function - */ - [ - { - '@t': 'Function', - data: { name: '', body: '', proto: 'Function' }, - }, - 'function () {}', - ], - [ - { - '@t': 'Function', - data: { name: 'myFunction', body: '', proto: 'Function' }, - }, - 'function myFunction() {}', - ], - [ - { '@t': '[[Date]]', data: 1659720915173 }, - 'Fri Aug 05 2022 17:35:15 GMT+0000 (Coordinated Universal Time)', - ], - [{ '@t': '[[NaN]]', data: '' }, 'NaN'], - [{ '@t': '[[RegExp]]', data: { src: '\\/\\/', flags: '' } }, '/\\/\\//'], - [ - { - '@t': 'HTMLElement', - data: { tagName: 'button', attributes: {}, innerHTML: 'Click' }, - }, - '', - ], - [ - [ - { - '@t': 'HTMLElement', - data: { tagName: 'button', attributes: {}, innerHTML: 'string' }, - }, - { - '@t': 'HTMLElement', - data: { tagName: 'button', attributes: {}, innerHTML: 'number' }, - }, - { - '@t': 'HTMLElement', - data: { tagName: 'button', attributes: {}, innerHTML: 'boolean' }, - }, - ], - '[, , ]', - ], - - /** - * Mix - */ - [ - { - foo: [{ '@t': '[[Date]]', data: 1659975293702 }], - baz: { - '@t': 'Function', - data: { name: 'baz', body: '', proto: 'Function' }, - }, - }, - '{ foo: [Mon Aug 08 2022 16:14:53 GMT+0000 (Coordinated Universal Time)], baz: function baz() {} }', - ], - [ - { - foo: { anotherFoo: [123, 'string', []] }, - baz: { - '@t': 'Function', - data: { name: 'baz', body: '', proto: 'Function' }, - }, - }, - '{ foo: { anotherFoo: [123, "string", []] }, baz: function baz() {} }', - ], - [ - [ - 123, - 'foo', - { - '@t': 'Function', - data: { name: 'myFunction', body: '', proto: 'Function' }, - }, - [ - 123, - 'anotherFoo', - { - '@t': 'Function', - data: { name: 'anotherFunction', body: '', proto: 'Function' }, - }, - ], - { '@t': '[[Date]]', data: 1659975293702 }, - ], - '[123, "foo", function myFunction() {}, [123, "anotherFoo", function anotherFunction() {}], Mon Aug 08 2022 16:14:53 GMT+0000 (Coordinated Universal Time)]', - ], - - /** - * Recursive references - */ - [{ '@r': 1 }, 'function () {}'], - [{ '@r': 2 }, '[function () {}]'], - [{ '@r': 4 }, '[123, "foo"]'], -]; - -describe(fromConsoleToString, () => { - it('should format message', () => { - cases.forEach(([input, output]) => { - expect(fromConsoleToString(input, references)).toStrictEqual(output); - }); - }); -}); diff --git a/src/components/preview/index.tsx b/src/components/preview/index.tsx index 20abab9..2dfb35a 100644 --- a/src/components/preview/index.tsx +++ b/src/components/preview/index.tsx @@ -116,6 +116,8 @@ export const SandpackPreview = defineComponent({ onMounted(() => { nextTick(() => { + if (unsubscribe) unsubscribe(); + unsubscribe = listen((message: SandpackMessage) => { if (message.type === 'resize') { iframeComputedHeight.value = message.height; diff --git a/src/components/tests/SandpackTests.tsx b/src/components/tests/SandpackTests.tsx index 59cdb46..940cc36 100644 --- a/src/components/tests/SandpackTests.tsx +++ b/src/components/tests/SandpackTests.tsx @@ -1,13 +1,15 @@ import { classNames } from '../../utils/classNames'; import { css, THEME_PREFIX } from '../../styles'; import { + computed, DefineComponent, defineComponent, onBeforeUnmount, + onMounted, onUnmounted, PropType, ref, - watch, + watchEffect, } from 'vue'; import { failTextClassName, setTestTheme } from './style'; import { Header } from './Header'; @@ -104,300 +106,295 @@ export const SandpackTests = defineComponent({ watchMode: props.watchMode || true, } as State); - watch( - [ - () => sandpack.activeFile, - state, - ], - () => { - let currentDescribeBlocks: string[] = []; - let currentSpec = ''; - - unsubscribe = listen((data) => { - // Note: short-circuit if message isn't for the currently active spec when `suiteOnly` is true - if ( - state.value.suiteOnly && - (('path' in data && data.path !== sandpack.activeFile) || - ('test' in data && - 'path' in data.test && - data.test.path !== sandpack.activeFile)) - ) { - return; - } + const runAllTests = () => { + state.value.status = 'running'; + state.value.specs = {}; - if ( - data.type === 'action' && - data.action === 'clear-errors' && - data.source === 'jest' - ) { - currentSpec = data.path; - return; - } + const client = getClient(); + if (client) { + client.dispatch({ type: 'run-all-tests' }); + } + }; - if (data.type === 'test') { - if (data.event === 'initialize_tests') { - currentDescribeBlocks = []; - currentSpec = ''; - if (state.value.watchMode) { - return runAllTests(); - } else { - state.value = { - ...state.value, - status: 'idle', - specs: {}, - }; - return; - } - } + const runSpec = () => { + state.value = { + ...state.value, + status: 'running', + specs: {}, + }; - if (data.event === 'test_count') { - state.value = { - ...state.value, - specsCount: data.count, - }; - return; - } + const client = getClient(); + if (client) { + client.dispatch({ type: 'run-tests', path: sandpack.activeFile }); + } + }; - if (data.event === 'total_test_start') { - currentDescribeBlocks = []; + const isSpecOpen = computed(() => sandpack.activeFile.match(testFileRegex) !== null); + + const subscribeIFrameData = () => { + let currentDescribeBlocks: string[] = []; + let currentSpec = ''; + + if (unsubscribe) unsubscribe(); + unsubscribe = listen((data) => { + // Note: short-circuit if message isn't for the currently active spec when `suiteOnly` is true + if ( + state.value.suiteOnly && + (('path' in data && data.path !== sandpack.activeFile) || + ('test' in data && + 'path' in data.test && + data.test.path !== sandpack.activeFile)) + ) { + return; + } + + if ( + data.type === 'action' && + data.action === 'clear-errors' && + data.source === 'jest' + ) { + currentSpec = data.path; + return; + } + + if (data.type === 'test') { + if (data.event === 'initialize_tests') { + currentDescribeBlocks = []; + currentSpec = ''; + if (state.value.watchMode) { + return runAllTests(); + } else { state.value = { ...state.value, - status: 'running', + status: 'idle', + specs: {}, }; return; } + } - if (data.event === 'total_test_end') { - if (props.onComplete !== undefined) { - props.onComplete(state.value.specs); - } - state.value = { - ...state.value, - status: 'complete', - }; + if (data.event === 'test_count') { + state.value = { + ...state.value, + specsCount: data.count, + }; + return; + } - return; - } + if (data.event === 'total_test_start') { + currentDescribeBlocks = []; + state.value = { + ...state.value, + status: 'running', + }; + return; + } - if (data.event === 'add_file') { - state.value = set(state.value, ['specs', data.path], { - describes: {}, - tests: {}, - name: data.path, - }); - return; + if (data.event === 'total_test_end') { + if (props.onComplete !== undefined) { + props.onComplete(state.value.specs); } + state.value = { + ...state.value, + status: 'complete', + }; - if (data.event === 'remove_file') { - const specs = Object.entries(state.value.specs).reduce( - (acc, [key, value]) => { - if (key === data.path) { - return acc; - } else { - return { ...acc, [key]: value }; - } - }, - {}, - ); + return; + } - state.value = { ...state.value, specs }; - return; - } + if (data.event === 'add_file') { + state.value = set(state.value, ['specs', data.path], { + describes: {}, + tests: {}, + name: data.path, + }); + return; + } + + if (data.event === 'remove_file') { + const specs = Object.entries(state.value.specs).reduce( + (acc, [key, value]) => { + if (key === data.path) { + return acc; + } else { + return { ...acc, [key]: value }; + } + }, + {}, + ); + + state.value = { ...state.value, specs }; + return; + } + + if (data.event === 'file_error') { + state.value = set(state.value, ['specs', data.path, 'error'], data.error); + return; + } + + if (data.event === 'describe_start') { + currentDescribeBlocks.push(data.blockName); + const [describePath, currentDescribe] = splitTail( + currentDescribeBlocks, + ); + const spec = currentSpec; - if (data.event === 'file_error') { - state.value = set(state.value, ['specs', data.path, 'error'], data.error); + if (currentDescribe === undefined) { return; } - if (data.event === 'describe_start') { - currentDescribeBlocks.push(data.blockName); - const [describePath, currentDescribe] = splitTail( - currentDescribeBlocks, - ); - const spec = currentSpec; + state.value = set( + state.value, + [ + 'specs', + spec, + 'describes', + ...flatMap(describePath, (name) => [name, 'describes']), + currentDescribe, + ], + { + name: data.blockName, + tests: {}, + describes: {}, + }, + ); + return; + } - if (currentDescribe === undefined) { - return; - } + if (data.event === 'describe_end') { + currentDescribeBlocks.pop(); + return; + } + if (data.event === 'add_test') { + const [describePath, currentDescribe] = splitTail( + currentDescribeBlocks, + ); + const test: Test = { + status: 'idle', + errors: [], + name: data.testName, + blocks: [...currentDescribeBlocks], + path: data.path, + }; + if (currentDescribe === undefined) { + state.value = set( + state.value, + ['specs', data.path, 'tests', data.testName], + test, + ); + return; + } else { state.value = set( state.value, [ 'specs', - spec, + data.path, 'describes', ...flatMap(describePath, (name) => [name, 'describes']), currentDescribe, + 'tests', + data.testName, ], - { - name: data.blockName, - tests: {}, - describes: {}, - }, + test, ); return; } + } - if (data.event === 'describe_end') { - currentDescribeBlocks.pop(); - return; - } - - if (data.event === 'add_test') { - const [describePath, currentDescribe] = splitTail( - currentDescribeBlocks, - ); - const test: Test = { - status: 'idle', - errors: [], - name: data.testName, - blocks: [...currentDescribeBlocks], - path: data.path, - }; - if (currentDescribe === undefined) { - state.value = set( - state.value, - ['specs', data.path, 'tests', data.testName], - test, - ); - return; - } else { - state.value = set( - state.value, - [ - 'specs', - data.path, - 'describes', - ...flatMap(describePath, (name) => [name, 'describes']), - currentDescribe, - 'tests', - data.testName, - ], - test, - ); - return; - } - } - - if (data.event === 'test_start') { - const { test } = data; - const [describePath, currentDescribe] = splitTail(test.blocks); + if (data.event === 'test_start') { + const { test } = data; + const [describePath, currentDescribe] = splitTail(test.blocks); - const startedTest: Test = { - status: 'running', - name: test.name, - blocks: test.blocks, - path: test.path, - errors: [], - }; + const startedTest: Test = { + status: 'running', + name: test.name, + blocks: test.blocks, + path: test.path, + errors: [], + }; - if (currentDescribe === undefined) { - state.value = set( - state.value, - ['specs', test.path, 'tests', test.name], - startedTest, - ); - return; - } else { - state.value = set( - state.value, - [ - 'specs', - test.path, - 'describes', - ...flatMap(describePath, (name) => [name, 'describes']), - currentDescribe, - 'tests', - test.name, - ], - startedTest, - ); - return; - } + if (currentDescribe === undefined) { + state.value = set( + state.value, + ['specs', test.path, 'tests', test.name], + startedTest, + ); + return; + } else { + state.value = set( + state.value, + [ + 'specs', + test.path, + 'describes', + ...flatMap(describePath, (name) => [name, 'describes']), + currentDescribe, + 'tests', + test.name, + ], + startedTest, + ); + return; } + } - if (data.event === 'test_end') { - const { test } = data; - const [describePath, currentDescribe] = splitTail(test.blocks); - const endedTest = { - status: test.status, - errors: test.errors, - duration: test.duration, - name: test.name, - blocks: test.blocks, - path: test.path, - }; - - if (currentDescribe === undefined) { - state.value = set( - state.value, - ['specs', test.path, 'tests', test.name], - endedTest, - ); - } else { - state.value = set( - state.value, - [ - 'specs', - test.path, - 'describes', - ...flatMap(describePath, (name) => [name, 'describes']), - currentDescribe, - 'tests', - test.name, - ], - endedTest, - ); - } + if (data.event === 'test_end') { + const { test } = data; + const [describePath, currentDescribe] = splitTail(test.blocks); + const endedTest = { + status: test.status, + errors: test.errors, + duration: test.duration, + name: test.name, + blocks: test.blocks, + path: test.path, + }; + + if (currentDescribe === undefined) { + state.value = set( + state.value, + ['specs', test.path, 'tests', test.name], + endedTest, + ); + } else { + state.value = set( + state.value, + [ + 'specs', + test.path, + 'describes', + ...flatMap(describePath, (name) => [name, 'describes']), + currentDescribe, + 'tests', + test.name, + ], + endedTest, + ); } } - }); - }, - ); - - const runAllTests = () => { - state.value.status = 'running'; - state.value.specs = {}; - - const client = getClient(); - if (client) { - client.dispatch({ type: 'run-all-tests' }); - } + } + }); }; - const runSpec = () => { - state.value = { - ...state.value, - status: 'running', - specs: {}, - }; + const listenIframeAction = () => { + if (unsunscribe) unsunscribe(); - const client = getClient(); - if (client) { - client.dispatch({ type: 'run-tests', path: sandpack.activeFile }); - } + unsunscribe = listen(({ type }) => { + if (type === 'done' && state.value.watchMode) { + if (isSpecOpen.value) { + runSpec(); + } else { + runAllTests(); + } + } + }); }; - watch( - [ - () => state.value.watchMode, - () => sandpack.activeFile, - runAllTests, - runSpec, - ], - () => { - unsunscribe = listen(({ type }) => { - if (type === 'done' && state.value.watchMode) { - const isSpecOpen = sandpack.activeFile.match(testFileRegex) !== null; - if (isSpecOpen) { - runSpec(); - } else { - runAllTests(); - } - } - }); - }, - ); + watchEffect(listenIframeAction); + watchEffect(subscribeIFrameData); + + onMounted(listenIframeAction); onBeforeUnmount(() => { if (unsunscribe) unsunscribe(); @@ -413,10 +410,10 @@ export const SandpackTests = defineComponent({ sandpack.setActiveFile(file); }; - const specs = Object.values(state.value.specs); - const duration = getDuration(specs); - const testResults = getAllTestResults(specs); - const suiteResults = getAllSuiteResults(specs); + const specs = computed(() => Object.values(state.value.specs)); + const duration = computed(() => getDuration(specs.value)); + const testResults = computed(() => getAllTestResults(specs.value)); + const suiteResults = computed(() => getAllSuiteResults(specs.value)); return () => ( - {specs.length === 0 && state.value.status === 'complete' ? ( + {specs.value.length === 0 && state.value.status === 'complete' ? (

No test files found.

@@ -478,16 +475,16 @@ export const SandpackTests = defineComponent({ <> - {state.value.status === 'complete' && testResults.total > 0 && ( + {state.value.status === 'complete' && testResults.value.total > 0 && (

)} diff --git a/src/contexts/sandpackContext.tsx b/src/contexts/sandpackContext.tsx index b290947..385e623 100644 --- a/src/contexts/sandpackContext.tsx +++ b/src/contexts/sandpackContext.tsx @@ -20,6 +20,7 @@ import { UnwrapNestedRefs, nextTick, StyleValue, + toRaw, } from 'vue'; import type { BundlerState, @@ -195,7 +196,7 @@ const SandpackProvider = defineComponent({ if (recompileMode === 'immediate') { Object.values(clients).forEach((client) => { client.updatePreview({ - files: state.files, + files: toRaw(state.files), }); }); } @@ -205,7 +206,7 @@ const SandpackProvider = defineComponent({ debounceHook = window.setTimeout(() => { Object.values(clients).forEach((client) => { client.updatePreview({ - files: state.files, + files: toRaw(state.files), }); }); }, recompileDelay); @@ -551,8 +552,8 @@ const SandpackProvider = defineComponent({ Object.values(clients).forEach((client) => { client.updatePreview({ - files: stateFromProps.files, - template: stateFromProps.environment, + files: toRaw(stateFromProps.files), + template: toRaw(stateFromProps.environment), }); }); diff --git a/src/hooks/useSandpackClient.ts b/src/hooks/useSandpackClient.ts index c809610..f8713e0 100644 --- a/src/hooks/useSandpackClient.ts +++ b/src/hooks/useSandpackClient.ts @@ -1,5 +1,5 @@ import { generateRandomId } from '../utils/stringUtils'; -import { onBeforeUnmount, onMounted, onUnmounted, ref, Ref } from 'vue'; +import { toRaw, onBeforeUnmount, onMounted, onUnmounted, ref, Ref } from 'vue'; import { useSandpack } from '../contexts/sandpackContext'; import type { ListenerFunction, @@ -52,7 +52,10 @@ export const useSandpackClient = (): UseSandpackClient => { } }); - const getClient = (): SandpackClient | null => sandpack.clients[clientId.value] || null; + const getClient = (): SandpackClient | null => { + const { clients } = sandpack; + return toRaw(clients[clientId.value] || null); + }; return { sandpack, diff --git a/src/presets/Sandpack.tsx b/src/presets/Sandpack.tsx index 0a73629..60b29da 100644 --- a/src/presets/Sandpack.tsx +++ b/src/presets/Sandpack.tsx @@ -18,6 +18,7 @@ import { defineComponent, PropType, ref, + watch, } from 'vue'; import { SandpackFiles, @@ -116,6 +117,7 @@ const Sandpack = defineComponent({ const rightColumnStyle = computed(() => ({ flexGrow: previewPart.value, flexShrink: previewPart.value, + flexBasis: 0, minWidth: 700 * (previewPart.value / (previewPart.value + editorPart.value)), gap: consoleVisibility.value ? 1 : 0, height: props.options?.editorHeight, // use the original editor height @@ -133,6 +135,16 @@ const Sandpack = defineComponent({ /> ) : undefined); + const hasRightColumn = computed(() => props.options?.showConsole || props.options?.showConsoleButton); + + watch( + [() => props.options?.showConsole], + () => { + consoleVisibility.value = props.options?.showConsole ?? false; + }, + { immediate: true }, + ); + return () => ( {mode.value === 'preview' && ( )} diff --git a/src/presets/SandpackRender.tsx b/src/presets/SandpackRender.tsx index a22d44a..4162369 100644 --- a/src/presets/SandpackRender.tsx +++ b/src/presets/SandpackRender.tsx @@ -10,11 +10,9 @@ export const SandpackRender = defineComponent({ default: false, }, }, - setup(props, { slots }) { + setup(props, { slots, attrs }) { return () => props.fragment ? ( - <> - { slots.default ? slots.default() : null } - + slots.default ? slots.default() : null ) : ( { slots.default ? slots.default() : null }