diff --git a/README.md b/README.md
index 37c5076..9d95e09 100644
--- a/README.md
+++ b/README.md
@@ -51,18 +51,20 @@ PS.: It is important to add the `--template ui5-webcomponents-react-seed` at the
- Multiple Scripts.
-# Engine Included
+# Engine & Samples Included
- Fallback Engine (``, `` and ``);
- MockServer Engine (w/ `json-server`);
-- HTTP Request Engine (w/ `Request`, `APIProvider`, `BrowserProvider`);
+- API HTTP Request Engine (w/ `Request`, `APIProvider`, `BrowserProvider`);
- Permission Engine (w/ `RouteValidator` and `ComponentValidator`).
- Pagination Engine (w/ custom hook `usePaginatedGet`).
+- ToDo Form Edition w/ `yup` and `formik`.
+
# Hooks Included
- `useRequest`: Which includes `get`, `post`, `patch`, `delete`, `put` HTTP helpers;
@@ -75,7 +77,6 @@ Following one of the several recommendations for structuring files on a React ba
The only custom change we have incremented were the Custom Components and the folder for each project containing the `tests` artefacts.
-
# Scripts Included
In the project directory, you can run:
diff --git a/package.json b/package.json
index bdbba37..5f8356a 100644
--- a/package.json
+++ b/package.json
@@ -49,6 +49,7 @@
"env-cmd": "^10.1.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4",
+ "formik": "^2.1.7",
"husky": "^4.2.5",
"i18next": "^19.7.0",
"i18next-browser-languagedetector": "^6.0.1",
@@ -67,7 +68,8 @@
"react-query": "^2.17.2",
"react-query-devtools": "^2.5.0",
"react-router-dom": "^5.2.0",
- "react-scripts": "3.4.3"
+ "react-scripts": "3.4.3",
+ "yup": "^0.29.3"
},
"scripts": {
"build": "react-scripts build",
diff --git a/server/data/Todo/GET_TODO_BY_ID.js b/server/data/Todo/GET_TODO_BY_ID.js
index 81d19b2..c48be5a 100644
--- a/server/data/Todo/GET_TODO_BY_ID.js
+++ b/server/data/Todo/GET_TODO_BY_ID.js
@@ -1,10 +1,14 @@
+const { MemoryRouter } = require('react-router-dom');
+
module.exports = {
data: {
todos: {
id: 'UG9rZW1vbjowMDE=',
- number: '001',
+ description: '001',
name: 'Task 1',
- completed: false,
+ completed: true,
+ priority: 'HIGH',
+ type: 'PERSONAL',
},
},
};
diff --git a/server/data/Todo/GET_TODO_LIST.js b/server/data/Todo/GET_TODO_LIST.js
index af6844e..36ecf73 100644
--- a/server/data/Todo/GET_TODO_LIST.js
+++ b/server/data/Todo/GET_TODO_LIST.js
@@ -2,61 +2,61 @@ module.exports = {
content: [
{
id: 'UG9rZW1vbjowMDE=',
- number: '001',
+ description: '001',
name: 'Task 1',
completed: false,
},
{
id: '2U9frg1Z1W1o1DE=',
- number: '002',
+ description: '002',
name: 'Task 2',
completed: false,
},
{
id: '2Ug11Z1dv1W1og1DE=',
- number: '003',
+ description: '003',
name: 'Task 3',
completed: false,
},
{
id: 'gxfas1cczg1ff1DE=',
- number: '004',
+ description: '004',
name: 'Task 4',
completed: false,
},
{
id: '2Uxfg1ka1Zga1o1DE=',
- number: '005',
+ description: '005',
name: 'Task 5',
completed: false,
},
{
id: '2z11Uxgfg1kaza1DE=',
- number: '006',
+ description: '006',
name: 'Task 9',
completed: false,
},
{
id: '2Uxgfg1ka1Zga11DE=',
- number: '007',
+ description: '007',
name: 'Task 7',
completed: false,
},
{
id: '2Uxfag3ka1Zga1o1DE=',
- number: '008',
+ description: '008',
name: 'Task 8',
completed: false,
},
{
id: '2Uxfg7ka1Zga1o1DE=',
- number: '009',
+ description: '009',
name: 'Task 9',
completed: false,
},
{
id: '2Ugg7ka1Zg31o1DE=',
- number: '010',
+ description: '010',
name: 'Task 10',
completed: false,
},
diff --git a/server/data/Todo/GET_TODO_LIST_PAGE_1.js b/server/data/Todo/GET_TODO_LIST_PAGE_1.js
index b2ac349..eaf3e54 100644
--- a/server/data/Todo/GET_TODO_LIST_PAGE_1.js
+++ b/server/data/Todo/GET_TODO_LIST_PAGE_1.js
@@ -2,7 +2,7 @@ module.exports = {
content: [
{
id: '5Uxfhxka1Zga1o1DE=',
- number: '011',
+ description: '011',
name: 'Task 11',
completed: false,
},
diff --git a/server/routes.json b/server/routes.json
index 6a2a643..d16bb70 100644
--- a/server/routes.json
+++ b/server/routes.json
@@ -1,6 +1,6 @@
{
"*/v1/user/logged": "/GET_USER_LOGGED",
- "*/v1/todo/detail/:id": "/GET_TODO_BY_ID",
"*/v1/todo/all\\?page=0": "/GET_TODO_LIST",
- "*/v1/todo/all\\?page=1": "/GET_TODO_LIST_PAGE_1"
+ "*/v1/todo/all\\?page=1": "/GET_TODO_LIST_PAGE_1",
+ "*/v1/todo/:id": "/GET_TODO_BY_ID"
}
diff --git a/src/App.js b/src/App.js
index 7bd7f50..a8e4774 100644
--- a/src/App.js
+++ b/src/App.js
@@ -9,7 +9,6 @@ import Shell from './components/Shell/Shell';
import Routes from './routes/Routes';
import './App.css';
-import CenteredContent from './components/Layout/CenteredContent';
function App() {
const { t } = useTranslation();
@@ -20,9 +19,7 @@ function App() {
-
-
-
+
);
diff --git a/src/auth/Components/Validator.js b/src/auth/components/Validator.js
similarity index 100%
rename from src/auth/Components/Validator.js
rename to src/auth/components/Validator.js
diff --git a/src/auth/Components/Validator.test.js b/src/auth/components/Validator.test.js
similarity index 86%
rename from src/auth/Components/Validator.test.js
rename to src/auth/components/Validator.test.js
index 4188bac..dda9766 100644
--- a/src/auth/Components/Validator.test.js
+++ b/src/auth/components/Validator.test.js
@@ -3,7 +3,7 @@ import React from 'react';
import '@testing-library/jest-dom/extend-expect';
import { render, waitFor, screen, serverCustom } from '../../util/TestSetup';
import ComponentValidator from './Validator';
-import APIProvider from '../../util/URL/APIProvider';
+import APIProvider from '../../util/api/url/APIProvider';
describe('Validator.js Test Suite', () => {
const GET_USER_LOGGED_RESPONSE = {
@@ -25,7 +25,7 @@ describe('Validator.js Test Suite', () => {
{childText}
,
- { route: '/todo/list' },
+ { route: '/todo/all' },
);
const child = await waitFor(() => screen.getByText(childText));
@@ -39,22 +39,23 @@ describe('Validator.js Test Suite', () => {
{childText}
,
- { route: '/todo/list' },
+ { route: '/todo/all' },
);
const child = await waitFor(() => screen.getByText(childText));
expect(child).toBeInTheDocument();
});
- test('should not appear in the document', async () => {
+ test.only('should not appear in the document', async () => {
const childText = 'inner text';
render(
{childText}
,
- { route: '/todo/list' },
+ { route: '/todo/all' },
);
+ console.log('PATHHHHHH', window.location.pathname);
const child = screen.queryByAltText(childText);
expect(child).not.toBeInTheDocument();
diff --git a/src/auth/Components/__snapshots__/Validator.test.js.snap b/src/auth/components/__snapshots__/Validator.test.js.snap
similarity index 100%
rename from src/auth/Components/__snapshots__/Validator.test.js.snap
rename to src/auth/components/__snapshots__/Validator.test.js.snap
diff --git a/src/auth/Routes/Validator.js b/src/auth/routes/Validator.js
similarity index 100%
rename from src/auth/Routes/Validator.js
rename to src/auth/routes/Validator.js
diff --git a/src/components/Form/FieldBase/FieldBase.js b/src/components/Form/FieldBase/FieldBase.js
new file mode 100644
index 0000000..bcfd4d9
--- /dev/null
+++ b/src/components/Form/FieldBase/FieldBase.js
@@ -0,0 +1,19 @@
+import React from 'react';
+
+import { FlexBox, FlexBoxDirection } from '@ui5/webcomponents-react';
+import Label from '../Label/Label';
+
+const FieldBase = ({ labelText, ...props }) => {
+ return (
+
+ {labelText && (
+
+ )}
+ {props.children}
+
+ );
+};
+
+export default FieldBase;
diff --git a/src/components/Form/FieldBase/FieldBase.test.js b/src/components/Form/FieldBase/FieldBase.test.js
new file mode 100644
index 0000000..2c01525
--- /dev/null
+++ b/src/components/Form/FieldBase/FieldBase.test.js
@@ -0,0 +1,40 @@
+import React from 'react';
+
+import '@testing-library/jest-dom/extend-expect';
+import { render, screen } from '../../../util/TestSetup';
+
+import FieldBase from '../FieldBase/FieldBase';
+
+describe('FieldBase.js Test Suite', () => {
+ beforeEach(() => {
+ render(
+
+ Some Inner Text
+ ,
+ );
+ });
+
+ test('should match snapshot', () => {
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ test('should render', () => {
+ const component = screen.getByTestId('fieldbase-wrapper');
+
+ expect(component).toBeInTheDocument();
+ });
+
+ test('should render with Inner Component with Text Content as text', () => {
+ const component = screen.getByTestId('fieldbase-wrapper');
+ const innerComponent = screen.getByTestId('inner-component-wrapper');
+
+ expect(component).toBeInTheDocument();
+ expect(innerComponent).toHaveTextContent('Some Inner Text');
+ });
+});
diff --git a/src/components/Form/FieldBase/__snapshots__/FieldBase.test.js.snap b/src/components/Form/FieldBase/__snapshots__/FieldBase.test.js.snap
new file mode 100644
index 0000000..ea39cae
--- /dev/null
+++ b/src/components/Form/FieldBase/__snapshots__/FieldBase.test.js.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FieldBase.js Test Suite should match snapshot 1`] = `
+
+
+ Label {
+ "_domRefReadyPromise": Promise {
+ "_deferredResolve": [Function],
+ },
+ "_firePropertyChange": false,
+ "_fullyConnected": false,
+ "_inDOM": false,
+ "_monitoredChildProps": Map {},
+ "_shouldInvalidateParent": false,
+ "_state": Object {
+ "for": "",
+ "required": false,
+ "showColon": false,
+ "wrap": true,
+ },
+ "_upToDate": false,
+ }
+
+
+
+`;
diff --git a/src/components/Form/Input/Input.js b/src/components/Form/Input/Input.js
new file mode 100644
index 0000000..e853d94
--- /dev/null
+++ b/src/components/Form/Input/Input.js
@@ -0,0 +1,24 @@
+import React from 'react';
+
+import { spacing } from '@ui5/webcomponents-react-base';
+import { Input as UI5Input, ValueState } from '@ui5/webcomponents-react';
+import FieldBase from '../FieldBase/FieldBase';
+
+const Input = ({ field, form: { touched, errors }, labelText, style, ...props }) => {
+ const errorMsg = touched[field.name] && errors[field.name];
+ const errorState = errorMsg ? ValueState.Error : ValueState.None;
+
+ const innerStyle = {
+ ...style,
+ ...spacing.sapUiTinyMarginBottom,
+ width: '100%',
+ };
+
+ return (
+
+ {errorMsg}} style={innerStyle} {...props} {...field} />
+
+ );
+};
+
+export default Input;
diff --git a/src/components/Form/Input/Input.test.js b/src/components/Form/Input/Input.test.js
new file mode 100644
index 0000000..93faf57
--- /dev/null
+++ b/src/components/Form/Input/Input.test.js
@@ -0,0 +1,32 @@
+import React from 'react';
+
+import '@testing-library/jest-dom/extend-expect';
+import { render, screen } from '../../../util/TestSetup';
+
+import Input from '../Input/Input';
+
+describe('Input.js Test Suite', () => {
+ test('should match snapshot', () => {
+ const { asFragment } = render();
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ test('should have rendered with the fieldbase', () => {
+ render();
+
+ const fieldbase = screen.getByTestId('fieldbase-wrapper');
+ const component = screen.getByTestId('input-wrapper');
+
+ expect(fieldbase).toBeInTheDocument();
+ expect(component).toBeInTheDocument();
+ });
+
+ test('should have attribute name as description if passed', () => {
+ render();
+
+ const component = screen.getByTestId('input-wrapper');
+
+ expect(component).toHaveProperty('name', 'description');
+ });
+});
diff --git a/src/components/Form/Input/__snapshots__/Input.test.js.snap b/src/components/Form/Input/__snapshots__/Input.test.js.snap
new file mode 100644
index 0000000..5a2c19f
--- /dev/null
+++ b/src/components/Form/Input/__snapshots__/Input.test.js.snap
@@ -0,0 +1,39 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Input.js Test Suite should match snapshot 1`] = `
+
+
+ Label {
+ "_domRefReadyPromise": Promise {
+ "_deferredResolve": [Function],
+ },
+ "_firePropertyChange": false,
+ "_fullyConnected": false,
+ "_inDOM": false,
+ "_monitoredChildProps": Map {},
+ "_shouldInvalidateParent": false,
+ "_state": Object {
+ "for": "",
+ "required": false,
+ "showColon": false,
+ "wrap": true,
+ },
+ "_upToDate": false,
+ }
+
+
+
+
+
+`;
diff --git a/src/components/Form/Label/Label.js b/src/components/Form/Label/Label.js
new file mode 100644
index 0000000..a8aa5e8
--- /dev/null
+++ b/src/components/Form/Label/Label.js
@@ -0,0 +1,20 @@
+import React from 'react';
+
+import { spacing } from '@ui5/webcomponents-react-base';
+import { Label as UI5Label } from '@ui5/webcomponents-react';
+
+const Label = ({ style, ...props }) => {
+ const innerStyle = {
+ ...style,
+ ...spacing.sapUiTinyMarginBottom,
+ lineHeight: '20px',
+ };
+
+ return (
+
+ {props.children}
+
+ );
+};
+
+export default Label;
diff --git a/src/components/Form/Label/Label.test.js b/src/components/Form/Label/Label.test.js
new file mode 100644
index 0000000..64a6b10
--- /dev/null
+++ b/src/components/Form/Label/Label.test.js
@@ -0,0 +1,41 @@
+import React from 'react';
+
+import '@testing-library/jest-dom/extend-expect';
+import { render, screen } from '../../../util/TestSetup';
+
+import Label from '../Label/Label';
+
+describe('Label.js Test Suite', () => {
+ test('should match snapshot', () => {
+ const { asFragment } = render(
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ test('should render children', () => {
+ render(
+ ,
+ );
+
+ const component = screen.getByTestId('label-wrapper');
+ const inner = screen.getByTestId('inner-text-wrapper');
+
+ expect(component).toBeInTheDocument();
+ expect(inner).toBeInTheDocument();
+ });
+
+ test('should render with lineHeight of 20px', () => {
+ render();
+
+ const component = screen.getByTestId('label-wrapper');
+
+ expect(component).toBeInTheDocument();
+ expect(component).toHaveStyle('lineHeight: 20px');
+ });
+});
diff --git a/src/components/Form/Label/__snapshots__/Label.test.js.snap b/src/components/Form/Label/__snapshots__/Label.test.js.snap
new file mode 100644
index 0000000..b7411ce
--- /dev/null
+++ b/src/components/Form/Label/__snapshots__/Label.test.js.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Label.js Test Suite should match snapshot 1`] = `
+
+ Label {
+ "_domRefReadyPromise": Promise {
+ "_deferredResolve": [Function],
+ },
+ "_firePropertyChange": false,
+ "_fullyConnected": false,
+ "_inDOM": false,
+ "_monitoredChildProps": Map {},
+ "_shouldInvalidateParent": false,
+ "_state": Object {
+ "for": "",
+ "required": false,
+ "showColon": false,
+ "wrap": true,
+ },
+ "_upToDate": false,
+ }
+
+`;
diff --git a/src/components/Form/Select/Select.js b/src/components/Form/Select/Select.js
new file mode 100644
index 0000000..c8c5d44
--- /dev/null
+++ b/src/components/Form/Select/Select.js
@@ -0,0 +1,30 @@
+import React from 'react';
+
+import { spacing } from '@ui5/webcomponents-react-base';
+import { Select as UI5Select, Option, ValueState } from '@ui5/webcomponents-react';
+import FieldBase from '../FieldBase/FieldBase';
+
+const Select = ({ field, options, labelText, form: { touched, errors, setFieldValue }, style, ...props }) => {
+ const errorMsg = touched[field.name] && errors[field.name];
+ const errorState = errorMsg ? ValueState.Error : ValueState.None;
+
+ const innerStyle = {
+ ...style,
+ ...spacing.sapUiTinyMarginBottom,
+ width: '100%',
+ };
+
+ return (
+
+ setFieldValue(field.name, e.detail.selectedOption.dataset.id)} {...props}>
+ {options.map((option) => (
+
+ ))}
+
+
+ );
+};
+
+export default Select;
diff --git a/src/components/Form/Select/Select.test.js b/src/components/Form/Select/Select.test.js
new file mode 100644
index 0000000..4b52a41
--- /dev/null
+++ b/src/components/Form/Select/Select.test.js
@@ -0,0 +1,51 @@
+import React from 'react';
+
+import '@testing-library/jest-dom/extend-expect';
+import { render, screen } from '../../../util/TestSetup';
+
+import Select from '../Select/Select';
+
+const descrOptions = [
+ { id: 'LOW', text: 'Low' },
+ { id: 'MEDIUM', text: 'Medium' },
+ { id: 'HIGH', text: 'High' },
+];
+
+describe('Select.js Test Suite', () => {
+ test('should match snapshot', () => {
+ const { asFragment } = render(
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ test('should have rendered with the fieldbase', () => {
+ render();
+
+ const fieldbase = screen.getByTestId('fieldbase-wrapper');
+ const component = screen.getByTestId('select-wrapper');
+
+ expect(fieldbase).toBeInTheDocument();
+ expect(component).toBeInTheDocument();
+ });
+
+ test('should have 3 options with MEDIUM selected', () => {
+ render(
+ ,
+ );
+
+ const component = screen.getByTestId('select-wrapper');
+ const options = screen.getAllByTestId('select-option-wrapper');
+ expect(component.children).toHaveLength(3);
+ expect(options[0].selected).toBeFalsy();
+ expect(options[1].selected).toBeTruthy();
+ expect(options[2].selected).toBeFalsy();
+ });
+});
diff --git a/src/components/Form/Select/__snapshots__/Select.test.js.snap b/src/components/Form/Select/__snapshots__/Select.test.js.snap
new file mode 100644
index 0000000..414dd0c
--- /dev/null
+++ b/src/components/Form/Select/__snapshots__/Select.test.js.snap
@@ -0,0 +1,83 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Select.js Test Suite should match snapshot 1`] = `
+
+
+ Label {
+ "_domRefReadyPromise": Promise {
+ "_deferredResolve": [Function],
+ },
+ "_firePropertyChange": false,
+ "_fullyConnected": false,
+ "_inDOM": false,
+ "_monitoredChildProps": Map {},
+ "_shouldInvalidateParent": false,
+ "_state": Object {
+ "for": "",
+ "required": false,
+ "showColon": false,
+ "wrap": true,
+ },
+ "_upToDate": false,
+ }
+
+ Option {
+ "_domRefReadyPromise": Promise {
+ "_deferredResolve": [Function],
+ },
+ "_firePropertyChange": false,
+ "_fullyConnected": false,
+ "_inDOM": false,
+ "_monitoredChildProps": Map {},
+ "_shouldInvalidateParent": false,
+ "_state": Object {
+ "icon": null,
+ "selected": false,
+ "value": "LOW",
+ },
+ "_upToDate": false,
+ }
+ Option {
+ "_domRefReadyPromise": Promise {
+ "_deferredResolve": [Function],
+ },
+ "_firePropertyChange": false,
+ "_fullyConnected": false,
+ "_inDOM": false,
+ "_monitoredChildProps": Map {},
+ "_shouldInvalidateParent": false,
+ "_state": Object {
+ "icon": null,
+ "selected": false,
+ "value": "MEDIUM",
+ },
+ "_upToDate": false,
+ }
+ Option {
+ "_domRefReadyPromise": Promise {
+ "_deferredResolve": [Function],
+ },
+ "_firePropertyChange": false,
+ "_fullyConnected": false,
+ "_inDOM": false,
+ "_monitoredChildProps": Map {},
+ "_shouldInvalidateParent": false,
+ "_state": Object {
+ "icon": null,
+ "selected": false,
+ "value": "HIGH",
+ },
+ "_upToDate": false,
+ }
+
+
+
+`;
diff --git a/src/components/Form/Switch/Switch.js b/src/components/Form/Switch/Switch.js
new file mode 100644
index 0000000..4f5d434
--- /dev/null
+++ b/src/components/Form/Switch/Switch.js
@@ -0,0 +1,22 @@
+import React from 'react';
+
+import { Switch as UI5Switch } from '@ui5/webcomponents-react';
+import FieldBase from '../FieldBase/FieldBase';
+
+const style = {
+ switch: {
+ width: 'min-content',
+ },
+};
+
+const Switch = ({ field, form: { values, setFieldValue }, labelText, ...props }) => {
+ const value = values[field.name];
+
+ return (
+
+ setFieldValue(field.name, !value)} style={style.switch} {...props} />
+
+ );
+};
+
+export default Switch;
diff --git a/src/components/Form/TextArea/TextArea.js b/src/components/Form/TextArea/TextArea.js
new file mode 100644
index 0000000..ffe0194
--- /dev/null
+++ b/src/components/Form/TextArea/TextArea.js
@@ -0,0 +1,26 @@
+import React from 'react';
+
+import { spacing } from '@ui5/webcomponents-react-base';
+import { TextArea as UI5TextArea, ValueState } from '@ui5/webcomponents-react';
+import FieldBase from '../FieldBase/FieldBase';
+
+const TextArea = ({ field, form: { touched, errors }, labelText, rows, style, ...props }) => {
+ const errorMsg = touched[field.name] && errors[field.name];
+ const errorState = errorMsg ? ValueState.Error : ValueState.None;
+
+ return (
+
+ {errorMsg}}
+ rows={rows}
+ style={style ? style : spacing.sapUiSmallMarginBottom}
+ {...props}
+ {...field}
+ />
+
+ );
+};
+
+export default TextArea;
diff --git a/src/components/Form/TextArea/TextArea.test.js b/src/components/Form/TextArea/TextArea.test.js
new file mode 100644
index 0000000..a0fd1c8
--- /dev/null
+++ b/src/components/Form/TextArea/TextArea.test.js
@@ -0,0 +1,26 @@
+import React from 'react';
+
+import '@testing-library/jest-dom/extend-expect';
+import { render, screen } from '../../../util/TestSetup';
+
+import TextArea from '../TextArea/TextArea';
+
+describe('TextArea.js Test Suite', () => {
+ test('should match snapshot', () => {
+ const { asFragment } = render(
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ test('should have rendered with the fieldbase', () => {
+ render();
+
+ const fieldbase = screen.getByTestId('fieldbase-wrapper');
+ const component = screen.getByTestId('textArea-wrapper');
+
+ expect(fieldbase).toBeInTheDocument();
+ expect(component).toBeInTheDocument();
+ });
+});
diff --git a/src/components/Form/TextArea/__snapshots__/TextArea.test.js.snap b/src/components/Form/TextArea/__snapshots__/TextArea.test.js.snap
new file mode 100644
index 0000000..ed0bb74
--- /dev/null
+++ b/src/components/Form/TextArea/__snapshots__/TextArea.test.js.snap
@@ -0,0 +1,40 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TextArea.js Test Suite should match snapshot 1`] = `
+
+
+ Label {
+ "_domRefReadyPromise": Promise {
+ "_deferredResolve": [Function],
+ },
+ "_firePropertyChange": false,
+ "_fullyConnected": false,
+ "_inDOM": false,
+ "_monitoredChildProps": Map {},
+ "_shouldInvalidateParent": false,
+ "_state": Object {
+ "for": "",
+ "required": false,
+ "showColon": false,
+ "wrap": true,
+ },
+ "_upToDate": false,
+ }
+
+
+
+
+
+`;
diff --git a/src/components/Layout/CenteredContent.js b/src/components/Layout/CenteredContent.js
index 01d7ced..86fb49b 100644
--- a/src/components/Layout/CenteredContent.js
+++ b/src/components/Layout/CenteredContent.js
@@ -1,17 +1,18 @@
import React from 'react';
+import { isMobile } from 'react-device-detect';
const style = {
centered: {
- maxWidth: '970px',
+ minWidth: isMobile ? '80%' : '50%',
padding: '1rem',
margin: '0 auto',
},
};
-export default function CenteredContent({ children }) {
+export default function CenteredContent({ ...props }) {
return (
- {children}
+ {props.children}
);
}
diff --git a/src/components/Layout/CenteredContent.test.js b/src/components/Layout/CenteredContent.test.js
index 364c09e..460aec4 100644
--- a/src/components/Layout/CenteredContent.test.js
+++ b/src/components/Layout/CenteredContent.test.js
@@ -22,10 +22,10 @@ describe('Validator.js Test Suite', () => {
expect(content).toBeInTheDocument();
});
- test('should have a g of 970px', () => {
+ test('should have a minWidth of 50% when desktop', () => {
const content = screen.getByTestId('centered-content');
expect(content).toBeInTheDocument();
- expect(content).toHaveStyle(`max-width:970px`);
+ expect(content).toHaveStyle('min-width:50%');
});
});
diff --git a/src/components/Layout/__snapshots__/CenteredContent.test.js.snap b/src/components/Layout/__snapshots__/CenteredContent.test.js.snap
index bd7e7e1..d65ff11 100644
--- a/src/components/Layout/__snapshots__/CenteredContent.test.js.snap
+++ b/src/components/Layout/__snapshots__/CenteredContent.test.js.snap
@@ -4,7 +4,7 @@ exports[`Validator.js Test Suite should match snapshot 1`] = `
`;
diff --git a/src/components/NavBack/NavBack.js b/src/components/NavBack/NavBack.js
new file mode 100644
index 0000000..4ecfe1f
--- /dev/null
+++ b/src/components/NavBack/NavBack.js
@@ -0,0 +1,23 @@
+import React from 'react';
+
+import { Button, ButtonDesign, FlexBox } from '@ui5/webcomponents-react';
+import { useHistory } from 'react-router-dom';
+import { spacing } from '@ui5/webcomponents-react-base';
+
+import i18n from '../../util/i18n';
+
+export default function NavBack({ text = i18n.t('components.navback.text'), icon = 'nav-back', design = ButtonDesign.Transparent, disabled = false }) {
+ const history = useHistory();
+
+ return (
+
+
+
+ );
+}
+
+export const NavBackIcon = {
+ NONE: '',
+};
diff --git a/src/components/NavBack/NavBack.test.js b/src/components/NavBack/NavBack.test.js
new file mode 100644
index 0000000..f264093
--- /dev/null
+++ b/src/components/NavBack/NavBack.test.js
@@ -0,0 +1,48 @@
+import React from 'react';
+
+import '@testing-library/jest-dom/extend-expect';
+import { fireEvent, render, screen } from '../../util/TestSetup';
+
+import NavBack from './NavBack';
+
+describe('NavBack.js Test Suite', () => {
+ beforeEach(() => {
+ render(, { route: 'TODO_EDIT' });
+ });
+
+ test('should match snapshot', () => {
+ const navback = screen.getByTestId('navback-wrapper');
+
+ expect(navback).toMatchSnapshot();
+ });
+
+ test('should contains Back text if nothing is passed', () => {
+ const navback = screen.getByTestId('navback-wrapper');
+
+ expect(navback).toHaveTextContent('Back');
+ });
+
+ test('should contains transparent button', () => {
+ const navback = screen.getByTestId('navback-wrapper');
+
+ expect(navback).toHaveProperty('design', 'Transparent');
+ });
+
+ test('should redirect to home if pressed', async () => {
+ const navback = screen.getByTestId('navback-wrapper');
+
+ await fireEvent.click(navback);
+
+ const currentURL = window.location.pathname;
+
+ expect(currentURL).toEqual('/');
+ });
+
+ test('should contains Nav Back text if is passed', () => {
+ render();
+
+ const navback = screen.getAllByTestId('navback-wrapper')[1];
+
+ expect(navback).toHaveTextContent('Nav Back');
+ });
+});
diff --git a/src/components/NavBack/__snapshots__/NavBack.test.js.snap b/src/components/NavBack/__snapshots__/NavBack.test.js.snap
new file mode 100644
index 0000000..541c02e
--- /dev/null
+++ b/src/components/NavBack/__snapshots__/NavBack.test.js.snap
@@ -0,0 +1,11 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`NavBack.js Test Suite should match snapshot 1`] = `
+
+ Back
+
+`;
diff --git a/src/components/Pagination/Pagination.js b/src/components/Pagination/Pagination.js
index 43bfe24..18532d9 100644
--- a/src/components/Pagination/Pagination.js
+++ b/src/components/Pagination/Pagination.js
@@ -1,4 +1,5 @@
import React from 'react';
+
import PropTypes from 'prop-types';
import { FlexBox } from '@ui5/webcomponents-react/lib/FlexBox';
import { FlexBoxAlignItems } from '@ui5/webcomponents-react/lib/FlexBoxAlignItems';
diff --git a/src/components/Popover/Info/PopoverInfo.js b/src/components/Popover/Info/PopoverInfo.js
index d15a627..bc65810 100644
--- a/src/components/Popover/Info/PopoverInfo.js
+++ b/src/components/Popover/Info/PopoverInfo.js
@@ -6,7 +6,7 @@ import { FlexBoxDirection, Title } from '@ui5/webcomponents-react';
import { FlexBox } from '@ui5/webcomponents-react/lib/FlexBox';
import { spacing } from '@ui5/webcomponents-react-base';
-export default function PopoverInfo({ popoverRef, placementType, title, children }) {
+export default function PopoverInfo({ popoverRef, placementType, title, ...props }) {
return (
@@ -17,7 +17,7 @@ export default function PopoverInfo({ popoverRef, placementType, title, children
)}
- {children}
+ {props.children}
);
diff --git a/src/components/Shell/Shell.js b/src/components/Shell/Shell.js
index 1fb7248..20f8fc1 100644
--- a/src/components/Shell/Shell.js
+++ b/src/components/Shell/Shell.js
@@ -6,7 +6,7 @@ import { ShellBar } from '@ui5/webcomponents-react/lib/ShellBar';
import { Avatar } from '@ui5/webcomponents-react/lib/Avatar';
import { AvatarShape } from '@ui5/webcomponents-react/lib/AvatarShape';
import { AvatarSize } from '@ui5/webcomponents-react/lib/AvatarSize';
-import BrowserProvider from '../../util/URL/BrowserProvider';
+import BrowserProvider from '../../util/browser/BrowserProvider';
import PopoverListItems from '../Popover/List/PopoverListItems';
const style = {
@@ -44,7 +44,7 @@ const Shell = ({ title, ...props }) => {
primaryTitle={title}
style={style.shell}
logo={}
- onLogoClick={() => history.push(BrowserProvider.HOME)}
+ onLogoClick={() => history.push(BrowserProvider.getUrl('HOME'))}
profile={}
onProfileClick={(e) => popoverConfigItemsRef.current.openBy(e.detail.targetRef)}
{...props}
diff --git a/src/hooks/useAuthority.js b/src/hooks/useAuthority.js
index 7f09124..7db44fc 100644
--- a/src/hooks/useAuthority.js
+++ b/src/hooks/useAuthority.js
@@ -1,8 +1,9 @@
import { useGet } from './useRequest';
import Constants from '../util/Constants';
+import APIProvider from '../util/api/url/APIProvider';
export function useHasAccess(allowedAuthorities, authorityKey) {
- const { data, status } = useGet(Constants.REACT_QUERY.KEYS.RQ_GET_USER_LOGGED, 'GET_USER_LOGGED', null);
+ const { data, status } = useGet(Constants.REACT_QUERY.KEYS.RQ_GET_USER_LOGGED, APIProvider.getUrl('GET_USER_LOGGED'), null);
if (status !== Constants.REACT_QUERY.CODES.SUCCESS) {
return null;
@@ -12,7 +13,7 @@ export function useHasAccess(allowedAuthorities, authorityKey) {
}
export function useHasPendableAccess(allowedAuthorities, authorityKey) {
- const { data, status } = useGet(Constants.REACT_QUERY.KEYS.RQ_GET_USER_LOGGED, 'GET_USER_LOGGED', null);
+ const { data, status } = useGet(Constants.REACT_QUERY.KEYS.RQ_GET_USER_LOGGED, APIProvider.getUrl('GET_USER_LOGGED'), null);
if (status === Constants.REACT_QUERY.CODES.LOADING) {
return status;
diff --git a/src/hooks/useRequest.js b/src/hooks/useRequest.js
index 822b996..9770ccb 100644
--- a/src/hooks/useRequest.js
+++ b/src/hooks/useRequest.js
@@ -1,7 +1,6 @@
import { usePaginatedQuery, useQuery } from 'react-query';
-import APIProvider from '../util/URL/APIProvider';
-import Request from '../util/Request/Request';
+import Request from '../util/api/engine/Request';
const FIVE_MINUTES_IN_MILLISECONDS = 1000 * 60 * 5;
@@ -17,50 +16,49 @@ const REQUEST = {
DELETE: 'delete',
};
-const _fetchData = (operation, urlKey, dataParam, config) => {
+const _fetchData = (operation, url, dataParam, config) => {
return async () => {
- const url = APIProvider.getUrl(urlKey);
const res = await Request[operation](url, dataParam, config);
return res.data;
};
};
-const _useOperation = (reactQueryKey, operation, urlKey, dataParam, config) => {
- const { data, status } = useQuery(reactQueryKey, _fetchData(operation, urlKey, dataParam, config), STALE_TIME);
+const _useOperation = (reactQueryKey, operation, url, dataParam, config) => {
+ const { data, status } = useQuery(reactQueryKey, _fetchData(operation, url, dataParam, config), STALE_TIME);
return { data, status };
};
-const _usePaginatedOperation = (reactQueryKey, pageDependency, operation, urlKey, dataParam, config) => {
+const _usePaginatedOperation = (reactQueryKey, pageDependency, operation, url, dataParam, config) => {
const parameters = {
params: {
...dataParam,
page: pageDependency,
},
};
- const { resolvedData, latestData, status } = usePaginatedQuery([reactQueryKey, pageDependency], _fetchData(operation, urlKey, parameters, config), STALE_TIME);
+ const { resolvedData, latestData, status } = usePaginatedQuery([reactQueryKey, pageDependency], _fetchData(operation, url, parameters, config), STALE_TIME);
return { resolvedData, latestData, status };
};
-export function useGet(reactQueryKey, urlKey, config) {
- return _useOperation(reactQueryKey, REQUEST.GET, urlKey, null, config ? config : null);
+export function useGet(reactQueryKey, url, config) {
+ return _useOperation(reactQueryKey, REQUEST.GET, url, null, config ? config : null);
}
-export function usePaginatedGet(reactQueryKey, pageDependency, urlKey, config) {
- return _usePaginatedOperation(reactQueryKey, pageDependency, REQUEST.GET, urlKey, null, config ? config : null);
+export function usePaginatedGet(reactQueryKey, pageDependency, url, config) {
+ return _usePaginatedOperation(reactQueryKey, pageDependency, REQUEST.GET, url, null, config ? config : null);
}
-export function usePost(reactQueryKey, urlKey, dataParam, config) {
- return _useOperation(reactQueryKey, REQUEST.POST, urlKey, dataParam ? dataParam : null, config ? config : null);
+export function usePost(reactQueryKey, url, dataParam, config) {
+ return _useOperation(reactQueryKey, REQUEST.POST, url, dataParam ? dataParam : null, config ? config : null);
}
-export function usePut(reactQueryKey, urlKey, dataParam, config) {
- return _useOperation(reactQueryKey, REQUEST.PUT, urlKey, dataParam ? dataParam : null, config ? config : null);
+export function usePut(reactQueryKey, url, dataParam, config) {
+ return _useOperation(reactQueryKey, REQUEST.PUT, url, dataParam ? dataParam : null, config ? config : null);
}
-export function usePatch(reactQueryKey, urlKey, dataParam, config) {
- return _useOperation(reactQueryKey, REQUEST.PATCH, urlKey, dataParam ? dataParam : null, config ? config : null);
+export function usePatch(reactQueryKey, url, dataParam, config) {
+ return _useOperation(reactQueryKey, REQUEST.PATCH, url, dataParam ? dataParam : null, config ? config : null);
}
-export function useDelete(reactQueryKey, urlKey, config) {
- return _useOperation(reactQueryKey, REQUEST.DELETE, urlKey, null, config ? config : null);
+export function useDelete(reactQueryKey, url, config) {
+ return _useOperation(reactQueryKey, REQUEST.DELETE, url, null, config ? config : null);
}
diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json
index ca6181e..56f6e11 100644
--- a/src/locales/en/translation.json
+++ b/src/locales/en/translation.json
@@ -11,5 +11,6 @@
"page.error.alt": "Error",
"page.notfound.text": "Hmmm, we could find this URL",
"page.notfound.alt": "Not Found",
- "page.fallback.reload.text": "Reload this page"
+ "page.fallback.reload.text": "Reload this page",
+ "components.navback.text": "Back"
}
diff --git a/src/pages/Fallback/Fallback.js b/src/pages/Fallback/Fallback.js
index 3fe7f0f..5e7c900 100644
--- a/src/pages/Fallback/Fallback.js
+++ b/src/pages/Fallback/Fallback.js
@@ -6,7 +6,7 @@ import { FlexBoxAlignItems } from '@ui5/webcomponents-react/lib/FlexBoxAlignItem
import { FlexBoxDirection } from '@ui5/webcomponents-react/lib/FlexBoxDirection';
import { FlexBoxJustifyContent } from '@ui5/webcomponents-react/lib/FlexBoxJustifyContent';
-import BrowserProvider from '../../util/URL/BrowserProvider';
+import BrowserProvider from '../../util/browser/BrowserProvider';
const style = {
wrapper: {
@@ -29,7 +29,7 @@ const Fallback = ({ image, altImage, text, reload }) => {
{text}
{reload && (
-
+
{t('page.fallback.reload.text')}
)}
diff --git a/src/pages/Todo/Edit/TodoEdit.js b/src/pages/Todo/Edit/TodoEdit.js
index 4f34b03..3714faa 100644
--- a/src/pages/Todo/Edit/TodoEdit.js
+++ b/src/pages/Todo/Edit/TodoEdit.js
@@ -1,5 +1,32 @@
import React from 'react';
+import { Helmet } from 'react-helmet';
-export default function TodoEdit() {
- return ;
+import { useGet } from '../../../hooks/useRequest';
+import { Spinner } from '@ui5/webcomponents-react';
+import NavBack from '../../../components/NavBack/NavBack';
+import CenteredContent from '../../../components/Layout/CenteredContent';
+import TodoEditForm from './TodoEditForm';
+import Constants from '../../../util/Constants';
+import APIProvider from '../../../util/api/url/APIProvider';
+
+const onSubmitEditForm = (values, actions) => {
+ actions.setSubmitting(true);
+ alert(JSON.stringify(values, null, 2));
+ actions.resetForm(true);
+ actions.setSubmitting(false);
+};
+
+export default function TodoEdit({ match }) {
+ const { data, status } = useGet(Constants.REACT_QUERY.KEYS.GET_TODO_BY_ID, APIProvider.getUrl('GET_TODO_BY_ID', [{ value: match.params.id }]));
+
+ return (
+ <>
+
+
+
+ {status === Constants.REACT_QUERY.CODES.LOADING && }
+ {status === Constants.REACT_QUERY.CODES.SUCCESS && }
+
+ >
+ );
}
diff --git a/src/pages/Todo/Edit/TodoEditForm.js b/src/pages/Todo/Edit/TodoEditForm.js
new file mode 100644
index 0000000..8f1f389
--- /dev/null
+++ b/src/pages/Todo/Edit/TodoEditForm.js
@@ -0,0 +1,61 @@
+import React from 'react';
+
+import { Field, Form, Formik } from 'formik';
+import { Button, ButtonDesign, FlexBox, FlexBoxAlignItems, FlexBoxDirection, InputType } from '@ui5/webcomponents-react';
+import Input from '../../../components/Form/Input/Input';
+import Switch from '../../../components/Form/Switch/Switch';
+import TextArea from '../../../components/Form/TextArea/TextArea';
+import Select from '../../../components/Form/Select/Select';
+import TodoEditFormValidationSchema from './TodoEditFormValidationSchema';
+import NavBack, { NavBackIcon } from '../../../components/NavBack/NavBack';
+
+const style = {
+ putWrapperUp: {
+ marginTop: '-50px',
+ },
+};
+
+const typeOptions = [
+ { id: 'WORK', text: 'Work' },
+ { id: 'PERSONAL', text: 'Personal' },
+ { id: 'SCHOOL', text: 'School' },
+];
+
+const priorityOptions = [
+ { id: 'LOW', text: 'Low' },
+ { id: 'MEDIUM', text: 'Medium' },
+ { id: 'HIGH', text: 'High' },
+];
+
+export default function TodoEditForm({ data, onSubmitHandler }) {
+ return (
+
+
Todo Edit Form
+
+ {({ isSubmitting, handleSubmit }) => (
+
+ )}
+
+
+ );
+}
diff --git a/src/pages/Todo/Edit/TodoEditFormValidationSchema.js b/src/pages/Todo/Edit/TodoEditFormValidationSchema.js
new file mode 100644
index 0000000..0e6c212
--- /dev/null
+++ b/src/pages/Todo/Edit/TodoEditFormValidationSchema.js
@@ -0,0 +1,9 @@
+import * as yup from 'yup';
+
+const TodoEditFormValidationSchema = yup.object({
+ name: yup.string().required().max(25, 'This field must have maximum 25 characters'),
+ description: yup.string().required().max(255, 'This field must have maximum 255 characters'),
+ completed: yup.boolean(),
+});
+
+export default TodoEditFormValidationSchema;
diff --git a/src/pages/Todo/Edit/TodoForm.js b/src/pages/Todo/Edit/TodoForm.js
deleted file mode 100644
index e726b3a..0000000
--- a/src/pages/Todo/Edit/TodoForm.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from 'react';
-
-export default function TodoForm() {
- return ;
-}
diff --git a/src/pages/Todo/List/TodoList.js b/src/pages/Todo/List/TodoList.js
index 06e7167..3a44084 100644
--- a/src/pages/Todo/List/TodoList.js
+++ b/src/pages/Todo/List/TodoList.js
@@ -4,20 +4,21 @@ import { useHistory } from 'react-router-dom';
import { MobileView, BrowserView, IEView, isMobile, isTablet, isDesktop, isIE, isChrome, isOpera } from 'react-device-detect';
import HyperLink from '../../../components/HyperLink/HyperLink';
-import BrowserProvider from '../../../util/URL/BrowserProvider';
-import ComponentValidator from '../../../auth/Components/Validator';
-import TodoListPagination from './TodoListPagination';
+import BrowserProvider from '../../../util/browser/BrowserProvider';
+import ComponentValidator from '../../../auth/components/Validator';
+import TodoListPaginatedItems from './TodoListPaginatedItems';
+import CenteredContent from '../../../components/Layout/CenteredContent';
export default function TodoList() {
const history = useHistory();
return (
- <>
+
Routing
history.push('/dontexist')} text="Test NotFound Page" />
- history.push(BrowserProvider.BUGGY)} text="Test Error Page" />
+ history.push(BrowserProvider.getUrl('BUGGY'))} text="Test Error Page" />
@@ -44,8 +45,8 @@ export default function TodoList() {
{isIE ? 'This Text is rendered only for IE' : 'This Text is rendered only when is NOT IE'}
{isOpera ? 'This Text is rendered only for OPERA' : 'This Text is rendered only when is NOT OPERA'}
- Pagination
-
- >
+ Pagination + Edition (Formik and Yup)
+
+
);
}
diff --git a/src/pages/Todo/List/TodoListPagination.js b/src/pages/Todo/List/TodoListPaginatedItems.js
similarity index 60%
rename from src/pages/Todo/List/TodoListPagination.js
rename to src/pages/Todo/List/TodoListPaginatedItems.js
index 5ee237f..677f78d 100644
--- a/src/pages/Todo/List/TodoListPagination.js
+++ b/src/pages/Todo/List/TodoListPaginatedItems.js
@@ -1,15 +1,24 @@
import React, { useState } from 'react';
+import { useHistory } from 'react-router-dom';
import { usePaginatedGet } from '../../../hooks/useRequest';
import { List } from '@ui5/webcomponents-react/lib/List';
import { StandardListItem } from '@ui5/webcomponents-react/lib/StandardListItem';
import { Spinner } from '@ui5/webcomponents-react';
import { Pagination } from '../../../components/Pagination/Pagination';
+
import Constants from '../../../util/Constants';
+import BrowserProvider from '../../../util/browser/BrowserProvider';
+import APIProvider from '../../../util/api/url/APIProvider';
-export default function TodoListPagination() {
+export default function TodoListPaginatedItems() {
+ const history = useHistory();
const [page, setPage] = useState(0);
- const { resolvedData, status } = usePaginatedGet(Constants.REACT_QUERY.KEYS.RQ_GET_TODO_LIST, page, 'GET_TODO_LIST');
+ const { resolvedData, status } = usePaginatedGet(Constants.REACT_QUERY.KEYS.RQ_GET_TODO_LIST, page, APIProvider.getUrl('GET_TODO_LIST'));
+
+ const redirectToEditPage = (e) => {
+ history.push(BrowserProvider.getUrl('TODO_EDIT', [{ value: e.detail.item.dataset.id }]));
+ };
return (
@@ -18,23 +27,9 @@ export default function TodoListPagination() {
) : (
<>
{`Records (${resolvedData.numberOfElements} / ${resolvedData.totalElements})`}
-
+ redirectToEditPage(e)}>
{resolvedData.content.map((todo, index) => (
-
+
{todo.name}
))}
diff --git a/src/routes/Routes.js b/src/routes/Routes.js
index ae33441..f1c1e4f 100644
--- a/src/routes/Routes.js
+++ b/src/routes/Routes.js
@@ -1,20 +1,22 @@
import React from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
-import BrowserProvider from '../util/URL/BrowserProvider';
+import BrowserProvider from '../util/browser/BrowserProvider';
+import RouteValidator from '../auth/routes/Validator';
import TodoList from '../pages/Todo/List/TodoList';
+import TodoEdit from '../pages/Todo/Edit/TodoEdit';
import NotFound from '../pages/Fallback/NotFound';
import Buggy from '../pages/Fallback/Buggy';
-import RouteValidator from '../auth/Routes/Validator';
const Routes = () => {
return (
-
-
-
-
-
+
+
+
+
+
+
);
};
diff --git a/src/util/Constants.js b/src/util/Constants.js
index 7c68bbe..bee8ba3 100644
--- a/src/util/Constants.js
+++ b/src/util/Constants.js
@@ -8,6 +8,7 @@ export default {
KEYS: {
RQ_GET_USER_LOGGED: 'RQ_GET_USER_LOGGED',
RQ_GET_TODO_LIST: 'RQ_GET_TODO_LIST',
+ GET_TODO_BY_ID: 'GET_TODO_BY_ID',
},
},
};
diff --git a/src/util/TestSetup.js b/src/util/TestSetup.js
index 667ad42..ec1d312 100644
--- a/src/util/TestSetup.js
+++ b/src/util/TestSetup.js
@@ -7,14 +7,14 @@ import { createMemoryHistory } from 'history';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
-import BrowserProvider from './URL/BrowserProvider';
+import BrowserProvider from './browser/BrowserProvider';
-const render = (ui, { route = BrowserProvider.HOME, ...renderOptions } = {}) => {
- const WrapperProvider = ({ children }) => {
+const render = (ui, { route = BrowserProvider.getUrl('HOME'), ...renderOptions } = {}) => {
+ const WrapperProvider = ({ ...props }) => {
const history = createMemoryHistory({ initialEntries: [route] });
return (
- {children}
+ {props.children}
);
};
diff --git a/src/util/URL/APIProvider.js b/src/util/URL/APIProvider.js
deleted file mode 100644
index 86a6766..0000000
--- a/src/util/URL/APIProvider.js
+++ /dev/null
@@ -1,21 +0,0 @@
-const URLs = {
- GET_USER_LOGGED: '/v1/user/logged',
- GET_TODO_BY_ID: '/v1/todo/detail/:id',
- GET_TODO_LIST: '/v1/todo/all',
-};
-
-export default {
- getUrl(key) {
- const url = URLs[key];
-
- if (!url) {
- throw new Error('Url defined by Constant: ' + key + ' not found in APIProvider.js');
- }
-
- return url;
- },
-
- replace(url, replaceValue, searchValue = ':id') {
- return url.replace(searchValue, replaceValue);
- },
-};
diff --git a/src/util/URL/BrowserProvider.js b/src/util/URL/BrowserProvider.js
deleted file mode 100644
index fe1dcc6..0000000
--- a/src/util/URL/BrowserProvider.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export default {
- HOME: '/',
- TODO_LIST: '/todo/list',
- TODO_DETAIL: '/todo/:id/detail',
- NOT_FOUND: '/notFound',
- BUGGY: '/buggy',
- ANY: '/*',
-};
diff --git a/src/util/URLProvider.js b/src/util/URLProvider.js
new file mode 100644
index 0000000..d695332
--- /dev/null
+++ b/src/util/URLProvider.js
@@ -0,0 +1,18 @@
+export default {
+ getUrl(urls, key, replaceOptions) {
+ let url = urls[key];
+
+ if (!url) {
+ throw new Error('Url defined: ' + key + ' not found');
+ }
+
+ if (replaceOptions) {
+ replaceOptions.forEach((replaceOption) => {
+ if (!replaceOption.search) replaceOption.search = ':id';
+ url = url.replace(replaceOption.search, replaceOption.value);
+ });
+ }
+
+ return url;
+ },
+};
diff --git a/src/util/Request/Request.js b/src/util/api/engine/Request.js
similarity index 100%
rename from src/util/Request/Request.js
rename to src/util/api/engine/Request.js
diff --git a/src/util/Request/Request.test.js b/src/util/api/engine/Request.test.js
similarity index 93%
rename from src/util/Request/Request.test.js
rename to src/util/api/engine/Request.test.js
index 1946dea..f6b98d6 100644
--- a/src/util/Request/Request.test.js
+++ b/src/util/api/engine/Request.test.js
@@ -1,7 +1,7 @@
import '@testing-library/jest-dom/extend-expect';
-import { serverCustom } from '../../util/TestSetup';
-import APIProvider from '../../util/URL/APIProvider';
+import { serverCustom } from '../../TestSetup';
+import APIProvider from '../url/APIProvider';
import Request from './Request';
jest.mock('./Request');
diff --git a/src/util/api/url/APIProvider.js b/src/util/api/url/APIProvider.js
new file mode 100644
index 0000000..fa6abf9
--- /dev/null
+++ b/src/util/api/url/APIProvider.js
@@ -0,0 +1,13 @@
+import URLProvider from '../../URLProvider';
+
+const URLs = {
+ GET_USER_LOGGED: '/v1/user/logged',
+ GET_TODO_BY_ID: '/v1/todo/:id',
+ GET_TODO_LIST: '/v1/todo/all',
+};
+
+export default {
+ getUrl(key, replaceOptions) {
+ return URLProvider.getUrl(URLs, key, replaceOptions);
+ },
+};
diff --git a/src/util/URL/APIProvider.test.js b/src/util/api/url/APIProvider.test.js
similarity index 68%
rename from src/util/URL/APIProvider.test.js
rename to src/util/api/url/APIProvider.test.js
index ea92cef..873b614 100644
--- a/src/util/URL/APIProvider.test.js
+++ b/src/util/api/url/APIProvider.test.js
@@ -13,7 +13,7 @@ describe('APIProvider.js Test Suite', () => {
test('should throw error when passed wrong key', () => {
const key = 'DOES_NOT_EXIST';
- const errorMessage = 'Url defined by Constant: ' + key + ' not found in APIProvider.js';
+ const errorMessage = 'Url defined: ' + key + ' not found';
const getUrl = () => APIProvider.getUrl(key);
@@ -23,16 +23,16 @@ describe('APIProvider.js Test Suite', () => {
test('should correctly replace key when passed correct replaceValue', () => {
const key = 'GET_TODO_BY_ID';
- const output = APIProvider.replace(APIProvider.getUrl(key), 1);
+ const output = APIProvider.getUrl(key, [{ value: 1 }]);
- expect(output).toEqual('/v1/todo/detail/1');
+ expect(output).toEqual('/v1/todo/1');
});
test('should erroneously replace key when passed correct replaceValue', () => {
const key = 'GET_TODO_BY_ID';
- const replaceUrl = () => APIProvider.replace(APIProvider.getUrl(key), 1, ':not_id');
+ const replaceUrl = () => APIProvider.getUrl(key, [{ value: 1, search: ':not_id' }]);
- expect(replaceUrl()).toEqual('/v1/todo/detail/:id');
+ expect(replaceUrl()).toEqual('/v1/todo/:id');
});
});
diff --git a/src/util/browser/BrowserProvider.js b/src/util/browser/BrowserProvider.js
new file mode 100644
index 0000000..fe75b63
--- /dev/null
+++ b/src/util/browser/BrowserProvider.js
@@ -0,0 +1,17 @@
+import URLProvider from '../URLProvider';
+
+const URLs = {
+ HOME: '/',
+ TODO_DETAIL: '/todo/detail/:id',
+ TODO_EDIT: '/todo/edit/:id',
+ TODO_LIST: '/todo/all',
+ NOT_FOUND: '/notFound',
+ BUGGY: '/buggy',
+ ANY: '/*',
+};
+
+export default {
+ getUrl(key, replaceOptions) {
+ return URLProvider.getUrl(URLs, key, replaceOptions);
+ },
+};
diff --git a/src/util/browser/BrowserProvider.test.js b/src/util/browser/BrowserProvider.test.js
new file mode 100644
index 0000000..6c4f0a3
--- /dev/null
+++ b/src/util/browser/BrowserProvider.test.js
@@ -0,0 +1,38 @@
+import '@testing-library/jest-dom/extend-expect';
+
+import BrowserProvider from './BrowserProvider';
+
+describe('BrowserProvider.js Test Suite', () => {
+ test('should return URL when passed correct key', () => {
+ const key = 'HOME';
+
+ const output = BrowserProvider.getUrl(key);
+
+ expect(output).toEqual('/');
+ });
+
+ test('should throw error when passed wrong key', () => {
+ const key = 'DOES_NOT_EXIST';
+ const errorMessage = 'Url defined: ' + key + ' not found';
+
+ const getUrl = () => BrowserProvider.getUrl(key);
+
+ expect(getUrl).toThrowError(errorMessage);
+ });
+
+ test('should correctly replace key when passed correct replaceValue', () => {
+ const key = 'TODO_DETAIL';
+
+ const output = BrowserProvider.getUrl(key, [{ value: 1 }]);
+
+ expect(output).toEqual('/todo/detail/1');
+ });
+
+ test('should erroneously replace key when passed correct replaceValue', () => {
+ const key = 'TODO_DETAIL';
+
+ const replaceUrl = () => BrowserProvider.getUrl(key, [{ value: 1, search: ':not_id' }]);
+
+ expect(replaceUrl()).toEqual('/todo/detail/:id');
+ });
+});
diff --git a/template.json b/template.json
index ff0e795..ef2113e 100644
--- a/template.json
+++ b/template.json
@@ -34,7 +34,9 @@
"react-scripts": "3.4.3",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4",
- "prettier": "^2.1.1"
+ "prettier": "^2.1.1",
+ "formik": "^2.1.7",
+ "yup": "^0.29.3"
},
"scripts": {
"build": "react-scripts build",
diff --git a/yarn.lock b/yarn.lock
index 1e6a5e1..b6816db 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4229,6 +4229,11 @@ deep-is@~0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
+deepmerge@^2.1.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
+ integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
+
default-gateway@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
@@ -5368,6 +5373,11 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
+fn-name@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c"
+ integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA==
+
follow-redirects@^1.0.0, follow-redirects@^1.10.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
@@ -5418,6 +5428,20 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
+formik@^2.1.7:
+ version "2.1.7"
+ resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.7.tgz#40bd04e59b242176d0a17c701830f1536cd7506b"
+ integrity sha512-n1wviIh0JsvHqj9PufNvOV+fS7mFwh9FfMxxTMnTrKR/uVYMS06DKaivXBlJdDF0qEwTcPHxSmIQ3deFHL3Hsg==
+ dependencies:
+ deepmerge "^2.1.1"
+ hoist-non-react-statics "^3.3.0"
+ lodash "^4.17.14"
+ lodash-es "^4.17.14"
+ react-fast-compare "^2.0.1"
+ scheduler "^0.18.0"
+ tiny-warning "^1.0.2"
+ tslib "^1.10.0"
+
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -7782,6 +7806,11 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
+lodash-es@^4.17.11, lodash-es@^4.17.14:
+ version "4.17.15"
+ resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
+ integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
+
lodash-id@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/lodash-id/-/lodash-id-0.14.0.tgz#baf48934e543a1b5d6346f8c84698b1a8c803896"
@@ -9953,6 +9982,11 @@ prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"
+property-expr@^2.0.2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910"
+ integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==
+
proxy-addr@~2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
@@ -10199,6 +10233,11 @@ react-error-overlay@^6.0.7:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
+react-fast-compare@^2.0.1:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
+ integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
+
react-fast-compare@^3.1.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
@@ -10928,6 +10967,14 @@ saxes@^5.0.0:
dependencies:
xmlchars "^2.2.0"
+scheduler@^0.18.0:
+ version "0.18.0"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4"
+ integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==
+ dependencies:
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+
scheduler@^0.19.1:
version "0.19.1"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
@@ -11718,6 +11765,11 @@ symbol-tree@^3.2.2, symbol-tree@^3.2.4:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
+synchronous-promise@^2.0.13:
+ version "2.0.13"
+ resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702"
+ integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA==
+
table@^5.2.3:
version "5.4.6"
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
@@ -11924,6 +11976,11 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+toposort@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
+ integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
+
touch@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
@@ -12947,3 +13004,16 @@ yargs@^15.1.0, yargs@^15.4.1:
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"
+
+yup@^0.29.3:
+ version "0.29.3"
+ resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea"
+ integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ==
+ dependencies:
+ "@babel/runtime" "^7.10.5"
+ fn-name "~3.0.0"
+ lodash "^4.17.15"
+ lodash-es "^4.17.11"
+ property-expr "^2.0.2"
+ synchronous-promise "^2.0.13"
+ toposort "^2.0.2"