diff --git a/.git-hooks/pre-commit b/.git-hooks/pre-commit new file mode 100644 index 0000000..3a5f413 --- /dev/null +++ b/.git-hooks/pre-commit @@ -0,0 +1,5 @@ +#!/bin/sh + +yarn lint + +yarn test \ No newline at end of file diff --git a/.github/workflows/build_publish.yaml b/.github/workflows/build_publish.yaml new file mode 100644 index 0000000..a7ff0ad --- /dev/null +++ b/.github/workflows/build_publish.yaml @@ -0,0 +1,38 @@ +name: Build and Release +on: + push: + branches: + - master + workflow_dispatch: +jobs: + build-release: + name: Build the web app and make a zip release + runs-on: ubuntu-latest + steps: + - name: Checkout to repository + uses: actions/checkout@v3 + + - name: Setup Node.js 16.x + uses: actions/setup-node@v1 + with: + node-version: "16.x" + + - name: Install dependencies + run: yarn install + + - name: Run tests + run: yarn test + + - name: Build the app + run: yarn build-prod + + - name: Zip the build + run: zip -r person-management-app.zip build + + - name: Release + uses: softprops/action-gh-release@v1 + with: + name: Build ${{ github.run_number }} + tag_name: v-${{ github.run_number }} + files: person-management-app.zip + token: ${{secrets.BAHMNI_PAT}} diff --git a/.github/workflows/validate_PR.yaml b/.github/workflows/validate_PR.yaml new file mode 100644 index 0000000..0f2fd27 --- /dev/null +++ b/.github/workflows/validate_PR.yaml @@ -0,0 +1,24 @@ +name: Workflow to validate PRs + +on: + pull_request: + branches: [master] + +jobs: + build-release: + name: Build the web app and make a zip release + runs-on: ubuntu-latest + steps: + - name: Checkout to repository + uses: actions/checkout@v3 + + - name: Setup Node.js 16.x + uses: actions/setup-node@v1 + with: + node-version: "16.x" + + - name: Install dependencies + run: yarn install + + - name: Run tests + run: yarn test diff --git a/README.md b/README.md index 06e1acd..c57632b 100644 --- a/README.md +++ b/README.md @@ -94,3 +94,7 @@ Tests will be written with Jest and Enzyme. To run the tests: Run **`yarn test`** + +## Release + +Github Artifacts are created with the help of workflow which contains the build of the person-management-app. The workflow runs on any changes in the master branch and releases a new build. All releases can be accessed [here](https://github.com/Arjun-Go/person-management-app/releases). \ No newline at end of file diff --git a/modify-build-index.js b/modify-build-index.js new file mode 100644 index 0000000..a7a1e80 --- /dev/null +++ b/modify-build-index.js @@ -0,0 +1,26 @@ +const fs = require('fs'); + +const indexPath = 'build/index.html'; +let html = fs.readFileSync(indexPath, 'utf8'); + +const cssHrefRegex = /href="\.\/static\/css\/([^"]+\.css)"/; +const cssMatch = html.match(cssHrefRegex); +if (cssMatch) { + const originalHref = cssMatch[0]; + const cssFilename = cssMatch[1]; + const newHref = `href="/person-management/static/css/${cssFilename}"`; + + html = html.replace(originalHref, newHref); +} + +const jsHrefRegex = /src="\.\/static\/js\/([^"]+\.js)"/; +const jsMatch = html.match(jsHrefRegex); +if (jsMatch) { + const originalHref = jsMatch[0]; + const jsFilename = jsMatch[1]; + const newHref = `src="/person-management/static/js/${jsFilename}"`; + + html = html.replace(originalHref, newHref); + } + +fs.writeFileSync(indexPath, html); \ No newline at end of file diff --git a/package.json b/package.json index 275d048..c4da602 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom --watchAll=false", + "build-prod": "react-scripts build && node modify-build-index.js", "eject": "react-scripts eject" }, "resolutions": { diff --git a/src/App.js b/src/App.js index 0c7c044..039973e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,16 +1,18 @@ import React, { Component } from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; -import FormContainer from './containers/FormContainer'; +import CreatePerson from './containers/CreatePerson'; import PersonDashboard from './containers/PersonDashboard'; import './index.css'; +import EditPerson from './containers/EditPerson'; class App extends Component { render() { return ( - + - + + diff --git a/src/api/personApi.js b/src/api/personApi.js new file mode 100644 index 0000000..082ab8c --- /dev/null +++ b/src/api/personApi.js @@ -0,0 +1,96 @@ +import { Constants } from '../components/common/constants'; + +export async function getPersonAttributeTypeUuid(attributeName) { + try { + const url = + Constants.personAttributeType + '?q=' + attributeName + '&v=default'; + const response = await fetch(url).then(function(response) { + if (!response.status === 200) { + throw Error({ response: response }); + } + return response.json(); + }); + return response.results[0].uuid; + } catch (error) { + console.error(error); + return error.response; + } +} + +export async function savePerson(payload) { + try { + return await fetch(Constants.person, { + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + }, + credentials: 'include' + }); + } catch (error) { + console.error(error); + return error.response; + } +} + +export async function searchPerson(person) { + try { + const url = + Constants.person + + '?q=' + + person + + '&v=custom%3Auuid%2Cdisplay%2Cage%2Cgender%2CdateCreated'; + return await fetch(url, { + method: 'GET', + credentials: 'include' + }); + } catch (error) { + console.error(error); + return error.response; + } +} + +export async function fetchPerson(uuid) { + try { + const url = Constants.person + '/' + uuid; + return await fetch(url, { + method: 'GET', + credentials: 'include' + }); + } catch (error) { + console.error(error); + return error.response; + } +} + +export async function updatePerson(uuid, payload) { + try { + const url = Constants.person + '/' + uuid; + return await fetch(url, { + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + }, + credentials: 'include' + }); + } catch (error) { + console.error(error); + return error.response; + } +} + +export async function fetchPersonAttributeConfig() { + try { + const url = Constants.registrationConfig; + return await fetch(url, { + method: 'GET', + credentials: 'include' + }); + } catch (error) { + console.error(error); + return error.response; + } +} diff --git a/src/components/common/Button.css b/src/components/common/Button.css index cc5e08c..5fb9113 100644 --- a/src/components/common/Button.css +++ b/src/components/common/Button.css @@ -1,6 +1,7 @@ .buttonContainer { display: flex; - justify-content: flex-end; + padding: 0 0 0 2%; + justify-content: space-evenly; } .buttonWithSpinner { @@ -27,6 +28,23 @@ background-color: #9b9b9b; } +.cancelButtonWithSpinner { + border: none; + border-radius: var(--border-radius); + height: var(--height); + background-color: #393939; + color: white; + margin-bottom: 50px; + margin-right: 1rem; + padding: 0 45px; + width: 155px; + cursor: pointer; +} + +.cancelButtonWithSpinner:hover { + background-color: #595959; +} + .spinner { position: relative; border-radius: 50%; diff --git a/src/components/common/Button.js b/src/components/common/Button.js index 7f4ec19..958c746 100644 --- a/src/components/common/Button.js +++ b/src/components/common/Button.js @@ -37,7 +37,9 @@ const Button = props => { return (
+
+
+ + + )} {modal} @@ -383,4 +505,4 @@ class FormContainer extends Component { } } -export default FormContainer; +export default CreatePerson; diff --git a/src/containers/CreatePerson.test.js b/src/containers/CreatePerson.test.js new file mode 100644 index 0000000..609dccd --- /dev/null +++ b/src/containers/CreatePerson.test.js @@ -0,0 +1,169 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import CreatePerson from './CreatePerson'; +import Input from '../components/common/Input'; +import SelectFromList from '../components/common/Dropdown'; +import moment from 'moment'; + +jest.mock('../api/personApi', () => { + return { + fetchPersonAttributeConfig: jest.fn(() => + Promise.resolve({ + status: 200, + json: jest.fn(() => + Promise.resolve({ + config: { + personAttributesForRelations: [ + { + name: 'occupation', + attributeName: 'occupationNew', + text: 'Occupation' + } + ] + } + }) + ) + }) + ), + getPersonAttributeTypeUuid: jest.fn(() => + Promise.resolve({ + status: 200, + json: jest.fn(() => + Promise.resolve({ + results: [ + { + uuid: '8d871d18-c2cc-11de-8d13-0010c6dffd0f', + display: 'Occupation' + } + ] + }) + ) + }) + ) + }; +}); + +describe('CreatePerson', () => { + let wrapper; + + beforeEach(() => { + wrapper = shallow(); + }); + + it('renders fourteen components', () => { + expect(wrapper.find(Input).length).toEqual(7); + }); + + it('renders one component', () => { + expect(wrapper.find(SelectFromList).length).toEqual(1); + }); + + it('renders firstName input', () => { + expect(wrapper.find('#firstName').length).toEqual(1); + }); + + describe('the user populates first name input', () => { + const exampleFirstName = 'Max'; + let firstNameInput; + + beforeEach(() => { + firstNameInput = wrapper.find('#firstName'); + firstNameInput.simulate('change', { + target: { value: exampleFirstName, name: 'firstName' } + }); + }); + + it('should update the state property firstName', () => { + expect(wrapper.state().person.firstName).toEqual(exampleFirstName); + }); + + it('requires First name input', () => { + expect(firstNameInput.props().required).toBe(true); + }); + }); // end of firstName describe + + describe('the user selects value from gender options', () => { + const exampleGenderSelected = 'Female'; + let selectGender; + + beforeEach(() => { + selectGender = wrapper.find('#lastName'); + selectGender.simulate('change', { + target: { name: 'gender', value: exampleGenderSelected } + }); + }); + + it('should update the state property gender', () => { + expect(wrapper.state().person.gender).toEqual(exampleGenderSelected); + }); + }); // end of gender options describe + + describe('the user populates birthdate', () => { + const exampleDOB = '2019-07-01'; + let dobInput; + + beforeEach(() => { + dobInput = wrapper.find('#birthdate'); + dobInput.simulate('change', { + target: { value: exampleDOB, name: 'birthdate' } + }); + }); + + it('should update the state property birthdate', () => { + expect(wrapper.state().person.birthdate).toEqual(exampleDOB); + }); + + it('requires birthdate input', () => { + expect(dobInput.props().required).toBe(true); + }); + + it('should update the state property age', () => { + const age = { years: 0, months: 0, days: 0 }; + const today = moment(); + age.years = moment.duration(today.diff(exampleDOB)).years(); + age.months = moment.duration(today.diff(exampleDOB)).months(); + age.days = moment.duration(today.diff(exampleDOB)).days(); + expect(wrapper.state().person.age.years).toEqual(age.years); + expect(wrapper.state().person.age.months).toEqual(age.months); + expect(wrapper.state().person.age.days).toEqual(age.days); + }); + }); // end of birthdate describe + + describe('the user populates age (year, month and day)', () => { + const exampleYears = 4; + const exampleMonths = 1; + const exampleDays = 2; + let yearsInput, monthsInput, daysInput; + beforeEach(() => { + yearsInput = wrapper.find('#age'); + monthsInput = wrapper.find('#months'); + daysInput = wrapper.find('#days'); + yearsInput.simulate('change', { + target: { value: exampleYears, name: 'years' } + }); + monthsInput.simulate('change', { + target: { value: exampleMonths, name: 'months' } + }); + daysInput.simulate('change', { + target: { value: exampleDays, name: 'days' } + }); + }); + + it('should update the state property years, months and days', () => { + expect(wrapper.state().person.age.years).toEqual(exampleYears); + expect(wrapper.state().person.age.months).toEqual(exampleMonths); + expect(wrapper.state().person.age.days).toEqual(exampleDays); + }); + + it('should update the state property age', () => { + const now = moment(); + const dob = moment() + .year(now.year() - exampleYears) + .month(now.month() - exampleMonths) + .date(now.date() - exampleDays); + expect(wrapper.state().person.birthdate).toEqual( + dob.format('YYYY-MM-DD') + ); + }); + }); // end of birthdate describe +}); // end of outer describe diff --git a/src/containers/FormContainer.css b/src/containers/EditPerson.css similarity index 84% rename from src/containers/FormContainer.css rename to src/containers/EditPerson.css index 1752eab..3513375 100644 --- a/src/containers/FormContainer.css +++ b/src/containers/EditPerson.css @@ -34,11 +34,12 @@ .checkbox-container { display: flex; justify-content: flex-start; + padding-top: 0.938rem; } #hidden-title, #display-none { - opacity: 0; + display: none; } /* Radio buttons flex container */ @@ -80,6 +81,17 @@ color: red; } +.div-select { + border: 1.2px solid #9b9b9b; + border-radius: var(--border-radius); + height: var(--height); + width: 220px; + background-color: rgba(250, 250, 250, 0.5); + margin-bottom: 0.8rem; + outline: 0 none; + color: black; +} + @media (max-width: 910px) { #root > div > form > div:nth-child(3) > fieldset > div { width: 80%; diff --git a/src/containers/EditPerson.js b/src/containers/EditPerson.js new file mode 100644 index 0000000..ee918eb --- /dev/null +++ b/src/containers/EditPerson.js @@ -0,0 +1,519 @@ +import moment from 'moment'; +import React, { Component } from 'react'; +import { + fetchPerson, + getPersonAttributeTypeUuid, + updatePerson, + fetchPersonAttributeConfig +} from '../api/personApi'; +import Button from '../components/common/Button'; +import Checkbox from '../components/common/Checkbox'; +import Dropdown from '../components/common/Dropdown'; +import Input from '../components/common/Input'; +import Navbar from '../components/common/Navbar'; +import { genderOptions } from '../components/common/constants'; +import ModalError from '../components/common/modals/ModalError'; +import ModalSuccess from '../components/common/modals/ModalSuccess'; +import './EditPerson.css'; + +class EditPerson extends Component { + constructor(props) { + super(props); + this.state = { + uuid: this.props.match.params.uuid, + person: { + firstName: '', + middleName: '', + lastName: '', + gender: '', + birthdate: moment(), + birthdateEstimated: false + }, + showModal: false, + isAPIError: false, + isRequestError: false, + isRequestLoading: false, + lastUpdatedPerson: '', + attributes: [], + attributesData: [] + }; + this.handleClearForm = this.handleClearForm.bind(this); + } + + componentDidMount() { + this.getAttributes().then(response => { + const attributes = response.config.personAttributesForRelations.map( + async attribute => { + const uuid = await getPersonAttributeTypeUuid( + attribute.attributeName + ); + return { + ...attribute, + value: '', + uuid: uuid + }; + } + ); + + Promise.all(attributes) + .then(resolvedAttributes => { + this.setState({ + attributes: resolvedAttributes + }); + }) + .then(() => { + this.loadPersonData(); + }); + }); + } + + getAttributes = async () => { + const response = await fetchPersonAttributeConfig(); + if (response.status === 200) { + return response.json(); + } else { + return Promise.reject({ + status: response.status, + statusText: response.statusText + }); + } + }; + + loadPersonData = async () => { + fetchPerson(this.state.uuid) + .then(response => { + if (response.status === 200) { + this.setState({ + isRequestLoading: false + }); + return response.json(); + } else { + return Promise.reject({ + status: response.status, + statusText: response.statusText + }); + } + }) + .then(data => { + const { firstName, middleName, lastName } = this.getAllNames( + data.display + ); + const gender = this.getGender(data.gender); + this.setState({ + person: { + firstName: firstName, + middleName: middleName, + lastName: lastName, + gender: gender, + birthdate: moment(data.birthdate).format('YYYY-MM-DD'), + birthdateEstimated: data.birthdateEstimated + } + }); + // eslint-disable-next-line + if (data.attributes != []) { + this.setState({ attributesData: data.attributes }); + this.setPersonAttributeValues(data.attributes); + } + }); + }; + + getGender = gender => { + switch (gender) { + case 'M': + return 'Male'; + case 'F': + return 'Female'; + case 'O': + return 'Other'; + default: + return gender; + } + }; + + setPersonAttributeValues = attributes => { + attributes.forEach(attribute => { + const attributeName = attribute.display.split(' = ')[0]; + const attributeValue = attribute.display.split(' = ')[1]; + this.setState(prevState => ({ + attributes: prevState.attributes.map(stateAttribute => { + if (stateAttribute.attributeName === attributeName) { + return { ...stateAttribute, value: attributeValue }; + } + return stateAttribute; + }) + })); + }); + }; + + getAllNames = name => { + const nameArray = name.split(' '); + const firstName = nameArray[0]; + if (nameArray.length === 2) { + const lastName = nameArray[1]; + return { firstName, lastName }; + } else if (nameArray.length === 3) { + const middleName = nameArray[1]; + const lastName = nameArray[2]; + return { firstName, middleName, lastName }; + } + }; + + handleChange = ({ target: input }) => { + const person = { ...this.state.person }; + person[input.name] = input.value; + this.setState({ person }); + }; + + hideModal = () => { + this.setState({ + showModal: false + }); + }; + + handleClearForm() { + this.setPersonAttributeValues(this.state.attributesData); + this.setState({ + isRequestError: false + }); + } + + isVoided = value => { + return value === '' ? true : false; + }; + + createFormPayload = () => { + const { + firstName, + middleName, + lastName, + gender, + birthdate, + birthdateEstimated + } = this.state.person; + + const attributes = this.state.attributes.map(attribute => { + return { + attributeType: { + uuid: attribute.uuid + }, + voided: this.isVoided(attribute.value), + value: !this.isVoided(attribute.value) ? attribute.value : null + }; + }); + + const formPayload = { + names: [ + { + familyName: lastName, + givenName: firstName, + middleName: middleName + } + ], + gender, + birthdateEstimated, + attributes: attributes + }; + if (this.isVoided(birthdate)) { + formPayload.birthdate = birthdate + 'T12:00:00.000+0000'; + } + return formPayload; + }; + + handlePersonUpdate = e => { + e.preventDefault(); + const payload = this.createFormPayload(); + this.updateRequest(payload); + }; + + handleOtherAttributesChange = ({ target: input }) => { + const attributes = [...this.state.attributes]; + const index = attributes.findIndex( + attribute => attribute.name === input.name + ); + attributes[index].value = input.value; + this.setState({ attributes }); + }; + + updateRequest(formPayload) { + const { firstName, lastName } = this.state.person; + this.setState({ + isRequestLoading: true + }); + updatePerson(this.state.uuid, formPayload) + .then(response => { + if (response.status === 200) { + this.setState({ + ...this.state.submitForm, + lastUpdatedPerson: firstName + ' ' + lastName, + isRequestLoading: false + }); + return response.json(); + } else { + return Promise.reject({ + status: response.status, + statusText: response.statusText + }); + } + }) + .then(response => { + this.setState({ + showModal: true + }); + this.sendPersonToIframe(response); + this.handleClearForm(); + }) + .catch(error => + this.setState( + { + isRequestError: true, + showModal: true, + isRequestLoading: false + }, + () => console.error('Error:', error) + ) + ); + } + + sendPersonToIframe = async response => { + var iframe = window.frameElement; + if (iframe) { + const delay = 4000; + await new Promise(resolve => setTimeout(resolve, delay)); + window.parent.postMessage(response, '*'); + } + }; + + errorModalText = [ + 'An error occurred while trying to register this person.', + 'Please try again.' + ]; + + successModalText = ['was updated.']; + + render() { + const { + firstName, + middleName, + lastName, + gender, + birthdate, + birthdateEstimated + } = this.state.person; + + const { + isRequestError, + isRequestLoading, + showModal, + lastUpdatedPerson: lastCreatedPerson + } = this.state; + + const personAttributes = this.state.attributes; + + const isEnabled = !isRequestLoading; + + let modal = null; + + if (showModal) { + if (isRequestError) { + modal = ( + + ); + } else { + modal = ( + + ); + } + } + + return ( +
+ +
+
+
+ Name +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+ Age +
+
+ + +
+
+ + + +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ {personAttributes.length > 0 && ( +
+
+
+
+ Other Information + {personAttributes.map(attribute => { + return ( +
+
+ +
+
+ ); + })} +
+
+
+
+
+
+
+
+
+
+ )} + {modal} +
+
+ ); + } +} + +export default EditPerson; diff --git a/src/containers/EditPerson.test.js b/src/containers/EditPerson.test.js new file mode 100644 index 0000000..ab42e58 --- /dev/null +++ b/src/containers/EditPerson.test.js @@ -0,0 +1,123 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import EditPerson from './EditPerson'; +import Input from '../components/common/Input'; +import SelectFromList from '../components/common/Dropdown'; + +jest.mock('../api/personApi', () => { + return { + fetchPerson: jest.fn(() => { + return Promise.resolve({ + status: 200, + json: jest.fn(() => + Promise.resolve({ + display: 'John Doe', + uuid: '819ba69e-6c13-4803-8a14-d7abfac66e99', + gender: 'M', + birthdate: '1990-01-01', + birthdateEstimated: false, + attributes: [ + { + display: 'occupation = Teacher' + } + ] + }) + ) + }); + }), + fetchPersonAttributeConfig: jest.fn(() => + Promise.resolve({ + status: 200, + json: jest.fn(() => + Promise.resolve({ + config: { + personAttributesForRelations: [ + { + name: 'occupation', + attributeName: 'occupationNew', + text: 'Occupation' + } + ] + } + }) + ) + }) + ), + getPersonAttributeTypeUuid: jest.fn(() => + Promise.resolve({ + status: 200, + json: jest.fn(() => + Promise.resolve({ + results: [ + { + uuid: '8d871d18-c2cc-11de-8d13-0010c6dffd0f', + display: 'Occupation' + } + ] + }) + ) + }) + ) + }; +}); + +describe('EditPerson', () => { + let wrapper; + + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('renders fourteen components', () => { + expect(wrapper.find(Input).length).toEqual(7); + }); + + it('renders one component', () => { + expect(wrapper.find(SelectFromList).length).toEqual(1); + }); + + it('renders firstName input', () => { + expect(wrapper.find('#firstName').length).toEqual(1); + }); + + it('Checks if firstName input is disabled', () => { + expect(wrapper.find('#firstName').prop('disabled')).toBe(true); + }); + + it('renders lastName input', () => { + expect(wrapper.find('#lastName').length).toEqual(1); + }); + + it('Checks if lastName input is disabled', () => { + expect(wrapper.find('#lastName').prop('disabled')).toBe(true); + }); + + it('renders occupation input', () => { + setTimeout(() => { + wrapper.update(); + expect(wrapper.find('#occupation').length).toEqual(1); + done(); + }, 1000); + }); + + describe('the user populates email input', () => { + const exampleOccupation = 'Teacher'; + let firstNameInput; + + it('should update the state property email', () => { + setTimeout(() => { + wrapper.update(); + firstNameInput = wrapper.find('#occupation'); + firstNameInput.simulate('change', { + target: { value: exampleOccupation, name: 'occupation' } + }); + expect(wrapper.state().attributes[0].value).toEqual(exampleOccupation); + done(); + }, 1000); + }); + }); +}); diff --git a/src/containers/FormContainer.test.js b/src/containers/FormContainer.test.js deleted file mode 100644 index cb901a9..0000000 --- a/src/containers/FormContainer.test.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import FormContainer from './FormContainer'; -import Input from '../components/common/Input'; -import SelectFromList from '../components/common/RadioButtonGroup'; - -describe('FormContainer', () => { - let wrapper; - - beforeEach(() => { - wrapper = shallow(); - }); - - it('renders seven components', () => { - expect(wrapper.find(Input).length).toEqual(7); - }); - - it('renders one component', () => { - expect(wrapper.find(SelectFromList).length).toEqual(1); - }); - - it('renders firstName input', () => { - expect(wrapper.find('#firstName').length).toEqual(1); - }); - - describe('the user populates first name input', () => { - const exampleFirstName = 'Max'; - let firstNameInput; - - beforeEach(() => { - firstNameInput = wrapper.find('#firstName'); - firstNameInput.simulate('change', { - target: { value: exampleFirstName, name: 'firstName' } - }); - }); - - it('should update the state property firstName', () => { - expect(wrapper.state().person.firstName).toEqual(exampleFirstName); - }); - - it('requires First name input', () => { - expect(firstNameInput.props().required).toBe(true); - }); - }); // end of firstName describe - - describe('the user selects value from gender options', () => { - const exampleGenderSelected = 'Female'; - let selectGender; - - beforeEach(() => { - selectGender = wrapper.find('#selectGender'); - selectGender.simulate('change', { - target: { name: 'gender', value: exampleGenderSelected } - }); - }); - - it('should update the state property gender', () => { - expect(wrapper.state().person.gender).toEqual(exampleGenderSelected); - }); - }); // end of gender options describe -}); // end of outer describe diff --git a/src/containers/PersonDashboard.js b/src/containers/PersonDashboard.js index 29caa0d..46f6ecf 100644 --- a/src/containers/PersonDashboard.js +++ b/src/containers/PersonDashboard.js @@ -6,6 +6,7 @@ import Table from '../components/common/Table'; import Button from '../components/common/Button'; import './PersonDashboard.css'; import { useTranslation } from 'react-i18next'; +import { searchPerson } from '../api/personApi'; class PersonDashboard extends Component { state = { @@ -35,18 +36,8 @@ class PersonDashboard extends Component { this.setState({ isRequestLoading: true }); - const url = process.env.REACT_APP_URL; - const searchPerson = this.state.person.name; - const q = '?q=' + searchPerson; - const fullUrl = url + q; - const customData = - '&v=custom%3Auuid%2Cdisplay%2Cage%2Cgender%2CdateCreated'; - const fullUrlCustom = fullUrl + customData; - fetch(fullUrlCustom, { - method: 'GET', - credentials: 'include' - }) + searchPerson(this.state.person.name) .then(response => { if (response.status === 200) { this.setState({ diff --git a/src/index.css b/src/index.css index 22b290c..fae669f 100644 --- a/src/index.css +++ b/src/index.css @@ -19,7 +19,7 @@ form { position: relative; margin-left: 4rem; - width: 55%; + width: 80%; } hr { @@ -46,6 +46,8 @@ legend { input[type='text'], input[type='number'], input[type='date'], +input[type='email'], +input[type='tel'], label.labelRadio { border: 1.2px solid #9b9b9b; border-radius: var(--border-radius); @@ -58,7 +60,9 @@ label.labelRadio { input[type='text'], input[type='number'], -input[type='date'] { +input[type='date'], +input[type='email'], +input[type='tel'] { box-shadow: var(--box-shadow); box-shadow: inset 0 1px 4px hsla(0, 0, 0, 0.2); @@ -96,6 +100,10 @@ input[type='checkbox']:checked + label span { background: url('../src/assets/checkbox_checked.svg') 0px center no-repeat; } +input[type='checkbox']:disabled + label span { + cursor: not-allowed; +} + @media (max-width: 1080px) { form { width: 70%; diff --git a/src/setupTests.js b/src/setupTests.js index 82edfc9..cbd1fe6 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -1,4 +1,13 @@ import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn(), + clear: jest.fn() +}; + +global.localStorage = localStorageMock; + configure({ adapter: new Adapter() });