diff --git a/LICENSE b/LICENSE index 6d1eddf5..1eb34609 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 spearmintjs +Copyright (c) 2019-2021 spearmintjs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index c9e230e5..40c5f1c5 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,14 @@ 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 +# How to use Download spearmint @ spearmintjs.com. Available for Mac OS and Windows. +
+ +### React Testing +
To run React tests generated by spearmint, install the following in your dev dependencies. npm i -D jest @testing-library/jest-dom @testing-library/react test-data-bot @@ -18,15 +22,44 @@ To run Hooks / Context tests generated by spearmint, install the following in yo npm i -D @testing-library/react-hooks +
+ +### Endpoint Testing +
To run Endpoint tests generated by spearmint, install the following in your dev dependencies. 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 +
+ +To run Accessibility tests generated by spearmint on HTML, install the following in your dev dependencies. + + npm i -D axe-core regenerator-runtime jest + +To run Accessibility tests generated by spearmint on React Components, install the following in your dev dependencies. + + npm i -D axe-core regenerator-runtime jest enzyme enzyme-adapter-react-16 + +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 + + +
+ +# How it works 1. On the initial screen, enter the URL of your project and load your application to start creating tests. @@ -42,16 +75,22 @@ To run Puppeteer tests generated by spearmint, install the following in your dev ![](/public/testfile.png?raw=true) -## Team +
+ +### The Spearmint Team +
> Alex [@apark0720](https://github.com/apark0720)  ·  +> 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)  ·  -> Chloe [@HeyItsChloe](https://github.com/HeyItsChloe)
+> 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)  ·  -> Johnny [@johnny-lim](https://github.com/johnny-lim)
+> 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)  ·  > Linda [@lcwish](https://github.com/lcwish)  ·  @@ -59,7 +98,10 @@ To run Puppeteer tests generated by spearmint, install the following in your dev > Mike [@mbcoker](https://github.com/mbcoker)  ·  > 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)  ·  +> Rachel [@rachethecreator](https://github.com/rachethecreator)  ·  +> Sean [@sean-haverstock](https://github.com/Sean-Haverstock)
+> Sharon [@sharon-zhu](https://github.com/sharon-zhu)  ·  > Sieun [@sieunjang](https://github.com/sieunjang)  ·  -> Tristen [@twastell](https://github.com/twastell) +> Tolan [@taoantaoan](https://github.com/taoantaoan)  ·  +> Tristen [@twastell](https://github.com/twastell)
+
\ No newline at end of file diff --git a/todoDemo.mov b/deprecated files/todoDemo.mov similarity index 100% rename from todoDemo.mov rename to deprecated files/todoDemo.mov diff --git a/package.json b/package.json index f4a53634..82b4b970 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "Spearmint", - "version": "0.1.0", - "description": "Spearmint", + "name": "spearmint", + "version": "0.5.0", + "description": "An open-source developer tool that simplifies testing and hopes to help increase awareness about web accessibility.", "author": "spearmintjs", "build": { "appId": "com.spearmint", @@ -37,20 +37,23 @@ "react-dom": "^16.8.6", "react-modal": "^3.8.1", "react-monaco-editor": "^0.25.1", - "react-scripts": "3.4.1", + "react-scripts": "^3.4.1", "sass": "^1.26.5", "typescript": "^3.9.2", "wait-on": "^3.3.0" }, "scripts": { - "react-start": "react-scripts start", - "react-build": "NODE_ENV=production react-scripts build", "test": "react-scripts 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", + "react-build": "NODE_ENV=production react-scripts build", "electron-build": "NODE_ENV=production electron-builder -mwl", "release": "npm run react-build && electron-builder --publish=always", - "build": "NODE_ENV=production npm run react-build && npm run electron-build", + "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 .\"" }, "browserslist": { @@ -69,9 +72,12 @@ "@testing-library/jest-dom": "^5.8.0", "@testing-library/react": "^8.0.9", "@testing-library/react-hooks": "^3.2.1", + "@types/classnames": "^2.2.11", "@types/jest": "^25.2.3", "@typescript-eslint/eslint-plugin": "^2.33.0", "@typescript-eslint/parser": "^2.33.0", + "chai": "^4.3.4", + "chai-as-promised": "^7.1.1", "electron": "^5.0.3", "electron-builder": "^22.6.1", "electron-devtools-installer": "^3.0.0", @@ -82,6 +88,7 @@ "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.20.0", "eslint-plugin-react-hooks": "^2.5.1", + "mocha": "^8.3.2", "react-test-renderer": "^16.12.0", "spectron": "^5.0.0", "test-data-bot": "^0.8.0" diff --git a/public/index.html b/public/index.html index d8464cb2..36315630 100644 --- a/public/index.html +++ b/public/index.html @@ -20,7 +20,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - spearmint + spearmint | testing, simplified @@ -42,6 +42,5 @@ return '/monaco-editor-worker-loader-proxy.js'; }, }; - // require(['vs/editor/editor.main'], function() {}); diff --git a/src/App.jsx b/src/App.jsx index 55fdbb3c..3705bd11 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,10 +8,6 @@ import RightPanel from './pages/RightPanel/RightPanel'; import About from './pages/About/About'; const App = () => { - // useReducer takes a reducer and initial state as - // args and return the current state paired with a dispatch method - // distpatchTo method invokes associated reducer function - const [global, dispatchToGlobal] = useReducer(globalReducer, globalState); if (!global.isProjectLoaded) { diff --git a/src/__tests__/globalReducer.test.js b/src/__tests__/globalReducer.test.js index 3d4e2d8b..868daf32 100644 --- a/src/__tests__/globalReducer.test.js +++ b/src/__tests__/globalReducer.test.js @@ -179,14 +179,14 @@ describe('Global Reducer works properly', () => { }); }); - it('should handle UPDATE_FILE_SHOW', () => { - let action = { type: 'UPDATE_FILE_SHOW', testString: '' }; + it('should handle UPDATE_FILE', () => { + let action = { type: 'UPDATE_FILE', testString: '' }; expect(globalReducer(initialState, action)).toEqual({ ...initialState, file: '', }); action = { - type: 'UPDATE_FILE_SHOW', + type: 'UPDATE_FILE', testString: `import React from "react"; import { render, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect`, diff --git a/src/__tests__/spec.e2e.js b/src/__tests__/spec.e2e.js new file mode 100644 index 00000000..e816cd07 --- /dev/null +++ b/src/__tests__/spec.e2e.js @@ -0,0 +1,49 @@ +const Application = require('spectron').Application; +const path = require('path'); +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); + +// specifies the path of the application to launch +const electronPath = require('electron'); + +// tell spectron to look and use the main.js file + package.json located 2 levels above +const appPath = path.join(__dirname, '../..'); + + +// instantiates the spearmint application given the optional paramaters of the Application API +const app = new Application({ + path: electronPath, // string path to the Electron application executable to launch + args: [appPath], // array of paths to find the executable files and package.json +}); + +// define the use of chai and chai as promised packages +global.before(function () { + chai.should(); + chai.use(chaiAsPromised); +}); + +describe('Application Accessibility Audit', function () { + this.timeout(10000); + + beforeEach(function () { + return app.start(); + }); + + afterEach(function () { + if (app && app.isRunning()) { + return app.stop(); + } + }); + + it('Audits Accessibility', function (done) { + app.client.auditAccessibility().then(function (audit) { + if (audit.failed) { + console.error('Please address the following accessibility issues in your application: \n', audit.results) + } + else { + console.log('No accessibility issues have been found.') + } + done() + }) + }); +}); \ No newline at end of file diff --git a/src/__tests__/spec.integra.js b/src/__tests__/spec.integra.js new file mode 100644 index 00000000..a712ff1f --- /dev/null +++ b/src/__tests__/spec.integra.js @@ -0,0 +1,41 @@ +const Application = require('spectron').Application; +const path = require('path'); +const assert = require('assert'); + +// specifies the path of the application to launch +const electronPath = require('electron'); + +// tell spectron to look and use the main.js file + package.json located 2 levels above +const appPath = path.join(__dirname, '../..'); + +// instantiates the spearmint application given the optional paramaters of the Application API +const app = new Application({ + path: electronPath, // string path to the Electron application executable to launch + args: [appPath], // array of paths to find the executable files and package.json +}); + + +describe('Application Accessibility Audit', function () { + this.timeout(10000); + + beforeEach(function () { + return app.start(); + }); + + afterEach(function () { + if (app && app.isRunning()) { + return app.stop(); + } + }); + + it('Audits Accessibility', function () { + return app.client.auditAccessibility().then(function (audit) { + if (audit.failed) { + console.error('Please address the following accessibility issues in your application: \n', audit.results) + } + else { + console.log('No accessibility issues have been found.') + } + }) + }); +}); \ No newline at end of file diff --git a/src/assets/images/spearmintHomepage.png b/src/assets/images/spearmintHomepage.png new file mode 100644 index 00000000..ade25ff4 Binary files /dev/null and b/src/assets/images/spearmintHomepage.png differ diff --git a/src/assets/stylesheets/colors.scss b/src/assets/stylesheets/colors.scss index d1b06664..7ffe1fdf 100644 --- a/src/assets/stylesheets/colors.scss +++ b/src/assets/stylesheets/colors.scss @@ -1,5 +1,5 @@ -$mint: #02c2c3; -$mint2: #69e5ce; +$mint: #038181; +$mint2: #02c3c33f; $dark-gray: #808080; $light-gray: #d5d5d5; $light-gray2: #f6f8f9; diff --git a/src/assets/stylesheets/reset.scss b/src/assets/stylesheets/reset.scss index 630b3f53..09272c4f 100644 --- a/src/assets/stylesheets/reset.scss +++ b/src/assets/stylesheets/reset.scss @@ -89,7 +89,7 @@ video { /* make sure to set some focus styles for accessibility */ :focus { - outline: 0; + outline: 2px solid darkblue; } /* HTML5 display-role reset for older browsers */ diff --git a/src/components/AccTestComponent/AccTestTypes/AccTestTypes.module.scss b/src/components/AccTestComponent/AccTestTypes/AccTestTypes.module.scss new file mode 100644 index 00000000..84759b4b --- /dev/null +++ b/src/components/AccTestComponent/AccTestTypes/AccTestTypes.module.scss @@ -0,0 +1,13 @@ +#AccTestTypesComponent{ + margin-right: 35px; +} + +#AccTestTypesLabel { + display: block; + margin-bottom: 6px; +} + +.AccTestTypesInput{ + margin-top: 4px; + min-height: 35px; +} \ No newline at end of file diff --git a/src/components/AccTestComponent/AccTestTypes/AccTestTypes.tsx b/src/components/AccTestComponent/AccTestTypes/AccTestTypes.tsx new file mode 100644 index 00000000..aa3ff600 --- /dev/null +++ b/src/components/AccTestComponent/AccTestTypes/AccTestTypes.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import styles from './AccTestTypes.module.scss'; + +const AccTestTypes = ({ dispatch, action, currTypes }) => { + const handleChange = (e: React.ChangeEvent) => { + dispatch(action(e.target.value)); + }; + + return ( +
+ + +
+ ); +}; + +export default AccTestTypes; diff --git a/src/components/AccTestComponent/CatTagFilter/CatTagFilter.module.scss b/src/components/AccTestComponent/CatTagFilter/CatTagFilter.module.scss new file mode 100644 index 00000000..e316317e --- /dev/null +++ b/src/components/AccTestComponent/CatTagFilter/CatTagFilter.module.scss @@ -0,0 +1,15 @@ +@import '../../../assets/stylesheets/fonts.scss'; +@import '../../../assets/stylesheets/colors.scss'; + +#CatTagFilter { + font-weight: bold; + font-size: 0.75rem; + position: relative; + margin-left: 5px; + margin-top: 10px; + z-index: 3; + + #accTestCatTypes:focus { + border: 2px solid darkblue; + } +} \ No newline at end of file diff --git a/src/components/AccTestComponent/CatTagFilter/CatTagFilter.tsx b/src/components/AccTestComponent/CatTagFilter/CatTagFilter.tsx new file mode 100644 index 00000000..529f9155 --- /dev/null +++ b/src/components/AccTestComponent/CatTagFilter/CatTagFilter.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import styles from './CatTagFilter.module.scss'; + +const CatTagFilter = ({ dispatch, tagAction, textAction, itId, catTag }) => { + const handleChange = (e: React.ChangeEvent) => { + dispatch(tagAction(itId, e.target.value)); + if (e.target.value === 'none') dispatch(textAction(`Component is accessible regarding all axe-core categories.`, itId)); + else dispatch(textAction(`Component is accessible regarding ${e.target.value}.`, itId)); + }; + + return ( +
+ + +
+ ); +}; + +export default CatTagFilter; diff --git a/src/components/AccTestComponent/DescribeRenderer/DescribeRenderer.module.scss b/src/components/AccTestComponent/DescribeRenderer/DescribeRenderer.module.scss new file mode 100644 index 00000000..401dee9c --- /dev/null +++ b/src/components/AccTestComponent/DescribeRenderer/DescribeRenderer.module.scss @@ -0,0 +1,82 @@ +@import '../../../assets/stylesheets/fonts.scss'; +@import '../../../assets/stylesheets/colors.scss'; +@import '../../../pages/LeftPanel/LeftPanel.module.scss'; + +#describeBlock { + position: relative; + background-color: white; + border-radius: 3px; + padding: 1rem; + margin-top: 10px; + box-shadow: 1px 1px 5px gray; + display: flex; + flex-direction: column; + z-index: 0; + + .describeClose { + 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; + } + } + + .separator { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100px; + // background-color: rgba(128, 128, 128, 0.20); + background-color: #02c3c33f; + border-bottom: 2px solid gray; + opacity: 100%; + z-index: 0; + } + + .describeLabel { + font-weight: bold; + font-size: 0.9rem; + z-index: 3; + font-family: $oxygen; + } + + .describeStatement { + border: none; + border-bottom: 1px solid $mint; + background-color: rgba(0, 0, 0, 0); + margin-top: .8rem; + margin-bottom: .8rem; + font-size: 0.9rem; + z-index: 3; + } + + .buttonContainer { + display: flex; + justify-content: center; + align-items: center; + height: 30px; + margin-top: 1rem; + + .addIt { + height: auto; + color: white; + padding: 0.5rem; + border-radius: 5px; + background-color: $mint; + + &:hover { + background-color: white; + color: $mint; + } + } + } +} diff --git a/src/components/AccTestComponent/DescribeRenderer/DescribeRenderer.tsx b/src/components/AccTestComponent/DescribeRenderer/DescribeRenderer.tsx new file mode 100644 index 00000000..0987594e --- /dev/null +++ b/src/components/AccTestComponent/DescribeRenderer/DescribeRenderer.tsx @@ -0,0 +1,109 @@ +import React, { ChangeEvent } from 'react'; +import cn from 'classnames'; +import { Draggable, Droppable } from 'react-beautiful-dnd'; +import ItRenderer from '../ItRenderer/ItRenderer'; +import StandardTagFilter from '../StandardTagFilter/StandardTagFilter'; +import styles from './DescribeRenderer.module.scss'; +import { deleteDescribeBlock, addItStatement } from '../../../context/actions/accTestCaseActions'; + +const DescribeRenderer = ({ + dispatcher, + describeBlocks, + itStatements, + updateDescribeText, + updateItStatementText, + updateDescribeStandardTag, + updateItCatTag, + type, +}) => { + const deleteDescribeBlockHandleClick = (e: ChangeEvent) => { + e.stopPropagation(); + const describeId = e.target.id; + dispatcher(deleteDescribeBlock(describeId)); + }; + + const addItStatementHandleClick = (e: ChangeEvent) => { + const describeId = e.target.id; + dispatcher(addItStatement(describeId)); + }; + + const deleteDescribeBlockOnKeyUp = (e: ChangeEvent) => { + if (e.charCode === 13) { + const describeId = e.target.id; + dispatcher(deleteDescribeBlock(describeId)); + } + }; + + return describeBlocks.allIds.map((id: string, i: number) => ( + + {(provided) => ( +
+
+ + + + + + +

{describeBlocks.byId[id].text}

+
+ + + {(innerProvided) => ( +
+ + {innerProvided.placeholder} +
+ )} +
+ +
+ +
+ +
+
+ )} + + )); +}; + +export default DescribeRenderer; diff --git a/src/components/AccTestComponent/ItRenderer/ItRenderer.module.scss b/src/components/AccTestComponent/ItRenderer/ItRenderer.module.scss new file mode 100644 index 00000000..5fde752c --- /dev/null +++ b/src/components/AccTestComponent/ItRenderer/ItRenderer.module.scss @@ -0,0 +1,69 @@ +@import '../../../assets/stylesheets/colors.scss'; +@import '../../../assets/stylesheets/fonts.scss'; + +#ItRenderer { + position: relative; + display: flex; + flex-direction: column; + margin-top: 1.5rem; + padding: 0.5rem; + background-color: $light-gray2; + border-radius: 3px; + box-shadow: 2px 2px 5px gray; + height: 100px; + + .itClose { + position: absolute; + font-size: 1.25rem; + top: 5px; + right: 5px; + transition: 250ms; + cursor: pointer; + z-index: 3; + + &:hover { + color: red; + font-size: 1.5rem; + font-weight: bold; + } + } + + .itStatement { + position: absolute; + bottom: 15px; + width: 97.4%; + padding: 0.1rem; + padding-left: 0.4rem; + font-size: 1.1em; + border-bottom: 1px solid $mint; + background-color: rgba(0, 0, 0, 0); + } + + .buttonsContainer { + margin-top: 1rem; + display: flex; + justify-content: space-evenly; + align-items: center; + } + + .reactButton { + height: auto; + font-size: 1rem; + background-color: rgba(0, 0, 0, 0); + border: none; + min-width: 150px; + border-radius: 3px; + font-family: $oxygen; + text-align: center; + transition: 150ms; + color: $mint; + + &:hover { + font-size: 1.1rem; + } + + i { + margin-right: 0.5rem; + } + } +} diff --git a/src/components/AccTestComponent/ItRenderer/ItRenderer.tsx b/src/components/AccTestComponent/ItRenderer/ItRenderer.tsx new file mode 100644 index 00000000..9fa9f4da --- /dev/null +++ b/src/components/AccTestComponent/ItRenderer/ItRenderer.tsx @@ -0,0 +1,73 @@ +import React, { ChangeEvent, useContext } from 'react'; +import cn from 'classnames'; +import { Draggable } from 'react-beautiful-dnd'; +import { AccTestCaseContext } from '../../../context/reducers/accTestCaseReducer'; +import CatTagFilter from '../CatTagFilter/CatTagFilter'; + +import { + deleteItStatement, +} from '../../../context/actions/accTestCaseActions'; + +import styles from './ItRenderer.module.scss'; + +const ItRenderer = ({ + itStatements, + describeId, + updateItStatementText, + updateItCatTag, +}) => { + + const [, dispatchToAccTestCase] = useContext(AccTestCaseContext); + + const deleteItStatementHandleClick = (e: ChangeEvent) => { + const itId = e.target.id; + dispatchToAccTestCase(deleteItStatement(describeId, itId)); + }; + + const deleteItStatementOnKeyUp = (e) => { + if (e.charCode === 13) { + const itId = e.target.id; + dispatchToAccTestCase(deleteItStatement(describeId, itId)); + } + } + + return itStatements.allIds[describeId].map((id: string, i: number) => ( + + {(provided) => ( +
+ + + + + +

{itStatements.byId[id].text}

+
+ +
+ )} +
+ )); +}; + +export default ItRenderer; diff --git a/src/components/AccTestComponent/PuppeteerUrl/PuppeteerUrl.tsx b/src/components/AccTestComponent/PuppeteerUrl/PuppeteerUrl.tsx new file mode 100644 index 00000000..dcfa8bef --- /dev/null +++ b/src/components/AccTestComponent/PuppeteerUrl/PuppeteerUrl.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +// import styles used in AccTestTypes for input labels et al. +import styles from '../AccTestTypes/AccTestTypes.module.scss'; + +const AccTestTypes = ({ dispatch, action }) => { + const handleChange = (e: React.ChangeEvent) => { + dispatch(action(e.target.value)); + }; + + return ( +
+ + + +
+ ); +}; + +export default AccTestTypes; diff --git a/src/components/AccTestComponent/StandardTagFilter/StandardTagFilter.module.scss b/src/components/AccTestComponent/StandardTagFilter/StandardTagFilter.module.scss new file mode 100644 index 00000000..337f727a --- /dev/null +++ b/src/components/AccTestComponent/StandardTagFilter/StandardTagFilter.module.scss @@ -0,0 +1,14 @@ +@import '../../../assets/stylesheets/fonts.scss'; +@import '../../../assets/stylesheets/colors.scss'; + +#StandardTagFilter { + font-weight: bold; + font-size: 0.8rem; + position: relative; + margin-top: 10px; + z-index: 3; + + #accTestStandardTypes:focus { + border: 2px solid darkblue; + } +} \ No newline at end of file diff --git a/src/components/AccTestComponent/StandardTagFilter/StandardTagFilter.tsx b/src/components/AccTestComponent/StandardTagFilter/StandardTagFilter.tsx new file mode 100644 index 00000000..699a225b --- /dev/null +++ b/src/components/AccTestComponent/StandardTagFilter/StandardTagFilter.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import styles from './StandardTagFilter.module.scss'; + +const StandardTagFilter = ({ dispatch, tagAction, textAction, describeId, standardTag }) => { + const handleChange = (e: React.ChangeEvent) => { + dispatch(tagAction(describeId, e.target.value)); + if (e.target.value === 'none') dispatch(textAction(`Component is accessible according to all standards enforced by axe-core.`, describeId)); + else dispatch(textAction(`Component is accessible according to ${e.target.value} standards.`, describeId)); + }; + + return ( +
+ + +
+ ); +}; + +export default StandardTagFilter; diff --git a/src/components/EndpointTestComponent/Endpoint.tsx b/src/components/EndpointTestComponent/Endpoint.tsx index 7b421c99..591029d2 100644 --- a/src/components/EndpointTestComponent/Endpoint.tsx +++ b/src/components/EndpointTestComponent/Endpoint.tsx @@ -70,13 +70,7 @@ const Endpoint = ({ endpoint, index, dispatchToEndpointTestCase }: EndpointProps dispatchToEndpointTestCase(addAssertion(index)); }; - const testDescription = useRef(null); - useEffect(() => { - if (testDescription && testDescription.current) { - testDescription.current.focus(); - } - }, []); return (
@@ -109,7 +103,7 @@ const Endpoint = ({ endpoint, index, dispatchToEndpointTestCase }: EndpointProps >
{ dispatchToGlobal(toggleFolderView(testFolderFilePath)); dispatchToGlobal(highlightFile(`${fileName}.test.js`)); }; - // co + const filePathMap = {}; + const populateFilePathMap = (file) => { + const javaScriptFileTypes = ['js', 'jsx', 'ts', 'tsx']; + const fileType = file.fileName.split('.')[1]; + if (javaScriptFileTypes.includes(fileType) || fileType === 'html') { + // const componentName = file.fileName.split('.')[0]; + filePathMap[file.fileName] = file.filePath; + } + }; + const generateFileTreeObject = (projectFilePath) => { const fileArray = fs.readdirSync(projectFilePath).map((fileName) => { - //replace backslashes for Windows OS + // replace backslashes for Windows OS projectFilePath = projectFilePath.replace(/\\/g, '/'); - let filePath = `${projectFilePath}/${fileName}`; + const filePath = `${projectFilePath}/${fileName}`; const file = { filePath, fileName, files: [], }; - //generateFileTreeObj will be recursively called if it is a folder + + 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') { - if (fileData.isDirectory()) { - file.files = generateFileTreeObject(file.filePath); - file.files.forEach((file) => { - let javaScriptFileTypes = ['js', 'jsx', 'ts', 'tsx']; - let fileType = file.fileName.split('.')[1]; - if (javaScriptFileTypes.includes(fileType)) { - let componentName = file.fileName.split('.')[0]; - filePathMap[componentName] = file.filePath; - } - }); - } + if (file.fileName !== 'node_modules' && file.fileName !== '.git' && fileData.isDirectory()) { + file.files = generateFileTreeObject(file.filePath); } return file; }); diff --git a/src/components/Modals/ExportFileModal.module.scss b/src/components/Modals/ExportFileModal.module.scss index 15ae49fc..8b983d18 100644 --- a/src/components/Modals/ExportFileModal.module.scss +++ b/src/components/Modals/ExportFileModal.module.scss @@ -89,6 +89,10 @@ -webkit-appearance: none; -moz-appearance: none; } + button:focus { + outline: 2px solid darkblue + } + } #text { @@ -101,11 +105,17 @@ color: $mint; border-radius: 3px; } +#save:focus{ + outline: 1px solid darkblue; +} #newTestButtons { display: flex; justify-content: center; } +#newTestButtons:focus { + outline: 1px solid darkblue +} #testMenu { padding: 10px; diff --git a/src/components/Modals/Modal.jsx b/src/components/Modals/Modal.jsx index 5b228908..a10cab2c 100644 --- a/src/components/Modals/Modal.jsx +++ b/src/components/Modals/Modal.jsx @@ -1,3 +1,8 @@ +/* + * Handles modals that pop up from pressing buttons "New Test +" or "Run Test", + * which render on the top Test Menu component. + */ + import React from 'react'; import ReactModal from 'react-modal'; import styles from './ExportFileModal.module.scss'; @@ -10,16 +15,18 @@ const Modal = ({ dispatchToMockData, dispatchTestCase, createTest, + testType = null, + puppeteerUrl = 'sample.io', }) => { const { copySuccess, codeRef, handleCopy } = useCopy(); const { handleNewTest } = useNewTest( dispatchToMockData, dispatchTestCase, createTest, - closeModal + closeModal, ); - const script = useGenerateScript(title); + const script = useGenerateScript(title, testType, puppeteerUrl); const modalStyles = { overlay: { @@ -32,7 +39,7 @@ const Modal = ({ className={styles.modal} isOpen={isModalOpen} onRequestClose={closeModal} - contentLabel='Save?' + contentLabel="Save?" shouldCloseOnOverlayClick={true} shouldCloseOnEsc={true} ariaHideApp={false} @@ -42,28 +49,47 @@ const Modal = ({

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

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

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

- ) : ( -
-            
- {script} -

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

-
-
- )} - - {title === 'New Test' ? ( - - ) : ( - + {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' + ? ( + + ) + : ( + + )} diff --git a/src/components/Modals/modalHooks.js b/src/components/Modals/modalHooks.js index 5a154be7..1812e033 100644 --- a/src/components/Modals/modalHooks.js +++ b/src/components/Modals/modalHooks.js @@ -35,9 +35,34 @@ export function useNewTest(dispatchToMockData, dispatchTestCase, createTest, clo return { handleNewTest }; } -export function useGenerateScript(test) { +export function useGenerateScript(test, testType = null, puppeteerUrl = 'sample.io') { const [{ projectFilePath }] = useContext(GlobalContext); switch (test) { + case 'acc': + if (testType === 'html') { + return ( + `cd ${projectFilePath} + npm i -D axe-core regenerator-runtime jest + jest` + ); + } + if (testType === 'react') { + return ( + `cd ${projectFilePath} + npm i -D axe-core regenerator-runtime jest enzyme enzyme-adapter-react-16 + jest` + ); + } + if (testType === 'puppeteer') { + return ( + `cd ${projectFilePath} + npm i -D axe-core puppeteer + node ${puppeteerUrl} + ` + ); + + } + return 'error'; case 'react': return ( `cd ${projectFilePath}\n` + diff --git a/src/components/NavBar/NavBar.module.scss b/src/components/NavBar/NavBar.module.scss index 080abf24..a05f1877 100644 --- a/src/components/NavBar/NavBar.module.scss +++ b/src/components/NavBar/NavBar.module.scss @@ -21,7 +21,9 @@ margin-bottom: 40px; cursor: pointer; background-color: transparent; - outline: none; + :focus { + outline: 2px solid darkblue + } } .icons { diff --git a/src/components/OpenFolder/OpenFolderButton.jsx b/src/components/OpenFolder/OpenFolderButton.jsx index fb972b2f..9c67d90c 100644 --- a/src/components/OpenFolder/OpenFolderButton.jsx +++ b/src/components/OpenFolder/OpenFolderButton.jsx @@ -23,9 +23,8 @@ const { dialog } = remote; const OpenFolder = () => { const [{ isProjectLoaded, isFileDirectoryOpen, isTestModalOpen }, dispatchToGlobal] = useContext( - GlobalContext + GlobalContext, ); - const filePathMap = {}; const handleOpenFolder = () => { const directory = dialog.showOpenDialog({ @@ -51,31 +50,33 @@ const OpenFolder = () => { } }; - //reads contents within the selected directory and checks if it is a file/folder + const filePathMap = {}; + const populateFilePathMap = (file) => { + const javaScriptFileTypes = ['js', 'jsx', 'ts', 'tsx']; + const fileType = file.fileName.split('.')[1]; + if (javaScriptFileTypes.includes(fileType) || fileType === 'html') { + // const componentName = file.fileName.split('.')[0]; + filePathMap[file.fileName] = file.filePath; + } + }; + const generateFileTreeObject = (directoryPath) => { const fileArray = electronFs.readdirSync(directoryPath).map((fileName) => { - //replace backslashes for Windows OS + // replace backslashes for Windows OS directoryPath = directoryPath.replace(/\\/g, '/'); - let filePath = `${directoryPath}/${fileName}`; + const filePath = `${directoryPath}/${fileName}`; const file = { filePath, fileName, files: [], }; - //generateFileTreeObj will be recursively called if it is a folder + + 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') { - if (fileData.isDirectory()) { - file.files = generateFileTreeObject(file.filePath); - file.files.forEach((file) => { - let javaScriptFileTypes = ['js', 'jsx', 'ts', 'tsx']; - let fileType = file.fileName.split('.')[1]; - if (javaScriptFileTypes.includes(fileType)) { - let componentName = file.fileName.split('.')[0]; - filePathMap[componentName] = file.filePath; - } - }); - } + if (file.fileName !== 'node_modules' && file.fileName !== '.git' && fileData.isDirectory()) { + file.files = generateFileTreeObject(file.filePath); } return file; }); diff --git a/src/components/OpenFolder/OpenFolderButton.module.scss b/src/components/OpenFolder/OpenFolderButton.module.scss index b590b106..9197490b 100644 --- a/src/components/OpenFolder/OpenFolderButton.module.scss +++ b/src/components/OpenFolder/OpenFolderButton.module.scss @@ -1,4 +1,4 @@ -@import '/../../assets/stylesheets/colors.scss'; +@import './../../assets/stylesheets/colors.scss'; .navBtn { padding: 0; @@ -7,7 +7,9 @@ margin-bottom: 40px; cursor: pointer; background-color: transparent; - outline: none; + :focus { + outline: 2px solid darkblue; + } } .icons { @@ -23,11 +25,17 @@ width: 250px; height: 1.5rem; color: $dark-gray; - outline: none; box-shadow: none; cursor: pointer; -webkit-appearance: none; -moz-appearance: none; + :focus { + outline: 2px solid darkblue; + } +} + +#openBtn:focus{ + outline: 2px solid darkblue } .tooltip { diff --git a/src/components/ReactTestComponent/DescribeRenderer/DescribeRenderer.jsx b/src/components/ReactTestComponent/DescribeRenderer/DescribeRenderer.jsx index f3a5baae..982dc1c9 100644 --- a/src/components/ReactTestComponent/DescribeRenderer/DescribeRenderer.jsx +++ b/src/components/ReactTestComponent/DescribeRenderer/DescribeRenderer.jsx @@ -1,7 +1,7 @@ import React, { useRef, useEffect } from 'react'; -import ItRenderer from '../ItRenderer/ItRenderer'; -// import { Draggable } from 'react-beautiful-dnd'; import cn from 'classnames'; +import { Draggable, Droppable } from 'react-beautiful-dnd'; +import ItRenderer from '../ItRenderer/ItRenderer'; import styles from './DescribeRenderer.module.scss'; import { deleteDescribeBlock, addItstatement } from '../../../context/actions/reactTestCaseActions'; @@ -12,16 +12,11 @@ const DescribeRenderer = ({ describeBlocks, itStatements, statements, - draggableStatements, handleChangeDescribeText, handleChangeItStatementText, type, }) => { - const testDescription = useRef(null); - useEffect(() => { - testDescription.current.focus(); - }, []); const deleteDescribeBlockHandleClick = (e) => { e.stopPropagation(); @@ -29,95 +24,86 @@ const DescribeRenderer = ({ dispatcher(deleteDescribeBlock(describeId)); }; + const deleteReactDescribeBlockOnKeyUp = (e) => { + if (e.charCode === 13) { + const describeId = e.target.id; + dispatcher(deleteDescribeBlock(describeId)); + } +} const addItStatementHandleClick = (e) => { const describeId = e.target.id; dispatcher(addItstatement(describeId)); }; - return draggableStatements.map((id, i) => { - return ( -
- - - - -
- -
- -
-
- ); - }); -}; - -export default DescribeRenderer; - -{ - /* + return describeBlocks.allIds.map((id, i) => ( + {(provided) => (
+ +
- + + + {(innerProvided) => ( +
+ {/* {JSON.stringify(provided)} */} + + {innerProvided.placeholder} +
+ )} +
- +
+ )} -
*/ -} +
+ )); +}; + +export default DescribeRenderer; diff --git a/src/components/ReactTestComponent/ItRenderer/ItRenderer.jsx b/src/components/ReactTestComponent/ItRenderer/ItRenderer.jsx index 0a15233a..bac5ade6 100644 --- a/src/components/ReactTestComponent/ItRenderer/ItRenderer.jsx +++ b/src/components/ReactTestComponent/ItRenderer/ItRenderer.jsx @@ -1,5 +1,6 @@ import React, { useContext } from 'react'; import cn from 'classnames'; +import { Draggable } from 'react-beautiful-dnd'; import ReactTestStatements from '../../TestCase/ReactTestStatements'; import CustomInput from '../CustomInput/CustomInput'; import { @@ -20,11 +21,6 @@ const ItRenderer = ({ }) => { const [, dispatchToReactTestCase] = useContext(ReactTestCaseContext); - // filter out ids not belonging to the correct describe block - const filteredIds = itStatements.allIds.filter((id) => { - return itStatements.byId[id].describeId === describeId; - }); - const addRenderHandleClick = (e) => { const itId = e.target.id; dispatchToReactTestCase(addRender(describeId, itId)); @@ -34,6 +30,13 @@ const ItRenderer = ({ const itId = e.target.id; dispatchToReactTestCase(deleteItStatement(describeId, itId)); }; + + const deleteReactItStatementOnKeyUp = (e) => { + if (e.charCode === 13) { + const itId = e.target.id; + dispatchToReactTestCase(deleteItStatement(describeId, itId)); + } + } const addActionHandleClick = (e) => { const itId = e.target.id; dispatchToReactTestCase(addAction(describeId, itId)); @@ -43,47 +46,63 @@ const ItRenderer = ({ dispatchToReactTestCase(addAssertion(describeId, itId)); }; - return filteredIds.map((id, i) => ( -
- - -
- -
- {type === 'react' && ( -
- - - + return itStatements.allIds[describeId].map((id, i) => ( + + {(provided) => ( +
+ + +
+ +
+ {type === 'react' && ( +
+ + + +
+ )}
- )} -
-
+
+ )} + )); }; diff --git a/src/components/ReactTestComponent/Render/Prop.module.scss b/src/components/ReactTestComponent/Render/Prop.module.scss index 77b32a29..b950eaf5 100644 --- a/src/components/ReactTestComponent/Render/Prop.module.scss +++ b/src/components/ReactTestComponent/Render/Prop.module.scss @@ -1,4 +1,4 @@ -@import '/../../../assets/stylesheets/colors.scss'; +@import './../../../assets/stylesheets/colors.scss'; #renderPropsFlexBox { display: flex; diff --git a/src/components/SearchInput/SearchInput.jsx b/src/components/SearchInput/SearchInput.jsx index 6be2e078..096ef841 100644 --- a/src/components/SearchInput/SearchInput.jsx +++ b/src/components/SearchInput/SearchInput.jsx @@ -6,25 +6,27 @@ const SearchInput = ({ action, filePathMap, options, - reactTestCase, - updateTypesFilePath, - updateActionsFilePath, - type, + reactTestCase = null, + updateTypesFilePath = null, + updateActionsFilePath = null, + type = null, label, }) => { const [activeOption, setActiveOption] = useState(0); const [filteredOptions, setFilteredOptions] = useState([]); const [showOptions, setShowOptions] = useState(false); const [userInput, setUserInput] = useState(''); + const handleChange = (e) => { const input = e.currentTarget.value; - const filteredOptions = options.filter( - (optionName) => optionName.toLowerCase().indexOf(input.toLowerCase()) > -1 + // this filters the options as we type, showing only relevant results from file tree + const newFilteredOptions = options.filter( + (optionName) => (optionName.toLowerCase().indexOf(input.toLowerCase()) > -1) ); setActiveOption(0); - setFilteredOptions(filteredOptions); + setFilteredOptions(newFilteredOptions); setShowOptions(true); setUserInput(e.currentTarget.value); }; @@ -37,69 +39,79 @@ const SearchInput = ({ const selectedOption = e.target.type; const filePath = filePathMap[selectedOption] || ''; + + // updateTypesFilePath and updateActionsFilePath are only not-null if used within Redux if (updateTypesFilePath) { dispatch(updateTypesFilePath(selectedOption, filePath, type)); } if (updateActionsFilePath) dispatch(updateActionsFilePath(selectedOption, filePath, type)); + if (action) dispatch(action(selectedOption, filePath)); }; + const handleKeyDown = (e) => { if (action) dispatch(action('', '')); - if (e.keyCode === 13) { + + if (e.keyCode === 13) { // keycode 13 = enter setActiveOption(0); setShowOptions(false); setUserInput(filteredOptions[activeOption]); + const selectedOption = filteredOptions[activeOption]; const filePath = filePathMap[selectedOption] || ''; + if (action) dispatch(action(selectedOption, filePath)); + + // following if is only relevant to redux if (updateTypesFilePath) { dispatch(updateTypesFilePath(selectedOption, filePath, type)); } } else if (e.keyCode === 38) { + // keycode 38 = up arrow - if at top, return, else, move active option highlight up if (activeOption === 0) { return; } setActiveOption(activeOption - 1); } else if (e.keyCode === 40) { + // keycode 40 = down arrow - if at bottom, return, else, move active option highlight down if (activeOption === filteredOptions.length - 1) { return; } setActiveOption(activeOption + 1); } }; + let optionList; if (showOptions && userInput) { - if (filteredOptions) { - optionList = ( + optionList = filteredOptions + ? (
    - {filteredOptions.map((optionName, index) => { - return ( -
  • - {optionName} -
  • - ); - })} + {filteredOptions.map((optionName, index) => ( +
  • + {optionName} +
  • + ))}
- ); - } else { - optionList = ( + ) + : (
No Option!
); - } } + return (
{ + const [accTestCase, dispatchToAccTestCase] = useContext(AccTestCaseContext); + + const { describeBlocks, itStatements, testType } = accTestCase; + + const [{ filePathMap }] = useContext(GlobalContext); + + const reorder = (list: Array, startIndex: number, endIndex: number) => { + const result = Array.from(list); + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + return result; + }; + + const onDragEnd = (result: DropResult) => { + // edge cases: dropped to a non-destination, or dropped where it was grabbed (no change) + if (!result.destination) return; + if (result.destination.index === result.source.index) return; + + const list = result.draggableId.includes('describe') ? describeBlocks.allIds : itStatements.allIds[result.type]; + const func = result.draggableId.includes('describe') ? updateDescribeOrder : updateItStatementOrder; + + const reorderedStatements = reorder(list, result.source.index, result.destination.index); + + dispatchToAccTestCase(func(reorderedStatements, result.type)); + }; + + return ( +
+ + +
+
+ + + {testType === 'puppeteer' ? ( + + ) : ( +
+ +
+ +
+
+ )} +
+ + + + {(provided) => ( +
+ + {provided.placeholder} +
+ )} +
+
+
+
+ ); +}; +export default AccTestCase; diff --git a/src/components/TestCase/HooksTestCase.tsx b/src/components/TestCase/HooksTestCase.tsx index a5d0ee18..cc66c664 100644 --- a/src/components/TestCase/HooksTestCase.tsx +++ b/src/components/TestCase/HooksTestCase.tsx @@ -13,13 +13,7 @@ import { HooksStatements } from '../../utils/hooksTypes'; const HooksTestCase = () => { const [{ hooksStatements }, dispatchToHooksTestCase] = useContext(HooksTestCaseContext); - const testDescription = useRef(null); - useEffect(() => { - if (testDescription && testDescription.current) { - testDescription.current.focus(); - } - }, []); const handleUpdateHooksTestStatement = (e: React.ChangeEvent) => { dispatchToHooksTestCase(updateHooksTestStatement(e.target.value)); @@ -57,7 +51,7 @@ const HooksTestCase = () => {

{ - //changes to pull down context const [reactTestCase, dispatchToReactTestCase] = useReducer( reactTestCaseReducer, - reactTestCaseState + reactTestCaseState, ); - // const { describeBlocks, itStatements, statements } = reactTestCase; const [{ mockData }, dispatchToMockData] = useContext(MockDataContext); const [{ filePathMap }] = useContext(GlobalContext); - const draggableStatements = describeBlocks.allIds; const handleAddMockData = () => { dispatchToMockData(createMockData()); @@ -48,10 +48,33 @@ const ReactTestCase = () => { dispatchToReactTestCase(updateItStatementText(text, itId)); }; + const reorder = (list, startIndex, endIndex) => { + const result = Array.from(list); + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + return result; + }; + + const onDragEnd = (result) => { + // edge cases: dropped to a non-destination, or dropped where it was grabbed (no change) + if (!result.destination) return; + if (result.destination.index === result.source.index) return; + + const list = result.draggableId.includes('describe') ? describeBlocks.allIds : itStatements.allIds[result.type]; + const func = result.draggableId.includes('describe') ? updateDescribeOrder : updateItStatementOrder; + + const reorderedStatements = reorder( + list, + result.source.index, + result.destination.index, + ); + dispatchToReactTestCase(func(reorderedStatements, result.type)); + }; + return (
- -
+ {mockData.length > 0 && (
{mockData.map((data) => { @@ -85,18 +109,34 @@ const ReactTestCase = () => { })}
)} - + + + + {(provided) => ( +
+ + {provided.placeholder} +
+ )} +
+
); }; + export default ReactTestCase; diff --git a/src/components/TestCase/ReduxTestCase.tsx b/src/components/TestCase/ReduxTestCase.tsx index 297d2c11..5a6a6553 100644 --- a/src/components/TestCase/ReduxTestCase.tsx +++ b/src/components/TestCase/ReduxTestCase.tsx @@ -20,11 +20,7 @@ const ReduxTestCase = () => { ReduxTestCaseContext ); - const testDescription: Ref = useRef(null); - useEffect(() => { - testDescription.current.focus(); - }, []); const handleUpdateReduxTestStatement = (e: React.ChangeEvent) => { dispatchToReduxTestCase(updateReduxTestStatement(e.target.value)); @@ -61,8 +57,7 @@ const ReduxTestCase = () => {
{ + // link to accessibility testing docs url + const accUrl = 'https://www.deque.com/axe/core-documentation/api-documentation/'; + + // initialize hooks + const { title, isModalOpen, openModal, openScriptModal, closeModal } = useToggleModal('acc'); + const [accTestCase, dispatchToAccTestCase] = useContext(AccTestCaseContext); + const [{ projectFilePath, file, exportBool }, dispatchToGlobal] = useContext(GlobalContext); + const generateTest = useGenerateTest('acc', projectFilePath); + + // setValidCode to true on load. + useEffect(() => { + dispatchToGlobal(setValidCode(true)); + }, []); + + // handle change to add a Describe Block + const handleAddDescribeBlock = () => { + dispatchToAccTestCase(addDescribeBlock()); + }; + + // handle change to open accessibility URL docs on right panel + const openDocs = () => { + dispatchToGlobal(openBrowserDocs(accUrl)); + }; + + // handle change for 'preview' button to generate test + const fileHandle = () => { + dispatchToGlobal(updateFile(generateTest(accTestCase))); + dispatchToGlobal(toggleRightPanel('codeEditorView')); + dispatchToGlobal(setFilePath('')); + }; + + if (!file && exportBool) dispatchToGlobal(updateFile(generateTest(accTestCase))); + + return ( +
+
+
+ + + + + + {/* Just send user to docs on button click */} +
+ +
+ +
+
+
+ ); +} + +export default AccTestMenu; \ No newline at end of file diff --git a/src/components/TestMenu/EndpointTestMenu.tsx b/src/components/TestMenu/EndpointTestMenu.tsx index e654e5ba..2e8b3690 100644 --- a/src/components/TestMenu/EndpointTestMenu.tsx +++ b/src/components/TestMenu/EndpointTestMenu.tsx @@ -27,7 +27,6 @@ const EndpointTestMenu = () => { const [{ projectFilePath, file, exportBool }, dispatchToGlobal] = useContext(GlobalContext); const { title, isModalOpen, openModal, openScriptModal, closeModal } = useToggleModal('endpoint'); const generateTest = useGenerateTest('endpoint', projectFilePath); - let valid; // Endpoint testing docs url const endpointUrl = 'https://www.npmjs.com/package/supertest'; @@ -59,8 +58,8 @@ const EndpointTestMenu = () => { }; if (exportBool) { - valid = validateInputs('endpoint', endpointTestCase); - valid ? dispatchToGlobal(setValidCode(true)) : dispatchToGlobal(setValidCode(false)); + const valid = validateInputs('endpoint', endpointTestCase); + dispatchToGlobal(setValidCode(valid)); dispatchToGlobal(toggleExportBool()); if (valid && !file) dispatchToGlobal(updateFile(generateTest(endpointTestCase))); } @@ -69,7 +68,7 @@ const EndpointTestMenu = () => {
- + diff --git a/src/components/TestMenu/HooksTestMenu.tsx b/src/components/TestMenu/HooksTestMenu.tsx index 7c07a94d..e69b7b64 100644 --- a/src/components/TestMenu/HooksTestMenu.tsx +++ b/src/components/TestMenu/HooksTestMenu.tsx @@ -18,7 +18,6 @@ import { useToggleModal, validateInputs } from './testMenuHooks'; const HooksTestMenu = () => { // Hooks testing docs url const hooksUrl = 'https://react-hooks-testing-library.com/usage/basic-hooks'; - let valid; const [{ hooksTestStatement, hooksStatements }, dispatchToHooksTestCase] = useContext( HooksTestCaseContext ); @@ -45,23 +44,22 @@ const HooksTestMenu = () => { }; if (exportBool) { - valid = validateInputs('hooks', hooksStatements); - valid ? dispatchToGlobal(setValidCode(true)) : dispatchToGlobal(setValidCode(false)); + const valid = validateInputs('hooks', hooksStatements); + dispatchToGlobal(setValidCode(valid)); dispatchToGlobal(toggleExportBool()); if (valid && !file) dispatchToGlobal(updateFile(generateTest(hooksStatements))); } if (!file && exportBool) { - validateInputs('hooks', hooksStatements) - ? dispatchToGlobal(setValidCode(true)) - : dispatchToGlobal(setValidCode(false)); + const valid = validateInputs('hooks', hooksStatements); + dispatchToGlobal(setValidCode(valid)); dispatchToGlobal(updateFile(generateTest({ hooksTestStatement, hooksStatements }))); } return (
- diff --git a/src/components/TestMenu/ReactTestMenu.jsx b/src/components/TestMenu/ReactTestMenu.jsx index 9ad5a50b..f235f913 100644 --- a/src/components/TestMenu/ReactTestMenu.jsx +++ b/src/components/TestMenu/ReactTestMenu.jsx @@ -50,7 +50,7 @@ const ReactTestMenu = () => {
- + + diff --git a/src/components/TestMenu/TestMenu.module.scss b/src/components/TestMenu/TestMenu.module.scss index f6277912..a3ba8225 100644 --- a/src/components/TestMenu/TestMenu.module.scss +++ b/src/components/TestMenu/TestMenu.module.scss @@ -22,6 +22,8 @@ border: 0.5px $light-gray solid; } + + button:hover { background-color: white; color: $mint; @@ -29,6 +31,7 @@ } } + #left { float: left; button { diff --git a/src/context/actions/accTestCaseActions.ts b/src/context/actions/accTestCaseActions.ts new file mode 100644 index 00000000..4319551d --- /dev/null +++ b/src/context/actions/accTestCaseActions.ts @@ -0,0 +1,108 @@ +/* ------------------------------ Action Types ------------------------------ */ + +export const actionTypes = { + ADD_DESCRIBE_BLOCK: 'ADD_DESCRIBE_BLOCK', + DELETE_DESCRIBE_BLOCK: 'DELETE_DESCRIBE_BLOCK', + UPDATE_DESCRIBE_TEXT: 'UPDATE_DESCRIBE_TEXT', + UPDATE_DESCRIBE_ORDER: 'UPDATE_DESCRIBE_ORDER', + UPDATE_DESCRIBE_STANDARD_TAG: 'UPDATE_DESCRIBE_STANDARD_TAG', + + ADD_ITSTATEMENT: 'ADD_ITSTATEMENT', + DELETE_ITSTATEMENT: 'DELETE_ITSTATEMENT', + UPDATE_ITSTATEMENT_TEXT: 'UPDATE_ITSTATEMENT_TEXT', + UPDATE_ITSTATEMENT_ORDER: 'UPDATE_ITSTATEMENT_ORDER', + UPDATE_IT_CAT_TAG: 'UPDATE_IT_CAT_TAG', + + CREATE_NEW_TEST: 'CREATE_NEW_TEST', + OPEN_INFO_MODAL: 'OPEN_INFO_MODAL', + CLOSE_INFO_MODAL: 'CLOSE_INFO_MODAL', + + UPDATE_FILE_PATH: 'UPDATE_FILE_PATH', + UPDATE_TEST_TYPE: 'UPDATE_TEST_TYPE', + CREATE_PUPPETEER_URL: 'CREATE_PUPPETEER_URL', +}; + +/* --------------------------------- Actions -------------------------------- */ + +export const addDescribeBlock = () => ({ + type: actionTypes.ADD_DESCRIBE_BLOCK, +}); + +export const deleteDescribeBlock = (describeId: string) => ({ + type: actionTypes.DELETE_DESCRIBE_BLOCK, + describeId, +}); + +export const updateDescribeText = (text: string, describeId: string) => ({ + type: actionTypes.UPDATE_DESCRIBE_TEXT, + text, + describeId, +}); + +export const updateDescribeOrder = (reorderedDescribe: Array) => ({ + type: actionTypes.UPDATE_DESCRIBE_ORDER, + reorderedDescribe, +}); + +export const updateDescribeStandardTag = (describeId: string, standardTag: string) => ({ + type: actionTypes.UPDATE_DESCRIBE_STANDARD_TAG, + describeId, + standardTag, +}); + +export const addItStatement = (describeId: string) => ({ + type: actionTypes.ADD_ITSTATEMENT, + describeId, +}); + +export const deleteItStatement = (describeId: string, itId: string) => ({ + type: actionTypes.DELETE_ITSTATEMENT, + describeId, + itId, +}); + +export const updateItStatementText = (text: string, itId: string) => ({ + type: actionTypes.UPDATE_ITSTATEMENT_TEXT, + itId, + text, +}); + +export const updateItStatementOrder = (reorderedIt: Array, describeId: string) => ({ + type: actionTypes.UPDATE_ITSTATEMENT_ORDER, + reorderedIt, + describeId, +}); + +export const updateItCatTag = (itId: string, catTag: string) => ({ + type: actionTypes.UPDATE_IT_CAT_TAG, + itId, + catTag, +}); + +export const createNewTest = () => ({ + type: actionTypes.CREATE_NEW_TEST, +}); + +export const openInfoModal = () => ({ + type: actionTypes.OPEN_INFO_MODAL, +}); + +export const closeInfoModal = () => ({ + type: actionTypes.CLOSE_INFO_MODAL, +}); + +export const updateFilePath = (fileName: string, filePath: string) => ({ + type: actionTypes.UPDATE_FILE_PATH, + fileName, + filePath, +}); + +export const updateTestType = (testType: string) => ({ + type: actionTypes.UPDATE_TEST_TYPE, + testType, +}); + +export const createPuppeteerUrl = (puppeteerUrl: string) => ({ + type: actionTypes.CREATE_PUPPETEER_URL, + puppeteerUrl, +}); diff --git a/src/context/actions/globalActions.js b/src/context/actions/globalActions.js index bb4c2883..f7dd40ef 100644 --- a/src/context/actions/globalActions.js +++ b/src/context/actions/globalActions.js @@ -13,10 +13,10 @@ export const actionTypes = { //added SET_TEST_CASE: 'SET_TEST_CASE', TOGGLE_MODAL: 'TOGGLE_MODAL', - UPDATE_FILE_SHOW: 'UPDATE_FILE_SHOW', + UPDATE_FILE: 'UPDATE_FILE', OPEN_BROWSER_DOCS: 'OPEN_BROWSER_DOCS', - NEW_TEST_CLOSE_BROWSER_DOCS: 'NEW_TEST_CLOSE_BROWSER_DOCS', - EXPORT: 'EXPORT', + RESET_TO_PROJECT_URL: 'RESET_TO_PROJECT_URL', // formerly NEW_TEST_CLOSE_BROWSER_DOCS + TOGGLE_EXPORT_BOOL: 'TOGGLE_EXPORT_BOOL', SET_FILE_PATH: 'SET_FILE_PATH', SET_VALID_CODE: 'SET_VALID_CODE', }; @@ -80,7 +80,7 @@ export const toggleModal = () => ({ }); export const updateFile = (testString) => ({ - type: actionTypes.UPDATE_FILE_SHOW, + type: actionTypes.UPDATE_FILE, testString, }); @@ -89,8 +89,12 @@ export const openBrowserDocs = (docsUrl) => ({ docsUrl, }); +export const resetToProjectUrl = () => ({ + type: actionTypes.RESET_TO_PROJECT_URL, +}); + export const toggleExportBool = () => ({ - type: actionTypes.EXPORT, + type: actionTypes.TOGGLE_EXPORT_BOOL, }); export const setFilePath = (filePath) => ({ @@ -98,11 +102,7 @@ export const setFilePath = (filePath) => ({ filePath, }); -export const resetToProjectUrl = () => ({ - type: actionTypes.NEW_TEST_CLOSE_BROWSER_DOCS, -}); - -export const setValidCode = (verdict) => ({ +export const setValidCode = (validCode) => ({ type: actionTypes.SET_VALID_CODE, - validCode: verdict, + validCode, }); diff --git a/src/context/actions/reactTestCaseActions.js b/src/context/actions/reactTestCaseActions.js index f1e462ef..1a449042 100644 --- a/src/context/actions/reactTestCaseActions.js +++ b/src/context/actions/reactTestCaseActions.js @@ -1,15 +1,15 @@ /* ------------------------------ Action Types ------------------------------ */ export const actionTypes = { - TOGGLE_REACT: 'TOGGLE_REACT', - ADD_DESCRIBE_BLOCK: 'ADD_DESCRIBE_BLOCK', DELETE_DESCRIBE_BLOCK: 'DELETE_DESCRIBE_BLOCK', + UPDATE_DESCRIBE_TEXT: 'UPDATE_DESCRIBE_TEXT', + UPDATE_DESCRIBE_ORDER: 'UPDATE_DESCRIBE_ORDER', ADD_ITSTATEMENT: 'ADD_ITSTATEMENT', - - UPDATE_STATEMENTS_ORDER: 'UPDATE_STATEMENTS_ORDER', - UPDATE_DESCRIBE_TEXT: 'UPDATE_DESCRIBE_TEXT', + DELETE_ITSTATEMENT: 'DELETE_ITSTATEMENT', + UPDATE_ITSTATEMENT_TEXT: 'UPDATE_ITSTATEMENT_TEXT', + UPDATE_ITSTATEMENT_ORDER: 'UPDATE_ITSTATEMENT_ORDER', ADD_ACTION: 'ADD_ACTION', DELETE_ACTION: 'DELETE_ACTION', @@ -27,9 +27,6 @@ export const actionTypes = { DELETE_PROP: 'DELETE_PROP', UPDATE_PROP: 'UPDATE_PROP', - UPDATE_ITSTATEMENT_TEXT: 'UPDATE_ITSTATEMENT_TEXT', - DELETE_ITSTATEMENT: 'DELETE_ITSTATEMENT', - CREATE_NEW_TEST: 'CREATE_NEW_TEST', OPEN_INFO_MODAL: 'OPEN_INFO_MODAL', CLOSE_INFO_MODAL: 'CLOSE_INFO_MODAL', @@ -50,6 +47,18 @@ export const deleteDescribeBlock = (describeId) => { }; }; +export const updateDescribeText = (text, describeId) => ({ + type: actionTypes.UPDATE_DESCRIBE_TEXT, + text, + describeId, +}); + +export const updateDescribeOrder = (reorderedDescribe) => ({ + type: actionTypes.UPDATE_DESCRIBE_ORDER, + reorderedDescribe, +}); + + export const addItstatement = (describeId) => ({ type: actionTypes.ADD_ITSTATEMENT, describeId, @@ -61,21 +70,19 @@ export const deleteItStatement = (describeId, itId) => ({ itId, }); -export const toggleReact = () => ({ - type: actionTypes.TOGGLE_REACT, -}); - -export const updateStatementsOrder = (draggableStatements) => ({ - type: actionTypes.UPDATE_STATEMENTS_ORDER, - draggableStatements, +export const updateItStatementText = (text, itId) => ({ + type: actionTypes.UPDATE_ITSTATEMENT_TEXT, + itId, + text, }); -export const updateDescribeText = (text, describeId) => ({ - type: actionTypes.UPDATE_DESCRIBE_TEXT, - text, +export const updateItStatementOrder = (reorderedIt, describeId) => ({ + type: actionTypes.UPDATE_ITSTATEMENT_ORDER, + reorderedIt, describeId, }); + export const addAction = (describeId, itId) => ({ type: actionTypes.ADD_ACTION, describeId, @@ -106,6 +113,7 @@ export const updateAction = ({ suggestions, }); + export const addAssertion = (describeId, itId) => ({ type: actionTypes.ADD_ASSERTION, describeId, @@ -138,6 +146,7 @@ export const updateAssertion = ({ suggestions, }); + export const addRender = (describeId, itId) => ({ type: actionTypes.ADD_RENDER, describeId, @@ -155,6 +164,7 @@ export const updateRenderComponent = (componentName, filePath) => ({ filePath, }); + export const addProp = (statementId) => ({ type: actionTypes.ADD_PROP, statementId, @@ -176,11 +186,6 @@ export const updateProp = (statementId, id, propKey, propValue) => ({ propValue, }); -export const updateItStatementText = (text, itId) => ({ - type: actionTypes.UPDATE_ITSTATEMENT_TEXT, - itId, - text, -}); export const createNewTest = () => ({ type: actionTypes.CREATE_NEW_TEST, diff --git a/src/context/reducers/accTestCaseReducer.ts b/src/context/reducers/accTestCaseReducer.ts new file mode 100644 index 00000000..51fc807f --- /dev/null +++ b/src/context/reducers/accTestCaseReducer.ts @@ -0,0 +1,304 @@ +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, + propId: 1, + describeBlocks: { + byId: { + describe0: { + id: 'describe0', + text: 'Component is accessible according to all standards enforced by axe-core.', + standardTag: 'none', + }, + }, + allIds: ['describe0'], + }, + itStatements: { + byId: { + it0: { + id: 'it0', + describeId: 'describe0', + text: 'Component is accessible regarding all axe-core categories.', + catTag: 'none', + }, + }, + allIds: { + describe0: ['it0'], + }, + }, + fileName: '', + filePath: '', + testType: 'html', + puppeteerUrl: 'sample.io', +}; + +/* ---------------------------- Helper Functions ---------------------------- */ + +const createDescribeBlock = (describeId: string) => { + return { + id: describeId, + text: 'Component is accessible according to all standards enforced by axe-core.', + standardTag: 'none', + }; +}; + +const createItStatement = (describeId: string, itId: string) => ({ + id: itId, + describeId, + text: 'Component is accessible regarding all axe-core categories.', + catTag: 'none', +}); + +/* ------------------------- Accessibility Test Case Reducer ------------------------ */ + +export const accTestCaseReducer = (state: AccTestCaseState, action: Action) => { + Object.freeze(state); + + let describeBlocks; + let itStatements; + + if (state && action) { + describeBlocks = { ...state.describeBlocks }; + itStatements = { ...state.itStatements }; + } + + switch (action.type) { + case actionTypes.ADD_DESCRIBE_BLOCK: { + let updatedDescribeId = state.describeId; + const describeId = `describe${state.describeId}`; + + return { + ...state, + describeId: ++updatedDescribeId, + describeBlocks: { + ...describeBlocks, + byId: { + ...describeBlocks.byId, + [describeId]: createDescribeBlock(describeId), + }, + allIds: [...(describeBlocks.allIds || []), describeId], + }, + itStatements: { + ...itStatements, + allIds: { + ...itStatements.allIds, + [describeId]: [], + }, + }, + }; + } + case actionTypes.DELETE_DESCRIBE_BLOCK: { + const { describeId } = action; + const newDescById = { ...describeBlocks.byId }; + const newItById = { ...itStatements.byId }; + const newItAllIds = { ...itStatements.allIds }; + + // delete it from describeBlocks.byId + delete newDescById[describeId]; + // delete it from describeBlocks.allIds + const newDescAllIds = describeBlocks.allIds.filter((id: string) => id !== describeId); + + // delete from itStatements.byId + itStatements.allIds[describeId].forEach((itId: number) => { + delete newItById[itId]; + }); + // delete from itStatements.allIds + delete newItAllIds[describeId]; + + return { + ...state, + describeBlocks: { + byId: newDescById, + allIds: newDescAllIds, + }, + itStatements: { + byId: newItById, + allIds: newItAllIds, + }, + }; + } + case actionTypes.UPDATE_DESCRIBE_TEXT: { + const { describeId, text } = action; + const byIds = { ...describeBlocks.byId }; + const block = { ...describeBlocks.byId[describeId] }; + return { + ...state, + describeBlocks: { + ...describeBlocks, + byId: { + ...byIds, + [describeId]: { + ...block, + text, + }, + }, + }, + }; + } + case actionTypes.UPDATE_DESCRIBE_ORDER: { + const { reorderedDescribe } = action; + + return { + ...state, + describeBlocks: { + ...describeBlocks, + allIds: reorderedDescribe, + }, + }; + } + case actionTypes.UPDATE_DESCRIBE_STANDARD_TAG: { + const { describeId, standardTag } = action; + + return { + ...state, + describeBlocks: { + ...describeBlocks, + byId: { + ...describeBlocks.byId, + [describeId]: { + ...describeBlocks.byId[describeId], + standardTag, + }, + }, + }, + }; + } + case actionTypes.ADD_ITSTATEMENT: { + const { describeId } = action; + const itId = `it${state.itId}`; + let updatedItId = state.itId; + + return { + ...state, + itId: ++updatedItId, + itStatements: { + ...itStatements, + byId: { + ...itStatements.byId, + [itId]: createItStatement(describeId, itId), + }, + allIds: { + ...itStatements.allIds, + [describeId]: [...(itStatements.allIds[describeId]), itId], + }, + }, + }; + } + case actionTypes.DELETE_ITSTATEMENT: { + const { itId, describeId } = action; + const byId = { ...itStatements.byId }; + delete byId[itId]; + const newAllIds = itStatements.allIds[describeId].filter((id: number) => id !== itId); + + return { + ...state, + itStatements: { + ...itStatements, + byId: { + ...byId, + }, + allIds: { + ...itStatements.allIds, + [describeId]: [...newAllIds], + }, + }, + }; + } + case actionTypes.UPDATE_ITSTATEMENT_TEXT: { + const { itId, text } = action; + const byId = { ...itStatements.byId }; + const block = { ...itStatements.byId[itId] }; + return { + ...state, + itStatements: { + ...itStatements, + byId: { + ...byId, + [itId]: { + ...block, + text, + }, + }, + }, + }; + } + case actionTypes.UPDATE_ITSTATEMENT_ORDER: { + const { reorderedIt, describeId } = action; + + return { + ...state, + itStatements: { + ...itStatements, + allIds: { + ...itStatements.allIds, + [describeId]: reorderedIt, + }, + }, + }; + } + case actionTypes.UPDATE_IT_CAT_TAG: { + const { itId, catTag } = action; + + return { + ...state, + itStatements: { + ...itStatements, + byId: { + ...itStatements.byId, + [itId]: { + ...itStatements.byId[itId], + catTag, + }, + }, + }, + }; + } + case actionTypes.CREATE_NEW_TEST: { + return { ...accTestCaseState }; + } + case actionTypes.OPEN_INFO_MODAL: { + return { + ...state, + modalOpen: true, + }; + } + case actionTypes.CLOSE_INFO_MODAL: { + return { + ...state, + modalOpen: false, + }; + } + case actionTypes.UPDATE_FILE_PATH: { + const { fileName, filePath } = action; + return { + ...state, + fileName, + filePath, + }; + } + case actionTypes.UPDATE_TEST_TYPE: { + const { testType } = action; + return { + ...state, + testType, + }; + } + case actionTypes.CREATE_PUPPETEER_URL: { + const { puppeteerUrl } = action; + return { + ...state, + puppeteerUrl, + }; + } + default: + return state; + } +}; diff --git a/src/context/reducers/endpointTestCaseReducer.ts b/src/context/reducers/endpointTestCaseReducer.ts index 362b0d79..c04e4e9b 100644 --- a/src/context/reducers/endpointTestCaseReducer.ts +++ b/src/context/reducers/endpointTestCaseReducer.ts @@ -66,7 +66,7 @@ const deepCopy = (endpointStatements: EndpointObj[]) => { export const endpointTestCaseReducer = (state: EndpointTestCaseState, action: Action) => { Object.freeze(state); - let endpointStatements = [...state.endpointStatements]; + let endpointStatements: Array = [...state.endpointStatements]; switch (action.type) { case actionTypes.ADD_ENDPOINT: @@ -106,7 +106,7 @@ export const endpointTestCaseReducer = (state: EndpointTestCaseState, action: Ac endpointStatements, }; case actionTypes.UPDATE_SERVER_FILEPATH: - const { serverFilePath, serverFileName } = action; + const { serverFilePath, serverFileName } = action; return { ...state, serverFilePath, diff --git a/src/context/reducers/globalReducer.js b/src/context/reducers/globalReducer.js index 705f2013..040301cb 100644 --- a/src/context/reducers/globalReducer.js +++ b/src/context/reducers/globalReducer.js @@ -37,9 +37,10 @@ export const globalReducer = (state, action) => { projectUrl: url, }; case actionTypes.LOAD_PROJECT: + const isProjectLoaded = action.load; return { ...state, - isProjectLoaded: action.load, + isProjectLoaded, }; case actionTypes.CREATE_FILE_TREE: const fileTree = action.fileTree; @@ -49,10 +50,9 @@ export const globalReducer = (state, action) => { }; case actionTypes.TOGGLE_FILE_DIRECTORY: - const isFileDirectoryOpen = !state.isFileDirectoryOpen; return { ...state, - isFileDirectoryOpen, + isFileDirectoryOpen: !state.isFileDirectoryOpen, }; case actionTypes.CLOSE_RIGHT_PANEL: const projUrl = state.projectUrl; @@ -111,11 +111,11 @@ export const globalReducer = (state, action) => { isTestModalOpen: !state.isTestModalOpen, }; // - case actionTypes.UPDATE_FILE_SHOW: - const updatedFile = action.testString; + case actionTypes.UPDATE_FILE: + const file = action.testString; return { ...state, - file: updatedFile, + file, }; case actionTypes.OPEN_BROWSER_DOCS: const docsUrl = action.docsUrl; @@ -125,18 +125,18 @@ export const globalReducer = (state, action) => { isRightPanelOpen: true, rightPanelDisplay: 'browserView', }; - case actionTypes.NEW_TEST_CLOSE_BROWSER_DOCS: + case actionTypes.RESET_TO_PROJECT_URL: + // formerly NEW_TEST_CLOSE_BROWSER_DOCS const urlReset = state.projectUrl; return { ...state, url: urlReset, projectUrl: urlReset, }; - case actionTypes.EXPORT: - let exportBool = !state.exportBool; + case actionTypes.TOGGLE_EXPORT_BOOL: return { ...state, - exportBool, + exportBool: !state.exportBool, }; case actionTypes.SET_FILE_PATH: const filePath = action.filePath; @@ -145,9 +145,10 @@ export const globalReducer = (state, action) => { filePath, }; case actionTypes.SET_VALID_CODE: + const validCode = action.validCode; return { ...state, - validCode: action.validCode, + validCode, }; default: return state; diff --git a/src/context/reducers/hooksTestCaseReducer.ts b/src/context/reducers/hooksTestCaseReducer.ts index 4a92531a..6eb79a92 100644 --- a/src/context/reducers/hooksTestCaseReducer.ts +++ b/src/context/reducers/hooksTestCaseReducer.ts @@ -1,6 +1,6 @@ import { createContext } from 'react'; // import { actionTypes } from '../actions/hooksTestCaseActions'; -import { HooksTestCaseState, Assertion, Action, Hooks, Callback } from '../../utils/hooksTypes'; +import { HooksTestCaseState, Assertion, HooksAction, Hooks, Callback } from '../../utils/hooksTypes'; export const HooksTestCaseContext: any = createContext(null); @@ -62,6 +62,8 @@ export const hooksTestCaseState: HooksTestCaseState = { hooksTestStatement: '', hooksStatements: [], statementId: 0, + hookFileName: '', + hookFilePath: '', }; // const createContexts = (statementId: number) => ({ @@ -106,7 +108,7 @@ const deepCopy = (hooksStatements: Hooks[]) => { return fullCopy; }; -export const hooksTestCaseReducer = (state: HooksTestCaseState, action: Action) => { +export const hooksTestCaseReducer = (state: HooksTestCaseState, action: HooksAction) => { Object.freeze(state); let hooksStatements = [...state.hooksStatements]; diff --git a/src/context/reducers/reactTestCaseReducer.js b/src/context/reducers/reactTestCaseReducer.js index 43b8781a..3893e6e2 100644 --- a/src/context/reducers/reactTestCaseReducer.js +++ b/src/context/reducers/reactTestCaseReducer.js @@ -27,7 +27,9 @@ export const reactTestCaseState = { text: '', }, }, - allIds: ['it0'], + allIds: { + describe0: ['it0'], + }, }, statements: { byId: { @@ -102,14 +104,26 @@ const createProp = (propId, statementId) => ({ propValue: '', }); -const deleteChildren = (object, deletionId, lookup) => { - const allIdCopy = object.allIds.filter((id) => object.byId[id][lookup] !== deletionId); - - object.allIds.forEach((id) => { - if (object.byId[id][lookup] === deletionId) { +const deleteChildren = (object, deletionId, lookup, it) => { + let allIdCopy; + if (it) { + // delete everything appropriate in itStatements.byId object + object.allIds[deletionId].forEach((id) => { delete object.byId[id]; - } - }); + }); + // delete everything appropriate in itStatements.allIds object + delete object.allIds[deletionId]; + allIdCopy = object.allIds; + } else { + // use .filter to delete from statements.allIds array + allIdCopy = object.allIds.filter((id) => object.byId[id][lookup] !== deletionId); + // delete from statements.byId object + object.allIds.forEach((id) => { + if (object.byId[id][lookup] === deletionId) { + delete object.byId[id]; + } + }); + } return allIdCopy; }; @@ -145,22 +159,11 @@ export const reactTestCaseReducer = (state, action) => { }, allIds: [...(describeBlocks.allIds || []), describeId], }, - }; - } - case actionTypes.UPDATE_DESCRIBE_TEXT: { - const { describeId, text } = action; - const byIds = { ...describeBlocks.byId }; - const block = { ...describeBlocks.byId[describeId] }; - return { - ...state, - describeBlocks: { - ...describeBlocks, - byId: { - ...byIds, - [describeId]: { - ...block, - text, - }, + itStatements: { + ...itStatements, + allIds: { + ...itStatements.allIds, + [describeId]: [], }, }, }; @@ -171,7 +174,7 @@ export const reactTestCaseReducer = (state, action) => { delete byId[describeId]; const allIds = describeBlocks.allIds.filter((id) => id !== describeId); - const itStatementAllIds = deleteChildren(itStatements, describeId, 'describeId'); + const itStatementAllIds = deleteChildren(itStatements, describeId, 'describeId', 'it'); const statementAllIds = deleteChildren(statements, describeId, 'describeId'); return { @@ -188,7 +191,7 @@ export const reactTestCaseReducer = (state, action) => { byId: { ...itStatements.byId, }, - allIds: [...itStatementAllIds], + allIds: itStatementAllIds, }, statements: { ...statements, @@ -199,6 +202,34 @@ export const reactTestCaseReducer = (state, action) => { }, }; } + case actionTypes.UPDATE_DESCRIBE_TEXT: { + const { describeId, text } = action; + const byIds = { ...describeBlocks.byId }; + const block = { ...describeBlocks.byId[describeId] }; + return { + ...state, + describeBlocks: { + ...describeBlocks, + byId: { + ...byIds, + [describeId]: { + ...block, + text, + }, + }, + }, + }; + } + case actionTypes.UPDATE_DESCRIBE_ORDER: { + const { reorderedDescribe } = action; + return { + ...state, + describeBlocks: { + ...describeBlocks, + allIds: reorderedDescribe, + }, + }; + } case actionTypes.ADD_ITSTATEMENT: { const { describeId } = action; @@ -214,7 +245,10 @@ export const reactTestCaseReducer = (state, action) => { ...itStatements.byId, [itId]: createItStatement(describeId, itId), }, - allIds: [...(itStatements.allIds || []), itId], + allIds: { + ...itStatements.allIds, + [describeId]: [...itStatements.allIds[describeId], itId], + }, }, }; } @@ -238,9 +272,10 @@ export const reactTestCaseReducer = (state, action) => { } case actionTypes.DELETE_ITSTATEMENT: { const { itId } = action; + const { describeId } = itStatements.byId[itId]; const byId = { ...itStatements.byId }; delete byId[itId]; - const allIds = itStatements.allIds.filter((id) => id !== itId); + const allIds = itStatements.allIds[describeId].filter((id) => id !== itId); const statementAllIds = deleteChildren(statements, itId, 'itId'); return { @@ -250,7 +285,10 @@ export const reactTestCaseReducer = (state, action) => { byId: { ...byId, }, - allIds: [...allIds], + allIds: { + ...itStatements.allIds, + [describeId]: [...allIds], + }, }, statements: { ...statements, @@ -261,6 +299,20 @@ export const reactTestCaseReducer = (state, action) => { }, }; } + case actionTypes.UPDATE_ITSTATEMENT_ORDER: { + const { reorderedIt, describeId } = action; + return { + ...state, + itStatements: { + ...itStatements, + allIds: { + ...itStatements.allIds, + [describeId]: reorderedIt, + }, + }, + }; + } + case actionTypes.ADD_ACTION: { const { describeId, itId } = action; const byIds = { ...statements.byId }; @@ -281,6 +333,22 @@ export const reactTestCaseReducer = (state, action) => { }, }; } + case actionTypes.DELETE_ACTION: { + const { statementId } = action; + const byId = { ...statements.byId }; + delete byId[statementId]; + const allIds = [...statements.allIds].filter((statement) => statement !== statementId); + return { + ...state, + statements: { + ...statements, + byId: { + ...byId, + }, + allIds: [...allIds], + }, + }; + } case actionTypes.UPDATE_ACTION: { const { id, @@ -315,22 +383,7 @@ export const reactTestCaseReducer = (state, action) => { }, }; } - case actionTypes.DELETE_ACTION: { - const { statementId } = action; - const byId = { ...statements.byId }; - delete byId[statementId]; - const allIds = [...statements.allIds].filter((statement) => statement !== statementId); - return { - ...state, - statements: { - ...statements, - byId: { - ...byId, - }, - allIds: [...allIds], - }, - }; - } + case actionTypes.ADD_ASSERTION: { const { describeId, itId } = action; const byIds = { ...statements.byId }; @@ -351,6 +404,22 @@ export const reactTestCaseReducer = (state, action) => { }, }; } + case actionTypes.DELETE_ASSERTION: { + const { statementId } = action; + const byId = { ...statements.byId }; + delete byId[statementId]; + const allIds = [...statements.allIds].filter((statement) => statement !== statementId); + return { + ...state, + statements: { + ...statements, + byId: { + ...byId, + }, + allIds: [...allIds], + }, + }; + } case actionTypes.UPDATE_ASSERTION: { const { id, @@ -387,22 +456,7 @@ export const reactTestCaseReducer = (state, action) => { }, }; } - case actionTypes.DELETE_ASSERTION: { - const { statementId } = action; - const byId = { ...statements.byId }; - delete byId[statementId]; - const allIds = [...statements.allIds].filter((statement) => statement !== statementId); - return { - ...state, - statements: { - ...statements, - byId: { - ...byId, - }, - allIds: [...allIds], - }, - }; - } + case actionTypes.ADD_RENDER: { const { describeId, itId } = action; const byIds = { ...statements.byId }; @@ -423,15 +477,6 @@ export const reactTestCaseReducer = (state, action) => { }, }; } - case actionTypes.UPDATE_RENDER_COMPONENT: { - const { componentName, filePath } = action; - statements.componentName = componentName; - statements.componentPath = filePath; - return { - ...state, - statements, - }; - } case actionTypes.DELETE_RENDER: { const { statementId } = action; const byId = { ...statements.byId }; @@ -448,6 +493,16 @@ export const reactTestCaseReducer = (state, action) => { }, }; } + case actionTypes.UPDATE_RENDER_COMPONENT: { + const { componentName, filePath } = action; + statements.componentName = componentName; + statements.componentPath = filePath; + return { + ...state, + statements, + }; + } + case actionTypes.ADD_PROP: { const { statementId } = action; const propId = `prop${state.propId}`; @@ -469,17 +524,9 @@ export const reactTestCaseReducer = (state, action) => { }, }; } - case actionTypes.UPDATE_PROP: { - const { id, statementId, propKey, propValue } = action; - const updatedProps = [...statements.byId[statementId].props]; - - updatedProps.forEach((prop) => { - if (prop.id === id) { - prop.propKey = propKey; - prop.propValue = propValue; - } - }); - + case actionTypes.DELETE_PROP: { + const { id, statementId } = action; + const props = statements.byId[statementId].props.filter((prop) => prop.id !== id); return { ...state, statements: { @@ -488,15 +535,23 @@ export const reactTestCaseReducer = (state, action) => { ...statements.byId, [statementId]: { ...statements.byId[statementId], - props: updatedProps, + props, }, }, }, }; } - case actionTypes.DELETE_PROP: { - const { id, statementId } = action; - const props = statements.byId[statementId].props.filter((prop) => prop.id !== id); + case actionTypes.UPDATE_PROP: { + const { id, statementId, propKey, propValue } = action; + const updatedProps = [...statements.byId[statementId].props]; + + updatedProps.forEach((prop) => { + if (prop.id === id) { + prop.propKey = propKey; + prop.propValue = propValue; + } + }); + return { ...state, statements: { @@ -505,12 +560,13 @@ export const reactTestCaseReducer = (state, action) => { ...statements.byId, [statementId]: { ...statements.byId[statementId], - props, + props: updatedProps, }, }, }, }; } + case actionTypes.CREATE_NEW_TEST: { return { ...state, @@ -520,7 +576,7 @@ export const reactTestCaseReducer = (state, action) => { }, itStatements: { byId: {}, - allIds: [], + allIds: {}, }, statements: { byId: {}, @@ -528,16 +584,18 @@ export const reactTestCaseReducer = (state, action) => { }, }; } - case actionTypes.OPEN_INFO_MODAL: + case actionTypes.OPEN_INFO_MODAL: { return { ...state, modalOpen: true, }; - case actionTypes.CLOSE_INFO_MODAL: + } + case actionTypes.CLOSE_INFO_MODAL: { return { ...state, modalOpen: false, }; + } default: return state; } diff --git a/src/context/useGenerateTest.jsx b/src/context/useGenerateTest.jsx index 93bd4839..9649f394 100644 --- a/src/context/useGenerateTest.jsx +++ b/src/context/useGenerateTest.jsx @@ -1,3 +1,4 @@ +/* eslint-disable */ const remote = window.require('electron').remote; const fs = remote.require('fs'); const path = remote.require('path'); @@ -38,13 +39,10 @@ function useGenerateTest(test, projectFilePath) { // React It Statements const addReactItStatement = (describeId) => { const itStatements = reactTestCase.itStatements; - itStatements.allIds.forEach((itId) => { - if (itStatements.byId[itId].describeId === describeId) { - testFileCode += `it('${itStatements.byId[itId].text}', () => {`; - addReactStatements(itId); - testFileCode += '})'; - } - testFileCode += '\n'; + itStatements.allIds[describeId].forEach((itId) => { + testFileCode += `it('${itStatements.byId[itId].text}', () => {`; + addReactStatements(itId); + testFileCode += '})\n'; }); }; @@ -787,7 +785,301 @@ function useGenerateTest(test, projectFilePath) { `; }; + /* ------------------------------------------ ACCESSIBILITY TESTING ------------------------------------------ */ + + const addAccImportStatements = () => { + let { filePath, fileName } = accTestCase; + filePath = path.relative(projectFilePath, filePath); + filePath = filePath.replace(/\\/g, '/'); + + testFileCode += ` + const axe = require('axe-core'); + const regeneratorRuntime = require('regenerator-runtime');`; + + if (accTestCase.testType === 'html') { + testFileCode += ` + const path = require('path'); + const fs = require('fs'); + + const html = fs.readFileSync(path.resolve(__dirname, + '../${filePath}'), 'utf8');`; + } else if (accTestCase.testType === 'react') { + testFileCode += ` + import React from 'react'; + import { configure, mount } from 'enzyme'; + import Adapter from 'enzyme-adapter-react-16'; + + import ${fileName.split('.')[0]} from '../${filePath}';`; + } + }; + + const addAccDescribeBlocks = () => { + const { describeBlocks } = accTestCase; + + describeBlocks.allIds.forEach((id) => { + testFileCode += ` + + describe('${describeBlocks.byId[id].text}', () => {`; + addAccPrint(); + if (accTestCase.testType === 'react') addMount(); + addAccBeforeAll(id); + addAccItStatements(id); + testFileCode += `}); \n \n`; + }); + }; + + const addAccPrint = () => { + testFileCode += ` + const print = (violations) => { + if (violations.length === 0) { + console.log('Congrats! Keep up the good work, you have 0 known violations!'); + } else { + violations.forEach(axeViolation => { + const whereItFailed = axeViolation.nodes.map(node => node.html); + // uncomment the line(s) below to see suggestions on how to fix accessibility issues + // const failureSummary = axeViolation.nodes.map(node => node.failureSummary); + + const { description, help, helpUrl } = axeViolation; + + console.log('---------', + '\\nTEST DESCRIPTION: ', description, + '\\nISSUE: ', help, + '\\nMORE INFO: ', helpUrl, + '\\nWHERE IT FAILED: ', whereItFailed, + // '\\nHOW TO FIX: ', failureSummary + ); + }); + } + } + `; + } + + const addMount = () => { + testFileCode += ` + const mountToDoc = (reactElm) => { + configure({ adapter: new Adapter() }); + if (!document) { + // Set up a basic DOM + global.document = jsdom(''); + } + if (!wrapper) { + wrapper = document.createElement('main'); + document.body.appendChild(wrapper); + } + + const container = mount(reactElm); + wrapper.innerHTML = ''; + wrapper.appendChild(container.getDOMNode()); + return container; + } + `; + } + + const addAccBeforeAll = (descId) => { + const { fileName } = accTestCase; + testFileCode += ` + let options;`; + + if (accTestCase.testType === 'react') { + testFileCode += ` + let linkNode; + let wrapper;`; + } + + testFileCode += `\n + beforeAll((done) => { + // exclude tests that are incompatible + options = { + rules: { + 'color-contrast': { enabled: false }, + 'link-in-text-block': { enabled: false }, + },` + + if (accTestCase.describeBlocks.byId[descId].standardTag !== 'none') { + testFileCode += ` + runOnly: { + type: 'tag', + value: ['${accTestCase.describeBlocks.byId[descId].standardTag}'] + }` + } + + testFileCode += ` + }; + `; + + if (accTestCase.testType === 'html') { + testFileCode += ` + // get language tag from imported html file and assign to jsdom document + const langTag = html.match(/ + ); + linkNode = linkComponent.getDOMNode(); + `; + } + + testFileCode += ` + done(); + }); + `; + } + + const addAccItStatements = (descId) => { + const { itStatements } = accTestCase; + + itStatements.allIds[descId].forEach((itId) => { + testFileCode += ` + it('${itStatements.byId[itId].text}', (done) => {` + + if(itStatements.byId[itId].catTag !== 'none') { + testFileCode += ` + options.runOnly.value.push('cat.${itStatements.byId[itId].catTag}')` + } + + if (accTestCase.testType === 'react') { + testFileCode += ` + axe.run(linkNode, options, async (err, results) => {` + } else { + testFileCode += ` + axe.run(options, async (err, results) => {` + } + + testFileCode += ` + if (err) { + console.log('err: ', err); + done(); + } + + print(results.violations); + + expect(err).toBe(null); + expect(results.violations).toHaveLength(0); + done(); + }); + }) + `; + }); + }; + + const addAccPuppeteer = () => { + testFileCode += ` + const puppeteer = require('puppeteer'); + const axeCore = require('axe-core'); + const { parse: parseURL } = require('url'); + const assert = require('assert'); + + // Cheap URL validation + const isValidURL = input => { + const u = parseURL(input); + return u.protocol && u.host; + }; + + // node axe-puppeteer.js + const url = process.argv[2]; + assert(isValidURL(url), 'Invalid URL'); + + const main = async url => { + let browser; + let results; + try { + // Setup Puppeteer + browser = await puppeteer.launch(); + + // Get new page + const page = await browser.newPage(); + await page.goto(url); + + // Inject and run axe-core + const handle = await page.evaluateHandle(\` + // Inject axe source code + \${axeCore.source} + // Run axe + axe.run(); + \`); + + // Get the results from 'axe.run()'. + results = await handle.jsonValue(); + // Destroy the handle & return axe results. + await handle.dispose(); + } catch (err) { + // Ensure we close the puppeteer connection when possible + if (browser) { + await browser.close(); + } + + // Re-throw + throw err; + } + + // test violations + results = results.violations; + console.log('--------------violations length: ', results.length); + + await browser.close(); + return results; + }; + + main(url) + .then(results => { + // console.log(results); + const violations = results; + violations.forEach(axeViolation => { + const whereItFailed = axeViolation.nodes.map(node => node.html); + // uncomment the line(s) below to see suggestions on how to fix accessibility issues + // const failureSummary = axeViolation.nodes.map(node => node.failureSummary); + + const { description, help, helpUrl } = axeViolation; + + console.log( + '---------', + '\\nTEST DESCRIPTION: ', + description, + '\\nISSUE: ', + help, + '\\nMORE INFO: ', + helpUrl, + '\\nWHERE IT FAILED: ', + whereItFailed + // '\\nHOW TO FIX: ', failureSummary + ); + }); + }) + .catch(err => { + console.error('Error running axe-core:', err.message); + process.exit(1); + }); + ` + }; + switch (test) { + case 'acc': + var accTestCase = testState; + if (accTestCase.testType === 'puppeteer') { + return ( + addAccPuppeteer(), + (testFileCode = beautify(testFileCode, { + indent_size: 2, + space_in_empty_paren: true, + e4x: true, + })) + ) + } else { + return ( + addAccImportStatements(), + addAccDescribeBlocks(), + (testFileCode = beautify(testFileCode, { + indent_size: 2, + space_in_empty_paren: true, + e4x: true, + })) + ); + } + case 'react': var reactTestCase = testState; var mockData = mockDataState; diff --git a/src/pages/About/About.tsx b/src/pages/About/About.tsx index c3583a18..ad014ab1 100644 --- a/src/pages/About/About.tsx +++ b/src/pages/About/About.tsx @@ -5,6 +5,7 @@ import { Action } from '../../utils/endpointTypes'; const image1 = require('../../assets/images/newReact.png'); const image2 = require('../../assets/images/testfile.png'); +const image3 = require('../../assets/images/spearmintHomepage.png'); const About = ({ dispatch: dispatchToGlobal }: { dispatch: (action: Action) => void }) => { const handleChangeBack = () => { @@ -17,11 +18,11 @@ const About = ({ dispatch: dispatchToGlobal }: { dispatch: (action: Action) => v

- Spearmint helps developers easily create functional React/Redux/Endpoint/Paint Timing + Spearmint helps developers easily create functional React/Redux/Endpoint/Paint Timing/Accessibility tests without writing any code. It dynamically converts user inputs into executable Jest test code by using DOM query selectors provided by @testing-library.

@@ -61,6 +62,30 @@ const About = ({ dispatch: dispatchToGlobal }: { dispatch: (action: Action) => v
           npm i -D jest puppeteer
         
+ + +

+ To run Accessibility tests generated by spearmint on HTML, install the following in your dev dependencies. +

+
+          npm i -D axe-core regenerator-runtime jest
+        
+ +

+ To run Accessibility tests generated by spearmint on React Components, install the following in your dev dependencies. +

+
+          npm i -D axe-core regenerator-runtime jest enzyme enzyme-adapter-react-16
+        
+ +

+ 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
+        
+ +

How it works

  1. @@ -70,8 +95,8 @@ const About = ({ dispatch: dispatchToGlobal }: { dispatch: (action: Action) => v

    @@ -83,7 +108,7 @@ const About = ({ dispatch: dispatchToGlobal }: { dispatch: (action: Action) => v

- + React test generation page

  1. @@ -102,11 +127,16 @@ const About = ({ dispatch: dispatchToGlobal }: { dispatch: (action: Action) => v

- + Puppeteer test generation page

-

Team

+

The Spearmint Team

+ Alfred @astaiglesia  ·  Annie{' '} + @annieshinn  ·  Gabriel{' '} + @bielchristo  ·  Sharon{' '} + @sharon-zhu  ·  Tolan{' '} + @taoantaoan

Ben @bkwak  ·  Evan{' '} @Berghoer  ·  Nicolas{' '} @nicolaspita  ·  Luis{' '} diff --git a/src/pages/ProjectLoader/ProjectLoader.jsx b/src/pages/ProjectLoader/ProjectLoader.jsx index 68ff6528..cf4aa153 100644 --- a/src/pages/ProjectLoader/ProjectLoader.jsx +++ b/src/pages/ProjectLoader/ProjectLoader.jsx @@ -40,27 +40,29 @@ const ProjectLoader = () => {

spearmint + > + /> + testing, simplified
+
01 Enter test site's URL
- +
02 - Select your application
+ Select your application
diff --git a/src/pages/ProjectLoader/ProjectLoader.module.scss b/src/pages/ProjectLoader/ProjectLoader.module.scss index c816bbf2..3225592a 100644 --- a/src/pages/ProjectLoader/ProjectLoader.module.scss +++ b/src/pages/ProjectLoader/ProjectLoader.module.scss @@ -4,10 +4,11 @@ #upperPart { width: 100%; height: 50vh; - background-color: #02c2c3; + background-color: #038181; display: flex; + flex-direction: column; justify-content: center; - align-items: flex-end; + align-items: center; } #title { @@ -16,7 +17,17 @@ vertical-align: bottom; font-family: 'comfortaa'; color: #fff; - margin-bottom: 30px; + padding-top: 30px; + margin-top: 30px; + flex: 1; +} + +#purpose { + font-size: 1rem; + text-align: center; + font-family: 'comfortaa'; + color: #fff; + flex: 1; } #leaf { @@ -91,11 +102,10 @@ letter-spacing: 1px; } + #helpBtn { color: white; background-color: $mint; - // margin-right: 4px; - // margin-bottom: 4px; margin: 15px 4px 4px 4px; height: 40px; width: 110px; @@ -110,6 +120,9 @@ background-color: white; color: $mint; } +#helpBtn:focus { + outline: 1px solid darkblue +} #bottomDiv { display: flex; diff --git a/src/pages/TestFile/TestFile.jsx b/src/pages/TestFile/TestFile.jsx index f4ec846e..39cd8822 100644 --- a/src/pages/TestFile/TestFile.jsx +++ b/src/pages/TestFile/TestFile.jsx @@ -1,9 +1,9 @@ -import styles from '../../components/Modals/ExportFileModal.module.scss'; +/* eslint-disable linebreak-style */ import React, { useContext, useReducer, Fragment } from 'react'; import ReactModal from 'react-modal'; +import styles from '../../components/Modals/ExportFileModal.module.scss'; -//may be able to delete toggleReact, etc. from their respective action files - +// may be able to delete toggleReact, etc. from their respective action files import ReactTestCase from '../../components/TestCase/ReactTestCase'; import { @@ -40,6 +40,13 @@ import { mockDataReducer, } from '../../context/reducers/mockDataReducer'; +import { + AccTestCaseContext, + accTestCaseState, + accTestCaseReducer, +} from '../../context/reducers/accTestCaseReducer'; +import AccTestCase from '../../components/TestCase/AccTestCase'; + import { GlobalContext } from '../../context/reducers/globalReducer'; import { setTestCase, toggleModal } from '../../context/actions/globalActions'; @@ -64,6 +71,10 @@ const TestFile = () => { puppeteerTestCaseReducer, puppeteerTestCaseState ); + const [accTestCase, dispatchToAccTestCase] = useReducer( + accTestCaseReducer, + accTestCaseState + ); const closeTestModal = () => { dispatchToGlobal(toggleModal()); @@ -99,25 +110,28 @@ const TestFile = () => {

What would you like to test?

- - - + +
- + {/* instantiate context for each test option */} {testCase === 'redux' && (
@@ -160,6 +174,16 @@ const TestFile = () => {
)} + {testCase === 'acc' && ( +
+ + + +
+ )} + {testCase === '' && (
diff --git a/src/utils/accTypes.ts b/src/utils/accTypes.ts new file mode 100644 index 00000000..dc091d0c --- /dev/null +++ b/src/utils/accTypes.ts @@ -0,0 +1,37 @@ +export interface AccTestCaseState { + modalOpen: boolean; + describeId: number; + itId: number; + statementId: number; + propId: number; + describeBlocks: DescribeBlocks + itStatements: ItStatements; + fileName: string; + filePath: string; + testType: string; + puppeteerUrl: string; +} +export interface DescribeBlocks { + byId: Object; + allIds: Array; +} +export interface ItStatements { + byId: Object; + allIds: Object; +} +export interface Action { + type: string; + id?: string; + draggableStatements?: Array; + index?: number; + text?: string; + itId?: number; + describeId?: number | string; + reorderedDescribe?: Array; + reorderedIt?: Array; + fileName?: string; + filePath?: string; + describeBlocks: any[]; + standardTag: string; + catTag: string; +} diff --git a/src/utils/endpointTypes.ts b/src/utils/endpointTypes.ts index 1e5d2dbc..bfd6927e 100644 --- a/src/utils/endpointTypes.ts +++ b/src/utils/endpointTypes.ts @@ -61,50 +61,31 @@ export interface Header { export type EventTarget = { target: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; }; - -// export interface GlobalState { -// url: string; -// projectUrl: string; -// isProjectLoaded: boolean; -// fileTree: string; -// componentName: string; -// isFileDirectoryOpen: boolean; -// isRightPanelOpen: boolean; -// rightPanelDisplay: string; -// isFolderOpen: object; -// isFileHighlighted: string; -// projectFilePath: string; -// filePathMap: object; -// file: string; -// testCase: string; -// docsOpen: boolean; -// isTestModalOpen: boolean; -// exportBool: boolean; -// fileName: string; -// filePath: string; -// validCode: boolean; -// } - -// export type HooksAction = -// | { type: 'TOGGLE_HOOKS' | 'ADD_CONTEXT' | 'ADD_HOOKRENDER' | 'ADD_HOOK_UPDATES' | 'ADD_HOOKRENDER' | 'CREATE_NEW_HOOKS_TEST' } -// | { type: 'UPDATE_HOOKS_TEST_STATEMENT'; hooksTestStatement: string } -// | { type: 'DELETE_CONTEXT' | 'DELETE_HOOKRENDER' | 'DELETE_HOOK_UPDATES'; id: number } -// | { type: 'UPDATE_CONTEXT'; id: number; queryVariant: string; querySelector: string; queryValue: string; values: string; textNodes: string; providerComponent: string; consumerComponent: string; context: string; } -// | { type: 'UPDATE_HOOKRENDER'; id: number; hook: string; parameterOne: string; expectedReturnValue: string; returnValue: string; } -// | { type: 'UPDATE_HOOK_UPDATES'; id: number; hook: string; hookFileName: string; hookFilePath: string; callbackFunc: string; managedState: string; updatedState: string;} -// | { type: 'UPDATE_HOOKS_FILEPATH'; hookFileName: string; hookFilePath: string } -// | { type: 'UPDATE_CONTEXT_FILEPATH'; contextFileName: string; contextFilePath: string } -// | { type: 'UPDATE_STATEMENTS_ORDER'; draggableStatements: Array }; - export interface EndpointTestMenuProps { dispatchToEndpointTestCase: (action: object) => void; } - -// export interface HooksTestModalProps extends HooksTestMenuProps { -// isHooksModalOpen: boolean; -// closeHooksModal: () => void; -// } - export interface EndpointTestStatementsProps extends EndpointTestMenuProps { endpointStatements: Array; } + +// export type EndpointAction = +// { +// type: +// | 'TOGGLE_ENDPOINT' +// | 'CREATE_NEW_ENDPOINT_TEST' +// | 'ADD_ENDPOINT' +// | 'OPEN_INFO_MODAL' +// | 'CLOSE_INFO_MODAL'; +// } +// | { type: 'TOGGLE_POST'; index: number } +// | { type: 'UPDATE_ENDPOINT_STATEMENTS_ORDER'; draggableStatements: string } +// | { type: 'UPDATE_SERVER_FILEPATH'; serverFilePath: string; serverFileName: string } +// | { type: 'DELETE_ENDPOINT' | 'ADD_HEADER' | 'ADD_ASSERTION'; id: number} +// | { type: 'UPDATE_ENDPOINT'; endpoint: object; id: number} +// | { type: 'UPDATE_STATEMENTS_ORDER'; draggableStatements: Array } +// | { type: 'DELETE_HEADER' | 'DELETE_ASSERTION'; index: number; id: number } + +// | { type: 'UPDATE_POST'; text: string; index: number } +// | { type: 'UPDATE_ASSERTION'; id: number; index: number; assertion: Assertion } +// | { type: 'TOGGLE_DB'; db: string | boolean } +// | { type: 'UPDATE_DB_FILEPATH'; dbFilePath: string }; diff --git a/src/utils/hooksTypes.ts b/src/utils/hooksTypes.ts index 6f287027..10eb6ae3 100644 --- a/src/utils/hooksTypes.ts +++ b/src/utils/hooksTypes.ts @@ -25,30 +25,30 @@ export interface Callback { callbackFunc: string; } -export interface Action { - type: string; - id?: number; - context?: string; - hook?: string; - parameters?: any; - expectedState?: any; - expectedValue?: any; - textNodes?: string; - queryVariant?: string; - querySelector?: string; - queryValue?: string; - values?: string; - providerComponent?: string; - consumerComponent?: string; - draggableStatements?: Array; - testName?: string; - index?: number; - text?: string; - contextFileName?: string; - contextFilePath?: string; - assertions?: Assertion; - callbackfunc?: Callback; -} +// export interface Action { +// type: string; +// id?: number; +// context?: string; +// hook?: string; +// parameters?: any; +// expectedState?: any; +// expectedValue?: any; +// textNodes?: string; +// queryVariant?: string; +// querySelector?: string; +// queryValue?: string; +// values?: string; +// providerComponent?: string; +// consumerComponent?: string; +// draggableStatements?: Array; +// testName?: string; +// index?: number; +// text?: string; +// contextFileName?: string; +// contextFilePath?: string; +// assertions?: Assertion; +// callbackfunc?: Callback; +// } export interface Hooks { id: number; @@ -64,41 +64,53 @@ export interface Hooks { } /* ---------------------------- Actions In Reducer coming from hooksTestCaseActions ---------------------- */ -// export type HooksAction = -// | { -// type: -// | 'TOGGLE_HOOKS' -// | 'ADD_CONTEXT' -// | 'ADD_HOOK_UPDATES' -// | 'CREATE_NEW_HOOKS_TEST'; -// } -// | { type: 'UPDATE_HOOKS_TEST_STATEMENT'; hooksTestStatement: string } -// | { type: 'DELETE_CONTEXT' | 'DELETE_HOOK_UPDATES'; id: number } -// | { -// type: 'UPDATE_CONTEXT'; -// id: number; -// queryVariant: string; -// querySelector: string; -// queryValue: string; -// values: string; -// textNodes: string; -// providerComponent: string; -// consumerComponent: string; -// context: string; -// } -// | { -// type: 'UPDATE_HOOK_UPDATES'; -// id: number; -// hook: string; -// hookFileName: string; -// hookFilePath: string; -// callbackFunc: string; -// managedState: string; -// updatedState: string; -// } -// | { type: 'UPDATE_HOOKS_FILEPATH'; hookFileName: string; hookFilePath: string } -// | { type: 'UPDATE_CONTEXT_FILEPATH'; contextFileName: string; contextFilePath: string } -// | { type: 'UPDATE_STATEMENTS_ORDER'; draggableStatements: Array }; +export type HooksAction = + | { + type: + | 'TOGGLE_HOOKS' + | 'ADD_CONTEXT' + | 'ADD_HOOK_UPDATES' + | 'CREATE_NEW_HOOKS_TEST' + | 'OPEN_INFO_MODAL' + | 'CLOSE_INFO_MODAL'; + } + | { type: 'UPDATE_HOOKS_TEST_STATEMENT'; hooksTestStatement: string } + | { type: 'DELETE_CONTEXT' | 'DELETE_HOOK_UPDATES' | 'TOGGLE_TYPEOF' | 'ADD_CALLBACKFUNC'; id: number } + | { + type: 'UPDATE_CONTEXT'; + id: number; + queryVariant: string; + querySelector: string; + queryValue: string; + values: string; + textNodes: string; + providerComponent: string; + consumerComponent: string; + context: string; + } + | { + type: 'UPDATE_HOOK_UPDATES'; + id: number; + hook: string; + hookFileName: string; + hookFilePath: string; + callbackFunc: string; + managedState: string; + updatedState: string; + } + | { type: 'UPDATE_HOOKS_FILEPATH'; hookFileName: string; hookFilePath: string } + | { type: 'UPDATE_CONTEXT_FILEPATH'; contextFileName: string; contextFilePath: string } + | { type: 'UPDATE_STATEMENTS_ORDER'; draggableStatements: Array } + | { type: 'ADD_ASSERTION'; index: number } + | { + type: 'UPDATE_ASSERTION'; + index: number; + id: number; + assertion: Assertion + } + | { type: 'DELETE_ASSERTION'; index: number; id: number } + | { type: 'UPDATE_CALLBACKFUNC'; index: number; id: number; callbackFunc: Callback } + | { type: 'DELETE_CALLBACKFUNC'; index: number; id: number; }; export interface HooksTestMenuProps { dispatchToHooksTestCase: (action: object) => void; diff --git a/tsconfig.json b/tsconfig.json index b0fa87c4..1b81a0c9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -13,7 +17,13 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "preserve" + "jsx": "react", + "noFallthroughCasesInSwitch": true }, - "include": ["src", "src/context/__tests__", "__tests__/hello.test.js"] + "include": [ + "src", + "src/context/__tests__", + "__tests__/hello.test.js", + "tests/spec.integra.js" + ] }