Skip to content
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

I5011 improve collection success msg appearance #5113

Merged

Conversation

rebmullin
Copy link
Contributor

fixes mozilla/addons#11765

video
https://monosnap.com/image/52gnX55LZiRElcneQSKGCGWiJAHHwN.png

I feel like we should add some transition effect here too...

@rebmullin rebmullin changed the title I5011 improve collection success msg I5011 improve collection success msg appearance May 25, 2018
@bobsilverberg bobsilverberg self-assigned this May 26, 2018
@bobsilverberg
Copy link
Contributor

I feel like we should add some transition effect here too...

I agree, we should use a css transition to make it fade out, rather than just disappear after 5 seconds.

@codecov-io
Copy link

codecov-io commented May 29, 2018

Codecov Report

Merging #5113 into master will increase coverage by 0.14%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    mozilla/addons-frontend#5113      +/-   ##
==========================================
+ Coverage    96.7%   96.84%   +0.14%     
==========================================
  Files         214      215       +1     
  Lines        5062     5172     +110     
  Branches     1012     1022      +10     
==========================================
+ Hits         4895     5009     +114     
+ Misses        149      145       -4     
  Partials       18       18
Impacted Files Coverage Δ
src/amo/components/CollectionManager/index.js 100% <100%> (ø) ⬆️
src/core/api/index.js 100% <0%> (ø) ⬆️
src/amo/api/collections.js 100% <0%> (ø) ⬆️
src/amo/api/users.js 100% <0%> (ø) ⬆️
src/amo/sagas/collections.js 100% <0%> (ø) ⬆️
src/amo/components/UserProfileEdit/index.js 100% <0%> (ø) ⬆️
src/amo/sagas/users.js 100% <0%> (ø) ⬆️
src/amo/components/AddonsCard/index.js 100% <0%> (ø) ⬆️
src/amo/reducers/users.js 100% <0%> (ø) ⬆️
src/amo/components/Collection/index.js 100% <0%> (ø) ⬆️
... and 4 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 681e839...da7cdf2. Read the comment docs.

@rebmullin
Copy link
Contributor Author

hey @bobsilverberg - I've updated to use a transition. Please let me know what you think about this and some reallocation of the spacing (I've adjusted spacing here a bit b/c IMO it still looked a little odd when the space changes):

https://monosnap.com/image/mdmViit8JIIB6UPrBLlGP0s7G9dTNf.png

also - I noticed the library has updated to v2:
https://github.com/reactjs/react-transition-group. Should I create a new issue to do an upgrade? It looks like its used in the disco pane too..

@bobsilverberg bobsilverberg self-requested a review May 29, 2018 18:31
Copy link
Contributor

@bobsilverberg bobsilverberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, thanks @rebmullin. I didn't know about ReactCSSTransitionGroup. I assumed we'd just use css transitions manually, but if it makes it easier that's cool.

Regarding the version, that's odd because Greenkeeper usually keeps us up to date on out-of-date dependencies. Are you sure the new version is the latest stable version?

I find the video difficult to view. For ease of verification could you also include just a screenshot of the message when it is on the screen? We should get @pwalm to sign off on the final positioning of that.

I'm not sure what the standard are for transitions, but I imagined something a bit more gradual. I.e., the notice would appear, and remain static for a couple of seconds, and then fade away gradually over the remaining 3 seconds. Again, maybe @pwalm has an opinion on this.

The code looks good, and I just have one question about the test, so this is an r+wc, but we should wait to hear back from @pwalm before merging it.

@@ -61,6 +64,10 @@ describe(__filename, () => {
});
});

afterEach(() => {
clock.restore();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're only using the clock in one test, is there a reason we have to do this in afterEach?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

altho - i don't think it would hurt having this here... I am just thinking if another tests is added with a clock..

I also see clock some other files where it appears only to be used 1 time and has this similar set up...


state = root.state();
expect(state.addonAddedStatus).toEqual(null);
root.setProps({ hasAddonBeenAdded: true });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why you're setting the state here again, and I would expect odd results because of this. Was this just a line of code left in by accident?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's what I thought too but it seems that I need to get the updated state (when I do it this way it works :/)
I also had to setProps too which I thought was odd but again it worked this way. Is it possible this is happening b/c i am missing some other piece of code here?

@rebmullin
Copy link
Contributor Author

I assumed we'd just use css transitions manually, but if it makes it easier that's cool.

We could but I think this library helps with transitioning when the html is removed from the DOM.

Regarding the version, that's odd because Greenkeeper usually keeps us up to date on out-of-date dependencies. Are you sure the new version is the latest stable version?

From what i can tell, v2 has been out for a while (almost a year), altho it does say v1 is still actively maintained

here's a screenshot of before and after (to get an idea of spacing adjustments):
Alt text

Alt text

also - here is an updated video which i * think * might show a little better:
https://monosnap.com/file/5Wi6NTTC5DPQlouBmOOZxaIzJh5QD9#

I agree the transition seems a little fast. I did look at photon which I think suggests even faster but yeh I agree.. best to see what @pwalm thinks for this scenario

@@ -89,6 +93,14 @@ export class CollectionManagerBase extends React.Component<Props, State> {
this.setState({
addonAddedStatus: props.hasAddonBeenAdded ?
ADDON_ADDED_STATUS_SUCCESS : null,
}, () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We decided not to use the setState second argument in https://github.com/mozilla/addons-frontend/issues/3077.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh? ok.. let me try another way...

@@ -89,6 +93,14 @@ export class CollectionManagerBase extends React.Component<Props, State> {
this.setState({
addonAddedStatus: props.hasAddonBeenAdded ?
ADDON_ADDED_STATUS_SUCCESS : null,
}, () => {
if (props.hasAddonBeenAdded) {
setTimeout(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could inject setTimeout as a default prop and pass a function that executes directly in the test cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @willdurand - do you mean something like this?

  static defaultProps = {
    setTimeout: () => {},
  };

I guess I am not totally clear of the benefit of this..? let me know if i am missing something or if something like this is ok:

componentWillReceiveProps(props: Props) {
    ....
 
    if (hasAddonBeenAdded !== hasAddonBeenAddedNew) {
      this.setState({
        addonAddedStatus: props.hasAddonBeenAdded ?
          ADDON_ADDED_STATUS_SUCCESS : null,
      });
    }
    
    // If the message status has been set, we'll reset the message status after 5 seconds
    if (hasAddonBeenAddedNew && hasAddonBeenAddedNew !== hasAddonBeenAdded) {
      setTimeout(this.resetMessageStatus, MESSAGE_RESET_TIME);
    }
  }

Copy link
Member

@willdurand willdurand May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean having a prop called setTimeout that is configured with setTimeout :D

In the tests, we can pass (func) => func() to that prop. That way no need to deal with ticks in the tests. The benefit is that it is simpler.

@willdurand
Copy link
Member

Regarding the version, that's odd because Greenkeeper usually keeps us up to date on out-of-date dependencies. Are you sure the new version is the latest stable version?

I think we never upgraded by fear of breaking things, but we should...

@bobsilverberg
Copy link
Contributor

I think we never upgraded by fear of breaking things, but we should...

Thanks @willdurand. @rebmullin could you open a separate issue for that?

@pwalm
Copy link
Contributor

pwalm commented May 30, 2018

Looks beautiful! Ship it!

@rebmullin
Copy link
Contributor Author

..@rebmullin could you open a separate issue for that?

sure @bobsilverberg. But how would we go about updating this if Greenkeeper didn't? is it just a manually change?

@rebmullin
Copy link
Contributor Author

In the tests, we can pass (func) => func() to that prop. That way no need to deal with ticks in the tests..

hey @willdurand, I still had to use ticks so I may be missing something. Can you please take a look at my changes when you can? thanks

@willdurand
Copy link
Member

hey @willdurand, I still had to use ticks so I may be missing something. Can you please take a look at my changes when you can? thanks

Sorry, here is what I had in mind. Note that we may have to rename the prop from setTimeout to _setTimeout (and to write _setTimeout: setTimeout in the defaultProps). The idea is to fully control the setTimeout without having to deal with clock/tick. We often do this for things like window or config for example.

diff --git a/src/amo/components/CollectionManager/index.js b/src/amo/components/CollectionManager/index.js
index 554d44fe3..31cf9c8e7 100644
--- a/src/amo/components/CollectionManager/index.js
+++ b/src/amo/components/CollectionManager/index.js
@@ -78,7 +78,7 @@ type State = {|
 
 export class CollectionManagerBase extends React.Component<Props, State> {
   static defaultProps = {
-    setTimeout: (fn) => setTimeout(fn, MESSAGE_RESET_TIME),
+    setTimeout,
   };
 
   constructor(props: Props) {
@@ -104,7 +104,7 @@ export class CollectionManagerBase extends React.Component<Props, State> {
     }
 
     if (hasAddonBeenAddedNew && hasAddonBeenAddedNew !== hasAddonBeenAdded) {
-      this.props.setTimeout(this.resetMessageStatus);
+      this.props.setTimeout(this.resetMessageStatus, MESSAGE_RESET_TIME);
     }
   }
 
diff --git a/tests/unit/amo/components/TestCollectionManager.js b/tests/unit/amo/components/TestCollectionManager.js
index ff08d05b1..b8a2d7730 100644
--- a/tests/unit/amo/components/TestCollectionManager.js
+++ b/tests/unit/amo/components/TestCollectionManager.js
@@ -44,7 +44,6 @@ const simulateAutoSearchCallback = (props = {}) => {
 };
 
 describe(__filename, () => {
-  let clock;
   let fakeRouter;
   let store;
   const apiHost = config.get('apiHost');
@@ -55,7 +54,6 @@ describe(__filename, () => {
   beforeEach(() => {
     fakeRouter = createFakeRouter();
     store = dispatchClientMetadata().store;
-    clock = sinon.useFakeTimers();
     dispatchSignInActions({
       lang,
       store,
@@ -64,10 +62,6 @@ describe(__filename, () => {
     });
   });
 
-  afterEach(() => {
-    clock.restore();
-  });
-
   const getProps = ({
     collection = createInternalCollection({
       detail: createFakeCollectionDetail(),
@@ -707,7 +701,8 @@ describe(__filename, () => {
   });
 
   it('displays a notification for 5 seconds after an add-on has been added', () => {
-    const root = render({});
+    const setTimeoutSpy = sinon.stub();
+    const root = render({ setTimeout: setTimeoutSpy });
 
     expect(root.find(Notice)).toHaveLength(0);
 
@@ -715,18 +710,15 @@ describe(__filename, () => {
 
     expect(root.find(Notice)).toHaveLength(1);
     expect(root.find(Notice).children()).toHaveText('Added to collection');
+    expect(root).toHaveState('addonAddedStatus', ADDON_ADDED_STATUS_SUCCESS);
+    sinon.assert.calledWith(setTimeoutSpy, root.instance().resetMessageStatus, 5000);
 
-    const state = root.state();
-    expect(state.addonAddedStatus).toEqual(ADDON_ADDED_STATUS_SUCCESS);
-
-    const reset = () => {
-      root.setState({ addonAddedStatus: null });
-    };
-
-    // After 5 seconds the Notice will go away.
-    root.setProps(setTimeout(reset));
+    // Simulate the setTimeout behavior.
+    root.instance().resetMessageStatus();
+    // See: https://github.com/airbnb/enzyme/blob/master/docs/guides/migration-from-2-to-3.md#for-mount-updates-are-sometimes-required-when-they-werent-before
+    root.update();
 
-    clock.tick(6000);
+    expect(root).toHaveState('addonAddedStatus', null);
     expect(root.find(Notice)).toHaveLength(0);
   });
 

Does that make sense?

@rebmullin
Copy link
Contributor Author

@willdurand, thanks for helping with this : ) I think it makes sense - basically i think your point is you can call that same function from the test thus avoiding the clock stuff. I also am seeing that I need to dig into advanced testing a bit more :0
I pretty much followed what you noted above but with a small tweak to get it fully working. let me know how it looks now : )

@@ -72,25 +79,40 @@ type State = {|
|};

export class CollectionManagerBase extends React.Component<Props, State> {
static defaultProps = {
setTimeout,
_window: typeof window !== 'undefined' ? window : {},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative would be to bind the setTimeout function to window:

    setTimeout: typeof window !== 'undefined' ? window.setTimeout.bind(window) : () => {},

</Notice>
)}

<ReactCSSTransitionGroup
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot how to use this component.. do we have to keep it all the time in the UI tree? (or can we put it in the condition where Notice is).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes - it has to always be in the DOM to work

<ReactCSSTransitionGroup
className="NoticePlaceholder"
component="div"
transitionName="overlay"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a predefined name or custom?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No i meant to change that... thanks for catching


expect(root.find(Notice)).toHaveLength(0);

root.setProps({ hasAddonBeenAdded: true });

expect(root.find(Notice)).toHaveLength(1);
expect(root.find(Notice).children()).toHaveText('Added to collection');

expect(root).toHaveState('addonAddedStatus', ADDON_ADDED_STATUS_SUCCESS);
sinon.assert.calledWith(setTimeoutSpy, root.instance().resetMessageStatus, 5000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess here we can use MESSAGE_RESET_TIME


// Simulate the setTimeout behavior.
root.instance().resetMessageStatus();
// See: https://github.com/airbnb/enzyme/blob/master/docs/guides/migration-from-2-to-3.md#for-mount-updates-are-sometimes-required-when-they-werent-before
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rebmullin
Copy link
Contributor Author

@willdurand, thanks for the feedback. how's it look now?

@rebmullin rebmullin requested a review from willdurand May 31, 2018 19:30
Copy link
Member

@willdurand willdurand left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@rebmullin rebmullin merged commit 3e5b031 into mozilla:master May 31, 2018
@rebmullin rebmullin deleted the I5011-improve-collection-success-msg branch August 1, 2018 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve appearance of confirmation message displayed when adding an add-on to a collection
5 participants