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

Extend exampleData to further clean up messageActionSheet tests #3946

Merged
merged 11 commits into from
Jul 20, 2020

Conversation

gnprice
Copy link
Member

@gnprice gnprice commented Mar 3, 2020

Thanks @agrawal-d for upgrading the message action sheet tests in #3824 / 4670210 to be well-typed! And thanks @ray-kraesig for reviewing and merging that PR.

After reading that commit, I saw some ways that that test file could be further improved to reduce boilerplate and also to make the test data more typical and more fully coherent (e.g., stream name and ID properties agreeing with each other.) Some of those involve extending exampleData with new features. This PR is the result.

(@agrawal-d , I can't seem to add you as a requested reviewer -- perhaps you're not in the GitHub org? But please do take a look. 🙂 )

@agrawal-d
Copy link
Member

I have a few examinations this week, I'll take a look once they are over.

@@ -1,5 +1,6 @@
// @flow strict-local
import deepFreeze from 'deep-freeze';

Copy link
Member

Choose a reason for hiding this comment

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

Oh! I didn't know about this!

I have one question - In several files, there is a newline after // @flow strict-local, in some files there isn't. I couldn't decipher any pattern. What is the correct practice?

Copy link
Member Author

Choose a reason for hiding this comment

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

In several files, there is a newline after // @flow strict-local, in some files there isn't. I couldn't decipher any pattern. What is the correct practice?

Reasonable question!

I think we don't really have a set style. :-) I can't think of a particular preference I really have, either.

I did just now go look for examples of how I've done it myself, when adding a new file or adding type-checking to a file. I used the command git lsp --author greg -S '@flow strict-local' (i.e. git log --stat -p --author greg -S '@flow strict-local' but I have an alias for log --stat -p), and used "the secret to git log -p" to conveniently browse the results.

The result is... I do both. 😄

pin_to_top: false,
audible_notifications: false,
desktop_notifications: false,
email_address: '??? make this value representative before using in a test :)',
Copy link
Member

Choose a reason for hiding this comment

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

I had a look at zulip/zulip's zerver/lib/email_mirror_helpers.py:32, here's how the stream email is generated:

def encode_email_address_helper(name: str, email_token: str, show_sender: bool=False) -> str:
    # Some deployments may not use the email gateway
    if settings.EMAIL_GATEWAY_PATTERN == '':
        return ''

    # Given the fact that we have almost no restrictions on stream names and
    # that what characters are allowed in e-mail addresses is complicated and
    # dependent on context in the address, we opt for a simple scheme:
    # 1. Replace all substrings of non-alphanumeric characters with a single hyphen.
    # 2. Use Django's slugify to convert the resulting name to ascii.
    # 3. If the resulting name is shorter than the name we got in step 1,
    # it means some letters can't be reasonably turned to ascii and have to be dropped,
    # which would mangle the name, so we just skip the name part of the address.
    name = re.sub(r"\W+", '-', name)
    slug_name = slugify(name)
    encoded_name = slug_name if len(slug_name) == len(name) else ''

    # If encoded_name ends up empty, we just skip this part of the address:
    if encoded_name:
        encoded_token = "%s.%s" % (encoded_name, email_token)
    else:
        encoded_token = email_token

    if show_sender:
        encoded_token += ".show-sender"

    return settings.EMAIL_GATEWAY_PATTERN % (encoded_token,)

I'm not at all familiar with Python, but it looks like we use a combination of the stream name in ASCII and some server configs to generate the stream name. Perhaps, this might help us in using a representative value here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah -- the easiest way to get a representative value is probably to look at some empirical data, e.g. in the console when debugging the app.

But also it's not a priority to fill in this particular property's value, because we don't have any tests that care about it. I suspect it's something we actually completely don't care about in the app 😉, and so ideally would just drop and not even store in Redux.

audible_notifications: false,
desktop_notifications: false,
email_address: '??? make this value representative before using in a test :)',
is_old_stream: true,
Copy link
Member

Choose a reason for hiding this comment

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

I couldn't find the meaning of is_old_stream in the codebase - can you please tell me? Also, it doesn't appear to be used anywhere at all ( I am probably wrong but a search in VS Code for is_old_stream gives only declarations, no uses. )

Copy link
Member Author

Choose a reason for hiding this comment

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

From a scan of the server code, it seems to mean "this stream is old enough that the stream_weekly_traffic number is meaningful", and concretely "this stream is at least 7 days old".

It's not a great name 😉

Won't matter to us unless/until we start doing something with stream_weekly_traffic too.

Copy link
Member

@agrawal-d agrawal-d left a comment

Choose a reason for hiding this comment

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

I took a look at this as you suggested - I learnt a lot. Thanks! The commit messages were very helpful, and I can definitely see how these changes make the tests and exampleData cleaner.

(Please don't mind the 'approved' tag - I accidentally clicked it, these are just general comments.)

@@ -157,7 +144,7 @@ describe('constructHeaderActionButtons', () => {

const subscriptions = deepFreeze([
{
...baseBackgroundData.subscriptions[0],
...eg.subscription,
Copy link
Member

@agrawal-d agrawal-d Mar 4, 2020

Choose a reason for hiding this comment

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

If in_home_view is already true in makeSubscription then should we still provide it here - Did you add it for readability?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah -- specifically, I left it explicit here because it's part of what this test is about.

If it were omitted, then looking at this test you wouldn't be able to tell whether the test actually tests the case it's meant to test unless you knew that eg.subscription definitely has in_home_view: true. That is the logical default, and so you could perhaps figure that it ought to be the default... but even to know it's the logical default, you have to know, and think about, some context about what it means.

Whereas when it's explicitly here, then you can look at this test code very locally, and think through it at just a very straightforward step-by-step level, and see that it does exactly what it says it does.

@@ -42,9 +36,11 @@ describe('constructActionButtons', () => {

test('show unstar message option if message is starred', () => {
const message = eg.streamMessage({ id: 1 });
// eslint-disable-next-line no-useless-computed-key
const flags = { ...baseBackgroundData.flags, starred: { [1]: true, [2]: true } };
Copy link
Member

Choose a reason for hiding this comment

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

This is a nice trick! :-)

in_home_view: true,
},
]);
const subscriptions = [{ ...eg.subscription, in_home_view: true }];
Copy link
Member

@agrawal-d agrawal-d Mar 4, 2020

Choose a reason for hiding this comment

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

Same as above - Did you add in_home_view: true for readability?

Copy link
Member Author

Choose a reason for hiding this comment

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

(same answer)

Copy link
Contributor

@rk-for-zulip rk-for-zulip left a comment

Choose a reason for hiding this comment

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

Thanks! One note and one nit; ✔️ otherwise.

@@ -440,7 +440,7 @@ export type Submessage = $ReadOnly<{|
* See also `MessagesState` for discussion of how we fetch and store message
* data.
*/
export type Message = $ReadOnly<{
export type Message = $ReadOnly<{|
Copy link
Contributor

Choose a reason for hiding this comment

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

Some thoughts, taken from a draft branch on my local machine:

@@ -493,4 +493,13 @@ export type Message = $ReadOnly<{
 
   timestamp: number,
   type: 'stream' | 'private',
+
+  // This type is not exact. The server may have sent us arbitrary data, so it's
+  // not safe to {...spread} objects of this type.
+  //
+  // TODO: destructure and validate message data received from the Zulip server,
+  // transforming it into a normalized, exact `Message` type strictly for app-
+  // internal use. (See docs/architecture/crunchy-shell.md.)
+
+  ...
 }>;

Really, none of the server-sent data types should be exact – future revisions of the server will almost certainly add new fields to some of them, and we shouldn't make promises that we know won't be kept. 😕

Copy link
Contributor

@chrisbobbe chrisbobbe Jul 20, 2020

Choose a reason for hiding this comment

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

Really, none of the server-sent data types should be exact – future revisions of the server will almost certainly add new fields to some of them, and we shouldn't make promises that we know won't be kept. 😕

When those future revisions arrive, though, we'll try to update the types accordingly, right? Having exact types with temporary out-of-date states feels better, to me, than making everything inexact. We have server and client capabilities to handle migrations gracefully, and we (aim to) keep careful notes on fields about when they were introduced / removed / changed.

Copy link
Contributor

Choose a reason for hiding this comment

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

+  // TODO: destructure and validate message data received from the Zulip server,
+  // transforming it into a normalized, exact `Message` type strictly for app-
+  // internal use. (See docs/architecture/crunchy-shell.md.)

This sounds right as an eventual goal, though.

@@ -30,9 +22,11 @@ describe('constructActionButtons', () => {

test('show star message option if message is not starred', () => {
const message = eg.streamMessage({ id: 3 });
// eslint-disable-next-line no-useless-computed-key
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's worth linking to facebook/flow#380 around here somewhere.

chrisbobbe pushed a commit to chrisbobbe/zulip-mobile that referenced this pull request Jul 10, 2020
Also demonstrate using them, to reduce boilerplate in the
newly-well-typed messageActionSheet tests.

Several values change, generally to a more representative value or
a default value.  One notable one is `in_home_view`: this means
"not muted", and so it's an important fact that its default is true.

[chris: cherry-picked from zulip#3946.]
chrisbobbe pushed a commit to chrisbobbe/zulip-mobile that referenced this pull request Jul 10, 2020
Also demonstrate using them, to reduce boilerplate in the
newly-well-typed messageActionSheet tests.

Several values change, generally to a more representative value or
a default value.  One notable one is `in_home_view`: this means
"not muted", and so it's an important fact that its default is true.

[cherry-picked from zulip#3946.]
This is a small formatting nit, maintaining a pattern we generally
observe throughout the codebase.
This looks like interesting detail that should matter somehow, but
in fact it doesn't.  Make the data totally boring instead, to help
the reader focus on the test details that do matter.
This allows us to skip reciting this boring stuff in a place where
the code doesn't actually care about it.
Also demonstrate using them, to reduce boilerplate in the
newly-well-typed messageActionSheet tests.

Several values change, generally to a more representative value or
a default value.  One notable one is `in_home_view`: this means
"not muted", and so it's an important fact that its default is true.
@chrisbobbe
Copy link
Contributor

@gnprice I'm taking a look at this now, and will likely merge soon unless you know of obstacles and haven't written them down yet. 🙂

The way this file had worked for a while was that this flags object,
with these particular arbitrary message IDs, was used globally in
the file and then these two particular tests used message IDs that
just happened to have specific relationships to those here.

That's no good. :-)  It makes these tests harder to read, by obscuring
their relationship to the data they're actually testing; and it makes
all the other tests harder to read, by raising the possibility that
they might somehow depend on these oddly-specific `flags` values.

Instead, push this data down into the specific tests that use it.
Much clearer.

Also demonstrate how to avoid the Flow error: write the key as an
actual number.  The trick is just to tell ESLint to go away when it
tries to rewrite the key as a (numeric-looking) string. [1]

[1] facebook/flow#380

[chris: link to Flow issue in code and commit message]
Also demonstrate using the messages' IDs, replacing arbitrary IDs like
1, 2, and 3 in the messageActionSheet tests.
And demonstrate using it, cutting some boilerplate in a test.

This also lets us make the emoji code and name both real, and in
agreement with each other.
This is preferable to passing properties like `display_recipient` or
`stream_id`, because it encapsulates the work of ensuring those are
kept aligned with each other.

[chris: tweak to respond to a Flow warning that probably came from
the recent React Native upgrade]
It's often convenient in a test to have a message in a given stream,
a subscription to the same stream, etc.  Our baseline example objects
have the natural relationships of this kind, and deliberately so
because it is so often convenient and because it's easy to make new
unrelated objects when that's desired.

Document those relationships, to distinguish them from any accidental
arbitrary facts about these example objects, so that we can cheerfully
rely on them in tests.
The Subscription object has both a stream name and a stream ID, and
so to keep the test data well-formed these should be kept aligned in
referring to the same stream.  Similarly the Message object.

Right now to make the test pass it's enough to set the stream name,
because we don't use the stream ID; but that's a case of zulip#3918 and
should change, after which the mismatch will be a live cause of test
failures.

We could do this by passing a stream object to `eg.makeSubscription`
and to `eg.streamMessage`.  But the most natural one to use is
`eg.stream`; and the example data already refers to `eg.stream` by
default in all the natural places, among them `eg.streamMessage()`
and `eg.subscription`, so simply use that.
This makes a little less background noise in reading these tests
from the aspects that are boring, helping the parts that are
distinctive to each test stand out.  By making the file about
25% shorter, it also helps look at several tests at once so as
to see what they're doing in common and how they differ.
@chrisbobbe
Copy link
Contributor

Merged, thanks, @gnprice! I made two small tweaks:

I linked to facebook/flow#380 as Ray suggested at #3946 (comment).

I got a Flow warning in

7d9e7f3 exampleData: In eg.streamMessage, take an optional stream.

that probably wasn't showing up when this PR was first composed; we've upgraded Flow since then. I addressed its complaint.

@chrisbobbe chrisbobbe merged commit 87f8c8e into zulip:master Jul 20, 2020
@gnprice
Copy link
Member Author

gnprice commented Jul 21, 2020

Thanks @chrisbobbe !

That Flow warning is pretty odd. I spent a few minutes investigating and wound up with a diagnosis and a cleaner workaround -- so I pushed that just now as a small tweak, d141be7.

@gnprice gnprice deleted the pr-exampledata branch March 14, 2022 19:29
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.

4 participants