diff --git a/README.md b/README.md index 2a0b200..040ad5f 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ The goal of this library is to make writing and debugging tests iteratively easi Install the package via: -```npm install react-testing-visualizer``` +```npm install testing-library-visualizer``` If you want to have your app's styling and assets available while debugging then you should build the application and put the following in a jest setup file. If you're using Create React App the following can go into `setupTests.js`. If you don't have a file that sets up the jest context, you can [specify one](https://jestjs.io/docs/configuration#setupfiles-array). ```javascript import { setup, -} from "react-testing-visualizer"; +} from "testing-library-visualizer"; import path from "path"; import { expect } from "@jest/globals"; import { screen, within, fireEvent } from "@testing-library/react"; @@ -32,7 +32,7 @@ registerCommands({ screen, within, fireEvent, userEvent, expect }); // This shou Once setup, debugging a test is simple. In the test file with the test you want to debug add: ```javascript -import { debugTest } from "react-testing-visualizer"; +import { debugTest } from "testing-library-visualizer"; ``` Then replace `test` with `debugTest`. For example: @@ -40,7 +40,7 @@ Then replace `test` with `debugTest`. For example: ```javascript import { render } from "@testing-library/react"; import App from "./App"; -import { debugTest } from "react-testing-visualizer"; +import { debugTest } from "testing-library-visualizer"; debugTest("Test App", async () => { render(); @@ -105,7 +105,7 @@ To do this you'll need to add the following to your jest test setup file. ```javascript import { registerStyling, -} from "react-testing-visualizer"; +} from "testing-library-visualizer"; registerStyling(/* */); ``` @@ -119,7 +119,7 @@ In many projects you'll define custom commands that you will want to run in the ```javascript import { registerCommands, -} from "react-testing-visualizer"; +} from "testing-library-visualizer"; registerCommands({ test: () => { diff --git a/package/package-lock.json b/package/package-lock.json index 84a2138..e338005 100644 --- a/package/package-lock.json +++ b/package/package-lock.json @@ -1,11 +1,11 @@ { - "name": "react-testing-visualizer", + "name": "testing-library-visualizer", "version": "0.1.9", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "react-testing-visualizer", + "name": "testing-library-visualizer", "version": "0.1.9", "license": "MIT", "dependencies": { diff --git a/package/src/commandParser.js b/package/src/commandParser.js index b1be0a4..2f95560 100644 --- a/package/src/commandParser.js +++ b/package/src/commandParser.js @@ -25,7 +25,9 @@ function refresh() { let IDENTIFIER_MAP = { highlight, refresh, + console, }; + export const registerCommands = (commands) => { IDENTIFIER_MAP = { ...IDENTIFIER_MAP, ...commands }; }; @@ -133,39 +135,71 @@ export async function runCommand( }); var lineNumber = 0; + let consoleOutputs = []; + + function parseLogs(lineNumber, consoleOutputs) { + for (const consoleLog of consoleLogQueue) { + if (!consoleLog.seen) { + consoleLog.seen = true; + + for (const arg of consoleLog.arguments) { + if (consoleLog.method === "error") { + consoleOutputs.push({ + type: "error", + message: `Error printed to console.error. This error occurred asynchronously, and may have happened before this line was executed.\n\n${arg}`, + lineNumber, + }); + } else { + consoleOutputs.push({ + message: String(arg), + type: consoleLog.method, + lineNumber, + }); + } + } + } + } + } try { if (parseTree.type !== "Program") { throw SyntaxError; } - for (const statement of parseTree.body) { - await evaluator.traverseTree(statement); - lineNumber += 1; - } - lineNumber -= 1; - function delay(time) { return new Promise((resolve) => setTimeout(resolve, time)); } - await delay(10); + for (const statement of parseTree.body) { + await evaluator.traverseTree(statement); - for (const consoleLog of consoleLogQueue) { - if (consoleLog.method === "error" && !consoleLog.seen) { - consoleLog.seen = true; - throw Error( - `Error printed to console.error. This error occurred asynchronously, and may have happened before this line was executed.\n\n${consoleLog.arguments[0]}` - ); - } + parseLogs(lineNumber, consoleOutputs); + lineNumber += 1; } - return { ok: true, error: null, lineNumber: null }; + lineNumber -= 1; + await delay(1); + + // Run this again after the delay in case there are more messages + parseLogs(lineNumber, consoleOutputs); + + return { + ok: true, + error: null, + consoleOutputs, + }; } catch (error) { return { ok: false, - error: { ...error, message: removeUnicodeColor(error.message) }, - lineNumber, + error: { ...error }, + consoleOutputs: [ + ...consoleOutputs, + { + type: "error", + message: removeUnicodeColor(error.message), + lineNumber, + }, + ], }; } } diff --git a/package/src/commandParser.test.js b/package/src/commandParser.test.js index 9fe25bb..2ba98eb 100644 --- a/package/src/commandParser.test.js +++ b/package/src/commandParser.test.js @@ -86,7 +86,7 @@ function TestComponent() { return ( <> {text} - @@ -94,17 +94,30 @@ function TestComponent() { } test("reports warnings", async () => { + await debuggerSetup(async () => { + render(); + const output = ( + await runCommand( + "userEvent.click(screen.getByText('click me'))", + consoleLogQueue + ) + ).consoleOutputs[0]; + expect(output.message).toEqual( + expect.stringMatching(/not wrapped in act/g) + ); + expect(output.type).toEqual("error"); + }); +}); + +test("reports console logs", async () => { await debuggerSetup(async () => { render(); - expect( - ( - await runCommand( - "userEvent.click(screen.getByText('click me'))", - consoleLogQueue - ) - ).error.message - ).toEqual(expect.stringMatching(/not wrapped in act/g)); + const output = ( + await runCommand("console.log('hello world')", consoleLogQueue) + ).consoleOutputs[0]; + expect(output.message).toEqual(expect.stringMatching(/hello world/g)); + expect(output.type).toEqual("log"); }); }); diff --git a/package/src/testingUtil.js b/package/src/testingUtil.js index f81aee4..318d76d 100644 --- a/package/src/testingUtil.js +++ b/package/src/testingUtil.js @@ -61,6 +61,9 @@ export const debuggerSetup = async (fn) => { consoleLogQueue.push({ method: "log", arguments: arguments }); return _log.apply(console, arguments); }; + console.log_without_reporting = function () { + return _log.apply(console, arguments); + }; console.warn = function () { consoleLogQueue.push({ method: "warn", arguments: arguments }); @@ -175,11 +178,7 @@ fastify.post("/command", async (request, reply) => { html: addStyleLinks( replaceFilePaths(document.documentElement.innerHTML, manifest) ), - error: output.error && { - name: output.error.name, - message: output.error.message, - lineNumber: output.lineNumber, - }, + consoleOutputs: output.consoleOutputs, }; }); @@ -200,7 +199,9 @@ export const start = async (setupFunction) => { await getCssFiles(); await setupFunction(); await fastify.listen(3001); - console.log("Debug server is running, open at localhost:3001"); + console.log_without_reporting( + "Debug server is running, open at localhost:3001" + ); while (isListening) { await sleep(50); } diff --git a/test-app/package-lock.json b/test-app/package-lock.json index 5bfe0d3..28d4670 100644 --- a/test-app/package-lock.json +++ b/test-app/package-lock.json @@ -13,7 +13,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "^5.0.0", - "react-testing-visualizer": "file:../package", + "testing-library-visualizer": "file:../package", "web-vitals": "^2.1.2" }, "devDependencies": { @@ -23,7 +23,7 @@ } }, "../package": { - "name": "react-testing-visualizer", + "name": "testing-library-visualizer", "version": "0.1.9", "license": "MIT", "dependencies": { @@ -13111,10 +13111,6 @@ } } }, - "node_modules/react-testing-visualizer": { - "resolved": "../package", - "link": true - }, "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -14573,6 +14569,10 @@ "node": ">=8" } }, + "node_modules/testing-library-visualizer": { + "resolved": "../package", + "link": true + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -25201,31 +25201,6 @@ "workbox-webpack-plugin": "^6.4.1" } }, - "react-testing-visualizer": { - "version": "file:../package", - "requires": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-transform-react-jsx": "^7.16.7", - "@babel/preset-env": "^7.16.11", - "@testing-library/dom": "^8.11.1", - "@testing-library/jest-dom": "^5.16.1", - "@testing-library/react": "^12.1.2", - "@testing-library/user-event": "^13.5.0", - "acorn": "^8.7.0", - "babel-jest": "^27.4.6", - "eslint": "^8.8.0", - "eslint-config-react-app": "^7.0.0", - "fastify": "^3.25.0", - "fastify-static": "^4.5.0", - "jest": "^27.4.7", - "prettier": "2.5.1", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "semantic-release": "^19.0.2", - "webpack-cli": "^4.9.2" - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -26294,6 +26269,31 @@ "minimatch": "^3.0.4" } }, + "testing-library-visualizer": { + "version": "file:../package", + "requires": { + "@babel/cli": "^7.16.8", + "@babel/core": "^7.16.12", + "@babel/plugin-transform-react-jsx": "^7.16.7", + "@babel/preset-env": "^7.16.11", + "@testing-library/dom": "^8.11.1", + "@testing-library/jest-dom": "^5.16.1", + "@testing-library/react": "^12.1.2", + "@testing-library/user-event": "^13.5.0", + "acorn": "^8.7.0", + "babel-jest": "^27.4.6", + "eslint": "^8.8.0", + "eslint-config-react-app": "^7.0.0", + "fastify": "^3.25.0", + "fastify-static": "^4.5.0", + "jest": "^27.4.7", + "prettier": "2.5.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "semantic-release": "^19.0.2", + "webpack-cli": "^4.9.2" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/test-app/package.json b/test-app/package.json index 3e0e43f..a647d5a 100644 --- a/test-app/package.json +++ b/test-app/package.json @@ -8,7 +8,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "^5.0.0", - "react-testing-visualizer": "file:../package", + "testing-library-visualizer": "file:../package", "web-vitals": "^2.1.2" }, "scripts": { diff --git a/test-app/src/App.test.js b/test-app/src/App.test.js index ba20c35..236c692 100644 --- a/test-app/src/App.test.js +++ b/test-app/src/App.test.js @@ -1,6 +1,6 @@ import { render, screen } from "@testing-library/react"; import App from "./App"; -import { debugTest } from "react-testing-visualizer"; +import { debugTest } from "testing-library-visualizer"; debugTest("renders learn react link", async () => { render(); diff --git a/test-app/src/setupTests.js b/test-app/src/setupTests.js index 700e275..7195cee 100644 --- a/test-app/src/setupTests.js +++ b/test-app/src/setupTests.js @@ -7,14 +7,13 @@ import { registerStyling, setup, registerCommands, -} from "react-testing-visualizer"; +} from "testing-library-visualizer"; import path from "path"; +import { screen, within, fireEvent } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; setup(path.join(__dirname, "..", "build")); registerStyling("static/css/test.css"); -registerCommands({ - test: () => { - console.log("test command"); - }, -}); + +registerCommands({ screen, within, fireEvent, userEvent, expect }); diff --git a/test-runner/package-lock.json b/test-runner/package-lock.json index 051b082..ea8551d 100644 --- a/test-runner/package-lock.json +++ b/test-runner/package-lock.json @@ -35,7 +35,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "^5.0.0", - "react-testing-visualizer": "^1.0.1", + "testing-library-visualizer": "^1.0.3", "web-vitals": "^2.1.2" }, "devDependencies": { @@ -43,6 +43,39 @@ "prettier": "2.5.1" } }, + "../package": { + "name": "testing-library-visualizer", + "version": "0.1.9", + "extraneous": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.7.0", + "fastify": "^3.25.0", + "fastify-static": "^4.5.0" + }, + "devDependencies": { + "@babel/cli": "^7.16.8", + "@babel/core": "^7.16.12", + "@babel/plugin-transform-react-jsx": "^7.16.7", + "@babel/preset-env": "^7.16.11", + "@testing-library/dom": "^8.11.1", + "@testing-library/jest-dom": "^5.16.1", + "@testing-library/react": "^12.1.2", + "@testing-library/user-event": "^13.5.0", + "babel-jest": "^27.4.6", + "eslint": "^8.8.0", + "eslint-config-react-app": "^7.0.0", + "jest": "^27.4.7", + "prettier": "2.5.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "semantic-release": "^19.0.2", + "webpack-cli": "^4.9.2" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@babel/code-frame": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", @@ -4587,9 +4620,9 @@ } }, "node_modules/avvio": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.2.tgz", - "integrity": "sha512-XW2CMCmZaCmCCsIaJaLKxAzPwF37fXi1KGxNOvedOpeisLdmxZnblGc3hpHWYnlP+KOUxZsazh43WXNHgXpbqw==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.5.tgz", + "integrity": "sha512-AOhBxyLVdpOad3TujtC9kL/9r3HnTkxwQ5ggOsYrvvZP1cCFvzHWJd5XxZDFuTn+IN8vkKSG5SEJrd27vCSbeA==", "dependencies": { "archy": "^1.0.0", "debug": "^4.0.0", @@ -7505,9 +7538,9 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "node_modules/fast-redact": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.0.tgz", - "integrity": "sha512-dir8LOnvialLxiXDPESMDHGp82CHi6ZEYTVkcvdn5d7psdv9ZkkButXrOeXST4aqreIRR+N7CYlsrwFuorurVg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.1.tgz", + "integrity": "sha512-odVmjC8x8jNeMZ3C+rPMESzXVSEU8tSWSHv9HFxP2mm89G/1WwqhrerJDQm9Zus8X6aoRgQDThKqptdNA6bt+A==", "engines": { "node": ">=6" } @@ -7518,9 +7551,9 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "node_modules/fastify": { - "version": "3.27.1", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.27.1.tgz", - "integrity": "sha512-GLn3ow5BGqg/m+ztXvztp8Xp7SuH99vAm4zfbN7407Qzi4mB055SG/lWH/gYolz5Oq2K8LtUpZqt1Ccf/YkVmA==", + "version": "3.27.4", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.27.4.tgz", + "integrity": "sha512-SOfnHBxG9zxCSIvt6aHoR/cao8QBddWmGP/mb5KQKRc+KI1kB7b79M2hCDOTSyHdLAF2OX+oI6X3weeLc+MqKg==", "dependencies": { "@fastify/ajv-compiler": "^1.0.0", "abstract-logging": "^2.0.0", @@ -7536,7 +7569,7 @@ "rfdc": "^1.1.4", "secure-json-parse": "^2.0.0", "semver": "^7.3.2", - "tiny-lru": "^7.0.0" + "tiny-lru": "^8.0.1" } }, "node_modules/fastify-error": { @@ -7550,9 +7583,9 @@ "integrity": "sha512-qKcDXmuZadJqdTm6vlCqioEbyewF60b/0LOFCcYN1B6BIZGlYJumWWOYs70SFYLDAH4YqdE1cxH/RKMG7rFxgA==" }, "node_modules/fastify-static": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.5.0.tgz", - "integrity": "sha512-Q7Tgl55AjsmBwiO4hKYib2BUCt+XTWLJ6Xp8YPPHU3EsrKNpevJ4cz8pjf1Ey1QhHw9O8Y2FDKdu+IC74oHvqw==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.6.1.tgz", + "integrity": "sha512-vy7N28U4AMhuOim12ZZWHulEE6OQKtzZbHgiB8Zj4llUuUQXPka0WHAQI3njm1jTCx4W6fixUHfpITxweMtAIA==", "dependencies": { "content-disposition": "^0.5.3", "encoding-negotiator": "^2.0.1", @@ -7563,11 +7596,6 @@ "send": "^0.17.1" } }, - "node_modules/fastify-warning": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", - "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==" - }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -11008,20 +11036,20 @@ } }, "node_modules/light-my-request": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.7.1.tgz", - "integrity": "sha512-7/bT6M+iHY90L9/rW7aboVYt0o0uOqzIx4yofC67d6WE9uLksecGf3mxPuZfVjMONG+i6hCq6z5NZCxqzpA2yw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.8.0.tgz", + "integrity": "sha512-C2XESrTRsZnI59NSQigOsS6IuTxpj8OhSBvZS9fhgBMsamBsAuWN1s4hj/nCi8EeZcyAA6xbROhsZy7wKdfckg==", "dependencies": { "ajv": "^8.1.0", "cookie": "^0.4.0", - "fastify-warning": "^0.2.0", + "process-warning": "^1.0.0", "set-cookie-parser": "^2.4.1" } }, "node_modules/light-my-request/node_modules/ajv": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", - "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -11931,9 +11959,9 @@ } }, "node_modules/pino": { - "version": "6.13.4", - "resolved": "https://registry.npmjs.org/pino/-/pino-6.13.4.tgz", - "integrity": "sha512-g4tHSISmQJYUEKEMVdaZ+ZokWwFnTwZL5JPn+lnBVZ1BuBbrSchrXwQINknkM5+Q4fF6U9NjiI8PWwwMDHt9zA==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz", + "integrity": "sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==", "dependencies": { "fast-redact": "^3.0.0", "fast-safe-stringify": "^2.0.8", @@ -13622,19 +13650,6 @@ } } }, - "node_modules/react-testing-visualizer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-testing-visualizer/-/react-testing-visualizer-1.0.1.tgz", - "integrity": "sha512-L5SXKPf0ge1nWRdoJ8X7YJh0r1c+njkiwRZPoeBVK5Oxn8n627WFb2hnJbz2utzifg4mCyBgTDu4F4wJQF1H+g==", - "dependencies": { - "acorn": "^8.7.0", - "fastify": "^3.25.0", - "fastify-static": "^4.5.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -15152,6 +15167,19 @@ "node": ">=8" } }, + "node_modules/testing-library-visualizer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/testing-library-visualizer/-/testing-library-visualizer-1.0.3.tgz", + "integrity": "sha512-DzkDzitMC68/2gUGwE0YdX1V0PS1m4Pyil+81s9m0x8X33xEc7dtGhjKic5cj+6OnPErwMnC3TfDk3Ap3J5aEg==", + "dependencies": { + "acorn": "^8.7.0", + "fastify": "^3.25.0", + "fastify-static": "^4.5.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -15173,9 +15201,9 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, "node_modules/tiny-lru": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", - "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-8.0.2.tgz", + "integrity": "sha512-ApGvZ6vVvTNdsmt676grvCkUCGwzG9IqXma5Z07xJgiC5L7akUMof5U8G2JTI9Rz/ovtVhJBlY6mNhEvtjzOIg==", "engines": { "node": ">=6" } @@ -19782,9 +19810,9 @@ } }, "avvio": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.2.tgz", - "integrity": "sha512-XW2CMCmZaCmCCsIaJaLKxAzPwF37fXi1KGxNOvedOpeisLdmxZnblGc3hpHWYnlP+KOUxZsazh43WXNHgXpbqw==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.5.tgz", + "integrity": "sha512-AOhBxyLVdpOad3TujtC9kL/9r3HnTkxwQ5ggOsYrvvZP1cCFvzHWJd5XxZDFuTn+IN8vkKSG5SEJrd27vCSbeA==", "requires": { "archy": "^1.0.0", "debug": "^4.0.0", @@ -21962,9 +21990,9 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "fast-redact": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.0.tgz", - "integrity": "sha512-dir8LOnvialLxiXDPESMDHGp82CHi6ZEYTVkcvdn5d7psdv9ZkkButXrOeXST4aqreIRR+N7CYlsrwFuorurVg==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.1.tgz", + "integrity": "sha512-odVmjC8x8jNeMZ3C+rPMESzXVSEU8tSWSHv9HFxP2mm89G/1WwqhrerJDQm9Zus8X6aoRgQDThKqptdNA6bt+A==" }, "fast-safe-stringify": { "version": "2.1.1", @@ -21972,9 +22000,9 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "fastify": { - "version": "3.27.1", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.27.1.tgz", - "integrity": "sha512-GLn3ow5BGqg/m+ztXvztp8Xp7SuH99vAm4zfbN7407Qzi4mB055SG/lWH/gYolz5Oq2K8LtUpZqt1Ccf/YkVmA==", + "version": "3.27.4", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.27.4.tgz", + "integrity": "sha512-SOfnHBxG9zxCSIvt6aHoR/cao8QBddWmGP/mb5KQKRc+KI1kB7b79M2hCDOTSyHdLAF2OX+oI6X3weeLc+MqKg==", "requires": { "@fastify/ajv-compiler": "^1.0.0", "abstract-logging": "^2.0.0", @@ -21990,7 +22018,7 @@ "rfdc": "^1.1.4", "secure-json-parse": "^2.0.0", "semver": "^7.3.2", - "tiny-lru": "^7.0.0" + "tiny-lru": "^8.0.1" } }, "fastify-error": { @@ -22004,9 +22032,9 @@ "integrity": "sha512-qKcDXmuZadJqdTm6vlCqioEbyewF60b/0LOFCcYN1B6BIZGlYJumWWOYs70SFYLDAH4YqdE1cxH/RKMG7rFxgA==" }, "fastify-static": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.5.0.tgz", - "integrity": "sha512-Q7Tgl55AjsmBwiO4hKYib2BUCt+XTWLJ6Xp8YPPHU3EsrKNpevJ4cz8pjf1Ey1QhHw9O8Y2FDKdu+IC74oHvqw==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.6.1.tgz", + "integrity": "sha512-vy7N28U4AMhuOim12ZZWHulEE6OQKtzZbHgiB8Zj4llUuUQXPka0WHAQI3njm1jTCx4W6fixUHfpITxweMtAIA==", "requires": { "content-disposition": "^0.5.3", "encoding-negotiator": "^2.0.1", @@ -22017,11 +22045,6 @@ "send": "^0.17.1" } }, - "fastify-warning": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", - "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==" - }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -24467,20 +24490,20 @@ } }, "light-my-request": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.7.1.tgz", - "integrity": "sha512-7/bT6M+iHY90L9/rW7aboVYt0o0uOqzIx4yofC67d6WE9uLksecGf3mxPuZfVjMONG+i6hCq6z5NZCxqzpA2yw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.8.0.tgz", + "integrity": "sha512-C2XESrTRsZnI59NSQigOsS6IuTxpj8OhSBvZS9fhgBMsamBsAuWN1s4hj/nCi8EeZcyAA6xbROhsZy7wKdfckg==", "requires": { "ajv": "^8.1.0", "cookie": "^0.4.0", - "fastify-warning": "^0.2.0", + "process-warning": "^1.0.0", "set-cookie-parser": "^2.4.1" }, "dependencies": { "ajv": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", - "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -25140,9 +25163,9 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pino": { - "version": "6.13.4", - "resolved": "https://registry.npmjs.org/pino/-/pino-6.13.4.tgz", - "integrity": "sha512-g4tHSISmQJYUEKEMVdaZ+ZokWwFnTwZL5JPn+lnBVZ1BuBbrSchrXwQINknkM5+Q4fF6U9NjiI8PWwwMDHt9zA==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz", + "integrity": "sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==", "requires": { "fast-redact": "^3.0.0", "fast-safe-stringify": "^2.0.8", @@ -26260,16 +26283,6 @@ "workbox-webpack-plugin": "^6.4.1" } }, - "react-testing-visualizer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-testing-visualizer/-/react-testing-visualizer-1.0.1.tgz", - "integrity": "sha512-L5SXKPf0ge1nWRdoJ8X7YJh0r1c+njkiwRZPoeBVK5Oxn8n627WFb2hnJbz2utzifg4mCyBgTDu4F4wJQF1H+g==", - "requires": { - "acorn": "^8.7.0", - "fastify": "^3.25.0", - "fastify-static": "^4.5.0" - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -27390,6 +27403,16 @@ "minimatch": "^3.0.4" } }, + "testing-library-visualizer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/testing-library-visualizer/-/testing-library-visualizer-1.0.3.tgz", + "integrity": "sha512-DzkDzitMC68/2gUGwE0YdX1V0PS1m4Pyil+81s9m0x8X33xEc7dtGhjKic5cj+6OnPErwMnC3TfDk3Ap3J5aEg==", + "requires": { + "acorn": "^8.7.0", + "fastify": "^3.25.0", + "fastify-static": "^4.5.0" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -27411,9 +27434,9 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, "tiny-lru": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", - "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==" + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-8.0.2.tgz", + "integrity": "sha512-ApGvZ6vVvTNdsmt676grvCkUCGwzG9IqXma5Z07xJgiC5L7akUMof5U8G2JTI9Rz/ovtVhJBlY6mNhEvtjzOIg==" }, "tmpl": { "version": "1.0.5", diff --git a/test-runner/package.json b/test-runner/package.json index a1cf9f8..0f5fd34 100644 --- a/test-runner/package.json +++ b/test-runner/package.json @@ -30,7 +30,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "^5.0.0", - "react-testing-visualizer": "^1.0.1", + "testing-library-visualizer": "^1.0.3", "web-vitals": "^2.1.2" }, "proxy": "http://localhost:3001", diff --git a/test-runner/src/App.css b/test-runner/src/App.css index 5d6bb65..da2280c 100644 --- a/test-runner/src/App.css +++ b/test-runner/src/App.css @@ -11,7 +11,6 @@ flex-direction: column; width: 450px; justify-content: space-between; - } .test-iframe { @@ -22,7 +21,6 @@ width: 100%; } - button { background-color: rgb(95, 95, 231); color: white; @@ -44,4 +42,10 @@ button:focus { .code-window-titles { font-size: 15px; margin: 10px; -} \ No newline at end of file +} + +.editor-div { + flex: 1 1 20%; + min-height: 0; + overflow: auto; +} diff --git a/test-runner/src/App.test.js b/test-runner/src/App.test.js index 62fb970..379e4df 100644 --- a/test-runner/src/App.test.js +++ b/test-runner/src/App.test.js @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from "@testing-library/react"; +import { render, screen, waitFor, within } from "@testing-library/react"; import App from "./App"; import MockAdapter from "axios-mock-adapter"; import axios from "axios"; @@ -42,9 +42,12 @@ test("can send command", async () => { "
Hello World
" ); - await user.click(screen.getAllByRole("textbox")[1]); + await user.click( + within(screen.getByTestId("command-input")).getByRole("textbox") + ); mockAxios.onPost("/command", { command: "refresh()" }).reply(200, { html: "
Goodbye
", + consoleOutputs: [], }); await user.keyboard("refresh()"); @@ -57,7 +60,39 @@ test("can send command", async () => { ) ); - expect(screen.getAllByRole("textbox")[0]).toHaveTextContent(/refresh\(\)/); + expect( + within(screen.getByTestId("command-history")).getByRole("textbox") + ).toHaveTextContent(/refresh\(\)/); +}); + +test("can see command output", async () => { + const user = userEvent.setup(); + mockAxios.onGet("/load").reply(200, { + html: "
Hello World
", + availableCommands: {}, + }); + + render(); + + expect(await screen.findByTitle("test-page-content")).toHaveAttribute( + "srcDoc", + "
Hello World
" + ); + + mockAxios.onPost("/command", { command: "refresh()" }).reply(200, { + html: "
Goodbye
", + consoleOutputs: [{ message: "Hello World", lineNumber: 0, type: "log" }], + }); + + await user.click( + within(screen.getByTestId("command-input")).getByRole("textbox") + ); + await user.keyboard("refresh()"); + await user.click(screen.getByText("Submit")); + + // expect( + // await within(screen.getByTestId("command-output")).findByRole("textbox") + // ).toHaveTextContent(/Output: Hello World/); }); test("sets available commands", async () => { @@ -74,7 +109,9 @@ test("sets available commands", async () => { "
Hello World
" ); - await user.click(screen.getAllByRole("textbox")[1]); + await user.click( + within(screen.getByTestId("command-input")).getByRole("textbox") + ); await user.keyboard("scr"); await user.click(await screen.findByText("een")); @@ -96,16 +133,21 @@ test("resetting test updates command history", async () => { "
Hello World
" ); - await user.click(screen.getAllByRole("textbox")[1]); + await user.click( + within(screen.getByTestId("command-input")).getByRole("textbox") + ); mockAxios.onPost("/command", { command: "refresh()" }).reply(200, { html: "
Goodbye
", + consoleOutputs: [], }); await user.keyboard("refresh()"); await user.click(screen.getByText("Submit")); await waitFor(() => - expect(screen.getAllByRole("textbox")[0]).toHaveTextContent(/refresh\(\)/) + expect( + within(screen.getByTestId("command-history")).getByRole("textbox") + ).toHaveTextContent(/refresh\(\)/) ); mockAxios.onPost("/reset").reply(200, { @@ -113,17 +155,21 @@ test("resetting test updates command history", async () => { }); await user.click(screen.getByText(/Reset Test/)); - await user.click(screen.getAllByRole("textbox")[1]); + await user.click( + within(screen.getByTestId("command-input")).getByRole("textbox") + ); await user.keyboard("refresh()"); await user.click(screen.getByText("Submit")); - expect(screen.getAllByRole("textbox")[0]).toHaveTextContent( + expect( + within(screen.getByTestId("command-history")).getByRole("textbox") + ).toHaveTextContent( /refresh\(\).*\/\/ <------ Test Reset ------>.*refresh\(\)/ ); expect(window.localStorage.setItem).toHaveBeenLastCalledWith( "TEST_HISTORY", - '[{"content":"refresh()\\n","errors":[],"wasReset":true},{"content":"refresh()\\n","errors":[]}]' + '[{"content":"refresh()\\n","consoleOutputs":[],"wasReset":true},{"content":"refresh()\\n","consoleOutputs":[]}]' ); }); @@ -141,23 +187,32 @@ test("can use arrows for command history", async () => { "
Hello World
" ); - await user.click(screen.getAllByRole("textbox")[1]); + await user.click( + within(screen.getByTestId("command-input")).getByRole("textbox") + ); mockAxios.onPost("/command", { command: "refresh()" }).reply(200, { html: "
Goodbye
", + consoleOutputs: [], }); await user.keyboard("refresh()"); await user.click(screen.getByText("Submit")); await waitFor(() => - expect(screen.getAllByRole("textbox")[0]).toHaveTextContent(/refresh\(\)/) + expect( + within(screen.getByTestId("command-history")).getByRole("textbox") + ).toHaveTextContent(/refresh\(\)/) ); - await user.click(screen.getAllByRole("textbox")[1]); + await user.click( + within(screen.getByTestId("command-input")).getByRole("textbox") + ); await user.keyboard("[ControlLeft>][ArrowUp][/ControlLeft]"); await waitFor(() => - expect(screen.getAllByRole("textbox")[1]).toHaveTextContent(/refresh\(\)/) + expect( + within(screen.getByTestId("command-input")).getByRole("textbox") + ).toHaveTextContent(/refresh\(\)/) ); expect(window.localStorage.setItem).toHaveBeenCalledWith( diff --git a/test-runner/src/CommandInput.js b/test-runner/src/CommandInput.js index e11169e..444131d 100644 --- a/test-runner/src/CommandInput.js +++ b/test-runner/src/CommandInput.js @@ -7,6 +7,16 @@ import ErrorBoundary from "./ErrorBoundary"; const HISTORY_KEY = "HISTORY"; const TEST_HISTORY_KEY = "TEST_HISTORY"; +function combineConsoleOutputs(consoleOutputs) { + return consoleOutputs + .map((output) => + output.type === "error" + ? `Error: ${output.message}` + : `Output: ${output.message}` + ) + .join("\n\n"); +} + function CommandInput({ setInnerHTML, availableCommands }) { const [commandHistory, setCommandHistory] = useState(() => window.localStorage.getItem(HISTORY_KEY) @@ -21,12 +31,13 @@ function CommandInput({ setInnerHTML, availableCommands }) { ...existingHistory, { content: "", - errors: [], + consoleOutputs: [], wasReset: false, }, ]; }); const [editorValue, setEditorValue] = useState(""); + const [output, setOutput] = useState(""); useEffect(() => { window.localStorage.setItem(HISTORY_KEY, JSON.stringify(commandHistory)); @@ -48,17 +59,17 @@ function CommandInput({ setInnerHTML, availableCommands }) { if (index === readOnlyEditor.length - 1) { return { content: editor.content + editorValue + "\n", - errors: response.data.error + consoleOutputs: response.data.consoleOutputs ? [ - ...editor.errors, - { - line: - response.data.error.lineNumber + - editor.content.split("\n").length, - message: response.data.error.message, - }, + ...editor.consoleOutputs, + ...response.data.consoleOutputs.map((output) => ({ + message: output.message, + type: output.type, + lineNumber: + output.lineNumber + editor.content.split("\n").length, + })), ] - : [...editor.errors], + : [...editor.consoleOutputs], }; } else { return { ...editor }; @@ -66,6 +77,8 @@ function CommandInput({ setInnerHTML, availableCommands }) { }) ); setEditorValue(""); + + setOutput(combineConsoleOutputs(response.data.consoleOutputs)); }); }, [ commandHistory, @@ -83,7 +96,7 @@ function CommandInput({ setInnerHTML, availableCommands }) { { ...readOnlyEditor[readOnlyEditor.length - 1], wasReset: true }, { content: "", - errors: [], + consoleOutputs: [], wasReset: false, }, ]); @@ -104,34 +117,42 @@ function CommandInput({ setInnerHTML, availableCommands }) { } else { acc.content += editor.content; } - acc.errors = [ - ...acc.errors, - ...editor.errors.map((error) => ({ - ...error, - line: error.line + currentLine, + acc.consoleOutputs = [ + ...acc.consoleOutputs, + ...editor.consoleOutputs.map((output) => ({ + ...output, + lineNumber: output.lineNumber + currentLine, })), ]; return acc; }, - { errors: [], content: "" } + { content: "", consoleOutputs: [] } ); }, [readOnlyEditor]); return ( <> -
-

Code History

+

Code History

+
+
+

Output

+
+
-
-

Code Input

+

Code Input

+
- error.is(errorEffect) + const consoleEffects = transaction.effects.filter((error) => + error.is(consoleEffect) ); - value = value.update({ - add: errorEffects.map((error) => { - return new ErrorGutterMarker(error.value).range( - error.value.from, - error.value.to - ); - }), - sort: true, - }); + if (consoleEffects.length > 0) { + value = value.update({ + filter: () => false, + add: consoleEffects.map((consoleEff) => { + if (consoleEff.value.type === "error") { + return new ErrorGutterMarker(consoleEff.value).range( + consoleEff.value.from, + consoleEff.value.to + ); + } else { + return new WarnGutterMarker(consoleEff.value).range( + consoleEff.value.from, + consoleEff.value.to + ); + } + }), + sort: true, + }); + } return value; }, @@ -148,9 +169,17 @@ const errorState = StateField.define({ return EditorView.decorations.from(field, (value) => { let marks = Decoration.none; for (let iter = value.iter(); iter.value !== null; iter.next()) { - marks = marks.update({ - add: [underlineMark.range(iter.from, iter.to)], - }); + if (iter.to > iter.from) { + if (iter.value.data.type === "error") { + marks = marks.update({ + add: [underlineErrorMark.range(iter.from, iter.to)], + }); + } else { + marks = marks.update({ + add: [underlineWarningMark.range(iter.from, iter.to)], + }); + } + } } return marks; @@ -167,13 +196,11 @@ const cursorTooltipBaseTheme = EditorView.baseTheme({ }, }); -const errorHover = hoverTooltip((view, pos, side) => { - const state = view.state.field(errorState); +const consoleHover = hoverTooltip((view, pos, side) => { + const state = view.state.field(consoleState); let { from, to } = view.state.doc.lineAt(pos); for (let iter = state.iter(); iter.value !== null; iter.next()) { if (from === iter.from) { - console.log(iter.value.errorData); - return { pos: from, end: to, @@ -181,7 +208,9 @@ const errorHover = hoverTooltip((view, pos, side) => { create(view) { let dom = document.createElement("div"); dom.className = "cm-tooltip-error"; - dom.innerHTML = `

${iter.value.errorData.error}

`; + dom.innerHTML = iter.value.data.value + .map((message) => `

${message}

`) + .join(""); return { dom }; }, }; @@ -192,10 +221,10 @@ const errorHover = hoverTooltip((view, pos, side) => { }); const errorGutter = [ - errorState, + consoleState, gutter({ class: "cm-breakpoint-gutter", - markers: (v) => v.state.field(errorState), + markers: (v) => v.state.field(consoleState), initialSpacer: () => new ErrorGutterMarker(), }), EditorView.baseTheme({ @@ -207,13 +236,17 @@ const errorGutter = [ }), ]; -const underlineTheme = EditorView.baseTheme({ - ".cm-underline": { textDecoration: "underline 1px red wavy" }, +const errorUnderlineTheme = EditorView.baseTheme({ + ".cm-underline-error": { textDecoration: "underline 1px red wavy" }, +}); + +const warningUnderlineTheme = EditorView.baseTheme({ + ".cm-underline-warning": { textDecoration: "underline 1px #ccac01 wavy" }, }); const setCodeHistory = (codeMirrorRef, commandHistory) => { if (!codeMirrorRef.current) return; - console.log("settingCodeHistory"); + codeMirrorRef.current.dispatch({ effects: [ updateCommandHistoryEffect.of({ commandHistory }), @@ -242,31 +275,35 @@ const setText = (codeMirrorRef, content) => { codeMirrorRef.current.dispatch({ effects: scrollEffect }); }; -const setErrors = (codeMirrorRef, errors) => { - if (!codeMirrorRef.current || !errors) return; - console.log(errors); +function combineOutputsType(consoleOutputs) { + return consoleOutputs.some((output) => output.type === "error") + ? "error" + : "log"; +} - const existingErrorState = codeMirrorRef.current.state.field(errorState); +function groupByLineNumber(consoleOutputs) { + return consoleOutputs.reduce((acc, output) => { + acc[output.lineNumber] = [...(acc[output.lineNumber] || []), output]; + return acc; + }, {}); +} - const existingErrors = []; - for (let iter = existingErrorState.iter(); iter.value !== null; iter.next()) { - existingErrors.push(iter.value); - } +const setConsoleOutputs = (codeMirrorRef, consoleOutputs) => { + if (!codeMirrorRef.current || !consoleOutputs) return; + + const outputsByLine = groupByLineNumber(consoleOutputs); + + const effects = Object.entries(outputsByLine).map(([lineNumber, outputs]) => { + return consoleEffect.of({ + from: codeMirrorRef.current.state.doc.line(lineNumber).from, + to: codeMirrorRef.current.state.doc.line(lineNumber).to, + value: outputs.map((output) => output.message), + type: combineOutputsType(outputs), + }); + }); codeMirrorRef.current.dispatch({ - effects: errors - .map((error) => { - return errorEffect.of({ - from: codeMirrorRef.current.state.doc.line(error.line).from, - to: codeMirrorRef.current.state.doc.line(error.line).to, - error: error.message, - }); - }) - .filter((error) => { - return !existingErrors.find( - (existingError) => existingError.errorData.from === error.value.from - ); - }), + effects, }); }; @@ -276,7 +313,7 @@ export default function Editor({ submit, content = "", commandHistory, - errors = [], + consoleOutputs = [], readonly = false, }) { const codeEditorRef = useRef(); @@ -298,8 +335,8 @@ export default function Editor({ }, [content, codeMirrorRef]); useEffect(() => { - setErrors(codeMirrorRef, errors); - }, [errors, codeMirrorRef]); + setConsoleOutputs(codeMirrorRef, consoleOutputs); + }, [consoleOutputs, codeMirrorRef]); const myCompletions = useCallback( (context) => { @@ -437,6 +474,7 @@ export default function Editor({ rectangularSelection(), highlightActiveLine(), highlightSelectionMatches(), + EditorView.lineWrapping, keymap.of([ ...sendCommandKeyMap, ...closeBracketsKeymap, @@ -457,9 +495,10 @@ export default function Editor({ updateListener, submitFunctionState, commandHistoryState, - errorHover, + consoleHover, cursorTooltipBaseTheme, - underlineTheme, + errorUnderlineTheme, + warningUnderlineTheme, EditorView.editable.of(!readonly), ], }); @@ -476,7 +515,7 @@ export default function Editor({ codeMirrorRef.current.focus(); setCodeHistory(codeMirrorRef, commandHistory); setText(codeMirrorRef, content); - setErrors(codeMirrorRef, errors); + setConsoleOutputs(codeMirrorRef, consoleOutputs); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [ @@ -504,7 +543,11 @@ export default function Editor({ ); return ( <> -
+
); } diff --git a/test-runner/src/Editor.test.js b/test-runner/src/Editor.test.js index e4d051e..0d796df 100644 --- a/test-runner/src/Editor.test.js +++ b/test-runner/src/Editor.test.js @@ -13,11 +13,13 @@ test("can render errors in editor", async () => { render( ); - const errorSymbols = screen.queryAllByText("🛑"); + const errorSymbols = screen.queryAllByText("🔴"); expect(errorSymbols[0]).toHaveStyle({ visibility: "hidden" }); expect(errorSymbols[1]).toBeInTheDocument(); }); @@ -28,7 +30,9 @@ test("can render error tooltip in editor", async () => { render( ); await user.hover(await screen.findByText(/hello/)); @@ -47,7 +51,9 @@ test("command history is reset when changed", async () => { rerender( ); @@ -57,3 +63,20 @@ test("command history is reset when changed", async () => { await expect(await screen.findByText(/new/)).toBeInTheDocument(); }); + +test("can render console logs in editor", async () => { + render( + + ); + + expect(screen.getByText("⚠️")).toBeInTheDocument(); + // eslint-disable-next-line testing-library/no-node-access + expect(screen.getByText("hello").parentNode).toHaveClass( + "cm-underline-warning" + ); +}); diff --git a/test-runner/src/setupTests.js b/test-runner/src/setupTests.js index 10620cc..4fc49ad 100644 --- a/test-runner/src/setupTests.js +++ b/test-runner/src/setupTests.js @@ -3,7 +3,7 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import "@testing-library/jest-dom"; -import { setup, registerCommands } from "react-testing-visualizer"; +import { setup, registerCommands } from "testing-library-visualizer"; import path from "path"; import { expect } from "@jest/globals"; import { screen, within, fireEvent } from "@testing-library/react";