-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SaveMessage component to Header (#2133)
* add SaveMessage component to Header * preserve space between Check and SaveMessage components * use moment#duration to calculate time differences, use expect#toMatch for better errors * appease the demands of our linter * thanks, yoda... * update Header test and snapshot * working auto-update test * add dem semicolons * ugly auto-update test, but it seems to work * refactor tests * remove leading zero from hour * address feedback * refactor into functional component, auto-update test broken :( * use react-test-renderer for testing * update snapshots * appease the linter * update snapshots * update changelog
- Loading branch information
Showing
6 changed files
with
160 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import moment from "moment"; | ||
import { useEffect, useState } from "react"; | ||
import PropTypes from 'prop-types'; | ||
|
||
// Configure moment to display '1 time-unit ago' instead of 'a time-unit ago' | ||
// https://github.com/moment/moment/issues/3764 | ||
moment.updateLocale("en", { | ||
relativeTime: { | ||
s: "seconds", | ||
m: "1 minute", | ||
mm: "%d minutes", | ||
h: "1 hour", | ||
hh: "%d hours", | ||
d: "1 day", | ||
dd: "%d days", | ||
M: "1 month", | ||
MM: "%d months", | ||
y: "1 year", | ||
yy: "%d years", | ||
}, | ||
}); | ||
|
||
const SaveMessage = ({ lastSaved }) => { | ||
const [currentMoment, setCurrentMoment] = useState(() => moment()); | ||
|
||
useEffect(() => { | ||
const timerID = setInterval(() => setCurrentMoment(moment()), 1000); | ||
return () => clearInterval(timerID); | ||
}); | ||
|
||
const lastSavedMoment = moment(lastSaved); | ||
const difference = currentMoment.diff(lastSavedMoment); | ||
const duration = moment.duration(difference); | ||
let result = "Last saved "; | ||
|
||
if (duration.asMinutes() < 1) return "Saved"; | ||
|
||
if (duration.asDays() < 1) { | ||
result += lastSavedMoment.format("h:mm a"); | ||
} else if (duration.asYears() < 1) { | ||
result += lastSavedMoment.format("MMMM D"); | ||
} else { | ||
result += lastSavedMoment.format("MMMM D, YYYY"); | ||
} | ||
|
||
result += ` (${lastSavedMoment.fromNow()})`; | ||
return result; | ||
}; | ||
|
||
SaveMessage.propTypes = { | ||
lastSaved: PropTypes.oneOfType([ | ||
PropTypes.instanceOf(Date), | ||
PropTypes.instanceOf(moment), | ||
PropTypes.string | ||
]).isRequired, | ||
}; | ||
|
||
export default SaveMessage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import React from "react"; | ||
import { create, act } from "react-test-renderer"; | ||
import moment from "moment"; | ||
import SaveMessage from "./SaveMessage"; | ||
|
||
describe("<SaveMessage />", () => { | ||
let subject; | ||
|
||
describe('when saved less than 1 minute ago, it displays "Saved"', () => { | ||
[ | ||
["1 second ago", 1], | ||
["2 seconds ago", 2], | ||
["30 seconds ago", 30], | ||
["59 seconds", 59], | ||
].forEach(([testName, seconds]) => { | ||
test(testName, () => { | ||
const lastSaved = moment().subtract(seconds, "seconds"); | ||
// https://reactjs.org/docs/test-renderer.html#testrendereract | ||
act(() => { | ||
subject = create(<SaveMessage lastSaved={lastSaved} />); | ||
}) | ||
expect(subject.toJSON()).toEqual("Saved"); | ||
}); | ||
}); | ||
}); | ||
|
||
describe("when observed saved time changes to 1 minute ago", () => { | ||
const now = new Date(2020, 0, 1, 12, 0); | ||
const oneMinuteFromNow = new Date(2020, 0, 1, 12, 1); | ||
let mockDateNow; | ||
|
||
beforeEach(() => { | ||
jest.useFakeTimers(); | ||
mockDateNow = jest | ||
.spyOn(Date, "now") | ||
.mockReturnValueOnce(now) | ||
.mockReturnValueOnce(now) | ||
.mockReturnValueOnce(now) | ||
.mockReturnValue(oneMinuteFromNow); | ||
}); | ||
|
||
afterEach(() => { | ||
mockDateNow.mockRestore(); | ||
jest.clearAllTimers(); | ||
}); | ||
|
||
it('auto-updates from "Saved" to (1 minute ago)', () => { | ||
act(() => { | ||
subject = create(<SaveMessage lastSaved={now} />); | ||
}) | ||
expect(subject.toJSON()).toMatch("Saved"); | ||
act(() => jest.advanceTimersByTime(60 * 1000)); | ||
expect(subject.toJSON()).toMatch(/\(1 minute ago\)$/); | ||
}); | ||
}); | ||
|
||
describe("given current time is January 1, 2020 12:00 pm", () => { | ||
const jan1AtNoon = new Date(2020, 0, 1, 12, 0); | ||
let mockDateNow; | ||
|
||
beforeEach(() => { | ||
mockDateNow = jest | ||
.spyOn(Date, "now") | ||
.mockReturnValue(jan1AtNoon.getTime()); | ||
}); | ||
|
||
afterEach(() => { | ||
mockDateNow.mockRestore(); | ||
}); | ||
|
||
[ | ||
[1, "minute", "Last saved 11:59 am (1 minute ago)"], | ||
[60 * 24 - 1, "minutes", "Last saved 12:01 pm (1 day ago)"], | ||
[3, "hours", "Last saved 9:00 am (3 hours ago)"], | ||
[1, "day", "Last saved December 31 (1 day ago)"], | ||
[30, "days", "Last saved December 2 (1 month ago)"], | ||
[364, "days", "Last saved January 2 (1 year ago)"], | ||
[3, "years", "Last saved January 1, 2017 (3 years ago)"], | ||
].forEach(([value, timeUnit, result]) => { | ||
test(`when saved ${value} ${timeUnit} ago, it displays "${result}"`, () => { | ||
const lastSaved = moment().subtract(value, timeUnit); | ||
act(() => { | ||
subject = create(<SaveMessage lastSaved={lastSaved} />); | ||
}) | ||
expect(subject.toJSON()).toEqual(result); | ||
}); | ||
}); | ||
}); | ||
}); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.