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

(More) consistently clear old data on all ways of leaving an account #5612

Merged
merged 15 commits into from
Dec 22, 2022

Conversation

chrisbobbe
Copy link
Contributor

@chrisbobbe chrisbobbe commented Dec 14, 2022

Oops, it seems I should have thought a bit harder for #5606. 🙂 In particular, this PR picks up some threads I dropped there:

  • Give test coverage for all the reducers that use the new RESET_ACCOUNT_DATA action.
  • Clear per-account session state on RESET_ACCOUNT_DATA
  • Clear per-account settings state on RESET_ACCOUNT_DATA

Those interesting changes come after a string of code-style tightenings (and one "start using Flow" commit) for several of the test files they touch.

Fixes: #4446

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

Thanks for fixing these up! Small comments below about the tests.

Comment on lines +27 to +66
expect(
settingsReducer(
prevState,
eg.mkActionRegisterComplete({
enable_offline_push_notifications: true,
enable_online_push_notifications: true,
enable_stream_push_notifications: true,
user_settings: undefined,
}),
),
).toMatchObject({
Copy link
Member

Choose a reason for hiding this comment

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

BTW one style/formatting tweak I often find handy on expressions like this is to pull out a long sub-expression as a local variable, so that the small bits surrounding it can get put together on one line instead of spread out on separate lines each. Like this:

like this:

-      expect(
-        settingsReducer(
-          prevState,
-          eg.mkActionRegisterComplete({
-            enable_offline_push_notifications: true,
-            enable_online_push_notifications: true,
-            enable_stream_push_notifications: true,
-            user_settings: undefined,
-          }),
-        ),
-      ).toMatchObject({
+      const action = eg.mkActionRegisterComplete({
+        enable_offline_push_notifications: true,
+        enable_online_push_notifications: true,
+        enable_stream_push_notifications: true,
+        user_settings: undefined,
+      });
+      expect(settingsReducer(prevState, action)).toMatchObject({

where effectively the expect(, settingsReducer(, prevState, ),, and ) get moved from 5 separate lines to a single line, because the mere name action is short enough to fit on a line with all of them at once.

(I'm not requesting that you go back and reformat all this code in that style — doesn't matter nearly enough for that. But it's a suggestion for when making other changes in the future.)

Copy link
Contributor Author

@chrisbobbe chrisbobbe Dec 16, 2022

Choose a reason for hiding this comment

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

pull out a long sub-expression as a local variable

Makes sense. I think there are one or two small tradeoffs with this approach, though:

  • I think you'll often end up doing some by-hand "weight" comparison of each sub-expression, seeing which one(s) could save you the most lines by being pulled out like this: is it the previous state, the action, the actual or expected state, etc.
  • The right choice won't consistently be the same kind of sub-expression (previous state, action, actual or expected state), right? So in a large group of very similar tests, one ends up reading a slightly different pattern in the code.

I'm happy to try it going forward, though, and see how big these tradeoffs are in practice.

Copy link
Member

Choose a reason for hiding this comment

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

Yep, agreed.

I think in a lot of these reducer tests, pulling the action out has this effect, because the action is usually long and the previous state is usually already a local variable. So one approach on those trade-offs would be to consistently pull the action out.

Comment on lines +80 to +114
expect(
settingsReducer(
baseState,
deepFreeze({ type: SET_GLOBAL_SETTINGS, update: { theme: 'night' } }),
),
).toEqual({ ...baseState, theme: 'night' });
Copy link
Member

Choose a reason for hiding this comment

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

Another example of the same idea:

-      expect(
-        settingsReducer(
-          baseState,
-          deepFreeze({ type: SET_GLOBAL_SETTINGS, update: { theme: 'night' } }),
-        ),
-      ).toEqual({ ...baseState, theme: 'night' });
+      const action = deepFreeze({ type: SET_GLOBAL_SETTINGS, update: { theme: 'night' } });
+      expect(settingsReducer(baseState, action)).toEqual({ ...baseState, theme: 'night' });

[
deepFreeze({ ...eg.action.message_fetch_start, narrow: HOME_NARROW }),
deepFreeze({ type: MESSAGE_FETCH_ERROR, narrow: HOME_NARROW, error: new Error() }),
].reduce(caughtUpReducer, eg.baseReduxState.caughtUp),
Copy link
Member

Choose a reason for hiding this comment

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

This one seems different from the rest of what's happening in these "Tighten code style" commits — it's changing the actual test data. The old code started from a state with something at [HOME_NARROW_STR], while the new code starts from the base state (which is empty for caughtUp.)

Maybe that change in test data is fine and good, but it'd be best to keep it separate from these changes whose effects are otherwise just a smidge more complex than a pure reformatting.

[
deepFreeze({ ...eg.action.message_fetch_start, narrow: HOME_NARROW }),
deepFreeze({ type: MESSAGE_FETCH_ERROR, narrow: HOME_NARROW, error: new Error() }),
].reduce(narrowsReducer, eg.baseReduxState.narrows),
Copy link
Member

Choose a reason for hiding this comment

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

similar comment as on caughtUp test above

Comment on lines 336 to 340
const prevState = Immutable.Map({ [HOME_NARROW_STR]: [1, 2] });
expect(
narrowsReducer(prevState, {
...eg.action.message_fetch_complete,
anchor: LAST_MESSAGE_ANCHOR,
Copy link
Member

Choose a reason for hiding this comment

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

The test should mention HOME_NARROW in the action here (even though it happens to be that already in eg.action.message_fetch_complete), because it's essential to the test that it's the same narrow as in the state above.

Comment on lines 18 to 26
test('RESET_ACCOUNT_DATA', () => {
expect(
[
{ ...eg.action.message_fetch_complete, foundNewest: true, foundOldest: true },
eg.action.reset_account_data,
].reduce(caughtUpReducer, eg.baseReduxState.caughtUp),
).toEqual(eg.baseReduxState.caughtUp);
});
Copy link
Member

Choose a reason for hiding this comment

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

Hmm. I like the idea of using actions and the reducer to construct the starting state. But I find this specific way of writing the test a bit unsatisfying because

  • for the test to be effective (that is, for it to not just vacuously pass even if we were to break the thing it's testing), it's essential that the first action in this list produces some state that isn't equal to the base/initial state;
  • but it's not immediately obvious on reading the test that that's true — it depends on some details of what's in that action, and one has to read it closely and think through the meaning of caughtUp and how it should behave here to confirm that it's true;
  • relatedly, if we were to make some future change to the reducer's semantics such that this first action didn't produce anything different from the initial state — not super likely in this example, but again to reach that conclusion one has to think about the semantics of caughtUp and decide that this is fundamental to it — then the test would become vacuous, and nothing would cause us to closely reread this code and notice that.

Here's an alternative version, which preserves the property that we construct the state through the reducer rather than as an object literal:

  test('RESET_ACCOUNT_DATA', () => {
    const initialState = eg.baseReduxState.caughtUp;
    const prevState = caughtUpReducer(initialState, {
      ...eg.action.message_fetch_complete,
      foundNewest: true,
      foundOldest: true,
    });
    expect(prevState).not.toEqual(initialState);

    expect(caughtUpReducer(prevState, eg.action.reset_account_data)).toEqual(initialState);
  });

That way, the setup explicitly checks that the state before reset isn't already equal to the initial state.

(Similarly for several other reducers' tests added in this commit.)

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, yeah, makes sense. Thanks!

`initialState` wasn't a valid NarrowsState, which is

  Immutable.Map<string, $ReadOnlyArray<number>>

For what that type-checking problem is and how we plan to avoid it
in the future, see the next commit.

While we're here, make the test data a bit more interesting than the
intended value, by having the action act on a narrow that's
different from the one already in the state.
This way, we can test that the reducer doesn't go mess something up
at a different key.
In zulip#5606, I updated all the existing tests for ACCOUNT_SWITCH, etc.,
to test RESET_ACCOUNT_DATA instead, naturally.

…But that didn't give complete coverage of the RESET_ACCOUNT_DATA
action, because some tests weren't testing for ACCOUNT_SWITCH, etc.,
in the first place. So, add the coverage now.
Really an instance of zulip#4446 that I forgot about in zulip#5606, oops.

Related: zulip#4446
Really an instance of zulip#4446 that I forgot about in zulip#5606, oops.

Related: zulip#4446
@chrisbobbe chrisbobbe force-pushed the pr-data-clearing-leave-account-more branch from a1734e1 to e8ad88c Compare December 21, 2022 14:36
@chrisbobbe
Copy link
Contributor Author

chrisbobbe commented Dec 21, 2022

Thanks for the review! Revision pushed. This one includes #5622.

I'd like to get at least the changes to sessionReducer in before I continue on disallow-ancient-servers, #5102.

@gnprice
Copy link
Member

gnprice commented Dec 22, 2022

Thanks! Looks good — merging.

@gnprice gnprice merged commit e8ad88c into zulip:main Dec 22, 2022
@chrisbobbe chrisbobbe deleted the pr-data-clearing-leave-account-more branch December 22, 2022 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Consistently clear old data on all ways of leaving an account
2 participants