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

Automated tests for Unread Indicators feature and final polish #10929

Merged
merged 19 commits into from
Sep 16, 2022

Conversation

marcaaron
Copy link
Contributor

@marcaaron marcaaron commented Sep 9, 2022

Details

There are several changes introduced in this PR, but most of them exist to enable UI testing for the entire app.

  • Several modules needed to be mocked
  • Prevent editing or deleting of report actions that are pendingAction === 'add' (this is temporary but I noticed this feature is pretty broken so disabled it for now until we are totally migrated to using reportActionID for reportAction_* values keys)
  • Report sequenceNumber were being set to clientID which made it impossible to mark a "pending" comment as unread. Instead we use a guessed sequenceNumber for this. I suspect this may need to change in the future or depend on reportActionID as well.
  • When deleting a message we were not optimistically adding in the type field (Fixed)
  • Strike-through for deleted messages also was not working correctly as the message was entirely unset and when going offline there was no comment to show with the strike-through. The message is entirely lost when we do this so I moved it to a previousMessage field that we can use as a fallback.

Fixed Issues

$ https://github.com/Expensify/Expensify/issues/228076
$ #10799
$ #10753

Tests

  • Automated tests were added, but these testing flows laid out in the Unread Indicators doc should be performed.

Testing Unread LHN Status, “New Messages” badge, and New Line Indicator

  1. Start a chat between User A and User B.
  2. Ensure User A is not looking at the chat.
  3. Send a message as User B
  4. Verify the LHN row shows in bold for User A to indicate unread messages
  5. Visit the chat as User A and verify the LHN row no longer shows in bold
  6. Verify there is a “----- New” line above the message sent by User B
  7. Scroll up and verify that the “New Messages” button appears floating above the content.
  8. Verify that tapping this button will scroll you to the bottom of the chat but not clear the “---- New” line marker.
  9. Mark a comment as “unread”
  10. Verify the “---- New” line marker appears
  11. Mark as unread comments above and below the existing new marker
  12. Verify the marker appears in the correct position each time
  13. While the new marker is active leave a comment as User A
  14. Verify the new marker and new messages badge both disappear
  15. Mark another comment as unread.
  16. Navigate away from the chat
  17. Navigate back to the chat
  18. Verify the chat in the LHN shows as read
  19. Verify the New Line marker and new messages badge is still present
  20. Navigate away one more time and back
  21. Verify the new marker and new messages badge both disappear
  22. Mark another comment as unread.
  23. Navigate away from the chat
  24. Navigate back to the chat
  25. Minimize the app and reopen it
  26. Verify the new marker and new message badge both disappear
  27. Switch to a mobile view (small screen) and mark a message as unread
  28. Tap the header to reveal the LHN
  29. Verify the chat in the LHN shows as “unread”
  30. Tap the chat
  31. Verify the new marker and badge are present
  32. Tap the header to reveal the LHN
  33. Verify the chat in the LHN shows as “read”
  34. Tap the chat
  35. Verify the new marker and badge are no longer present.
  36. Scroll up in the chat so that last comment is out of view
  37. Leave a comment as User B
  38. Verify the “new messages badge” shows for User A
  39. Scroll down and verify the “new line marker” shows for User A in the correct position.
  40. Scroll back up and leave another comment as User B
  41. Scroll down as User A
  42. Verify the new marker is in the same position as it was before

Testing Deleted Comment Edge Case

  1. Start a chat between User A and User B.
  2. Ensure User A is not looking at the chat.
  3. Send a message as User B.
  4. Navigate to the chat as User A
  5. Mark the most recent message as “unread”
  6. As User B delete that message
  7. Verify that the report no longer shows as “unread” for User A and that the new marker has disappeared.
  • Verify that no errors appear in the JS console

PR Review Checklist

Contributor (PR Author) Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • iOS / native
    • Android / native
    • iOS / Safari
    • Android / Chrome
    • MacOS / Chrome
    • MacOS / Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product was added in all src/languages/* files
    • I verified any copy / text that was added to the app is correct English and approved by marketing by tagging the marketing team on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • Any functional components have the displayName property
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • I have checked off every checkbox in the PR author checklist, including those that don't apply to this PR.

PR Reviewer Checklist

The Contributor+ will copy/paste it into a new comment and complete it after the author checklist is completed

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I verified tests pass on all platforms & I tested again on:
    • iOS / native
    • Android / native
    • iOS / Safari
    • Android / Chrome
    • MacOS / Chrome
    • MacOS / Desktop
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product was added in all src/languages/* files
    • I verified any copy / text that was added to the app is correct English and approved by marketing by tagging the marketing team on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • Any functional components have the displayName property
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

QA Steps

Same as Tests above

  • Verify that no errors appear in the JS console

Screenshots

Web

Mobile Web

Desktop

iOS

Android

@marcaaron marcaaron self-assigned this Sep 9, 2022
@melvin-bot
Copy link

melvin-bot bot commented Sep 9, 2022

npm has a package.json file and a package-lock.json file. It seems you updated one without the other, which is usually a sign of a mistake. If you are updating a package make sure that you update the version in package.json then run npm install

@marcaaron marcaaron changed the title [WIP] automated navigation e2e testing (exploration) [WIP] automated navigation ui testing (exploration) Sep 11, 2022
@marcaaron marcaaron changed the title [WIP] automated navigation ui testing (exploration) [WIP] Automated tests for Unread Indicators feature Sep 12, 2022
Add mock

Fix UrbanAirship mock

Get main drawer to render

rename to navigation test

Just render the sidebar links for now

Fix tests and make ReportScreen render

Add report action and verify the action renders

Test for report-action-item visibility

Mock disconnect for Pusher

add way to set initial url for testing;

Test for drawer status

Test for the unread indicator

Mock local notification and make sure only one notification appears

upgrade test library dependencies and use act to get rid of weird warning

undo test version bumps

Fix tests

Add comment

Test behavior of unread indicator in report and sidebar

Rename back to unread indicators

Test new messages badge indicator

refactor and improve Report screen visibility check

Update mock for AppState

Clear new line indicator when report page is in view and we return from background

remove extra logs

add JSDocs

Check the LHN lastMessageText

Fix ReportTest

Check for pendingAction when deleting comment

Fix deleted comments not showing strikethrough while offline

Stop allowing deletion or editing of pendingAction add comments as users cannot be updated correctly via sequenceNumber

Rollback some changes

Use sequenceNumber for handledReportActions for now

Remove log add doc
@marcaaron marcaaron force-pushed the marcaaron-automatedUnreadTest branch from ca20849 to b199711 Compare September 13, 2022 14:16
@marcaaron marcaaron changed the title [WIP] Automated tests for Unread Indicators feature Automated tests for Unread Indicators feature and final polish Sep 13, 2022
@marcaaron
Copy link
Contributor Author

This one is testing well for me locally, but going to wait until the Auth PR for reportActionCount is deployed before taking off draft (as I am testing against that branch locally).

@neil-marcellini neil-marcellini self-requested a review September 13, 2022 17:32
Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {
[USER_B_EMAIL]: TestHelper.buildPersonalDetails(USER_B_EMAIL, USER_B_ACCOUNT_ID, 'B'),
});
await waitForPromisesToResolve();
Copy link
Contributor

Choose a reason for hiding this comment

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

Again here, adding await to each of the merge() methods should allow this waitForPromisesToResolve() to be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah in this case, I think we are not just waiting for Onyx.merge() but also some fun stuff that "eventually happens" after the merge.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, maybe this is the cause to some of my test failures that I'm seeing on my new sidebarlinks PR. I need to explore it further.

Copy link
Contributor

@marcochavezf marcochavezf left a comment

Choose a reason for hiding this comment

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

Nice! 🔥 Just a few comments

@marcaaron marcaaron marked this pull request as ready for review September 16, 2022 04:34
@marcaaron marcaaron requested a review from a team as a code owner September 16, 2022 04:34
@melvin-bot melvin-bot bot requested review from PauloGasparSv and removed request for a team September 16, 2022 04:35
@marcaaron
Copy link
Contributor Author

Reworked the tests here a bunch based on @tgolen's feedback. Feeling pretty good about the changes. The one thing I'm not 100% sure on is the usage of act(). When I tried to refactor it to not use async/await it stopped working and I saw errors that seemed to suggest we had to use async/await. Here's what I've got for an alternative...

import {act} from '@testing-library/react-native';
import waitForPromisesToResolve from './waitForPromisesToResolve';
/**
* This method is necessary because react-navigation's NavigationContainer makes an internal state update when parsing the
* linkingConfig to get the initialState. This throws some warnings related to async code if we do not wrap this call in an act().
*
* See: https://callstack.github.io/react-native-testing-library/docs/understanding-act/#asynchronous-act
*
* This apparently will not work unless we use async/await because of some kind of voodoo magic inside the react-test-renderer
* so we are suppressing our lint rule and avoiding async/await everywhere else in our tests.
*
* @returns {Promise}
*/
// eslint-disable-next-line @lwc/lwc/no-async-await
export default async function waitForPromisesToResolveWithAct() {
// eslint-disable-next-line @lwc/lwc/no-async-await
await act(async () => {
await waitForPromisesToResolve();
});
}

PauloGasparSv
PauloGasparSv previously approved these changes Sep 16, 2022
Copy link
Contributor

@PauloGasparSv PauloGasparSv left a comment

Choose a reason for hiding this comment

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

My dev env is really buggy (not sending notifications correctly, duplicating messages, only working correctly while debugging) this past week so for now I'll approve the code for the changes I've read and I'll leave the tests for the other reviewers.

Copy link
Contributor

@neil-marcellini neil-marcellini left a comment

Choose a reason for hiding this comment

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

I've only looked at this a bit so far, but it's really cool! For now, I have a few questions:

*
* @returns {RenderAPI}
*/
function signInAndGetAppWithUnreadChat() {
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB: For future tests it would be nice to have a signInAndGetApp function, maybe in TestHelpers.js. I imagine most ui tests in the future will need to start with that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great point. I think there's also a chance that a lot of UI tests could not need this since they might test more isolated individual components and not flows that require navigation. I think this would be good to add in a future PR though once there is a second test that needs this behavior.

Copy link
Contributor

@tgolen tgolen left a comment

Choose a reason for hiding this comment

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

Wow, those tests are looking great now!

@@ -0,0 +1 @@
export default {};
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be OK if we also make a requirement that any mock file also should have an explanation for why it's mocked? I think that would be super helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that sounds great. The answer won't always be the same so it is good context to add for sure and also will help people understand all the reasons why something could or should be mocked.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But also would say we can do that in a cleanup step.

Comment on lines +26 to +27
// The main app uses a NativeModule called BootSplash to show/hide a splash screen. Since we can't use this in the node environment
// where tests run we simulate a behavior where the splash screen is always hidden (similar to web which has no splash screen at all).
Copy link
Contributor

Choose a reason for hiding this comment

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

❤️ I love this comment. It makes it so clear why the mock is necessary!

// Scroll and verify that the new messages badge is also hidden
scrollUpToRevealNewMessagesBadge(renderedApp);
expect(isNewMessagesBadgeVisible(renderedApp)).toBe(false);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks great with the promises!!

Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {
[USER_C_EMAIL]: TestHelper.buildPersonalDetails(USER_C_EMAIL, USER_C_ACCOUNT_ID, 'C'),
});
return waitForPromisesToResolve();
Copy link
Contributor

Choose a reason for hiding this comment

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

Something else I did in the LHN tests was to use return Onyx.multiSet() which would prevent you from needing to deal with three separate promises here and can remove another waitForPromisesToResolve.

But in that case, I think we also need to wrap them in act() to ensure all the component tree is updated properly before making assertions. I have yet to test that out fully.

That also assume that using set() is OK (which I think it usually is for tests).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gonna leave this one - mainly because having a few different merges happen seems closer to what would happen in the UI.

import {act} from '@testing-library/react-native';
import waitForPromisesToResolve from './waitForPromisesToResolve';

/**
Copy link
Contributor

Choose a reason for hiding this comment

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

I love this comment, but what's not clear to me is when it should be used. Is there some guidance that can be added for that? Like... "Use when..." and "Do not use when..."

@marcaaron
Copy link
Contributor Author

Updated. Thanks for the additional comments @tgolen and @neil-marcellini

@melvin-bot
Copy link

melvin-bot bot commented Sep 16, 2022

npm has a package.json file and a package-lock.json file. It seems you updated one without the other, which is usually a sign of a mistake. If you are updating a package make sure that you update the version in package.json then run npm install

Copy link
Contributor

@tgolen tgolen left a comment

Choose a reason for hiding this comment

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

Looks great!

@marcaaron marcaaron dismissed marcochavezf’s stale review September 16, 2022 22:50

Gonna go for the merge here as it did not seem like there was any major pushback from @marcochavezf or @neil-marcellini

@marcaaron marcaaron merged commit 4562b5c into main Sep 16, 2022
@marcaaron marcaaron deleted the marcaaron-automatedUnreadTest branch September 16, 2022 22:51
@melvin-bot melvin-bot bot added the Emergency label Sep 16, 2022
@melvin-bot
Copy link

melvin-bot bot commented Sep 16, 2022

@marcaaron looks like this was merged without passing tests. Please add a note explaining why this was done and remove the Emergency label if this is not an emergency.

@marcaaron
Copy link
Contributor Author

Oh shoot are we supposed to add a reviewer checklist in a comment or edit the description? I’m not sure 🥲

This was not an emergency. Just the contributor checklist check.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by @marcaaron in version: 1.2.2-0 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@OSBotify
Copy link
Contributor

🚀 Deployed to production by @luacmartins in version: 1.2.2-0 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

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.

6 participants