diff --git a/circle.yml b/circle.yml
index 5d83576..ba52c24 100644
--- a/circle.yml
+++ b/circle.yml
@@ -23,7 +23,6 @@ test:
- npm run lint
- npm run security:check
- npm run test:server
- - npm run test:client -- --browsers PhantomJS
- export APP_HOST=`ifconfig eth0 | grep -oP 'inet addr:\K\S+'` && COMPOSE_FILE=docker-compose-test.yml docker-compose up -d
- npm run test:e2e
diff --git a/package.json b/package.json
index 9d5d7d7..796deca 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,7 @@
"security:check": "./node_modules/.bin/nsp check",
"start": "node build/server",
"test": "npm run test:server && npm run security:check",
- "test-full": "npm run test:server && npm run test:client && npm run security:check && npm run lint && npm run test:e2e",
- "test:client": "karma start spec/karma.config.js",
+ "test-full": "npm run test:server && npm run security:check && npm run lint && npm run test:e2e",
"test:e2e": "wdio spec/wdio.config.js",
"test:server": "mocha --compilers js:babel-core/register --require spec/mocha-helper.js --recursive --reporter spec $(find src -name *spec.js)"
},
@@ -76,6 +75,7 @@
"chai": "^3.5.0",
"css-loader": "^0.23.1",
"css-modules-require-hook": "^2.1.0",
+ "enzyme": "^2.4.1",
"eslint": "^2.12.0",
"eslint-config-airbnb": "^9.0.1",
"eslint-plugin-import": "^1.8.1",
diff --git a/spec/fixtures/events.js b/spec/fixtures/events.js
index 5142e97..700d41b 100644
--- a/spec/fixtures/events.js
+++ b/spec/fixtures/events.js
@@ -19,13 +19,20 @@ export const defaultEvent = {
url: 'http://www.frontendlondon.co.uk/'
},
],
- datetime: {
+ startDateTime: {
iso: '2016-06-25T11:00:00+0000',
date: '25',
month: '06',
monthSym: 'Jun',
year: '2016',
},
+ endDateTime: {
+ iso: '2016-06-28T11:00:00+0000',
+ date: '28',
+ month: '06',
+ monthSym: 'Jun',
+ year: '2016',
+ },
body: [
{
type: 'paragraph',
diff --git a/src/server/api/controllers/events.js b/src/server/api/controllers/events.js
index dba26db..afc0122 100644
--- a/src/server/api/controllers/events.js
+++ b/src/server/api/controllers/events.js
@@ -29,7 +29,14 @@ const allEventFields = `
title
url
}
- datetime {
+ startDateTime {
+ iso
+ date
+ month
+ monthSym
+ year
+ }
+ endDateTime {
iso
date
month
@@ -77,7 +84,7 @@ export default class EventsController {
.then((response) => response.json())
.then((events) => {
res.send({ list: events.data.allEvents.sort((a, b) =>
- new Date(b.datetime.iso) - new Date(a.datetime.iso)
+ new Date(b.startDateTime.iso) - new Date(a.startDateTime.iso)
) });
})
.catch((err) => {
diff --git a/src/shared/components/date-bubble/index.js b/src/shared/components/date-bubble/index.js
index 7d54c36..44f8a8e 100644
--- a/src/shared/components/date-bubble/index.js
+++ b/src/shared/components/date-bubble/index.js
@@ -1,28 +1,37 @@
// Displays date bubble
-import React, { Component } from 'react';
+import React, { PropTypes } from 'react';
import styles from './style.css';
-export default class DateBubble extends Component {
- static propTypes = {
- date: React.PropTypes.string,
- month: React.PropTypes.string,
- year: React.PropTypes.string,
- };
-
- render() {
+function displayDateContent(startDateTime, endDateTime) {
+ if (endDateTime) {
return (
-
-
- {this.props.date}
-
-
- {this.props.month}
-
-
- {this.props.year}
-
-
- );
+ `${startDateTime.date} ${startDateTime.monthSym} ${startDateTime.year} - `
+ + `${endDateTime.date} ${endDateTime.monthSym} ${endDateTime.year}`);
}
+ return (
+ `${startDateTime.date} ${startDateTime.monthSym} ${startDateTime.year}`);
}
+
+const DateBubble = ({
+ startDateTime,
+ endDateTime,
+}) => (
+
+ {displayDateContent(startDateTime, endDateTime)}
+
+);
+
+
+const dateShape = {
+ date: PropTypes.string.isRequired,
+ monthSym: PropTypes.string.isRequired,
+ year: PropTypes.string.isRequired,
+};
+
+DateBubble.propTypes = {
+ startDateTime: PropTypes.shape(dateShape).isRequired,
+ endDateTime: PropTypes.shape(dateShape),
+};
+
+export default DateBubble;
diff --git a/src/shared/components/date-bubble/style.css b/src/shared/components/date-bubble/style.css
index 987f16b..65e7bda 100644
--- a/src/shared/components/date-bubble/style.css
+++ b/src/shared/components/date-bubble/style.css
@@ -1,15 +1,14 @@
-@value mobile from "../variables/breakpoints.css";
+@value red from "../variables/colors.css";
.dateBubble {
- background: #be1414;
display: inline-block;
- font-size: 0.8em;
- color: #fff;
+ font-size: 0.9em;
+ font-weight: bold;
+ color: red;
text-align: center;
text-transform: uppercase;
min-width: 3em;
margin-bottom: 1em;
- padding: 0.3em 0;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
@@ -18,27 +17,11 @@
}
.date {
- font-size: 2em;
+ font-size: 0.9em;
line-height: 1.2em;
}
-.month {
-}
-
-.year {
-}
-
-@media mobile {
- .dateBubble {
- padding: 0.3em;
- }
-
- .date, .month, .year {
- display: inline-block;
- margin: 0 2px;
- }
-
- .date {
- font-size: 1em;
- }
+.date, .month, .year {
+ display: inline-block;
+ margin: 0 2px;
}
diff --git a/src/shared/components/event-links-list/index.js b/src/shared/components/event-links-list/index.js
index 68ebde0..c1e1fe7 100644
--- a/src/shared/components/event-links-list/index.js
+++ b/src/shared/components/event-links-list/index.js
@@ -1,47 +1,51 @@
// Displays list of links related to the event
-import React, { Component } from 'react';
+import React, { PropTypes } from 'react';
import classNames from 'classnames';
import layout from '../utils/layout.css';
import icons from '../icons/style.css';
import styles from './style.css';
-export default class EventLinksList extends Component {
- static propTypes = {
- linkList: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
- listType: React.PropTypes.oneOf(['external', 'internal']).isRequired,
- };
+const EventLinksList = ({
+ linkList,
+ listType,
+}) => {
+ if (linkList.length === 0) return ();
+ return (
+
+ );
+};
- render() {
- if (this.props.linkList.length === 0) return null;
- const { listType } = this.props;
+EventLinksList.propTypes = {
+ linkList: PropTypes.arrayOf(PropTypes.shape({
+ url: PropTypes.string,
+ title: PropTypes.string,
+ })).isRequired,
+ listType: React.PropTypes.oneOf(['external', 'internal']).isRequired,
+};
- return (
-
- );
- }
-}
+export default EventLinksList;
diff --git a/src/shared/components/event-meta/index.js b/src/shared/components/event-meta/index.js
new file mode 100644
index 0000000..46f36bb
--- /dev/null
+++ b/src/shared/components/event-meta/index.js
@@ -0,0 +1,46 @@
+import React, { PropTypes } from 'react';
+import styles from './style.css';
+
+import TagsList from '../tags-list';
+import EventLinksList from '../event-links-list';
+
+const EventMeta = ({
+ internalLinks,
+ externalLinks,
+ tags,
+}) => {
+ if (internalLinks.length > 0 && externalLinks.length > 0) {
+ return (
+ {
+ externalLinks || internalLinks ?
+
+
+
+
+ :
+ }
+ {
+ tags
+ ?
+ :
+ }
+
+ );
+ }
+
+ return ();
+};
+
+EventMeta.propTypes = {
+ internalLinks: EventLinksList.propTypes.linkList,
+ externalLinks: EventLinksList.propTypes.linkList,
+ tags: PropTypes.arrayOf(PropTypes.string),
+};
+
+export default EventMeta;
diff --git a/src/shared/components/event-meta/index.spec.js b/src/shared/components/event-meta/index.spec.js
new file mode 100644
index 0000000..4516f2e
--- /dev/null
+++ b/src/shared/components/event-meta/index.spec.js
@@ -0,0 +1,16 @@
+import EventMeta from './index.js';
+import React from 'react';
+import { shallow } from 'enzyme';
+import { expect } from 'chai';
+import { defaultEvent } from '../../../../spec/fixtures/events.js';
+
+describe('EventMeta component', () => {
+ it('renders successfully with default props', () => {
+ const wrapper = shallow();
+ expect(wrapper.find('div').length).to.equal(2);
+ });
+});
diff --git a/src/shared/components/event-meta/style.css b/src/shared/components/event-meta/style.css
new file mode 100644
index 0000000..b30fbca
--- /dev/null
+++ b/src/shared/components/event-meta/style.css
@@ -0,0 +1,4 @@
+.eventLinks {
+ composes: cf from "../../components/utils/layout.css";
+ margin-bottom: 1em;
+}
diff --git a/src/shared/components/event-title/index.js b/src/shared/components/event-title/index.js
new file mode 100644
index 0000000..a87b38d
--- /dev/null
+++ b/src/shared/components/event-title/index.js
@@ -0,0 +1,35 @@
+import React, { PropTypes } from 'react';
+import classNames from 'classnames';
+import icons from '../icons/style.css';
+import styles from './style.css';
+import { h2 } from '../typography/style.css';
+
+const EventTitle = ({
+ eventTitle,
+ eventHref,
+}) => (
+
+
+
+ {eventTitle}
+
+
+
+
+);
+
+EventTitle.propTypes = {
+ eventTitle: PropTypes.string.isRequired,
+ eventHref: PropTypes.string.isRequired,
+};
+
+export default EventTitle;
diff --git a/src/shared/components/event-title/index.spec.js b/src/shared/components/event-title/index.spec.js
new file mode 100644
index 0000000..f37444f
--- /dev/null
+++ b/src/shared/components/event-title/index.spec.js
@@ -0,0 +1,14 @@
+import EventTitle from './index.js';
+import React from 'react';
+import { shallow } from 'enzyme';
+import { expect } from 'chai';
+
+describe('EventTitle component', () => {
+ it('renders successfully with default props', () => {
+ const wrapper = shallow();
+ expect(wrapper.find('h2').length).to.equal(1);
+ });
+});
diff --git a/src/shared/components/event-title/style.css b/src/shared/components/event-title/style.css
new file mode 100644
index 0000000..4708cd8
--- /dev/null
+++ b/src/shared/components/event-title/style.css
@@ -0,0 +1,27 @@
+@value red from "../variables/colors.css";
+
+.eventTitleLink {
+ text-decoration: none;
+}
+
+.eventTitle {
+ font-size: 1.6em;
+ text-decoration: none;
+ display: inline;
+ margin: 0 0 20px 0;
+ float: left;
+ text-align: left;
+}
+
+.eventTitle span {
+ display: inline;
+}
+
+.eventTitle:hover, .eventTitle:active, .eventTitle:focus {
+ color: red;
+}
+
+.arrow {
+ color: red;
+ padding-left: 5px;
+}
diff --git a/src/shared/components/events-list-entry/index.js b/src/shared/components/events-list-entry/index.js
new file mode 100644
index 0000000..e3d468f
--- /dev/null
+++ b/src/shared/components/events-list-entry/index.js
@@ -0,0 +1,66 @@
+import React, { PropTypes } from 'react';
+import styles from './style.css';
+import EventImage from '../event-image';
+import DateBubble from '../date-bubble';
+import EventMeta from '../event-meta';
+import EventTitle from '../event-title';
+import HR from '../hr';
+import { Grid, Cell } from '../grid';
+import { setEndDate, eventImagePath } from '../../util/events';
+import { eventHref } from '../../util/event';
+
+const EventsListEntry = ({
+ event,
+ timeline,
+}) => (
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+ {event.strapline}
+
+
+ |
+
+
+ |
+
+ |
+
+
+);
+
+EventsListEntry.propTypes = {
+ event: PropTypes.object.isRequired,
+ timeline: PropTypes.oneOf(['past', 'future', 'today']).isRequired,
+};
+
+export default EventsListEntry;
diff --git a/src/shared/components/events-list-entry/index.spec.js b/src/shared/components/events-list-entry/index.spec.js
new file mode 100644
index 0000000..fafe545
--- /dev/null
+++ b/src/shared/components/events-list-entry/index.spec.js
@@ -0,0 +1,15 @@
+import EventsListEntry from './index.js';
+import React from 'react';
+import { shallow } from 'enzyme';
+import { expect } from 'chai';
+import { defaultEvent } from '../../../../spec/fixtures/events.js';
+
+describe('EventsListEntry component', () => {
+ it('renders successfully with default props', () => {
+ const wrapper = shallow();
+ expect(wrapper.find('li').length).to.equal(1);
+ });
+});
diff --git a/src/shared/components/events-list-entry/style.css b/src/shared/components/events-list-entry/style.css
new file mode 100644
index 0000000..305f810
--- /dev/null
+++ b/src/shared/components/events-list-entry/style.css
@@ -0,0 +1,15 @@
+.eventItem {
+ position: relative;
+ list-style-type: none;
+}
+
+.mobileHorizontalLine {
+ display: block;
+ margin: 1.5em 0 0 0;
+}
+
+.eventDescription {
+ margin-top: 10px;
+ float: left;
+}
+
diff --git a/src/shared/components/events-list/index.js b/src/shared/components/events-list/index.js
index 2242d37..835610e 100644
--- a/src/shared/components/events-list/index.js
+++ b/src/shared/components/events-list/index.js
@@ -2,134 +2,50 @@
// You can request only displaying events of past or future
// with the `timeline` prop
-/* eslint-disable max-len */
-
-import React, { Component } from 'react';
+import React, { PropTypes } from 'react';
import styles from './style.css';
+import EventsTimelineTitle from '../events-timeline-title';
+import EventsListEntry from '../events-list-entry';
+import { splitEvents } from '../../util/events';
+
+const EventsList = ({
+ events,
+ timeline,
+}) => {
+ const relevantEvents =
+ timeline
+ ? splitEvents({
+ events,
+ timeline,
+ reverse: timeline === 'future',
+ })
+ : events;
+
+ if (relevantEvents.length > 0) {
+ return (
+
+
+
+ {
+ relevantEvents.map((event) => (
+
+ ))
+ }
+
+
+ );
+ }
-import EventImage from '../event-image';
-import { imageAssetsEndpoint } from '../../config';
-import DateBubble from '../date-bubble';
-import HR from '../hr';
-import { Grid, Cell } from '../grid';
-import classNames from 'classnames';
-import icons from '../icons/style.css';
-
-import TagsList from '../tags-list';
-import EventLinksList from '../event-links-list';
-import { eventHref } from '../../util/event';
-import { splitEvents } from '../../util/split-events';
-
-export default class EventsList extends Component {
- static propTypes = {
- events: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
- timeline: React.PropTypes.oneOf(['past', 'future', 'today']),
- };
-
- render() {
- const relevantEvents =
- this.props.timeline
- ? splitEvents(
- this.props.events,
- this.props.timeline,
- { reverse: this.props.timeline === 'future' }
- )
- : this.props.events;
-
+ return ();
+};
- if (relevantEvents.length > 0) {
- return (
-
- {(() => {
- switch (this.props.timeline) {
- case 'past':
- return (
Past events
);
- case 'future':
- return (
Upcoming events
);
- case 'today':
- return (
Today
);
- default:
- return null;
- }
- })()}
-
- {
- relevantEvents.map((event) => (
- -
-
-
-
-
- |
-
-
- |
-
-
-
-
-
-
-
- {event.title}
-
-
-
-
-
- {event.strapline}
-
- {
- event.externalLinks || event.internalLinks ?
-
- {
- event.externalLinks ?
-
- : null
- }
- {
- event.internalLinks ?
-
- : null
- }
-
- : null
- }
- {
- event.tags
- ?
- : null
- }
- |
-
-
- |
-
- |
-
-
- ))
- }
-
-
- );
- }
+EventsList.propTypes = {
+ events: PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ timeline: EventsTimelineTitle.propTypes.timeline,
+};
- return null;
- }
-}
+export default EventsList;
diff --git a/src/shared/components/events-list/index.spec.js b/src/shared/components/events-list/index.spec.js
index e69de29..914e135 100644
--- a/src/shared/components/events-list/index.spec.js
+++ b/src/shared/components/events-list/index.spec.js
@@ -0,0 +1,17 @@
+import EventsList from './index.js';
+import React from 'react';
+import { shallow } from 'enzyme';
+import { expect } from 'chai';
+import { defaultEvent } from '../../../../spec/fixtures/events.js';
+
+describe('EventsList component', () => {
+ it('renders successfully with default props', () => {
+ const wrapper = shallow();
+ expect(wrapper.find('ul').length).to.equal(1);
+ });
+});
diff --git a/src/shared/components/events-list/style.css b/src/shared/components/events-list/style.css
index 643bf7f..67adf29 100644
--- a/src/shared/components/events-list/style.css
+++ b/src/shared/components/events-list/style.css
@@ -14,71 +14,18 @@
text-align: center;
}
-.eventsListTimelineSection h2.eventTitle {
- text-align: left;
-}
-
.eventsList {
font-size: 1em;
margin-top: 0;
padding-left: 0;
}
-.eventItem {
- position: relative;
- list-style-type: none;
-}
-
-.eventTitleLink {
- text-decoration: none;
-}
-
-.eventTitle {
- composes: h2 from "../../components/typography/style.css";
- font-size: 1.6em;
- text-decoration: none;
- display: inline;
- margin: 0 0 20px 0;
- float: left;
-}
-
-.eventTitle span {
- display: inline;
-}
-
-.eventTitle:hover, .eventTitle:active, .eventTitle:focus {
- color: red;
-}
-
.arrow {
color: red;
padding-left: 5px;
}
-.eventDescription {
- margin-top: 10px;
- float: left;
-}
-
-.eventLinks {
- composes: cf from "../../components/utils/layout.css";
- margin-bottom: 1em;
-}
-
-.mobileHorizontalLine {
- display: none;
- margin: 1.5em 0 0 0;
-}
-
@media mobile {
- .wideHorizontalLine {
- display: none;
- }
-
- .mobileHorizontalLine {
- display: block;
- }
-
.eventsList {
padding: 0;
}
diff --git a/src/shared/components/events-timeline-title/index.js b/src/shared/components/events-timeline-title/index.js
new file mode 100644
index 0000000..593200e
--- /dev/null
+++ b/src/shared/components/events-timeline-title/index.js
@@ -0,0 +1,22 @@
+import React, { PropTypes } from 'react';
+
+const EventsTimelineTitle = ({
+ timeline,
+}) => {
+ switch (timeline) {
+ case 'past':
+ return (Past events
);
+ case 'future':
+ return (Upcoming events
);
+ case 'today':
+ return (Today
);
+ default:
+ return null;
+ }
+};
+
+EventsTimelineTitle.propTypes = {
+ timeline: PropTypes.oneOf(['past', 'future', 'today']).isRequired,
+};
+
+export default EventsTimelineTitle;
diff --git a/src/shared/components/events-timeline-title/index.spec.js b/src/shared/components/events-timeline-title/index.spec.js
new file mode 100644
index 0000000..21bb5ce
--- /dev/null
+++ b/src/shared/components/events-timeline-title/index.spec.js
@@ -0,0 +1,29 @@
+import EventsTimelineTitle from './index.js';
+import React from 'react';
+import { shallow } from 'enzyme';
+import { expect } from 'chai';
+
+describe('EventsTimelineTitle component', () => {
+ it('renders successfully with today timeline provided', () => {
+ const wrapper = shallow();
+ expect(wrapper.find('h2').length).to.equal(1);
+ expect(wrapper.find('h2').text()).to.equal('Today');
+ });
+
+ it('renders successfully with future timeline provided', () => {
+ const wrapper = shallow();
+ expect(wrapper.find('h2').length).to.equal(1);
+ expect(wrapper.find('h2').text()).to.equal('Upcoming events');
+ });
+
+ it('renders successfully with past timeline provided', () => {
+ const wrapper = shallow();
+ expect(wrapper.find('h2').length).to.equal(1);
+ expect(wrapper.find('h2').text()).to.equal('Past events');
+ });
+
+ it('does not return a result if not passed a timeline proeprty', () => {
+ const wrapper = shallow();
+ expect(wrapper.find('h2').length).to.equal(0);
+ });
+});
diff --git a/src/shared/containers/event/index.js b/src/shared/containers/event/index.js
index 96771c9..d7d2951 100644
--- a/src/shared/containers/event/index.js
+++ b/src/shared/containers/event/index.js
@@ -12,9 +12,9 @@ import HR from '../../components/hr';
import { Grid, Cell } from '../../components/grid';
import DateBubble from '../../components/date-bubble';
import EventsSideList from '../../components/events-side-list';
-import EventLinksList from '../../components/event-links-list';
-import TagsList from '../../components/tags-list';
-import { splitEvents } from '../../util/split-events';
+import EventMeta from '../../components/event-meta';
+
+import { splitEvents, setEndDate } from '../../util/events';
import marked from 'marked';
import Helmet from 'react-helmet';
@@ -24,8 +24,15 @@ export class Event extends Component {
render() {
const { event, events } = this.props;
- let futureEvents = splitEvents(events, 'future', { reverse: true });
- let todayEvents = splitEvents(events, 'today');
+ const futureEvents = splitEvents({
+ events,
+ timeline: 'future',
+ reverse: true,
+ });
+ const todayEvents = splitEvents({
+ events,
+ timeline: 'today',
+ });
return (
@@ -34,19 +41,20 @@ export class Event extends Component {
-
+
|
-
-
+
{event.title}
@@ -62,35 +70,11 @@ export class Event extends Component {
)
}
- {
- event.externalLinks || event.internalLinks ?
-
- {
- event.externalLinks ?
-
- : null
- }
- {
- event.internalLinks ?
-
- : null
- }
-
- : null
- }
- {
- event.tags.length ? (
-
-
-
- ) : null
- }
+
|
|
diff --git a/src/shared/util/event.js b/src/shared/util/event.js
index f582c8f..7ccab4a 100644
--- a/src/shared/util/event.js
+++ b/src/shared/util/event.js
@@ -1,4 +1,4 @@
/* eslint-disable max-len */
export function eventHref(event) {
- return `/about-us/events/${event.datetime.year}/${event.datetime.month}/${event.datetime.date}/${event.slug}`;
+ return `/about-us/events/${event.startDateTime.year}/${event.startDateTime.month}/${event.startDateTime.date}/${event.slug}`;
}
diff --git a/src/shared/util/events.js b/src/shared/util/events.js
new file mode 100644
index 0000000..039551d
--- /dev/null
+++ b/src/shared/util/events.js
@@ -0,0 +1,60 @@
+import dateFns from 'date-fns';
+import { imageAssetsEndpoint } from '../config';
+
+export function splitEvents({
+ events, // array of events
+ timeline, // one of 'past', 'today', 'future'
+ reverse = false, // optionally reverse final list of events
+ todayDateTime = new Date(), // iso string representing today date
+}) {
+ let relevantEvents = events.filter(event => {
+ const startDateTime = dateFns.parse(event.startDateTime.iso);
+ const endDateTime = dateFns.parse(event.endDateTime.iso);
+
+ // In a rare case of user error we omit this event from the output list
+ if ((!dateFns.isSameDay(startDateTime, endDateTime) &&
+ !dateFns.isBefore(startDateTime, endDateTime)) ||
+ dateFns.differenceInMinutes(endDateTime, startDateTime) < 0) {
+ return false;
+ }
+
+ switch (timeline) {
+ case 'today':
+ if (dateFns.isSameDay(startDateTime, endDateTime)) {
+ return dateFns.isSameDay(startDateTime, todayDateTime);
+ } else { // eslint-disable-line no-else-return
+ return dateFns.isWithinRange(todayDateTime, startDateTime, endDateTime);
+ }
+ case 'past':
+ return (!dateFns.isWithinRange(todayDateTime, startDateTime, endDateTime) && dateFns.isBefore(endDateTime, todayDateTime) && !dateFns.isSameDay(endDateTime, todayDateTime));
+ case 'future':
+ return (!dateFns.isSameDay(startDateTime, todayDateTime) &&
+ dateFns.isAfter(startDateTime, todayDateTime));
+ default:
+ return false;
+ }
+ }, this);
+
+ if (relevantEvents.length > 1 && reverse === true) {
+ relevantEvents = relevantEvents.reverse();
+ }
+
+ return relevantEvents;
+}
+
+// Helper function for displaying date range for multi day events
+// Date range would only be displayed if end date is set on the
+// child component
+export function setEndDate(timeline, startDateTime, endDateTime) {
+ if (timeline === 'today' &&
+ startDateTime.date !== endDateTime.date) {
+ return endDateTime;
+ }
+ return null;
+}
+
+export function eventImagePath(
+ featureImageFilename) {
+ const f = featureImageFilename || 'red-badger-event.jpg';
+ return imageAssetsEndpoint + f;
+}
diff --git a/src/shared/util/events.spec.js b/src/shared/util/events.spec.js
new file mode 100644
index 0000000..b50747b
--- /dev/null
+++ b/src/shared/util/events.spec.js
@@ -0,0 +1,367 @@
+/* eslint-disable no-multi-str */
+/* eslint-disable no-unused-expressions */
+/* eslint-disable no-unused-vars */
+
+import { splitEvents, setEndDate, eventImagePath } from './events';
+import { expect } from 'chai';
+import { imageAssetsEndpoint } from '../config';
+import * as MockDate from 'mockdate';
+
+let testEventsSingleDay = [];
+let testEventsMultiDay = [];
+let testEventsMultiDayInProgress = [];
+let testErrorMultiDay = [];
+let testEventsSingleDayWrongStartEndTime = [];
+let currentDate;
+
+describe('Set end date', () => {
+ const startDateTime = {
+ date: 5,
+ };
+ const endDateTime = {
+ date: 8,
+ };
+
+ it('returns end date when timeline is set to today \
+ and start date is different', () => {
+ expect(setEndDate('today', startDateTime, endDateTime))
+ .to.deep.equal(endDateTime);
+ });
+
+ it('returns null when timeline is not set to today \
+ and start date is different', () => {
+ expect(setEndDate('past', startDateTime, endDateTime)).to.be.null;
+ });
+
+ it('returns null when timeline is set to today \
+ and start date is same', () => {
+ const endDateTimeToday = {
+ date: 5,
+ };
+ expect(setEndDate('past', startDateTime, endDateTimeToday)).to.be.null;
+ });
+});
+
+describe('Event image path', () => {
+ it('returns full image path when image filename is provided', () => {
+ expect(eventImagePath('hi.jpg')).to.equal('//res.cloudinary.com/red-badger-assets/image/upload/events/hi.jpg');
+ });
+ it('returns default image path when image filename is not provided', () => {
+ expect(eventImagePath()).to.equal('//res.cloudinary.com/red-badger-assets/image/upload/events/red-badger-event.jpg');
+ });
+ it('returns default image path when image filename is null', () => {
+ expect(eventImagePath(null)).to.equal('//res.cloudinary.com/red-badger-assets/image/upload/events/red-badger-event.jpg');
+ });
+});
+
+describe('SplitEvents', () => {
+ beforeEach(() => {
+ MockDate.set('Thu Jun 23 2016 14:10:56 GMT+0100 (BST)');
+
+ currentDate = new Date();
+
+ const laterToday = new Date();
+ laterToday.setHours(currentDate.getHours() + 1);
+
+ const earlierToday = new Date();
+ earlierToday.setHours(currentDate.getHours() - 1);
+
+ const futureDate = new Date();
+ futureDate.setDate(futureDate.getDate() + 5);
+
+ const previousDate = new Date();
+ previousDate.setDate(previousDate.getDate() - 5);
+
+ const threeDaysAgo = new Date();
+ threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
+
+ const yesterday = new Date();
+ yesterday.setDate(yesterday.getDate() - 1);
+
+ const tomorrow = new Date();
+ tomorrow.setDate(tomorrow.getDate() + 1);
+
+ testEventsMultiDay = [
+ {
+ id: 'past-multi-day-event',
+ startDateTime: {
+ iso: previousDate,
+ },
+ endDateTime: {
+ iso: threeDaysAgo,
+ },
+ },
+ {
+ id: 'today-multi-day-event',
+ startDateTime: {
+ iso: currentDate,
+ },
+ endDateTime: {
+ iso: futureDate,
+ },
+ },
+ {
+ id: 'future-multi-day-event',
+ startDateTime: {
+ iso: tomorrow,
+ },
+ endDateTime: {
+ iso: futureDate,
+ },
+ },
+ ];
+
+ testErrorMultiDay = [
+ {
+ id: 'today-multi-day-event',
+ startDateTime: {
+ iso: tomorrow,
+ },
+ endDateTime: {
+ iso: yesterday,
+ },
+ },
+ ];
+
+ testEventsMultiDayInProgress = [
+ {
+ id: 'past-multi-day-event',
+ startDateTime: {
+ iso: previousDate,
+ },
+ endDateTime: {
+ iso: threeDaysAgo,
+ },
+ },
+ {
+ id: 'yesterday-tomorrow-multi-day-event',
+ startDateTime: {
+ iso: yesterday,
+ },
+ endDateTime: {
+ iso: futureDate,
+ },
+ },
+ ];
+
+ testEventsSingleDay = [
+ {
+ id: 'past-event-1',
+ startDateTime: {
+ iso: previousDate,
+ },
+ endDateTime: {
+ iso: previousDate,
+ },
+ },
+ {
+ id: 'past-event-2',
+ startDateTime: {
+ iso: previousDate,
+ },
+ endDateTime: {
+ iso: previousDate,
+ },
+ },
+ {
+ id: 'today-event-1',
+ startDateTime: {
+ iso: currentDate,
+ },
+ endDateTime: {
+ iso: currentDate,
+ },
+ },
+ {
+ id: 'today-event-2',
+ startDateTime: {
+ iso: currentDate,
+ },
+ endDateTime: {
+ iso: currentDate,
+ },
+ },
+ {
+ id: 'later-today-event',
+ startDateTime: {
+ iso: laterToday,
+ },
+ endDateTime: {
+ iso: laterToday,
+ },
+ },
+ {
+ id: 'earlier-today-event',
+ startDateTime: {
+ iso: earlierToday,
+ },
+ endDateTime: {
+ iso: earlierToday,
+ },
+ },
+ {
+ id: 'future-event-1',
+ startDateTime: {
+ iso: futureDate,
+ },
+ endDateTime: {
+ iso: futureDate,
+ },
+ },
+ {
+ id: 'future-event-2',
+ startDateTime: {
+ iso: futureDate,
+ },
+ endDateTime: {
+ iso: futureDate,
+ },
+ },
+ ];
+
+ testEventsSingleDayWrongStartEndTime = [
+ {
+ id: 'earlier-event',
+ startDateTime: {
+ iso: earlierToday,
+ },
+ endDateTime: {
+ iso: laterToday,
+ },
+ },
+ {
+ id: 'later-today-event',
+ startDateTime: {
+ iso: laterToday,
+ },
+ endDateTime: {
+ iso: earlierToday,
+ },
+ }];
+ });
+
+
+ afterEach(() => {
+ MockDate.reset();
+ });
+
+ describe('multi day events', () => {
+ it('omits events with end date earlier then start date from final list', () => {
+ const timeline = 'today';
+ const returnedEvents = splitEvents({
+ events: testErrorMultiDay,
+ timeline,
+ todayDateTime: currentDate,
+ });
+ expect(returnedEvents.length).to.equal(0);
+ });
+
+ it('returns todays events correctly when event start date is today and end date is tomorrow', () => {
+ const timeline = 'today';
+ const returnedEvents = splitEvents({
+ events: testEventsMultiDay,
+ timeline,
+ todayDateTime: currentDate,
+ });
+ expect(returnedEvents.length).to.equal(1);
+ expect(returnedEvents[0].id).to.equal('today-multi-day-event');
+ });
+
+ it('returns todays events correctly when event start date was yesterday and end date is tomorrow', () => {
+ const timeline = 'today';
+ const returnedEvents = splitEvents({
+ events: testEventsMultiDayInProgress,
+ timeline,
+ todayDateTime: currentDate,
+ });
+ expect(returnedEvents.length).to.equal(1);
+ expect(returnedEvents[0].id).to.equal('yesterday-tomorrow-multi-day-event');
+ });
+
+ it('returns future events correctly for upcoming multiday events', () => {
+ const timeline = 'future';
+ const returnedEvents = splitEvents({
+ events: testEventsMultiDay,
+ timeline,
+ todayDateTime: currentDate,
+ });
+ expect(returnedEvents.length).to.equal(1);
+ expect(returnedEvents[0].id).to.equal('future-multi-day-event');
+ });
+
+ it('returns past events correctly for multiday events', () => {
+ const timeline = 'past';
+ const returnedEvents = splitEvents({
+ events: testEventsMultiDay,
+ timeline,
+ todayDateTime: currentDate,
+ });
+ expect(returnedEvents.length).to.equal(1);
+ expect(returnedEvents[0].id).to.equal('past-multi-day-event');
+ });
+ });
+
+ describe('single day events', () => {
+ it('returns future events', () => {
+ const timeline = 'future';
+ const returnedEvents = splitEvents({
+ events: testEventsSingleDay,
+ timeline,
+ todayDateTime: currentDate,
+ });
+ expect(returnedEvents.length).to.equal(2);
+ expect(returnedEvents[0].id).to.equal('future-event-1');
+ expect(returnedEvents[1].id).to.equal('future-event-2');
+ });
+
+ it('returns past events', () => {
+ const timeline = 'past';
+ const returnedEvents = splitEvents({
+ events: testEventsSingleDay,
+ timeline,
+ todayDateTime: currentDate,
+ });
+ expect(returnedEvents.length).to.equal(2);
+ expect(returnedEvents[0].id).to.equal('past-event-1');
+ expect(returnedEvents[1].id).to.equal('past-event-2');
+ });
+
+ it('returns todays events', () => {
+ const timeline = 'today';
+ const returnedEvents = splitEvents({
+ events: testEventsSingleDay,
+ timeline,
+ todayDateTime: currentDate,
+ });
+ expect(returnedEvents.length).to.equal(4);
+ expect(returnedEvents[0].id).to.equal('today-event-1');
+ expect(returnedEvents[1].id).to.equal('today-event-2');
+ expect(returnedEvents[2].id).to.equal('later-today-event');
+ expect(returnedEvents[3].id).to.equal('earlier-today-event');
+ });
+
+ it('returns events in reverse order when specified', () => {
+ const timeline = 'future';
+ const returnedEvents = splitEvents({
+ events: testEventsSingleDay,
+ timeline,
+ todayDateTime: currentDate,
+ reverse: true,
+ });
+ expect(returnedEvents.length).to.equal(2);
+ expect(returnedEvents[0].id).to.equal('future-event-2');
+ expect(returnedEvents[1].id).to.equal('future-event-1');
+ });
+
+ it('omits event from the result if end time is earlier than start time', () => {
+ const timeline = 'today';
+ const returnedEvents = splitEvents({
+ events: testEventsSingleDayWrongStartEndTime,
+ timeline,
+ todayDateTime: currentDate,
+ });
+ expect(returnedEvents.length).to.equal(1);
+ expect(returnedEvents[0].id).to.equal('earlier-event');
+ });
+ });
+});
diff --git a/src/shared/util/split-events.js b/src/shared/util/split-events.js
deleted file mode 100644
index 622bc6b..0000000
--- a/src/shared/util/split-events.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import dateFns from 'date-fns';
-
-export function splitEvents(events, timeline, { reverse = false } = {}) {
- let relevantEvents = events.filter(event => {
- const d = dateFns.parse(event.datetime.iso);
- switch (timeline) {
- case 'today':
- return dateFns.isToday(d);
- case 'past':
- return (dateFns.isPast(d) && !dateFns.isToday(d));
- default:
- return (dateFns.isFuture(d) && !dateFns.isToday(d));
- }
- }, this);
-
- if (relevantEvents.length > 1 && reverse === true) {
- relevantEvents = relevantEvents.reverse();
- }
- return relevantEvents;
-}
diff --git a/src/shared/util/split-events.spec.js b/src/shared/util/split-events.spec.js
deleted file mode 100644
index 5cfb2b6..0000000
--- a/src/shared/util/split-events.spec.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import { splitEvents } from './split-events';
-import { expect } from 'chai';
-import * as MockDate from 'mockdate';
-
-let testevents = [];
-
-describe('SplitEvents', () => {
- beforeEach(() => {
- MockDate.set('Thu Jun 23 2016 14:10:56 GMT+0100 (BST)');
-
- const currentDate = new Date();
-
- const laterToday = new Date();
- laterToday.setHours(currentDate.getHours() + 1);
-
- const earlierToday = new Date();
- earlierToday.setHours(currentDate.getHours() - 1);
-
- const futureDate = new Date();
- futureDate.setDate(futureDate.getDate() + 5);
-
- const previousDate = new Date();
- previousDate.setDate(previousDate.getDate() - 5);
-
- testevents = [
- {
- id: 'past-event-1',
- datetime: {
- iso: previousDate,
- },
- },
- {
- id: 'past-event-2',
- datetime: {
- iso: previousDate,
- },
- },
- {
- id: 'today-event-1',
- datetime: {
- iso: currentDate,
- },
- },
- {
- id: 'today-event-2',
- datetime: {
- iso: currentDate,
- },
- },
- {
- id: 'later-today-event',
- datetime: {
- iso: laterToday,
- },
- },
- {
- id: 'earlier-today-event',
- datetime: {
- iso: earlierToday,
- },
- },
- {
- id: 'future-event-1',
- datetime: {
- iso: futureDate,
- },
- },
- {
- id: 'future-event-2',
- datetime: {
- iso: futureDate,
- },
- },
- ];
- });
-
- afterEach(() => {
- MockDate.reset();
- });
-
- it('returns future events', () => {
- const timeline = 'future';
- const returnedEvents = splitEvents(testevents, timeline);
- expect(returnedEvents.length).to.equal(2);
- expect(returnedEvents[0].id).to.equal('future-event-1');
- expect(returnedEvents[1].id).to.equal('future-event-2');
- });
-
- it('returns past events', () => {
- const timeline = 'past';
- const returnedEvents = splitEvents(testevents, timeline);
- expect(returnedEvents.length).to.equal(2);
- expect(returnedEvents[0].id).to.equal('past-event-1');
- expect(returnedEvents[1].id).to.equal('past-event-2');
- });
-
- it('returns todays events', () => {
- const timeline = 'today';
- const returnedEvents = splitEvents(testevents, timeline);
- expect(returnedEvents.length).to.equal(4);
- expect(returnedEvents[0].id).to.equal('today-event-1');
- expect(returnedEvents[1].id).to.equal('today-event-2');
- expect(returnedEvents[2].id).to.equal('later-today-event');
- expect(returnedEvents[3].id).to.equal('earlier-today-event');
- });
-
- it('returns events in reverse order when specified', () => {
- const timeline = 'future';
- const returnedEvents = splitEvents(testevents, timeline, { reverse: true });
- expect(returnedEvents.length).to.equal(2);
- expect(returnedEvents[0].id).to.equal('future-event-2');
- expect(returnedEvents[1].id).to.equal('future-event-1');
- });
-});
| |