-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SaveMessage component to Header #2133
Conversation
); | ||
expect(subject.text()).toMatch(/^Last saved/); | ||
expect(subject.text()).toMatch(/11:59 am/); | ||
expect(subject.text()).toMatch(/\(1 minute ago\)$/); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might want to condense these expectations to one line. I'm curious which you think is easier to read and easier to update.
expect(subject.text()).toMatch("Last saved 11:59 am (1 minute ago)");
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After that, it might be possible to format these remaining tests like on line 11
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I like them separate
This deploy was cleaned up. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It says 03:23 (probably shouldn't have the leading zero before the 3)
it doesn't have EDT after pm
the task description says "p.m." but I see "pm" (though I agree that p.m. looks funny)
Regarding "am" vs "a.m.", I'd recommend having design or product weigh in. Historically we've been pretty flexible about tweaking designs for implementation simplicity. Long-term maintainability beats short-term pixel-perfect designs. 😄 Pinging @beparticular @jjames521 @lauraponce @jeromeleecms if y'all have feedback. |
Citing our much-missed alum's (Nicole Fenton) book, Nicely Said, am / pm is a commonly accepted abbreviation used for the web (example provided without periods)... I am good to hold to that guidance... |
How do you all feel about the 3-character Time Zone being a "must-have?" The displayed time would always be in the client's browser-detected time zone, from my understanding of how the |
Yes.
Input into the |
It feels like getting the three-character timezone from the browser is unsupported by the We could investigate this, if we really want to spend the effort. However, it feels like time could be better spent elsewhere... |
@radavis Great question - I had originally thought just having a 2 letter time zone appended to the content would be helpful since we don't all work in the same time zone... however... am I understanding that the time zone is automatically localized to client's time zone? If that's the case, I would be OK with dropping it off entirely -- unless someone has a compelling reason for us to keep it there. |
Yes. As long as the moment docs are telling the truth :) |
Alright I'm good with dropping it then! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks good to me. My only feedback is around style choices and consistency, so that's something for you and @pphillips-fearless to decide on. I'm happy to weigh in on why we chose certain approaches (though sometimes it's just... because), but ultimately y'all will be the ones living in this code, so it makes sense for it to bend to your preferences.
web/src/components/SaveMessage.js
Outdated
}, | ||
}); | ||
|
||
class SaveMessage extends React.Component { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've mostly switched over to functional components using React hooks (in this case, useEffect
and useState
). Since you and @pphillips-fearless are taking over the code, I'll defer to your opinions on which you would prefer, but wanted to point this out for consistency's sake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes. I can definitely change this to a functional component since that will maintain consistency with other components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just reading up on hooks, not having used them before, but so far I like what I'm reading
web/src/components/SaveMessage.js
Outdated
// eslint's "yoda": "Expected literal to be on the right side of <=" | ||
// Which is easier to visualize on a number line, Mr. Yoda? | ||
// lowerBound <= object.value() && object.value() < upperBound // or... | ||
if (duration.asMinutes() >= 1 && duration.asDays() < 1) { | ||
result = `${result} ${lastSavedMoment.format("h:mm a")}`; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lowerBound <= object.value() && object.value() < upperBound
could read: is the value between the lower and upper bound
web/src/components/SaveMessage.js
Outdated
if (duration.asDays() >= 1 && duration.asYears() < 1) { | ||
result = `${result} ${lastSavedMoment.format("MMMM D")}`; | ||
} | ||
if (duration.asYears() >= 1) { | ||
result = `${result} ${lastSavedMoment.format("MMMM D, YYYY")}`; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these would be better as else if
s, so the conditionals don't get evaluated if a previous block already executed.
web/src/components/SaveMessage.js
Outdated
lastSaved: PropTypes.oneOfType([ | ||
PropTypes.instanceOf(Date), | ||
PropTypes.instanceOf(moment), | ||
PropTypes.string | ||
].isRequred), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, your propTypes
hygiene is way better than mine. Love this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, cool. I was just thinking of the possible types you could throw at this component. I'm not necessarily testing all of those types, but moment should be able to handle them...
web/src/components/SaveMessage.js
Outdated
SaveMessage.defaultProps = { | ||
lastSaved: moment(), | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since lastSaved
is required, I don't think a defaultProp
is necessary. Elsewhere, we only define defaultProps
if the prop is not required. Again deferring to you and @pphillips-fearless's preferences on how you like to write the code, though.
(This is the sort of comment I can imagine leaving lots of over the next few weeks, just pointing out how we've structured things in the past so y'all can make your best decisions about how you want to do things.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the linter didn't like that it didn't have a default prop. I can take it out and verify.
let lastSaved; | ||
let subject; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let lastSaved; | |
let subject; |
I'd move these closer to the point-of-use, at which point they can be const
s. For me, at least, it's clearer that the value is new and I'm not worrying about overwriting some previously existing variable.
lastSaved = moment().subtract(seconds, "seconds"); | ||
subject = shallow( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lastSaved = moment().subtract(seconds, "seconds"); | |
subject = shallow( | |
const lastSaved = moment().subtract(seconds, "seconds"); | |
const subject = shallow( |
This is a stylistic choice, so again something for you and @pphillips-fearless to decide how you want to do it. I'm just imagining some future version of you or another dev looking at this and wondering "where did lastSaved
come from?" and then having to search for it. 🤷♂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like using const whenever possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the file grows, it could be difficult to find these variable declarations. Your argument makes sense. 👍
mockDateNow = jest | ||
.spyOn(Date, "now") | ||
.mockReturnValueOnce(now) | ||
.mockReturnValue(oneMinuteFromNow); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes me a little bit nervous because it relies on the internal behavior of moment
. Ideally jest.useFakeTimers()
would let us initialize it with a timestamp and it would also fake out Date
for us (sinon's fake timers work that way), and then we could just fast-forward the timer by 60 seconds. Alas, Jest isn't there...
(Which is all to say, I don't love this but I also think it's probably the best solution.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is necessary because the component under test is using moment (which is using Date) under the hood. I can attempt to refactor this and mock moment's functionality, it just might take me a bit longer to figure out.... I agree, it doesn't look pretty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No no, please don't! It's good as-is. It's a shortcoming of the jest time mocks, not your code!
const testName = `when saved ${value} ${timeUnit} ago, it displays "${result}"` | ||
test(testName, () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const testName = `when saved ${value} ${timeUnit} ago, it displays "${result}"` | |
test(testName, () => { | |
test(`when saved ${value} ${timeUnit} ago, it displays "${result}"`, () => { |
Another style choice. My preference is not to create variables that are only used once on the immediate next line unless creating them is a long or complex sequence. In the case of strings, I think making them is easy enough to read (especially with template strings!) not to ever bother. My thinking here is that when you create a variable, you're implying something about the lifetime of that value, so my brain stashes it away so I know what it is when I see it later. But then I never see it later. 😛
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can do. I always wrestle with the idea of "Am I doing too much on a single line?," and tend to break things up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops. Meant to mark that as approved.
@radavis Can you add @jeromeleecms and @lauraponce as reviewers on this PR? It'll also need product and design's approval before shipping.
Just noticing that I can't get to the deployed site via the cms-eapd-bot's link. I'll investigate this tomorrow in the AM. |
@jeromeleecms think you had referenced the design system being somewhat built off the ideology of Material Design in some ways. In that case am / pm would fit with the system pretty nicely, and as Nicole had said in Nicely Said. it's commonly accepted anyways. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to open a tech debt issue to come back and think about these tests at some point. These particular ones are a decent candidate for snapshots, for example.
Certainly not something worth changing now and maybe not worth changing at all! But let's put a pin in it and figure it out later. 😄
act(() => { | ||
subject = create(<SaveMessage lastSaved={now} />); | ||
}) | ||
expect(subject.toJSON()).toMatch("Saved"); | ||
act(() => jest.advanceTimersByTime(60 * 1000)); | ||
expect(subject.toJSON()).toMatch(/\(1 minute ago\)$/); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally not a hack. I think this is how React recommends doing tests if you've got hooks, really. It's possible we should being transitioning entirely to react-test-renderer
. Otherwise, there are ways to make it work with Enzyme.
Again, something to put a pin in and revisit later. Definitely not something we have to figure out for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I appreciate the extended conversations here as we come up to speed.
@jeromeleecms @lauraponce When you have a free moment, could you view the new save message functionality, here, please?: https://ec2-3-87-56-55.compute-1.amazonaws.com/apd Thank you! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I first load the page, it shows as "Saved" as if I just then saved it. I wasn't sure if this was just because of something with the test that we're using, or will it always save right when you load the page even when no new content is added?
@lauraponce Good catch! I noticed that too and remembered an issue from a while back where the APD immediately saves when it's loaded for the very first time. (The backend creates the APD and the frontend adds some initial data for the federal citations. Then the frontend tries to save it before anything gets lost.) It should only happen the first time an APD loads, so we need to double-check that. If it happens every time, that's probably a bug! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can tell this works as intended. Thanks for the video! Now I don't have to wait for 1 year before I approve this PR 😂
Good to go on my end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mgwalker that makes sense! It must have just been the first time it was loading the APD (with the fake data in it). This looks good!
Regarding accessibility of this feature, from Heather Battaglia via Slack...
|
Resolves #2104
This pull request changes...
This pull request is ready to merge when...
Associated OpenAPI documentation has been updatedThis feature is done when...
What's missing/incongruent with Issue #2104...
Testing auto updating isn't done, yet.Done!Fix Header Component snapshot test.Fixed!CircleCI tasks are stuck. Does it need a kick?Opening a PR on this repo, and not a fork, made it work.