├── .gitignore ├── my-app ├── src │ ├── emptyFile.cy.js │ ├── UserTests │ │ ├── TestBlock.cy.js │ │ └── UserTestFile.cy.js │ ├── types │ │ ├── ImportObj.ts │ │ └── Tree.ts │ ├── index.css │ ├── preload.ts │ ├── index.html │ ├── main.tsx │ ├── getNonce.ts │ ├── components │ │ ├── FormWrapper.tsx │ │ ├── useMultiStepForm.ts │ │ ├── SelectAction.tsx │ │ ├── TestGenContainer.tsx │ │ ├── App.tsx │ │ ├── SmallerPreviewPopup.tsx │ │ ├── Flow Components │ │ │ ├── CustomNode.tsx │ │ │ ├── HtmlCustomNode.tsx │ │ │ ├── Flow.tsx │ │ │ └── HtmlFlow.tsx │ │ ├── GetFile.tsx │ │ ├── PreviewPopup.tsx │ │ ├── ButtonComponent.tsx │ │ ├── TestingUi.tsx │ │ ├── DescribePage.tsx │ │ ├── ItBlockPage.tsx │ │ ├── DropdownButton.tsx │ │ ├── DynamicModal.tsx │ │ ├── Webview.tsx │ │ └── StatementPage.tsx │ ├── options │ │ ├── optionVariables.ts │ │ ├── assertionOptions.ts │ │ ├── actionOptions.ts │ │ └── otherCommandOptions.ts │ ├── renderer.ts │ ├── Routes │ │ ├── Home.tsx │ │ └── MainPage.tsx │ ├── index.ts │ └── parser.ts ├── logo.png ├── icons │ ├── icon.ico │ ├── icon.png │ └── icon.icns ├── postcss.config.js ├── .eslintrc.json ├── webpack.renderer.config.ts ├── tsconfig.json ├── tailwind.config.js ├── webpack.plugins.ts ├── webpack.main.config.ts ├── webpack.rules.ts ├── testing │ └── test2.js ├── forge.config.ts ├── .gitignore └── package.json ├── .DS_Store ├── package.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /my-app/src/emptyFile.cy.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /my-app/src/UserTests/TestBlock.cy.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Cydekick/HEAD/.DS_Store -------------------------------------------------------------------------------- /my-app/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Cydekick/HEAD/my-app/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@types/node": "^20.6.3" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /my-app/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Cydekick/HEAD/my-app/icons/icon.ico -------------------------------------------------------------------------------- /my-app/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Cydekick/HEAD/my-app/icons/icon.png -------------------------------------------------------------------------------- /my-app/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Cydekick/HEAD/my-app/icons/icon.icns -------------------------------------------------------------------------------- /my-app/src/types/ImportObj.ts: -------------------------------------------------------------------------------- 1 | export type ImportObj = { 2 | [key: string]: { importPath: string; importName: string }; 3 | }; 4 | -------------------------------------------------------------------------------- /my-app/src/UserTests/UserTestFile.cy.js: -------------------------------------------------------------------------------- 1 | describe('fd', () => { 2 | it('sd', () => { 3 | .clear()}) 4 | it('sdf', () => { 5 | }) 6 | }) -------------------------------------------------------------------------------- /my-app/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body { 7 | height: 100%; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /my-app/src/preload.ts: -------------------------------------------------------------------------------- 1 | // See the Electron documentation for details on how to use preload scripts: 2 | // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts 3 | -------------------------------------------------------------------------------- /my-app/postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-var-requires: "off" */ 2 | const tailwindcss = require('tailwindcss'); 3 | 4 | module.exports = { 5 | plugins: [ 6 | tailwindcss('./tailwind.config.js')], 7 | }; 8 | -------------------------------------------------------------------------------- /my-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /my-app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import App from './components/App'; 3 | 4 | const root = createRoot(document.getElementById('root')); 5 | 6 | root.render( 7 | 8 | ); 9 | 10 | 11 | -------------------------------------------------------------------------------- /my-app/src/getNonce.ts: -------------------------------------------------------------------------------- 1 | export function getNonce(): string { 2 | let text = ''; 3 | const possible = 4 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 5 | for (let i = 0; i < 32; i++) { 6 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 7 | } 8 | return text; 9 | } 10 | -------------------------------------------------------------------------------- /my-app/src/components/FormWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | type FormWrapperProps = { 4 | title: string; 5 | children: ReactNode; 6 | }; 7 | 8 | export function FormWrapper({ title, children }: FormWrapperProps) { 9 | return ( 10 | <> 11 |

{title}

12 |
{children}
13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /my-app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:import/recommended", 12 | "plugin:import/electron", 13 | "plugin:import/typescript" 14 | ], 15 | "parser": "@typescript-eslint/parser" 16 | } 17 | -------------------------------------------------------------------------------- /my-app/webpack.renderer.config.ts: -------------------------------------------------------------------------------- 1 | import type { Configuration } from 'webpack'; 2 | 3 | import { rules } from './webpack.rules'; 4 | import { plugins } from './webpack.plugins'; 5 | 6 | export const rendererConfig: Configuration = { 7 | target: 'electron-renderer', 8 | module: { 9 | rules, 10 | }, 11 | plugins, 12 | resolve: { 13 | extensions: ['.js', '.ts', '.jsx', '.tsx', '.css'], 14 | }, 15 | watchOptions: { 16 | ignored: /UserTests/ 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /my-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "allowJs": true, 5 | "module": "commonjs", 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "noImplicitAny": true, 9 | "sourceMap": true, 10 | "baseUrl": ".", 11 | "outDir": "dist", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "jsx": "react-jsx", 15 | "paths": { 16 | "*": ["node_modules/*"] 17 | } 18 | }, 19 | "include": ["src/**/*"] 20 | } 21 | -------------------------------------------------------------------------------- /my-app/src/types/Tree.ts: -------------------------------------------------------------------------------- 1 | // React component tree is a nested data structure, children are Trees 2 | 3 | export type Tree = { 4 | id: string; 5 | name: string; 6 | fileName: string; 7 | filePath: string; 8 | importPath: string; 9 | expanded: boolean; 10 | depth: number; 11 | count: number; 12 | thirdParty: boolean; 13 | reactRouter: boolean; 14 | reduxConnect: boolean; 15 | children: Tree[]; 16 | htmlChildrenTestIds: any; 17 | parentList: string[]; 18 | props: { [key: string]: boolean }; 19 | error: string; 20 | }; 21 | -------------------------------------------------------------------------------- /my-app/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/**/*.{js,jsx,ts,tsx}'], 4 | important: true, 5 | theme: { 6 | extend: { 7 | colors:{ 8 | secondary: "#14161d", 9 | primary: "#1DF28F", 10 | primaryDark: "#0bc06c", 11 | secondaryPrimary: "#048C7F", 12 | secondaryPrimaryDark: "#037066" 13 | }, 14 | backgroundImage: { 15 | logo: "url('/logo.png')", 16 | }, 17 | }, 18 | }, 19 | plugins: [], 20 | }; 21 | -------------------------------------------------------------------------------- /my-app/webpack.plugins.ts: -------------------------------------------------------------------------------- 1 | import type IForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; 2 | import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-var-requires 5 | const ForkTsCheckerWebpackPlugin: typeof IForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 6 | 7 | 8 | export const plugins = [ 9 | new ForkTsCheckerWebpackPlugin({ 10 | logger: 'webpack-infrastructure', 11 | }), 12 | new MonacoWebpackPlugin({ 13 | languages: ['javascript'] 14 | }), 15 | ]; 16 | -------------------------------------------------------------------------------- /my-app/webpack.main.config.ts: -------------------------------------------------------------------------------- 1 | import type { Configuration } from 'webpack'; 2 | 3 | import { rules } from './webpack.rules'; 4 | 5 | export const mainConfig: Configuration = { 6 | target: 'electron-main', 7 | /** 8 | * This is the main entry point for your application, it's the first file 9 | * that runs in the main process. 10 | */ 11 | entry: './src/index.ts', 12 | // Put your normal webpack config below here 13 | module: { 14 | rules, 15 | }, 16 | performance: { 17 | hints: false, 18 | }, 19 | resolve: { 20 | extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json'], 21 | }, 22 | watchOptions: { 23 | ignored: /UserTests/ 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /my-app/src/options/optionVariables.ts: -------------------------------------------------------------------------------- 1 | export const empty = ''; 2 | 3 | export const commonAssertions = [ 4 | 'have.length', 5 | 'not.have.class', 6 | 'have.value', 7 | 'have.text', 8 | 'include.text', 9 | 'not.contain', 10 | 'match', 11 | 'be.visible', 12 | 'not.be.visible', 13 | 'not.exist', 14 | 'be.checked', 15 | 'have.css', 16 | 'not.have.css', 17 | 'be.disabled', 18 | 'not.be.disabled', 19 | 'be-enabled', 20 | 'eq', 21 | ]; 22 | 23 | export const encodingArray = [ 24 | 'null', 25 | 'ascii', 26 | 'base64', 27 | 'binary', 28 | 'hex', 29 | 'latin1', 30 | 'utf8', 31 | 'utf-8', 32 | 'ucs2', 33 | 'ucs-2', 34 | 'utf16le', 35 | 'utf-16le', 36 | ]; 37 | 38 | -------------------------------------------------------------------------------- /my-app/src/components/useMultiStepForm.ts: -------------------------------------------------------------------------------- 1 | // import { ReactElement, useState } from 'react'; 2 | 3 | // export function useMultistepForm(steps: ReactElement[]) { 4 | // const [currentStepIndex, setCurrentStepIndex] = useState(0); 5 | 6 | // function next() { 7 | // setCurrentStepIndex(i => { 8 | // if (i >= steps.length - 1) return i; 9 | // return i + 1; 10 | // }); 11 | // } 12 | 13 | // function back() { 14 | // setCurrentStepIndex(i => { 15 | // if (i <= 0) return i; 16 | // return i - 1; 17 | // }); 18 | // } 19 | 20 | // function goTo(index: number) { 21 | // setCurrentStepIndex(index); 22 | // } 23 | 24 | // return { 25 | // currentStepIndex, 26 | // step: steps[currentStepIndex], 27 | // steps, 28 | // isFirstStep: currentStepIndex === 0, 29 | // isLastStep: currentStepIndex === steps.length - 1, 30 | // goTo, 31 | // next, 32 | // back, 33 | // }; 34 | // } 35 | -------------------------------------------------------------------------------- /my-app/src/components/SelectAction.tsx: -------------------------------------------------------------------------------- 1 | import { FormWrapper } from './FormWrapper'; 2 | 3 | type UserData = { 4 | actionType: string; 5 | data1: string; 6 | }; 7 | 8 | type UserFormProps = UserData & { 9 | updateFields: (fields: Partial) => void; 10 | }; 11 | 12 | function SelectAction({ actionType, data1, updateFields }: UserFormProps) { 13 | return ( 14 | 15 | 16 | 25 | updateFields({ data1: e.target.value })}> 30 | 31 | ); 32 | } 33 | export default SelectAction; 34 | -------------------------------------------------------------------------------- /my-app/src/components/TestGenContainer.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement, useState } from 'react'; 2 | import DescribePage from './DescribePage'; 3 | import ItBlockPage from './ItBlockPage'; 4 | import StatementPage from './StatementPage'; 5 | import { Tree } from './../types/Tree' 6 | 7 | type TestGenContainerProps = { 8 | currentComponent: Tree, 9 | currentHTML: string, 10 | currentTestId: string, 11 | } 12 | 13 | function TestGenContainer({currentComponent, currentHTML, currentTestId}: TestGenContainerProps) { 14 | const [currentPageNum, setCurrentPageNum] = useState(0); 15 | const arrayOfReact: ReactElement[] = [ 16 | , 17 | , 18 | , 19 | ]; 20 | return ( 21 |
22 | {arrayOfReact[currentPageNum]} 23 |
24 | ); 25 | } 26 | export default TestGenContainer; 27 | -------------------------------------------------------------------------------- /my-app/src/renderer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file will automatically be loaded by webpack and run in the "renderer" context. 3 | * To learn more about the differences between the "main" and the "renderer" context in 4 | * Electron, visit: 5 | * 6 | * https://electronjs.org/docs/latest/tutorial/process-model 7 | * 8 | * By default, Node.js integration in this file is disabled. When enabling Node.js integration 9 | * in a renderer process, please be aware of potential security implications. You can read 10 | * more about security risks here: 11 | * 12 | * https://electronjs.org/docs/tutorial/security 13 | * 14 | * To enable Node.js integration in this file, open up `main.js` and enable the `nodeIntegration` 15 | * flag: 16 | * 17 | * ``` 18 | * // Create the browser window. 19 | * mainWindow = new BrowserWindow({ 20 | * width: 800, 21 | * height: 600, 22 | * webPreferences: { 23 | * nodeIntegration: true 24 | * } 25 | * }); 26 | * ``` 27 | */ 28 | 29 | import './index.css'; 30 | import './main'; 31 | console.log( 32 | '👋 This message is being logged by "renderer.js", included via webpack', 33 | ); 34 | -------------------------------------------------------------------------------- /my-app/webpack.rules.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleOptions } from 'webpack'; 2 | 3 | export const rules: Required['rules'] = [ 4 | // Add support for native node modules 5 | { 6 | // We're specifying native_modules in the test because the asset relocator loader generates a 7 | // "fake" .node file which is really a cjs file. 8 | test: /native_modules[/\\].+\.node$/, 9 | use: 'node-loader', 10 | }, 11 | { 12 | test: /[/\\]node_modules[/\\].+\.(m?js|node)$/, 13 | parser: { amd: false }, 14 | use: { 15 | loader: '@vercel/webpack-asset-relocator-loader', 16 | options: { 17 | outputAssetBase: 'native_modules', 18 | }, 19 | }, 20 | }, 21 | { 22 | test: /\.tsx?$/, 23 | exclude: /(node_modules|\.webpack)/, 24 | use: { 25 | loader: 'ts-loader', 26 | options: { 27 | transpileOnly: true, 28 | }, 29 | }, 30 | }, 31 | { 32 | // loads .css files 33 | test: /\.css$/, 34 | use: ['style-loader', 'css-loader', 'postcss-loader'], 35 | }, 36 | { 37 | test: /\.ttf$/, 38 | type: 'asset/resource' 39 | }, 40 | ]; 41 | -------------------------------------------------------------------------------- /my-app/src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Home from "../Routes/Home"; 3 | import MainPage from "../Routes/MainPage"; 4 | import {Tree} from "../types/Tree" 5 | 6 | const App = () => { 7 | const [url, setUrl] = React.useState('http://localhost:8080/'); 8 | const [fileTree, setFileTree] = React.useState({ 9 | id: '', 10 | name: '', 11 | fileName: '', 12 | filePath: '', 13 | importPath: '', 14 | expanded: false, 15 | depth: 0, 16 | count: 0, 17 | thirdParty: false, 18 | reactRouter: false, 19 | reduxConnect: false, 20 | children: [], 21 | htmlChildrenTestIds: {}, 22 | parentList: [], 23 | props: {}, 24 | error: '', 25 | }); 26 | const [pageState, setPageState] = React.useState("Home"); 27 | 28 | return pageState === "Home" ? ( 29 | 36 | ) : ( 37 | 38 | ); 39 | }; 40 | 41 | export default App; 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Preston Mounivong 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 | -------------------------------------------------------------------------------- /my-app/src/components/SmallerPreviewPopup.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MonacoEditor from 'react-monaco-editor'; 3 | const path = window.require('path') 4 | const fs = window.require('fs'); 5 | const os = window.require('os'); 6 | 7 | 8 | type SmallerPreviewPopupProps = { 9 | code: string, 10 | setCode: React.Dispatch>; 11 | } 12 | 13 | const SmallerPreviewPopup: React.FC = ({ code, setCode }) => { 14 | 15 | const handleEditorChange = (newValue: string) => { 16 | setCode(newValue); 17 | const filePath = path.join(os.tmpdir(), 'UserTests', 'TestBlock.cy.js'); 18 | fs.writeFileSync(filePath, newValue); 19 | }; 20 | 21 | 22 | 23 | return ( 24 |
25 | 38 |
39 | ); 40 | }; 41 | 42 | export default SmallerPreviewPopup 43 | 44 | -------------------------------------------------------------------------------- /my-app/src/components/Flow Components/CustomNode.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from "react"; 2 | import { Handle, Position } from "react-flow-renderer"; 3 | import { Tree as TreeType } from "../../types/Tree"; 4 | type CustomNodeProps = { 5 | data:{ 6 | name:string; 7 | testid:string; 8 | props:{[key: string]: boolean;} 9 | filePath:string; 10 | setCurrentComponent: React.Dispatch>; 11 | nodeData:TreeType; 12 | isSelected:boolean 13 | key:string 14 | } 15 | } 16 | function CustomNode({ data }:CustomNodeProps) { 17 | 18 | const handleClick = () => { 19 | data.setCurrentComponent(data.nodeData); 20 | }; 21 | 22 | 23 | return ( 24 |
29 | 30 | 31 | 32 |
33 | ); 34 | } 35 | 36 | export default memo(CustomNode); 37 | -------------------------------------------------------------------------------- /my-app/testing/test2.js: -------------------------------------------------------------------------------- 1 | import { describe, it, before, after } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import { Builder } from 'selenium-webdriver'; 4 | 5 | describe('Landing Page', function() { 6 | let driver; 7 | 8 | before(async function() { 9 | driver = await new Builder() 10 | .forBrowser('electron') 11 | .usingServer('http://localhost:9515') // Electron-Chromedriver’s port 12 | .build(); 13 | }); 14 | 15 | after(async function() { 16 | await driver.quit(); 17 | }); 18 | 19 | it('should disable the Next button if no input is provided', async function() { 20 | await driver.get('file:///path-to-your/electron-app.html'); 21 | const nextButton = await driver.findElement({ css: 'button' }); 22 | expect(await nextButton.isEnabled()).to.be.false; 23 | }); 24 | 25 | it('should enable the Next button when input is provided', async function() { 26 | // Simulate file input and port input 27 | await driver.findElement({ css: 'input[type=text]' }).sendKeys('3000'); 28 | // Assume GetFile triggers a change in fileTree.name when a file is selected 29 | // Enable the Next button via your logic 30 | const nextButton = await driver.findElement({ css: 'button' }); 31 | expect(await nextButton.isEnabled()).to.be.true; 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /my-app/forge.config.ts: -------------------------------------------------------------------------------- 1 | import type { ForgeConfig } from '@electron-forge/shared-types'; 2 | import { MakerSquirrel } from '@electron-forge/maker-squirrel'; 3 | import { MakerZIP } from '@electron-forge/maker-zip'; 4 | import { MakerDeb } from '@electron-forge/maker-deb'; 5 | import { MakerRpm } from '@electron-forge/maker-rpm'; 6 | import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives'; 7 | import { WebpackPlugin } from '@electron-forge/plugin-webpack'; 8 | 9 | import { mainConfig } from './webpack.main.config'; 10 | import { rendererConfig } from './webpack.renderer.config'; 11 | 12 | const config: ForgeConfig = { 13 | packagerConfig: { 14 | asar: { 15 | unpackDir: "src/UserTests" 16 | }, 17 | icon: './icons/icon', // based on platform, will "add" .png for linux, .ico for windows, and .icns for mac 18 | }, 19 | rebuildConfig: {}, 20 | makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})], 21 | plugins: [ 22 | new AutoUnpackNativesPlugin({}), 23 | new WebpackPlugin({ 24 | mainConfig, 25 | renderer: { 26 | config: rendererConfig, 27 | entryPoints: [ 28 | { 29 | html: './src/index.html', 30 | js: './src/renderer.ts', 31 | name: 'main_window', 32 | preload: { 33 | js: './src/preload.ts', 34 | }, 35 | }, 36 | ], 37 | }, 38 | }), 39 | ], 40 | }; 41 | 42 | export default config; 43 | -------------------------------------------------------------------------------- /my-app/src/components/GetFile.tsx: -------------------------------------------------------------------------------- 1 | import { Parser } from "../parser"; 2 | import React from "react"; 3 | import { Tree } from "../types/Tree"; 4 | 5 | type GetFileProps = { 6 | setter: (tre: Tree) => void; 7 | }; 8 | 9 | const GetFile = ({ setter }: GetFileProps) => { 10 | const [fileName, setFileName] = React.useState(""); 11 | 12 | function parseTree() { 13 | const file = (document.getElementById("theInputFile") as HTMLInputElement) 14 | .files[0]; 15 | const DaParser = new Parser(file.path); 16 | DaParser.parse(); 17 | setFileName(DaParser.tree.fileName); 18 | setter(DaParser.tree); 19 | } 20 | 21 | return ( 22 |
23 | 26 | 33 |
34 | 35 | {fileName.length === 0 36 | ? "No file selected" 37 | : `Currently Selected: ${fileName}`} 38 | 39 |
40 |
41 | ); 42 | }; 43 | 44 | export default GetFile; 45 | -------------------------------------------------------------------------------- /my-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | .DS_Store 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # TypeScript cache 43 | *.tsbuildinfo 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | .env.test 63 | 64 | # parcel-bundler cache (https://parceljs.org/) 65 | .cache 66 | 67 | # next.js build output 68 | .next 69 | 70 | # nuxt.js build output 71 | .nuxt 72 | 73 | # vuepress build output 74 | .vuepress/dist 75 | 76 | # Serverless directories 77 | .serverless/ 78 | 79 | # FuseBox cache 80 | .fusebox/ 81 | 82 | # DynamoDB Local files 83 | .dynamodb/ 84 | 85 | # Webpack 86 | .webpack/ 87 | 88 | # Vite 89 | .vite/ 90 | 91 | # Electron-Forge 92 | out/ 93 | -------------------------------------------------------------------------------- /my-app/src/components/Flow Components/HtmlCustomNode.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Handle, Position } from "react-flow-renderer"; 3 | 4 | type HtmlCustomNode = { 5 | id:string; 6 | data: { 7 | name:string; 8 | attributes:any; 9 | setCurrentHTML:React.Dispatch>; 10 | setCurrentTestId:React.Dispatch>; 11 | isSelected:boolean; 12 | } 13 | } 14 | 15 | const CustomNode = ({ id, data }:HtmlCustomNode) => { 16 | 17 | const handleClick = () =>{ 18 | // if data-cy exists set current html to that data.name and set currentTestid to that testid 19 | if (data.attributes["data-cy"]){ 20 | data.setCurrentHTML(data.name); 21 | data.setCurrentTestId(`data-cy = ${data.attributes["data-cy"].value}`) 22 | } 23 | else if (data.attributes["id"]){ 24 | data.setCurrentHTML(data.name); 25 | data.setCurrentTestId(`id = ${data.attributes["id"].value}`) 26 | } 27 | }; 28 | 29 | return ( 30 |
40 | 47 | 48 | 49 |
50 | ); 51 | }; 52 | 53 | export default CustomNode; 54 | -------------------------------------------------------------------------------- /my-app/src/components/PreviewPopup.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MonacoEditor from 'react-monaco-editor'; 3 | // const { ipcRenderer } = window.require('electron'); 4 | const path = window.require('path'); 5 | const fs = window.require('fs'); 6 | const os = window.require('os'); 7 | type PreviewPopupProps = { 8 | onClose: () => void; 9 | }; 10 | 11 | const PreviewPopup: React.FC = ({ onClose }) => { 12 | const [code, setCode] = React.useState(''); 13 | const filePreviewPath = path.join( 14 | os.tmpdir(), 15 | 'UserTests', 16 | 'UserTestFile.cy.js', 17 | ); 18 | React.useEffect(() => { 19 | setCode(fs.readFileSync(filePreviewPath, 'utf-8')); 20 | }, []); 21 | 22 | const handleEditorChange = (newValue: string) => { 23 | setCode(newValue); 24 | }; 25 | 26 | const handleClose = () => { 27 | fs.writeFileSync(filePreviewPath, code); 28 | onClose(); 29 | }; 30 | 31 | return ( 32 |
40 |
41 | 53 | 58 |
59 |
60 | ); 61 | }; 62 | 63 | export default PreviewPopup; 64 | -------------------------------------------------------------------------------- /my-app/src/components/ButtonComponent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PreviewPopup from './PreviewPopup'; 3 | import path from 'path'; 4 | import os from 'os'; 5 | import fs from 'fs'; 6 | 7 | const { ipcRenderer } = window.require('electron'); 8 | 9 | 10 | const ButtonComponent = () => { 11 | const [open, setOpen] = React.useState(false); 12 | 13 | const handleOpen = () => { 14 | setOpen(true); 15 | }; 16 | 17 | const handleClose = () => { 18 | setOpen(false); 19 | }; 20 | 21 | // const handleSaveFile = async () => { 22 | // const tempDirPath = path.join(os.tmpdir(), 'UserTests', 'UserTestFile.cy.js'); 23 | // const content = fs.readFileSync(tempDirPath, 'utf-8'); 24 | 25 | // // const { dialog } = window.require('@electron/remote'); 26 | // const filePath = await dialog.showSaveDialog({ 27 | // title: 'Save your file', 28 | // defaultPath: 'UserTestFile.cy.js', 29 | // filters: [{ name: 'JavaScript', extensions: ['js'] }], 30 | // }); 31 | 32 | // if (filePath) { 33 | // fs.writeFileSync(filePath, content); 34 | // } 35 | // }; 36 | const handleSaveFile = () => { 37 | const tempDirPath = path.join(os.tmpdir(), 'UserTests', 'UserTestFile.cy.js'); 38 | const content = fs.readFileSync(tempDirPath, 'utf-8'); 39 | 40 | ipcRenderer.invoke('save-file', content); 41 | }; 42 | 43 | 44 | return ( 45 |
46 | 51 | 52 | 55 | {open && } 56 |
57 | ); 58 | }; 59 | 60 | export default ButtonComponent; -------------------------------------------------------------------------------- /my-app/src/components/TestingUi.tsx: -------------------------------------------------------------------------------- 1 | // import React, { useState, FormEvent } from 'react'; 2 | // import { useMultistepForm } from './useMultiStepForm'; 3 | // import SelectAction from './SelectAction'; 4 | 5 | // type FormData = { 6 | // actionType: string; 7 | // selectedItem: string; 8 | // data1: string; 9 | // data2: string; 10 | // data3: string; 11 | // }; 12 | 13 | // const INITIAL_DATA: FormData = { 14 | // actionType: '', 15 | // selectedItem: '', 16 | // data1: '', 17 | // data2: '', 18 | // data3: '', 19 | // }; 20 | 21 | // const TestingUi = () => { 22 | // const [data, setData] = useState(INITIAL_DATA); 23 | // function updateFields(fields: Partial) { 24 | // setData(prev => { 25 | // return { ...prev, ...fields }; 26 | // }); 27 | // } 28 | 29 | // const { steps, currentStepIndex, step, isFirstStep, isLastStep, back, next } = 30 | // useMultistepForm([ 31 | // , 32 | //
, 33 | // , 34 | // ]); 35 | // function onSubmit(e: FormEvent) { 36 | // e.preventDefault(); 37 | // if (!isLastStep) return next(); 38 | // alert('we gonna add an action'); 39 | // } 40 | // return ( 41 | //
42 | // 45 | // 50 | //

51 | //
52 | //
53 | // {currentStepIndex + 1} / {steps.length} 54 | //
55 | // {step} 56 | //
57 | // {!isFirstStep && ( 58 | // 61 | // )} 62 | // 63 | //
64 | //
65 | //
66 | // ); 67 | // }; 68 | 69 | // export default TestingUi; 70 | -------------------------------------------------------------------------------- /my-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cydekick", 3 | "productName": "Cydekick", 4 | "version": "1.0.0", 5 | "description": "My Electron application description", 6 | "main": ".webpack/main", 7 | "scripts": { 8 | "start": "electron-forge start", 9 | "package": "electron-forge package", 10 | "make": "electron-forge make", 11 | "make:linux": "electron-forge make --platform=linux", 12 | "make:win": "electron-forge make --platform=win32", 13 | "make:mac": "electron-forge make --platform=darwin", 14 | "publish": "electron-forge publish", 15 | "lint": "eslint --ext .ts,.tsx ." 16 | }, 17 | "keywords": [], 18 | "author": { 19 | "name": "Siddhant Saxena", 20 | "email": "sidsaxena27@gmail.com" 21 | }, 22 | "license": "MIT", 23 | "devDependencies": { 24 | "@babel/core": "^7.22.20", 25 | "@babel/parser": "^7.22.16", 26 | "@babel/preset-react": "^7.22.15", 27 | "@electron-forge/cli": "^6.4.2", 28 | "@electron-forge/maker-deb": "^6.4.2", 29 | "@electron-forge/maker-rpm": "^6.4.2", 30 | "@electron-forge/maker-squirrel": "^6.4.2", 31 | "@electron-forge/maker-zip": "^6.4.2", 32 | "@electron-forge/plugin-auto-unpack-natives": "^6.4.2", 33 | "@electron-forge/plugin-webpack": "^6.4.2", 34 | "@types/react": "^18.2.21", 35 | "@types/react-dom": "^18.2.7", 36 | "@typescript-eslint/eslint-plugin": "^5.62.0", 37 | "@typescript-eslint/parser": "^5.62.0", 38 | "@vercel/webpack-asset-relocator-loader": "^1.7.3", 39 | "babel-loader": "^9.1.3", 40 | "css-loader": "^6.8.1", 41 | "electron": "26.2.1", 42 | "eslint": "^8.49.0", 43 | "eslint-plugin-import": "^2.28.1", 44 | "fork-ts-checker-webpack-plugin": "^7.3.0", 45 | "node-loader": "^2.0.0", 46 | "postcss-loader": "^7.3.3", 47 | "postcss-nesting": "^12.0.1", 48 | "style-loader": "^3.3.3", 49 | "tailwindcss": "^3.3.3", 50 | "ts-loader": "^9.4.4", 51 | "ts-node": "^10.9.1", 52 | "typescript": "~4.5.4" 53 | }, 54 | "dependencies": { 55 | "@dagrejs/dagre": "^1.0.4", 56 | "@electron/remote": "^2.0.11", 57 | "@types/node": "^20.7.0", 58 | "babel-plugin-react-css-modules": "^5.2.6", 59 | "electron-squirrel-startup": "^1.0.0", 60 | "fs": "^0.0.1-security", 61 | "monaco-editor": "^0.39.0", 62 | "monaco-editor-webpack-plugin": "^7.1.0", 63 | "path": "^0.12.7", 64 | "react": "^18.2.0", 65 | "react-dom": "^18.2.0", 66 | "react-flow-renderer": "^10.3.17", 67 | "react-monaco-editor": "^0.54.0", 68 | "react-router": "^6.16.0", 69 | "react-router-dom": "^6.16.0", 70 | "util": "^0.12.5" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /my-app/src/Routes/Home.tsx: -------------------------------------------------------------------------------- 1 | import GetFile from "../components/GetFile"; 2 | import React from "react"; 3 | import { Tree } from "../types/Tree"; 4 | 5 | /* 6 | mint greenish: #1DF28F 7 | darker green: #048C7F 8 | background color: #1B1E26 9 | 10 | */ 11 | type HomePageProps = { 12 | setUrl: React.Dispatch>; 13 | url: string; 14 | fileTree:Tree; 15 | setPageState: React.Dispatch>; 16 | setFileTree: React.Dispatch>; 17 | }; 18 | 19 | const Home = (props: HomePageProps) => { 20 | const { fileTree, setUrl, setFileTree, url, setPageState } = props; 21 | const [buttonSlide, setButtonSlide] = React.useState(false); 22 | // Button Handler to switch Routes 23 | const handleSubmission = () => { 24 | setButtonSlide(true) 25 | setTimeout(()=>{ 26 | setPageState("MainPage"); 27 | setButtonSlide(false) 28 | }, 300); 29 | } 30 | 31 | const handleChange = () => { 32 | const urlInputElement = document.getElementById( 33 | "url_form_id" 34 | ) as HTMLInputElement; 35 | if (urlInputElement) { 36 | setUrl(urlInputElement.value); 37 | } 38 | } 39 | 40 | return ( 41 |
42 |
43 |
44 | 45 | 46 | 47 |
48 | 55 | 68 |
69 | 70 |
71 |
72 | ); 73 | }; 74 | 75 | export default Home; 76 | -------------------------------------------------------------------------------- /my-app/src/components/DescribePage.tsx: -------------------------------------------------------------------------------- 1 | import SmallerPreviewPopup from './SmallerPreviewPopup' 2 | import React from 'react'; 3 | const fs = window.require('fs'); 4 | const os = window.require('os'); 5 | 6 | const path = window.require('path') 7 | 8 | type DescribePageProps = { 9 | setCurrentPageNum: React.Dispatch> 10 | } 11 | 12 | function DescribePage({ setCurrentPageNum }: DescribePageProps) { 13 | 14 | //States 15 | const [code, setCode] = React.useState(''); // initial string that is displayed on the editor 16 | 17 | //text that is rendered whenever the describe page is mounted 18 | React.useEffect(() => { 19 | const fileContent = `'Welcome to Cydekick!' \n'Enter text for your describe block!'` 20 | setCode(fileContent); 21 | }, []); 22 | 23 | //Describe block function that appends our describe block to the monaco editor 24 | function createDescribeBlock(): void { 25 | const describeText = (document.getElementById('describeText') as HTMLInputElement).value 26 | const testFileContent = describeBlock(describeText); 27 | // const filePath = path.join(__dirname, 'src', 'UserTests', 'TestBlock.cy.js'); 28 | const filePath = path.join(os.tmpdir(), 'UserTests', 'TestBlock.cy.js'); 29 | 30 | 31 | fs.writeFileSync(filePath, testFileContent); 32 | setCurrentPageNum(1) 33 | } 34 | 35 | //describe block that returns a describe string 36 | function describeBlock(string: string): string { 37 | return `describe('${string}', () => {`; 38 | } 39 | 40 | 41 | return ( 42 |
43 |
46 |

Name for describe block:

47 | 52 | 57 |
58 |
59 | 60 |
61 |
62 | ); 63 | } 64 | 65 | export default DescribePage 66 | 67 | -------------------------------------------------------------------------------- /my-app/src/components/ItBlockPage.tsx: -------------------------------------------------------------------------------- 1 | import SmallerPreviewPopup from './SmallerPreviewPopup'; 2 | const fs = window.require('fs'); 3 | const os = window.require('os'); 4 | const path = window.require('path') 5 | import React from 'react'; 6 | 7 | type ItBlockPageProps = { 8 | setCurrentPageNum: React.Dispatch> 9 | } 10 | 11 | function ItBlockPage({ setCurrentPageNum }: ItBlockPageProps) { 12 | //states 13 | const [code, setCode] = React.useState(''); 14 | 15 | //renders the current state of the editor 16 | React.useEffect(() => { 17 | // const filePath = path.join(__dirname, 'src', 'UserTests', 'TestBlock.cy.js'); 18 | const filePath = path.join(os.tmpdir(), 'UserTests', 'TestBlock.cy.js'); 19 | 20 | const fileContent = fs.readFileSync(filePath, 'utf8') 21 | setCode(fileContent); 22 | }, []); 23 | 24 | //appends the itBlock to the Editor 25 | function createItBlock(): void { 26 | const itText = (document.getElementById('itText') as HTMLInputElement).value 27 | const testFileContent = itBlock(itText); 28 | // const filePath = path.join(__dirname, 'src', 'UserTests', 'TestBlock.cy.js'); 29 | const filePath = path.join(os.tmpdir(), 'UserTests', 'TestBlock.cy.js'); 30 | 31 | fs.appendFileSync(filePath, testFileContent); 32 | setCurrentPageNum(2); 33 | } 34 | 35 | //creates an it String that will be displayed on the monaco editor 36 | function itBlock(string: string): string { 37 | return '\n\t' + `it('${string}', () => {`; 38 | } 39 | 40 | return ( 41 |
42 |
45 |

Name for test:

46 | 51 | 56 |
57 |
58 | 59 |
60 |
61 | ); 62 | } 63 | export default ItBlockPage; 64 | -------------------------------------------------------------------------------- /my-app/src/options/assertionOptions.ts: -------------------------------------------------------------------------------- 1 | import { empty, commonAssertions } from "./optionVariables"; 2 | 3 | type ModalCreateCodeType = (string | number)[]; 4 | 5 | const assertionOptions = { 6 | should: { 7 | option: "Should", 8 | code: ".should()", 9 | tooltip: "Assert element state or value.", 10 | modal: [ 11 | { type: "label", labelText: "Assert element state or value." }, 12 | { type: "select", options: commonAssertions }, 13 | { 14 | type: "input", 15 | inputType: "OPTIONAL: Value to assert against chainer.", 16 | }, 17 | { 18 | type: "input", 19 | inputType: "OPTIONAL: A method to be called on the chainer.", 20 | }, 21 | ], 22 | modalCreateCode: function (args: ModalCreateCodeType): string { 23 | if (args[1] === empty && args[2] === empty) { 24 | return `.should('${args[0]}')`; 25 | } else if (args[2] === empty) { 26 | const value1: string | number = isNaN(Number(args[1])) 27 | ? `'${args[1]}'` 28 | : Number(args[1]); 29 | return `.should('${args[0]}', ${value1})`; 30 | } else if (args[0] && args[1] && args[2]) { 31 | const value1: string | number = isNaN(Number(args[1])) 32 | ? `'${args[1]}'` 33 | : Number(args[1]); 34 | const value2: string | number = isNaN(Number(args[2])) 35 | ? `'${args[2]}'` 36 | : Number(args[2]); 37 | return `.should('${args[0]}', ${value1}, ${value2})`; 38 | } else { 39 | return; 40 | } 41 | }, 42 | }, 43 | and: { 44 | option: "And", 45 | code: ".and()", 46 | tooltip: "Chain additional assertions.", 47 | modal: [ 48 | { type: "label", labelText: "Chain additional assertions." }, 49 | { type: "select", options: commonAssertions }, 50 | { 51 | type: "input", 52 | inputType: "OPTIONAL: Value to assert against chainer.", 53 | }, 54 | { 55 | type: "input", 56 | inputType: "OPTIONAL: A method to be called on the chainer.", 57 | }, 58 | ], 59 | modalCreateCode: function (args: ModalCreateCodeType): string { 60 | if (args[1] === empty && args[2] === empty) { 61 | return `.and('${args[0]}')`; 62 | } else if (args[2] === empty) { 63 | const value1: string | number = isNaN(Number(args[1])) 64 | ? `'${args[1]}'` 65 | : Number(args[1]); 66 | return `.and('${args[0]}', ${value1})`; 67 | } else if (args[0] && args[1] && args[2]) { 68 | const value1: string | number = isNaN(Number(args[1])) 69 | ? `'${args[1]}'` 70 | : Number(args[1]); 71 | const value2: string | number = isNaN(Number(args[2])) 72 | ? `'${args[2]}'` 73 | : Number(args[2]); 74 | return `.and('${args[0]}', ${value1}, ${value2})`; 75 | } else { 76 | return; 77 | } 78 | }, 79 | }, 80 | }; 81 | 82 | export default assertionOptions; 83 | -------------------------------------------------------------------------------- /my-app/src/index.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow, ipcMain, dialog } from 'electron'; 2 | // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack 3 | // plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on 4 | // whether you're running in development or production). 5 | import fs from 'fs'; 6 | import * as os from 'os'; 7 | import * as path from 'path'; 8 | // import { initialize, enable } from '@electron/remote/main'; 9 | declare const MAIN_WINDOW_WEBPACK_ENTRY: string; 10 | declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string; 11 | 12 | // initialize(); 13 | 14 | // Handle creating/removing shortcuts on Windows when installing/uninstalling. 15 | if (require('electron-squirrel-startup')) { 16 | app.quit(); 17 | } 18 | 19 | const createWindow = (): void => { 20 | // Create the browser window. 21 | const mainWindow = new BrowserWindow({ 22 | height: 800, 23 | width: 1200, 24 | webPreferences: { 25 | preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY, 26 | nodeIntegration: true, 27 | contextIsolation: false, 28 | webviewTag: true, 29 | }, 30 | icon: '../logo.png' 31 | }); 32 | 33 | // enable(mainWindow.webContents); 34 | 35 | // and load the index.html of the app. 36 | mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); 37 | 38 | // Open the DevTools. 39 | mainWindow.webContents.openDevTools(); 40 | }; 41 | 42 | // This method will be called when Electron has finished 43 | // initialization and is ready to create browser windows. 44 | // Some APIs can only be used after this event occurs. 45 | // app.on('ready', createWindow); 46 | app.on('ready', () => { 47 | const tempDir = path.join(os.tmpdir(), 'UserTests'); 48 | fs.mkdirSync(tempDir, { recursive: true }); 49 | 50 | const files = ['UserTestFile.cy.js', 'TestBlock.cy.js']; 51 | files.forEach(file => { 52 | const filePath = path.join(tempDir, file); 53 | fs.writeFileSync(filePath, ''); // creates file with empty content 54 | }); 55 | 56 | createWindow(); 57 | }); 58 | 59 | 60 | // Quit when all windows are closed, except on macOS. There, it's common 61 | // for applications and their menu bar to stay active until the user quits 62 | // explicitly with Cmd + Q. 63 | app.on('window-all-closed', () => { 64 | if (process.platform !== 'darwin') { 65 | app.quit(); 66 | } 67 | }); 68 | 69 | app.on('activate', () => { 70 | // On OS X it's common to re-create a window in the app when the 71 | // dock icon is clicked and there are no other windows open. 72 | if (BrowserWindow.getAllWindows().length === 0) { 73 | createWindow(); 74 | } 75 | }); 76 | 77 | ipcMain.handle('save-file', async (event, content) => { 78 | const { filePath } = await dialog.showSaveDialog({ 79 | title: 'Save your file', 80 | defaultPath: 'UserTestFile.cy.js', 81 | filters: [{ name: 'JavaScript', extensions: ['js'] }], 82 | }); 83 | 84 | if (filePath) { 85 | fs.writeFileSync(filePath, content); 86 | } 87 | }); -------------------------------------------------------------------------------- /my-app/src/components/DropdownButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import DynamicModal from './DynamicModal'; 3 | 4 | interface Modal { 5 | type: string; 6 | labelText?: string; 7 | inputType?: string; 8 | options?: string[] 9 | } 10 | 11 | interface OptionDetails { 12 | option: string; 13 | code: string; 14 | tooltip: string; 15 | modal?: Modal[]; 16 | modalCreateCode?: (text:string[]) => string; 17 | } 18 | 19 | interface Props { 20 | label: string; 21 | options: Record; 22 | onClickOption: (code: string, details: OptionDetails) => void; 23 | dropDown: string; 24 | setDropDown: (param:string)=>void 25 | } 26 | 27 | const DropdownButton: React.FC = ({ dropDown, setDropDown, label, onClickOption, options }) => { 28 | const [isOpen, setIsOpen] = useState(false); 29 | 30 | // array to keep track of state 31 | const [modalOpenStates, setModalOpenStates] = useState( 32 | Object.keys(options).map(() => false), 33 | ); 34 | 35 | function onButtonClick(index: number, optionDetails: OptionDetails) { 36 | if (!optionDetails.modal) { 37 | onClickOption(optionDetails.code, optionDetails); 38 | } else { 39 | // Open the modal for the selected option by updating its state 40 | const updatedModalStates = [...modalOpenStates]; 41 | updatedModalStates[index] = true; 42 | setModalOpenStates(updatedModalStates); 43 | } 44 | } 45 | 46 | function closeModal(index: number) { 47 | // Close the modal for the specified option by updating its state 48 | const updatedModalStates = [...modalOpenStates]; 49 | updatedModalStates[index] = false; 50 | setModalOpenStates(updatedModalStates); 51 | } 52 | React.useEffect(() =>{ 53 | if (label !== dropDown){ 54 | setIsOpen(false) 55 | } 56 | }, [dropDown]) 57 | return ( 58 |
59 |
60 | {' '} 61 | {/* Adjust spacing between parent buttons */} 62 | 69 | {/* Add other parent buttons here */} 70 |
71 | {isOpen && ( 72 |
73 | {Object.values(options).map((optionDetails, index) => ( 74 |
75 | 83 | closeModal(index)} 87 | onClickOption={onClickOption} 88 | /> 89 |
90 | ))} 91 |
92 | )} 93 |
94 | ); 95 | }; 96 | 97 | export default DropdownButton; 98 | -------------------------------------------------------------------------------- /my-app/src/components/DynamicModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface Modal { 4 | type: string; 5 | labelText?: string; 6 | inputType?: string; 7 | options?: string[] 8 | } 9 | 10 | 11 | interface OptionDetails { 12 | option: string; 13 | code: string; 14 | tooltip: string; 15 | modal?: Modal[]; 16 | modalCreateCode?: (text:string[]) => string; 17 | } 18 | 19 | type DynamicModalType = { 20 | infoObj:OptionDetails; 21 | isOpen:boolean; 22 | setIsOpen:React.Dispatch>; 23 | onClickOption:(code: string, details: OptionDetails) => void; 24 | } 25 | 26 | function DynamicModal({ infoObj, isOpen, setIsOpen, onClickOption }:DynamicModalType) { 27 | function createCode(): void { 28 | const modalForm = document.getElementById('modalForm') as HTMLFormElement | null; 29 | if (modalForm){ 30 | 31 | const modalElements = modalForm.elements as HTMLFormControlsCollection; 32 | const arrayOfEleVal: any[] = []; 33 | for (const ele of modalElements) { 34 | if (ele instanceof HTMLTextAreaElement || ele instanceof HTMLSelectElement) arrayOfEleVal.push(ele.value); 35 | } 36 | onClickOption(infoObj.modalCreateCode(arrayOfEleVal), infoObj); 37 | setIsOpen(false); 38 | } 39 | } 40 | 41 | function createLabel(labelText: string) { 42 | return ; 43 | } 44 | 45 | function createInput(inputType:string) { 46 | return