diff --git a/docs/tutorialkit.dev/package.json b/docs/tutorialkit.dev/package.json index 328274998..7cefd8a60 100644 --- a/docs/tutorialkit.dev/package.json +++ b/docs/tutorialkit.dev/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@tutorialkit/react": "workspace:*", - "@webcontainer/api": "1.2.0", + "@webcontainer/api": "1.2.4", "classnames": "^2.5.1", "react": "^18.3.1", "react-dom": "^18.3.1" diff --git a/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx b/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx index 2a37d85c0..36720ff3a 100644 --- a/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx +++ b/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx @@ -98,6 +98,13 @@ type I18nText = { */ defaultPreviewTitleText?: string, + /** + * Title attribute for the preview reload button. + * + * @default 'Reload Preview' + */ + reloadPreviewTitle?: string, + /** * Text for the toggle terminal button. * diff --git a/e2e/package.json b/e2e/package.json index 0e5e3a817..b94f67b62 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -18,6 +18,7 @@ "@tutorialkit/runtime": "workspace:*", "@tutorialkit/theme": "workspace:*", "@tutorialkit/types": "workspace:*", + "@types/react": "^18.3.3", "@types/node": "^22.2.0", "@unocss/reset": "^0.59.4", "@unocss/transformer-directives": "^0.62.0", diff --git a/e2e/src/components/ButtonWriteToFile.tsx b/e2e/src/components/ButtonWriteToFile.tsx new file mode 100644 index 000000000..5262ce893 --- /dev/null +++ b/e2e/src/components/ButtonWriteToFile.tsx @@ -0,0 +1,27 @@ +import tutorialStore from 'tutorialkit:store'; + +interface Props { + filePath: string; + newContent: string; + testId?: string; +} + +export function ButtonWriteToFile({ filePath, newContent, testId = 'write-to-file' }: Props) { + async function writeFile() { + await new Promise((resolve) => { + tutorialStore.lessonFullyLoaded.subscribe((value) => { + if (value) { + resolve(); + } + }); + }); + + tutorialStore.updateFile(filePath, newContent); + } + + return ( + + ); +} diff --git a/e2e/src/content/tutorial/tests/preview/single/_files/index.html b/e2e/src/content/tutorial/tests/preview/single/_files/index.html new file mode 100644 index 000000000..896c9cfcc --- /dev/null +++ b/e2e/src/content/tutorial/tests/preview/single/_files/index.html @@ -0,0 +1 @@ +Index page diff --git a/e2e/src/content/tutorial/tests/preview/single/content.md b/e2e/src/content/tutorial/tests/preview/single/content.md deleted file mode 100644 index 387761f49..000000000 --- a/e2e/src/content/tutorial/tests/preview/single/content.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -type: lesson -title: Single -previews: - - [8000, "Node Server"] ---- - -# Preview test - Single diff --git a/e2e/src/content/tutorial/tests/preview/single/content.mdx b/e2e/src/content/tutorial/tests/preview/single/content.mdx new file mode 100644 index 000000000..73c41eee3 --- /dev/null +++ b/e2e/src/content/tutorial/tests/preview/single/content.mdx @@ -0,0 +1,13 @@ +--- +type: lesson +title: Single +template: file-server +previews: + - [8000, "Node Server"] +--- + +import { ButtonWriteToFile } from '@components/ButtonWriteToFile'; + +# Preview test - Single + + diff --git a/e2e/test/preview.test.ts b/e2e/test/preview.test.ts index def45692e..3aae6b011 100644 --- a/e2e/test/preview.test.ts +++ b/e2e/test/preview.test.ts @@ -13,6 +13,22 @@ test('user can see single preview tab', async ({ page }) => { await expect(preview.getByText('Index page')).toBeVisible(); }); +test('user can reload a preview tab', async ({ page }) => { + await page.goto(`${BASE_URL}/single`); + + const preview = page.frameLocator('[title="Node Server"]'); + + await expect(preview.getByText('Index page')).toBeVisible(); + + await page.getByTestId('write-to-file').click(); + + await expect(preview.getByText('Index page')).toBeVisible(); + + await page.getByRole('button', { name: 'Reload Preview' }).click(); + + await expect(preview.getByText('New content')).toBeVisible(); +}); + test('user can see multiple preview tabs', async ({ page }) => { await page.goto(`${BASE_URL}/multiple`); diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 000000000..272d33e8c --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "jsx": "react-jsx", + "baseUrl": "./", + "jsxImportSource": "react", + "paths": { + "@*": ["src/*"] + } + }, + "include": ["src", "./*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/astro/package.json b/packages/astro/package.json index c2f395a5c..b5aef391d 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -43,7 +43,7 @@ "@tutorialkit/types": "workspace:*", "@types/react": "^18.3.3", "@unocss/reset": "^0.62.2", - "@webcontainer/api": "1.2.0", + "@webcontainer/api": "1.2.4", "astro": "^4.15.0", "astro-expressive-code": "^0.35.3", "chokidar": "3.6.0", diff --git a/packages/astro/src/default/utils/content/default-localization.ts b/packages/astro/src/default/utils/content/default-localization.ts index 6f8171826..427a0f8de 100644 --- a/packages/astro/src/default/utils/content/default-localization.ts +++ b/packages/astro/src/default/utils/content/default-localization.ts @@ -9,6 +9,7 @@ export const DEFAULT_LOCALIZATION = { filesTitleText: 'Files', prepareEnvironmentTitleText: 'Preparing Environment', defaultPreviewTitleText: 'Preview', + reloadPreviewTitle: 'Reload Preview', toggleTerminalButtonText: 'Toggle Terminal', solveButtonText: 'Solve', resetButtonText: 'Reset', diff --git a/packages/react/package.json b/packages/react/package.json index 71d408f6d..d377daf1c 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -77,7 +77,7 @@ "@replit/codemirror-lang-svelte": "^6.0.0", "@tutorialkit/runtime": "workspace:*", "@tutorialkit/theme": "workspace:*", - "@webcontainer/api": "1.2.0", + "@webcontainer/api": "1.2.4", "@xterm/addon-fit": "^0.10.0", "@xterm/addon-web-links": "^0.11.0", "@xterm/xterm": "^5.5.0", diff --git a/packages/react/src/Panels/PreviewPanel.tsx b/packages/react/src/Panels/PreviewPanel.tsx index 591e8d158..50bc0a6ab 100644 --- a/packages/react/src/Panels/PreviewPanel.tsx +++ b/packages/react/src/Panels/PreviewPanel.tsx @@ -1,6 +1,7 @@ import { useStore } from '@nanostores/react'; import type { PreviewInfo, TutorialStore } from '@tutorialkit/runtime'; import type { I18n } from '@tutorialkit/types'; +import { reloadPreview } from '@webcontainer/api/utils'; import { createElement, forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef } from 'react'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import { BootScreen } from '../BootScreen.js'; @@ -161,6 +162,12 @@ function Preview({ preview, iframe, previewCount, first, last, toggleTerminal, i } }, [preview.url, iframe.ref]); + function reload() { + if (iframe.ref) { + reloadPreview(iframe.ref); + } + } + return (
-
+ {previewTitle(preview, previewCount, i18n)}
{last && ( diff --git a/packages/runtime/package.json b/packages/runtime/package.json index fc254d9cf..60d582392 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@tutorialkit/types": "workspace:*", - "@webcontainer/api": "1.2.0", + "@webcontainer/api": "1.2.4", "nanostores": "^0.10.3" }, "devDependencies": { diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index a180c23fb..febb68ec8 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -5,7 +5,7 @@ "type": "module", "private": true, "devDependencies": { - "@webcontainer/api": "1.2.0", + "@webcontainer/api": "1.2.4", "typescript": "^5.4.5", "vitest": "^1.6.0" } diff --git a/packages/types/src/schemas/i18n.ts b/packages/types/src/schemas/i18n.ts index e1c386a15..69ebafa0a 100644 --- a/packages/types/src/schemas/i18n.ts +++ b/packages/types/src/schemas/i18n.ts @@ -66,6 +66,13 @@ export const i18nSchema = z.object({ */ defaultPreviewTitleText: z.string().optional().describe('Text shown on top of the preview section.'), + /** + * Title attribute for the preview reload button. + * + * @default 'Reload Preview' + */ + reloadPreviewTitle: z.string().optional().describe('Title attribute for the preview reload button.'), + /** * Text for the toggle terminal button. * diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdeb4a794..f41856e7f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,8 +91,8 @@ importers: specifier: workspace:* version: link:../../packages/react '@webcontainer/api': - specifier: 1.2.0 - version: 1.2.0 + specifier: 1.2.4 + version: 1.2.4 classnames: specifier: ^2.5.1 version: 2.5.1 @@ -178,6 +178,9 @@ importers: '@types/node': specifier: ^22.2.0 version: 22.4.2 + '@types/react': + specifier: ^18.3.3 + version: 18.3.3 '@unocss/reset': specifier: ^0.59.4 version: 0.59.4 @@ -312,8 +315,8 @@ importers: specifier: ^0.62.2 version: 0.62.3 '@webcontainer/api': - specifier: 1.2.0 - version: 1.2.0 + specifier: 1.2.4 + version: 1.2.4 astro: specifier: ^4.15.0 version: 4.15.0(@types/node@22.4.2)(typescript@5.5.3) @@ -540,8 +543,8 @@ importers: specifier: workspace:* version: link:../theme '@webcontainer/api': - specifier: 1.2.0 - version: 1.2.0 + specifier: 1.2.4 + version: 1.2.4 '@xterm/addon-fit': specifier: ^0.10.0 version: 0.10.0(@xterm/xterm@5.5.0) @@ -595,8 +598,8 @@ importers: specifier: workspace:* version: link:../types '@webcontainer/api': - specifier: 1.2.0 - version: 1.2.0 + specifier: 1.2.4 + version: 1.2.4 nanostores: specifier: ^0.10.3 version: 0.10.3 @@ -660,8 +663,8 @@ importers: packages/test-utils: devDependencies: '@webcontainer/api': - specifier: 1.2.0 - version: 1.2.0 + specifier: 1.2.4 + version: 1.2.4 typescript: specifier: ^5.4.5 version: 5.5.3 @@ -4038,8 +4041,8 @@ packages: resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} dev: true - /@webcontainer/api@1.2.0: - resolution: {integrity: sha512-tzoKBd4lLdhHy5GHFpUkl+ndoSba8JqmB7x0ZQFnWfjbcbQOvKQfxA8MEMUYhgqjWHnbrWdAfnBEHz5f5lYG5A==} + /@webcontainer/api@1.2.4: + resolution: {integrity: sha512-vV42eKuat5QGz7agFJupT5sZj0CHOj/gg6J3/HanvgOVETt7gupzR+iuVNHwudS3yuW+x78Ai7T6fwvV7uBThQ==} /@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0): resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==}