Skip to content

Commit

Permalink
refactor(WebexActivity): move activity header into its own file
Browse files Browse the repository at this point in the history
  • Loading branch information
lalli-flores committed Oct 31, 2019
1 parent c5f10be commit fcb30a7
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 154 deletions.
61 changes: 61 additions & 0 deletions src/components/WebexActivity/ActivityHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import PropTypes from 'prop-types';
import {format, isToday, isSameWeek, isYesterday} from 'date-fns';

import WebexAvatar from '../WebexAvatar/WebexAvatar';
import {usePerson} from '../hooks';

import './ActivityHeader.scss';

/**
* Returns a formatted timestamp based on the given date's offset from the current time.
*
* @param {Date} timestamp Date instance to format
* @returns {string} formattedDate
*/
export function formatMessageDate(timestamp) {
let formattedDate;

if (isToday(timestamp)) {
// 12:00 PM
formattedDate = format(timestamp, 'p');
} else if (isYesterday(timestamp)) {
// Yesterday, 12:00 PM
formattedDate = `Yesterday, ${format(timestamp, 'p')}`;
} else if (isSameWeek(timestamp, new Date())) {
// Monday, 12:00 PM
formattedDate = format(timestamp, 'iiii, p');
} else {
// 1/1/2020, 12:00 PM
formattedDate = format(timestamp, 'P, p');
}

return formattedDate;
}

/**
* ActivityHeader component displays the header content of an activity.
* Header includes the avatar of the activity author as well as the
* activity timestamp.
*
* @param {object} props
* @returns {object} JSX of the component
*/
export default function ActivityHeader({personID, timestamp}) {
const {displayName} = usePerson(personID);

return (
<div className="activity-header">
<WebexAvatar personID={personID} />
<div className="activity-author">
<span>{displayName}</span>
<span className="activity-timestamp">{formatMessageDate(new Date(timestamp))}</span>
</div>
</div>
);
}

ActivityHeader.propTypes = {
personID: PropTypes.string.isRequired,
timestamp: PropTypes.string.isRequired,
};
28 changes: 28 additions & 0 deletions src/components/WebexActivity/ActivityHeader.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
$content-left-margin: 3.5rem; //avatar width + margin

.activity-header {
display: flex;
flex-direction: column;
position: relative;

.activity-author {
@include noselect();
display: flex;
min-width: 18.75rem !important;
margin-left: $content-left-margin;
font-size: 0.875rem;
line-height: 1.375rem;
text-align: left;
color: $md-gray-70;
word-wrap: break-word;
}

.md-avatar {
position: absolute;
margin: 0 0.5rem;
}

.activity-timestamp {
margin-left: 0.5rem;
}
}
56 changes: 56 additions & 0 deletions src/components/WebexActivity/ActivityHeader.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import {addDays, addHours, startOfToday, startOfWeek, startOfYesterday, subDays} from 'date-fns';

import ActivityHeader, {formatMessageDate} from './ActivityHeader';

jest.mock('../hooks/usePerson');

describe('Activity Header', () => {
let today, sunday;

beforeEach(() => {
today = startOfToday(); // Mock date is set to Thu, Aug 1 2019 00:00
sunday = startOfWeek(today); // Start of week is Sun, July 28 2019 00:00
});

describe('component snapshot', () => {
test('matches header at 3:00 PM', () => {
const headerComponent = <ActivityHeader personID="default" timestamp={addHours(today, 15).toString()} />;

expect(shallow(headerComponent)).toMatchSnapshot();
});
});

describe('formatMessageDate() returns', () => {
test('AM/PM time from today date', () => {
const nineAM = addHours(today, 9);

expect(formatMessageDate(nineAM)).toEqual('9:00 AM');
});

test('"Yesterday" and AM/PM time from yesterday date', () => {
const yesterdayOnePM = addHours(startOfYesterday(), 13);

expect(formatMessageDate(yesterdayOnePM)).toEqual('Yesterday, 1:00 PM');
});

test('day name and AM/PM time from a date of this week', () => {
const monday = addDays(sunday, 1);
const mondayTenAM = addHours(monday, 10);

expect(formatMessageDate(mondayTenAM)).toEqual('Monday, 10:00 AM');
});

test('formatted date for dates older than a week', () => {
const lastFriday = subDays(sunday, 2);
const lastFridayNoon = addHours(lastFriday, 12);

expect(formatMessageDate(lastFridayNoon)).toEqual('07/26/2019, 12:00 PM');
});
});

afterEach(() => {
today = null;
sunday = null;
});
});
54 changes: 7 additions & 47 deletions src/components/WebexActivity/WebexActivity.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import {format, isToday, isSameWeek, isYesterday} from 'date-fns';

import WebexAvatar from '../WebexAvatar/WebexAvatar';
import {useActivity, usePerson} from '../hooks';
import {useActivity} from '../hooks';

import ActivityHeader from './ActivityHeader';
import './WebexActivity.scss';

/**
* Returns a formatted timestamp based on the given date's offset from the current time.
* WebexActivity component displays activity content.
*
* @param {Date} timestamp Date instance to format
* @returns {string} formattedDate
* @param {object} props
* @returns {object} JSX of the component
*/
export function formatMessageDate(timestamp) {
let formattedDate;

if (isToday(timestamp)) {
// 12:00 PM
formattedDate = format(timestamp, 'p');
} else if (isYesterday(timestamp)) {
// Yesterday 12:00 PM
formattedDate = `Yesterday ${format(timestamp, 'p')}`;
} else if (isSameWeek(timestamp, new Date())) {
// Monday 12:00 PM
formattedDate = format(timestamp, 'iiii p');
} else {
// 1/1/2020 12:00 PM
formattedDate = format(timestamp, 'P p');
}

return formattedDate;
}

export function Header({personID, created}) {
const {displayName} = usePerson(personID);

return (
<div className="activity-header">
<WebexAvatar personID={personID} />
<div className="activity-author">
<span>{displayName}</span>
<span className="activity-timestamp">{formatMessageDate(new Date(created))}</span>
</div>
</div>
);
}

Header.propTypes = {
personID: PropTypes.string.isRequired,
created: PropTypes.string.isRequired,
};

export default function WebexActivity({activityID}) {
const {created, displayHeader, ID, personID, text} = useActivity(activityID);
const header = displayHeader ? <Header personID={personID} created={created} /> : null;
const {ID, text, created, displayHeader, personID} = useActivity(activityID);
const header = displayHeader ? <ActivityHeader personID={personID} timestamp={created} /> : null;

return (
<div className="activity" key={ID}>
Expand Down
26 changes: 0 additions & 26 deletions src/components/WebexActivity/WebexActivity.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,6 @@ $content-left-margin: 3.5rem; //avatar width + margin
width: 100%;
margin: 0.5rem 0;

.activity-header {
display: flex;
position: relative;

.activity-author {
@include noselect();
display: flex;
min-width: 18.75rem !important;
margin-left: $content-left-margin;
font-size: 0.875rem;
line-height: 1.375rem;
text-align: left;
color: $md-gray-70;
word-wrap: break-word;
}

.md-avatar {
position: absolute;
margin: 0 0.5rem;
}

.activity-timestamp {
margin-left: 0.5rem;
}
}

.activity-content {
display: flex;
align-items: flex-start;
Expand Down
41 changes: 3 additions & 38 deletions src/components/WebexActivity/WebexActivity.test.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import React from 'react';

import jsonData from '../../data';

import WebexActivity, {Header, formatMessageDate} from './WebexActivity';
import WebexActivity from './WebexActivity';

jest.mock('../hooks/useActivity');
jest.mock('../hooks/usePerson');

describe('Webex Activity component', () => {
describe('Header component snapshot', () => {
test('matches snapshot with "default" props', () => {
expect(shallow(<Header personID="default" created={jsonData.activities.default.created} />)).toMatchSnapshot();
});
});

describe('Webex Activity snapshots', () => {
describe('Webex Activity', () => {
describe('component snapshot', () => {
test('matches snapshot with "default" text', () => {
expect(shallow(<WebexActivity activityID="default" />)).toMatchSnapshot();
});
Expand Down Expand Up @@ -43,30 +34,4 @@ describe('Webex Activity component', () => {
expect(shallow(<WebexActivity activityID="old" />)).toMatchSnapshot();
});
});

describe('unit testing', () => {
test('formatMessageDate() returns today date', () => {
expect(formatMessageDate(new Date('today'))).toEqual('today p');
});

test('formatMessageDate() returns today date', () => {
expect(formatMessageDate(new Date('yesterday'))).toEqual('Yesterday yesterday p');
});

test('formatMessageDate() returns same week date', () => {
expect(formatMessageDate(new Date('sameWeek'))).toEqual('sameWeek iiii p');
});

test('formatMessageDate() returns today date', () => {
expect(formatMessageDate(new Date('old'))).toEqual('old P p');
});

test('throws error with inappropriate activityID', () => {
const wrongID = 'Wrong activityID';

expect(() => shallow(<WebexActivity activityID={wrongID} />)).toThrowError(
new Error(`Could not find activity with ID "${wrongID}"`)
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Activity Header component snapshot matches header at 3:00 PM 1`] = `
<div
className="activity-header"
>
<WebexAvatar
personID="default"
/>
<div
className="activity-author"
>
<span>
Webex Component User
</span>
<span
className="activity-timestamp"
>
3:00 PM
</span>
</div>
</div>
`;
Loading

0 comments on commit fcb30a7

Please sign in to comment.