diff --git a/src/components/navigation/www/navigation.jsx b/src/components/navigation/www/navigation.jsx
index 515b29b82..9ed2e3cb4 100644
--- a/src/components/navigation/www/navigation.jsx
+++ b/src/components/navigation/www/navigation.jsx
@@ -27,7 +27,8 @@ class Navigation extends React.Component {
super(props);
bindAll(this, [
'getProfileUrl',
- 'handleSearchSubmit'
+ 'handleSearchSubmit',
+ 'setupMessagePolling'
]);
this.state = {
messageCountIntervalId: -1 // javascript method interval id for getting messsage count.
@@ -35,27 +36,18 @@ class Navigation extends React.Component {
}
componentDidMount () {
if (this.props.user) {
- const intervalId = setInterval(() => {
- this.props.getMessageCount(this.props.user.username);
- }, 120000); // check for new messages every 2 mins.
- this.setState({ // eslint-disable-line react/no-did-mount-set-state
- messageCountIntervalId: intervalId
- });
+ // Setup polling for messages to start in 2 minutes.
+ setTimeout(this.setupMessagePolling.bind(this, 2), 2 * 60 * 1000);
}
}
componentDidUpdate (prevProps) {
if (prevProps.user !== this.props.user) {
this.props.handleCloseAccountNav();
if (this.props.user) {
- const intervalId = setInterval(() => {
- this.props.getMessageCount(this.props.user.username);
- }, 120000); // check for new messages every 2 mins.
- this.setState({ // eslint-disable-line react/no-did-update-set-state
- messageCountIntervalId: intervalId
- });
+ setTimeout(this.setupMessagePolling.bind(this, 2), 2 * 60 * 1000);
} else {
// clear message count check, and set to default id.
- clearInterval(this.state.messageCountIntervalId);
+ clearTimeout(this.state.messageCountIntervalId);
this.props.setMessageCount(0);
this.setState({ // eslint-disable-line react/no-did-update-set-state
messageCountIntervalId: -1
@@ -66,7 +58,7 @@ class Navigation extends React.Component {
componentWillUnmount () {
// clear message interval if it exists
if (this.state.messageCountIntervalId !== -1) {
- clearInterval(this.state.messageCountIntervalId);
+ clearTimeout(this.state.messageCountIntervalId);
this.props.setMessageCount(0);
this.setState({
messageCountIntervalId: -1
@@ -77,6 +69,23 @@ class Navigation extends React.Component {
if (!this.props.user) return;
return `/users/${this.props.user.username}/`;
}
+
+ setupMessagePolling (minutes) {
+ this.props.getMessageCount(this.props.user.username);
+ // We only poll if it has been less than 30 minutes.
+ // Chances of someone actively using the page for that long without
+ // a navigation is low.
+ if (minutes < 32) {
+ const nextFetch = minutes * 2;
+ const timeoutId = setTimeout(() => {
+ this.setupMessagePolling(nextFetch);
+ }, nextFetch * 60000);
+ this.setState({ // eslint-disable-line react/no-did-mount-set-state
+ messageCountIntervalId: timeoutId
+ });
+ }
+ }
+
handleSearchSubmit (formData) {
let targetUrl = '/search/projects';
if (formData.q) {
diff --git a/test/unit/components/navigation.test.jsx b/test/unit/components/navigation.test.jsx
index 9a9547a9a..e74bf125b 100644
--- a/test/unit/components/navigation.test.jsx
+++ b/test/unit/components/navigation.test.jsx
@@ -1,5 +1,5 @@
const React = require('react');
-const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
+const {shallowWithIntl, mountWithIntl} = require('../../helpers/intl-helpers.jsx');
import configureStore from 'redux-mock-store';
const Navigation = require('../../../src/components/navigation/www/navigation.jsx');
const Registration = require('../../../src/components/registration/registration.jsx');
@@ -11,6 +11,7 @@ describe('Navigation', () => {
beforeEach(() => {
store = null;
+ jest.useFakeTimers();
});
const getNavigationWrapper = props => {
@@ -103,4 +104,88 @@ describe('Navigation', () => {
navWrapper.find('a.registrationLink').simulate('click', {preventDefault () {}});
expect(navInstance.props.handleClickRegistration).toHaveBeenCalled();
});
+
+ test('Component sets up message polling when it mounts', () => {
+ store = mockStore({
+ navigation: {
+ registrationOpen: false
+ },
+ messageCount: {
+ messageCount: 5
+ }
+ });
+ const props = {
+ user: {
+ thumbnailUrl: 'scratch.mit.edu',
+ username: 'auser'
+ },
+ getMessageCount: jest.fn()
+ };
+ const intlWrapper = mountWithIntl(
+ , {context: {store},
+ childContextTypes: {store}
+ });
+
+ intlWrapper.children().find('Navigation')
+ .instance();
+ const twoMin = 2 * 60 * 1000;
+ expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), twoMin);
+ // Advance timers passed the intial two minutes.
+ jest.advanceTimersByTime(twoMin + 1);
+ expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), twoMin * 2);
+ expect(props.getMessageCount).toHaveBeenCalled();
+ });
+
+ test('SetupMessagePolling polls for messages 5 times', () => {
+ store = mockStore({
+ navigation: {
+ registrationOpen: false
+ },
+ messageCount: {
+ messageCount: 5
+ }
+ });
+ const props = {
+ user: {
+ thumbnailUrl: 'scratch.mit.edu',
+ username: 'auser'
+ },
+ getMessageCount: jest.fn()
+ };
+ const intlWrapper = mountWithIntl(
+ , {context: {store},
+ childContextTypes: {store}
+ });
+
+ const navInstance = intlWrapper.children().find('Navigation')
+ .instance();
+ // Clear the timers and mocks because componentDidMount
+ // has already called setupMessagePolling.
+ jest.clearAllTimers();
+ jest.clearAllMocks();
+
+ navInstance.setupMessagePolling(2);
+
+ // Check that we set the timeout to backoff exponentially
+ let minutes = 2 * 60 * 1000;
+ for (let count = 1; count < 5; ++count) {
+ jest.advanceTimersByTime(minutes + 1);
+ expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), minutes * 2);
+ expect(props.getMessageCount).toHaveBeenCalledTimes(count);
+ minutes = minutes * 2;
+ }
+
+ // Exhaust all timers (there shouldn't be any left)
+ jest.runAllTimers();
+ // We exponentially back off checking for messages, starting at 2 min
+ // and stop after 32 minutes so it should happen 5 times total.
+ expect(props.getMessageCount).toHaveBeenCalledTimes(5);
+ // setTimeout happens 1 fewer since the original call to
+ // setupMessagePolling isn't counted here.
+ expect(setTimeout).toHaveBeenCalledTimes(4);
+ });
});