From d01989fc544e1249cc44be99f525917077e40909 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Thu, 14 Sep 2023 23:35:42 +0300 Subject: [PATCH 01/10] feat(tests): use browserJest --- src/livecodes/core.ts | 16 ++++++++++++++-- src/livecodes/result/result-page.ts | 22 +++++----------------- src/livecodes/vendors.ts | 8 +++++--- src/sdk/models.ts | 2 +- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/livecodes/core.ts b/src/livecodes/core.ts index 7686ffe37..7c22e9116 100644 --- a/src/livecodes/core.ts +++ b/src/livecodes/core.ts @@ -3387,12 +3387,24 @@ const handleTestResults = () => { eventsManager.addEventListener(window, 'message', (ev: any) => { if (ev.origin !== sandboxService.getOrigin()) return; if (ev.data.type !== 'testResults') return; - toolsPane?.tests?.showResults(ev.data.payload); + + let results = ev.data.payload?.results; + const error = ev.data.payload?.error; + if (Array.isArray(results)) { + results = results.map((result) => { + if (result.status === 'done') { + result.status = result.errors?.length === 0 ? 'pass' : 'fail'; + } + return result; + }); + } + + toolsPane?.tests?.showResults({ results, error }); let testResultsEvent: CustomEvent<{ results: TestResult[]; error?: string } | void>; if (sdkWatchers.tests.hasSubscribers()) { testResultsEvent = new CustomEvent(customEvents.testResults, { - detail: JSON.parse(JSON.stringify(ev.data.payload)), + detail: JSON.parse(JSON.stringify({ results, error })), }); } else { testResultsEvent = new CustomEvent(customEvents.testResults); diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index 4143817c1..51ccabe3b 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -12,7 +12,7 @@ import { getAppCDN, modulesService } from '../services'; // eslint-disable-next-line import/no-internal-modules import { testImports } from '../toolspane/test-imports'; import { escapeScript, getAbsoluteUrl, isRelativeUrl, objectMap, toDataUrl } from '../utils'; -import { esModuleShimsPath, jestLiteUrl, spacingJsUrl } from '../vendors'; +import { esModuleShimsPath, browserJestUrl, spacingJsUrl } from '../vendors'; export const createResultPage = async ({ code, @@ -277,7 +277,7 @@ export const createResultPage = async ({ // tests if (runTests && !forExport) { const jestScript = dom.createElement('script'); - jestScript.src = jestLiteUrl; + jestScript.src = browserJestUrl; jestScript.dataset.env = 'development'; dom.body.appendChild(jestScript); @@ -285,23 +285,11 @@ export const createResultPage = async ({ testScript.type = 'module'; testScript.dataset.env = 'development'; testScript.innerHTML = ` -const { - afterAll, - afterEach, - beforeAll, - beforeEach, - describe, - describe: { only: fdescribe, skip: xdescribe }, - it, - test, - test: { only: fit, skip: xtest, skip: xit }, - expect, - jest } = window.jestLite.core; - +const {afterAll, afterEach, beforeAll, beforeEach, describe, fdescribe, xdescribe, it, test, fit, xtest, xit, expect, jest} = window.browserJest; ${escapeScript(compiledTests)} -window.jestLite.core.run().then(results => { - parent.postMessage({type: 'testResults', payload: {results}}, '*'); +window.browserJest.run().then(results => { + parent.postMessage({type: 'testResults', payload: {results: results.testResults }}, '*'); }).catch((error) => { parent.postMessage({type: 'testResults', payload: {error: error.message || String(error)}}, '*'); }); diff --git a/src/livecodes/vendors.ts b/src/livecodes/vendors.ts index 70b9bd429..b25982cd8 100644 --- a/src/livecodes/vendors.ts +++ b/src/livecodes/vendors.ts @@ -4,7 +4,7 @@ import { modulesService } from './services/modules'; const { getUrl, getModuleUrl } = modulesService; export const vendorsBaseUrl = // 'http://127.0.0.1:8081/'; - /* @__PURE__ */ getUrl('@live-codes/browser-compilers@0.7.6/dist/'); + /* @__PURE__ */ getUrl('@live-codes/browser-compilers@0.8.0/dist/'); export const acornUrl = /* @__PURE__ */ getUrl('acorn@8.8.2/dist/acorn.js'); @@ -34,6 +34,10 @@ export const biwaschemeUrl = /* @__PURE__ */ getUrl('biwascheme@0.8.0/release/bi export const blocklyCdnBaseUrl = /* @__PURE__ */ getUrl('blockly@9.3.3/'); +export const browserJestUrl = /* @__PURE__ */ getUrl( + '@live-codes/browser-jest@0.0.2/dist/browser-jest.umd.js', +); + export const brythonBaseUrl = /* @__PURE__ */ getUrl('brython@3.11.2/'); export const chaiUrl = /* @__PURE__ */ getModuleUrl('chai@4.3.6'); @@ -182,8 +186,6 @@ export const hpccJsCdnUrl = /* @__PURE__ */ getUrl('@hpcc-js/wasm@2.13.0/dist/in export const imbaBaseUrl = /* @__PURE__ */ getUrl('imba@2.0.0-alpha.229/dist/'); -export const jestLiteUrl = /* @__PURE__ */ getUrl('jest-lite@1.0.0-alpha.4/dist/core.js'); - export const jestTypesUrl = /* @__PURE__ */ getUrl('@types/jest@27.4.1/index.d.ts'); export const jsclUrl = /* @__PURE__ */ getUrl( diff --git a/src/sdk/models.ts b/src/sdk/models.ts index ed842589c..8764cab2a 100644 --- a/src/sdk/models.ts +++ b/src/sdk/models.ts @@ -903,7 +903,7 @@ export interface EventsManager { export interface TestResult { duration: number; errors: string[]; - status: 'pass' | 'fail'; + status: 'pass' | 'fail' | 'skip'; testPath: string[]; } From 5623c8e1d3dff905070ab9ad96a77469284ff593 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Sat, 16 Sep 2023 04:56:44 +0300 Subject: [PATCH 02/10] fix(tools): show skipped tests --- src/livecodes/styles/app.scss | 4 ++++ src/livecodes/toolspane/test-viewer.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/livecodes/styles/app.scss b/src/livecodes/styles/app.scss index b297c82a0..e78ae8224 100644 --- a/src/livecodes/styles/app.scss +++ b/src/livecodes/styles/app.scss @@ -763,6 +763,10 @@ a { content: '✔'; } } + + &.skip::before { + content: '○'; + } } .test-error { diff --git a/src/livecodes/toolspane/test-viewer.ts b/src/livecodes/toolspane/test-viewer.ts index 3a941704c..ebd30a80f 100644 --- a/src/livecodes/toolspane/test-viewer.ts +++ b/src/livecodes/toolspane/test-viewer.ts @@ -62,6 +62,9 @@ export const createTestViewer = ( if (item.classList.contains('fail')) { item.classList.remove('fail'); } + if (item.classList.contains('skip')) { + item.classList.remove('skip'); + } }); const testSummary = testResultsElement.querySelector('.test-summary'); if (testSummary) { @@ -112,6 +115,7 @@ export const createTestViewer = ( const passed = results.filter((r) => r.status === 'pass').length; const failed = results.filter((r) => r.status === 'fail').length; + const skipped = results.filter((r) => r.status === 'skip').length; const total = results.length; const duration = results.reduce((totalDuration, r) => totalDuration + r.duration, 0) / 1000; const summary = document.createElement('div'); @@ -119,6 +123,7 @@ export const createTestViewer = ( summary.innerHTML = ` Tests: ${failed !== 0 ? '' + failed + ' failed,' : ''} ${passed !== 0 ? '' + passed + ' passed,' : ''} + ${skipped !== 0 ? '' + skipped + ' skipped,' : ''} ${total} total
Time: ${duration}s `; From 6fa16f2504e0f767d120d15d54f85e73d1fab2f8 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Sat, 16 Sep 2023 06:41:40 +0300 Subject: [PATCH 03/10] feat(config): add `autotest` to `UserConfig` this allows test watch mode to be configured in playground config --- src/livecodes/config/config.ts | 1 + src/livecodes/config/default-config.ts | 1 + src/livecodes/config/validate-config.ts | 1 + src/livecodes/core.ts | 26 ++++++++++++++----------- src/sdk/models.ts | 1 + 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/livecodes/config/config.ts b/src/livecodes/config/config.ts index 0ee68dfbc..a87aacf97 100644 --- a/src/livecodes/config/config.ts +++ b/src/livecodes/config/config.ts @@ -35,6 +35,7 @@ export const getContentConfig = (config: Config | ContentConfig): ContentConfig export const getUserConfig = (config: Config | UserConfig): UserConfig => ({ autoupdate: config.autoupdate, autosave: config.autosave, + autotest: config.autotest, delay: config.delay, formatOnsave: config.formatOnsave, recoverUnsaved: config.recoverUnsaved, diff --git a/src/livecodes/config/default-config.ts b/src/livecodes/config/default-config.ts index f659fa856..b9838f96b 100644 --- a/src/livecodes/config/default-config.ts +++ b/src/livecodes/config/default-config.ts @@ -6,6 +6,7 @@ export const defaultConfig: Config = { tags: [], autoupdate: true, autosave: false, + autotest: false, delay: 1500, formatOnsave: false, mode: 'full', diff --git a/src/livecodes/config/validate-config.ts b/src/livecodes/config/validate-config.ts index 1fb210116..11dcb1395 100644 --- a/src/livecodes/config/validate-config.ts +++ b/src/livecodes/config/validate-config.ts @@ -82,6 +82,7 @@ export const validateConfig = (config: Partial): Partial => { ...(is(config.tags, 'array', 'string') ? { tags: removeDuplicates(config.tags) } : {}), ...(is(config.autoupdate, 'boolean') ? { autoupdate: config.autoupdate } : {}), ...(is(config.autosave, 'boolean') ? { autosave: config.autosave } : {}), + ...(is(config.autotest, 'boolean') ? { autotest: config.autotest } : {}), ...(is(config.delay, 'number') ? { delay: Number(config.delay) } : {}), ...(is(config.formatOnsave, 'boolean') ? { formatOnsave: config.formatOnsave } : {}), ...(includes(modes, config.mode) ? { mode: config.mode } : {}), diff --git a/src/livecodes/core.ts b/src/livecodes/core.ts index 7c22e9116..40b879a79 100644 --- a/src/livecodes/core.ts +++ b/src/livecodes/core.ts @@ -167,7 +167,6 @@ let isSaved = true; let changingContent = false; let consoleInputCodeCompletion: any; let starterTemplates: Template[]; -let watchTests = false; let initialized = false; let isDestroyed = false; const broadcastInfo: BroadcastInfo = { @@ -1921,35 +1920,36 @@ const handleChangeLanguage = () => { const handleChangeContent = () => { const contentChanged = async (editorId: EditorId, loading: boolean) => { updateConfig(); + const config = getConfig(); addConsoleInputCodeCompletion(); - const shouldRunTests = Boolean(watchTests && getConfig().tests?.content); - if ((getConfig().autoupdate || shouldRunTests) && !loading) { + const shouldRunTests = Boolean(config.autotest && config.tests?.content); + if ((config.autoupdate || shouldRunTests) && !loading) { await run(editorId, shouldRunTests); } - if (getConfig().markup.content !== getCache().markup.content) { + if (config.markup.content !== getCache().markup.content) { await getResultPage({ sourceEditor: editorId }); } for (const key of Object.keys(customEditors)) { - if (getConfig()[editorId].language === key) { + if (config[editorId].language === key) { await customEditors[key]?.show(true, { baseUrl, editors, - config: getConfig(), - html: getCache().markup.compiled || getConfig().markup.content || '', + config, + html: getCache().markup.compiled || config.markup.content || '', eventsManager, }); } } - if (getConfig().autosave) { + if (config.autosave) { await save(); } dispatchChangeEvent(); - loadModuleTypes(editors, getConfig()); + loadModuleTypes(editors, config); }; const debouncecontentChanged = (editorId: EditorId) => @@ -3416,6 +3416,10 @@ const handleTestResults = () => { }; const handleTests = () => { + if (getConfig().autotest) { + UI.getWatchTestsButton()?.classList.remove('disabled'); + } + eventsManager.addEventListener( UI.getRunTestsButton(), 'click', @@ -3431,8 +3435,8 @@ const handleTests = () => { 'click', (ev: Event) => { ev.preventDefault(); - watchTests = !watchTests; - if (watchTests) { + setUserConfig({ autotest: !getConfig().autotest }); + if (getConfig().autotest) { UI.getWatchTestsButton()?.classList.remove('disabled'); runTests(); } else { diff --git a/src/sdk/models.ts b/src/sdk/models.ts index 8764cab2a..62d2959c0 100644 --- a/src/sdk/models.ts +++ b/src/sdk/models.ts @@ -103,6 +103,7 @@ export interface AppConfig { export interface UserConfig extends EditorConfig, FormatterConfig { autoupdate: boolean; autosave: boolean; + autotest: boolean; delay: number; formatOnsave: boolean; theme: Theme; From a52413ee3983f169396b461bc93c34549fcd1b07 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Sat, 16 Sep 2023 07:14:27 +0300 Subject: [PATCH 04/10] fix(sync): fix `autosync` toggle not being saved in user data --- src/livecodes/core.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/livecodes/core.ts b/src/livecodes/core.ts index 40b879a79..7bece6bf8 100644 --- a/src/livecodes/core.ts +++ b/src/livecodes/core.ts @@ -2164,8 +2164,8 @@ const handleSettings = () => { const toggles = UI.getSettingToggles(); toggles.forEach((toggle) => { eventsManager.addEventListener(toggle, 'change', async () => { - const configKey = toggle.dataset.config; - if (!configKey || !(configKey in getConfig())) return; + const configKey = toggle.dataset.config as keyof Config | 'autosync'; + if (!configKey || (!(configKey in getConfig()) && configKey !== 'autosync')) return; if (configKey === 'theme') { setConfig({ ...getConfig(), theme: toggle.checked ? 'dark' : 'light' }); From ef3aecba8bb0092024f406e59cc938697f03cee2 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Sat, 16 Sep 2023 16:04:52 +0300 Subject: [PATCH 05/10] feat(tools): run `configureToolsPane` in `loadConfig` --- src/livecodes/core.ts | 59 +++++++++++++++---- .../templates/starter/jest-react-starter.ts | 10 ++-- .../templates/starter/jest-starter.ts | 10 ++-- src/sdk/models.ts | 1 + 4 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/livecodes/core.ts b/src/livecodes/core.ts index 7bece6bf8..abfac90cc 100644 --- a/src/livecodes/core.ts +++ b/src/livecodes/core.ts @@ -472,8 +472,12 @@ const updateEditors = async (editors: Editors, config: Config) => { } }; -const showMode = (config: Config) => { - if (config.mode === 'full') { +const showMode = (mode?: Config['mode']) => { + if (!mode) { + mode = 'full'; + } + + if (mode === 'full') { if (params.view === 'editor') { split?.show('code', true); } @@ -481,7 +485,7 @@ const showMode = (config: Config) => { split?.show('output', true); } } - if (config.mode === 'editor' || config.mode === 'codeblock' || config.mode === 'result') { + if (mode === 'editor' || mode === 'codeblock' || mode === 'result') { split?.destroy(); split = null; } @@ -492,7 +496,7 @@ const showMode = (config: Config) => { codeblock: '010', result: '001', }; - const modeConfig = modes[config.mode] || '111'; + const modeConfig = modes[mode] || '111'; const toolbarElement = UI.getToolbarElement(); const editorContainerElement = UI.getEditorContainerElement(); @@ -536,19 +540,19 @@ const showMode = (config: Config) => { split?.destroy(true); split = null; } - if (config.mode === 'editor' || config.mode === 'codeblock') { + if (mode === 'editor' || mode === 'codeblock') { runButton.style.visibility = 'hidden'; codeRunButton.style.visibility = 'hidden'; } - if (config.mode === 'codeblock') { + if (mode === 'codeblock') { editorTools.style.display = 'none'; } - if (config.mode === 'result') { + if (mode === 'result') { if (!['full', 'open', 'closed'].includes(toolsPane?.getStatus() || '')) { toolsPane?.hide(); } } - if (config.mode === 'full' && !split) { + if (mode === 'full' && !split) { split = createSplitPanes(); } window.dispatchEvent(new Event(customEvents.resizeEditor)); @@ -1118,10 +1122,10 @@ const loadConfig = async ( flush = true, ) => { changingContent = true; - + const validConfig = upgradeAndValidate(newConfig); const content = getContentConfig({ ...defaultConfig, - ...upgradeAndValidate(newConfig), + ...validConfig, }); setConfig({ ...getConfig(), @@ -1152,6 +1156,9 @@ const loadConfig = async ( // load config await bootstrap(true); + showMode(validConfig.mode); + configureToolsPane(validConfig.tools); + changingContent = false; }; @@ -3689,6 +3696,30 @@ const loadToolsPane = async () => { getResultElement().classList.remove('full'); }; +const configureToolsPane = (tools?: Config['tools']) => { + if (!tools) { + toolsPane?.close(); + return; + } + if (tools.status === 'none') { + toolsPane?.hide(); + return; + } + if (tools.status === 'full') { + toolsPane?.maximize(); + } + if (tools.status === 'open') { + toolsPane?.open(); + } + if (tools.status === 'closed' || tools.status === '') { + toolsPane?.close(); + } + // TODO: handle tools.enabled + if (tools.active) { + toolsPane?.setActiveTool(tools.active); + } +}; + const basicHandlers = () => { notifications = createNotifications(); modal = createModal(); @@ -3715,7 +3746,7 @@ const basicHandlers = () => { handleFullscreen(); } - showMode(getConfig()); + showMode(getConfig().mode); }; const extraHandlers = async () => { @@ -4006,6 +4037,8 @@ const bootstrap = async (reload = false) => { } if (isEmbed && !getConfig().tests?.content?.trim()) { toolsPane?.disableTool('tests'); + } else { + toolsPane?.enableTool('tests'); } if (!reload) { @@ -4072,7 +4105,7 @@ const initializePlayground = async ( configureEmmet(getConfig()); }; -const createApi = (deps: { showMode: (config: Config) => void }): API => { +const createApi = (deps: { showMode: (mode: Config['mode']) => void }): API => { const apiGetShareUrl = async (shortUrl = false) => (await share(shortUrl, true, false)).url; const apiGetConfig = async (contentOnly = false): Promise => { @@ -4089,7 +4122,7 @@ const createApi = (deps: { showMode: (config: Config) => void }): API => { // TODO: apply changes in App AppConfig, UserConfig & EditorConfig if (newAppConfig.mode !== getConfig().mode) { - deps.showMode(newAppConfig); + deps.showMode(newAppConfig.mode); } setConfig(newAppConfig); diff --git a/src/livecodes/templates/starter/jest-react-starter.ts b/src/livecodes/templates/starter/jest-react-starter.ts index 77bdedfb0..d9ce50f58 100644 --- a/src/livecodes/templates/starter/jest-react-starter.ts +++ b/src/livecodes/templates/starter/jest-react-starter.ts @@ -54,11 +54,6 @@ const root = createRoot(document.querySelector("#app")); root.render(); `.trimStart(), }, - stylesheets: [], - scripts: [], - cssPreset: '', - imports: {}, - types: {}, tests: { language: 'tsx', content: ` @@ -113,4 +108,9 @@ describe("Page", () => { }); `.trimStart(), }, + tools: { + enabled: 'all', + active: 'tests', + status: 'open', + }, }; diff --git a/src/livecodes/templates/starter/jest-starter.ts b/src/livecodes/templates/starter/jest-starter.ts index e9a9a66b1..8273558ba 100644 --- a/src/livecodes/templates/starter/jest-starter.ts +++ b/src/livecodes/templates/starter/jest-starter.ts @@ -67,11 +67,6 @@ button.addEventListener( ); `.trimStart(), }, - stylesheets: [], - scripts: [], - cssPreset: '', - imports: {}, - types: {}, tests: { language: 'tsx', content: ` @@ -116,4 +111,9 @@ describe("Page", () => { }); `.trimStart(), }, + tools: { + enabled: 'all', + active: 'tests', + status: 'open', + }, }; diff --git a/src/sdk/models.ts b/src/sdk/models.ts index 62d2959c0..ba323560c 100644 --- a/src/sdk/models.ts +++ b/src/sdk/models.ts @@ -524,6 +524,7 @@ export type Template = Pick & { name: TemplateName; thumbnail: string; + tools?: Config['tools']; }; export type TemplateName = From 4ecfe83a17f600baba8961f2ec0c23dd6709bfe9 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Sat, 16 Sep 2023 19:20:01 +0300 Subject: [PATCH 06/10] fix(tools): fix showing toolspane in result mode --- src/livecodes/core.ts | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/livecodes/core.ts b/src/livecodes/core.ts index abfac90cc..481dab81f 100644 --- a/src/livecodes/core.ts +++ b/src/livecodes/core.ts @@ -140,6 +140,13 @@ import { customEvents } from './events/custom-events'; import { populateConfig } from './import/utils'; import { permanentUrlService } from './services/permanent-url'; +// declare global dependencies +declare const window: Window & { + deps: { + showMode: typeof showMode; + }; +}; + const stores: Stores = createStores(); const eventsManager = createEventsManager(); let notifications: ReturnType; @@ -1156,8 +1163,9 @@ const loadConfig = async ( // load config await bootstrap(true); - showMode(validConfig.mode); - configureToolsPane(validConfig.tools); + // layout + window.deps.showMode(validConfig.mode); + configureToolsPane(validConfig.tools, validConfig.mode); changingContent = false; }; @@ -3694,9 +3702,17 @@ const loadToolsPane = async () => { handleTests(); handleResultZoom(); getResultElement().classList.remove('full'); + configureToolsPane(getConfig().tools, getConfig().mode); }; -const configureToolsPane = (tools?: Config['tools']) => { +const configureToolsPane = ( + tools: Config['tools'] | undefined, + mode: Config['mode'] | undefined, +) => { + if (mode === 'result' && (!tools || tools.status === '' || tools.status === 'none')) { + toolsPane?.hide(); + return; + } if (!tools) { toolsPane?.close(); return; @@ -3746,7 +3762,8 @@ const basicHandlers = () => { handleFullscreen(); } - showMode(getConfig().mode); + window.deps.showMode(getConfig().mode); + configureToolsPane(getConfig().tools, getConfig().mode); }; const extraHandlers = async () => { @@ -4105,7 +4122,7 @@ const initializePlayground = async ( configureEmmet(getConfig()); }; -const createApi = (deps: { showMode: (mode: Config['mode']) => void }): API => { +const createApi = (): API => { const apiGetShareUrl = async (shortUrl = false) => (await share(shortUrl, true, false)).url; const apiGetConfig = async (contentOnly = false): Promise => { @@ -4122,7 +4139,7 @@ const createApi = (deps: { showMode: (mode: Config['mode']) => void }): API => { // TODO: apply changes in App AppConfig, UserConfig & EditorConfig if (newAppConfig.mode !== getConfig().mode) { - deps.showMode(newAppConfig.mode); + window.deps.showMode(newAppConfig.mode); } setConfig(newAppConfig); @@ -4251,28 +4268,32 @@ const createApi = (deps: { showMode: (mode: Config['mode']) => void }): API => { }; const initApp = async (config: Partial, baseUrl: string) => { + window.deps = { showMode }; await initializePlayground({ config, baseUrl }, async () => { basicHandlers(); await loadToolsPane(); await extraHandlers(); }); - return createApi({ showMode }); + return createApi(); }; const initEmbed = async (config: Partial, baseUrl: string) => { + window.deps = { showMode }; await initializePlayground({ config, baseUrl, isEmbed: true }, async () => { basicHandlers(); await loadToolsPane(); }); - return createApi({ showMode }); + return createApi(); }; const initLite = async (config: Partial, baseUrl: string) => { + window.deps = { showMode }; await initializePlayground({ config, baseUrl, isEmbed: true, isLite: true }, () => { basicHandlers(); }); - return createApi({ showMode }); + return createApi(); }; const initHeadless = async (config: Partial, baseUrl: string) => { + window.deps = { showMode: () => undefined }; await initializePlayground({ config, baseUrl, isEmbed: true, isHeadless: true }, () => { notifications = { info: () => undefined, @@ -4286,7 +4307,7 @@ const initHeadless = async (config: Partial, baseUrl: string) => { handleConsole(); handleTestResults(); }); - return createApi({ showMode: () => undefined }); + return createApi(); }; export { initApp, initEmbed, initLite, initHeadless }; From 0f636889953b2c8c0961b855592cdbac0983c2ad Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Sat, 16 Sep 2023 21:11:06 +0300 Subject: [PATCH 07/10] feat(config): update UI on loading new config --- src/livecodes/core.ts | 24 ++++++++++++------- .../templates/starter/jest-react-starter.ts | 1 + .../templates/starter/jest-starter.ts | 1 + src/livecodes/toolspane/test-viewer.ts | 4 +++- src/sdk/models.ts | 1 + 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/livecodes/core.ts b/src/livecodes/core.ts index 481dab81f..4daefaede 100644 --- a/src/livecodes/core.ts +++ b/src/livecodes/core.ts @@ -1134,13 +1134,15 @@ const loadConfig = async ( ...defaultConfig, ...validConfig, }); - setConfig({ + const config = { ...getConfig(), + ...(validConfig.autotest != null ? { autotest: validConfig.autotest } : {}), + ...(validConfig.mode != null ? { mode: validConfig.mode } : {}), + ...(validConfig.tools != null ? { tools: validConfig.tools } : {}), ...content, - }); - await importExternalContent({ - config: getConfig(), - }); + }; + setConfig(config); + await importExternalContent({ config }); setProjectRecover(); if (flush) { @@ -1163,13 +1165,19 @@ const loadConfig = async ( // load config await bootstrap(true); - // layout - window.deps.showMode(validConfig.mode); - configureToolsPane(validConfig.tools, validConfig.mode); + updateUI(config); changingContent = false; }; +const updateUI = (config: Config) => { + window.deps.showMode(config.mode); + configureToolsPane(config.tools, config.mode); + if (config.autotest) { + UI.getWatchTestsButton()?.classList.remove('disabled'); + } +}; + const setUserConfig = (newConfig: Partial | null, save = true) => { const userConfig = getUserConfig({ ...getConfig(), diff --git a/src/livecodes/templates/starter/jest-react-starter.ts b/src/livecodes/templates/starter/jest-react-starter.ts index d9ce50f58..9eb5e9619 100644 --- a/src/livecodes/templates/starter/jest-react-starter.ts +++ b/src/livecodes/templates/starter/jest-react-starter.ts @@ -5,6 +5,7 @@ export const jestReactStarter: Template = { title: 'Jest/React Starter', thumbnail: 'assets/templates/jest.svg', activeEditor: 'script', + autotest: true, markup: { language: 'html', content: ` diff --git a/src/livecodes/templates/starter/jest-starter.ts b/src/livecodes/templates/starter/jest-starter.ts index 8273558ba..ea19ccd7b 100644 --- a/src/livecodes/templates/starter/jest-starter.ts +++ b/src/livecodes/templates/starter/jest-starter.ts @@ -4,6 +4,7 @@ export const jestStarter: Template = { name: 'jest', title: 'Jest Starter', thumbnail: 'assets/templates/jest.svg', + autotest: true, activeEditor: 'script', markup: { language: 'html', diff --git a/src/livecodes/toolspane/test-viewer.ts b/src/livecodes/toolspane/test-viewer.ts index ebd30a80f..c60eff2a0 100644 --- a/src/livecodes/toolspane/test-viewer.ts +++ b/src/livecodes/toolspane/test-viewer.ts @@ -1,6 +1,7 @@ +/* eslint-disable import/no-internal-modules */ import type { Config, Editors, EventsManager, TestResult, TestViewer } from '../models'; +import { sandboxService } from '../services/sandbox'; import { getToolspaneElement } from '../UI'; -// eslint-disable-next-line import/no-internal-modules import * as icons from '../UI/icons'; export const createTestViewer = ( @@ -101,6 +102,7 @@ export const createTestViewer = ( item.classList.add('test-result', result.status); result.errors .map((err) => err.split('at Object.')[0]?.trim()) + .map((err) => err.split(`at ${sandboxService.getResultUrl()}`)[0]?.trim()) .map((err) => err.startsWith('AssertionError: ') ? err.replace('AssertionError: ', '') : err, ) diff --git a/src/sdk/models.ts b/src/sdk/models.ts index ba323560c..394eb72af 100644 --- a/src/sdk/models.ts +++ b/src/sdk/models.ts @@ -525,6 +525,7 @@ export type Template = Pick Date: Sat, 16 Sep 2023 22:44:04 +0300 Subject: [PATCH 08/10] fix toolspane status --- src/livecodes/core.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/livecodes/core.ts b/src/livecodes/core.ts index 4daefaede..59c919c9f 100644 --- a/src/livecodes/core.ts +++ b/src/livecodes/core.ts @@ -3710,7 +3710,6 @@ const loadToolsPane = async () => { handleTests(); handleResultZoom(); getResultElement().classList.remove('full'); - configureToolsPane(getConfig().tools, getConfig().mode); }; const configureToolsPane = ( @@ -3721,6 +3720,9 @@ const configureToolsPane = ( toolsPane?.hide(); return; } + if (tools?.active) { + toolsPane?.setActiveTool(tools.active); + } if (!tools) { toolsPane?.close(); return; @@ -3739,9 +3741,6 @@ const configureToolsPane = ( toolsPane?.close(); } // TODO: handle tools.enabled - if (tools.active) { - toolsPane?.setActiveTool(tools.active); - } }; const basicHandlers = () => { @@ -3769,9 +3768,6 @@ const basicHandlers = () => { handleExternalResources(); handleFullscreen(); } - - window.deps.showMode(getConfig().mode); - configureToolsPane(getConfig().tools, getConfig().mode); }; const extraHandlers = async () => { From 8b38299492763841527443e7340dab0b3dba0971 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Sun, 17 Sep 2023 00:33:29 +0300 Subject: [PATCH 09/10] upgrade browser-jest to fix jest mocks --- src/livecodes/vendors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/livecodes/vendors.ts b/src/livecodes/vendors.ts index b25982cd8..a238a7492 100644 --- a/src/livecodes/vendors.ts +++ b/src/livecodes/vendors.ts @@ -35,7 +35,7 @@ export const biwaschemeUrl = /* @__PURE__ */ getUrl('biwascheme@0.8.0/release/bi export const blocklyCdnBaseUrl = /* @__PURE__ */ getUrl('blockly@9.3.3/'); export const browserJestUrl = /* @__PURE__ */ getUrl( - '@live-codes/browser-jest@0.0.2/dist/browser-jest.umd.js', + '@live-codes/browser-jest@0.0.3/dist/browser-jest.umd.js', ); export const brythonBaseUrl = /* @__PURE__ */ getUrl('brython@3.11.2/'); From ec8778f9272f5970c6e10700e95bef401834d7d7 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Sun, 17 Sep 2023 00:34:32 +0300 Subject: [PATCH 10/10] docs(tools): add docs for test updates --- docs/docs/configuration/configuration-object.md | 8 ++++++++ docs/docs/features/tests.md | 4 ++-- docs/docs/sdk/js-ts.md | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/docs/configuration/configuration-object.md b/docs/docs/configuration/configuration-object.md index 7f8f1fe46..4c95bde37 100644 --- a/docs/docs/configuration/configuration-object.md +++ b/docs/docs/configuration/configuration-object.md @@ -371,6 +371,14 @@ Default: `false` If `true`, the project is automatically saved on code change, after time [delay](#delay). +### `autotest` + +Type: [`boolean`](../api/interfaces/Config.md#autotest) + +Default: `false` + +If `true`, the project is watched for code changes which trigger tests to auto-run. + ### `delay` Type: [`number`](../api/interfaces/Config.md#delay) diff --git a/docs/docs/features/tests.md b/docs/docs/features/tests.md index ef7c5e023..5d38651ee 100644 --- a/docs/docs/features/tests.md +++ b/docs/docs/features/tests.md @@ -105,8 +105,8 @@ describe('test global', () => { ## Supported Jest features -- Jest globals: `expect`, `test`, `it`, `describe`, `beforeAll`, `afterAll`, `beforeEach`, `afterEach` -- Jest function mocks: `jest.fn`, `jest.spyOn`, `jest.clearAllMocks`, `jest.resetAllMocks` +- [Jest globals](https://jestjs.io/docs/api): `expect`, `test`, `xtest`, `it`, `fit`, `xit`, `describe`, `fdescribe`, `xdescribe`, `beforeAll`, `afterAll`, `beforeEach`, `afterEach` +- Jest function mocks: `jest.fn`, `jest.mocked`, `jest.replaceProperty`, `jest.spyOn` These can be directly used in the test editor, without the need for any imports. Autocomplete is available in Monaco editor for Jest API. diff --git a/docs/docs/sdk/js-ts.md b/docs/docs/sdk/js-ts.md index b2f7c8b19..c198a64a2 100644 --- a/docs/docs/sdk/js-ts.md +++ b/docs/docs/sdk/js-ts.md @@ -379,7 +379,7 @@ createPlayground('#container').then((playground) => { const testsWatcher = playground.watch('tests', ({ results }) => { // this will run when tests run results.forEach((testResult) => { - console.log('status:', testResult.status); // "pass" or "fail" + console.log('status:', testResult.status); // "pass", "fail" or "skip" console.log(testResult.errors); // array of errors as strings }); });