@financial-times/n-myft-ui 40.0.2 → 40.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +0 -10
- package/.toolkitrc.yml +0 -1
- package/.toolkitstate/ci.json +2 -2
- package/package.json +2 -2
- package/components/unread-articles-indicator/README.md +0 -5
- package/components/unread-articles-indicator/date-fns.js +0 -25
- package/components/unread-articles-indicator/device-session.js +0 -19
- package/components/unread-articles-indicator/index.js +0 -31
- package/components/unread-articles-indicator/initialise-feed-start-time.js +0 -54
- package/components/unread-articles-indicator/storage.js +0 -35
- package/test/unread-articles-indicator/device-session.spec.js +0 -97
- package/test/unread-articles-indicator/index.spec.js +0 -43
- package/test/unread-articles-indicator/initialise-feed-start-time.spec.js +0 -178
- package/test/unread-articles-indicator/storage.spec.js +0 -142
package/.circleci/config.yml
CHANGED
@@ -3,10 +3,6 @@ orbs:
|
|
3
3
|
tool-kit: financial-times/dotcom-tool-kit@5
|
4
4
|
executors:
|
5
5
|
node:
|
6
|
-
docker:
|
7
|
-
- image: cimg/node:18.20-browsers
|
8
|
-
working_directory: /home/circleci/project/node_workspace
|
9
|
-
node22_4-browsers:
|
10
6
|
docker:
|
11
7
|
- image: cimg/node:22.4-browsers
|
12
8
|
working_directory: /home/circleci/project/node22_workspace
|
@@ -63,7 +59,6 @@ workflows:
|
|
63
59
|
parameters:
|
64
60
|
executor:
|
65
61
|
- node
|
66
|
-
- node22_4-browsers
|
67
62
|
filters:
|
68
63
|
tags:
|
69
64
|
only: /^v\d+\.\d+\.\d+(-.+)?/
|
@@ -75,7 +70,6 @@ workflows:
|
|
75
70
|
parameters:
|
76
71
|
executor:
|
77
72
|
- node
|
78
|
-
- node22_4-browsers
|
79
73
|
filters:
|
80
74
|
tags:
|
81
75
|
only: /^v\d+\.\d+\.\d+(-.+)?/
|
@@ -87,7 +81,6 @@ workflows:
|
|
87
81
|
parameters:
|
88
82
|
executor:
|
89
83
|
- node
|
90
|
-
- node22_4-browsers
|
91
84
|
filters:
|
92
85
|
tags:
|
93
86
|
only: /^v\d+\.\d+\.\d+(-.+)?/
|
@@ -122,7 +115,6 @@ workflows:
|
|
122
115
|
parameters:
|
123
116
|
executor:
|
124
117
|
- node
|
125
|
-
- node22_4-browsers
|
126
118
|
- tool-kit/build:
|
127
119
|
name: tool-kit/build-<< matrix.executor >>
|
128
120
|
requires:
|
@@ -131,7 +123,6 @@ workflows:
|
|
131
123
|
parameters:
|
132
124
|
executor:
|
133
125
|
- node
|
134
|
-
- node22_4-browsers
|
135
126
|
- tool-kit/test:
|
136
127
|
name: tool-kit/test-<< matrix.executor >>
|
137
128
|
requires:
|
@@ -140,4 +131,3 @@ workflows:
|
|
140
131
|
parameters:
|
141
132
|
executor:
|
142
133
|
- node
|
143
|
-
- node22_4-browsers
|
package/.toolkitrc.yml
CHANGED
package/.toolkitstate/ci.json
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@financial-times/n-myft-ui",
|
3
|
-
"version": "40.0
|
3
|
+
"version": "40.1.0",
|
4
4
|
"description": "Client side component for interaction with myft",
|
5
5
|
"main": "server.js",
|
6
6
|
"scripts": {
|
@@ -133,7 +133,7 @@
|
|
133
133
|
"node": "22.11.0"
|
134
134
|
},
|
135
135
|
"engines": {
|
136
|
-
"node": "
|
136
|
+
"node": "22.x"
|
137
137
|
},
|
138
138
|
"x-dash": {
|
139
139
|
"engine": {
|
@@ -1,5 +0,0 @@
|
|
1
|
-
# n-myft-ui/unread-articles-indicator
|
2
|
-
|
3
|
-
Note: this component is decommissioned but a small function (`getNewArticlesSinceTime`) of it is still in use by next-myft-page to display a "New" label under an article in the Timeline view only.
|
4
|
-
|
5
|
-
All other previous functionality of the component has been removed.
|
@@ -1,25 +0,0 @@
|
|
1
|
-
// date-fns from v2 doesn't accept String arguments anymore.
|
2
|
-
// the detail => https://github.com/date-fns/date-fns/blob/HEAD/CHANGELOG.md#200---2019-08-20
|
3
|
-
// By adding validation for dates before their functions allows us to know it when unexpected value passed.
|
4
|
-
|
5
|
-
import isTodayOriginal from 'date-fns/src/isToday';
|
6
|
-
import isAfterOriginal from 'date-fns/src/isAfter';
|
7
|
-
import addMinutesOriginal from 'date-fns/src/addMinutes';
|
8
|
-
import startOfDayOriginal from 'date-fns/src/startOfDay';
|
9
|
-
import isValidOriginal from 'date-fns/src/isValid';
|
10
|
-
import parseISO from 'date-fns/src/parseISO';
|
11
|
-
|
12
|
-
const isValid = (date) => {
|
13
|
-
if (!isValidOriginal(date)) {
|
14
|
-
console.error("Invalid date passed", [date]); //eslint-disable-line
|
15
|
-
}
|
16
|
-
return date;
|
17
|
-
};
|
18
|
-
|
19
|
-
const isToday = (date) => isTodayOriginal(isValid(date));
|
20
|
-
const isAfter = (date, dateToCompare) =>
|
21
|
-
isAfterOriginal(isValid(date), isValid(dateToCompare));
|
22
|
-
const addMinutes = (date, amount) => addMinutesOriginal(isValid(date), amount);
|
23
|
-
const startOfDay = (date) => startOfDayOriginal(isValid(date));
|
24
|
-
|
25
|
-
export { isToday, isAfter, addMinutes, startOfDay, isValid, parseISO };
|
@@ -1,19 +0,0 @@
|
|
1
|
-
import { isAfter, addMinutes } from 'date-fns';
|
2
|
-
import * as storage from './storage';
|
3
|
-
const SESSION_THRESHOLD_MINUTES = 30;
|
4
|
-
|
5
|
-
export default class DeviceSession {
|
6
|
-
constructor () {
|
7
|
-
this.expiry = storage.getDeviceSessionExpiry();
|
8
|
-
const newExpiry = addMinutes(new Date(), SESSION_THRESHOLD_MINUTES);
|
9
|
-
storage.setDeviceSessionExpiry(newExpiry);
|
10
|
-
}
|
11
|
-
|
12
|
-
isNewSession () {
|
13
|
-
if (this.expiry) {
|
14
|
-
return isAfter(new Date(), this.expiry);
|
15
|
-
} else {
|
16
|
-
return true;
|
17
|
-
}
|
18
|
-
}
|
19
|
-
}
|
@@ -1,31 +0,0 @@
|
|
1
|
-
import { startOfDay } from 'date-fns';
|
2
|
-
import * as storage from './storage';
|
3
|
-
import initialiseFeedStartTime from './initialise-feed-start-time';
|
4
|
-
import sessionClient from 'next-session-client';
|
5
|
-
|
6
|
-
let initialFeedStartTime;
|
7
|
-
let userId;
|
8
|
-
|
9
|
-
async function getValidSession () {
|
10
|
-
if (!userId) {
|
11
|
-
const { uuid } = await sessionClient.uuid();
|
12
|
-
if (!uuid) throw new Error('No userId');
|
13
|
-
userId = uuid;
|
14
|
-
}
|
15
|
-
return userId;
|
16
|
-
}
|
17
|
-
|
18
|
-
// Export used in next-myft -page to determine whether to add "New" label to articles in feed
|
19
|
-
//KEEP: This function is in use in next-myft-page do not delete!
|
20
|
-
export async function getNewArticlesSinceTime () {
|
21
|
-
const user = await getValidSession();
|
22
|
-
const dayStart = startOfDay(new Date());
|
23
|
-
|
24
|
-
if (!initialFeedStartTime && storage.isAvailable()) {
|
25
|
-
try {
|
26
|
-
initialFeedStartTime = await initialiseFeedStartTime(user, new Date());
|
27
|
-
} catch (e) {}
|
28
|
-
}
|
29
|
-
|
30
|
-
return initialFeedStartTime || dayStart;
|
31
|
-
}
|
@@ -1,54 +0,0 @@
|
|
1
|
-
import { isToday, startOfDay } from 'date-fns';
|
2
|
-
import DeviceSession from './device-session';
|
3
|
-
import * as storage from './storage';
|
4
|
-
import { json as fetchJson } from 'fetchres';
|
5
|
-
|
6
|
-
const deviceSession = new DeviceSession();
|
7
|
-
|
8
|
-
export const fetchUserLastVisitedAt = (userId) => {
|
9
|
-
const url = '/__myft/users/:userId/last-seen?source=next-myft'.replace(
|
10
|
-
':userId',
|
11
|
-
userId
|
12
|
-
);
|
13
|
-
return fetch(url, { credentials: 'include' })
|
14
|
-
.then(fetchJson)
|
15
|
-
.then(({ lastSeen }) => new Date(lastSeen));
|
16
|
-
};
|
17
|
-
|
18
|
-
/**
|
19
|
-
* @param {Date} now Date representing the time now
|
20
|
-
* @param {Date} previousFeedStartTime Date representing the time we last used to determine if articles are new for the user
|
21
|
-
* @return {Promise<Date>} date when we now determine articles to be 'new' for the user
|
22
|
-
*/
|
23
|
-
const determineFeedStartTime = (userId, now, previousFeedStartTime) => {
|
24
|
-
if (
|
25
|
-
previousFeedStartTime &&
|
26
|
-
isToday(previousFeedStartTime) &&
|
27
|
-
!deviceSession.isNewSession()
|
28
|
-
) {
|
29
|
-
return Promise.resolve(previousFeedStartTime);
|
30
|
-
}
|
31
|
-
|
32
|
-
return fetchUserLastVisitedAt(userId)
|
33
|
-
.then((userLastVisitedAt) =>
|
34
|
-
isToday(userLastVisitedAt) ? userLastVisitedAt : Promise.reject(new Error('No last visited user data today'))
|
35
|
-
)
|
36
|
-
.catch(() => startOfDay(now));
|
37
|
-
};
|
38
|
-
|
39
|
-
/**
|
40
|
-
* Sets/updates the time after which articles must be published to count towards indicator count
|
41
|
-
* @param {Date} now Date representing the time now
|
42
|
-
*/
|
43
|
-
export default (userId, now) => {
|
44
|
-
const previousFeedStartTime =
|
45
|
-
storage.isAvailable() && storage.getFeedStartTime();
|
46
|
-
return determineFeedStartTime(userId, now, previousFeedStartTime).then(
|
47
|
-
(startTime) => {
|
48
|
-
if (storage.isAvailable()) {
|
49
|
-
storage.setFeedStartTime(startTime);
|
50
|
-
}
|
51
|
-
return startTime;
|
52
|
-
}
|
53
|
-
);
|
54
|
-
};
|
@@ -1,35 +0,0 @@
|
|
1
|
-
import { isValid } from 'date-fns';
|
2
|
-
|
3
|
-
const DEVICE_SESSION_EXPIRY = 'deviceSessionExpiry';
|
4
|
-
const FEED_START_TIME = 'newArticlesSinceTime';
|
5
|
-
|
6
|
-
const isISOString = (str) => typeof str === 'string' && str.charAt(10) === 'T';
|
7
|
-
const getStoredDate = (key) => {
|
8
|
-
const value = window.localStorage.getItem(key);
|
9
|
-
const date = new Date(value);
|
10
|
-
|
11
|
-
return isISOString(value) && isValid(date) ? date : null;
|
12
|
-
};
|
13
|
-
|
14
|
-
export const getDeviceSessionExpiry = () =>
|
15
|
-
getStoredDate(DEVICE_SESSION_EXPIRY);
|
16
|
-
|
17
|
-
export const setDeviceSessionExpiry = (date) =>
|
18
|
-
window.localStorage.setItem(DEVICE_SESSION_EXPIRY, date.toISOString());
|
19
|
-
|
20
|
-
export const getFeedStartTime = () => getStoredDate(FEED_START_TIME);
|
21
|
-
|
22
|
-
export const setFeedStartTime = (date) =>
|
23
|
-
window.localStorage.setItem(FEED_START_TIME, date.toISOString());
|
24
|
-
|
25
|
-
export const isAvailable = () => {
|
26
|
-
try {
|
27
|
-
const storage = window.localStorage;
|
28
|
-
const x = '__storage_test__';
|
29
|
-
storage.setItem(x, x);
|
30
|
-
storage.removeItem(x);
|
31
|
-
return true;
|
32
|
-
} catch (e) {
|
33
|
-
return false;
|
34
|
-
}
|
35
|
-
};
|
@@ -1,97 +0,0 @@
|
|
1
|
-
/* global expect */
|
2
|
-
import sinon from 'sinon';
|
3
|
-
import addMinutes from 'date-fns/addMinutes';
|
4
|
-
import parseISO from 'date-fns/parseISO';
|
5
|
-
|
6
|
-
const expiryTimestamp = parseISO('2018-06-14T12:00:00.000Z');
|
7
|
-
const timestampBeforeExpiry = '2018-06-14T11:40:00.000Z';
|
8
|
-
const timestampAfterExpiry = '2018-06-14T12:10:00.000Z';
|
9
|
-
|
10
|
-
const SESSION_THRESHOLD_MINUTES = 30;
|
11
|
-
|
12
|
-
describe('DeviceSession', () => {
|
13
|
-
|
14
|
-
let clock;
|
15
|
-
let deviceSession;
|
16
|
-
let deviceSessionExpiry;
|
17
|
-
|
18
|
-
const mockStorage = {
|
19
|
-
getDeviceSessionExpiry: sinon.stub().callsFake(() => deviceSessionExpiry),
|
20
|
-
setDeviceSessionExpiry: sinon.spy()
|
21
|
-
};
|
22
|
-
|
23
|
-
const subjectInjector = require('inject-loader!../../components/unread-articles-indicator/device-session');
|
24
|
-
const DeviceSession = subjectInjector({
|
25
|
-
'./storage': mockStorage
|
26
|
-
});
|
27
|
-
|
28
|
-
afterEach(() => {
|
29
|
-
deviceSessionExpiry = undefined;
|
30
|
-
clock.restore();
|
31
|
-
sinon.resetHistory();
|
32
|
-
});
|
33
|
-
|
34
|
-
describe('constructor:', () => {
|
35
|
-
|
36
|
-
const timeNow = timestampBeforeExpiry;
|
37
|
-
|
38
|
-
beforeEach(() => {
|
39
|
-
clock = sinon.useFakeTimers(new Date(timeNow));
|
40
|
-
deviceSessionExpiry = expiryTimestamp;
|
41
|
-
deviceSession = new DeviceSession();
|
42
|
-
});
|
43
|
-
|
44
|
-
it('should set this.expiry a timestamp from localStorage', () => {
|
45
|
-
expect(mockStorage.getDeviceSessionExpiry).to.have.been.calledOnce;
|
46
|
-
expect(deviceSession.expiry).to.equal(expiryTimestamp);
|
47
|
-
});
|
48
|
-
|
49
|
-
it('should call storage.setDeviceSessionExpiry with correct timestamp', () => {
|
50
|
-
const correctTimestamp = addMinutes(parseISO(timeNow), SESSION_THRESHOLD_MINUTES);
|
51
|
-
|
52
|
-
expect(mockStorage.setDeviceSessionExpiry).to.have.been.calledOnce;
|
53
|
-
expect(mockStorage.setDeviceSessionExpiry).to.have.been.calledWith(correctTimestamp);
|
54
|
-
});
|
55
|
-
});
|
56
|
-
|
57
|
-
describe('isNewSession:', () => {
|
58
|
-
|
59
|
-
it('should return true if the user visits for the first time (initially no value in localStorage)', () => {
|
60
|
-
const timeNow = '2018-06-14T08:00:00.000Z';
|
61
|
-
const newExpiryTimestamp = addMinutes(parseISO(timeNow), SESSION_THRESHOLD_MINUTES);
|
62
|
-
|
63
|
-
clock = sinon.useFakeTimers(new Date(timeNow));
|
64
|
-
deviceSessionExpiry = undefined;
|
65
|
-
deviceSession = new DeviceSession();
|
66
|
-
|
67
|
-
expect(deviceSession.isNewSession()).to.be.true;
|
68
|
-
expect(mockStorage.setDeviceSessionExpiry).to.have.been.calledWith(newExpiryTimestamp);
|
69
|
-
});
|
70
|
-
|
71
|
-
it('should return true if the user returns after session (initial value in localStorage is in the past)', () => {
|
72
|
-
const timeNow = timestampAfterExpiry;
|
73
|
-
const newExpiryTimestamp = addMinutes(parseISO(timeNow), SESSION_THRESHOLD_MINUTES);
|
74
|
-
|
75
|
-
clock = sinon.useFakeTimers(new Date(timeNow));
|
76
|
-
deviceSessionExpiry = expiryTimestamp;
|
77
|
-
deviceSession = new DeviceSession();
|
78
|
-
|
79
|
-
expect(deviceSession.isNewSession()).to.be.true;
|
80
|
-
expect(mockStorage.setDeviceSessionExpiry).to.have.been.calledWith(newExpiryTimestamp);
|
81
|
-
});
|
82
|
-
|
83
|
-
it('should return false if the user returns within session (initial value in localStorage is in the future)', () => {
|
84
|
-
const timeNow = timestampBeforeExpiry;
|
85
|
-
const newExpiryTimestamp = addMinutes(parseISO(timeNow), SESSION_THRESHOLD_MINUTES);
|
86
|
-
|
87
|
-
clock = sinon.useFakeTimers(new Date(timeNow));
|
88
|
-
deviceSessionExpiry = expiryTimestamp;
|
89
|
-
deviceSession = new DeviceSession();
|
90
|
-
|
91
|
-
expect(deviceSession.isNewSession()).to.be.false;
|
92
|
-
expect(mockStorage.setDeviceSessionExpiry).to.have.been.calledWith(newExpiryTimestamp);
|
93
|
-
});
|
94
|
-
|
95
|
-
});
|
96
|
-
|
97
|
-
});
|
@@ -1,43 +0,0 @@
|
|
1
|
-
/* global expect */
|
2
|
-
import sinon from 'sinon';
|
3
|
-
|
4
|
-
const FEED_START_TIME = '2018-06-05T10:00:00.000Z';
|
5
|
-
|
6
|
-
describe('unread stories indicator', () => {
|
7
|
-
let unreadArticlesComponent;
|
8
|
-
let mockInitialiseFeedStartTime;
|
9
|
-
let mockStorage;
|
10
|
-
let isStorageAvailable;
|
11
|
-
|
12
|
-
beforeEach(() => {
|
13
|
-
mockStorage = {
|
14
|
-
getFeedStartTime: sinon.stub().returns(FEED_START_TIME),
|
15
|
-
setFeedStartTime: sinon.stub(),
|
16
|
-
isAvailable: sinon.stub().callsFake(() => isStorageAvailable)
|
17
|
-
};
|
18
|
-
|
19
|
-
mockInitialiseFeedStartTime = sinon.stub().resolves();
|
20
|
-
|
21
|
-
unreadArticlesComponent = require('inject-loader!../../components/unread-articles-indicator')({
|
22
|
-
'next-session-client': {
|
23
|
-
uuid: sinon.stub().resolves({uuid: '00000000-0000-0000-0000-000000000000'})
|
24
|
-
},
|
25
|
-
'./storage': mockStorage,
|
26
|
-
'./initialise-feed-start-time': mockInitialiseFeedStartTime
|
27
|
-
});
|
28
|
-
});
|
29
|
-
|
30
|
-
describe('getNewsArticlesSinceTime', () => {
|
31
|
-
it('should not do anything if storage is not available', () => {
|
32
|
-
isStorageAvailable = false;
|
33
|
-
unreadArticlesComponent.getNewArticlesSinceTime();
|
34
|
-
expect(mockInitialiseFeedStartTime).to.not.have.been.called;
|
35
|
-
});
|
36
|
-
|
37
|
-
it('should initialise feed start time', () => {
|
38
|
-
isStorageAvailable = true;
|
39
|
-
return unreadArticlesComponent.getNewArticlesSinceTime();
|
40
|
-
expect(mockInitialiseFeedStartTime).to.have.been.calledOnce;
|
41
|
-
});
|
42
|
-
});
|
43
|
-
});
|
@@ -1,178 +0,0 @@
|
|
1
|
-
/* global expect */
|
2
|
-
|
3
|
-
import sinon from 'sinon';
|
4
|
-
import fetchMock from 'fetch-mock';
|
5
|
-
|
6
|
-
const mockDateFns = {
|
7
|
-
isToday (x) {
|
8
|
-
return x && x.toISOString().startsWith('2018-03-05T');
|
9
|
-
},
|
10
|
-
startOfDay (x) {
|
11
|
-
return new Date(x.toISOString().slice(0,11)+'00:00:00Z');
|
12
|
-
}
|
13
|
-
};
|
14
|
-
|
15
|
-
const TIME_NOW = new Date('2018-03-05T16:00:00Z');
|
16
|
-
const START_OF_TODAY = new Date('2018-03-05T00:00:00Z');
|
17
|
-
const VISIT_TIME_TODAY = new Date('2018-03-05T14:00:00Z');
|
18
|
-
const VISIT_TIME_YESTERDAY = new Date('2018-03-04T14:00:00Z');
|
19
|
-
const FEED_TIME_TODAY = new Date('2018-03-05T12:00:00Z');
|
20
|
-
const FEED_TIME_YESTERDAY = new Date('2018-03-04T12:00:00Z');
|
21
|
-
const USER_ID = '00000000-0000-0000-0000-000000000000';
|
22
|
-
|
23
|
-
const mockSetFeedStartTime = sinon.stub();
|
24
|
-
|
25
|
-
|
26
|
-
describe('initialiseFeedStartTime', () => {
|
27
|
-
let isNewSession;
|
28
|
-
let feedStartTime;
|
29
|
-
let lastVisitTime;
|
30
|
-
const injector = require('inject-loader!../../components/unread-articles-indicator/initialise-feed-start-time');
|
31
|
-
const initialiseFeedStartTime = injector({
|
32
|
-
'date-fns': {
|
33
|
-
isToday: mockDateFns.isToday,
|
34
|
-
startOfDay: mockDateFns.startOfDay
|
35
|
-
},
|
36
|
-
'./device-session': () => ({ isNewSession: () => isNewSession }),
|
37
|
-
'./storage': {
|
38
|
-
setFeedStartTime: mockSetFeedStartTime,
|
39
|
-
getFeedStartTime: sinon.stub().callsFake( () => feedStartTime ),
|
40
|
-
isAvailable: sinon.stub().returns(true)
|
41
|
-
}
|
42
|
-
}).default;
|
43
|
-
|
44
|
-
const expectStartTime = startTime => expect(mockSetFeedStartTime.firstCall.args[0].toISOString()).equal(startTime.toISOString());
|
45
|
-
|
46
|
-
before(() => {
|
47
|
-
fetchMock.get(`begin:/__myft/users/${USER_ID}/last-seen`, () => ({
|
48
|
-
lastSeen: lastVisitTime && lastVisitTime.toISOString()
|
49
|
-
}));
|
50
|
-
});
|
51
|
-
after(() => {
|
52
|
-
fetchMock.restore();
|
53
|
-
});
|
54
|
-
|
55
|
-
context('with no previous feed start time', () => {
|
56
|
-
before(() => {
|
57
|
-
feedStartTime = undefined;
|
58
|
-
});
|
59
|
-
context('and previous visit today', () => {
|
60
|
-
before(() => {
|
61
|
-
mockSetFeedStartTime.resetHistory();
|
62
|
-
lastVisitTime = VISIT_TIME_TODAY;
|
63
|
-
return initialiseFeedStartTime(USER_ID, TIME_NOW);
|
64
|
-
});
|
65
|
-
it('sets start time to visit time', () => {
|
66
|
-
expectStartTime(VISIT_TIME_TODAY);
|
67
|
-
});
|
68
|
-
});
|
69
|
-
context('and previous visit yesterday', () => {
|
70
|
-
before(() => {
|
71
|
-
mockSetFeedStartTime.resetHistory();
|
72
|
-
lastVisitTime = VISIT_TIME_YESTERDAY;
|
73
|
-
return initialiseFeedStartTime(USER_ID, TIME_NOW);
|
74
|
-
});
|
75
|
-
it('sets start time to start of today', () => {
|
76
|
-
expectStartTime(START_OF_TODAY);
|
77
|
-
});
|
78
|
-
});
|
79
|
-
context('and no previous visit', () => {
|
80
|
-
before(() => {
|
81
|
-
mockSetFeedStartTime.resetHistory();
|
82
|
-
lastVisitTime = undefined;
|
83
|
-
return initialiseFeedStartTime(USER_ID, TIME_NOW);
|
84
|
-
});
|
85
|
-
it('sets start time to start of today', () => {
|
86
|
-
expectStartTime(START_OF_TODAY);
|
87
|
-
});
|
88
|
-
});
|
89
|
-
});
|
90
|
-
context('with previous feed start time today', () => {
|
91
|
-
before(() => {
|
92
|
-
feedStartTime = FEED_TIME_TODAY;
|
93
|
-
});
|
94
|
-
context('and no new session', () => {
|
95
|
-
before(() => {
|
96
|
-
mockSetFeedStartTime.resetHistory();
|
97
|
-
lastVisitTime = VISIT_TIME_TODAY;
|
98
|
-
return initialiseFeedStartTime(USER_ID, );
|
99
|
-
});
|
100
|
-
it('sets start time to previous feed start time', () => {
|
101
|
-
expectStartTime(FEED_TIME_TODAY);
|
102
|
-
});
|
103
|
-
});
|
104
|
-
context('and new session started', () => {
|
105
|
-
before(() => {
|
106
|
-
isNewSession = true;
|
107
|
-
});
|
108
|
-
after(() => {
|
109
|
-
isNewSession = false;
|
110
|
-
});
|
111
|
-
context('and previous visit today', () => {
|
112
|
-
before(() => {
|
113
|
-
mockSetFeedStartTime.resetHistory();
|
114
|
-
lastVisitTime = VISIT_TIME_TODAY;
|
115
|
-
return initialiseFeedStartTime(USER_ID, TIME_NOW);
|
116
|
-
});
|
117
|
-
it('sets start time to visit time', () => {
|
118
|
-
expectStartTime(VISIT_TIME_TODAY);
|
119
|
-
});
|
120
|
-
});
|
121
|
-
context('and previous visit yesterday', () => {
|
122
|
-
before(() => {
|
123
|
-
mockSetFeedStartTime.resetHistory();
|
124
|
-
lastVisitTime = VISIT_TIME_YESTERDAY;
|
125
|
-
return initialiseFeedStartTime(USER_ID, TIME_NOW);
|
126
|
-
});
|
127
|
-
it('sets start time to start of today', () => {
|
128
|
-
expectStartTime(START_OF_TODAY);
|
129
|
-
});
|
130
|
-
});
|
131
|
-
context('and no previous visit', () => {
|
132
|
-
before(() => {
|
133
|
-
mockSetFeedStartTime.resetHistory();
|
134
|
-
lastVisitTime = undefined;
|
135
|
-
return initialiseFeedStartTime(USER_ID, TIME_NOW);
|
136
|
-
});
|
137
|
-
it('sets start time to start of today', () => {
|
138
|
-
expectStartTime(START_OF_TODAY);
|
139
|
-
});
|
140
|
-
});
|
141
|
-
});
|
142
|
-
});
|
143
|
-
context('with previous feed start time yesterday', () => {
|
144
|
-
before(() => {
|
145
|
-
feedStartTime = FEED_TIME_YESTERDAY;
|
146
|
-
});
|
147
|
-
context('and previous visit today', () => {
|
148
|
-
before(() => {
|
149
|
-
mockSetFeedStartTime.resetHistory();
|
150
|
-
lastVisitTime = VISIT_TIME_TODAY;
|
151
|
-
return initialiseFeedStartTime(USER_ID, TIME_NOW);
|
152
|
-
});
|
153
|
-
it('sets start time to visit time', () => {
|
154
|
-
expectStartTime(VISIT_TIME_TODAY);
|
155
|
-
});
|
156
|
-
});
|
157
|
-
context('and previous visit yesterday', () => {
|
158
|
-
before(() => {
|
159
|
-
mockSetFeedStartTime.resetHistory();
|
160
|
-
lastVisitTime = VISIT_TIME_YESTERDAY;
|
161
|
-
return initialiseFeedStartTime(USER_ID, TIME_NOW);
|
162
|
-
});
|
163
|
-
it('sets start time to start of today', () => {
|
164
|
-
expectStartTime(START_OF_TODAY);
|
165
|
-
});
|
166
|
-
});
|
167
|
-
context('and no previous visit', () => {
|
168
|
-
before(() => {
|
169
|
-
mockSetFeedStartTime.resetHistory();
|
170
|
-
lastVisitTime = undefined;
|
171
|
-
return initialiseFeedStartTime(USER_ID, TIME_NOW);
|
172
|
-
});
|
173
|
-
it('sets start time to start of today', () => {
|
174
|
-
expectStartTime(START_OF_TODAY);
|
175
|
-
});
|
176
|
-
});
|
177
|
-
});
|
178
|
-
});
|
@@ -1,142 +0,0 @@
|
|
1
|
-
/* global expect */
|
2
|
-
|
3
|
-
import sinon from 'sinon';
|
4
|
-
import * as storage from '../../components/unread-articles-indicator/storage';
|
5
|
-
|
6
|
-
describe('storage', () => {
|
7
|
-
let clock;
|
8
|
-
let mockStorage;
|
9
|
-
let now;
|
10
|
-
|
11
|
-
beforeEach(() => {
|
12
|
-
mockStorage = {};
|
13
|
-
now = new Date();
|
14
|
-
sinon.stub(window.Storage.prototype, 'getItem').callsFake(key => mockStorage[key]);
|
15
|
-
sinon.stub(window.Storage.prototype, 'setItem').callsFake((key, value) => mockStorage[key] = value);
|
16
|
-
clock = sinon.useFakeTimers(now);
|
17
|
-
});
|
18
|
-
|
19
|
-
afterEach(() => {
|
20
|
-
window.Storage.prototype.getItem.restore();
|
21
|
-
window.Storage.prototype.setItem.restore();
|
22
|
-
clock.restore();
|
23
|
-
});
|
24
|
-
|
25
|
-
describe('getDeviceSessionExpiry', () => {
|
26
|
-
describe('given a valid iso date is stored', () => {
|
27
|
-
beforeEach(() => {
|
28
|
-
mockStorage.deviceSessionExpiry = now.toISOString();
|
29
|
-
});
|
30
|
-
|
31
|
-
it('should return the correct iso date', () => {
|
32
|
-
expect(storage.getDeviceSessionExpiry().toISOString()).to.equal(now.toISOString());
|
33
|
-
});
|
34
|
-
});
|
35
|
-
|
36
|
-
describe('given no value is stored', () => {
|
37
|
-
beforeEach(() => {
|
38
|
-
mockStorage.deviceSessionExpiry = null;
|
39
|
-
});
|
40
|
-
|
41
|
-
it('should return null', () => {
|
42
|
-
expect(storage.getDeviceSessionExpiry()).to.equal(null);
|
43
|
-
});
|
44
|
-
});
|
45
|
-
|
46
|
-
describe('given an invalid value is stored', () => {
|
47
|
-
beforeEach(() => {
|
48
|
-
mockStorage.deviceSessionExpiry = 'abc';
|
49
|
-
});
|
50
|
-
|
51
|
-
it('should return null', () => {
|
52
|
-
expect(storage.getDeviceSessionExpiry()).to.equal(null);
|
53
|
-
});
|
54
|
-
});
|
55
|
-
|
56
|
-
describe('given a unix timestamp is stored (old format)', () => {
|
57
|
-
beforeEach(() => {
|
58
|
-
mockStorage.deviceSessionExpiry = String(now.getTime());
|
59
|
-
});
|
60
|
-
|
61
|
-
it('should return null', () => {
|
62
|
-
expect(storage.getDeviceSessionExpiry()).to.equal(null);
|
63
|
-
});
|
64
|
-
});
|
65
|
-
});
|
66
|
-
|
67
|
-
describe('setDeviceSessionExpiry', () => {
|
68
|
-
it('should store the date as an iso string', () => {
|
69
|
-
const date = new Date(2018, 6, 14, 11, 0, 0);
|
70
|
-
|
71
|
-
storage.setDeviceSessionExpiry(date);
|
72
|
-
|
73
|
-
expect(mockStorage.deviceSessionExpiry).to.equal(date.toISOString());
|
74
|
-
});
|
75
|
-
});
|
76
|
-
|
77
|
-
describe('getFeedStartTime', () => {
|
78
|
-
describe('given a valid iso date is stored', () => {
|
79
|
-
beforeEach(() => {
|
80
|
-
mockStorage.newArticlesSinceTime = now.toISOString();
|
81
|
-
});
|
82
|
-
|
83
|
-
it('should return the correct iso date', () => {
|
84
|
-
expect(storage.getFeedStartTime().toISOString()).to.equal(now.toISOString());
|
85
|
-
});
|
86
|
-
});
|
87
|
-
|
88
|
-
describe('given no value is stored', () => {
|
89
|
-
beforeEach(() => {
|
90
|
-
mockStorage.newArticlesSinceTime = null;
|
91
|
-
});
|
92
|
-
|
93
|
-
it('should return null', () => {
|
94
|
-
expect(storage.getFeedStartTime()).to.equal(null);
|
95
|
-
});
|
96
|
-
});
|
97
|
-
|
98
|
-
describe('given an invalid value is stored', () => {
|
99
|
-
beforeEach(() => {
|
100
|
-
mockStorage.newArticlesSinceTime = 'abc';
|
101
|
-
});
|
102
|
-
|
103
|
-
it('should return null', () => {
|
104
|
-
expect(storage.getFeedStartTime()).to.equal(null);
|
105
|
-
});
|
106
|
-
});
|
107
|
-
|
108
|
-
describe('given a unix timestamp is stored (old format)', () => {
|
109
|
-
beforeEach(() => {
|
110
|
-
mockStorage.newArticlesSinceTime = String(now.getTime());
|
111
|
-
});
|
112
|
-
|
113
|
-
it('should return the correct iso date', () => {
|
114
|
-
expect(storage.getFeedStartTime()).to.equal(null);
|
115
|
-
});
|
116
|
-
});
|
117
|
-
});
|
118
|
-
|
119
|
-
describe('setFeedStartTime', () => {
|
120
|
-
it('should store the date as an iso string', () => {
|
121
|
-
const date = new Date(2018, 5, 1, 11, 30, 0);
|
122
|
-
|
123
|
-
storage.setFeedStartTime(date);
|
124
|
-
|
125
|
-
expect(mockStorage.newArticlesSinceTime).to.equal(date.toISOString());
|
126
|
-
});
|
127
|
-
});
|
128
|
-
|
129
|
-
describe('isAvailable', () => {
|
130
|
-
it('should return true if it is available', () => {
|
131
|
-
sinon.stub(window.Storage.prototype, 'removeItem').callsFake((key) => delete mockStorage[key]);
|
132
|
-
|
133
|
-
expect(storage.isAvailable()).to.be.true;
|
134
|
-
});
|
135
|
-
|
136
|
-
it('should return false if it is not available', () => {
|
137
|
-
mockStorage = null;
|
138
|
-
|
139
|
-
expect(storage.isAvailable()).to.be.false;
|
140
|
-
});
|
141
|
-
});
|
142
|
-
});
|