diff --git a/.gitignore b/.gitignore
index f4fea82c3..2a0bed492 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,6 +108,9 @@ typings/
# dotenv environment variables file
.env
+# config file for db URI
+config.js
+
# parcel-bundler cache (https://parceljs.org/)
.cache
diff --git a/LICENSE.md b/LICENSE.md
index 2d347a54a..d176f7680 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020 ReacType
+Copyright (c) 2021 ReacType
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
diff --git a/README.md b/README.md
index 3c0dea74c..899dea3c2 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,49 @@
-
+
ReacType
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/team-reactype/ReacType/pulls)
![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)
-![Release: 4.0](https://img.shields.io/badge/Release-4.0-white)
+![Version 5.0](https://img.shields.io/badge/Release-5.0-lightgrey.svg)
-**ReacType** is a visual prototyping tool for developers employing **React** component architecture alongside the comprehensive type checking of **TypeScript**.
-In other words, **you can draw prototypes and export React / Typescript code!**
+**ReacType** is a visual prototyping tool for developers employing **React** component architecture alongside the comprehensive type-checking of **TypeScript**.
+In other words, **you can draw prototypes and export React / TypeScript code!**
-**ReacType** allows the user to _visualize_ their application architecture dynamically, employing a _drag-and-drop canvas display_ and a _real-time component code preview_. The user can create components and drag _instances_ of these components, as well as HTML elements, onto the canvas. This architecture can then be _exported_ as TypeScript application files to be used as a starter template for any repository.
+**ReacType** allows users to _visualize_ their application architecture dynamically, employing a _drag-and-drop canvas display_ and a _real-time component code preview_. Users can create components and drag _instances_ of these components, as well as HTML elements, onto the canvas. This architecture can then be _exported_ as TypeScript application files to be used as a starter template for any repository.
-**New with version 4.0:**
-- View dynamically created components/HTML elements in the component tree
-- View Typescript syntax for React
-- Code preview is fully editable (make changes before exporting project)
-- Create custom HTML elements
-- Improved UI experience
-- Implemented a comprehensive tutorial page with images
+**New with version 5.0:**
+
+- Elements may be added to components in any location, rather than only at the bottom
+- Compatibility with Gatsby.js
+- Modernized and cleaner UI, including enhanced dark mode
+- Tutorial has been updated to reflect other modifications
Download for [MacOS](https://github.com/team-reactype/ReacType/releases), [Windows](https://github.com/team-reactype/ReacType/releases/), [Linux](https://github.com/team-reactype/ReacType/releases/).
- **Mac users**: After opening the dmg and dragging ReacType into your Applications folder, ctrl+click the icon and select 'Open' from the context menu to run the app. This extra step is necessary since we don't have an Apple developer license yet.
-- **Windows users**: Install the application by running ReacType Setup 4.0.0.exe.
+- **Windows users**: Install the application by running ReacType Setup 5.0.0.exe.
- **Linux users**: Run the application as a super user in order to read and write files.
-![Gif of adding](https://i.imgur.com/Ioqkr00.gif)
+![Gif of adding](https://i.imgur.com/d1oHiTm.gif)
### How to use
-- **Sign-in page**: Sign up for an account, authenticate via Github/Facebook, or just continue as a guest.
-- **Tutorial**: Click ‘Tutorial’ from the help tab’s drop-down menu of the help tab at the top left of the application to view a tutorial page.
-- **Start a project (only after authenticating)**: After you authenticate via Github/Facebook, create a new project, and select whether you want your project to be a Next.js or a classic React project. Also, save your project so that you can return to it at a later time.
-- **Add Components**: Create components on the left panel. Components can be associated with a route, or they can be used within other components.
-- **Delete Components**: Delete components after focusing on them from the right panel. Be careful when deleting components because all instances of the component will be deleted within the application/project.
-- **Add Custom Elements**: Create custom elements or add HTML elements that you are more familiar with into the application. Once the project is exported, the HTML tags generated in the code preview will function the way the label is supposed to work. You can create functionality for custom elements in a new file. The tutorial on HTML Elements explains more on how to do this.
-- **Delete Elements**: Delete elements by clicking on the ‘X’ button next to the element. Be careful when deleting elements because all elements will be deleted within the application/project.
-- **Create instances on the canvas**: Each component has its canvas. Create an example of an element or HTML element by dragging it onto the canvas. Div components are arbitrarily nestable and useful for complex layouts. Next.js projects have Link components to enable client-side navigation to other routes.
-- **Component Tree**: Click on the component tree tab next to the code preview tab to view the component tree hierarchy.
-- **Update styling**: Click on an element on the canvas to update basic styling using the right functions. As you create new instances and add styling, watch as your code dynamically generates in the bottom panel.
-- **User preference features**: Select a theme for the code preview to your liking and change the application’s lighting.
-- **Export project**: Click the “Export Project’ button to export the project’s application files into a Typescript file. The exported project is fully functional with Webpack, Express server, routing, etc., and will match what’s mocked on the canvas.
+- **Sign-in page**: Sign up for an account, or just continue as a guest. Registered users enjoy additional project-saving functionality.
+- **Tutorial**: Click ‘Tutorial’ from the Help tab’s dropdown menu (at the top left of the application) to view a tutorial.
+- **Start a project (only after registration)**: Registered users can create a new project and select whether they want their project to be a Next.js, Gatsby.js, or classic React project. Also, registered users can save projects to return to them at a later time.
+- **Add Components**: Create components on the right panel. Components can be associated with a route, or they can be used within other components.
+- **Delete Components**: Delete components after focusing on them in the right panel. Be careful when deleting components: Upon deletion, all instances of the component will be removed within the application/project.
+- **Add Custom Elements**: Create custom elements or add provided HTML elements into the application. Once the project is exported, the HTML tags generated in the code preview will function as expected. You can specify functionality for custom elements in the code preview. The tutorial on HTML Elements explains more on how to do this.
+- **Delete Custom HTML Elements**: Delete custom HTML elements by clicking on the ‘X’ button adjacent to the element. Be careful when deleting custom elements: All instances of the element will be deleted within the application/project.
+- **Create Instances on the Canvas**: Each component has its own canvas. Add an element to a component by dragging it onto the canvas. Div components are arbitrarily nestable and useful for complex layouts. Next.js and Gatsby.js projects have Link components to enable client-side navigation to other routes.
+- **Component Tree**: Click on the Component Tree tab next to the Code Preview tab to view the component tree hierarchy.
+- **Update Styling**: Select an element on the canvas to update its basic style attributes on the right panel. As you create new instances and add styling, watch as your code dynamically generates in the code preview in the bottom panel.
+- **User Preference Features**: With the click of a button, toggle between light mode and dark mode, depending on your preference.
+- **Export project**: Click the “Export Project’ button to export the project’s application files into a TypeScript file. The exported project is fully functional with Webpack, Express server, routing, etc., and will match what is mocked on the canvas.
#### Contributors
@@ -52,6 +51,8 @@ Download for [MacOS](https://github.com/team-reactype/ReacType/releases), [Windo
[Adam Singer](https://linkedin.com/in/adsing) [@spincycle01](https://github.com/spincycle01)
+[Alex Wolinsky](https://www.linkedin.com/in/alex-wolinsky-80ab591b2/) [@aw2934](https://github.com/aw2934/)
+
[Andrew Cho](https://www.linkedin.com/in/andrewjcho84/) [@andrewjcho84](https://github.com/andrewjcho84)
[Brian Han](https://www.linkedin.com/in/brianjisoohan/) [@brianjshan](https://github.com/brianjshan)
@@ -72,6 +73,12 @@ Download for [MacOS](https://github.com/team-reactype/ReacType/releases), [Windo
[Jin Soo Lim](https://www.linkedin.com/in/jin-soo-lim-3a567b1b3/) [@jinsoolim](https://github.com/jinsoolim)
+[Julie Wu](https://www.linkedin.com/in/jwuarchitect/) [@yutingwu4](https://github.com/yutingwu4)
+
+[Linh Tran](https://www.linkedin.com/in/linhtran51/) [@Linhatran](https://github.com/Linhatran)
+
+[Luke Madden](https://www.linkedin.com/in/lukemadden/) [@lukemadden](https://github.com/lukemadden)
+
[Mitchel Severe](https://www.linkedin.com/in/misevere/) [@mitchelsevere](https://github.com/mitchelsevere)
[Natalie Vick](https://www.linkedin.com/in/vicknatalie/) [@natattackvick](https://github.com/natattackvick)
@@ -114,9 +121,9 @@ npm run prod
npm run dev
```
-- Please note that the development build is not connected to the production server. `npm run dev` should spin up the development server from the server folder of this repo. For additional information, the readme is [here](https://github.com/open-source-labs/ReacType/blob/master/server/README.md). Alternatively, you can also select "Continue as guest" on the log-in page of the app to not use any features that rely on the server (authentication and saving project data.)
+- Please note that the development build is not connected to the production server. `npm run dev` should spin up the development server from the server folder of this repo. For additional information, the readme is [here](https://github.com/open-source-labs/ReacType/blob/master/server/README.md). Alternatively, you can select "Continue as guest" on the login page of the app, which will not use any features that rely on the server (authentication and saving project data.)
-## To Run Your Exported Next.js Project
+## To Run Your Exported Next.js or Gatsby.js Project
- Open exported project directory
- Install dependencies
diff --git a/__tests__/BottomTabs.test.tsx b/__tests__/BottomTabs.test.tsx
index 4976e1525..311217f6b 100644
--- a/__tests__/BottomTabs.test.tsx
+++ b/__tests__/BottomTabs.test.tsx
@@ -1,6 +1,6 @@
import React, { useReducer} from 'react';
import '@testing-library/jest-dom';
-import { render, fireEvent, cleanup, screen } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
import BottomTabs from '../app/src/components/bottom/BottomTabs';
import StateContext from '../app/src/context/context';
diff --git a/__tests__/HTMLPanel.test.tsx b/__tests__/HTMLPanel.test.tsx
index 211f3a331..35784512c 100644
--- a/__tests__/HTMLPanel.test.tsx
+++ b/__tests__/HTMLPanel.test.tsx
@@ -22,7 +22,7 @@ function Test() {
}
-test('Renders HTMLPanel component', () => {
+test('Renders HTMLPanel component properly', () => {
render(
);
@@ -37,7 +37,8 @@ test('Renders HTMLPanel component', () => {
expect(screen.getByText('Header 1')).toBeInTheDocument();
expect(screen.getByText('Header 2')).toBeInTheDocument();
expect(screen.getByText('Span')).toBeInTheDocument();
-})
+ expect(screen.queryByText('separator')).toBe(null);
+});
test('Adds new custom element', () => {
render(
@@ -54,5 +55,4 @@ test('Adds new custom element', () => {
fireEvent.click(screen.getByDisplayValue('Add Element'));
expect(screen.getByText('Testing')).toBeInTheDocument();
-})
-
+});
diff --git a/__tests__/__snapshots__/enzyme.test.tsx.snap b/__tests__/__snapshots__/enzyme.test.tsx.snap
index 058674934..e69de29bb 100644
--- a/__tests__/__snapshots__/enzyme.test.tsx.snap
+++ b/__tests__/__snapshots__/enzyme.test.tsx.snap
@@ -1,502 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Test All 10 default HTML elements have rendered Matches snapshot 1`] = `
-
-
-
-`;
diff --git a/__tests__/componentReducer.test.ts b/__tests__/componentReducer.test.ts
index 326ef7cb0..31e501785 100644
--- a/__tests__/componentReducer.test.ts
+++ b/__tests__/componentReducer.test.ts
@@ -1,5 +1,5 @@
import reducer from '../app/src/reducers/componentReducer';
-import { State, Action, Component, ChildElement } from '../app/src/interfaces/InterfacesNew';
+import { State, Action } from '../app/src/interfaces/InterfacesNew';
import initialState from '../app/src/context/initialState';
@@ -27,7 +27,7 @@ describe('Testing componentReducer functionality', function () {
// TEST 'ADD CHILD'
describe('ADD CHILD reducer', () => {
- it('should add child component to top-level component', () => {
+ it('should add child component and separator to top-level component', () => {
const action: Action = {
type: 'ADD CHILD',
payload: {
@@ -40,11 +40,13 @@ describe('Testing componentReducer functionality', function () {
state.canvasFocus = { componentId: 1, childId: null };
state = reducer(state, action);
const newParent = state.components[0];
- // expect new parent's children array to have length 1
- expect(newParent.children.length).toEqual(1);
+ // expect new parent's children array to have length 2 (component + separator)
+ expect(newParent.children.length).toEqual(2);
+ // expect first element in children array to be separator
+ expect(newParent.children[0].name).toEqual('separator');
// expect new child to have type 'Component'
- expect(newParent.children[0].type).toEqual('Component');
- const addedChild = state.components.find(comp => comp.id === newParent.children[0].typeId);
+ expect(newParent.children[1].type).toEqual('Component');
+ const addedChild = state.components.find(comp => comp.id === newParent.children[1].typeId);
// expect new child typeId to correspond to component with name 'TestRegular'
expect(addedChild.name).toEqual('TestRegular');
})
@@ -89,9 +91,10 @@ describe('Testing componentReducer functionality', function () {
state = reducer(state, action);
// expect only one remaining child
const delParent = state.components.find(comp => comp.id === state.canvasFocus.componentId);
- // expect remaining child to have type 'Component'
- expect(delParent.children.length).toEqual(1);
+ // expect remaining child to have type 'Component' and to be preceded by separator
+ expect(delParent.children.length).toEqual(2);
expect(delParent.children[delParent.children.length -1].type).toEqual('Component');
+ expect(delParent.children[delParent.children.length -2].name).toEqual('separator');
})
})
diff --git a/__tests__/enzyme.test.tsx b/__tests__/enzyme.test.tsx
index 135aa49f2..3b11589ed 100644
--- a/__tests__/enzyme.test.tsx
+++ b/__tests__/enzyme.test.tsx
@@ -1,5 +1,5 @@
-import { shallow, mount } from 'enzyme';
-import React, { useState, useContext } from 'react';
+import { shallow } from 'enzyme';
+import React from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import StateContext from '../app/src/context/context';
@@ -13,14 +13,20 @@ import Canvas from '../app/src/components/main/Canvas';
import HTMLPanel from '../app/src/components/left/HTMLPanel';
import HTMLItem from '../app/src/components/left/HTMLItem';
import LeftContainer from '../app/src/containers/LeftContainer';
+import AppContainer from '../app/src/containers/AppContainer';
+import NavBar from '../app/src/components/top/NavBar';
+import MenuItem from '@material-ui/core/MenuItem';
+import Tab from '@material-ui/core/Tab';
+import RightContainer from '../app/src/containers/RightContainer';
-// npm test -- -u
+/* If there is an error with unmatched snapshots because of intentionally modified codes, delete the contents in enzyme.test.tsx.snap to record new codes as blueprints */
describe('Test the CanvasContainer component', () => {
const target = shallow();
it('Matches snapshot', () => {
expect(target).toMatchSnapshot();
});
+ // test if Canvas component is rendered
it('Contains Canvas component', () => {
expect(target.contains()).toBe(true);
});
@@ -28,9 +34,11 @@ describe('Test the CanvasContainer component', () => {
describe('Test the MainContainer component', () => {
const target = shallow();
+ // test it canvas container is rendered
it('Contains CanvasContainer component', () => {
expect(target.contains()).toBe(true);
});
+ // test if bottom panel is rendered
it('Contains BottomPanel component', () => {
expect(target.contains()).toBe(true);
});
@@ -41,9 +49,22 @@ describe('Test the BottomTabs component', () => {
it('Matches snapshot', () => {
expect(target).toMatchSnapshot();
});
+ // test if bottom tab has a Code Preview and a Component Tree button
+ it('Has two tabs called "Code Preview" and "Component Tree" ', () => {
+ expect(target.find(Tab)).toHaveLength(2);
+ expect(target.find(Tab).at(0).prop('label')).toEqual('Code Preview');
+ expect(target.find(Tab).at(1).prop('label')).toEqual('Component Tree');
+ });
+ // test if the dropdown menu exists on the bottom tab
+ it('Has a dropdown selection menu for Classic React, Gatsby.js, and Next.js', () => {
+ expect(target.find(MenuItem)).toHaveLength(3);
+ expect(target.find(MenuItem).at(0).text()).toEqual('Classic React');
+ expect(target.find(MenuItem).at(1).text()).toEqual('Gatsby.js');
+ expect(target.find(MenuItem).at(2).text()).toEqual('Next.js');
+ });
});
-
-describe('Test All 10 default HTML elements have rendered', () => {
+// test the drag and drop component in the left panel
+describe('Test HTMLPanel Component', () => {
const target = shallow(
@@ -52,7 +73,93 @@ describe('Test All 10 default HTML elements have rendered', () => {
);
+ const props = {
+ name: 'abc',
+ key:'html-abc',
+ id:1,
+ Icon:'icon',
+ handleDelete: jest.fn()
+ };
+
it('Matches snapshot', () => {
expect(target).toMatchSnapshot();
});
+ // test if there are html items such as form, img, etc. on the left side
+ it('Should render HTMLItem', () => {
+ expect(target.find()).toBeDefined();
+});
+
+// testing for AppContainer
+describe('Test AppContainer container', () => {
+ const target = shallow();
+
+ const props = {
+ setTheme: jest.fn(),
+ isThemeLight: jest.fn(),
+ };
+
+ // testing if there is a NavBar
+ it('Should render NavBar', () => {
+ expect(
+ target.find(
+
+ )
+ ).toBeDefined();
+ });
+ // testing for a RightContainer
+ it('Should render RightContainer', () => {
+ expect(
+ target.contains(
+ ,
+ ),
+ ).toBe(true);
});
+
+// testing for NavBar component
+describe('Test NavBar component', () => {
+ const props = {
+ setTheme: jest.fn(),
+ isThemeLight: jest.fn(),
+ };
+ const target = shallow(
+
+ );
+ // testing for 4 generic buttons in NavBar
+ it('Should render 4 buttons: "Clear Canvas", "Export", "Dark Mode", "Login"', () => {
+ expect(target.find('.navbarButton')).toHaveLength(4);
+ expect(
+ target
+ .find('.navbarButton')
+ .at(0)
+ .text(),
+ ).toEqual('Clear Canvas');
+ expect(
+ target
+ .find('.navbarButton')
+ .at(1)
+ .text(),
+ ).toEqual('Export');
+ expect(
+ target
+ .find('.navbarButton')
+ .at(2)
+ .text(),
+ ).toEqual('Dark Mode');
+ expect(
+ target
+ .find('.navbarButton')
+ .at(3)
+ .text(),
+ ).toEqual('Login');
+ });
+});
+
+describe('Test LeftContainer container', () => {
+ const target = shallow();
+ // test for the HTML panel (with all the html elements) on the left panel
+ it('Should render HTMLPanel', () => {
+ expect(target.find()).toBeDefined();
+ });
+});
+
+
diff --git a/__tests__/spec.js b/__tests__/spec.js
new file mode 100644
index 000000000..92ee61689
--- /dev/null
+++ b/__tests__/spec.js
@@ -0,0 +1,32 @@
+import 'regenerator-runtime/runtime'; // if there is an error with moduleNameMapper, npm -S install regenerator-runtime
+
+const { Application } = require('spectron');
+const electronPath = require('electron');
+const path = require('path');
+
+let app;
+
+beforeAll(() => {
+ // create a new app to test with setTimeout to be 15000 because the app takes a few seconds to spin up
+ app = new Application({
+ path: electronPath,
+ chromeDriverArgs: ['--disable-extensions'],
+ args: [path.join(__dirname, '../app/electron/main.js')] // this is the path from this test file to main.js inside electron folder
+ });
+ return app.start();
+}, 15000);
+
+// getWindowsCount() will return 2 instead of 1 in dev mode (one for the actual app, one in the browser at localhost:8080 in dev mode)
+test('Displays App window', async () => {
+ const windowCount = await app.client.getWindowCount();
+ // expect(windowCount).toBe(1); // this returns true/passed if in production mode, change mode in script "test" to 'production' instead of 'test'
+ expect(windowCount).toBe(2); // 'dev' or 'test' mode results in 2 windows (one for the app and one for the browser)
+});
+
+/* we want to test other functionalities of app.client such as text, title, etc. but even the examples from the official spectron website
+or github repo did not yield the same outcomes as demonstrated. So we stopped testing Electron app here */
+afterAll(() => {
+ if (app && app.isRunning()) {
+ return app.stop();
+ }
+});
diff --git a/__tests__/tree.test.tsx b/__tests__/tree.test.tsx
index 5aa655586..e0200a664 100644
--- a/__tests__/tree.test.tsx
+++ b/__tests__/tree.test.tsx
@@ -3,17 +3,23 @@ import React, { useReducer } from 'react';
import '@testing-library/jest-dom';
import { render, fireEvent, cleanup, screen } from '@testing-library/react';
import StateContext from '../app/src/context/context';
-import { State, Action, Component, ChildElement } from '../app/src/interfaces/Interfaces';
+import {
+ State,
+ Action,
+ Component,
+ ChildElement
+} from '../app/src/interfaces/Interfaces';
import initialState from '../app/src/context/initialState';
import reducer from '../app/src/reducers/componentReducer';
import 'd3';
-const tester = [
- {
- id: 1,
- name: 'index',
- style: {},
- code: `import React, { useState } from 'react';
+// tester populates the components array used for this testing suite
+const tester = [
+ {
+ id: 1,
+ name: 'index',
+ style: {},
+ code: `import React, { useState } from 'react';
import A from '../components/A';
import B from '../components/B';
import Head from 'next/head';
@@ -37,79 +43,81 @@ const tester = [
};
export default index;
`,
- children: [
- {
- childId: 1,
- children: [
- {
- childId: 2,
- children: [],
- name: 'A',
- style: {},
- type: "Component",
- typeId: 2
- }
- ],
- name: 'div',
- style: {},
- type: "HTML Element",
- typeId: 11
- },
- {
- childId: 3,
- children: [
- {
- childId: 4,
- children: [],
- name: 'B',
- style: {},
- type: "Component",
- typeId: 3
- }
- ],
- name: 'div',
- style: {},
- type: "HTML Element",
- typeId: 11
- }
- ],
- isPage: true
- },
- {
- id: 2,
- nextChildId: 1,
- name: 'A',
- style: {},
- code: '',
- children: [],
- isPage: false
- },
- {
- id: 3,
- nextChildId: 1,
- name: 'B',
- style: {},
- code: '',
- children: [],
- isPage: false
+ children: [
+ {
+ childId: 1,
+ children: [
+ {
+ childId: 2,
+ children: [],
+ name: 'A',
+ style: {},
+ type: 'Component',
+ typeId: 2
+ }
+ ],
+ name: 'div',
+ style: {},
+ type: 'HTML Element',
+ typeId: 11
+ },
+ {
+ childId: 3,
+ children: [
+ {
+ childId: 4,
+ children: [],
+ name: 'B',
+ style: {},
+ type: 'Component',
+ typeId: 3
+ }
+ ],
+ name: 'div',
+ style: {},
+ type: 'HTML Element',
+ typeId: 11
+ }
+ ],
+ isPage: true
+ },
+ {
+ id: 2,
+ nextChildId: 1,
+ name: 'A',
+ style: {},
+ code: '',
+ children: [],
+ isPage: false
+ },
+ {
+ id: 3,
+ nextChildId: 1,
+ name: 'B',
+ style: {},
+ code: '',
+ children: [],
+ isPage: false
}
- ]
+];
+// renders a tree of the components in tester
function Test() {
const [state, dispatch] = useReducer(reducer, initialState);
state.components = tester;
return (
-
+
);
}
-test('Test the tree functionality', function () {
- render();
-
+test('Test the tree functionality', function() {
+ render();
+ // elements that are not separators should appear in the tree
expect(screen.getByText('index')).toBeInTheDocument();
expect(screen.getByText('A')).toBeInTheDocument();
expect(screen.getByText('B')).toBeInTheDocument();
-
-})
\ No newline at end of file
+ // tree should not include separators
+ expect(screen.queryByText('separator')).toBe(null);
+});
diff --git a/__tests__/userAuth.test.ts b/__tests__/userAuth.test.ts
index e6f842aea..517b47312 100644
--- a/__tests__/userAuth.test.ts
+++ b/__tests__/userAuth.test.ts
@@ -1,44 +1,52 @@
import { sessionIsCreated, newUserIsCreated } from '../app/src/helperFunctions/auth';
+// tests auth.ts helper function and associated server routes
describe('Login Tests', () => {
jest.setTimeout(10000);
let username;
let password;
+ let isFbOauth; // whether OAuth is used
// Called under SignIn.tsx
describe('sessionIsCreated', () => {
it('returns the message \'No Username Input\' when no username is entered', async () => {
username = '';
- password = 'codesmith1!'
- const result = await sessionIsCreated(username, password).then((loginStatus) => loginStatus);
+ password = 'Reactype123!@#';
+ isFbOauth = false;
+ const result = await sessionIsCreated(username, password, isFbOauth).then((loginStatus) => loginStatus);
expect(result).toEqual('No Username Input');
})
it('returns the message \'No Password Input\' when no password is entered', async () => {
- username = 'reactyp3test';
- password = ''
- const result = await sessionIsCreated(username, password).then((loginStatus) => loginStatus);
+ username = 'reactype123';
+ password = '';
+ isFbOauth = false;
+ const result = await sessionIsCreated(username, password, isFbOauth).then((loginStatus) => loginStatus);
expect(result).toEqual('No Password Input');
})
it('returns the message \'Invalid Username\' when username does not exist', async () => {
- username = 'l!b'; //breaks the 4 character minimum and no special characters
+ username = 'l!b'; //breaks the 4 character minimum and no special characters restriction
password = 'test';
- const result = await sessionIsCreated(username, password).then((loginStatus) => loginStatus);
+ isFbOauth = false;
+ const result = await sessionIsCreated(username, password, isFbOauth).then((loginStatus) => loginStatus);
expect(result).toEqual('Invalid Username');
})
it('returns the message \'Incorrect Password\' when password does not match', async () => {
username = 'reactyp3test';
password = 'incorrect';
- const result = await sessionIsCreated(username, password).then((loginStatus) => loginStatus);
+ isFbOauth = false;
+ const result = await sessionIsCreated(username, password, isFbOauth).then((loginStatus) => loginStatus);
expect(result).toEqual('Incorrect Password');
})
-
+ // note that the username and password in this test are kept in the heroku database
+ // DO NOT CHANGE unless you have access to the heroku database
it('returns the message \'Success\' when the user passes all auth checks', async () => {
- username = 'reactyp3test';
+ username = 'testing';
password = 'codesmith1!';
- const result = await sessionIsCreated(username, password).then((loginStatus) => loginStatus);
+ isFbOauth = false;
+ const result = await sessionIsCreated(username, password, isFbOauth).then((loginStatus) => loginStatus);
expect(result).toEqual('Success');
})
})
diff --git a/__tests__/users.test.js b/__tests__/users.test.js
index 39e76ec71..58915a206 100644
--- a/__tests__/users.test.js
+++ b/__tests__/users.test.js
@@ -1,18 +1,25 @@
const request = require('supertest');
-//let server = 'https://reactype.herokuapp.com';
-let server = 'http://localhost:5000';
-const isDev = process.env.NODE_ENV === 'development';
-if (isDev) {
- server = 'http://localhost:5000';
-}
+// let server = 'https://reactype.herokuapp.com'; /* This is for production mode */
-console.log('is Dev====???', process.env.NODE_ENV);
+const server = 'http://localhost:5000';
+const browser = 'http://localhost:8080'; // for checking endpoints accessed with hash router
// tests user signup and login routes
describe('User authentication tests', () => {
- let num = Math.floor(Math.random() * 1000);
+ const num = Math.floor(Math.random() * 1000);
+ // tests whether signup page is returned on navigation to /#/signup endpoint
+ // note that /#/ is required in endpoint because it is accessed via hash router
describe('/signup', () => {
+ describe('GET', () => {
+ it('respond with status 200 and load signup file', () => {
+ return request(browser)
+ .get('/#/signup')
+ .expect('Content-Type', /text\/html/)
+ .expect(200);
+ });
+ });
+ // tests whether new user can sign up
describe('POST', () => {
it('responds with status 200 and json object on valid new user signup', () => {
return request(server)
@@ -20,38 +27,51 @@ describe('User authentication tests', () => {
.send({
username: `supertest${num}`,
email: `test${num}@test.com`,
- password: `${num}`,
+ password: `${num}`
})
- .expect('Content-Type', /json/)
+ .set('Content-Type', 'application/json')
.expect(200)
- .then((res) => expect(typeof res.body).toBe('object'));
+ .then(res => expect(typeof res.body).toBe('object'));
});
+ // if invalid signup input, should respond with status 400
it('responds with status 400 and json string on invalid new user signup', () => {
return request(server)
.post('/signup')
.send({
- username: 'reactyp3test',
- email: 'testaccount@gmail.com',
- password: 'password',
+ username: 'reactype123',
+ email: 'reactype@gmail.com',
+ password: 'Reactype123!@#'
})
+ .set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(400)
- .then((res) => expect(typeof res.body).toBe('string'));
+ .then(res => expect(typeof res.body).toBe('string'));
});
});
});
+ // tests whether login page is returned on navigation to /#/login endpoint
describe('/login', () => {
+ describe('GET', () => {
+ it('respond with status 200 and load login file', () => {
+ return request(browser)
+ .get('/#/login')
+ .expect('Content-Type', /text\/html/)
+ .expect(200);
+ });
+ });
+ // tests whether existing login information permits user to log in
describe('POST', () => {
it('responds with status 200 and json object on verified user login', () => {
return request(server)
.post('/login')
- .send({ username: 'testing', password: 'codesmith1!' })
+ .send({ username: 'reactype123', password: 'Reactype123!@#' })
.expect(200)
.expect('Content-Type', /json/)
- .then((res) =>
- expect(res.body.sessionId).toEqual('5fa99d1930e67b513c17ba61')
+ .then(res =>
+ expect(res.body.sessionId).toEqual('60123800e51f92e14363d97e')
);
});
+ // if invalid username/password, should respond with status 400
it('responds with status 400 and json string on invalid user login', () => {
return request(server)
.post('/login')
@@ -63,10 +83,11 @@ describe('User authentication tests', () => {
});
});
});
+// OAuth tests (currently inoperative)
describe('Github oauth tests', () => {
describe('/github/callback?code=', () => {
describe('GET', () => {
- it('responds with status 400 and error message if no code received', () => {
+ xit('responds with status 400 and error message if no code received', () => {
return request(server)
.get('/github/callback?code=')
.expect(400)
@@ -74,7 +95,7 @@ describe('Github oauth tests', () => {
return expect(res.text).toEqual('\"Undefined or no code received from github.com\"');
});
});
- it('responds with status 400 if invalid code received', () => {
+ xit('responds with status 400 if invalid code received', () => {
return request(server)
.get('/github/callback?code=123456')
.expect(400)
diff --git a/app/electron/main.js b/app/electron/main.js
index dafc3d7c1..9df1600a1 100644
--- a/app/electron/main.js
+++ b/app/electron/main.js
@@ -1,3 +1,9 @@
+/*
+@description: main.js is what controls the lifecycle of the electron application from initialization to termination.
+@actions: codes for Github Oauth has been commented out because of lack of functionality.
+*/
+require('dotenv').config();
+const path = require('path');
const {
app,
protocol,
@@ -5,34 +11,30 @@ const {
BrowserWindow,
session,
ipcMain,
- webContents
} = require('electron');
// The splash screen is what appears while the app is loading
const { initSplashScreen, OfficeTemplate } = require('electron-splashscreen');
const { resolve } = require('app-root-path');
+// to install react dev tool extension
const {
default: installExtension,
REACT_DEVELOPER_TOOLS
} = require('electron-devtools-installer');
const debug = require('electron-debug');
+// import custom protocol in protocol.js
const Protocol = require('./protocol');
// menu from another file to modularize the code
const MenuBuilder = require('./menu');
-const path = require('path');
-// const fs = require('fs');
-require('dotenv').config();
-
+// mode that the app is running in
const isDev =
process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
const port = 8080;
const selfHost = `http://localhost:${port}`;
-// main.js is what controls the lifecycle of the electron application
-
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
@@ -41,14 +43,13 @@ let menuBuilder;
// function to create a new browser window
// this function will be called when Electron has initialized itself
async function createWindow() {
+ // install react dev tools if we are in development mode
if (isDev) {
await installExtension([REACT_DEVELOPER_TOOLS])
.then(name => console.log(`Added Extension: ${name}`))
.catch(err => console.log('An error occurred: ', err));
} else {
- // Needs to happen before creating/loading the browser window;
- // not necessarily instead of extensions, just using this code block
- // so I don't have to write another 'if' statement
+ // this will happen before creating the browser window. it returns a Boolean whether the protocol of scheme 'app://' was successfully registered and a file (index-prod.html) was sent as the response
protocol.registerBufferProtocol(Protocol.scheme, Protocol.requestHandler);
}
@@ -196,19 +197,10 @@ app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', () => {
- // On macOS it is common for applications and their menu bar
- // to stay active until the user quits explicitly with Cmd + Q
- // if (process.platform !== 'darwin') {
- // app.quit();
- // } else {
- // ContextMenu.clearMainBindings(ipcMain);
- // }
app.quit();
});
app.on('activate', () => {
- // On macOS it's common to re-create a window in the app when the
- // dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow();
}
@@ -360,155 +352,154 @@ if (isDev) {
serverUrl = 'http://localhost:5000';
}
-// for github oauth login in production, since cookies are not accessible through document.cookie on local filesystem, we need electron to grab the cookie that is set from oauth, this listens for an set cookie event from the renderer process then sends back the cookie
-ipcMain.on('set_cookie', event => {
- session.defaultSession.cookies
- .get({ url: serverUrl })
- .then(cookie => {
- // this if statement is necessary or the setInterval on main app will constantly run and will emit this event.reply, causing a memory leak
- // checking for a cookie inside array will only emit reply when a cookie exists
- if (cookie[0]) {
- //console.log(cookie);
- event.reply('give_cookie', cookie);
- }
- })
- .catch(error => {
- console.log('Error giving cookies in set_cookie:', error);
- });
-});
-
-// again for production, document.cookie is not accessible so we need this listener on main to delete the cookie on logout
-ipcMain.on('delete_cookie', event => {
- session.defaultSession.cookies
- .remove(serverUrl, 'ssid')
- // .then(removed => {
- // console.log('Cookies deleted', removed);
- // })
- .catch(err => console.log('Error deleting cookie:', err));
-});
-
-// opens new window for github oauth when button on sign in page is clicked
-ipcMain.on('github', event => {
- // your applications credentials
- const githubUrl = 'https://github.com/login/oauth/authorize?';
- const options = {
- client_id: process.env.GITHUB_ID,
- client_secret: process.env.GITHUB_SECRET,
- scopes: ['user:email', 'notifications']
- };
- // create new browser window object with size, title, security options
- const github = new BrowserWindow({
- width: 800,
- height: 600,
- title: 'Github Oauth',
- webPreferences: {
- nodeIntegration: false,
- nodeIntegrationInWorker: false,
- nodeIntegrationInSubFrames: false,
- contextIsolation: true,
- enableRemoteModule: false,
- zoomFactor: 1.0
- }
- });
- const authUrl =
- `${githubUrl}client_id=${process.env.GITHUB_ID}`;
- github.loadURL(authUrl);
- github.show();
- const handleCallback = url => {
- const raw_code = /code=([^&]\*)/.exec(url) || null;
- const code = raw_code && raw_code.length > 1 ? raw_code[1] : null;
- const error = /\?error=(.+)\$/.exec(url);
-
- if (code || error) {
- // Close the browser if code found or error
- authWindow.destroy();
- }
-
- // If there is a code, proceed to get token from github
- if (code) {
- self.requestGithubToken(options, code);
- } else if (error) {
- alert(
- "Oops! Something went wrong and we couldn't" +
- 'log you in using Github. Please try again.'
- );
- }
- };
-
- github.webContents.on('will-navigate', (e, url) => handleCallback(url));
-
- github.webContents.on('did-finish-load', (e, url, a, b) => {
- github.webContents.selectAll();
- });
-
- github.webContents.on('did-get-redirect-request', (e, oldUrl, newUrl) =>
- handleCallback(newUrl)
- );
-
- // Reset the authWindow on close
- github.on('close', () => (authWindow = null), false);
-
- // if final callback is reached and we get a redirect from server back to our app, close oauth window
- github.webContents.on('will-redirect', (e, callbackUrl) => {
- const matches = callbackUrl.match(/(?<=\?=).*/);
- const ssid = matches ? matches[0] : '';
- callbackUrl = callbackUrl.replace(/\?=.*/, '');
- let redirectUrl = 'app://rse/';
- if (isDev) {
- redirectUrl = 'http://localhost:8080/';
- }
- if (callbackUrl === redirectUrl) {
- dialog.showMessageBox({
- type: 'info',
- title: 'ReacType',
- icon: resolve('app/src/public/icons/png/256x256.png'),
- message: 'Github Oauth Successful!'
- });
- github.close();
- win.webContents
- .executeJavaScript(`window.localStorage.setItem('ssid', '${ssid}')`)
- .then(result => win.loadURL(selfHost))
- .catch(err => console.log(err));
- }
- });
-});
-
-ipcMain.on('tutorial', event => {
- // create new browser window object with size, title, security options
- const tutorial = new BrowserWindow({
- width: 800,
- height: 600,
- minWidth: 661,
- title: 'Tutorial',
- webPreferences: {
- nodeIntegration: false,
- nodeIntegrationInWorker: false,
- nodeIntegrationInSubFrames: false,
- contextIsolation: true,
- enableRemoteModule: false,
- zoomFactor: 1.0
- }
- });
- // redirects to relevant server endpoint
- //github.loadURL(`${serverUrl}/github`);
- // show window
- tutorial.show();
- // if final callback is reached and we get a redirect from server back to our app, close oauth window
- // github.webContents.on('will-redirect', (e, callbackUrl) => {
- // let redirectUrl = 'app://rse/';
- // if (isDev) {
- // redirectUrl = 'http://localhost:8080/';
- // }
- // if (callbackUrl === redirectUrl) {
- // dialog.showMessageBox({
- // type: 'info',
- // title: 'ReacType',
- // icon: resolve('app/src/public/icons/png/256x256.png'),
- // message: 'Github Oauth Successful!'
- // });
- // github.close();
- // }
- // });
-});
-
-//module.exports = dialog;
+// // for github oauth login in production, since cookies are not accessible through document.cookie on local filesystem, we need electron to grab the cookie that is set from oauth, this listens for an set cookie event from the renderer process then sends back the cookie
+// ipcMain.on('set_cookie', event => {
+// session.defaultSession.cookies
+// .get({ url: serverUrl })
+// .then(cookie => {
+// // this if statement is necessary or the setInterval on main app will constantly run and will emit this event.reply, causing a memory leak
+// // checking for a cookie inside array will only emit reply when a cookie exists
+// if (cookie[0]) {
+// //console.log(cookie);
+// event.reply('give_cookie', cookie);
+// }
+// })
+// .catch(error => {
+// console.log('Error giving cookies in set_cookie:', error);
+// });
+// });
+
+// // again for production, document.cookie is not accessible so we need this listener on main to delete the cookie on logout
+// ipcMain.on('delete_cookie', event => {
+// session.defaultSession.cookies
+// .remove(serverUrl, 'ssid')
+// // .then(removed => {
+// // console.log('Cookies deleted', removed);
+// // })
+// .catch(err => console.log('Error deleting cookie:', err));
+// });
+
+// // opens new window for github oauth when button on sign in page is clicked
+// ipcMain.on('github', event => {
+// // your applications credentials
+// const githubUrl = 'https://github.com/login/oauth/authorize?';
+// const options = {
+// client_id: process.env.GITHUB_ID,
+// client_secret: process.env.GITHUB_SECRET,
+// scopes: ['user:email', 'notifications']
+// };
+// // create new browser window object with size, title, security options
+// const github = new BrowserWindow({
+// width: 800,
+// height: 600,
+// title: 'Github Oauth',
+// webPreferences: {
+// nodeIntegration: false,
+// nodeIntegrationInWorker: false,
+// nodeIntegrationInSubFrames: false,
+// contextIsolation: true,
+// enableRemoteModule: false,
+// zoomFactor: 1.0
+// }
+// });
+// const authUrl = `${githubUrl}client_id=${process.env.GITHUB_ID}`;
+// github.loadURL(authUrl);
+// github.show();
+// const handleCallback = url => {
+// const raw_code = /code=([^&]\*)/.exec(url) || null;
+// const code = raw_code && raw_code.length > 1 ? raw_code[1] : null;
+// const error = /\?error=(.+)\$/.exec(url);
+
+// if (code || error) {
+// // Close the browser if code found or error
+// authWindow.destroy();
+// }
+
+// // If there is a code, proceed to get token from github
+// if (code) {
+// self.requestGithubToken(options, code);
+// } else if (error) {
+// alert(
+// "Oops! Something went wrong and we couldn't" +
+// 'log you in using Github. Please try again.'
+// );
+// }
+// };
+
+// github.webContents.on('will-navigate', (e, url) => handleCallback(url));
+
+// github.webContents.on('did-finish-load', (e, url, a, b) => {
+// github.webContents.selectAll();
+// });
+
+// github.webContents.on('did-get-redirect-request', (e, oldUrl, newUrl) =>
+// handleCallback(newUrl)
+// );
+
+// // Reset the authWindow on close
+// github.on('close', () => (authWindow = null), false);
+
+// // if final callback is reached and we get a redirect from server back to our app, close oauth window
+// github.webContents.on('will-redirect', (e, callbackUrl) => {
+// const matches = callbackUrl.match(/(?<=\?=).*/);
+// const ssid = matches ? matches[0] : '';
+// callbackUrl = callbackUrl.replace(/\?=.*/, '');
+// let redirectUrl = 'app://rse/';
+// if (isDev) {
+// redirectUrl = 'http://localhost:8080/';
+// }
+// if (callbackUrl === redirectUrl) {
+// dialog.showMessageBox({
+// type: 'info',
+// title: 'ReacType',
+// icon: resolve('app/src/public/icons/png/256x256.png'),
+// message: 'Github Oauth Successful!'
+// });
+// github.close();
+// win.webContents
+// .executeJavaScript(`window.localStorage.setItem('ssid', '${ssid}')`)
+// .then(result => win.loadURL(selfHost))
+// .catch(err => console.log(err));
+// }
+// });
+// });
+
+// ipcMain.on('tutorial', event => {
+// // create new browser window object with size, title, security options
+// const tutorial = new BrowserWindow({
+// width: 800,
+// height: 600,
+// minWidth: 661,
+// title: 'Tutorial',
+// webPreferences: {
+// nodeIntegration: false,
+// nodeIntegrationInWorker: false,
+// nodeIntegrationInSubFrames: false,
+// contextIsolation: true,
+// enableRemoteModule: false,
+// zoomFactor: 1.0
+// }
+// });
+// // redirects to relevant server endpoint
+// //github.loadURL(`${serverUrl}/github`);
+// // show window
+// tutorial.show();
+// // if final callback is reached and we get a redirect from server back to our app, close oauth window
+// // github.webContents.on('will-redirect', (e, callbackUrl) => {
+// // let redirectUrl = 'app://rse/';
+// // if (isDev) {
+// // redirectUrl = 'http://localhost:8080/';
+// // }
+// // if (callbackUrl === redirectUrl) {
+// // dialog.showMessageBox({
+// // type: 'info',
+// // title: 'ReacType',
+// // icon: resolve('app/src/public/icons/png/256x256.png'),
+// // message: 'Github Oauth Successful!'
+// // });
+// // github.close();
+// // }
+// // });
+// });
+
+module.exports = dialog;
diff --git a/app/electron/menu.js b/app/electron/menu.js
index dbb31370c..85ab23511 100644
--- a/app/electron/menu.js
+++ b/app/electron/menu.js
@@ -7,6 +7,29 @@ const port = 5000;
const Protocol = require('./protocol');
const tutorialRoute = `http://localhost:${port}/tutorial`;
+/*
+DESCRIPTION: This file generates an array containing a menu based on the operating system the user is running.
+
+menuBuilder: The entire file is encompassed in menuBuilder. Ultimately, menuBuilder returns a function called
+ buildMenu that uses defaultTemplate to construct a menu at the top of the application (as invoked in main.js)
+
+ Standard menu roles (e.g., undo, redo, quit, paste, etc.) come from Electron API and need not be separately coded
+
+openTutorial: opens browser window containing tutorial on how to use the app
+ -Creates a browser window
+ -Tutorial is invoked within the "Help" menu
+
+defaultTemplate: returns an array of submenus (each an array)
+ -First, checks whether user is on a Mac (node returns 'darwin' for process.platform)
+ -Then generates a dropdown menu at the top of the screen (e.g., "File") accordingly
+ -The Mac check is necessary primarily for the first menu column, which is the name of the app
+ -If user is not on a Mac, alternative menus are generated
+ -Each menu:
+ -"label" is the field at the top of each menu (e.g., "File", "Edit", "View", etc.)
+ -"role" is a subitem within each menu (e.g., under "File," "Quit")
+ -"type: separator" creates a horizontal line in a menu (e.g., under "Redo" in the "Edit" menu)
+*/
+
// Create a template for a menu and create menu using that template
var MenuBuilder = function(mainWindow, appName) {
// https://electronjs.org/docs/api/menu#main-process
@@ -151,7 +174,6 @@ var MenuBuilder = function(mainWindow, appName) {
])
]
},
- // { role: "viewMenu" }
{
label: 'View',
submenu: [
diff --git a/app/electron/preload.js b/app/electron/preload.js
index 4656b06a9..0c30dde2f 100644
--- a/app/electron/preload.js
+++ b/app/electron/preload.js
@@ -15,6 +15,20 @@ const {
tutorial
} = require('./preloadFunctions/cookies');
+/*
+DESCRIPTION: This file appears to limit the node methods the Electron app can access.
+
+Per the docs:
+ -Main World is the JavaScript context in which the renderer code runs (that is, the page)
+ -Isolated World is where preload scripts run
+ -contextBridge is a module that safely exposes APIs from the isolated context in which preload scripts run
+ to the context in which the website or application runs (i.e., from Isolated World to Main World)
+
+We likely should not change this file unless we determine additional methods are necessary
+or some methods are not used.
+
+*/
+
// Expose protected methods that allow the renderer process to use select node methods
// without exposing all node functionality. This is a critical security feature
// 'mainWorld" is the context that the main renderer runs in
diff --git a/app/electron/protocol.js b/app/electron/protocol.js
index ffebb2cde..a7014f8b4 100644
--- a/app/electron/protocol.js
+++ b/app/electron/protocol.js
@@ -1,13 +1,15 @@
-
-// Implementing a custom protocol achieves two goals:
-// 1) Allows us to use ES6 modules/targets for Angular
-// 2) Avoids running the app in a file:// origin
+/*
+ @desc: register a custom protocol and specify file that will be served on request to the origin '/'. our app will be served from 'app://...' instead of the default 'file://...'
+ @exports: scheme, requestHandler
+ @usage: is used in main.js
+ */
const fs = require('fs');
const path = require('path');
const DIST_PATH = path.join(__dirname, '../../app/dist');
-const scheme = 'app';
+
+const scheme = 'app'; // it will serve resources like app://..... instead of default file://...
const mimeTypes = {
'.js': 'text/javascript',
@@ -28,26 +30,38 @@ function charset(mimeType) {
? 'utf-8'
: null;
}
-
+// return the file type
function mime(filename) {
const type = mimeTypes[path.extname(`${filename || ''}`).toLowerCase()];
- return type ? type : null;
+ return type || null;
}
+/* requestHandler
+ servers index-prod.html when we access the root endpoint '/'
+ read the file above and pass on an object includes mimeType, charset, and exisiting data read from the file
+*/
function requestHandler(req, next) {
+ // The URL() constructor returns a newly created URL object representing the URL defined by the parameters.
const reqUrl = new URL(req.url);
+ // path.normalize resolves '..' and '.' segments in sequential path segments
+ // url.pathname: an initial '/' followed by the path of the URL not including the query string or fragment (or the empty string if there is no path).
let reqPath = path.normalize(reqUrl.pathname);
+
+ // when app opens, serve index-prod.html
if (reqPath === '/') {
reqPath = '/index-prod.html';
}
+ // path.basename returns the last portion of a path which includes filename we want to serve
const reqFilename = path.basename(reqPath);
+ // use fs module to read index-prod.html (reqPath) in dist folder
fs.readFile(path.join(DIST_PATH, reqPath), (err, data) => {
- const mimeType = mime(reqFilename);
+ const mimeType = mime(reqFilename); // returns the file type
+ // check if there is no error and file type is valid, pass on the info to the next middleware
if (!err && mimeType !== null) {
next({
- mimeType: mimeType,
+ mimeType,
charset: charset(mimeType),
- data: data
+ data
});
} else {
console.error(err);
diff --git a/app/src/components/bottom/BottomPanel.tsx b/app/src/components/bottom/BottomPanel.tsx
index a3c2815cf..997c726a6 100644
--- a/app/src/components/bottom/BottomPanel.tsx
+++ b/app/src/components/bottom/BottomPanel.tsx
@@ -1,20 +1,17 @@
import React, { useContext } from 'react';
-// import StateContext from '../../context/context';
import BottomTabs from './BottomTabs';
import { Resizable } from 're-resizable';
-// const IPC = require('electron').ipcRenderer;
-
const BottomPanel = () => {
return (
-
+
diff --git a/app/src/components/bottom/BottomTabs.tsx b/app/src/components/bottom/BottomTabs.tsx
index f415dad0b..58e6c7c4c 100644
--- a/app/src/components/bottom/BottomTabs.tsx
+++ b/app/src/components/bottom/BottomTabs.tsx
@@ -10,6 +10,8 @@ import { emitKeypressEvents } from 'readline';
import NativeSelect from '@material-ui/core/NativeSelect';
import FormControl from '@material-ui/core/FormControl';
import { styleContext } from '../../containers/AppContainer';
+import MenuItem from '@material-ui/core/MenuItem';
+import Select from '@material-ui/core/Select';
const BottomTabs = () => {
// state that controls which tab the user is on
@@ -17,14 +19,20 @@ const BottomTabs = () => {
const [tab, setTab] = useState(0);
const classes = useStyles();
treeWrapper: HTMLDivElement;
- const [theme, setTheme] = useState('monokai');
+ const [theme, setTheme] = useState('solarized_light');
const { style } = useContext(styleContext);
- // method changes the
+
+ // breaks if handleChange is commented out
const handleChange = (event: React.ChangeEvent, value: number) => {
setTab(value);
};
-
+ // Allows users to toggle project between "next.js" and "Classic React"
+ // When a user changes the project type, the code of all components is rerendered
+ const handleProjectChange = event => {
+ const projectType = event.target.value;
+ dispatch({ type: 'CHANGE PROJECT TYPE', payload: { projectType } });
+ };
const { components, HTMLTypes } = state;
const changeTheme = e => {
@@ -33,7 +41,7 @@ const BottomTabs = () => {
return (
+
+
+ }
{modal}
);
diff --git a/app/src/components/left/HTMLPanel.tsx b/app/src/components/left/HTMLPanel.tsx
index 82fe984ce..d676ff4be 100644
--- a/app/src/components/left/HTMLPanel.tsx
+++ b/app/src/components/left/HTMLPanel.tsx
@@ -4,14 +4,27 @@ import StateContext from '../../context/context';
import HTMLItem from './HTMLItem';
import { makeStyles } from '@material-ui/core/styles';
-const HTMLPanel = (): JSX.Element => {
+/*
+DESCRIPTION: This is the bottom half of the left panel, starting from the 'HTML
+ Elements' header. The boxes containing each HTML element are rendered in
+ HTMLItem, which itself is rendered by this component.
+
+Central state contains all available HTML elements (stored in the HTMLTypes property).
+ The data for HTMLTypes is stored in HTMLTypes.tsx and is added to central state in
+ initialState.tsx.
+
+Hook state:
+ -tag:
+*/
+
+const HTMLPanel = (props): JSX.Element => {
const classes = useStyles();
const [tag, setTag] = useState('');
const [name, setName] = useState('');
const [errorMsg, setErrorMsg] = useState('');
const [errorStatus, setErrorStatus] = useState(false);
const [state, dispatch] = useContext(StateContext);
-
+ const {isThemeLight} = props;
let startingID = 0;
state.HTMLTypes.forEach(element => {
if (element.id >= startingID) startingID = element.id;
@@ -52,13 +65,15 @@ const HTMLPanel = (): JSX.Element => {
const triggerError = (type: String) => {
setErrorStatus(true);
if (type === 'empty') {
- setErrorMsg('Tag/ Tag name cannot be blank.');
+ setErrorMsg('* Input cannot be blank. *');
} else if (type === 'dupe') {
- setErrorMsg('Tag/ Tag name already exists.');
+ setErrorMsg('* Input already exists. *');
} else if (type === 'letters') {
- setErrorMsg('Tag/ Tag name must start with a letter.');
+ setErrorMsg('* Input must start with a letter. *');
} else if (type === 'symbolsDetected') {
- setErrorMsg('Tag/ Tag name must not contain symbols.');
+ setErrorMsg('* Input must not contain symbols. *');
+ } else if (type === 'length') {
+ setErrorMsg('* Input cannot exceed 10 characters. *');
}
};
@@ -111,6 +126,9 @@ const HTMLPanel = (): JSX.Element => {
} else if (checkNameDupe(tag) || checkNameDupe(name)) {
triggerError('dupe');
return;
+ } else if (name.length > 10) {
+ triggerError('length');
+ return;
}
createOption(tag, name);
resetError();
@@ -122,103 +140,154 @@ const HTMLPanel = (): JSX.Element => {
payload: id
});
};
-
+ // filter out separator so that it will not appear on the html panel
+ const htmlTypesToRender = state.HTMLTypes.filter(type => type.name !== 'separator')
return (
-