├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── public
├── AppIcon.png
├── appstore.png
├── electron.js
├── express.js
├── favicon.ico
├── index.html
├── manifest.json
├── mockAppIcon.png
└── robots.txt
├── src
├── App.css
├── App.js
├── App.test.js
├── components
│ ├── Button.js
│ ├── ErrorBoundary.js
│ ├── Label.js
│ ├── Sheet.js
│ ├── TextArea.js
│ └── TextBox.js
├── helpers
│ └── index.js
├── images
│ ├── AppIcon.png
│ ├── Expand.png
│ ├── Minimize.png
│ └── delete.png
├── index.css
├── index.js
├── logo.svg
├── pages
│ ├── HomePage
│ │ ├── HomePage.css
│ │ └── index.js
│ └── ReqForm
│ │ ├── ReqForm.css
│ │ └── index.js
├── serviceWorker.js
└── setupTests.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /dist
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Jaya Kumar Reddy Regalla
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Local Mock Server
2 |
3 | Local Mock Server is a simple tool for creating mock server locally. With this you can create number of mock api's by configuring request method, request path, response status and JSON response body
4 |
5 | This app spins up a server locally and saves all the JSON data locally on your system(on Desktop)
6 |
7 | Uses
8 | - Before integrating with the real api, front-end developers can create a mock server to simulate each endpoint and its corresponding response body and also response status
9 | - Helps in reducing the dependency of UI team with the server team
10 |
11 | ## ScreenShots
12 |
13 | - Creating new Mock API
14 |
15 |
16 | - Editing Mock API
17 |
18 |
19 | - JSON Viewer
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "local-mock-server",
3 | "description": "Local Mock Server is a simple tool for creating mock server locally. With this you can create number of mock api's by configuring request method, request path, response status and JSON response body",
4 | "version": "1.2.0",
5 | "author": "Jaya Kumar Reddy Regalla",
6 | "license": "MIT",
7 | "build": {
8 | "appId": "com.jay.localmockserver",
9 | "productName": "Local Mock Server",
10 | "mac": {
11 | "category": "public.app-category.utilities",
12 | "icon": "public/AppIcon.png"
13 | },
14 | "win": {
15 | "target": "nsis",
16 | "icon": "public/AppIcon.png"
17 | },
18 | "publish": [
19 | "github"
20 | ]
21 | },
22 | "main": "public/electron.js",
23 | "homepage": "./",
24 | "private": true,
25 | "dependencies": {
26 | "@testing-library/jest-dom": "^4.2.4",
27 | "@testing-library/react": "^9.3.2",
28 | "@testing-library/user-event": "^7.1.2",
29 | "cors": "^2.8.5",
30 | "cross-env": "^7.0.2",
31 | "electron-is-dev": "^1.1.0",
32 | "electron-updater": "^4.3.1",
33 | "react": "^16.13.1",
34 | "react-dom": "^16.13.1",
35 | "react-json-view": "^1.19.1",
36 | "react-scripts": "3.4.1",
37 | "styled-components": "^5.1.0"
38 | },
39 | "devDependencies": {
40 | "concurrently": "^5.1.0",
41 | "electron": "^8.2.0",
42 | "electron-builder": "^22.4.1",
43 | "wait-on": "^4.0.1"
44 | },
45 | "scripts": {
46 | "react-start": "react-scripts start",
47 | "react-build": "react-scripts build",
48 | "react-test": "react-scripts test --env=jsdom",
49 | "react-eject": "react-scripts eject",
50 | "electron-build": "electron-builder",
51 | "release": "yarn react-build && electron-builder --publish=never",
52 | "publish": "yarn react-build && electron-builder --publish=always",
53 | "publish-all": "yarn react-build && electron-builder -mwl --publish=always",
54 | "build": "yarn react-build && yarn electron-build",
55 | "start": "concurrently \"cross-env BROWSER=none yarn react-start\" \"wait-on http://localhost:3000 && electron .\""
56 | },
57 | "eslintConfig": {
58 | "extends": "react-app"
59 | },
60 | "browserslist": {
61 | "production": [
62 | ">0.2%",
63 | "not dead",
64 | "not op_mini all"
65 | ],
66 | "development": [
67 | "last 1 chrome version",
68 | "last 1 firefox version",
69 | "last 1 safari version"
70 | ]
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/public/AppIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jayakumarreddy/Local-Mock-Server/c4195d19f3a2a692f02da6acde2ad8d7e59b8c71/public/AppIcon.png
--------------------------------------------------------------------------------
/public/appstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jayakumarreddy/Local-Mock-Server/c4195d19f3a2a692f02da6acde2ad8d7e59b8c71/public/appstore.png
--------------------------------------------------------------------------------
/public/electron.js:
--------------------------------------------------------------------------------
1 | const { app, BrowserWindow } = require("electron");
2 | const path = require("path");
3 | const isDev = require("electron-is-dev");
4 | const { autoUpdater } = require("electron-updater");
5 |
6 | const { expressApp } = require("./express");
7 |
8 | const mockFilesStoragePath = app.getPath("userData");
9 |
10 | expressApp(mockFilesStoragePath);
11 |
12 | let mainWindow;
13 |
14 | function createWindow() {
15 | mainWindow = new BrowserWindow({
16 | webPreferences: {
17 | nodeIntegration: true,
18 | },
19 | icon: path.join(__dirname, "/public/mockAppIcon.png"),
20 | });
21 | mainWindow.loadURL(
22 | isDev
23 | ? "http://localhost:3000"
24 | : `file://${path.join(__dirname, "../build/index.html")}`
25 | );
26 | mainWindow.maximize();
27 | isDev && mainWindow.webContents.openDevTools();
28 |
29 | mainWindow.on("closed", () => (mainWindow = null));
30 | }
31 |
32 | app.on("ready", createWindow);
33 |
34 | app.on("window-all-closed", () => {
35 | if (process.platform !== "darwin") {
36 | app.quit();
37 | }
38 | });
39 |
40 | app.on("activate", () => {
41 | if (mainWindow === null) {
42 | createWindow();
43 | }
44 | });
45 |
46 | app.on("ready", function () {
47 | autoUpdater.checkForUpdatesAndNotify();
48 | });
49 |
--------------------------------------------------------------------------------
/public/express.js:
--------------------------------------------------------------------------------
1 | const expressApp = (mockFilesStoragePath) => {
2 | const express = require("express");
3 | const fs = require("fs");
4 | const app = express();
5 | const cors = require("cors");
6 | const port = 8000;
7 |
8 | const folderName = `mocks_folder`;
9 | const storage_folder = `${mockFilesStoragePath}/${folderName}`;
10 |
11 | fs.mkdirSync(storage_folder, { recursive: true });
12 |
13 | app.use(cors());
14 | app.use(express.json());
15 |
16 | app.use("/", (req, res, next) => {
17 | if (
18 | req.originalUrl !== "/allmocks" &&
19 | req.originalUrl !== "/createmock" &&
20 | req.originalUrl !== "/deletemock"
21 | ) {
22 | const rawData = fs.readFileSync(
23 | `${storage_folder}${req.originalUrl}/${req.method}.json`
24 | );
25 | const jsonData = JSON.parse(rawData);
26 | const resBody = jsonData.body;
27 | const resStatus = jsonData.status;
28 | res.status(resStatus).json(resBody);
29 | }
30 | next();
31 | });
32 |
33 | const getAllFiles = function (dirPath, arrayOfFiles) {
34 | let filesDir = fs.readdirSync(dirPath);
35 |
36 | arrayOfFiles = arrayOfFiles || [];
37 | filesDir.forEach((fileDir) => {
38 | if (fs.lstatSync(`${dirPath}/${fileDir}`).isDirectory()) {
39 | getAllFiles(`${dirPath}/${fileDir}`, arrayOfFiles);
40 | } else {
41 | // arrayOfFiles.push(`${dirPath}/${fileDir}`);
42 | if (fileDir !== ".DS_Store") {
43 | arrayOfFiles.push({
44 | reqPath: `${dirPath}/`,
45 | reqMethod: fileDir.replace(".json", ""),
46 | });
47 | }
48 | }
49 | });
50 |
51 | return arrayOfFiles;
52 | };
53 |
54 | app.get("/allmocks", (req, res) => {
55 | const files = getAllFiles(`${storage_folder}`);
56 | const mocks = files.map((file) => {
57 | return {
58 | ...file,
59 | reqPath: file.reqPath.replace(`${storage_folder}`, ""),
60 | };
61 | });
62 | res.status(200).send(mocks);
63 | });
64 |
65 | app.post("/createmock", (req, res) => {
66 | const { reqMethod, reqPath, resStatus, resBody } = req.body;
67 | const fileRawData = {
68 | status: resStatus,
69 | body: resBody,
70 | };
71 | const fileJSONData = JSON.stringify(fileRawData, null, 2);
72 |
73 | const folderPath = `${storage_folder}/${reqPath}`;
74 | fs.mkdir(folderPath, { recursive: true }, (err) => {
75 | if (err) {
76 | res.status(400).send(`Error in creating directory ${err}`);
77 | } else {
78 | fs.writeFile(`${folderPath}/${reqMethod}.json`, fileJSONData, (err) => {
79 | if (err) {
80 | res.status(400).send(`Error in writing to file ${err}`);
81 | } else {
82 | const rawData = fs.readFileSync(`${folderPath}/${reqMethod}.json`);
83 | const jsonData = JSON.parse(rawData);
84 | res.json(jsonData);
85 | }
86 | });
87 | }
88 | });
89 | });
90 |
91 | app.delete("/deletemock", (req, res) => {
92 | const { reqMethod, reqPath } = req.body;
93 |
94 | const filePath = `${storage_folder}/${reqPath}/${reqMethod}.json`;
95 |
96 | try {
97 | fs.unlinkSync(filePath);
98 | res.status(200).json({ success: "file Deleted" });
99 | } catch (err) {
100 | res.status(400).send(`Error in deleting file ${err}`);
101 | }
102 | });
103 |
104 | app.listen(port, () =>
105 | console.log(`Example app listening at http://localhost:${port}`)
106 | );
107 | };
108 | module.exports = { expressApp };
109 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jayakumarreddy/Local-Mock-Server/c4195d19f3a2a692f02da6acde2ad8d7e59b8c71/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
18 |
19 |
28 | Local Mock Server
29 |
30 |
31 | You need to enable JavaScript to run this app.
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/mockAppIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jayakumarreddy/Local-Mock-Server/c4195d19f3a2a692f02da6acde2ad8d7e59b8c71/public/mockAppIcon.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import HomePage from "./pages/HomePage";
5 | import "./App.css";
6 |
7 | const AppMain = styled.div`
8 | background-color: #292929;
9 | height: 100vh;
10 | `;
11 |
12 | function App() {
13 | return (
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render( );
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/Button.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Button = styled.button`
4 | background: #03dac5;
5 | border: 1px solid #000000;
6 | box-sizing: border-box;
7 | box-shadow: 6px 8px 4px rgba(0, 0, 0, 0.25);
8 | /* border-radius: 18px; */
9 | text-align: center;
10 | cursor: pointer;
11 | font-weight: bold;
12 | outline: none;
13 | height: ${props => (props.height ? props.height : "40px")};
14 | width: ${props => (props.width ? props.width : "150px")};
15 |
16 | &:active {
17 | transform: scale(0.9);
18 | }
19 | `;
20 |
21 | export default Button;
22 |
--------------------------------------------------------------------------------
/src/components/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | class ErrorBoundary extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = { hasError: false };
7 | }
8 |
9 | static getDerivedStateFromError(error) {
10 | // Update state so the next render will show the fallback UI.
11 | return { hasError: true };
12 | }
13 |
14 | componentDidCatch(error, errorInfo) {
15 | // You can also log the error to an error reporting service
16 | // logErrorToMyService(error, errorInfo);
17 | console.log("Error occured", errorInfo);
18 | }
19 |
20 | render() {
21 | if (this.state.hasError) {
22 | // You can render any custom fallback UI
23 | return Something went wrong.Please reload the app ;
24 | }
25 |
26 | return this.props.children;
27 | }
28 | }
29 |
30 | export default ErrorBoundary;
31 |
--------------------------------------------------------------------------------
/src/components/Label.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Label = styled.span`
4 | /* font-family: "Roboto Mono"; */
5 | font-size: ${props => props.fontSize};
6 | color: ${props => props.color};
7 | `;
8 |
9 | export default Label;
10 |
--------------------------------------------------------------------------------
/src/components/Sheet.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Sheet = styled.div`
4 | width: 100%;
5 | background: linear-gradient(
6 | 0deg,
7 | rgba(255, 255, 255, 0.12),
8 | rgba(255, 255, 255, 0.12)
9 | ),
10 | #121212;
11 | box-shadow: 0px 5px 5px rgba(0, 0, 0, 0.2), 0px 3px 14px rgba(0, 0, 0, 0.12),
12 | 0px 8px 10px rgba(0, 0, 0, 0.14);
13 | `;
14 |
15 | export default Sheet;
16 |
--------------------------------------------------------------------------------
/src/components/TextArea.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const TextArea = styled.textarea`
4 | background-color: #4f4f4f;
5 | /* border-radius: 18px; */
6 | border: none;
7 | outline: none;
8 | font-size: 17px;
9 | color: white;
10 | padding: 10px 10px;
11 | box-sizing: border-box;
12 | width: 100%;
13 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
14 | `;
15 |
16 | export default TextArea;
17 |
--------------------------------------------------------------------------------
/src/components/TextBox.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const TextBox = styled.input`
4 | background-color: #4f4f4f;
5 | /* border-radius: 18px; */
6 | height: ${props => (props.height ? props.height : "40px")};
7 | border: none;
8 | outline: none;
9 | font-size: 17px;
10 | color: white;
11 | padding: 5px 15px;
12 | box-sizing: border-box;
13 | width: 100%;
14 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
15 | `;
16 |
17 | export default TextBox;
18 |
--------------------------------------------------------------------------------
/src/helpers/index.js:
--------------------------------------------------------------------------------
1 | export const isStringifiedJSON = text => {
2 | if (typeof text !== "string") {
3 | return false;
4 | }
5 | try {
6 | return JSON.parse(text) instanceof Object;
7 | } catch (error) {
8 | return false;
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/src/images/AppIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jayakumarreddy/Local-Mock-Server/c4195d19f3a2a692f02da6acde2ad8d7e59b8c71/src/images/AppIcon.png
--------------------------------------------------------------------------------
/src/images/Expand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jayakumarreddy/Local-Mock-Server/c4195d19f3a2a692f02da6acde2ad8d7e59b8c71/src/images/Expand.png
--------------------------------------------------------------------------------
/src/images/Minimize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jayakumarreddy/Local-Mock-Server/c4195d19f3a2a692f02da6acde2ad8d7e59b8c71/src/images/Minimize.png
--------------------------------------------------------------------------------
/src/images/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jayakumarreddy/Local-Mock-Server/c4195d19f3a2a692f02da6acde2ad8d7e59b8c71/src/images/delete.png
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://bit.ly/CRA-PWA
17 | serviceWorker.unregister();
18 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/pages/HomePage/HomePage.css:
--------------------------------------------------------------------------------
1 | .home-page {
2 | display: flex;
3 | height: 100%;
4 | justify-content: space-between;
5 | }
6 |
7 | .left-wrapper {
8 | flex-basis: 35%;
9 | height: 100%;
10 | }
11 |
12 | .left-wrapper-inner {
13 | padding: 0px 0px 10px 10px;
14 | height: 100%;
15 | }
16 |
17 | .right-wrapper {
18 | flex-basis: 75%;
19 | }
20 |
21 | .right-wrapper-inner {
22 | height: 100%;
23 | padding: 20px 20px 10px 20px;
24 | }
25 |
26 | .header-title {
27 | display: flex;
28 | align-items: center;
29 | height: 10%;
30 | }
31 |
32 | .header-title img {
33 | margin-right: 10px;
34 | }
35 |
36 | .left-wrapper-mocks {
37 | color: white;
38 | height: 83%;
39 | overflow: auto;
40 | }
41 |
42 | .left-wrapper-mock {
43 | padding: 10px;
44 | margin: 7px 0px;
45 | font-size: 12px;
46 | cursor: pointer;
47 | word-break: break-all;
48 | display: flex;
49 | justify-content: space-between;
50 | align-items: center;
51 | }
52 |
53 | .left-wrapper-mock img {
54 | height: 12px;
55 | width: 9px;
56 | margin-left: 10px;
57 | }
58 |
59 | .left-wrapper-mock img:active {
60 | transform: scale(0.9);
61 | }
62 |
63 | .left-wrapper-no-mock {
64 | color: #e77354;
65 | font-weight: 500;
66 | font-size: 16px;
67 | }
68 |
69 | .left-wrapper-mock > div span:last-child {
70 | margin-left: 8px;
71 | }
72 |
73 | .left-wrapper-mock > span:first-child {
74 | color: #e57155;
75 | }
76 |
77 | .left-wrapper-mock-GET {
78 | color: #e5c552 !important;
79 | }
80 |
81 | .left-wrapper-mock-POST {
82 | color: #52d273 !important;
83 | }
84 |
85 | .left-wrapper-mock-PUT {
86 | color: #e95065 !important;
87 | }
88 |
89 | .left-wrapper-button button {
90 | margin-top: 10px;
91 | width: 100%;
92 | }
93 |
--------------------------------------------------------------------------------
/src/pages/HomePage/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import Sheet from "../../components/Sheet";
3 | import Label from "../../components/Label";
4 | import Button from "../../components/Button";
5 | import ErrorBoundary from "../../components/ErrorBoundary";
6 | import AppIcon from "../../images/AppIcon.png";
7 | import deleteIcon from "../../images/delete.png";
8 |
9 | import ReqForm from "../ReqForm";
10 |
11 | import "./HomePage.css";
12 |
13 | const defaultMockData = {
14 | reqMethod: "",
15 | reqPath: "",
16 | resStatus: "",
17 | resBody: JSON.stringify({})
18 | };
19 |
20 | const HomePage = () => {
21 | const [mocks, setMocks] = useState([]);
22 | const [mock, setMock] = useState(defaultMockData);
23 | const [createMockState, setCreateMockState] = useState({
24 | successMessage: "",
25 | errorMessage: "",
26 | isLoading: false,
27 | });
28 |
29 | const fetchMocks = async () => {
30 | let response = await fetch("http://localhost:8000/allmocks", {
31 | method: "GET",
32 | headers: {
33 | "Content-Type": "application/json",
34 | },
35 | });
36 | let result = await response.json();
37 | setMocks(result);
38 | };
39 |
40 | useEffect(() => {
41 | fetchMocks();
42 | }, []);
43 |
44 | const onsubmit = async ({ reqMethod, reqPath, resStatus, resBody }) => {
45 | setCreateMockState({
46 | successMessage: "",
47 | errorMessage: "",
48 | isLoading: true,
49 | });
50 | const response = await fetch("http://localhost:8000/createmock", {
51 | method: "POST",
52 | headers: {
53 | "Content-Type": "application/json",
54 | },
55 | body: JSON.stringify({
56 | reqMethod,
57 | reqPath,
58 | resStatus,
59 | resBody: JSON.parse(JSON.stringify(JSON.parse(resBody), null, 2)),
60 | }),
61 | });
62 | if (response.ok) {
63 | setCreateMockState(() => ({
64 | successMessage: "Create/Update Mock is success",
65 | errorMessage: "",
66 | isLoading: false
67 | }));
68 | setMock(() => defaultMockData);
69 | } else {
70 | setCreateMockState({
71 | successMessage: "",
72 | errorMessage: "Something went Wrong!!",
73 | isLoading: false,
74 | });
75 | }
76 | fetchMocks();
77 | };
78 |
79 | const onMockClick = async (mock) => {
80 | setCreateMockState({
81 | successMessage: "",
82 | errorMessage: "",
83 | isLoading: false,
84 | });
85 | let response = await fetch(`http://localhost:8000/${mock.reqPath}`, {
86 | method: `${mock.reqMethod}`,
87 | headers: {
88 | "Content-Type": "application/json",
89 | },
90 | });
91 | let result = await response.json();
92 | setMock({
93 | reqMethod: mock.reqMethod,
94 | reqPath: mock.reqPath,
95 | resStatus: response.status,
96 | resBody: JSON.stringify(result, null, 2),
97 | });
98 | };
99 |
100 | const onDeleteClick = async (e, { reqMethod, reqPath }) => {
101 | e.stopPropagation();
102 | const response = await fetch("http://localhost:8000/deletemock", {
103 | method: "DELETE",
104 | headers: {
105 | "Content-Type": "application/json",
106 | },
107 | body: JSON.stringify({
108 | reqMethod,
109 | reqPath,
110 | }),
111 | });
112 | if (response.ok) {
113 | fetchMocks();
114 | }
115 | };
116 |
117 | const onButtonClick = () => {
118 | setMock(defaultMockData);
119 | };
120 |
121 | return (
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | Mock Api’s
130 |
131 |
132 |
133 |
134 | {(mocks || []).length <= 0 && (
135 |
136 | No Mocks Present.
137 |
138 | )}
139 | {(mocks || []).map((mock) => {
140 | return (
141 |
onMockClick(mock)}
144 | key={`${mock.reqPath}${mock.resStatus}`}
145 | >
146 |
147 |
148 | {mock.reqMethod}
149 |
150 | {mock.reqPath}
151 |
152 | onDeleteClick(e, mock)}
156 | />
157 |
158 | );
159 | })}
160 |
161 |
162 | CREATE NEW MOCK API
163 |
164 |
165 |
166 |
179 |
180 |
181 | );
182 | };
183 |
184 | export default HomePage;
185 |
--------------------------------------------------------------------------------
/src/pages/ReqForm/ReqForm.css:
--------------------------------------------------------------------------------
1 | .right-wrapper-sheet {
2 | height: 100%;
3 | }
4 |
5 | .sheet-inner {
6 | padding: 15px 10px 10px 15px;
7 | height: 100%;
8 | }
9 |
10 | .sheet-req-wrapper {
11 | height: 15%;
12 | }
13 |
14 | .sheet-request {
15 | display: flex;
16 | justify-content: space-between;
17 | margin-top: 20px;
18 | }
19 |
20 | .sheet-inner-info {
21 | font-style: italic;
22 | font-size: 12px;
23 | margin-left: 20px;
24 | color: yellowgreen;
25 | }
26 |
27 | .sheet-request > div:first-child {
28 | flex-basis: 15%;
29 | }
30 |
31 | .sheet-request > div:nth-child(2) {
32 | flex-basis: 82%;
33 | }
34 |
35 | .sheet-response-code {
36 | height: 7%;
37 | display: flex;
38 | align-items: center;
39 | justify-content: flex-end;
40 | }
41 |
42 | .sheet-response-code input {
43 | width: 100px;
44 | margin-left: 20px;
45 | }
46 |
47 | .sheet-response-body {
48 | height: 70%;
49 | display: flex;
50 | flex-direction: column;
51 | justify-content: space-between;
52 | }
53 |
54 | .response-body-wrapper {
55 | height: 92%;
56 | }
57 |
58 | .response-body {
59 | height: 92%;
60 | position: relative;
61 | }
62 |
63 | .response-boy-full-screen {
64 | position: absolute;
65 | left: 0;
66 | top: 0;
67 | height: 100%;
68 | width: 100%;
69 | transition: all 1s ease;
70 | }
71 |
72 | .go-full-screen-button {
73 | position: absolute;
74 | right: 10px;
75 | top: 5px;
76 | color: red;
77 | height: 40px;
78 | width: 40px;
79 | cursor: pointer;
80 | z-index: 99;
81 | }
82 |
83 | .response-body-tabs {
84 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
85 | margin-bottom: 1px;
86 | background: #4f4f4f;
87 | width: 40%;
88 | height: 8%;
89 | display: flex;
90 | justify-content: space-between;
91 | align-items: center;
92 | color: white;
93 | padding: 2px 5px;
94 | }
95 |
96 | .response-body-tabs > span {
97 | padding: 6px 30px;
98 | border-radius: 15px;
99 | cursor: pointer;
100 | }
101 |
102 | .response-body-tabs > span.active {
103 | background-color: #03dac5;
104 | color: black;
105 | transition: all 0.5s;
106 | }
107 |
108 | .sheet-response-body textarea {
109 | height: 100%;
110 | }
111 |
112 | .sheet-response-body .tree-view-wrapper {
113 | height: 100%;
114 | overflow: auto;
115 | }
116 |
117 | .sheet-response-body .response-boy-full-screen .tree-view-wrapper,
118 | .sheet-response-body .response-boy-full-screen textarea {
119 | height: 100%;
120 | }
121 |
122 | .tree-view-wrapper {
123 | background-color: #4f4f4f;
124 | padding: 2px 2px;
125 | }
126 |
127 | .sheet-error {
128 | color: #a04b56;
129 | margin-top: 10px;
130 | font-weight: 500;
131 | }
132 |
133 | .sheet-success {
134 | color: yellowgreen;
135 | }
136 |
137 | .sheet-button {
138 | display: flex;
139 | justify-content: space-between;
140 | height: 8%;
141 | align-items: center;
142 | }
143 |
--------------------------------------------------------------------------------
/src/pages/ReqForm/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import JSONViewer from "react-json-view";
3 |
4 | import Sheet from "../../components/Sheet";
5 | import Label from "../../components/Label";
6 | import TextBox from "../../components/TextBox";
7 | import TextArea from "../../components/TextArea";
8 | import Button from "../../components/Button";
9 | import { isStringifiedJSON } from "../../helpers";
10 | import ExpandIcon from "../../images/Expand.png";
11 | import MinimizeIcon from "../../images/Minimize.png";
12 |
13 | import "./ReqForm.css";
14 |
15 | const ReqForm = ({
16 | onsubmit,
17 | reqMethodProp,
18 | reqPathProp,
19 | resStatusProp,
20 | resBodyProp,
21 | createMockState,
22 | setCreateMockState
23 | }) => {
24 | const [reqMethod, setReqMethod] = useState(reqMethodProp ?? "");
25 | const [reqPath, setReqPath] = useState(reqPathProp ?? "");
26 | const [resStatus, setResStatus] = useState(resStatusProp ?? "");
27 | const [resBody, setResBody] = useState(resBodyProp ?? JSON.stringify({}));
28 | const [formError, setFormError] = useState("");
29 | const [activeTab, setActiveTab] = useState("textview");
30 | const [goFullScreen, setGoFullScreen] = useState(false);
31 | const isObject = isStringifiedJSON(resBody);
32 |
33 | const onButtonClick = () => {
34 | if (reqMethod && reqPath && resStatus && resBody) {
35 | try {
36 | onsubmit({
37 | reqMethod,
38 | reqPath,
39 | resStatus,
40 | resBody
41 | });
42 | setFormError("");
43 | } catch (err) {
44 | setFormError("* Response Body JSON format not valid");
45 | setCreateMockState({
46 | successMessage: "",
47 | errorMessage: "",
48 | isLoading: false
49 | });
50 | }
51 | } else {
52 | setFormError("* All Fields are mandatory");
53 | }
54 | };
55 |
56 | useEffect(() => {
57 | setReqMethod(reqMethodProp);
58 | setReqPath(reqPathProp);
59 | setResStatus(resStatusProp);
60 | setResBody(resBodyProp);
61 | }, [reqMethodProp, reqPathProp, resStatusProp, resBodyProp]);
62 |
63 | return (
64 |
65 |
66 |
67 |
68 | Request :
69 |
70 | (To acces mock server hit port 8000 Ex:
71 | http://localhost:8000/users)
72 |
73 |
74 |
75 |
76 | {
79 | setReqMethod(e.target.value);
80 | }}
81 | value={reqMethod}
82 | >
83 |
84 |
85 | {
88 | setReqPath(e.target.value);
89 | }}
90 | value={reqPath}
91 | >
92 |
93 |
94 |
95 |
96 |
97 | Response Code :
98 |
99 | {
102 | setResStatus(e.target.value);
103 | }}
104 | value={resStatus}
105 | >
106 |
107 |
108 |
109 | Response Body :
110 |
111 |
112 |
113 | setActiveTab("textview")}
116 | >
117 | Text View
118 |
119 | {isObject && (
120 | setActiveTab("treeview")}
123 | >
124 | Tree View
125 |
126 | )}
127 |
128 |
133 |
setGoFullScreen(!goFullScreen)}
139 | />
140 | {activeTab === "textview" ? (
141 |
148 | ) : (
149 |
150 |
158 |
159 | )}
160 |
161 |
162 |
163 |
164 |
165 | {formError && {formError} }
166 | {createMockState.successMessage && (
167 |
168 | {createMockState.successMessage}
169 |
170 | )}
171 | {createMockState.errorMessage && (
172 | {createMockState.errorMessage}
173 | )}
174 |
175 |
{`CREATE/UPDATE${
176 | createMockState.isLoading ? "...." : ""
177 | }`}
178 |
179 |
180 |
181 | );
182 | };
183 |
184 | export default ReqForm;
185 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------