diff --git a/.eslintrc b/.eslintrc index 97e17131..c1cd6772 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,7 +6,8 @@ "rules": { "arrow-body-style": "off", "no-plusplus": "off", - "no-param-reassign": "off" + "no-param-reassign": "off", + "linebreak-style": "off" }, "env": { "jest": true, diff --git a/.gitignore b/.gitignore index 1dda36d4..754a1f13 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # production /build +/dist # misc .DS_Store diff --git a/README.md b/README.md index 40c5f1c5..6c5bee3b 100644 --- a/README.md +++ b/README.md @@ -2,80 +2,67 @@ Spearmint helps developers easily create functional React/Redux/Endpoint/Paint Timing tests without writing any code. It dynamically converts user inputs into executable Jest test code by using DOM query selectors provided by @testing-library. -# How to use +# Installation +Please download spearmint from our [website](https://www.spearmintjs.com/). Available for Mac OS, Windows and Linux. -Download spearmint @ spearmintjs.com. Available for Mac OS and Windows. -
+# How to use in development mode -### React Testing -
-To run React tests generated by spearmint, install the following in your dev dependencies. +### For Mac developers - npm i -D jest @testing-library/jest-dom @testing-library/react test-data-bot +**Prerequisite**: Install Xcode -To run Redux tests generated by spearmint, install the following in your dev dependencies, in addition to your React test installations above. +First install +1. Fork and clone this repository. +2. ```npm run install-once``` +3. ```npm run start``` - npm i -D redux-mock-store redux-thunk fetch-mock +*** -To run Hooks / Context tests generated by spearmint, install the following in your dev dependencies, in addition to your React test installations above. +### For Windows developers - npm i -D @testing-library/react-hooks +**Prerequisite**: Install Python and Microsoft C++ Build Tools. -
- -### Endpoint Testing -
-To run Endpoint tests generated by spearmint, install the following in your dev dependencies. +In an Administrator PowerShell run the following commands:
+```choco install python visualcpp-build-tools -y```
+```npm config set msvs_version 2017``` + +1. Fork and clone this repository. +2. ```npm run install-once``` +3. ```npm run start-windows``` - npm i -D jest supertest
-### Puppeteer Testing -
-To run Puppeteer tests generated by spearmint, install the following in your dev dependencies. - npm i -D jest puppeteer +# How it works -
- -### Accessiblity Testing -
+1. On the initial screen, load your application to start creating tests. -To run Accessibility tests generated by spearmint on HTML, install the following in your dev dependencies. +![](/public/mainPage.png) - npm i -D axe-core regenerator-runtime jest +2. Utilize our auto-complete, drop-down options, and tooltips features to easily create arrangement, action, and assertion test statements for React; reducer, action creator, asynchronous action creator, and middleware test statements for Redux; and hooks, context, and endpoint test statements. -To run Accessibility tests generated by spearmint on React Components, install the following in your dev dependencies. +![](/public/generateTest.png) - npm i -D axe-core regenerator-runtime jest enzyme enzyme-adapter-react-16 +3. Spearmint will then convert user input to dynamically generate a test file, which you can click export icon on the left nav bar to automatically save the test file in the **\_\_tests\_\_** folder. -To run Accessibility tests generated by spearmint on URL's with Puppeteer, install the following in your dev dependencies. - npm i -D axe-core puppeteer +4. Lastly click **Run Test** button and follow the guide and click what type of test you would like to perform. +![](/public/runTest.png) +5. Accessibility lens have been added in the app to give developers with different mismatches various options to interact with the app. +![](/public/AccLens_Demo.gif)
-# How it works - -1. On the initial screen, enter the URL of your project and load your application to start creating tests. - -![](https://lh4.googleusercontent.com/CAFpoefRUUxgNosudQuc7gabSReFiI_puZ_WTjrzUSzB6pgOUdQ1babF2mxJql2lC8TQ-jjVLOgG5Qka8SUfF2fi-u2H9xSP7rZ_0Udpj-ISFPAY028UYKIUZcgOApnipVZwE7xh) - -2. Utilize our auto-complete, drop-down options, and tooltips features to easily create arrangement, action, and assertion test statements for React; reducer, action creator, asynchronous action creator, and middleware test statements for Redux; and hooks, context, and endpoint test statements. - -![](/public/newReact.png?raw=true) -3. Refer to the browser view of your app that is displayed on the right to quickly identify values for your selectors and use the file directory to open up a code editor view to easily refer to your codebase for props information. +# Demo -4. Spearmint will then convert user input to dynamically generate a test file that will be saved inside a **tests** folder, which you can use to run ‘npm test’ on. +![](/public/inapp-test-demo-run-test.gif) -![](/public/testfile.png?raw=true) - -
+*** ### The Spearmint Team
@@ -84,24 +71,28 @@ To run Accessibility tests generated by spearmint on URL's with Puppeteer, insta > Alfred [@astaiglesia](https://github.com/astaiglesia)  ·  > Annie [@annieshinn](https://github.com/annieshinn)  ·  > Ben [@bkwak](https://github.com/bkwak)  ·  -> Charlie [@charlie-maloney](https://github.com/charlie-maloney)
+> Charlie [@charlie-maloney](https://github.com/charlie-maloney)  · 
> Chloe [@HeyItsChloe](https://github.com/HeyItsChloe)  ·  > Cornelius [@corneeltron](https://github.com/corneeltron)  ·  > Dave [@davefranz](https://github.com/davefranz)  ·  -> Evan [@Berghoer](https://github.com/Berghoer)  ·  -> Gabriel [@bielchristo](https://github.com/bielchristo)
+> Dieu [@dieunity](https://github.com/dieunity)  ·  +> Evan [@Berghoer](https://github.com/Berghoer)  · 
+> Gabriel [@bielchristo](https://github.com/bielchristo) > Johnny [@johnny-lim](https://github.com/johnny-lim)  ·  > Julie [@julicious100](https://github.com/julicious100)  ·  -> Karen [@karenpinilla](https://github.com/karenpinilla)  ·  +> Justin [@JIB3377](https://github.com/JIB3377)  ·  +> Karen [@karenpinilla](https://github.com/karenpinilla)  · 
> Linda [@lcwish](https://github.com/lcwish)  ·  -> Luis [@Luis-KM-Lo](https://github.com/Luis-KM-Lo)
+> Luis [@Luis-KM-Lo](https://github.com/Luis-KM-Lo)  ·  +> Max [@MaxWeisen](https://github.com/MaxWeisen)  ·  > Mike [@mbcoker](https://github.com/mbcoker)  ·  -> Natlyn [@natlynp](https://github.com/natlynp)  ·  +> Mo [@mhmaidi789](https://github.com/mhmaidi789)  · 
+> Natlyn [@natlynp](https://github.com/natlynp)  ·  > Nick [@nicolaspita](https://github.com/nicolaspita)  ·  > Rachel [@rachethecreator](https://github.com/rachethecreator)  ·  -> Sean [@sean-haverstock](https://github.com/Sean-Haverstock)
-> Sharon [@sharon-zhu](https://github.com/sharon-zhu)  ·  +> Sean [@sean-haverstock](https://github.com/Sean-Haverstock)  · 
+> Sharon [@sharon-zhu](https://github.com/sharon-zhu)  ·  > Sieun [@sieunjang](https://github.com/sieunjang)  ·  > Tolan [@taoantaoan](https://github.com/taoantaoan)  ·  -> Tristen [@twastell](https://github.com/twastell)
-
\ No newline at end of file +> Tristen [@twastell](https://github.com/twastell) +
diff --git a/config-overrides.js b/config-overrides.js new file mode 100644 index 00000000..928a4aee --- /dev/null +++ b/config-overrides.js @@ -0,0 +1,7 @@ +/* config-overrides.js */ + +module.exports = function override(config, env) { + //do stuff with the webpack config... + config.target = 'electron-renderer'; + return config; +} \ No newline at end of file diff --git a/package.json b/package.json index 82b4b970..1c365a9b 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "spearmint", - "version": "0.5.0", + "version": "0.6.0", "description": "An open-source developer tool that simplifies testing and hopes to help increase awareness about web accessibility.", - "author": "spearmintjs", + "author": "team spearmint", "build": { "appId": "com.spearmint", "files": [ @@ -12,12 +12,24 @@ ], "directories": { "buildResources": "build" + }, + "linux": { + "target": [ + "deb" + ], + "icon": "build/icon.*", + "maintainer": "Team Spearmint" } }, "main": "public/electron.js", + "bugs": { + "url": "https://github.com/oslabs-beta/spearmint_LA42/issues" + }, "homepage": "./", "private": true, "dependencies": { + "@material-ui/core": "^4.11.3", + "@material-ui/icons": "^4.11.2", "@types/jest": "^25.2.3", "@types/react-beautiful-dnd": "^12.1.2", "@types/react-dom": "^16.9.8", @@ -28,9 +40,10 @@ "dotenv": "^8.2.0", "electron-is-dev": "^1.1.0", "fibers": "^5.0.0", + "fix-path": "^3.0.0", "js-beautify": "^1.10.0", "monaco-editor": "^0.17.0", - "node-sass": "^4.12.0", + "node-pty": "^0.10.0", "react": "^16.8.6", "react-autosuggest": "^9.4.3", "react-beautiful-dnd": "^11.0.3", @@ -38,23 +51,29 @@ "react-modal": "^3.8.1", "react-monaco-editor": "^0.25.1", "react-scripts": "^3.4.1", - "sass": "^1.26.5", + "sass": "^1.32.11", "typescript": "^3.9.2", - "wait-on": "^3.3.0" + "wait-on": "^3.3.0", + "xterm": "^4.11.0", + "xterm-addon-fit": "^0.5.0", + "xterm-for-react": "^1.0.4" }, "scripts": { - "test": "react-scripts test --env=jsdom", + "install-once": "npm i && npm run electron-rebuild", + "test": "react-app-rewired test --env=jsdom", "test:e2e": "./node_modules/mocha/bin/mocha src/__tests__/spec.e2e.js", "test:integra": "mocha src/__tests__/spec.integra.js", "test:watch": "jest --watch", "react-start": "react-scripts start", "react-eject": "react-scripts eject", - "build": "NODE_ENV=production npm run react-build && npm run electron-build", + "build": "NODE_ENV=production react-app-rewired build && npm run electron-build", "react-build": "NODE_ENV=production react-scripts build", "electron-build": "NODE_ENV=production electron-builder -mwl", + "electron-rebuild": "electron-rebuild -f -w node-pty", + "postinstall": "electron-builder install-app-deps", "release": "npm run react-build && electron-builder --publish=always", - "start-windows": "SET NODE_ENV=development concurrently \"cross-env BROWSER=none npm run react-start\" \"wait-on http://localhost:3000 && electron .\"", - "start": "NODE_ENV=development concurrently \"cross-env BROWSER=none npm run react-start\" \"wait-on http://localhost:3000 && electron .\"" + "start-windows": "cross-env NODE_ENV=development concurrently \"cross-env BROWSER=none react-app-rewired start\" \"wait-on http://localhost:3000 && electron .\"", + "start": "NODE_ENV=development concurrently \"cross-env BROWSER=none react-app-rewired start\" \"wait-on http://localhost:3000 && electron .\"" }, "browserslist": { "production": [ @@ -78,9 +97,10 @@ "@typescript-eslint/parser": "^2.33.0", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", - "electron": "^5.0.3", + "electron": "^12.0.5", "electron-builder": "^22.6.1", "electron-devtools-installer": "^3.0.0", + "electron-rebuild": "^2.3.5", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.14.0", "eslint-config-airbnb-typescript": "^7.2.1", @@ -89,8 +109,9 @@ "eslint-plugin-react": "^7.20.0", "eslint-plugin-react-hooks": "^2.5.1", "mocha": "^8.3.2", + "react-app-rewired": "^2.1.8", "react-test-renderer": "^16.12.0", "spectron": "^5.0.0", "test-data-bot": "^0.8.0" } -} +} \ No newline at end of file diff --git a/public/AccLens_Demo.gif b/public/AccLens_Demo.gif new file mode 100644 index 00000000..ae8eb153 Binary files /dev/null and b/public/AccLens_Demo.gif differ diff --git a/public/appDemo - Copy.gif b/public/appDemo - Copy.gif deleted file mode 100644 index a123126c..00000000 Binary files a/public/appDemo - Copy.gif and /dev/null differ diff --git a/public/electron.js b/public/electron.js index e7accdd7..2e8f0fa4 100644 --- a/public/electron.js +++ b/public/electron.js @@ -1,52 +1,173 @@ -const { app, BrowserWindow } = require('electron'); -const path = require('path'); -const isDev = require('electron-is-dev'); -let mainWindow; - -if (isDev) console.log('electron version', process.versions.electron); - -if (isDev) { - const { - default: installExtension, - REACT_DEVELOPER_TOOLS, - } = require('electron-devtools-installer'); - function addDevTools() { - app.whenReady().then(() => { - installExtension(REACT_DEVELOPER_TOOLS) - .then((name) => console.log(`Added Extension: ${name}`)) - .catch((err) => console.log('An error occurred: ', err)); - }); - } -} -function createWindow() { - mainWindow = new BrowserWindow({ - width: 1550, - height: 750, - minHeight: 750, - icon: path.join(__dirname, 'public/icon.png'), - webPreferences: { - nodeIntegration: true, - webviewTag: true, - }, - }); - mainWindow.loadURL( - isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}` - ); - mainWindow.on('closed', () => (mainWindow = null)); -} - -if (isDev) { - app.on('ready', addDevTools); -} - -app.on('ready', createWindow); -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit(); - } -}); -app.on('activate', () => { - if (mainWindow === null) { - createWindow(); - } -}); +const { + app, BrowserWindow, ipcMain, dialog, +} = require('electron'); +const path = require('path'); +const isDev = require('electron-is-dev'); +const fs = require('fs'); +const os = require('os'); +const pty = require('node-pty'); +const fixPath = require('fix-path'); + +//Dynamic variable to change terminal type based on os +const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash'; +console.log("process.env.Path1: ", process.env.PATH); +//=> '/usr/bin' + +fixPath(); + +console.log("process.env.Path2: ", process.env.PATH); +//=> '/usr/local/bin:/usr/bin' +let mainWindow; + +if (isDev) console.log('electron version', process.versions.electron); + +if (isDev) { + const { + default: installExtension, + REACT_DEVELOPER_TOOLS, + } = require('electron-devtools-installer'); + function addDevTools() { + app.whenReady().then(() => { + installExtension(REACT_DEVELOPER_TOOLS) + .then((name) => console.log(`Added Extension: ${name}`)) + .catch((err) => console.log('An error occurred: ', err)); + }); + } +}; + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1550, + height: 750, + minHeight: 750, + icon: path.join(__dirname, 'icon.png'), + webPreferences: { + nodeIntegration: true, + webviewTag: true, + contextIsolation: false, + }, + }); + mainWindow.loadURL( + isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`, + ); + mainWindow.on('closed', () => (mainWindow = null)); + + // PTY PROCESS FOR IN APP TERMINAL + const ptyArgs = { + name: 'xterm-color', + cols: 80, + rows: 80, + cwd: process.env.HOME, + env: process.env, + }; + console.log("process.env.HOME: ", process.env.HOME); + + const ptyProcess = pty.spawn(shell, [], ptyArgs); + // with ptyProcess, we want to send incoming data to the channel terminal.incData + ptyProcess.on('data', (data) => { + mainWindow.webContents.send('terminal.incData', data); + }); + // in the main process, at terminal.toTerm channel, when data is received, + // main process will write to ptyProcess + ipcMain.on('terminal.toTerm', (event, data) => { + ptyProcess.write(data); + }); +} + +// EDITORVIEW.JSX SAVE FILE FUNCTIONALITY +ipcMain.on('EditorView.saveFile', (e, filePath, editedText) => { + fs.writeFile(filePath, editedText, (err) => { + if (err) throw err; + }); + // Return a success message upon save + e.returnValue = 'Changes Saved'; +}); + +/* + EXPORTFILEMODAL.JSX FILE FUNCTIONALITY + (check existence and create folder) +*/ +ipcMain.on('ExportFileModal.exists', (e, fileOrFolderPath) => { + e.returnValue = fs.existsSync(fileOrFolderPath, (err) => { + if (err) throw err; + }); +}); + +ipcMain.on('ExportFileModal.mkdir', (e, folderPath) => { + e.returnValue = fs.mkdirSync(folderPath, (err) => { + if (err) throw err; + }); +}); + +ipcMain.on('ExportFileModal.fileCreate', (e, filePath, file) => { + e.returnValue = fs.writeFile(filePath, file, (err) => { + if (err) throw err; + }); +}); + +ipcMain.on('ExportFileModal.readFile', (e, filePath) => { + e.returnValue = fs.readFileSync(filePath, 'utf8', (err) => { + if (err) throw err; + }); +}); + +// OPENFOLDERBUTTON.JSX FILE FUNCTIONALITY +ipcMain.on('OpenFolderButton.isDirectory', (e, filePath) => { + e.returnValue = fs.statSync(filePath).isDirectory(); +}); + +ipcMain.on('OpenFolderButton.dialog', (e) => { + const dialogOptions = { + properties: ['openDirectory', 'createDirectory'], + filters: [ + { name: 'Javascript Files', extensions: ['js', 'jsx'] }, + { name: 'Style', extensions: ['css'] }, + { name: 'Html', extensions: ['html'] }, + ], + message: 'Please select your project folder', + }; + e.returnValue = dialog.showOpenDialogSync(dialogOptions); +}); + +/* +UNIVERSAL IPC CALLS +(The following IPC calls are made from various components in the codebase) +*/ +ipcMain.on('Universal.stat', (e, filePath) => { + e.returnValue = fs.statSync(filePath).isDirectory(); +}); + +ipcMain.on('Universal.readDir', (e, projectFilePath) => { + e.returnValue = fs.readdirSync(projectFilePath, (err) => { + if (err) throw err; + }); +}); + +ipcMain.on('Universal.readFile', (e, filePath) => { + e.returnValue = fs.readFileSync(filePath, 'utf8', (err) => { + if (err) throw err; + }); +}); + +ipcMain.on('Universal.path', (e, folderPath, filePath) => { + e.returnValue = path.relative(folderPath, filePath, (err) => { + if (err) throw err; + }); +}) + +// ELECTRON BOILERPLATE FOR DEVTOOLS AND WINDOW CREATION +if (isDev) { + app.on('ready', addDevTools); +} + +app.on('ready', createWindow); +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); +app.on('activate', () => { + if (mainWindow === null) { + createWindow(); + } +}); diff --git a/public/generateTest.png b/public/generateTest.png new file mode 100644 index 00000000..5a6f60d7 Binary files /dev/null and b/public/generateTest.png differ diff --git a/public/inapp-test-demo-run-test.gif b/public/inapp-test-demo-run-test.gif new file mode 100644 index 00000000..93696d39 Binary files /dev/null and b/public/inapp-test-demo-run-test.gif differ diff --git a/public/mainPage.png b/public/mainPage.png new file mode 100644 index 00000000..6c27724a Binary files /dev/null and b/public/mainPage.png differ diff --git a/public/newReact.png b/public/newReact.png deleted file mode 100644 index 114cb4ca..00000000 Binary files a/public/newReact.png and /dev/null differ diff --git a/public/runTest.png b/public/runTest.png new file mode 100644 index 00000000..328b77b5 Binary files /dev/null and b/public/runTest.png differ diff --git a/public/testfile.png b/public/testfile.png deleted file mode 100644 index 32e7655d..00000000 Binary files a/public/testfile.png and /dev/null differ diff --git a/src/App.jsx b/src/App.jsx index 3705bd11..94ff5124 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,7 +3,7 @@ import styles from './App.module.scss'; import { GlobalContext, globalState, globalReducer } from './context/reducers/globalReducer'; import ProjectLoader from './pages/ProjectLoader/ProjectLoader'; import NavBar from './components/NavBar/NavBar'; -import LeftPanel from './pages//LeftPanel/LeftPanel'; +import LeftPanel from './pages/LeftPanel/LeftPanel'; import RightPanel from './pages/RightPanel/RightPanel'; import About from './pages/About/About'; @@ -19,9 +19,9 @@ const App = () => { ); - } else { - return ( - /** + } + return ( + /** * Wrap the components that we want to share the unique states with. * You can only provide one value to a Provider. * - In order to avoid creating separate Contexts, wrap multiples in an array (ex: testCase and dispatchToTestCase). @@ -33,36 +33,36 @@ const App = () => { * * We access the value that we gave to the Provider through useContext */ -
- - {global.isProjectLoaded === 'about' ? ( - <> - - {' '} - - ) : ( - <> - - - - )} - {global.isRightPanelOpen ? : ''} - -
- ); - } + > + + {global.isProjectLoaded === 'about' ? ( + <> + + + {' '} + + ) : ( + <> + + + + )} + {global.isRightPanelOpen ? : ''} + + + ); }; export default App; diff --git a/src/assets/stylesheets/colors.scss b/src/assets/stylesheets/colors.scss index 7ffe1fdf..5819abbc 100644 --- a/src/assets/stylesheets/colors.scss +++ b/src/assets/stylesheets/colors.scss @@ -1,5 +1,6 @@ $mint: #038181; $mint2: #02c3c33f; +$mint3: #0fa9a95c; $dark-gray: #808080; $light-gray: #d5d5d5; $light-gray2: #f6f8f9; diff --git a/src/components/BrowserView/BrowserView.jsx b/src/components/BrowserView/BrowserView.jsx index df5db622..a6d2eae2 100644 --- a/src/components/BrowserView/BrowserView.jsx +++ b/src/components/BrowserView/BrowserView.jsx @@ -1,20 +1,40 @@ -import React, { useContext } from 'react'; +import React, { useContext, useState } from 'react'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Checkbox from '@material-ui/core/Checkbox'; +import { makeStyles } from '@material-ui/core/styles'; import styles from './BrowserView.module.scss'; + import { GlobalContext } from '../../context/reducers/globalReducer'; import { setProjectUrl } from '../../context/actions/globalActions'; const BrowserView = () => { const [{ url }, dispatchToGlobal] = useContext(GlobalContext); + // Track checked button state + + const [checkedBoxes, setCheckBox] = useState({ + checkedMouse: false, + muted: false, + checkedGrayscale: false, + checkedContrast: false, + }); + + // Mute/Unmute webview + const muteAudio = (muted) => { + const webview = document.querySelector('webview'); + // console.log(webview); + webview.setAudioMuted(muted); + }; + // helper function to add the https or http const addHttps = (url) => { if (url.indexOf('http://') === 0 || url.indexOf('https://') === 0) { return url; } if (url.startsWith('localhost')) { - url = 'http://' + url; + url = `http://${url}`; return url; } - url = 'https://' + url; + url = `https://${url}`; return url; }; @@ -26,16 +46,140 @@ const BrowserView = () => { } }; + // Updates checkboxes states + const handleChangeCheckBox = (e) => { + // console.log(e.target.name); + switch (e.target.name) { + case 'checkedMouse': + setCheckBox({ ...checkedBoxes, checkedMouse: !checkedBoxes.checkedMouse }); + break; + case 'muted': + // MintyBois note: Invoke muteAudio in callback within setCheckBox. + setCheckBox((state) => { + const updatedState = { + ...state, + muted: !state.muted, + }; + muteAudio(updatedState.muted); + return updatedState; + }); + break; + + // checkedGrayscale state does not impact app usability + case 'checkedGrayscale': + setCheckBox({ + ...checkedBoxes, + checkedGrayscale: !checkedBoxes.checkedGrayscale, + }); + break; + + // Updates contrast + case 'checkedContrast': + setCheckBox({ + ...checkedBoxes, + checkedContrast: !checkedBoxes.checkedContrast, + }); + break; + + default: + break; + } + }; + + // const useStyles = makeStyles(() => ({ + // FormControlLabel: { + // // fontSize doesn't work, but color does work... + // fontSize: '1px', + // color: 'red', + // // , + // }, + // })); + + // const classes = useStyles(); + return ( - <> +
+
+
+ Accessibility Lens +
+ {/* trying to put some sort of flex style or centered style here to center the 3 check boxes...but no avail */} +
+ + )} + label="Disable Mouse Clicks" + /> + + )} + label="Grayscale" + /> + + )} + label="Low Contrast" + /> + + )} + label="Mute" + /> +
+
+ {/* Search bar */} - - + +
); }; diff --git a/src/components/BrowserView/BrowserView.module.scss b/src/components/BrowserView/BrowserView.module.scss index e6107421..4e8dd66c 100644 --- a/src/components/BrowserView/BrowserView.module.scss +++ b/src/components/BrowserView/BrowserView.module.scss @@ -2,7 +2,9 @@ @import '../../assets/stylesheets/fonts.scss'; #browserView { - height: 100vh; + display: flex; + justify-content: center; + height: 97vh; width: auto; } @@ -17,13 +19,52 @@ text-indent: 10px; } -#address { - width: 89%; - height: 28px; - border: none; - border: 1px solid $light-gray; - border-radius: 8px; - padding-left: 10px; +#browswerComponentTopLevelDiv{ + display: flex; + flex: 1; + flex-direction: column; +} + +#accessLensContainer{ + // background-color: $mint2; + display: flex; + flex-direction: column; + height: 80px; + min-width: 100px; + // border: $mint; + // border-width: .1em; + // border-radius: 5px; + // border-style: solid; + overflow-x: scroll; + overflow-y: hidden; + white-space: nowrap; + justify-content: center; +} + +#accessLensCheckBoxes{ + display: flex; + justify-content: center; + align-items: center; + background-color: $mint2; + border: $mint2; + border-width: .1em; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + border-style: solid; +} + +#accessLensLabel { + display: flex; + justify-content: center; + background-color: $mint; + color: white; + font-size: 18px; + font-family: $oxygen; + height: 25px; + border: $mint; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + border-style: solid; } // button { @@ -40,3 +81,20 @@ // background-color: $mint; // color: white; // } + +// #formControlLabel{ +// display: flex; +// background-color: $mint2; +// color: "white"; +// size: "20px"; +// margin: "10px"; +// } + +// #address { +// width: 89%; +// height: 28px; +// border: none; +// border: 1px solid $light-gray; +// border-radius: 8px; +// padding-left: 10px; +// } \ No newline at end of file diff --git a/src/components/EditorView/EditorView.jsx b/src/components/EditorView/EditorView.jsx index e1959dbd..1f1d0bdf 100644 --- a/src/components/EditorView/EditorView.jsx +++ b/src/components/EditorView/EditorView.jsx @@ -5,8 +5,8 @@ import { GlobalContext } from '../../context/reducers/globalReducer'; import { updateFile } from '../../context/actions/globalActions'; import styles from './EditorView.module.scss'; -const remote = window.require('electron').remote; -const fs = remote.require('fs'); +const { ipcRenderer } = require('electron'); + const Editor = () => { const [{ file, filePath }, dispatchToGlobal] = useContext(GlobalContext); @@ -21,11 +21,14 @@ const Editor = () => { colorDecorators: true, wrappingIndent: 'indent', automaticLayout: true, + codeLens: true, + // Added specific fontfamily and fontsize to address Windows curson misalignment issue + fontFamily: 'courier new', + fontSize: 12, }; const editorDidMount = () => { editor.setTheme('light-dark'); - // editor.focus(); }; const updatafile = (newValue, e) => { @@ -38,14 +41,14 @@ const Editor = () => { if (!filePath.length) setWasSaved('Preview Saved, be sure to export file'); } else setWasSaved('No Changes to Save'); if (filePath.length && editedText.length) { - setWasSaved('Changes Saved'); - await fs.writeFile(filePath, editedText, (err) => { - if (err) throw err; - }); + // Send main process the filePath and editedText in obj to save + const reply = ipcRenderer.sendSync('EditorView.saveFile', filePath, editedText); + // Upon reply from main process, update wasSaved state + setWasSaved(reply); } }; - let fileType = filePath.split('.')[1]; + const fileType = filePath.split('.')[1]; const extensionChecker = { png: 1, jpg: 1, @@ -54,15 +57,11 @@ const Editor = () => { return (
- - {wasSaved}
setWasSaved('')}> { onChange={updatafile} />
+
+ + {wasSaved} +
); }; diff --git a/src/components/EditorView/EditorView.module.scss b/src/components/EditorView/EditorView.module.scss index b30ac3c9..b0c196e8 100644 --- a/src/components/EditorView/EditorView.module.scss +++ b/src/components/EditorView/EditorView.module.scss @@ -13,7 +13,6 @@ border-radius: 5px; border: 0.5px $light-gray solid; position: relative; - top: -18px; } #save:hover { @@ -27,6 +26,5 @@ 'Apple Color Emoji', 'Segoe UI Emoji'; line-height: 1.5; position: relative; - top: -18px; color: rgb(90, 90, 90); } diff --git a/src/components/FileDirectory/FileDirectory.jsx b/src/components/FileDirectory/FileDirectory.jsx index 55a26e1b..2014808d 100644 --- a/src/components/FileDirectory/FileDirectory.jsx +++ b/src/components/FileDirectory/FileDirectory.jsx @@ -7,11 +7,13 @@ import { toggleRightPanel, updateFile, setFilePath, + setTabIndex, } from '../../context/actions/globalActions'; -const { remote } = window.require('electron'); -const fs = remote.require('fs'); +const { ipcRenderer } = require('electron'); + const fileImg = require('../../assets/images/file-document-outline.svg'); + const FileDirectory = ({ fileTree }) => { const [{ isFolderOpen, isFileHighlighted, projectFilePath }, dispatchToGlobal] = useContext( GlobalContext @@ -44,7 +46,8 @@ const FileDirectory = ({ fileTree }) => { }; const handleDisplayFileCode = (filePath) => { - const fileContent = fs.readFileSync(filePath, 'utf8'); + // const fileContent = fs.readFileSync(filePath, 'utf8'); + const fileContent = ipcRenderer.sendSync('Universal.readFile', filePath); dispatchToGlobal(updateFile(fileContent)); dispatchToGlobal(setFilePath(filePath)); }; @@ -94,6 +97,7 @@ const FileDirectory = ({ fileTree }) => { onClick={() => { handleDisplayFileCode(file.filePath); handleClickHighlightFile(file.fileName); + dispatchToGlobal(setTabIndex(0)); }} > {differImg(file.fileName)} diff --git a/src/components/Modals/ExportFileModal.jsx b/src/components/Modals/ExportFileModal.jsx index 293ad8d6..dc913855 100644 --- a/src/components/Modals/ExportFileModal.jsx +++ b/src/components/Modals/ExportFileModal.jsx @@ -12,8 +12,7 @@ import { import styles from './ExportFileModal.module.scss'; -const remote = window.require('electron').remote; -const fs = remote.require('fs'); +const { ipcRenderer } = require('electron'); const ExportFileModal = ({ isExportModalOpen, setIsExportModalOpen }) => { const [fileName, setFileName] = useState(''); @@ -38,7 +37,9 @@ const ExportFileModal = ({ isExportModalOpen, setIsExportModalOpen }) => { const handleClickSave = () => { // file name uniqueness check - if (fs.existsSync(projectFilePath + `/__tests__/${fileName}.test.js`)) { + const filePath = `${projectFilePath}/__tests__/${fileName}.test.js`; + const fileExists = ipcRenderer.sendSync('ExportFileModal.exists', filePath); + if (fileExists) { setInvalidFileName(true); return; } @@ -50,18 +51,22 @@ const ExportFileModal = ({ isExportModalOpen, setIsExportModalOpen }) => { /* ------------------------------------------ EXPORT + DISPLAY FILE ------------------------------------------ */ const exportTestFile = async () => { - if (!fs.existsSync(projectFilePath + '/__tests__')) { - fs.mkdirSync(projectFilePath + '/__tests__'); + const folderPath = `${projectFilePath}/__tests__`; + const folderExists = ipcRenderer.sendSync('ExportFileModal.exists', folderPath); + if (!folderExists) { + ipcRenderer.sendSync('ExportFileModal.mkdir', folderPath); } - await fs.writeFile(projectFilePath + `/__tests__/${fileName}.test.js`, file, (err) => { - if (err) throw err; - }); + const filePath = `${projectFilePath}/__tests__/${fileName}.test.js`; + ipcRenderer.sendSync('ExportFileModal.fileCreate', filePath, file); + dispatchToGlobal(createFileTree(generateFileTreeObject(projectFilePath))); - displayTestFile(projectFilePath + '/__tests__'); + displayTestFile(folderPath); }; const displayTestFile = (testFolderFilePath) => { - const fileContent = fs.readFileSync(testFolderFilePath + `/${fileName}.test.js`, 'utf8'); + const filePath = `${testFolderFilePath}/${fileName}.test.js`; + const fileContent = ipcRenderer.sendSync('ExportFileModal.readFile', filePath); + dispatchToGlobal(updateFile(fileContent)); dispatchToGlobal(toggleFolderView(testFolderFilePath)); dispatchToGlobal(highlightFile(`${fileName}.test.js`)); @@ -78,7 +83,8 @@ const ExportFileModal = ({ isExportModalOpen, setIsExportModalOpen }) => { }; const generateFileTreeObject = (projectFilePath) => { - const fileArray = fs.readdirSync(projectFilePath).map((fileName) => { + const filePaths = ipcRenderer.sendSync('Universal.readDir', projectFilePath); + const fileArray = filePaths.map((fileName) => { // replace backslashes for Windows OS projectFilePath = projectFilePath.replace(/\\/g, '/'); const filePath = `${projectFilePath}/${fileName}`; @@ -91,8 +97,8 @@ const ExportFileModal = ({ isExportModalOpen, setIsExportModalOpen }) => { populateFilePathMap(file); // generateFileTreeObj will be recursively called if it is a folder - const fileData = fs.statSync(file.filePath); - if (file.fileName !== 'node_modules' && file.fileName !== '.git' && fileData.isDirectory()) { + const fileData = ipcRenderer.sendSync('Universal.stat', file.filePath); + if (file.fileName !== 'node_modules' && file.fileName !== '.git' && fileData) { file.files = generateFileTreeObject(file.filePath); } return file; diff --git a/src/components/Modals/ExportFileModal.module.scss b/src/components/Modals/ExportFileModal.module.scss index 8b983d18..f48af1d6 100644 --- a/src/components/Modals/ExportFileModal.module.scss +++ b/src/components/Modals/ExportFileModal.module.scss @@ -1,6 +1,11 @@ @import '../../assets/stylesheets/colors.scss'; @import '../../assets/stylesheets/fonts.scss'; +// .grid-container { +// display: grid; +// grid-template-areas: 'myArea myArea myArea myArea'; +// } + .modal { position: fixed; width: 500px; @@ -10,14 +15,38 @@ border-radius: 5px; box-shadow: 0 0 0 1px white inset; object-fit: cover; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + top: 25%; + left: 22%; + // transform: translate(-90%, -50%); +} + +.modal2 { + position: fixed; + width: 500px; + font-family: $openSans; + background-color: white; + opacity: 1; + border: 8px solid $mint; + border-radius: 5px; + box-shadow: 0 0 0 1px white inset; + object-fit: cover; + // top: 25%; + // left: 22%; +} + +.modalCustomOverlay { + display: grid; + position: fixed; + inset: 0px; + background-color: rgba(255, 255, 255, 0.75); + z-index: 3; + // Overlay should only show in LeftPanel. Currently hard-coded. + // min-width: 876px; } #title { display: flex; - justify-content: space-between; + justify-content: center; align-items: center; background-color: $mint; margin: 1px; @@ -31,6 +60,22 @@ } } +#escapeButton { + position: absolute; + top: 5px; + right: 5px; + font-size: 1.25rem; + z-index: 3; + transition: 250ms; + + &:hover { + color: red; + cursor: pointer; + font-size: 1.5rem; + font-weight: bold; + } +} + #close { margin-right: 8px; width: 24px; @@ -43,10 +88,14 @@ #endpoint { color: black; - span { - margin-left: 50px; - } + } + #step { + color: rgb(255, 255, 255); + margin-bottom: 10px; + // span { + // margin-left: 50px; + // } span:hover { color: $mint; } @@ -60,6 +109,8 @@ li:first-child { margin-top: 4px; } + + } p { @@ -72,7 +123,7 @@ width: 430px; } button { - margin: 10px 5px 5px 5px; + margin: 5px 10px 5px 0px; font-size: 12px; text-align: center; vertical-align: middle; @@ -100,6 +151,7 @@ text-align: center; } + #save:hover { background-color: rgb(232, 232, 232); color: $mint; @@ -111,7 +163,17 @@ #newTestButtons { display: flex; - justify-content: center; + justify-content: flex-start; + + #feedback { + + p { + + font-size: 12px; + margin-top: 6px; + } + }; + } #newTestButtons:focus { outline: 1px solid darkblue @@ -146,33 +208,46 @@ } } +#accordionSummary { + background-color: $mint2; +} + +#accordionDetails { + background-color: 'white'; + opacity: 1; + +} + +#accordionDiv { + // background-color: $mint; + display: flex; + flex: 1; + flex-direction: column; +} + +#endPointGuide { + margin: 8px; +} + +#configGuide { + display: flex; + flex: 1; + flex-direction: column; + line-height: normal; +} + pre { + white-space: pre-wrap; + display: flex; + flex: 1; + flex-direction: column; padding: 12px 15px; - margin-top: -4px; + // margin-top: -4px; margin-bottom: 8px; overflow: auto; font-size: 85%; line-height: 1.45; - background-color: #f6f8fa; - // border-radius: 6px; - .code-wrapper { - background: #f4f4f4; - // padding: 1rem; - border: 1px solid #ddd; - color: #666; - width: 500px; - - code { - display: inline; - max-width: auto; - padding: 0; - margin: 0; - overflow: visible; - line-height: inherit; - word-wrap: normal; - background-color: initial; - white-space: pre; - border: 0; - } - } + background-color: #0a1f40; + color: white; + } diff --git a/src/components/Modals/Modal.jsx b/src/components/Modals/Modal.jsx index a10cab2c..48c49f5b 100644 --- a/src/components/Modals/Modal.jsx +++ b/src/components/Modals/Modal.jsx @@ -3,10 +3,29 @@ * which render on the top Test Menu component. */ -import React from 'react'; +import React, { useState, useContext } from 'react'; import ReactModal from 'react-modal'; import styles from './ExportFileModal.module.scss'; import { useCopy, useNewTest, useGenerateScript } from './modalHooks'; +import { setTabIndex, } from '../../context/actions/globalActions'; +// Accordion view +import Accordion from '@material-ui/core/Accordion'; +import AccordionSummary from '@material-ui/core/AccordionSummary'; +import AccordionDetails from '@material-ui/core/AccordionDetails'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import cn from 'classnames'; +import { GlobalContext } from '../../context/reducers/globalReducer'; + +const ipc = require('electron').ipcRenderer; +const os = require('os'); + +// ipc.on('Modal.shellType', (e, shellType) => { +// //Check os platform to change cmd for terminal execution +// let execute = '\n'; +// if (shellType === 'win32') { +// execute = '\r'; +// } +// }); const Modal = ({ title, @@ -25,8 +44,16 @@ const Modal = ({ createTest, closeModal, ); - + const [fileName, setFileName] = useState(''); + const [anchorEl, setAnchorEl] = useState(null); const script = useGenerateScript(title, testType, puppeteerUrl); + const [btnFeedback, setBtnFeedback] = useState({ changedDir: false, installed: false }); + const [{ isFileDirectoryOpen }, dispatchToGlobal] = useContext(GlobalContext); + + const clearAndClose = () => { + setBtnFeedback({ ...btnFeedback, changedDir: false, installed: false }); + closeModal(); + } const modalStyles = { overlay: { @@ -34,66 +61,321 @@ const Modal = ({ }, }; + // Change execute command based on os platform + let execute = '\n'; + if (os.platform() === 'win32') { + execute = '\r'; + } + + const changeDirectory = () => { + ipc.send('terminal.toTerm', `${script.cd}${execute}`); + setBtnFeedback({ ...btnFeedback, changedDir: true }); + }; + + const installDependencies = () => { + ipc.send('terminal.toTerm', `${script.install}${execute}`); + setBtnFeedback({ ...btnFeedback, installed: true }); + dispatchToGlobal(setTabIndex(2)); + }; + + const submitFileName = () => { + const fileName = document.getElementById('inputFileName').value; + setFileName(fileName); + } + + const jestTest = () => { + ipc.send('terminal.toTerm', `npx jest ${fileName}${execute}`); + dispatchToGlobal(setTabIndex(2)); + }; + const verboseTest = () => { + ipc.send('terminal.toTerm', `npx jest --verbose ${fileName}${execute}`); + dispatchToGlobal(setTabIndex(2)); + }; + const coverageTest = () => { + ipc.send('terminal.toTerm', `npx jest --coverage ${fileName}${execute}`); + dispatchToGlobal(setTabIndex(2)); + }; + + // Warning that tests will not be saved while transitioning between test types + if (title === 'New Test') { + return ( + +
+

{title}

+
+ +
+

+ Do you want to start a new test? All unsaved changes +
+ will be lost. +

+ + + + +
+
+ ); + } + + // EndPointGuide component definition, conditionally rendered + const EndPointGuide = () => { + // endpoint guide only exists when user is in endpoint testing + if (script.endPointGuide) { + const array = []; + for (let step in script.endPointGuide) { + array.push(
{script.endPointGuide[step]}{'\n'}
) + }; + // return accordion element + return ( + + ); + } + // return anything to not render accordion + return null; + }; + + // ReactDependencies component definition, conditionally rendered + const ReactDependencies = () => { + if (title === 'hooks' || title === 'react') { + return ( + + ) + } + return null; + } + return ( + {/* Modal Title */}
-

{title === 'New Test' ? title : 'Copy to Terminal'}

+

Run Tests in Terminal

+
-
- {title === 'New Test' - ? ( -

- Do you want to start a new test? All unsaved changes -
- will be lost. -

- ) - : ( -
-              
- - {script} - - - {testType === 'react' - ? -

- Requires React version 16 or less. -

- : null - } - -

- Note if you are using Create React App do not install jest -

-
-
- )} - - {title === 'New Test' - ? ( - - ) - : ( - - )} - - + {/* Accordian View */} +
+ {/* Configuration Guide */} + + + } + aria-controls="panel1a-content" + id="panel1a-header" + id={styles.accordionSummary} + > + Configuration Guide + + +
+ {/* Change Directory */} + + } + aria-controls="panel1a-content" + id={styles.accordionSummary} + > + 1. Set terminal to root directory. + + +
+
+                      
+ + {script.cd} + +
+
+ + +
+ {btnFeedback.changedDir === false ? null :

Directory has been changed to root directory.

} +
+
+
+
+
+ {/* Install Dependencies */} + + } + aria-controls="panel1a-content" + id={styles.accordionSummary}> + 2. Install dependencies and Jest. + + +
+
+                      
+ + {script.install} + +
+
+ + +
+
+
+
+
+
+ {/* Create config file only if title is react or hook */} + +
+
+
+ {/* Specify File to test */} + + } + aria-controls="panel1a-content" + // id="panel1a-header" + id={styles.accordionSummary} + > + Specify file to test (optional) + + + {/* Select test to run */} +
+ + + + +
+
+
+ {/* Testing */} + + } + aria-controls="panel1a-content" + // id="panel1a-header" + id={styles.accordionSummary} + > + Select and Run Tests + + + {/* Select test to run */} +
+ {/* To do: make button toggle on/off */} +
+                
+ + {`npx jest ${fileName}\n`} + {`npx jest --verbose ${fileName}\n`} + {`npx jest --coverage ${fileName}\n`} + +
+
+ + + + + +
+
+
); diff --git a/src/components/Modals/modalHooks.js b/src/components/Modals/modalHooks.js index 1812e033..d2b37b7c 100644 --- a/src/components/Modals/modalHooks.js +++ b/src/components/Modals/modalHooks.js @@ -36,82 +36,80 @@ export function useNewTest(dispatchToMockData, dispatchTestCase, createTest, clo } export function useGenerateScript(test, testType = null, puppeteerUrl = 'sample.io') { - const [{ projectFilePath }] = useContext(GlobalContext); + const [{ projectFilePath }] = useContext(GlobalContext) switch (test) { case 'acc': if (testType === 'html') { return ( - `cd ${projectFilePath} - npm i -D axe-core regenerator-runtime jest - jest` + { + cd: `cd ${projectFilePath}`, + install: 'npm i -D axe-core regenerator-runtime jest', + } ); } if (testType === 'react') { return ( - `cd ${projectFilePath} - npm i -D axe-core regenerator-runtime jest enzyme enzyme-adapter-react-16 - jest` + { + cd: `cd ${projectFilePath}`, + install: 'npm i -D axe-core regenerator-runtime jest enzyme enzyme-adapter-react-16', + } ); } if (testType === 'puppeteer') { return ( - `cd ${projectFilePath} - npm i -D axe-core puppeteer - node ${puppeteerUrl} - ` + { + cd: `cd ${projectFilePath}`, + install: 'npm i -D axe-core puppeteer', + } ); - } return 'error'; case 'react': return ( - `cd ${projectFilePath}\n` + - 'npm i -D @testing-library/jest-dom @testing-library/react test-data-bot jest\n' + - 'npm run test' + { + cd: `cd ${projectFilePath}`, + install: `npm i -D @testing-library/jest-dom @testing-library/react test-data-bot jest`, + } ); case 'redux': return ( - `cd ${projectFilePath}\n` + - 'npm i -D @testing-library/jest-dom @testing-library/react test-data-bot redux-mock-store redux-thunk fetch-mock node-fetch jest\n' + - 'npm run test' + { + cd: `cd ${projectFilePath}`, + install: 'npm i -D @testing-library/jest-dom @testing-library/react test-data-bot redux-mock-store redux-thunk fetch-mock node-fetch jest', + } ); case 'hooks': return ( - `cd ${projectFilePath}\n` + - 'npm i -D @testing-library/jest-dom @testing-library/react test-data-bot @testing-library/react-hooks jest\n' + - 'npm run test' + { + cd: `cd ${projectFilePath}`, + install: 'npm i -D @testing-library/jest-dom @testing-library/react test-data-bot @testing-library/react-hooks jest', + } ); case 'endpoint': + const endPointGuide = { + 1: `1. Please follow these steps to configure your files correctly. The tests will not run properly if you skip these steps.`, + 2: `2. Your server file MUST export your server object.`, + 3: `3. Comment out or remove the appropriate lines of code where the call to the server's listen method takes place.`, + 4: `4. If you are testing a route that involves querying a database, you must import the file where your database instance is created.`, + 5: `5. In that file, you must export your database instance object.`, + '5a': `5a. PostgreSQL: Pool, Client, or pg object.`, + '5b': `5b. MongoDB: MongoClient instance.`, + '5c': `5c. Mongoose: mongoose instance.`, + } return ( - <> -

- Please follow these steps to configure your files correctly. The tests will not run - properly if you skip these steps! -

-

1. Your server file MUST export your server object. -

2. Comment out or remove the appropriate lines of code where the call to the - server's listen method takes place -

- Example -

3. If your are testing a route that involves querying a database, you must - import the file where your database instance is created. -

-

    - 4. In that file, you must export your database instance object -
  • PostgreSQL: Pool, Client, or pg object
  • -
  • MongoDB: MongoClient instance
  • -
  • Mongoose: mongoose instance
  • -
- 5. Make sure to add "jest" to the test script in the package.json file. -

-

- npm i -D jest supertest -

- npm run test - + { + endPointGuide: endPointGuide, + cd: `cd ${projectFilePath}`, + install: 'npm i -D jest supertest regenerator-runtime', + } ); case 'puppeteer': - return `cd ${projectFilePath}\nnpm i -D jest puppeteer\nnpm run test`; + return ( + { + cd: `cd ${projectFilePath}`, + install: 'npm i -D puppeteer', + } + ); default: return ''; // code block diff --git a/src/components/NavBar/NavBar.jsx b/src/components/NavBar/NavBar.jsx index b0c004fd..3eed01c6 100644 --- a/src/components/NavBar/NavBar.jsx +++ b/src/components/NavBar/NavBar.jsx @@ -20,13 +20,15 @@ import ExportFileModal from '../Modals/ExportFileModal'; const menuIcon = require('../../assets/images/menu.png'); const exportIcon = require('../../assets/images/file-export.png'); -const browserIcon = require('../../assets/images/google-chrome.png'); -const codeIcon = require('../../assets/images/visual-studio-code.png'); -const homeIcon = require('../../assets/images/home.png'); +// const browserIcon = require('../../assets/images/google-chrome.png'); +// const codeIcon = require('../../assets/images/visual-studio-code.png'); +// const homeIcon = require('../../assets/images/home.png'); const NavBar = ({ inAboutPage }) => { const [ - { fileTree, isFileDirectoryOpen, projectUrl, rightPanelDisplay }, + { + fileTree, isFileDirectoryOpen, projectUrl, rightPanelDisplay, + }, dispatchToGlobal, ] = useContext(GlobalContext); const [isExportModalOpen, setIsExportModalOpen] = useState(false); @@ -37,21 +39,21 @@ const NavBar = ({ inAboutPage }) => { }; /* switches between code and browser view */ - const handleEditorToggle = () => { - if (!inAboutPage) dispatchToGlobal(toggleRightPanel('codeEditorView')); - }; + // const handleEditorToggle = () => { + // if (!inAboutPage) dispatchToGlobal(toggleRightPanel('codeEditorView')); + // }; /* switches between code and browser view */ - const handleBrowserToggle = () => { - if (inAboutPage) return; - if (rightPanelDisplay === 'browserView' && projectUrl) { - dispatchToGlobal(resetToProjectUrl()); - } - if (!projectUrl) { - dispatchToGlobal(setProjectUrl('https://google.com')); - } - dispatchToGlobal(toggleRightPanel('browserView')); - }; + // const handleBrowserToggle = () => { + // if (inAboutPage) return; + // if (rightPanelDisplay === 'browserView' && projectUrl) { + // dispatchToGlobal(resetToProjectUrl()); + // } + // if (!projectUrl) { + // dispatchToGlobal(setProjectUrl('https://google.com')); + // } + // dispatchToGlobal(toggleRightPanel('browserView')); + // }; /* exports the file (when true) */ const openExportModal = () => { @@ -60,19 +62,21 @@ const NavBar = ({ inAboutPage }) => { }; /*returns to project loader screen */ - const handleClickHome = () => { - dispatchToGlobal(loadProject(false)); - }; + // const handleClickHome = () => { + // dispatchToGlobal(loadProject(false)); + // }; /* * renders: buttons + icons for navbar, exportFileModal, boxes to open new folder and enter url, file directory */ return (
+ {/* File Explorer */} + {/* Export */} - - */} + + {/* Home Button. Deprecated as it results in redundant ptyProcess. Use Open folder to change directory. */} + {/* + */} {isFileDirectoryOpen && }
); diff --git a/src/components/OpenFolder/OpenFolderButton.jsx b/src/components/OpenFolder/OpenFolderButton.jsx index 9c67d90c..2ef93373 100644 --- a/src/components/OpenFolder/OpenFolderButton.jsx +++ b/src/components/OpenFolder/OpenFolderButton.jsx @@ -15,11 +15,15 @@ import { } from '../../context/actions/globalActions'; import { GlobalContext } from '../../context/reducers/globalReducer'; +const { ipcRenderer } = require('electron'); +const os = require('os'); const folderOpenIcon = require('../../assets/images/folder-open.png'); -const { remote } = window.require('electron'); -const electronFs = remote.require('fs'); -const { dialog } = remote; +// Change execute command based on os platform +let execute = '\n'; +if (os.platform() === 'win32') { + execute = '\r'; +} const OpenFolder = () => { const [{ isProjectLoaded, isFileDirectoryOpen, isTestModalOpen }, dispatchToGlobal] = useContext( @@ -27,19 +31,11 @@ const OpenFolder = () => { ); const handleOpenFolder = () => { - const directory = dialog.showOpenDialog({ - properties: ['openDirectory', 'createDirectory'], - filters: [ - { name: 'Javascript Files', extensions: ['js', 'jsx'] }, - { name: 'Style', extensions: ['css'] }, - { name: 'Html', extensions: ['html'] }, - ], - message: 'Please select your project folder', - }); + const directory = ipcRenderer.sendSync('OpenFolderButton.dialog'); if (directory && directory[0]) { let directoryPath = directory[0]; - //replace backslashes for Windows OS + // replace backslashes for Windows OS directoryPath = directoryPath.replace(/\\/g, '/'); dispatchToGlobal(setProjectFilePath(directoryPath)); dispatchToGlobal(createFileTree(generateFileTreeObject(directoryPath))); @@ -47,6 +43,9 @@ const OpenFolder = () => { dispatchToGlobal(setTestCase('')); if (!isTestModalOpen) dispatchToGlobal(toggleModal()); if (!isFileDirectoryOpen) dispatchToGlobal(toggleFileDirectory()); + + // Re-direct terminal directory to user selected directory + ipcRenderer.send('terminal.toTerm', `cd "${directoryPath}"${execute}`); } }; @@ -61,7 +60,8 @@ const OpenFolder = () => { }; const generateFileTreeObject = (directoryPath) => { - const fileArray = electronFs.readdirSync(directoryPath).map((fileName) => { + const filePaths = ipcRenderer.sendSync('Universal.readDir', directoryPath); + const fileArray = filePaths.map((fileName) => { // replace backslashes for Windows OS directoryPath = directoryPath.replace(/\\/g, '/'); const filePath = `${directoryPath}/${fileName}`; @@ -74,8 +74,8 @@ const OpenFolder = () => { populateFilePathMap(file); // generateFileTreeObj will be recursively called if it is a folder - const fileData = electronFs.statSync(file.filePath); - if (file.fileName !== 'node_modules' && file.fileName !== '.git' && fileData.isDirectory()) { + const isDirectory = ipcRenderer.sendSync('OpenFolderButton.isDirectory', filePath); + if (file.fileName !== 'node_modules' && file.fileName !== '.git' && isDirectory) { file.files = generateFileTreeObject(file.filePath); } return file; diff --git a/src/components/Terminal/TerminalView.tsx b/src/components/Terminal/TerminalView.tsx new file mode 100644 index 00000000..69745a14 --- /dev/null +++ b/src/components/Terminal/TerminalView.tsx @@ -0,0 +1,38 @@ +import React, { useEffect, ChangeEvent } from 'react'; +import { XTerm } from 'xterm-for-react'; +import { TerminalType } from '../../utils/terminalTypes'; + +const { Terminal } = require('xterm'); +const ipc = require('electron').ipcRenderer; + +const terminalArgs: TerminalType = { + fontSize: 15, + // Currently rows are hardcoded, next step is to make terminal sizing dynamic. + rows: 30, + fontFamily: 'monospace', + theme: { + background: '#002a36', + }, +}; + +const term = new Terminal(terminalArgs); + +const TerminalView = () => { + useEffect(() => { + // console.log(global.projectFilePath); + term.open(document.getElementsByClassName('terminal')[0]); + // when we have input events (e), we would send the data to the main processor + term.onData((e: ChangeEvent) => { + ipc.send('terminal.toTerm', e); + }); + // when incoming Data comes back to the main process, this ipc renderer + // will take it and writes it to xterm monitor + ipc.on('terminal.incData', (event, data: string) => { + term.write(data); + }); + }, []); + + return ; +}; + +export default TerminalView; diff --git a/src/components/TestMenu/AccTestMenu.tsx b/src/components/TestMenu/AccTestMenu.tsx index 70b53213..7f2e3980 100644 --- a/src/components/TestMenu/AccTestMenu.tsx +++ b/src/components/TestMenu/AccTestMenu.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useContext } from 'react'; import styles from '../TestMenu/TestMenu.module.scss'; import { GlobalContext } from '../../context/reducers/globalReducer'; -import { openBrowserDocs } from '../../context/actions/globalActions'; +import { openBrowserDocs, setTabIndex } from '../../context/actions/globalActions'; import { addDescribeBlock, createNewTest } from '../../context/actions/accTestCaseActions'; import Modal from '../Modals/Modal'; import useGenerateTest from '../../context/useGenerateTest.jsx'; @@ -10,18 +10,21 @@ import { setFilePath, toggleRightPanel, setValidCode, + setTestCase, + toggleModal, } from '../../context/actions/globalActions'; import { AccTestCaseContext } from '../../context/reducers/accTestCaseReducer'; import { useToggleModal } from './testMenuHooks'; +import TestFile from '../pages/TestFile'; const AccTestMenu = () => { // link to accessibility testing docs url - const accUrl = 'https://www.deque.com/axe/core-documentation/api-documentation/'; + const accUrl = 'https://www.deque.com/axe/core-documentation/api-documentation/'; // initialize hooks - const { title, isModalOpen, openModal, openScriptModal, closeModal } = useToggleModal('acc'); + const { title, isModalOpen, openModal, openScriptModal, closeModal, } = useToggleModal('acc'); const [accTestCase, dispatchToAccTestCase] = useContext(AccTestCaseContext); - const [{ projectFilePath, file, exportBool }, dispatchToGlobal] = useContext(GlobalContext); + const [{ projectFilePath, file, exportBool, isTestModalOpen, }, dispatchToGlobal] = useContext(GlobalContext); const generateTest = useGenerateTest('acc', projectFilePath); // setValidCode to true on load. @@ -44,6 +47,11 @@ const AccTestMenu = () => { dispatchToGlobal(updateFile(generateTest(accTestCase))); dispatchToGlobal(toggleRightPanel('codeEditorView')); dispatchToGlobal(setFilePath('')); + dispatchToGlobal(setTabIndex(0)); + }; + + const openNewTestModal = () => { + if (!isTestModalOpen) dispatchToGlobal(toggleModal()); }; if (!file && exportBool) dispatchToGlobal(updateFile(generateTest(accTestCase))); @@ -54,7 +62,7 @@ const AccTestMenu = () => {
-
- + ); } diff --git a/src/components/TestMenu/EndpointTestMenu.tsx b/src/components/TestMenu/EndpointTestMenu.tsx index 2e8b3690..5d0d93e9 100644 --- a/src/components/TestMenu/EndpointTestMenu.tsx +++ b/src/components/TestMenu/EndpointTestMenu.tsx @@ -7,6 +7,9 @@ import { setFilePath, setValidCode, toggleExportBool, + setTestCase, + toggleModal, + setTabIndex, } from '../../context/actions/globalActions'; import styles from './TestMenu.module.scss'; import Modal from '../Modals/Modal'; @@ -24,7 +27,7 @@ import { useToggleModal, validateInputs } from './testMenuHooks'; const EndpointTestMenu = () => { const [endpointTestCase, dispatchToEndpointTestCase] = useContext(EndpointTestCaseContext); - const [{ projectFilePath, file, exportBool }, dispatchToGlobal] = useContext(GlobalContext); + const [{ projectFilePath, file, exportBool, isTestModalOpen }, dispatchToGlobal] = useContext(GlobalContext); const { title, isModalOpen, openModal, openScriptModal, closeModal } = useToggleModal('endpoint'); const generateTest = useGenerateTest('endpoint', projectFilePath); // Endpoint testing docs url @@ -48,6 +51,7 @@ const EndpointTestMenu = () => { dispatchToGlobal(updateFile(generateTest(endpointTestCase))); dispatchToGlobal(toggleRightPanel('codeEditorView')); dispatchToGlobal(setFilePath('')); + dispatchToGlobal(setTabIndex(0)); }; const handleClickAddDatabase = () => { @@ -57,6 +61,10 @@ const EndpointTestMenu = () => { } else dispatchToEndpointTestCase(toggleDB('PostgreSQL')); }; + const openNewTestModal = () => { + if (!isTestModalOpen) dispatchToGlobal(toggleModal()); + }; + if (exportBool) { const valid = validateInputs('endpoint', endpointTestCase); dispatchToGlobal(setValidCode(valid)); @@ -64,6 +72,7 @@ const EndpointTestMenu = () => { if (valid && !file) dispatchToGlobal(updateFile(generateTest(endpointTestCase))); } + return (
diff --git a/src/components/TestMenu/HooksTestMenu.tsx b/src/components/TestMenu/HooksTestMenu.tsx index e69b7b64..e206b59a 100644 --- a/src/components/TestMenu/HooksTestMenu.tsx +++ b/src/components/TestMenu/HooksTestMenu.tsx @@ -7,6 +7,9 @@ import { toggleRightPanel, setValidCode, toggleExportBool, + setTestCase, + toggleModal, + setTabIndex, } from '../../context/actions/globalActions'; import styles from './TestMenu.module.scss'; import { addHookUpdates, createNewHooksTest } from '../../context/actions/hooksTestCaseActions'; @@ -22,7 +25,7 @@ const HooksTestMenu = () => { HooksTestCaseContext ); const { title, isModalOpen, openModal, openScriptModal, closeModal } = useToggleModal('hooks'); - const [{ projectFilePath, file, exportBool }, dispatchToGlobal] = useContext(GlobalContext); + const [{ projectFilePath, file, exportBool, isTestModalOpen }, dispatchToGlobal] = useContext(GlobalContext); const generateTest = useGenerateTest('hooks', projectFilePath); useEffect(() => { @@ -41,6 +44,11 @@ const HooksTestMenu = () => { dispatchToGlobal(updateFile(generateTest({ hooksTestStatement, hooksStatements }))); dispatchToGlobal(toggleRightPanel('codeEditorView')); dispatchToGlobal(setFilePath('')); + dispatchToGlobal(setTabIndex(0)); + }; + + const openNewTestModal = () => { + if (!isTestModalOpen) dispatchToGlobal(toggleModal()); }; if (exportBool) { diff --git a/src/components/TestMenu/PuppeteerTestMenu.tsx b/src/components/TestMenu/PuppeteerTestMenu.tsx index 7b36ea9b..2c3ac090 100644 --- a/src/components/TestMenu/PuppeteerTestMenu.tsx +++ b/src/components/TestMenu/PuppeteerTestMenu.tsx @@ -6,6 +6,9 @@ import { setFilePath, updateFile, setValidCode, + setTestCase, + toggleModal, + setTabIndex, } from '../../context/actions/globalActions'; import styles from './TestMenu.module.scss'; import Modal from '../Modals/Modal'; @@ -24,7 +27,7 @@ const PuppeteerTestMenu = () => { const { title, isModalOpen, openModal, openScriptModal, closeModal } = useToggleModal( 'puppeteer' ); - const [{ projectFilePath, file, exportBool }, dispatchToGlobal] = useContext(GlobalContext); + const [{ projectFilePath, file, exportBool, isTestModalOpen }, dispatchToGlobal] = useContext(GlobalContext); const generateTest = useGenerateTest('puppeteer', projectFilePath); useEffect(() => { @@ -46,7 +49,13 @@ const PuppeteerTestMenu = () => { dispatchToGlobal(updateFile(generateTest({ puppeteerStatements }))); dispatchToGlobal(toggleRightPanel('codeEditorView')); dispatchToGlobal(setFilePath('')); + dispatchToGlobal(setTabIndex(0)) }; + + const openNewTestModal = () => { + if (!isTestModalOpen) dispatchToGlobal(toggleModal()); + }; + if (!file && exportBool) dispatchToGlobal(updateFile(generateTest({ puppeteerStatements }))); return ( diff --git a/src/components/TestMenu/ReactTestMenu.jsx b/src/components/TestMenu/ReactTestMenu.jsx index f235f913..dbdf164c 100644 --- a/src/components/TestMenu/ReactTestMenu.jsx +++ b/src/components/TestMenu/ReactTestMenu.jsx @@ -11,6 +11,9 @@ import { setFilePath, toggleRightPanel, setValidCode, + setTestCase, + toggleModal, + setTabIndex, } from '../../context/actions/globalActions'; import { ReactTestCaseContext } from '../../context/reducers/reactTestCaseReducer'; import { useToggleModal } from './testMenuHooks'; @@ -23,7 +26,7 @@ const ReactTestMenu = () => { const { title, isModalOpen, openModal, openScriptModal, closeModal } = useToggleModal('react'); const [{ mockData }, dispatchToMockData] = useContext(MockDataContext); const [reactTestCase, dispatchToReactTestCase] = useContext(ReactTestCaseContext); - const [{ projectFilePath, file, exportBool }, dispatchToGlobal] = useContext(GlobalContext); + const [{ projectFilePath, file, exportBool, isTestModalOpen }, dispatchToGlobal] = useContext(GlobalContext); const generateTest = useGenerateTest('react', projectFilePath); useEffect(() => { @@ -42,6 +45,11 @@ const ReactTestMenu = () => { dispatchToGlobal(updateFile(generateTest(reactTestCase, mockData))); dispatchToGlobal(toggleRightPanel('codeEditorView')); dispatchToGlobal(setFilePath('')); + dispatchToGlobal(setTabIndex(0)); + }; + + const openNewTestModal = () => { + if (!isTestModalOpen) dispatchToGlobal(toggleModal()); }; if (!file && exportBool) dispatchToGlobal(updateFile(generateTest(reactTestCase, mockData))); diff --git a/src/components/TestMenu/ReduxTestMenu.tsx b/src/components/TestMenu/ReduxTestMenu.tsx index 5d13c395..e3d9fa8c 100644 --- a/src/components/TestMenu/ReduxTestMenu.tsx +++ b/src/components/TestMenu/ReduxTestMenu.tsx @@ -6,6 +6,9 @@ import { toggleRightPanel, setValidCode, openBrowserDocs, + setTestCase, + toggleModal, + setTabIndex, } from '../../context/actions/globalActions'; import { addAsync, @@ -25,7 +28,7 @@ const ReduxTestMenu = () => { ReduxTestCaseContext ); const { title, isModalOpen, openModal, openScriptModal, closeModal } = useToggleModal('redux'); - const [{ projectFilePath, file, exportBool }, dispatchToGlobal] = useContext(GlobalContext); + const [{ projectFilePath, file, exportBool, isTestModalOpen }, dispatchToGlobal] = useContext(GlobalContext); const generateTest = useGenerateTest('redux', projectFilePath); // Redux testing docs url const reduxUrl = 'https://redux.js.org/recipes/writing-tests'; @@ -58,6 +61,11 @@ const ReduxTestMenu = () => { dispatchToGlobal(updateFile(generateTest({ reduxStatements, reduxTestStatement }))); dispatchToGlobal(toggleRightPanel('codeEditorView')); dispatchToGlobal(setFilePath('')); + dispatchToGlobal(setTabIndex(0)); + }; + + const openNewTestModal = () => { + if (!isTestModalOpen) dispatchToGlobal(toggleModal()); }; if (!file && exportBool) diff --git a/src/components/TestMenu/testMenuHooks.js b/src/components/TestMenu/testMenuHooks.js index 517629ef..6415242b 100644 --- a/src/components/TestMenu/testMenuHooks.js +++ b/src/components/TestMenu/testMenuHooks.js @@ -1,8 +1,12 @@ -import { useState } from 'react'; +import { useState, useContext } from 'react'; +import { GlobalContext } from '../../context/reducers/globalReducer'; +import { setTabIndex } from '../../context/actions/globalActions'; export function useToggleModal(test) { const [isModalOpen, setIsModalOpen] = useState(false); const [title, setTitle] = useState(test); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [{ isFileDirectoryOpen }, dispatchToGlobal] = useContext(GlobalContext); const openModal = () => { setTitle('New Test'); @@ -12,6 +16,7 @@ export function useToggleModal(test) { const openScriptModal = () => { setTitle(title); setIsModalOpen(true); + dispatchToGlobal(setTabIndex(2)); }; const closeModal = () => { @@ -19,14 +24,16 @@ export function useToggleModal(test) { setTitle(test); }; - return { title, isModalOpen, openModal, openScriptModal, closeModal }; + return { + title, isModalOpen, openModal, openScriptModal, closeModal + }; } export const validateInputs = (testSuite, testCaseState) => { + let endpoint, assertion; switch (testSuite) { case 'endpoint': const { serverFilePath, addDB, dbFilePath, endpointStatements } = testCaseState; - let endpoint, assertion; if (!serverFilePath || (addDB && !dbFilePath)) return false; for (endpoint of endpointStatements) { if (!endpoint.method || !endpoint.route) return false; diff --git a/src/context/actions/globalActions.js b/src/context/actions/globalActions.js index f7dd40ef..ac301eb5 100644 --- a/src/context/actions/globalActions.js +++ b/src/context/actions/globalActions.js @@ -19,6 +19,7 @@ export const actionTypes = { TOGGLE_EXPORT_BOOL: 'TOGGLE_EXPORT_BOOL', SET_FILE_PATH: 'SET_FILE_PATH', SET_VALID_CODE: 'SET_VALID_CODE', + SET_TAB_INDEX: 'SET_TAB_INDEX', }; export const setProjectUrl = (url) => ({ @@ -106,3 +107,8 @@ export const setValidCode = (validCode) => ({ type: actionTypes.SET_VALID_CODE, validCode, }); + +export const setTabIndex = (tabIndex) => ({ + type: actionTypes.SET_TAB_INDEX, + tabIndex, +}) diff --git a/src/context/reducers/accTestCaseReducer.ts b/src/context/reducers/accTestCaseReducer.ts index 51fc807f..3aeadce4 100644 --- a/src/context/reducers/accTestCaseReducer.ts +++ b/src/context/reducers/accTestCaseReducer.ts @@ -1,12 +1,11 @@ -import { createContext } from 'react'; -import { AccTestCaseState, Action} from '../../utils/accTypes'; +import { createContext } from 'react'; +import { AccTestCaseState, Action } from '../../utils/accTypes'; import { actionTypes } from '../actions/accTestCaseActions'; export const AccTestCaseContext:any = createContext([]); export const accTestCaseState: AccTestCaseState = { modalOpen: false, - describeId: 1, itId: 1, statementId: 1, diff --git a/src/context/reducers/globalReducer.js b/src/context/reducers/globalReducer.js index 040301cb..64f691c7 100644 --- a/src/context/reducers/globalReducer.js +++ b/src/context/reducers/globalReducer.js @@ -4,7 +4,7 @@ import { actionTypes } from '../actions/globalActions'; export const GlobalContext = createContext(null); export const globalState = { - url: null, + url: "http://www.google.com/", projectUrl: null, isProjectLoaded: false, fileTree: null, @@ -23,6 +23,8 @@ export const globalState = { fileName: '', filePath: '', validCode: true, + // added new state for tab index + tabIndex: 0, }; export const globalReducer = (state, action) => { @@ -110,7 +112,7 @@ export const globalReducer = (state, action) => { ...state, isTestModalOpen: !state.isTestModalOpen, }; - // + case actionTypes.UPDATE_FILE: const file = action.testString; return { @@ -123,7 +125,7 @@ export const globalReducer = (state, action) => { ...state, url: docsUrl, isRightPanelOpen: true, - rightPanelDisplay: 'browserView', + tabIndex: 1, }; case actionTypes.RESET_TO_PROJECT_URL: // formerly NEW_TEST_CLOSE_BROWSER_DOCS @@ -150,7 +152,17 @@ export const globalReducer = (state, action) => { ...state, validCode, }; + + case actionTypes.SET_TAB_INDEX: + const tabIndex = action.tabIndex; + return { + ...state, + tabIndex, + } default: return state; + + } + }; diff --git a/src/context/useGenerateTest.jsx b/src/context/useGenerateTest.jsx index 9649f394..5ca4f667 100644 --- a/src/context/useGenerateTest.jsx +++ b/src/context/useGenerateTest.jsx @@ -1,18 +1,18 @@ /* eslint-disable */ -const remote = window.require('electron').remote; -const fs = remote.require('fs'); -const path = remote.require('path'); -const beautify = remote.require('js-beautify'); +const { ipcRenderer } = require('electron'); +const beautify = require('js-beautify'); function useGenerateTest(test, projectFilePath) { return (testState, mockDataState) => { let testFileCode = ''; - // import React from "react"; - /* ------------------------------------------ REACT IMPORT + TEST STATEMENTS ------------------------------------------ */ + +/* ------------------------------------------ REACT IMPORT + TEST STATEMENTS ------------------------------------------ */ // React Import Statements const addReactImportStatements = () => { - testFileCode += `import { render, fireEvent } from '@testing-library/react'; + testFileCode += ` + import React from 'react'; + import { render, fireEvent } from '@testing-library/react'; import { build, fake } from 'test-data-bot'; import '@testing-library/jest-dom/extend-expect' \n`; @@ -21,9 +21,10 @@ function useGenerateTest(test, projectFilePath) { // React Component Import Statement (Render Card) const addComponentImportStatement = () => { const componentPath = reactTestCase.statements.componentPath; - let filePath = path.relative(projectFilePath, componentPath); + let filePath = ipcRenderer.sendSync('Universal.path', projectFilePath, componentPath); filePath = filePath.replace(/\\/g, '/'); - testFileCode += `import ${reactTestCase.statements.componentName} from '../${filePath}';`; + const formattedComponentName = reactTestCase.statements.componentName.replace(/\.jsx?/,'') + testFileCode += `import ${formattedComponentName} from '../${filePath}';`; }; const addDescribeBlocks = () => { @@ -82,7 +83,8 @@ function useGenerateTest(test, projectFilePath) { // Render Jest Test Code const addRender = (statement, methods) => { let props = createRenderProps(statement.props); - testFileCode += `const {${methods}} = render(<${reactTestCase.statements.componentName} ${props}/>);`; + const formattedComponentName = reactTestCase.statements.componentName.replace(/\.jsx?/,''); + testFileCode += `const {${methods}} = render(<${formattedComponentName} ${props}/>);`; }; // Render Props Jest Test Code @@ -92,7 +94,7 @@ function useGenerateTest(test, projectFilePath) { }, ''); }; - /* ------------------------------------------ REDUX IMPORT + TEST STATEMENTS ------------------------------------------ */ +/* ------------------------------------------ REDUX IMPORT + TEST STATEMENTS ------------------------------------------ */ // Redux Import Statements function addReduxImportStatements() { @@ -226,18 +228,29 @@ function useGenerateTest(test, projectFilePath) { } } - /* ------------------------------------------ HOOKS & CONTEXT IMPORT + TEST STATEMENTS ------------------------------------------ */ +/* ------------------------------------------ HOOKS & CONTEXT IMPORT + TEST STATEMENTS ------------------------------------------ */ // Hooks & Context Import Statements const addHooksImportStatements = () => { - hooksTestCase.hooksStatements.forEach((statement) => { - switch (statement.type) { - case 'hooks': - return addRenderHooksImportStatement(), createPathToHooks(statement); - default: - return statement; - } - }); + if (Array.isArray(hooksTestCase)) { + hooksTestCase.forEach((statement) => { + switch (statement.type) { + case 'hooks': + return addRenderHooksImportStatement(), createPathToHooks(statement); + default: + return statement; + } + }) + } else if (typeof hooksTestCase === 'object') { + hooksTestCase.hooksStatements.forEach((statement) => { + switch (statement.type) { + case 'hooks': + return addRenderHooksImportStatement(), createPathToHooks(statement); + default: + return statement; + } + }); + } testFileCode += '\n'; }; @@ -269,19 +282,31 @@ function useGenerateTest(test, projectFilePath) { // Hooks & Context Test Statements const addHooksDescribeBlock = () => { testFileCode += `\nafterEach(cleanup);\ndescribe('${hooksTestCase.hooksTestStatement}', () => {`; - hooksTestCase.hooksStatements.forEach((statement) => { + if (Array.isArray(hooksTestCase)) { + hooksTestCase.forEach((statement) => { switch (statement.type) { case 'hooks': return addHookUpdates(statement); default: return statement; } - }); + }); + } else if (typeof hooksTestCase === 'object') { + hooksTestCase.hooksStatements.forEach((statement) => { + switch (statement.type) { + case 'hooks': + return addHookUpdates(statement); + default: + return statement; + } + }); + } + testFileCode += '});'; testFileCode += '\n'; }; - /* ------------------------------------------ ENDPOINT IMPORT + TEST STATEMENTS ------------------------------------------ */ +/* ------------------------------------------ ENDPOINT IMPORT + TEST STATEMENTS ------------------------------------------ */ // Endpoint Import Statements const addEndpointImportStatements = () => { @@ -302,7 +327,7 @@ function useGenerateTest(test, projectFilePath) { }); }; - /* ------------------------------------------ PUPPETEER IMPORT + TEST STATEMENTS ------------------------------------------ */ +/* ------------------------------------------ PUPPETEER IMPORT + TEST STATEMENTS ------------------------------------------ */ /* getLargestContentfulPaint() - creating a new PerformanceObserver object which will call the callback function when observed performance events happen @@ -356,14 +381,14 @@ function useGenerateTest(test, projectFilePath) { }); }; - /* ------------------------------------------ FILEPATHS ------------------------------------------ */ +/* ------------------------------------------ FILEPATHS ------------------------------------------ */ // Actions Filepath // creates import statement for action creators const createPathToActions = (statement) => { let filePath = null; if (statement.filePath) { - filePath = path.relative(projectFilePath, statement.filePath); + let filePath = ipcRenderer.sendSync('Universal.path', projectFilePath, statement.filePath); filePath = filePath.replace(/\\/g, '/'); } if (!testFileCode.includes(`import * as actions from from`) && filePath) { @@ -375,7 +400,7 @@ function useGenerateTest(test, projectFilePath) { function createPathToReducers(statement) { let filePath = null; if (statement.reducersFilePath) { - filePath = path.relative(projectFilePath, statement.reducersFilePath); + let filePath = ipcRenderer.sendSync('Universal.path', projectFilePath, statement.reducersFilePath); filePath = filePath.replace(/\\/g, '/'); } if ( @@ -393,7 +418,7 @@ function useGenerateTest(test, projectFilePath) { let filePath = null; let bool = false; if (statement.typesFilePath) { - filePath = path.relative(projectFilePath, statement.typesFilePath); + let filePath = ipcRenderer.sendSync('Universal.path', projectFilePath, statement.typesFilePath); filePath = filePath.replace(/\\/g, '/'); bool = areActionTypesDeclaredInSameFileAsActionCreators(statement.typesFilePath); } @@ -409,7 +434,7 @@ function useGenerateTest(test, projectFilePath) { } // This function returns true when actiontypes are declared in the same file as the action creators like with this app const areActionTypesDeclaredInSameFileAsActionCreators = (file) => { - const page = fs.readFileSync(file); + const page = ipcRenderer.sendSync('Universal.readFile', file); if (page.includes(`export const actionTypes`)) return true; else return false; }; @@ -419,7 +444,7 @@ function useGenerateTest(test, projectFilePath) { let filePath = null; console.log(filePath); if (statement.middlewaresFilePath) { - filePath = path.relative(projectFilePath, statement.middlewaresFilePath); + let filePath = ipcRenderer.sendSync('Universal.path', projectFilePath, statement.middlewaresFilePath); filePath = filePath.replace(/\\/g, '/'); } @@ -438,16 +463,37 @@ function useGenerateTest(test, projectFilePath) { // str += `${curr}, `; // return str; // }, ''); - const { hooksStatements } = hooksTestCase; - const hookImports = hooksStatements.reduce((str, { hook }) => { - str += `${hook}, `; - return str; - }, ''); - if (!testFileCode.includes(`import { ${hooksStatements[0].hook}`) && statement.hookFilePath) { - let filePath = path.relative(projectFilePath, statement.hookFilePath); - filePath = filePath.replace(/\\/g, '/'); - testFileCode += `import { ${hookImports} } from '../${filePath}';`; + // const { hooksStatements } = hooksTestCase; + // const hooksStatements = hooksTestCase.hooksStatements; + + + if (Array.isArray(hooksTestCase)) { + const hookImports = hooksTestCase.reduce((str, { hook }) => { + str += `${hook}, `; + return str; + }, ''); + + if (!testFileCode.includes(`import { ${hooksTestCase[0].hook}`) && statement.hookFilePath) { + let filePath = ipcRenderer.sendSync('Universal.path', projectFilePath, statement.hookFilePath); + filePath = filePath.replace(/\\/g, '/'); + + testFileCode += `import { ${hookImports} } from '../${filePath}';`; + } + + } else if (typeof hooksTestCase === 'object') { + const hookImports = hooksTestCase.hooksStatements.reduce((str, { hook }) => { + str += `${hook}, `; + return str; + }, ''); + + if (!testFileCode.includes(`import { ${hooksTestCase.hooksStatements[0].hook}`) && statement.hookFilePath) { + let filePath = ipcRenderer.sendSync('Universal.path', projectFilePath, statement.hookFilePath); + filePath = filePath.replace(/\\/g, '/'); + + testFileCode += `import { ${hookImports} } from '../${filePath}';`; + } + } } @@ -465,14 +511,15 @@ function useGenerateTest(test, projectFilePath) { // Endpoint Filepath const createPathToEndFiles = (serverFilePath, dbFilePath, addDB) => { if (serverFilePath) { - let filePath = path.relative(projectFilePath, serverFilePath); + let filePath = ipcRenderer.sendSync('Universal.path', projectFilePath, serverFilePath); filePath = filePath.replace(/\\/g, '/'); testFileCode = `const app = require('../${filePath}'); - const supertest = require('supertest') + const supertest = require('supertest'); + const regeneratorRuntime = require('regenerator-runtime'); const request = supertest(app)\n`; } else testFileCode = 'Please Select A Server!'; if (dbFilePath) { - let filePath = path.relative(projectFilePath, dbFilePath); + let filePath = ipcRenderer.sendSync('Universal.path', projectFilePath, dbFilePath); filePath = filePath.replace(/\\/g, '/'); switch (addDB) { @@ -494,7 +541,7 @@ function useGenerateTest(test, projectFilePath) { } }; - /* ------------------------------------------ MOCK DATA + METHODS ------------------------------------------ */ +/* ------------------------------------------ MOCK DATA + METHODS ------------------------------------------ */ const addMockData = () => { mockData.forEach((mockDatum) => { @@ -514,7 +561,7 @@ function useGenerateTest(test, projectFilePath) { }, ''); }; - /* ------------------------------------------ TEST STATEMENTS ------------------------------------------ */ +/* ------------------------------------------ TEST STATEMENTS ------------------------------------------ */ // Action Jest Test Code const addAction = (action) => { @@ -703,7 +750,7 @@ function useGenerateTest(test, projectFilePath) { // // Endpoint Jest Test Code const addEndpoint = (statement) => { - testFileCode += `\n test('${statement.testName}', async () => {\n const response = await request.${statement.method}('${statement.route}')`; + testFileCode += `\n test('${statement.testName}', async () => {\n const response = await request.${statement.method}('${statement.route}');`; testFileCode += statement.postData ? `.send( ${statement.postData.trim()})\n.set({'Content-Type': 'application/json',` : statement.headers.length @@ -722,7 +769,7 @@ function useGenerateTest(test, projectFilePath) { .replace(/\(([^)]+)\)/, '') .split(' ') .join(''); - testFileCode += `expect(response.${expectedResponse.toLowerCase()})`; + testFileCode += `\n expect(response.${expectedResponse.toLowerCase()})`; testFileCode += not ? `.not.${matcher}(${value});` : `.${matcher}(${value});`; }); testFileCode += '});'; @@ -789,7 +836,7 @@ function useGenerateTest(test, projectFilePath) { const addAccImportStatements = () => { let { filePath, fileName } = accTestCase; - filePath = path.relative(projectFilePath, filePath); + filePath = ipcRenderer.sendSync('Universal.path', projectFilePath, filePath); filePath = filePath.replace(/\\/g, '/'); testFileCode += ` @@ -965,7 +1012,7 @@ function useGenerateTest(test, projectFilePath) { `; }); }; - + const addAccPuppeteer = () => { testFileCode += ` const puppeteer = require('puppeteer'); diff --git a/src/pages/ProjectLoader/ProjectLoader.jsx b/src/pages/ProjectLoader/ProjectLoader.jsx index cf4aa153..f8cde6dc 100644 --- a/src/pages/ProjectLoader/ProjectLoader.jsx +++ b/src/pages/ProjectLoader/ProjectLoader.jsx @@ -55,22 +55,28 @@ const ProjectLoader = () => {
-
+ + {/* Enter Starting URL */} + {/*
01 Enter test site's URL
-
+
*/} + + {/* Open Project Directory */}
- 02 + {/* 02 */} Select your application
-
+ + {/* Get started */} + {/*
-
+
*/}
); diff --git a/src/pages/RightPanel/RightPanel.jsx b/src/pages/RightPanel/RightPanel.jsx index 0eb091e4..f67eaa87 100644 --- a/src/pages/RightPanel/RightPanel.jsx +++ b/src/pages/RightPanel/RightPanel.jsx @@ -1,24 +1,50 @@ -import React, { useContext } from 'react'; +import React, { useContext, useState } from 'react'; import styles from './RightPanel.module.scss'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; import EditorView from '../../components/EditorView/EditorView'; import BrowserView from '../../components/BrowserView/BrowserView'; import { GlobalContext } from '../../context/reducers/globalReducer'; -import { closeRightPanel } from '../../context/actions/globalActions'; +import { closeRightPanel, setTabIndex } from '../../context/actions/globalActions'; +import TerminalView from '../../components/Terminal/TerminalView'; +// import terminalStyles from '../../components/Terminal/TerminalView.module.scss'; const closeIcon = require('../../assets/images/close.png'); +// add tabviews (convert the code preview and browser preview) - mintyBois 4/16 +// eslint-disable + const RightPanel = () => { - const [{ rightPanelDisplay, url }, dispatchToGlobal] = useContext(GlobalContext); + const [{ rightPanelDisplay, url, tabIndex }, dispatchToGlobal] = useContext(GlobalContext); + const handleCloseRightPanelView = () => { dispatchToGlobal(closeRightPanel()); }; + // Tabbed View + return (
- close + {/* close */} + + dispatchToGlobal(setTabIndex(newValue))} centered> + + + + - {url && rightPanelDisplay === 'browserView' ? : <>} - {!url || rightPanelDisplay === 'codeEditorView' ? : <>} + {/* Editor Tab */} + + {/* Browser Tab */} + + {/* Test Terminal */} +
); }; diff --git a/src/pages/RightPanel/RightPanel.module.scss b/src/pages/RightPanel/RightPanel.module.scss index f51f1d58..557844ec 100644 --- a/src/pages/RightPanel/RightPanel.module.scss +++ b/src/pages/RightPanel/RightPanel.module.scss @@ -9,4 +9,4 @@ align-self: flex-end; margin-right: .5rem; cursor: pointer; -} +} \ No newline at end of file diff --git a/src/pages/TestFile/TestFile.jsx b/src/pages/TestFile/TestFile.jsx index 39cd8822..8417f450 100644 --- a/src/pages/TestFile/TestFile.jsx +++ b/src/pages/TestFile/TestFile.jsx @@ -2,6 +2,7 @@ import React, { useContext, useReducer, Fragment } from 'react'; import ReactModal from 'react-modal'; import styles from '../../components/Modals/ExportFileModal.module.scss'; +import cn from 'classnames'; // may be able to delete toggleReact, etc. from their respective action files import ReactTestCase from '../../components/TestCase/ReactTestCase'; @@ -105,7 +106,14 @@ const TestFile = () => { style={modalStyles} >
-

Test

+

Test

+

What would you like to test?

diff --git a/src/utils/terminalTypes.ts b/src/utils/terminalTypes.ts new file mode 100644 index 00000000..105db0fa --- /dev/null +++ b/src/utils/terminalTypes.ts @@ -0,0 +1,8 @@ +export interface TerminalType { + fontSize: number; + rows: number; + fontFamily?: string; + theme?: { + background?: string; + }; +} diff --git a/tsconfig.json b/tsconfig.json index 1b81a0c9..5f094ed9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,9 +21,9 @@ "noFallthroughCasesInSwitch": true }, "include": [ - "src", + "src/**/*", "src/context/__tests__", - "__tests__/hello.test.js", - "tests/spec.integra.js" + "__tests__/hello.test.js", + "tests/spec.integra.js" ] }