├── src
├── stylesheets
│ ├── components
│ │ ├── _reuploaddirectory.scss
│ │ ├── _filetree.scss
│ │ ├── _itstatement.scss
│ │ ├── _all.scss
│ │ ├── _testbuilder.scss
│ │ ├── _expectstatement.scss
│ │ ├── _maincontent.scss
│ │ ├── _describeblock.scss
│ │ ├── _fileviewer.scss
│ │ ├── _proploader.scss
│ │ ├── _testblock.scss
│ │ └── _folderupload.scss
│ └── styles.scss
├── setupEnzyme.ts
├── reduxComponents
│ ├── store.ts
│ ├── reducers
│ │ ├── combineReducers.ts
│ │ └── reducer.ts
│ ├── constants
│ │ └── actionTypes.ts
│ └── actions
│ │ └── actions.ts
├── typings
│ └── declarations.d.ts
├── index.tsx
├── app.tsx
├── index.html
├── components
│ ├── MainContent.tsx
│ ├── CodeViewer.tsx
│ ├── ProjectTestCode.tsx
│ ├── FileViewer.tsx
│ ├── ReuploadDirectory.tsx
│ ├── FileTree.tsx
│ ├── PropLoader.tsx
│ ├── ExportTestCode.tsx
│ ├── DescribeBlock.tsx
│ ├── ItStatement.tsx
│ ├── FolderUpload.tsx
│ ├── TestBuilder.tsx
│ ├── TestBlock.tsx
│ └── ExpectStatement.tsx
├── icons
│ └── icons.tsx
└── utils
│ └── globalCodeGenerator.ts
├── .vscode
└── settings.json
├── .DS_Store
├── .prettierrc.js
├── assets
├── .DS_Store
├── icon.png
├── icons
│ ├── .DS_Store
│ ├── default_file.svg
│ ├── default_folder.svg
│ ├── file_type_eslint.svg
│ ├── file_type_js.svg
│ ├── file_type_config.svg
│ ├── file_type_light_config.svg
│ ├── file_type_html.svg
│ ├── file_type_typescript.svg
│ ├── file_type_css.svg
│ ├── file_type_scss.svg
│ ├── file_type_json.svg
│ ├── file_type_reactjs.svg
│ ├── file_type_reactts.svg
│ ├── file_type_reacttemplate.svg
│ └── file_type_babel2.svg
├── CatalystInstallBG-05.tif
└── catalyst_icons
│ ├── Catalyst-01.png
│ ├── Catalystfull-02.png
│ ├── CatalystDockIcon-03.png
│ └── CatalystDockIconLarge-04.png
├── prettierrc.js
├── __tests__
├── .DS_Store
├── __snapshots__
│ └── app.test.tsx.snap
└── app.test.tsx
├── .gitignore
├── .babelrc
├── jest.config.json
├── tsconfig.json
├── webpack.electron.config.js
├── .eslintrc.js
├── LICENSE
├── webpack.react.config.js
├── electron
└── main.ts
├── README.md
└── package.json
/src/stylesheets/components/_reuploaddirectory.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.tabSize": 2
3 | }
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Catalyst/HEAD/.DS_Store
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | printWidth: 80,
4 | };
--------------------------------------------------------------------------------
/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Catalyst/HEAD/assets/.DS_Store
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Catalyst/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | printWidth: 80,
4 | };
--------------------------------------------------------------------------------
/__tests__/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Catalyst/HEAD/__tests__/.DS_Store
--------------------------------------------------------------------------------
/assets/icons/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Catalyst/HEAD/assets/icons/.DS_Store
--------------------------------------------------------------------------------
/assets/CatalystInstallBG-05.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Catalyst/HEAD/assets/CatalystInstallBG-05.tif
--------------------------------------------------------------------------------
/assets/catalyst_icons/Catalyst-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Catalyst/HEAD/assets/catalyst_icons/Catalyst-01.png
--------------------------------------------------------------------------------
/assets/catalyst_icons/Catalystfull-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Catalyst/HEAD/assets/catalyst_icons/Catalystfull-02.png
--------------------------------------------------------------------------------
/assets/catalyst_icons/CatalystDockIcon-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Catalyst/HEAD/assets/catalyst_icons/CatalystDockIcon-03.png
--------------------------------------------------------------------------------
/assets/catalyst_icons/CatalystDockIconLarge-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Catalyst/HEAD/assets/catalyst_icons/CatalystDockIconLarge-04.png
--------------------------------------------------------------------------------
/assets/icons/default_file.svg:
--------------------------------------------------------------------------------
1 | default_file
--------------------------------------------------------------------------------
/assets/icons/default_folder.svg:
--------------------------------------------------------------------------------
1 | default_folder
--------------------------------------------------------------------------------
/src/setupEnzyme.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import * as Enzyme from "enzyme";
3 | import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
4 |
5 | Enzyme.configure({
6 | adapter: new Adapter(),
7 | });
8 |
--------------------------------------------------------------------------------
/src/reduxComponents/store.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from "redux";
2 | import { composeWithDevTools } from "redux-devtools-extension";
3 | import { reducer } from "./reducers/reducer";
4 |
5 | export const store = createStore(reducer, composeWithDevTools());
6 |
--------------------------------------------------------------------------------
/__tests__/__snapshots__/app.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Testing app component matches snapshot 1`] = `
4 |
5 |
6 | Hello from React World
7 |
8 |
9 |
10 | `;
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore node_modules folder
2 | node_modules
3 |
4 | # Ignore module bundles folder
5 | dist
6 |
7 | # Ignore package-lock.json
8 | package-lock.json
9 |
10 | # Ignore files related to API keys
11 | .env
12 |
13 | # Ignore Mac system files
14 | .DS_Store
--------------------------------------------------------------------------------
/src/stylesheets/styles.scss:
--------------------------------------------------------------------------------
1 | @import 'components/all';
2 |
3 | $primary: #1c768f;
4 | $secondary: #032539;
5 | $fg: #FA991C;
6 | $bg: #FBF3F2;
7 |
8 |
9 | * {
10 | box-sizing: border-box;
11 | font-family: 'Nunito Sans', sans-serif;
12 | }
13 |
14 | button, select {
15 | outline: none;
16 | }
17 |
--------------------------------------------------------------------------------
/src/reduxComponents/reducers/combineReducers.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import { reducer } from "./reducer";
3 |
4 | export const rootReducer = combineReducers({
5 | generalReducer: reducer,
6 | });
7 |
8 | // giving type of reducer globally
9 | export type RootState = ReturnType;
10 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/typescript",
5 | "@babel/react"
6 | ],
7 | "plugins": [
8 | "@babel/proposal-class-properties",
9 | "@babel/proposal-object-rest-spread",
10 | ["@babel/plugin-transform-runtime",
11 | {
12 | "regenerator": true
13 | }]
14 | ]
15 | }
--------------------------------------------------------------------------------
/src/typings/declarations.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.svg" {
2 | const content: any;
3 | export default content;
4 | }
5 |
6 | declare module "*.css" {
7 | const css: any;
8 | export default css;
9 | }
10 |
11 | declare module "*.scss" {}
12 |
13 | declare module "*.png" {
14 | const png: any;
15 | export default png;
16 | }
17 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDom from "react-dom";
3 | import { Provider } from "react-redux";
4 | import { store } from "./reduxComponents/store";
5 | import { App } from "./app";
6 |
7 | ReactDom.render(
8 |
9 |
10 | ,
11 | document.getElementById("root")
12 | );
13 |
--------------------------------------------------------------------------------
/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": [
3 | "ts",
4 | "tsx",
5 | "js"
6 | ],
7 | "transform": {
8 | "\\.(ts|tsx)$": "ts-jest"
9 | },
10 | "testRegex": "/__tests__/.*\\.(ts|tsx|js)$",
11 | "setupFilesAfterEnv": [
12 | "./src/setupEnzyme.ts"
13 | ],
14 | "snapshotSerializers": [
15 | "enzyme-to-json/serializer"
16 | ]
17 | }
--------------------------------------------------------------------------------
/src/stylesheets/components/_filetree.scss:
--------------------------------------------------------------------------------
1 | $primary: #032539;
2 | $secondary: #1c768f;
3 | $fg: #FA991C;
4 | $bg: #FBF3F2;
5 |
6 | .FileTree li a:hover{
7 | cursor: pointer;
8 | text-decoration: underline;
9 | }
10 |
11 | .directory:hover {
12 | cursor: pointer;
13 | }
14 |
15 | .FileTree li {
16 | list-style-type: none;
17 | }
18 |
19 | .FileTree span img {
20 | width: 1rem;
21 | height: 1rem;
22 | margin-right:0.25rem;
23 | }
--------------------------------------------------------------------------------
/src/stylesheets/components/_itstatement.scss:
--------------------------------------------------------------------------------
1 | $primary: #032539;
2 | $secondary: #1c768f;
3 | $fg: #FA991C;
4 | $bg: #FBF3F2;
5 |
6 | .itBlock {
7 | margin: 1em 1em;
8 | .itinput {
9 | width: 85%;
10 | border: .5px solid $primary;
11 | border-radius: .5em;
12 | padding: .5em;
13 | }
14 | .removeit {
15 | background: $fg;
16 | font-size: 12px;
17 | padding: .25em .60em;
18 | margin-left: 1em;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/app.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector } from "react-redux";
3 | import { MainContent } from "./components/MainContent";
4 | import { FolderUpload } from "./components/FolderUpload";
5 |
6 | import "./stylesheets/styles";
7 |
8 | export const App: React.FC = () => {
9 | const fileTree = useSelector((state: any) => state.fileTree);
10 |
11 | return fileTree.length ? (
12 |
13 | ) : (
14 |
15 |
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/src/stylesheets/components/_all.scss:
--------------------------------------------------------------------------------
1 | @import 'describeblock';
2 | @import 'filetree';
3 | @import 'folderupload';
4 | @import 'testbuilder';
5 | @import 'testblock';
6 | @import 'fileviewer';
7 | @import 'maincontent';
8 | @import 'itstatement';
9 | @import 'expectstatement';
10 | @import 'reuploaddirectory';
11 | @import 'proploader';
12 |
13 |
14 | $primary: #032539;
15 | $secondary: #1c768f;
16 | $fg: #FA991C;
17 | $bg: #FBF3F2;
18 |
19 | body {
20 | margin: 0;
21 | padding: 0;
22 | height:100vh;
23 | width: 100vw;
24 | }
25 |
--------------------------------------------------------------------------------
/assets/icons/file_type_eslint.svg:
--------------------------------------------------------------------------------
1 | file_type_eslint
--------------------------------------------------------------------------------
/src/stylesheets/components/_testbuilder.scss:
--------------------------------------------------------------------------------
1 | $primary: #032539;
2 | $secondary: #1c768f;
3 | $fg: #FA991C;
4 | $bg: #FBF3F2;
5 |
6 | .testBuilder {
7 | border: 1px solid $primary;
8 | background: $primary;
9 | border-radius: 3em;
10 | box-shadow: 2px 2px 12px $primary;
11 | .addtestbutton {
12 | display:flex;
13 | justify-content: center;
14 | align-items: center;
15 | padding-bottom: 1em;
16 | .adddescribe {
17 | color: $primary;
18 | font-size: 20px;
19 | border: none;
20 | border-radius: 1em;
21 | padding: .25em .5em;
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/__tests__/app.test.tsx:
--------------------------------------------------------------------------------
1 | import { shallow, mount, render } from 'enzyme';
2 | import * as React from 'react';
3 | import { App } from '../src/app';
4 | import { FolderUpload } from '../src/components/FolderUpload'
5 |
6 | const wrapper = shallow( );
7 |
8 | describe('Testing app component', () => {
9 | it('renders', () => {
10 | expect(wrapper.exists()).toBe(true);
11 | })
12 | it('matches snapshot', () => {
13 | expect(wrapper).toMatchSnapshot();
14 | })
15 | it('render FolderUpload component', () => {
16 | expect(wrapper.children(FolderUpload).length).toEqual(1);
17 | })
18 | });
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esNext",
4 | "module": "commonjs",
5 | "lib": [
6 | "dom",
7 | "es2015",
8 | "es2016",
9 | "es2017",
10 | "dom.iterable"
11 | ],
12 | "allowJs": true,
13 | "jsx": "react",
14 | "allowSyntheticDefaultImports": true,
15 | "sourceMap": true,
16 | "outDir": "./dist",
17 | "strict": true,
18 | "esModuleInterop": true,
19 | "typeRoots": ["node_modules/@types", "typings"]
20 | },
21 | "exclude": [
22 | "node_modules",
23 | "dist"
24 | ],
25 | "include": ["./src/"]
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Catalyst
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/assets/icons/file_type_js.svg:
--------------------------------------------------------------------------------
1 | file_type_js
--------------------------------------------------------------------------------
/webpack.electron.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | resolve: {
5 | extensions: ['.tsx', '.ts', '.js', '.scss'],
6 | },
7 | devtool: 'source-map',
8 | entry: './electron/main.ts',
9 | target: 'electron-main',
10 | module: {
11 | rules: [
12 | {
13 | test: /\.(js|ts|tsx)$/,
14 | exclude: /node_modules/,
15 | use: {
16 | loader: 'babel-loader',
17 | },
18 | },
19 | {
20 | test: /\.s[ac]ss$/i,
21 | use: ["style-loader", "css-loader", "sass-loader"]
22 | },
23 | {
24 | test: /\.(png|jpe?g|gif)$/i,
25 | use: [
26 | {
27 | loader: 'file-loader',
28 | },
29 | ],
30 | }
31 | ],
32 | },
33 | output: {
34 | path: path.resolve(__dirname, './dist'),
35 | filename: 'main.js',
36 | },
37 | };
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["airbnb-typescript-prettier"],
3 | rules: {
4 | 'react/prop-types': 0,
5 | 'react/destructuring-assignment': 0,
6 | 'react/static-property-placement': 0,
7 | 'jsx-a11y/alt-text': 0,
8 | 'react/jsx-props-no-spreading': 0,
9 | 'import/prefer-default-export': 0,
10 | 'no-nested-ternary': 0,
11 | 'react-hooks/exhaustive-deps': 0,
12 | 'no-restricted-syntax': 0,
13 | 'jsx-a11y/no-static-element-interactions': 0,
14 | 'jsx-a11y/click-events-have-key-events': 0,
15 | 'jsx-a11y/anchor-is-valid': 0,
16 | 'no-plusplus': 0,
17 | 'class-methods-use-this': 0,
18 | 'react/button-has-type': 0,
19 | '@typescript-eslint/no-use-before-define': 0,
20 | '@typescript-eslint/no-unused-vars': 0,
21 | '@typescript-eslint/explicit-module-boundary-types': 0,
22 | 'no-case-declarations': 0,
23 | },
24 | };
--------------------------------------------------------------------------------
/src/components/MainContent.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { FileTree } from "./FileTree";
3 | import { CodeViewer } from "./CodeViewer";
4 | import { TestBuilder } from "./TestBuilder";
5 | import { TestBlock } from "./TestBlock";
6 |
7 | export const MainContent: React.FC = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
PROJECT DIRECTORY
16 |
17 |
18 |
19 |
TEST BUILDER
20 |
21 |
22 |
23 |
CODE VIEWER
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/assets/icons/file_type_config.svg:
--------------------------------------------------------------------------------
1 | file_type_config
--------------------------------------------------------------------------------
/assets/icons/file_type_light_config.svg:
--------------------------------------------------------------------------------
1 | file_type_light_config
--------------------------------------------------------------------------------
/src/components/CodeViewer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector } from "react-redux";
3 | import { ProjectTestCode } from "./ProjectTestCode";
4 | import { FileViewer } from "./FileViewer";
5 |
6 | export const CodeViewer: React.FC = () => {
7 | const codeViewer = useSelector((state: any) => state.codeViewer);
8 | const codeViewerChecker = useSelector(
9 | (state: any) => state.codeViewerChecker
10 | );
11 | // using fragment (<>) shorthand over React.Fragment
12 | return codeViewer === false ? (
13 | <>
14 |
15 |
No File Displayed
16 |
17 | Click on Generate Tests or a file in the project directory to view its
18 | code
19 |
20 |
21 | >
22 | ) : codeViewerChecker ? (
23 |
24 | ) : (
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/assets/icons/file_type_html.svg:
--------------------------------------------------------------------------------
1 | file_type_html
--------------------------------------------------------------------------------
/assets/icons/file_type_typescript.svg:
--------------------------------------------------------------------------------
1 | file_type_typescript
--------------------------------------------------------------------------------
/src/stylesheets/components/_expectstatement.scss:
--------------------------------------------------------------------------------
1 | $primary: #032539;
2 | $secondary: #1c768f;
3 | $fg: #FA991C;
4 | $bg: #FBF3F2;
5 |
6 | .expectBlock {
7 | margin: .5em 0em;
8 | padding: .5em;
9 | border: .5px solid $fg;
10 | background: $bg;
11 | border-radius: .5em;
12 | .expect1 {
13 | }
14 | .expect2 {
15 | margin-top: .5em;
16 | }
17 | .expectdrop1 {
18 | margin-left: 1em;
19 | padding: .25em .5em;
20 | border: .5px solid $secondary;
21 | border-radius: 1em;
22 | }
23 | .expectdrop2 {
24 | margin-right: 1em;
25 | padding: .25em .5em;
26 | border: .5px solid $secondary;
27 | border-radius: 1em;
28 | }
29 | input {
30 | font-size: 14px;
31 | border: .5px solid $secondary;
32 | border-radius: .5em;
33 | padding: .25em .5em;
34 | width: 40%;
35 | min-width: 5em;
36 | }
37 | .removeexpect {
38 | float: right;
39 | font-size: 12px;
40 | justify-content: center !important;
41 | background: $fg;
42 | margin: 0em auto;
43 | padding: .2em .55em;
44 | }
45 | }
--------------------------------------------------------------------------------
/src/stylesheets/components/_maincontent.scss:
--------------------------------------------------------------------------------
1 | $primary: #032539;
2 | $secondary: #1c768f;
3 | $fg: #FA991C;
4 | $bg: #FBF3F2;
5 |
6 | .main-content{
7 | height:100%;
8 | }
9 |
10 | .row {
11 | display: grid;
12 | grid-template-columns: 325px repeat(2, 1fr);
13 | grid-template-rows: 100%;
14 | width:100%;
15 | height: 90%;
16 | background: $bg;
17 | color: $primary;
18 | width: 100%;
19 | .left {
20 | overflow: auto;
21 | height:100%;
22 | border-right: 1px solid $secondary;
23 | h1 {
24 | text-align: center;
25 | font-size: 24px;
26 | }
27 | }
28 | .middle {
29 | overflow: auto;
30 | height:100%;
31 | border-left: 1px solid $secondary;
32 | border-right: 1px solid $secondary;
33 | padding: 0em 1.5em;
34 | min-width: 30em;
35 | h1 {
36 | text-align: center;
37 | font-size: 24px;
38 |
39 | }
40 | }
41 | .right {
42 | overflow: auto;
43 | height:100%;
44 | border-left: 1px solid $secondary;
45 | h1 {
46 | text-align: center;
47 | font-size: 24px;
48 |
49 | }
50 | }
51 | }
52 |
53 |
54 |
--------------------------------------------------------------------------------
/assets/icons/file_type_css.svg:
--------------------------------------------------------------------------------
1 | file_type_css
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Catalyst
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/stylesheets/components/_describeblock.scss:
--------------------------------------------------------------------------------
1 | $primary: #032539;
2 | $secondary: #1c768f;
3 | $fg: #FA991C;
4 | $bg: #FBF3F2;
5 |
6 | .describeBlock {
7 | // display: block;
8 | background: white;
9 | border-radius: 2em;
10 | padding: 1em 1em 1em 2em;
11 | margin: 1em;
12 | // box-shadow: 4px 4px 12px grey;
13 | button {
14 | font-size: 16px;
15 | border: none;
16 | border-radius: 1em;
17 | padding: .5em 1em;
18 | background: $secondary;
19 | color: $bg;
20 | }
21 | .remove {
22 | background: $fg;
23 | float: right;
24 | border-radius: 1.5em;
25 | padding: .10em .45em;
26 | }
27 | .describe {
28 | margin-bottom: 1em;
29 | .describetext {
30 | font-size: 1.5em;
31 | }
32 | .describeinput {
33 | border: .5px solid $primary;
34 | border-radius: .5em;
35 | padding: .5em;
36 | width: 95%;
37 | border-radius: 4px;
38 | outline: none;
39 | }
40 | form {
41 | margin-top: .1em;
42 | display:flex;
43 | align-items: center;
44 | padding-left: 0;
45 | input {
46 | margin-left: 0;
47 | }
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/src/stylesheets/components/_fileviewer.scss:
--------------------------------------------------------------------------------
1 | $primary: #032539;
2 | $secondary: #1c768f;
3 | $fg: #FA991C;
4 | $bg: #FBF3F2;
5 |
6 | div p {
7 | margin: 0;
8 | }
9 |
10 |
11 | .codeLine{
12 | display: flex;
13 | flex-direction: row;
14 |
15 | }
16 |
17 | .codeLine pre{
18 | margin: 0
19 | }
20 |
21 | .emptyfileview {
22 | color: $fg;
23 | border: none;
24 | width: 20em;
25 | margin: 1em;
26 | display: block;
27 | margin: 10em auto;
28 | padding-bottom: 1.5em;
29 | padding-top: 1.5em;
30 | justify-content: center;
31 | text-align: center;
32 | .emptyfiletitle {
33 | margin-top: 0;
34 | display: block;
35 | font-size: 30px;
36 | font-weight: 600;
37 | }
38 | .emptyfilebody {
39 | font-weight: 200;
40 | font-size: 24px;
41 | }
42 | }
43 |
44 | .codeBlock{
45 | padding-left: 5%;
46 | height:100%;
47 | .buttonHolder{
48 | width:100%;
49 | height:5%;
50 | }
51 | }
52 |
53 | .clearFile{
54 | display: block;
55 | float: right;
56 | }
57 |
58 | .remove {
59 | color: $bg;
60 | background: $fg;
61 | float: right;
62 | border-radius: 1.5em;
63 | border: none;
64 | padding: .10em .45em;
65 | margin-right: 1em;
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/src/reduxComponents/constants/actionTypes.ts:
--------------------------------------------------------------------------------
1 | export const UPDATE_DATA = "UPDATE_DATA";
2 | export const SET_FILE_VIEW = "SET_FILE_VIEW";
3 | export const CONSTRUCT_FILETREE = "CONSTRUCT_FILETREE";
4 | export const TOGGLE_FOLDER = "TOGGLE_FOLDER";
5 | export const UPDATE_KEY_OF_EXPECT = "UPDATE_KEY_OF_EXPECT";
6 | export const ADD_IT_STATEMENTS = "ADD_IT_STATEMENTS";
7 | export const UPDATE_KEY_OF_DESCRIBE = "UPDATE_KEY_OF_DESCRIBE";
8 | export const UPDATE_KEY_OF_IT = "UPDATE_KEY_OF_IT";
9 | export const UPDATE_IT_OBJ = "UPDATE_IT_OBJ";
10 | export const UPDATE_DESCRIBE = "UPDATE_DESCRIBE";
11 | export const UPDATE_COMPONENT_NAME = "UPDATE_COMPONENT_NAME";
12 | export const UPDATE_IT_STATEMENT = "UPDATE_IT_STATEMENT";
13 | export const ADD_COMPONENT_NAME = "ADD_COMPONENT_NAME";
14 | export const CLEAR_FILE = "CLEAR_FILE";
15 | export const DELETE_EXPECT = "DELETE_EXPECT";
16 | export const REMOVE_FROM_IT = "REMOVE_FROM_IT";
17 | export const SET_PROJECT_PATH = "SET_PROJECT_PATH";
18 | export const REFRESH_STATE = "REFRESH_STATE";
19 | export const UPDATE_DESCRIBE_BOOLEAN = "UPDATE_DESCRIBE_BOOLEAN";
20 | export const UPDATE_DESCRIBE_PROPS = "UPDATE_DESCRIBE_PROPS";
21 | export const SHOW_TESTCODE = "SHOW_TESTCODE";
22 | export const REMOVE_TESTCODE = "REMOVE_TESTCODE";
23 |
--------------------------------------------------------------------------------
/assets/icons/file_type_scss.svg:
--------------------------------------------------------------------------------
1 | file_type_scss
--------------------------------------------------------------------------------
/webpack.react.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | module.exports = {
5 | resolve: {
6 | extensions: ['.tsx', '.ts', '.js', '.scss', '.png', '.svg'],
7 | mainFields: ['main', 'module', 'browser'],
8 | },
9 | entry: './src/index.tsx',
10 | target: 'electron-renderer',
11 | devtool: 'source-map',
12 | module: {
13 | rules: [
14 | {
15 | test: /\.(js|ts|tsx)$/,
16 | exclude: /node_modules/,
17 | use: {
18 | loader: 'babel-loader',
19 | },
20 | },
21 | {
22 | test: /\.svg$/,
23 | use: ['url-loader']
24 | },
25 | {
26 | test: /\.s[ac]ss$/i,
27 | use: ["style-loader", "css-loader", "sass-loader"]
28 | },
29 | {
30 | test: /\.(png|jpe?g|gif)$/i,
31 | use: [
32 | {
33 | loader: 'file-loader',
34 | },
35 | ],
36 | }
37 | ],
38 | },
39 | devServer: {
40 | contentBase: path.join(__dirname, './dist/renderer'),
41 | historyApiFallback: true,
42 | compress: true,
43 | hot: true,
44 | port: 4000,
45 | publicPath: '/',
46 | },
47 | output: {
48 | path: path.resolve(__dirname, './dist/renderer'),
49 | filename: 'js/index.js',
50 | publicPath: './',
51 | },
52 | plugins: [
53 | new HtmlWebpackPlugin({
54 | template: './src/index.html'
55 | })
56 | ],
57 | };
--------------------------------------------------------------------------------
/assets/icons/file_type_json.svg:
--------------------------------------------------------------------------------
1 | file_type_json
--------------------------------------------------------------------------------
/src/icons/icons.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | // import logos from assests file here
4 |
5 | // tsx and jsx logo
6 | import tsxLogo from "../../assets/icons/file_type_reactts.svg";
7 |
8 | // js logo
9 | import jsLogo from "../../assets/icons/file_type_js.svg";
10 |
11 | // html logo
12 | import htmlLogo from "../../assets/icons/file_type_html.svg";
13 |
14 | // css logo
15 | import cssLogo from "../../assets/icons/file_type_css.svg";
16 |
17 | // ts logo
18 | import tsLogo from "../../assets/icons/file_type_typescript.svg";
19 |
20 | // scss logo
21 | import scssLogo from "../../assets/icons/file_type_scss.svg";
22 |
23 | // config logo
24 | import configLogo from "../../assets/icons/file_type_config.svg";
25 |
26 | // folder icon
27 | import folderIcon from "../../assets/icons/default_folder.svg";
28 |
29 | // json icon
30 | import jsonIcon from "../../assets/icons/file_type_json.svg";
31 |
32 | // babel icon
33 | import babelIcon from "../../assets/icons/file_type_babel2.svg";
34 |
35 | // eslint icon
36 | import eslintIcon from "../../assets/icons/file_type_eslint.svg";
37 |
38 | const FILE_ICONS: { [k: string]: JSX.Element } = {
39 | tsx: ,
40 | jsx: ,
41 | js: ,
42 | html: ,
43 | css: ,
44 | ts: ,
45 | scss: ,
46 | config: ,
47 | env: ,
48 | folder: ,
49 | json: ,
50 | babel: ,
51 | eslint: ,
52 | };
53 |
54 | export default FILE_ICONS;
55 |
--------------------------------------------------------------------------------
/src/stylesheets/components/_proploader.scss:
--------------------------------------------------------------------------------
1 | $primary: #032539;
2 | $secondary: #1c768f;
3 | $fg: #FA991C;
4 | $bg: #FBF3F2;
5 |
6 | .Prop {
7 | margin: .5em 0em;
8 | p {
9 | font-size: 20px;
10 | color: $primary;
11 | }
12 | .propform {
13 | margin-bottom: .5em;
14 | }
15 | .propcheck {
16 | height: 15px;
17 | width: 15px;
18 | border-radius: 4px;
19 | border: 1px solid $secondary;
20 | background: transparent;
21 | -webkit-appearance: none;
22 | -moz-appearance: none;
23 | -o-appearance: none;
24 | appearance: none;
25 | outline: none;
26 | display:inline-block;
27 | }
28 | .propcheck:checked {
29 | background: $secondary;
30 | }
31 | .propcheck:checked::before {
32 | height: 10px;
33 | width: 10px;
34 | content: '\2713';
35 | color: $bg;
36 | text-align: center;
37 | position: relative;
38 | top: -.1em;
39 | left: .1em;
40 | }
41 | .proplabel {
42 | display: inline;
43 | margin-right: .5em;
44 | }
45 | .addvalue {
46 | font-size: 16px;
47 | line-height: 1em !important;
48 | border-radius: .8em !important;
49 | margin-left: 1em;
50 | }
51 | .propChild {
52 | display: flex;
53 | margin: .5em 0em;
54 | align-items: center;
55 | .propval {
56 | width: 40%;
57 | }
58 | .proplabel {
59 | font-size: 18px;
60 | margin-right: .25em;
61 | margin-top: auto;
62 | margin-bottom: auto;
63 | }
64 | .removeprop {
65 | font-size: 14px !important;
66 | justify-content: center !important;
67 | background: $fg !important;
68 | padding: .175em .55em !important;
69 | }
70 | input {
71 | font-size: 14px;
72 | border: .5px solid $secondary;
73 | border-radius: .5em;
74 | padding: .25em .5em;
75 | margin-right: .5em;
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/ProjectTestCode.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import { RemoveTestCode } from "../reduxComponents/actions/actions";
4 |
5 | export const ProjectTestCode: React.FC = () => {
6 | const dispatch = useDispatch();
7 | const generatedTestCode = useSelector(
8 | (state: any) => state.generatedTestCode
9 | );
10 | const removeTestCode = () => dispatch(RemoveTestCode());
11 |
12 | const clearTestCode = () => {
13 | removeTestCode();
14 | };
15 |
16 | function pathToText() {
17 | const x: any = generatedTestCode;
18 | let counter = 0;
19 | for (let space = 0; space < x.length - 1; space++) {
20 | if (x[space] === " " && x[space + 1] === " ") {
21 | counter++;
22 | }
23 | }
24 |
25 | // move all in line styles into the css file
26 | const tester = x.split("\n").map((ele: string, id: number) => {
27 | let start = 0;
28 | while (ele[start] === " ") {
29 | start++;
30 | }
31 | let spaces = "";
32 | for (let i = 0; i < Math.floor(start / 2); i++) {
33 | spaces += "\t";
34 | }
35 | let begin = 5 - id.toString().length;
36 | let beginSpaces = id.toString();
37 | while (begin > 0) {
38 | beginSpaces += " ";
39 | begin--;
40 | }
41 | return (
42 |
43 |
44 | {beginSpaces}
45 | {spaces}
46 | {ele}{" "}
47 |
48 |
49 | );
50 | });
51 | return tester;
52 | }
53 |
54 | return generatedTestCode === "" ? (
55 | <>> // fragment shorthand
56 | ) : (
57 |
58 |
59 |
60 | X
61 |
62 |
63 |
{pathToText()}
64 |
65 | );
66 | };
67 |
--------------------------------------------------------------------------------
/assets/icons/file_type_reactjs.svg:
--------------------------------------------------------------------------------
1 | file_type_reactjs
--------------------------------------------------------------------------------
/assets/icons/file_type_reactts.svg:
--------------------------------------------------------------------------------
1 | file_type_reactts
--------------------------------------------------------------------------------
/assets/icons/file_type_reacttemplate.svg:
--------------------------------------------------------------------------------
1 | file_type_reacttemplate
--------------------------------------------------------------------------------
/src/components/FileViewer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import fs from "fs";
4 | import { ClearFile } from "../reduxComponents/actions/actions";
5 |
6 | export const FileViewer: React.FC = () => {
7 | const dispatch = useDispatch();
8 | const fileViewPath = useSelector((state: any) => state.fileToView);
9 | const clearFileInStore = () => dispatch(ClearFile());
10 |
11 | function clearFile() {
12 | clearFileInStore();
13 | }
14 |
15 | function pathToText() {
16 | const fileContent = fs.readFileSync(fileViewPath);
17 | const x: any = fileContent.toString();
18 | let counter = 0;
19 | for (let space = 0; space < x.length - 1; space++) {
20 | if (x[space] === " " && x[space + 1] === " ") {
21 | counter++;
22 | }
23 | }
24 |
25 | // move all in line styles into the css file
26 | const tester = x.split("\n").map((ele: string, id: number) => {
27 | let start = 0;
28 | while (ele[start] === " ") {
29 | start++;
30 | }
31 | let spaces = "";
32 | for (let i = 0; i < Math.floor(start / 2); i++) {
33 | spaces += "\t";
34 | }
35 | let begin = 5 - id.toString().length;
36 | let beginSpaces = id.toString();
37 | while (begin > 0) {
38 | beginSpaces += " ";
39 | begin--;
40 | }
41 | return (
42 |
43 |
44 | {beginSpaces}
45 | {spaces}
46 | {ele}{" "}
47 |
48 |
49 | );
50 | });
51 | return tester;
52 | }
53 |
54 | return fileViewPath === "" ? (
55 | <>> // fragment shorthand over React.Fragment
56 | ) : (
57 |
58 |
59 |
60 | X
61 |
62 |
63 |
64 |
65 | {pathToText()}
66 |
67 |
68 | );
69 | };
70 |
--------------------------------------------------------------------------------
/src/stylesheets/components/_testblock.scss:
--------------------------------------------------------------------------------
1 | $primary: #032539;
2 | $secondary: #1c768f;
3 | $fg: #FA991C;
4 | $bg: #FBF3F2;
5 |
6 | .testBlock{
7 | width: 100vw;
8 | height: 10vh;
9 | display: flex;
10 | align-items: center;
11 | background: $primary;
12 | box-shadow: 10px 15px orange;
13 | .catalysticon {
14 | width: 4.5em;
15 | height: 3em;
16 | margin-left: 1em;
17 | }
18 | .headerbar {
19 | display: flex;
20 | width: 95vw;
21 | align-items: center;
22 | justify-content: space-between;
23 | button {
24 | background: $primary;
25 | color:$bg;
26 | align-items: center;
27 | font-size: 24px;
28 | border: none;
29 | border-radius: 1.25em;
30 | padding: .25em .6em;
31 | }
32 | .headerlist {
33 | display: flex;
34 | list-style: none;
35 | padding-left: 0;
36 | margin-left: 0em;
37 | li {
38 | padding: 0em 1em;
39 | color: $bg;
40 | .generatetests {
41 | overflow: hidden;
42 | background: linear-gradient(to right, $fg, $fg 50%, $bg 50%);
43 | background-clip: text;
44 | -webkit-background-clip: text;
45 | -webkit-text-fill-color: transparent;
46 | background-size: 200% 100%;
47 | background-position: 100%;
48 | transition: background-position 275ms ease;
49 | }
50 | .generatetests:hover {
51 | background-position: 0 100%;
52 | }
53 | }
54 | }
55 | .iconlist {
56 | display: flex;
57 | list-style: none;
58 | padding-left: 0;
59 | margin: 0em 1em;
60 | height: 36px;
61 | li {
62 | margin: 0em .5em;
63 | color: $bg;
64 | a, a:hover, a:focus {
65 | text-decoration: none;
66 | color: inherit;
67 | .fa-github {
68 | font-size: 32px;
69 | }
70 | .fa-globe-americas {
71 | font-size: 32px;
72 | }
73 | .fa-question-circle {
74 | font-size: 32px;
75 | }
76 | }
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/electron/main.ts:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow } from 'electron';
2 | import * as path from 'path';
3 | import * as url from 'url';
4 | import installExtension, { REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS} from 'electron-devtools-installer';
5 |
6 | // keep a global reference of the window object, if you don't, the window will
7 | // be closed automatically when the javascript object is garbage collected
8 |
9 | let mainWindow: Electron.BrowserWindow | null;
10 |
11 |
12 | function createWindow() {
13 | // create the browser window
14 | mainWindow = new BrowserWindow({
15 | width: 1920,
16 | height: 1080,
17 | webPreferences: {
18 | nodeIntegration: true,
19 | // this allows us to access remote in other files of the app
20 | enableRemoteModule: true
21 | },
22 | icon: path.resolve(__dirname, '../assets/catalyst_icons/CatalystDockIconLarge-04.png')
23 | });
24 |
25 | if (process.platform === 'darwin') {
26 | app.dock.setIcon(path.resolve(__dirname, '../assets/catalyst_icons/CatalystDockIconLarge-04.png'));
27 | }
28 | if (process.env.NODE_ENV === 'development') {
29 | mainWindow.loadURL(`http://localhost:4000`);
30 |
31 | } else {
32 | const urlToLoad = url.format({
33 | pathname: path.resolve(__dirname, '../dist/renderer/index.html'),
34 | protocol: 'file:',
35 | slashes: true
36 | });
37 | mainWindow.loadURL(urlToLoad);
38 | };
39 |
40 |
41 |
42 | // emitted when the window is closed
43 | mainWindow.on('closed', () => {
44 | // derefernece the window object, normally windows are stored in an array if app
45 | // supports multi windows, this is the time when you should delete the corresponding element
46 | mainWindow = null;
47 | });
48 | }
49 |
50 | app.on('ready', () => {
51 | // calling installExtension func after the ready event was emitted by app
52 | // on the different extensions by looping through array of them
53 | [REDUX_DEVTOOLS, REACT_DEVELOPER_TOOLS].forEach(extension => {
54 | installExtension(extension)
55 | .then((name) => console.log(`Added Extension: ${name}`))
56 | .catch((err) => console.log('An error occurred: ', err));
57 | });
58 | // once extensions are looped through, invoking createWindow method
59 | createWindow();
60 | });
61 |
62 |
63 | app.allowRendererProcessReuse = true;
64 |
--------------------------------------------------------------------------------
/src/reduxComponents/actions/actions.ts:
--------------------------------------------------------------------------------
1 | import * as types from "../constants/actionTypes";
2 |
3 | export const ConstructFileTree = (files: []) => ({
4 | type: types.CONSTRUCT_FILETREE,
5 | payload: files,
6 | });
7 |
8 | export const SetFileView = (filePath: any) => ({
9 | type: types.SET_FILE_VIEW,
10 | payload: filePath,
11 | });
12 |
13 | export const ToggleFolder = (filePath: string) => ({
14 | type: types.TOGGLE_FOLDER,
15 | payload: filePath,
16 | });
17 |
18 | export const UpdateKeyOfExpect = () => ({
19 | type: types.UPDATE_KEY_OF_EXPECT,
20 | payload: "",
21 | });
22 |
23 | export const UpdateData = (data: any) => ({
24 | type: types.UPDATE_DATA,
25 | payload: data,
26 | });
27 |
28 | export const UpdateKeyOfDescribe = () => ({
29 | type: types.UPDATE_KEY_OF_DESCRIBE,
30 | payload: "",
31 | });
32 |
33 | export const UpdateKeyOfIt = () => ({
34 | type: types.UPDATE_KEY_OF_IT,
35 | payload: "",
36 | });
37 |
38 | export const UpdateItObj = (data: any) => ({
39 | type: types.UPDATE_IT_OBJ,
40 | payload: data,
41 | });
42 |
43 | export const UpdateDescribe = (data: any) => ({
44 | type: types.UPDATE_DESCRIBE,
45 | payload: data,
46 | });
47 |
48 | export const UpdateComponentName = (name: any) => ({
49 | type: types.UPDATE_COMPONENT_NAME,
50 | payload: name,
51 | });
52 |
53 | export const ClearFile = () =>({
54 | type: types.CLEAR_FILE,
55 | });
56 |
57 | export const deleteExpect = (data: any) => ({
58 | type: types.DELETE_EXPECT,
59 | payload: data,
60 | });
61 |
62 | export const removeFromIt = (data: any) => ({
63 | type: types.REMOVE_FROM_IT,
64 | payload: data,
65 | });
66 |
67 | export const SetProjectPath = (filePath: string) => ({
68 | type: types.SET_PROJECT_PATH,
69 | payload: filePath,
70 | });
71 |
72 | export const UpdateItStatement = (data: any) => ({
73 | type: types.UPDATE_IT_STATEMENT,
74 | payload: data,
75 | });
76 |
77 | export const RefreshState = () => ({
78 | type: types.REFRESH_STATE,
79 | });
80 |
81 | export const UpdateDescribeBoolean = (data: any) => ({
82 | type: types.UPDATE_DESCRIBE_BOOLEAN,
83 | payload: data,
84 | });
85 |
86 | export const ViewTestCode = (data: string) => ({
87 | type: types.SHOW_TESTCODE,
88 | payload: data,
89 | });
90 |
91 | export const RemoveTestCode = () => ({
92 | type: types.REMOVE_TESTCODE,
93 | });
94 |
--------------------------------------------------------------------------------
/src/components/ReuploadDirectory.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { remote } from "electron";
3 | import { useDispatch } from "react-redux";
4 | import { FileTree } from "./FolderUpload";
5 | import {
6 | ConstructFileTree,
7 | SetProjectPath,
8 | RefreshState,
9 | } from "../reduxComponents/actions/actions";
10 |
11 | export const ReuploadDirectory: React.FC = () => {
12 | const dispatch = useDispatch();
13 | // creates dispatch that will send file path as an action payload to reducer
14 | const constructFileTree = (files: any) => dispatch(ConstructFileTree(files));
15 | const setFilePath = (filePath: any) => dispatch(SetProjectPath(filePath));
16 | const refreshState = () => dispatch(RefreshState());
17 |
18 | const { dialog } = remote;
19 | async function uploadFolder() {
20 | // allows users to upload a folder
21 | const Project = await dialog.showOpenDialog(
22 | // sets opendirectory (allows directories to be selected) to be the feature that dialog uses
23 | {
24 | properties: ["openDirectory"],
25 | // the types of files that will be displayed or selected
26 | filters: [
27 | { name: "Javascript Files", extensions: ["js", "jsx"] },
28 | { name: "Typescript Files", extensions: ["ts", "tsx"] },
29 | { name: "HTML Files", extensions: ["html"] },
30 | ],
31 | message: "Choose a Project to Create Tests for:",
32 | }
33 | );
34 |
35 | // if the user cancels the action then undefined will be returned
36 | // if the user successfully completes the action then a string array will be returned
37 | // Project is an object that holds canceled (boolean to check if it was cancelled) and filePaths (array of filepaths)
38 | if (Project && !Project.canceled) {
39 | // holds the directory of the project that was selected
40 | let projectDirectory = Project.filePaths[0];
41 |
42 | // use regex to find all \ in the case of a windows user and replace with /
43 | projectDirectory = projectDirectory.replace(/\\/g, "/");
44 |
45 | // will create a new FileTree object for the root directory
46 | const rootTree = new FileTree(projectDirectory, "root");
47 |
48 | // will return an array of FileTree objects along with any children associated with it
49 | const fileTree = rootTree.createTree(projectDirectory);
50 |
51 | // dispatch call to refresh state
52 | refreshState();
53 | // dispatches fileTree to reducer
54 | constructFileTree(fileTree);
55 | // setting file path of project to access later by sending projectDirectory to store
56 | setFilePath(projectDirectory);
57 | }
58 | }
59 |
60 | return (
61 |
62 |
63 | Choose New Project
64 |
65 |
66 | );
67 | };
68 |
--------------------------------------------------------------------------------
/assets/icons/file_type_babel2.svg:
--------------------------------------------------------------------------------
1 | file_type_babel2
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## What is Catalyst?
4 |
5 | Catalyst is an application which allows developers to generate unit test cases for components utilizing the Jest framework and Javascript's Enzyme testing utility. With its interactive GUI, the creation of test code can be completed with just a few clicks.
6 |
7 | Catalyst is currently in beta and compatible for use with React 16.
8 |
9 | ## Installation
10 |
11 | First download the app [here](https://www.catalystjs.com/)
12 |
13 | Or you can also get started by cloning the repo using
14 |
15 | git clone https://github.com/oslabs-beta/Catalyst.git
16 |
17 | - Once you have the repo on your system, install all the dependencies using:
18 |
19 | npm install
20 |
21 | - After that is completed, you can open our app using:
22 |
23 | npm run dev
24 |
25 | Prior to running exported tests, Jest, Enzyme, and Enzyme Adapter for React 16 must be installed.
26 | To do this, enter the following line in the command line interface of your project's directory:
27 |
28 | npm install --save-dev jest enzyme enzyme-adapter-react-16
29 |
30 | ## How to use
31 |
32 | 1. Select the root folder for the project you would like to create tests for.
33 |
34 | 
35 |
36 | Opitonally, you can select which file you want to generate tests for and preview the component of choice on the right.
37 |
38 | 
39 |
40 | 2. Using our interactive GUI, choose from Enzyme's matcher API and fill in their respective selectors. If the selected matcher does not require a selector input, leave blank. Additionally, selector input box can be the value of your choosing. Once completed, click the 'Generate Tests' button to preview your test code.
41 |
42 | 
43 |
44 | 3. If satisfied, click the 'Export Test Code' button, name your
45 | file, and select the save button to save the generated code in your project. Catalyst will save this file in a \_\_tests\_\_ folder for you automatically if it doesn't already exist.
46 |
47 | 
48 |
49 | 4. Icons located in the right corner of application open additional windows with links to Catalyst's Github, Enzyme's official documentation, and a Enzyme Cheat Sheet.
50 |
51 | ## Contributing
52 |
53 | This project is still very early in development, so please inform our team of any issues found in the application or if you have any suggestions.
54 | Feel free to reach out to the development team if you'd like to make a pull requrest to add new features to this project.
55 |
56 | ## License
57 | Distributed under the MIT License. See LICENSE for more information.
58 |
59 | ## Authors
60 |
61 | > Jon Ascencio [jascenc1](https://github.com/jascenc1) ·
62 | > Danny Chung [chungdanny64](https://github.com/chungdanny64) ·
63 | > Gavin Crews [gcrews1894](https://github.com/gcrews1894) ·
64 | > Jarryl Oquias [jaroqui17](https://github.com/jaroqui17)
65 |
--------------------------------------------------------------------------------
/src/components/FileTree.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import * as electronFs from "fs";
4 | import { SetFileView, ToggleFolder } from "../reduxComponents/actions/actions";
5 | import FILE_ICONS from "../icons/icons";
6 |
7 | export const FileTree: React.FC = () => {
8 | const dispatch = useDispatch();
9 | // obtains the filetree from the store
10 | const fileTree = useSelector((state: any) => state.fileTree);
11 | // obtains the folder status from the store
12 | const isOpen = useSelector((state: any) => state.toggleFolder);
13 | // dispatches an action to set the file that will be viewed
14 | const setFileInRedux = (checker: any) => dispatch(SetFileView(checker));
15 | // dispatches an action to set the folder to open or not
16 | const toggleFolder = (filePath: string) => dispatch(ToggleFolder(filePath));
17 |
18 | // onclick function to set invoke the function to dispatch an action
19 | function setFileView(event: any) {
20 | // file path is saved as the id of the button
21 | setFileInRedux(event.target.id);
22 | }
23 |
24 | // func will strip extension ending from files which will be used as the key for the imported object that holds images for
25 | // those specific file icons
26 | const extensionGrabber = (ext: string) => {
27 | if (ext.split(".")[1] === undefined) {
28 | return "folder";
29 | }
30 | if (ext.split(".")[ext.split(".").length - 1].includes("babel")) {
31 | return "babel";
32 | }
33 | if (
34 | ext
35 | .split(".")
36 | [ext.split(".").length - 1].includes(
37 | "eslint" || ext.split(".")[1].includes("eslint")
38 | )
39 | ) {
40 | return "eslint";
41 | }
42 | return ext.split(".")[ext.split(".").length - 1];
43 | };
44 |
45 | // sending filepath to reducer to keep track of which folder is clicked on. if set to a global state then it will
46 | // toggle true and false on each click of the directory
47 | const handleToggle = (filePath: string) => {
48 | toggleFolder(filePath);
49 | };
50 |
51 | const traverseFileTree = (files: []) => {
52 | return (
53 |
54 | {files.map((file: any, id: number) => {
55 | const extension = extensionGrabber(file.name);
56 | const checker = electronFs.statSync(file.filepath);
57 |
58 | if (checker.isDirectory()) {
59 | return (
60 |
61 | handleToggle(file.filepath)}
65 | >
66 | {FILE_ICONS[extension]}
67 | {file.name}
68 |
69 | {isOpen[file.filepath] && traverseFileTree(file.children)}
70 |
71 | );
72 | }
73 | return (
74 |
75 |
76 | {FILE_ICONS[extension]}
77 | {file.name}
78 |
79 | {file.children.length > 0 && traverseFileTree(file.children)}
80 |
81 | );
82 | })}
83 |
84 | );
85 | };
86 |
87 | return fileTree ? traverseFileTree(fileTree) : <>>; // using fragment shorthand over React.Fragment
88 | };
89 |
--------------------------------------------------------------------------------
/src/stylesheets/components/_folderupload.scss:
--------------------------------------------------------------------------------
1 | $primary: #032539;
2 | $secondary: #1c768f;
3 | $fg: #FA991C;
4 | $bg: #FBF3F2;
5 |
6 | .frontheader {
7 | background: $primary;
8 | height: 30vh;
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | box-shadow: 0px 1px 15px $primary;
13 | }
14 |
15 | .frontpage {
16 | background: $bg;
17 | height: 100vh;
18 | padding: 0;
19 | margin: 0;
20 | }
21 |
22 | .frontbody {
23 | width: 30vw;
24 | height: 25em;
25 | display: flex;
26 | flex-wrap: wrap;
27 | margin-top: 4em;
28 | margin-left: auto;
29 | margin-right: auto;
30 | padding: 0;
31 | justify-content: center;
32 | align-items: center;
33 | text-align: center;
34 | h6{
35 | margin: 0;
36 | padding: 0;
37 | font-size: 48px;
38 | color: $primary;
39 | .fronth2 {
40 | margin-bottom: 0;
41 | padding-bottom: 0;
42 | }
43 | }
44 | p {
45 | color: $primary;
46 | margin: 0;
47 | padding: 0;
48 | font-size: 32px;
49 | }
50 | }
51 |
52 | // Variables
53 | $speed: "0.25s";
54 | $transition: all #{$speed} cubic-bezier(0.310, -0.105, 0.430, 1.400);
55 |
56 | /* Main Styles */
57 | .folderupload {
58 | display: flex;
59 | background-color: $fg;
60 | width: 350px;
61 | height: 75px;
62 | line-height: 75px;
63 | margin: auto;
64 | color: #fff;
65 | position: relative;
66 | top: 0;
67 | bottom: 0;
68 | left: 0;
69 | right: 0;
70 | cursor: pointer;
71 | overflow: hidden;
72 | border-radius: 5em;
73 | border: none;
74 | box-shadow: 0 0 20px 0 rgba(0,0,0,.3);
75 | transition: $transition;
76 |
77 | span,
78 | .icon {
79 | display: block;
80 | height: 100%;
81 | text-align: center;
82 | position: absolute;
83 | top: 0;
84 | }
85 |
86 | span {
87 | width: 72%;
88 | line-height: inherit;
89 | font-size: 22px;
90 | text-transform: uppercase;
91 | left: 0;
92 | transition: $transition;
93 |
94 | &:after {
95 | content: '';
96 | background-color: $bg;
97 | width: 2px;
98 | height: 70%;
99 | position: absolute;
100 | top: 15%;
101 | right: -1px;
102 | }
103 | }
104 | .icon {
105 | width: 28%;
106 | right: 0;
107 | transition: $transition;
108 | .fas {
109 | font-size: 30px;
110 | vertical-align: middle;
111 | transition: $transition, height #{$speed} ease;
112 | }
113 | .fa-upload {
114 | height: 36px;
115 | }
116 | .fa-check {
117 | display: none;
118 | }
119 | }
120 | &.success,
121 | &:hover {
122 |
123 | span {
124 | left: -72%;
125 | opacity: 0;
126 | }
127 |
128 | .icon {
129 | width: 100%;
130 |
131 | .fa {
132 | font-size: 45px;
133 | }
134 | }
135 | }
136 |
137 | &.success {
138 | background-color: #27ae60;
139 |
140 | .icon {
141 |
142 | .fa-remove {
143 | display: none;
144 | }
145 |
146 | .fa-check {
147 | display: inline-block;
148 | }
149 | }
150 | }
151 |
152 | &:hover {
153 | background-color: lighten($fg, 5%);
154 |
155 | .icon .fa-remove {
156 | height: 46px;
157 | }
158 | }
159 |
160 | &:active {
161 | opacity: 1;
162 | }
163 | }
164 |
165 | // .folderupload:after {
166 | // content: '';
167 | // position: absolute;
168 | // bottom: 0;
169 | // left: 0;
170 | // width: 100%;
171 | // height: 100%;
172 | // background-color: $fg;
173 | // border-radius: 10rem;
174 | // z-index: -2;
175 | // }
176 | // .folderupload:before {
177 | // content: '';
178 | // position: absolute;
179 | // bottom: 0;
180 | // left: 0;
181 | // width: 0%;
182 | // height: 100%;
183 | // background-color: darken($fg, 10%);
184 | // transition: all .3s;
185 | // border-radius: 10rem;
186 | // z-index: -1;
187 | // }
188 | // .folderupload:hover {
189 | // color: #fff;
190 | // &:before {
191 | // width: 100%;
192 | // }
193 | // }
194 |
195 | .catalystlogo {
196 | width: 40em;
197 | height: auto;
198 | }
--------------------------------------------------------------------------------
/src/components/PropLoader.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import { UpdateDescribeBoolean } from "../reduxComponents/actions/actions";
4 |
5 | interface Props {
6 | id: string;
7 | }
8 |
9 | export const PropLoader: React.FC = ({ id }) => {
10 | const temp = useRef([]);
11 | const [propArray, updatePropArray] = useState([]);
12 | const [displayArray, updateDisplay] = useState([]);
13 | const [count, updateCount] = useState(0);
14 | const [check, updateBool] = useState(false);
15 |
16 | const dispatch = useDispatch();
17 | const propBoolean = useSelector((state: any) => state.describePropBoolean);
18 |
19 | const updatePropBooleanInStore = (data: any) =>
20 | dispatch(UpdateDescribeBoolean(data));
21 |
22 | useEffect(() => {
23 | const display = [];
24 | for (const x of propArray) {
25 | for (const ele of Object.values(x)) {
26 | display.push(ele);
27 | }
28 | }
29 | updateDisplay(display);
30 | }, [propArray]);
31 |
32 | // adds a key value element to the display
33 | function addProp(
34 | event: React.MouseEvent
35 | ): void {
36 | event?.preventDefault();
37 | // checks to see if the checkbox is fille and if it is then add the prop field
38 | if (propBoolean[`${id}`]) {
39 | // save the element as in an object with a key associated to it for easy access on deletion
40 | const prop: { [k: string]: any } = {};
41 | prop[`describe${id}prop${count}`] = (
42 |
43 | {/* {count + 1}. */}
44 |
45 |
46 |
51 | X
52 |
53 |
54 | );
55 |
56 | temp.current = temp.current.concat([prop]);
57 | updatePropArray([...propArray, prop]);
58 | updateCount(count + 1);
59 | }
60 | }
61 |
62 | // deletes the selected key value field
63 | function test(event: React.MouseEvent): void {
64 | event.preventDefault();
65 | const answer = [];
66 | // loops through the current array of key value fields and adds those that are not selected
67 | for (const x of temp.current) {
68 | if (Object.keys(x)[0] !== event.target?.id.replace("button", "")) {
69 | answer.push(x);
70 | }
71 | }
72 | temp.current = answer;
73 | updatePropArray(answer);
74 | }
75 |
76 | // toggles the check box
77 | function updateCheck(): void {
78 | // if the checkbox is checked
79 | if (propBoolean[`${id}`]) {
80 | updatePropArray([]);
81 | temp.current = [];
82 | updateCount(0);
83 | }
84 | // sets the boolean value to the opposite value
85 | propBoolean[`${id}`] = !propBoolean[`${id}`];
86 | // update the boolean value in the store
87 | updatePropBooleanInStore(propBoolean);
88 | updateBool(!check);
89 | }
90 |
91 | return !check ? (
92 |
93 |
103 |
104 | ) : (
105 |
106 |
116 | {displayArray}
117 |
118 | Add Value
119 |
120 | );
121 | };
122 |
--------------------------------------------------------------------------------
/src/components/ExportTestCode.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | // eslint-disable-next-line import/no-extraneous-dependencies
4 | import { remote } from "electron";
5 | import * as electronFs from "fs";
6 | import path from "path";
7 | import { ViewTestCode } from "../reduxComponents/actions/actions";
8 | import { generateTestCode } from "../utils/globalCodeGenerator";
9 |
10 | const { dialog } = remote;
11 |
12 | export const ExportTestCode: React.FC = () => {
13 | // get generated TestCode from Global Store
14 | const generatedTestCode = useSelector(
15 | (state: any) => state.generatedTestCode
16 | );
17 | // get users project file path from store to aid in exporting generated test code to their machine
18 | const projectFilePath = useSelector((state: any) => state.filePathOfProject);
19 |
20 | const dispatch = useDispatch();
21 | // generated testCode from GUI is dispactched to store to be displayed in viewer
22 | const viewTestCode = (data: string) => dispatch(ViewTestCode(data));
23 |
24 | // helper function that will open browser window and default to a __tests__ directory for generated test code placement
25 | const openDialog = (userFilePath: string, testCode: string) => {
26 | // normalizing filePath to work cross-platform
27 | const filePath = path.normalize(`${userFilePath}/__tests__/`);
28 | dialog
29 | .showSaveDialog({
30 | title: "Please name your Test File",
31 | defaultPath: filePath, // can add location to save file on users directory
32 | filters: [
33 | {
34 | name: "Test Files",
35 | extensions: ["test"],
36 | },
37 | ],
38 | message: "Choose location",
39 | properties: ["createDirectory"],
40 | })
41 | .then((file) => {
42 | // stating whether dialog operation was cancelled or not
43 | if (!file.canceled) {
44 | // creating and writing to the generated.test file, adding js extension for enzyme compatibility
45 | electronFs.writeFile(
46 | `${file.filePath?.toString()}.js`,
47 | testCode,
48 | (err) => {
49 | if (err) {
50 | // eslint-disable-next-line no-console
51 | console.log(err.message);
52 | }
53 | // eslint-disable-next-line no-console
54 | console.log("saved");
55 | }
56 | );
57 | }
58 | })
59 | .catch((err) => {
60 | // eslint-disable-next-line no-console
61 | console.log(err);
62 | });
63 | };
64 |
65 | // determing if directory __tests__ exists already
66 | const exportTestCode = (userFilePath: string, testCode: string) => {
67 | // if __tests__ directory does not exist then create one and write generated test code into that newly created directory
68 | if (!electronFs.existsSync(`${userFilePath}/__tests__`)) {
69 | electronFs.mkdirSync(`${userFilePath}/__tests__`);
70 | openDialog(userFilePath, testCode);
71 | }
72 | // if __tests__ directory does exist then just generate another file into that directory
73 | else {
74 | openDialog(userFilePath, testCode);
75 | }
76 | };
77 |
78 | const exportCodeToDirectory = () => {
79 | if (generatedTestCode === "") {
80 | // if user logs in and does not want to visualize code, helper function will create test code
81 | // this test code will be sent to store as well as be exported
82 | const testCode = generateTestCode();
83 | // open window to export file
84 | exportTestCode(projectFilePath, testCode);
85 | // dispatch generated code to store to be seen on side panel
86 | viewTestCode(testCode);
87 | } else {
88 | const testCode = generateTestCode();
89 | // if user clicked on generat code before exporting code, it will be pulled from store
90 | exportTestCode(projectFilePath, testCode);
91 | viewTestCode(testCode);
92 | }
93 | };
94 |
95 | return (
96 |
97 |
98 | Export Test Code
99 |
100 |
101 | );
102 | };
103 |
--------------------------------------------------------------------------------
/src/components/DescribeBlock.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import { ItStatement } from "./ItStatement";
4 | import { PropLoader } from "./PropLoader";
5 | import {
6 | UpdateKeyOfIt,
7 | UpdateDescribe,
8 | UpdateComponentName,
9 | } from "../reduxComponents/actions/actions";
10 |
11 | interface Props {
12 | describeProp: string;
13 | removeDescribe: (id: number) => boolean;
14 | }
15 |
16 | export const DescribeBlock: React.FC = ({
17 | describeProp,
18 | removeDescribe,
19 | }) => {
20 | const globalDescribeObj = useSelector((state: any) => state.describes);
21 | const index = useSelector((state: any) => state.keyOfIt);
22 | const componentObj = useSelector((state: any) => state.componentObj);
23 |
24 | const [arrayOfIt, updateItArray] = useState([]);
25 |
26 | const dispatch = useDispatch();
27 |
28 | const updateItKey = () => dispatch(UpdateKeyOfIt());
29 | const updateGlobalDescribe = (data: any) => dispatch(UpdateDescribe(data));
30 | const updateComponentName = (name: string) =>
31 | dispatch(UpdateComponentName(name));
32 |
33 | const storeval: { [k: string]: any } = {};
34 | storeval[`${index}`] = "";
35 |
36 | useEffect(async (): Promise => {
37 | const itComponent: { [k: string]: any } = {};
38 | // creates a key value pair that will hold the index and the component
39 | itComponent[`${index}`] = await (
40 |
46 | );
47 | // update the array to be displayed with the component that was created
48 | updateItArray(arrayOfIt.concat(itComponent[`${index}`]));
49 | // update the key value of the it statements
50 | updateItKey();
51 | }, []);
52 |
53 | async function addIt() {
54 | const itComponent: { [k: string]: any } = {};
55 | itComponent[`${index}`] = await (
56 |
62 | );
63 | // add the index of the created it component to the object holding all describe blocks
64 | // globalDescribeObj[`${describeProp}`] = globalDescribeObj[`${describeProp}`].concat(index)
65 | globalDescribeObj[`${describeProp}`][index] = "";
66 | // updates the describe element in the store
67 | updateGlobalDescribe(globalDescribeObj);
68 | // updates the array to be displayed
69 | updateItArray(arrayOfIt.concat(itComponent[`${index}`]));
70 | // increment the number of it statements since one was added
71 | updateItKey();
72 | }
73 |
74 | function addComponentName(input: any): void {
75 | componentObj[describeProp] = input;
76 | updateComponentName(componentObj);
77 | }
78 |
79 | // removes the it block selected from the current describe block
80 | function removeIt(removeItId: number): boolean {
81 | // if there are more than one it statements then delete selected
82 | if (Object.keys(globalDescribeObj[describeProp]).length > 1) {
83 | delete globalDescribeObj[describeProp][`${removeItId}`];
84 | updateGlobalDescribe(globalDescribeObj);
85 | return true;
86 | }
87 | // if there is only one it statement then return false to notify that it cannot be deleted
88 | return false;
89 | }
90 |
91 | function removeDescribeComponent(): void {
92 | removeDescribe(parseInt(describeProp, 10));
93 | }
94 |
95 | return (
96 |
97 |
98 | X
99 |
100 |
101 |
Describe Block
102 |
addComponentName(e.target.value)}
106 | placeholder="Please enter component name:"
107 | />
108 |
109 |
115 | {arrayOfIt}
116 |
117 |
118 | + It Statement
119 |
120 |
121 | );
122 | };
123 |
--------------------------------------------------------------------------------
/src/components/ItStatement.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import {
4 | UpdateData,
5 | UpdateItObj,
6 | UpdateItStatement,
7 | } from "../reduxComponents/actions/actions";
8 | import { ExpectStatement } from "./ExpectStatement";
9 |
10 | interface Props {
11 | itProp: string;
12 | id: any;
13 | removeIt: (id: number) => boolean;
14 | }
15 |
16 | export const ItStatement: React.FC = ({ itProp, removeIt, id }) => {
17 | const dispatch = useDispatch();
18 | const index = useSelector((state: any) => state.keyOfExpect);
19 | const data = useSelector((state: any) => state.expects);
20 | const itObject = useSelector((state: any) => state.its);
21 | const updateIts = (data: any) => dispatch(UpdateItObj(data));
22 | const updateData = (data: any) => dispatch(UpdateData(data));
23 | const [arrayOfExpect, updateArray] = useState([]);
24 | const globalItInput = useSelector((state: any) => state.itInputObj);
25 | const updateItGlobal = (data: any) => dispatch(UpdateItStatement(data));
26 |
27 | const itKey = useSelector((state: any) => state.keyOfIt);
28 | const storeval: { [k: string]: any } = {};
29 | storeval[`${index}`] = "";
30 |
31 | useEffect(() => {
32 | // unsure about this typing and if piping would work ConcatArray | JSX.Element
33 | const x: { [k: string]: any } = {};
34 | x[`${index}`] = (
35 |
36 | );
37 | updateArray(arrayOfExpect.concat(x[`${index}`]));
38 | // update the store with the newly added it statement
39 | itObject[`${itKey}`] = storeval;
40 | updateIts(itObject);
41 | }, []);
42 |
43 | // adds an expect statement to the current it statement
44 | function addExpect() {
45 | const x: { [k: string]: any } = {};
46 | itObject[`${itProp}`][index] = "";
47 |
48 | x[`${index}`] = (
49 |
50 | );
51 | updateArray(arrayOfExpect.concat(x[index]));
52 | // create an object in to be passed into the store
53 | const newExpect: { [k: string]: any } = {};
54 | newExpect[`firstInput${index}`] = "type";
55 | newExpect.testTypes = "equal";
56 | newExpect[`lastInput${index}`] = "";
57 | // add the key value pair to the expect object in the store
58 | data[index] = newExpect;
59 |
60 | updateData(data);
61 | // updates the it object associated with the component to hold the key value of the child
62 | updateIts(itObject);
63 | }
64 |
65 | // remove an expect block from the it statement
66 | function removeExpect(removeId: number) {
67 | // if there are more than one then delete selected
68 | if (Object.keys(itObject[`${itProp}`]).length > 1) {
69 | delete itObject[`${itProp}`][`${removeId}`];
70 | updateIts(itObject);
71 | return true;
72 | }
73 | // if there are not more then one return false to notify that it cannot be deleted
74 | return false;
75 | }
76 |
77 | // updates the it statement value in the store
78 | const updateItStatement = (input: any) => {
79 | globalItInput[itProp] = input;
80 | updateItGlobal(globalItInput);
81 | };
82 |
83 | // removes the it statement from the current describe block
84 | function removeItStatement() {
85 | // id is the id of the it block that needs to be removed from the describe
86 | if (removeIt(id)) {
87 | // here we have to delete it blocks
88 | for (const itBlocks of Object.keys(itObject[`${itProp}`])) {
89 | delete data[`${itBlocks}`];
90 | }
91 | document.getElementById(`it-block ${itProp}`)?.remove();
92 | updateData(data);
93 | // delete the it block
94 | delete itObject[`${itProp}`];
95 | } else {
96 | console.log("cannot remove it block");
97 | }
98 | }
99 |
100 | return (
101 |
102 | {
106 | updateItStatement(e.target?.value);
107 | }}
108 | />
109 |
110 | X
111 |
112 | {arrayOfExpect}
113 |
114 | + Expect
115 |
116 |
117 | );
118 | };
119 |
--------------------------------------------------------------------------------
/src/reduxComponents/reducers/reducer.ts:
--------------------------------------------------------------------------------
1 | import * as types from "../constants/actionTypes";
2 |
3 | const initialState = {
4 | fileTree: [],
5 | fileToView: "",
6 | toggleFolder: {},
7 | filePathOfProject: "",
8 | generatedTestCode: "",
9 |
10 | describes: {},
11 | its: {},
12 | expects: {},
13 |
14 | keyOfDescribe: 0,
15 | keyOfIt: 0,
16 | keyOfExpect: 0,
17 | componentObj: {},
18 | itInputObj: {},
19 |
20 | describePropBoolean: {},
21 | codeViewer: false,
22 | codeViewerChecker: true,
23 | };
24 |
25 | export const reducer = (state: any = initialState, action: any) => {
26 | switch (action.type) {
27 | case types.CONSTRUCT_FILETREE:
28 | return {
29 | ...state,
30 | fileTree: action.payload,
31 | };
32 |
33 | case types.SET_FILE_VIEW:
34 | return {
35 | ...state,
36 | codeViewer: true,
37 | fileToView: action.payload,
38 | codeViewerChecker: true,
39 | };
40 |
41 | case types.TOGGLE_FOLDER:
42 | // creating an object that will hold the file path of each directory and will toggle from T to F
43 | // important as we can now keep track of whether that specifc directory/filepath has been clicked or not
44 | const toggleFolder = { ...state.toggleFolder };
45 | toggleFolder[action.payload] = !toggleFolder[action.payload];
46 | return {
47 | ...state,
48 | toggleFolder,
49 | };
50 |
51 | case types.UPDATE_KEY_OF_EXPECT:
52 | return {
53 | ...state,
54 | keyOfExpect: state.keyOfExpect + 1,
55 | };
56 |
57 | case types.UPDATE_KEY_OF_IT:
58 | return {
59 | ...state,
60 | keyOfIt: state.keyOfIt + 1,
61 | };
62 |
63 | case types.UPDATE_DATA:
64 | return {
65 | ...state,
66 | expects: action.payload,
67 | };
68 |
69 | case types.UPDATE_IT_OBJ:
70 | return {
71 | ...state,
72 | its: action.payload,
73 | };
74 |
75 | case types.UPDATE_KEY_OF_DESCRIBE:
76 | return {
77 | ...state,
78 | keyOfDescribe: state.keyOfDescribe + 1,
79 | };
80 |
81 | case types.UPDATE_DESCRIBE:
82 | return {
83 | ...state,
84 | describes: action.payload,
85 | };
86 |
87 | case types.UPDATE_COMPONENT_NAME:
88 | return {
89 | ...state,
90 | componentObj: action.payload,
91 | };
92 |
93 | case types.UPDATE_IT_STATEMENT:
94 | return {
95 | ...state,
96 | itInputObj: action.payload,
97 | };
98 |
99 | case types.CLEAR_FILE:
100 | return {
101 | ...state,
102 | codeViewer: false,
103 | codeViewerChecker: false,
104 | fileToView: "",
105 | };
106 |
107 | case types.DELETE_EXPECT:
108 | const { expects } = state;
109 | const id = action.payload.toString();
110 | delete expects[`${id}`];
111 | return {
112 | ...state,
113 | expects,
114 | };
115 |
116 | case types.REMOVE_FROM_IT:
117 | return {
118 | ...state,
119 | };
120 |
121 | case types.SET_PROJECT_PATH:
122 | return {
123 | ...state,
124 | filePathOfProject: action.payload,
125 | };
126 |
127 | case types.REFRESH_STATE:
128 | return {
129 | fileTree: [],
130 | fileToView: "",
131 | toggleFolder: {},
132 | filePathOfProject: "",
133 | generatedTestCode: "",
134 |
135 | describes: {},
136 | its: {},
137 | expects: {},
138 |
139 | keyOfDescribe: 0,
140 | keyOfIt: 0,
141 | keyOfExpect: 0,
142 | componentObj: {},
143 | itInputObj: {},
144 | describePropBoolean: {},
145 | codeViewer: false,
146 | codeViewerChecker: true,
147 | };
148 |
149 | case types.UPDATE_DESCRIBE_BOOLEAN:
150 | return {
151 | ...state,
152 | describePropBoolean: action.payload,
153 | };
154 |
155 | case types.SHOW_TESTCODE:
156 | return {
157 | ...state,
158 | codeViewer: true,
159 | codeViewerChecker: false,
160 | generatedTestCode: action.payload,
161 | };
162 |
163 | case types.REMOVE_TESTCODE:
164 | return {
165 | ...state,
166 | codeViewer: false,
167 | codeViewerChecker: true,
168 | generatedTestCode: "",
169 | };
170 |
171 | default:
172 | return state;
173 | }
174 | };
175 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "catalyst",
3 | "productName": "Catalyst",
4 | "version": "0.0.1",
5 | "description": "Catalyst will simplify the generation of unit tests for React framework with an emphasis on enzyme",
6 | "main": "dist/main.js",
7 | "scripts": {
8 | "dev:electron": "cross-env NODE_ENV=development webpack --config webpack.electron.config.js --mode development && electron .",
9 | "dev:react": "cross-env NODE_ENV=development webpack serve --config webpack.react.config.js --mode development",
10 | "prod:electron": "cross-env NODE_ENV=production webpack --config webpack.electron.config.js --mode production && electron .",
11 | "prod:react": "cross-env NODE_ENV=production webpack --config webpack.react.config.js --mode production",
12 | "dev": " concurrently \"npm run dev:electron\" \"npm run dev:react\"",
13 | "prod": "npm run prod:react && npm run prod:electron",
14 | "test": "jest",
15 | "testall": "jest --converge",
16 | "build-installer": "electron-builder",
17 | "format": "prettier --write src/**/*.{js,jsx,ts,tsx}",
18 | "lint": "eslint --fix src/**/*.{js,jsx,ts,tsx}"
19 | },
20 | "build": {
21 | "appId": "com.oslabs.catalyst",
22 | "productName": "Catalyst",
23 | "directories": {
24 | "output": "builder",
25 | "buildResources": "assets"
26 | },
27 | "files": [
28 | "dist/**/*",
29 | "node_modules/**/*",
30 | "assets/**/*"
31 | ],
32 | "win": {
33 | "target": [
34 | "nsis"
35 | ],
36 | "icon": "/assets/icon.png"
37 | },
38 | "nsis": {
39 | "oneClick": false,
40 | "allowToChangeInstallationDirectory": true
41 | },
42 | "mac": {
43 | "target": [
44 | "dmg"
45 | ],
46 | "icon": "assets/icon.png",
47 | "category": "public.app-category.Developer-tools",
48 | "identity": null
49 | },
50 | "dmg": {
51 | "background": "assets/CatalystInstallBG-05.tif",
52 | "icon": "assets/icon.png",
53 | "iconSize": 100,
54 | "window": {
55 | "width": 544,
56 | "height": 408
57 | },
58 | "contents": [
59 | {
60 | "x": 105,
61 | "y": 250
62 | },
63 | {
64 | "x": 440,
65 | "y": 250,
66 | "type": "link",
67 | "path": "/Applications"
68 | }
69 | ]
70 | }
71 | },
72 | "repository": {
73 | "type": "git",
74 | "url": "git+https://github.com/chungdanny64/Catalyst.git"
75 | },
76 | "keywords": [],
77 | "author": "Catalyst",
78 | "license": "MIT",
79 | "bugs": {
80 | "url": "https://github.com/chungdanny64/Catalyst/issues"
81 | },
82 | "dependencies": {
83 | "@babel/runtime": "^7.12.5",
84 | "@types/jest": "^26.0.15",
85 | "@types/react": "^16.9.56",
86 | "@types/react-dom": "^16.9.9",
87 | "@types/react-select": "^3.0.27",
88 | "concurrently": "^5.3.0",
89 | "cross-env": "^7.0.2",
90 | "dotenv": "^8.2.0",
91 | "html-webpack-plugin": "^4.5.0",
92 | "jest": "^26.6.3",
93 | "jquery": "^3.5.1",
94 | "react": "^17.0.1",
95 | "react-dom": "^17.0.1",
96 | "react-redux": "^7.2.2",
97 | "react-select": "^3.1.1",
98 | "redux": "^4.0.5",
99 | "redux-devtools-extension": "^2.13.8",
100 | "typescript": "^4.0.5"
101 | },
102 | "devDependencies": {
103 | "@babel/core": "^7.12.3",
104 | "@babel/plugin-proposal-class-properties": "^7.12.1",
105 | "@babel/plugin-proposal-object-rest-spread": "^7.12.1",
106 | "@babel/plugin-transform-runtime": "^7.12.1",
107 | "@babel/preset-env": "^7.12.1",
108 | "@babel/preset-react": "^7.12.5",
109 | "@babel/preset-typescript": "^7.12.1",
110 | "@types/enzyme": "^3.10.8",
111 | "@types/jquery": "^3.5.4",
112 | "@types/react-redux": "^7.1.11",
113 | "@wojtekmaj/enzyme-adapter-react-17": "^0.3.1",
114 | "babel-loader": "^8.2.1",
115 | "css-loader": "^5.0.1",
116 | "electron": "^11.0.4",
117 | "electron-builder": "^22.9.1",
118 | "electron-devtools-installer": "^3.1.1",
119 | "enzyme": "^3.11.0",
120 | "enzyme-to-json": "^3.6.1",
121 | "eslint": "^7.15.0",
122 | "eslint-config-airbnb-typescript-prettier": "^4.1.0",
123 | "file-loader": "^6.2.0",
124 | "prettier": "^2.2.1",
125 | "sass": "^1.30.0",
126 | "sass-loader": "^10.1.0",
127 | "style-loader": "^2.0.0",
128 | "ts-jest": "^26.4.4",
129 | "ts-loader": "^8.0.11",
130 | "typings-for-css-modules-loader": "^1.7.0",
131 | "url-loader": "^4.1.1",
132 | "webpack": "^5.10.0",
133 | "webpack-cli": "^4.2.0",
134 | "webpack-dev-server": "^3.11.0"
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/components/FolderUpload.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { remote } from "electron";
3 | import * as electronFs from "fs";
4 | import { useDispatch } from "react-redux";
5 | import {
6 | ConstructFileTree,
7 | SetProjectPath,
8 | } from "../reduxComponents/actions/actions";
9 | import catalystLogo from "../../assets/catalyst_icons/Catalystfull-02.png";
10 |
11 | export class FileTree {
12 | filepath: string;
13 |
14 | name: string;
15 |
16 | children: FileTree[];
17 |
18 | constructor(filepath: string, name: string) {
19 | this.filepath = filepath;
20 | this.name = name;
21 | this.children = [];
22 | }
23 |
24 | // directory will be root directory path
25 | createTree(directory: string): FileTree[] {
26 | let treeElements: FileTree[] = [];
27 |
28 | // readdirSync will return all the names of the files in the directory as an array
29 | electronFs.readdirSync(directory).forEach((fileName: string) => {
30 | if (
31 | fileName !== ".git" &&
32 | fileName !== "node_modules" &&
33 | fileName !== "dist" &&
34 | fileName !== "assets" &&
35 | fileName !== ".DS_Store" &&
36 | fileName !== ".eslintrc" &&
37 | fileName !== "README.md" &&
38 | fileName !== "package-lock.json" &&
39 | fileName !== "package.json" &&
40 | fileName !== ".gitignore" &&
41 | fileName !== "build" &&
42 | fileName !== ".vscode" &&
43 | fileName !== "webpack.config"
44 | ) {
45 | const fileInfo = new FileTree(`${directory}/${fileName}`, fileName);
46 |
47 | // statsync will return an object with properties of the filepath indicated
48 | const moreFileInfo = electronFs.statSync(fileInfo.filepath);
49 |
50 | // if the file that is currently at is a directory it will append the children to it
51 | if (moreFileInfo.isDirectory()) {
52 | fileInfo.children = fileInfo.createTree(fileInfo.filepath);
53 | }
54 |
55 | treeElements.push(fileInfo);
56 | }
57 | });
58 | return treeElements;
59 | }
60 | }
61 |
62 | export const FolderUpload: React.FC = () => {
63 | const dispatch = useDispatch();
64 | // creates dispatch that will send file path as an action payload to reducer
65 | const constructFileTree = (files: any) => dispatch(ConstructFileTree(files));
66 | const setFilePath = (filePath: any) => dispatch(SetProjectPath(filePath));
67 |
68 | const { dialog } = remote;
69 | async function uploadFolder() {
70 | // allows users to upload a folder
71 | const Project = await dialog.showOpenDialog(
72 | // sets opendirectory (allows directories to be selected) to be the feature that dialog uses
73 | {
74 | properties: ["openDirectory"],
75 | // the types of files that will be displayed or selected
76 | filters: [
77 | { name: "Javascript Files", extensions: ["js", "jsx"] },
78 | { name: "Typescript Files", extensions: ["ts", "tsx"] },
79 | { name: "HTML Files", extensions: ["html"] },
80 | ],
81 | message: "Choose a Project to Create Tests for:",
82 | }
83 | );
84 |
85 | // if the user cancels the action then undefined will be returned
86 | // if the user successfully completes the action then a string array will be returned
87 | // Project is an object that holds canceled (boolean to check if it was cancelled) and filePaths (array of filepaths)
88 | if (Project && !Project.canceled) {
89 | // holds the directory of the project that was selected
90 | let projectDirectory = Project.filePaths[0];
91 |
92 | // use regex to find all \ in the case of a windows user and replace with /
93 | projectDirectory = projectDirectory.replace(/\\/g, "/");
94 |
95 | // will create a new FileTree object for the root directory
96 | const rootTree = new FileTree(projectDirectory, "root");
97 |
98 | // will return an array of FileTree objects along with any children associated with it
99 | const fileTree = rootTree.createTree(projectDirectory);
100 | // dispatches fileTree to reducer
101 | constructFileTree(fileTree);
102 | // setting file path of project to access later by sending projectDirectory to store
103 | setFilePath(projectDirectory);
104 | }
105 | }
106 |
107 | return (
108 |
109 |
110 |
111 |
112 |
113 |
Make tests quickly
114 |
No coding necessary
115 |
116 | Get Started
117 |
118 |
119 |
120 |
121 |
122 |
123 | );
124 | };
125 |
--------------------------------------------------------------------------------
/src/components/TestBuilder.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import { DescribeBlock } from "./DescribeBlock";
4 | import {
5 | UpdateItObj,
6 | UpdateKeyOfDescribe,
7 | UpdateData,
8 | UpdateDescribe,
9 | UpdateDescribeBoolean,
10 | } from "../reduxComponents/actions/actions";
11 |
12 | export const TestBuilder: React.FC = () => {
13 | const dispatch = useDispatch();
14 |
15 | const describesFromStore = useSelector((state: any) => state.describes);
16 | const itFromStore = useSelector((state: any) => state.its);
17 | const expectFromStore = useSelector((state: any) => state.expects);
18 | const describeIndex = useSelector((state: any) => state.keyOfDescribe);
19 | const itIndex = useSelector((state: any) => state.keyOfIt);
20 | const describeBool = useSelector((state: any) => state.describePropBoolean);
21 | const fileTree = useSelector((state: any) => state.fileTree);
22 | const propBool = useSelector((state: any) => state.describePropBoolean);
23 |
24 | const updateDescribeIndex = () => dispatch(UpdateKeyOfDescribe());
25 | const updateIt = (data: any) => dispatch(UpdateItObj(data));
26 | const updateExpects = (data: any) => dispatch(UpdateData(data));
27 | const updateDescribe = (data: any) => dispatch(UpdateDescribe(data));
28 | const updateDescribeBool = (data: any) =>
29 | dispatch(UpdateDescribeBoolean(data));
30 |
31 | const [describes, updateDescribes] = useState([]);
32 |
33 | const storeval: { [k: string]: any } = {};
34 | storeval[`${itIndex}`] = "";
35 |
36 | useEffect(() => {
37 | const x: { [k: string]: any } = {};
38 | // create a new Describe block to be rendered. Will be inital describe
39 | x[`${describeIndex}`] = (
40 |
47 | );
48 | // add the describe block to be mapped later
49 | updateDescribes(describes.concat(x[`${describeIndex}`]));
50 | // adds the initial it statement key to the describe object in store to keep track of children components
51 | describesFromStore[`${describeIndex}`] = storeval;
52 | describeBool[`${describeIndex}`] = false;
53 | // updates the index of the it and describe because each has been added to the store
54 | updateDescribeIndex();
55 | updateDescribeBool(describeBool);
56 | // making fileTree as its dependency in order to re-render describe block if new project is chosen
57 | }, [fileTree]);
58 |
59 | function addDescribe() {
60 | const x: { [k: string]: any } = {};
61 | x[describeIndex] = (
62 |
69 | );
70 | // adds the describe block to the array of describe blocks to be rendered
71 | updateDescribes(describes.concat(x[`${describeIndex}`]));
72 | describesFromStore[`${describeIndex}`] = storeval;
73 | // creates a boolean value set to false within the describe prop boolean object in store
74 | describeBool[`${describeIndex}`] = false;
75 | // update the store
76 | updateDescribeBool(describeBool);
77 | updateDescribeIndex();
78 | }
79 |
80 | function removeDescribe(id: number): boolean {
81 | // if there are multiple describe blocks then delete current one selected
82 | if (Object.keys(describesFromStore).length > 1) {
83 | // delete all expect and it statements associated with the describe block from the store
84 | for (const it of Object.keys(describesFromStore[`${id}`])) {
85 | for (const expect of Object.keys(itFromStore[`${it}`])) {
86 | delete expectFromStore[`${expect}`];
87 | }
88 | delete itFromStore[`${it}`];
89 | updateExpects(expectFromStore);
90 | updateIt(itFromStore);
91 | }
92 | // deletes the describe key from the store in the describes object
93 | delete describesFromStore[`${id}`];
94 | // deletes the prop boolean key from the store describe prop boolean object
95 | delete propBool[`${id}`];
96 | updateDescribe(describesFromStore);
97 | // remove the describe block and all its children
98 | document.getElementById(`describeBlock${id}`)?.remove();
99 | return true;
100 | }
101 | // if there is only one block left then cannot delete
102 | console.log("cannot delete describe block");
103 | return false;
104 | }
105 |
106 | return (
107 |
108 |
109 | {describes}
110 |
111 |
112 |
113 | + Describe Block
114 |
115 |
116 |
117 | );
118 | };
119 |
--------------------------------------------------------------------------------
/src/utils/globalCodeGenerator.ts:
--------------------------------------------------------------------------------
1 | import * as electronFs from "fs";
2 | import { store } from "../reduxComponents/store";
3 |
4 | export function generateTestCode() {
5 | const currentState = store.getState();
6 |
7 | const describeGlobal = currentState.describes;
8 | const itsGlobal = currentState.its;
9 | const expectGlobal = currentState.expects;
10 | const describeInputGlobal = currentState.componentObj;
11 | const itInputGlobal = currentState.itInputObj;
12 | const { describePropBoolean } = currentState;
13 | const { fileTree } = currentState;
14 |
15 | const keysOfDescribe = Object.keys(describeGlobal);
16 |
17 | function findFile(fileTree: any, name: string): string {
18 | for (const x of fileTree) {
19 | const file = electronFs.statSync(x.filepath);
20 | if (file.isDirectory()) {
21 | const find = findFile(x.children, name);
22 | if (find !== "") {
23 | return find;
24 | }
25 | } else if (
26 | x.name.toLowerCase().includes(name) &&
27 | !x.name.toLowerCase().includes("_")
28 | ) {
29 | return x.filepath;
30 | }
31 | }
32 | return "";
33 | }
34 |
35 | // console.log(tree)
36 | let finalString = "";
37 | finalString += `import React from 'react';\nimport { configure, shallow } from 'enzyme';\nimport Adapter from 'enzyme-adapter-react-16';\n\nconfigure({ adapter: new Adapter() });\n\n`;
38 |
39 | // inserts the correct import statement for each component
40 | for (const i of keysOfDescribe) {
41 | let fileLocation = findFile(
42 | fileTree,
43 | `${describeInputGlobal[i]}`.trim().toLowerCase()
44 | );
45 | if (fileLocation !== "") {
46 | // let relativePath = path.relative(process.cwd(), fileLocation);
47 | // console.log(relativePath, 'this is relativePath');
48 | fileLocation = fileLocation.replace(".jsx", "");
49 | finalString += `import ${describeInputGlobal[i]} from \'${fileLocation}\'; \n\n`;
50 | }
51 | }
52 |
53 | // obtains all the prop fields from the dom and puts it into an array of html elements
54 | const allProps: HTMLCollectionOf = document.getElementsByClassName(
55 | "Prop"
56 | );
57 | // counter for the array of allProps
58 | let counter = 0;
59 |
60 | // loop all the describe blocks
61 | for (const i of keysOfDescribe) {
62 | finalString += `describe('${describeInputGlobal[i]}', () => {\n\tlet wrapper; \n\n`;
63 |
64 | // if the describe block should have props then retrieve it from the allProps object, if no then skip
65 | if (
66 | describePropBoolean[i] &&
67 | allProps[counter].getElementsByClassName("propChild").length > 0
68 | ) {
69 | finalString += `\tconst props = { \n`;
70 | for (const element of allProps[counter].getElementsByClassName(
71 | "propChild"
72 | )) {
73 | if (
74 | element.getElementsByTagName("input")[0].value === "" &&
75 | element.getElementsByTagName("input")[1].value === ""
76 | ) {
77 | continue;
78 | } else {
79 | finalString += `\t\t${
80 | element.getElementsByTagName("input")[0].value
81 | } : ${element.getElementsByTagName("input")[1].value}, \n`;
82 | }
83 | }
84 |
85 | finalString += `\t}; \n\n`;
86 | finalString += `\tbeforeAll(() => {\n\t\twrapper = shallow(<${describeInputGlobal[i]} {...props}/>);\n \t}); \n`;
87 | } else {
88 | finalString += `\tbeforeAll(() => {\n\t\twrapper = shallow(<${describeInputGlobal[i]}/>)\n \t}); \n`;
89 | }
90 | counter += 1;
91 | // loop through all the it statements that should be within the specidfied describe block
92 | for (const j of Object.keys(describeGlobal[i])) {
93 | finalString += `\n\tit('${itInputGlobal[j]}', () => { \n`;
94 | // loop through all of the expect statements that should be within the specific expect block
95 | for (const expect of Object.keys(itsGlobal[j])) {
96 | finalString += `\t\texpect(wrapper`;
97 | // loop through all of the selectors that exist
98 | for (const element of Object.values(expectGlobal[expect].selectors)) {
99 | // if the selector holds an object then print out the key with the value inside the ()
100 | if (typeof element === "object") {
101 | // checking if selector value is empty but still needs to be chained
102 | if (Object.keys(element)[0] && Object.values(element)[0] === "") {
103 | finalString += `${Object.keys(element)[0]}()`;
104 | } else {
105 | finalString += `${Object.keys(element)[0]}(${
106 | Object.values(element)[0]
107 | })`;
108 | }
109 | }
110 | // if the selector does not hold a string then append the string
111 | else if (element !== ".nothing") {
112 | finalString += `${element}()`;
113 | }
114 | }
115 | finalString += `)${expectGlobal[expect].testTypes}(${
116 | expectGlobal[expect][`lastInput${expect}`]
117 | });\n`;
118 | }
119 | finalString += "\t});\n";
120 | }
121 | finalString += "});\n";
122 | }
123 |
124 | return finalString;
125 | }
126 |
--------------------------------------------------------------------------------
/src/components/TestBlock.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import * as electronFs from "fs";
4 | import catalystIcon from "../../assets/catalyst_icons/Catalyst-01.png";
5 | import { ViewTestCode } from "../reduxComponents/actions/actions";
6 | import { ReuploadDirectory } from "./ReuploadDirectory";
7 | import { ExportTestCode } from "./ExportTestCode";
8 |
9 | export const TestBlock: React.FC = () => {
10 | const describeGlobal = useSelector((state: any) => state.describes);
11 | const itsGlobal = useSelector((state: any) => state.its);
12 | const expectGlobal = useSelector((state: any) => state.expects);
13 | const describeInputGlobal = useSelector((state: any) => state.componentObj);
14 | const itInputGlobal = useSelector((state: any) => state.itInputObj);
15 | const describePropBoolean = useSelector(
16 | (state: any) => state.describePropBoolean
17 | );
18 | const fileTree = useSelector((state: any) => state.fileTree);
19 |
20 | const dispatch = useDispatch();
21 | // generated testCode from GUI is dispactched to store to be displayed in viewer
22 | const viewTestCode = (data: string) => dispatch(ViewTestCode(data));
23 |
24 | function findFile(fileTree: any, name: string): string {
25 | for (const x of fileTree) {
26 | const file = electronFs.statSync(x.filepath);
27 | if (file.isDirectory()) {
28 | const find = findFile(x.children, name);
29 | if (find !== "") {
30 | return find;
31 | }
32 | } else if (
33 | x.name.toLowerCase().includes(name) &&
34 | !x.name.toLowerCase().includes("_")
35 | ) {
36 | return x.filepath;
37 | }
38 | }
39 | return "";
40 | }
41 |
42 | function handleClick(): void {
43 | const keysOfDescribe = Object.keys(describeGlobal);
44 |
45 | let finalString = "";
46 | finalString += `import React from 'react';\nimport { configure, shallow } from 'enzyme';\nimport Adapter from 'enzyme-adapter-react-16';\n\nconfigure({ adapter: new Adapter() });\n\n`;
47 |
48 | // inserts the correct import statement for each component
49 | for (const i of keysOfDescribe) {
50 | let fileLocation = findFile(
51 | fileTree,
52 | `${describeInputGlobal[i]}`.trim().toLowerCase()
53 | );
54 | if (fileLocation !== "") {
55 | fileLocation = fileLocation.replace(".jsx", "");
56 | finalString += `import ${describeInputGlobal[i]} from \'${fileLocation}\'; \n\n`;
57 | }
58 | }
59 |
60 | // obtains all the prop fields from the dom and puts it into an array of html elements
61 | const allProps: HTMLCollectionOf = document.getElementsByClassName(
62 | "Prop"
63 | );
64 | // counter for the array of allProps
65 | let counter = 0;
66 |
67 | // loop all the describe blocks
68 | for (const i of keysOfDescribe) {
69 | finalString += `describe('${describeInputGlobal[i]}', () => {\n\tlet wrapper; \n\n`;
70 |
71 | // if the describe block should have props then retrieve it from the allProps object, if no then skip
72 | if (
73 | describePropBoolean[i] &&
74 | allProps[counter].getElementsByClassName("propChild").length > 0
75 | ) {
76 | finalString += `\tconst props = { \n`;
77 | for (const element of allProps[counter].getElementsByClassName(
78 | "propChild"
79 | )) {
80 | if (
81 | element.getElementsByTagName("input")[0].value === "" &&
82 | element.getElementsByTagName("input")[1].value === ""
83 | ) {
84 | continue;
85 | } else {
86 | finalString += `\t\t${
87 | element.getElementsByTagName("input")[0].value
88 | } : ${element.getElementsByTagName("input")[1].value}, \n`;
89 | }
90 | }
91 |
92 | finalString += `\t}; \n\n`;
93 | finalString += `\tbeforeAll(() => {\n\t\twrapper = shallow(<${describeInputGlobal[i]} {...props}/>);\n \t}); \n`;
94 | } else {
95 | finalString += `\tbeforeAll(() => {\n\t\twrapper = shallow(<${describeInputGlobal[i]}/>)\n \t}); \n`;
96 | }
97 | counter += 1;
98 | // loop through all the it statements that should be within the specidfied describe block
99 | for (const j of Object.keys(describeGlobal[i])) {
100 | finalString += `\n\tit('${itInputGlobal[j]}', () => { \n`;
101 | // loop through all of the expect statements that should be within the specific expect block
102 | for (const expect of Object.keys(itsGlobal[j])) {
103 | finalString += `\t\texpect(wrapper`;
104 | // loop through all of the selectors that exist
105 | for (const element of Object.values(expectGlobal[expect].selectors)) {
106 | // if the selector holds an object then print out the key with the value inside the ()
107 | if (typeof element === "object") {
108 | // checking if selector value is empty but still needs to be chained
109 | if (Object.keys(element)[0] && Object.values(element)[0] === "") {
110 | finalString += `${Object.keys(element)[0]}()`;
111 | } else {
112 | finalString += `${Object.keys(element)[0]}(${
113 | Object.values(element)[0]
114 | })`;
115 | }
116 | }
117 | // if the selector does not hold a string then append the string
118 | else if (element !== ".nothing") {
119 | finalString += `${element}()`;
120 | }
121 | }
122 | finalString += `)${expectGlobal[expect].testTypes}(${
123 | expectGlobal[expect][`lastInput${expect}`]
124 | });\n`;
125 | }
126 | finalString += "\t});\n";
127 | }
128 | finalString += "});\n";
129 | }
130 | // dispatches generated test code to store for global usage
131 | viewTestCode(finalString);
132 | }
133 |
134 | return (
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | Generate Tests
145 |
146 |
147 |
148 |
149 |
150 |
151 |
180 |
181 |
182 | );
183 | };
184 |
--------------------------------------------------------------------------------
/src/components/ExpectStatement.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import {
4 | UpdateData,
5 | UpdateKeyOfExpect,
6 | deleteExpect,
7 | } from "../reduxComponents/actions/actions";
8 |
9 | interface Props {
10 | id: string;
11 | remove: (x: number) => boolean;
12 | }
13 |
14 | export const ExpectStatement: React.FC = ({ id, remove }) => {
15 | const dispatch = useDispatch();
16 | let counter = 0;
17 | let first: number;
18 | const data = useSelector((state: any) => state.expects);
19 | const index = useSelector((state: any) => state.keyOfExpect);
20 |
21 | const updateData = (data: any) => dispatch(UpdateData(data));
22 | const updateExpectKey = () => dispatch(UpdateKeyOfExpect());
23 | const deleteExpectFromStore = (data: any) => dispatch(deleteExpect(data));
24 |
25 | useEffect(() => {
26 | data[index] = {};
27 | data[index].testTypes = ".toEqual";
28 | data[index][`lastInput${index}`] = "";
29 | data[index].selectors = {};
30 | data[index].selectors[`expect${index}selector0`] = ".type";
31 | updateData(data);
32 | updateExpectKey();
33 | }, []);
34 |
35 | // removes the expect block selected
36 | function removeExpect() {
37 | // uses remove prop from ItStatement componene to see if the user is able to delete
38 | if (remove(parseInt(id, 10))) {
39 | deleteExpectFromStore(id);
40 | document.getElementById(`expect-block ${id}`)?.remove();
41 | }
42 | // if there is only one expect block then do not delete
43 | else {
44 | // eslint-disable-next-line no-console
45 | console.log("cannot remove expect block");
46 | }
47 | }
48 |
49 | // when a selector is changed then create new fields
50 | function handleChange(
51 | event:
52 | | React.ChangeEvent
53 | | React.ChangeEvent
54 | ) {
55 | const block = document.getElementById(id);
56 | // if the selector is set to find then append a new text box and selector
57 | if (
58 | event.target?.value === ".find" ||
59 | event.target?.value === ".contains" ||
60 | event.target?.value === ".every" ||
61 | event.target?.value === ".everyWhere" ||
62 | event.target?.value === ".hasClass" ||
63 | event.target?.value === ".exists" ||
64 | event.target?.value === ".forEach" ||
65 | event.target?.value === ".is" ||
66 | event.target?.value === ".at" ||
67 | event.target?.value === ".simulate" ||
68 | event.target?.value === ".prop" ||
69 | event.target?.value === ".tap" ||
70 | event.target?.value === ".some" ||
71 | event.target?.value === ".name" ||
72 | event.target?.value === ".isEmptyRender" ||
73 | event.target?.value === ".first" ||
74 | event.target?.value === ".get" ||
75 | event.target?.value === ".getElements" ||
76 | event.target?.value === ".hostNodes"
77 | ) {
78 | if (event.target?.id === `expect${id}selector0`) {
79 | first = counter;
80 | }
81 | // will keep value of prop key if user wants to change key again
82 | if (typeof data[`${id}`].selectors[`${event.target?.id}`] === "object") {
83 | const value = Object.values(
84 | data[`${id}`].selectors[`${event.target?.id}`]
85 | )[0];
86 | data[`${id}`].selectors[`${event.target?.id}`] = {};
87 | data[`${id}`].selectors[`${event.target?.id}`][
88 | `${event.target?.value}`
89 | ] = value;
90 | } else {
91 | data[`${id}`].selectors[`${event.target?.id}`] = {};
92 | data[`${id}`].selectors[`${event.target?.id}`][
93 | `${event.target?.value}`
94 | ] = "";
95 | // creates a text box for the input
96 | const child = document.createElement("input");
97 | child.id = `expect${id}input${counter}`;
98 | child.className = "inputbox";
99 | child.type = "text";
100 | child.placeholder = "Selector optional";
101 |
102 | child.onchange = () => {
103 | // eslint-disable-next-line @typescript-eslint/no-use-before-define
104 | inputText(event.target?.id, event.target?.value);
105 | };
106 |
107 | // eslint-disable-next-line no-plusplus
108 | counter++;
109 | // creates a selector for the next portion
110 | const secondSelector = document.createElement("select");
111 | secondSelector.id = `expect${id}selector${counter}`;
112 | secondSelector.className = "expectdrop1";
113 | secondSelector.innerHTML = `
114 |
115 | type
116 | text
117 | to find
118 | to exist
119 | contains
120 | every
121 | everyWhere(fn)
122 | has class
123 | forEach(fn)
124 | is
125 | at
126 | simulate
127 | prop
128 | tap(interceptor)
129 | some
130 | name
131 | is empty render
132 | first
133 | get
134 | get elements
135 | host nodes `;
136 | data[`${id}`].selectors[`expect${id}selector${counter}`] = ".nothing";
137 | secondSelector.onchange = handleChange;
138 |
139 | // create line break
140 | const linebreak = document.createElement("br");
141 |
142 | if (block) {
143 | block.appendChild(child);
144 | block.appendChild(linebreak);
145 | block.appendChild(secondSelector);
146 | }
147 | }
148 | }
149 | // select a new option for the selector
150 | else {
151 | const breaks = document.getElementsByTagName("br");
152 | let breaksLength = breaks.length;
153 | data[`${id}`].selectors[`${event.target?.id}`] = event.target?.value;
154 | let checker = false;
155 | // if the value changed was for the first selector
156 | if (event.target?.id === `expect${id}selector0`) {
157 | document.getElementById(`expect${id}input${first}`)?.remove();
158 | // eslint-disable-next-line no-plusplus
159 | breaks[breaksLength--]?.remove();
160 | }
161 |
162 | // delete all elements that were appended after the selector chosen
163 | for (const keys of Object.keys(data[`${id}`].selectors)) {
164 | // checker will be true when the correct identifier is found
165 | // all values after that identifier will be removed from the store
166 | if (checker) {
167 | delete data[`${id}`].selectors[`${keys}`];
168 | document.getElementById(`${keys}`)?.remove();
169 | document
170 | .getElementById(`${keys}`.replace("selector", "input"))
171 | ?.remove();
172 | // eslint-disable-next-line no-plusplus
173 | breaks[breaksLength--]?.remove();
174 | // eslint-disable-next-line no-plusplus
175 | counter--;
176 | } else if (keys === event.target.id) {
177 | checker = true;
178 | document
179 | .getElementById(`${keys}`.replace("selector", "input"))
180 | ?.remove();
181 | // eslint-disable-next-line no-plusplus
182 | breaks[breaksLength--]?.remove();
183 | }
184 | }
185 | }
186 | // updates the elements in the store
187 | updateData(data);
188 | }
189 |
190 | // updates the last input box in the store
191 | function updateInput(
192 | event:
193 | | React.ChangeEvent
194 | | React.ChangeEvent
195 | ) {
196 | data[`${id}`][`${event.target?.id}`] = event.target?.value;
197 | updateData(data);
198 | }
199 |
200 | // updates the last selector value (expectdrop2) in the store
201 | function updateLastSelector(event: any) {
202 | data[`${id}`][`${event.target?.id}`] = event.target?.value;
203 | updateData(data);
204 | }
205 |
206 | // sets the input box text as the value of the object associated with the selector
207 | function inputText(elementId: string, elementKey: string) {
208 | const text = (document.getElementById(
209 | elementId.replace("selector", "input")
210 | ) as HTMLInputElement).value;
211 | data[`${id}`].selectors[`${elementId}`][`${elementKey}`] = text;
212 | updateData(data);
213 | }
214 |
215 | return (
216 |
217 |
218 | expect wrapper
219 |
220 | X
221 |
222 |
223 |
228 | type
229 | text
230 | to find
231 | to exist
232 | contains
233 | every
234 | everyWhere(fn)
235 | has class
236 | forEach(fn)
237 | is
238 | at
239 | simulate
240 | prop
241 | tap(interceptor)
242 | some
243 | name
244 | is empty render
245 | is empty
246 | first
247 | get
248 | get elements
249 | host nodes
250 |
251 |
252 |
253 |
254 |
259 | to Equal
260 | to Match
261 | to Be
262 | to Have Length
263 |
264 |
265 |
266 |
267 | );
268 | };
269 |
--------------------------------------------------------------------------------