Skip to content

Commit

Permalink
Birthday reminders for Persons on your list (#339)
Browse files Browse the repository at this point in the history
* Created upcoming birthday summary and added card to main page

* Updated date format and birthday api retrieval based on api spec

* Finished adding component to home page properly utilising new api, creating new service functions

* Wrote some static tests where possible (Dates are always changing for birthday so cannot test in same way as previous tests

* Cleaned up code as per review changes, removed duplicate function, and fixed indentation

* Removed service account json

* Fixed test to ignore date
  • Loading branch information
asok3781 authored Mar 31, 2022
1 parent 98eb047 commit 0e77821
Show file tree
Hide file tree
Showing 10 changed files with 385 additions and 13 deletions.
1 change: 1 addition & 0 deletions forgettable-frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ yarn-error.log*

# Firebase Config
/src/firebase-config.js
forgettable_service_account.json

# dotenv environment variable files
.env
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import {Avatar} from '@mui/material';
import classes from './UpcomingBirthdaySummary.module.css';
import {getBirthdayDate} from '../../functions/dateFormatter';
import UnknownDetail from '../UnknownDetail/UnknownDetail';
import PropTypes from 'prop-types';
import {getImageSrcFromBuffer} from '../../functions/getImageSrcFromBuffer';

/*
* Component for displaying information for upcoming birthdays.
* Based off of EncounterCardSummary and PersonCardSummary components
* orginally authored by Mercury Lin (lin8231)
*
* Author: Aaron Song (ason720)
*/
const UpcomingBirthdaySummary = (props) => {
return (
<div
className={classes.UpcomingBirthdaySummary}
onClick={props.onClick}
data-testid="container-card"
>
<div className={classes.ContentContainer}>
<div className={classes.HeaderContainer}>
<Avatar
alt={props.firstName}
src={getImageSrcFromBuffer(props.img)}
sx={{
height: '70px',
width: '70px',
marginRight: '14px',
backgroundColor:
getComputedStyle(document.body).getPropertyValue('--prmry'),
fontSize:
getComputedStyle(document.body)
.getPropertyValue('--text-xxlarge'),
}}
/>
<div className={classes.IdentityInfoConatiner}>
<h3 data-testid="name-element">{props.firstName}</h3>
</div>
</div>
<div className={classes.DetailsContainer}>
<p>
{'Birthday: '}
{props.birthday ?
<span data-testid="birthdate-element">
{getBirthdayDate(props.birthday)}
</span> :
<UnknownDetail />}
</p>
</div>
</div>
</div>
);
};

UpcomingBirthdaySummary.propTypes = {
id: PropTypes.string,
firstName: PropTypes.string.isRequired,
img: PropTypes.string,
birthday: PropTypes.instanceOf(Date),
onClick: PropTypes.func.isRequired,
};

export default UpcomingBirthdaySummary;
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
@import '../../colours.css';

.UpcomingBirthdaySummary{
height: 150px;
width:240px;
cursor: pointer;
border-radius: var(--radius-card);
background-color: var(--card);
transition: 0.3s;
display: flex;
padding: 20px 22px;
box-sizing: border-box;
}

.UpcomingBirthdaySummary:hover {
background-color: var(--hilit);
}

.ContentContainer {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}

.HeaderContainer {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}

.IdentityInfoConatiner {
flex: 1;
display: flex;
flex-direction: column;

}

.IdentityInfoConatiner > h3 {
font-size: var(--text-medium);
color: var(--txt1);
font-weight: var(--font-semibold);
margin: 0;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1; /* number of lines to show */
line-clamp: 1;
-webkit-box-orient: vertical;
}

.IdentityInfoConatiner > p {
font-size: var(--text-card-body-normal);
margin: 0;
color: var(--txt2);
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2; /* number of lines to show */
line-clamp: 2;
-webkit-box-orient: vertical;
}

.DetailsContainer {
flex: 1;
display: flex;
flex-direction: column;
}

.DetailsContainer > p {
color: var(--txt2);
font-size: var(--text-xsmall);
}

.DescriptionText {
margin-top: -2px;
overflow: hidden;
text-align: left;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3; /* number of lines to show */
line-clamp: 3;
-webkit-box-orient: vertical;
}

.IdentityInfoConatiner > h3 {
text-align: left;
text-overflow: ellipsis;
overflow: hidden;
max-width: 110px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import {render, fireEvent, cleanup} from '@testing-library/react';

import ShallowRenderer from 'react-test-renderer/shallow';

import UpcomingBirthdaySummary from '../UpcomingBirthdaySummary';
import moment from 'moment';

jest.mock('../../../services');

const mockedUsedNavigate = jest.fn();

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate,
}));

it('renders UpcomingBirthdaySummary UI with correct hierarchy', () => {
const renderer = new ShallowRenderer();
renderer.render(<UpcomingBirthdaySummary
firstName='Bob'
onClick={() => { }} />);
const result = renderer.getRenderOutput();
expect(result).toMatchSnapshot();
});

afterEach(cleanup);

it('successfully fires event when card is clicked', () => {
const handleClick = jest.fn();

const {getByTestId} = render(
<UpcomingBirthdaySummary
firstName='Bob'
birthday={moment(1648698811606).toDate()}
onClick={handleClick}
/>);

const node = getByTestId('container-card');

fireEvent.click(node);
expect(handleClick).toHaveBeenCalledTimes(1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders UpcomingBirthdaySummary UI with correct hierarchy 1`] = `
<div
className="UpcomingBirthdaySummary"
data-testid="container-card"
onClick={[Function]}
>
<div
className="ContentContainer"
>
<div
className="HeaderContainer"
>
<ForwardRef(Avatar)
alt="Bob"
src={null}
sx={
Object {
"backgroundColor": "",
"fontSize": "",
"height": "70px",
"marginRight": "14px",
"width": "70px",
}
}
/>
<div
className="IdentityInfoConatiner"
>
<h3
data-testid="name-element"
>
Bob
</h3>
</div>
</div>
<div
className="DetailsContainer"
>
<p>
Birthday:
<UnknownDetail />
</p>
</div>
</div>
</div>
`;
40 changes: 40 additions & 0 deletions forgettable-frontend/src/functions/__tests__/dateFormatter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
getLongDateStringWithSpaces,
calculateAge,
getLongDateStringWithSlashes,
getMonthAndDay,
getDayOfWeek,
} from '../dateFormatter';

test(
Expand Down Expand Up @@ -83,3 +85,41 @@ test('getLongDateStringWithSlashes: should return a string representing "Unknown
expect(formattedString).toBe('Unknown');
};
});

test('getDayOfWeek: should return a string representing "Unknown" given a null Date object',
() => {
() => {
const formattedString = getDayOfWeek(null);

expect(formattedString).toBe('Unknown');
};
});

test('getDayOfWeek: should format date into day of the week',
() => {
() => {
const date = new Date('30-03-2022');
const formattedString = getDayOfWeek(date);

expect(formattedString).toBe('Wednesday');
};
});

test('getMonthAndDay: should return a string representing "Unknown" given a null Date object',
() => {
() => {
const formattedString = getMonthAndDay(null);

expect(formattedString).toBe('Unknown');
};
});

test('getMonthAndDay: should format date into day of the week',
() => {
() => {
const date = new Date('30-03-2022');
const formattedString = getMonthAndDay(date);

expect(formattedString).toBe('30 March');
};
});
Loading

0 comments on commit 0e77821

Please sign in to comment.