-
-
Notifications
You must be signed in to change notification settings - Fork 670
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
[REVIEWABLE] Stop using legacy Context API for styles #4046
Conversation
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.
General notes:
-
I have not quite given this commit the detailed review it needs, but I have given it a first pass and identified a few issues. One thing in particular that I have not done is to compare the existing and replacement styles to ensure there are no discrepancies – except in the case of
RawLabel
, where I knew there was one and also knew exactly what to look for (q.v. infra). -
Many commit messages refer to "the parent" without making it clear that the parent commit is meant. ("the previous commit" is probably a better phrasing, even if it's technically ambiguous.)
-
Many commit messages also have things like
As earlier, I'd be happy to put these in an instance field if preferred.
which won't be important after the PR is resolved, and probably shouldn't be in the commit message of a commit that's intended to be merged. It would be better to put this sort of thing in a comment in the PR itself. (Or, alternatively, reword the comment so that it still makes sense in three years.)
-
StreamItem [nfc]: Remove
isSelected
prop that is never passed.For reference, this was introduced back in PR Improve Stream sidebar style. Fixes #171 #199. See here for a bit of background, including why this feature was left in. (TL;DR: a) apparently we used to mimic the webapp's layout more strongly, with the left and right sidebars being in drawers you could pull out from either side of the screen, and b) Boris wanted to reuse this capability for another feature. Those are both gone, though.)
Specific things that break for me:
-
The icons and right-chevrons on the Settings page. These do not change when the setting is toggled; instead they remain the same color they were at app startup. (I haven't looked into why this is.)
-
Any
RawLabel
. These do change when the theme changes – but they're black in night mode (well,#01141C
), and a light grey in the default theme (#757575
).- Some experimentation suggests that this is actually a constant translucent color,
rgba(0,0,0,0.54)
. Why this is the default text color for RCTText elements, I couldn't possibly guess.
- Some experimentation suggests that this is actually a constant translucent color,
-
The Android status bar smoothly transitions between its night- and default-mode colors, but the rest of the app just snaps from one style to the next. (This PR doesn't cause that, just makes it more noticeable. If it's nontrivial to fix, it may be best to file a separate issue.)
src/__tests__/lib/exampleData.js
Outdated
}); | ||
}; | ||
|
||
export const subscription: Subscription = makeSubscription(); |
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 doesn't seem to be used anywhere.
src/__tests__/lib/exampleData.js
Outdated
args: { | ||
stream?: Stream, | ||
color?: string, | ||
in_home_view?: boolean, | ||
pin_to_top?: boolean, | ||
audible_notifications?: boolean, | ||
desktop_notifications?: boolean, | ||
email_address?: string, | ||
is_old_stream?: boolean, | ||
push_notifications?: null | boolean, | ||
stream_weekly_traffic?: number, | ||
} = {}, | ||
): Subscription => { |
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 could be more simply rendered as $Shape<Subscription>
, I think – e.g.:
export const makeSubscription = (args: $Shape<Subscription>): Subscription =>
Object.freeze({ ...baseSubscription, ...args });
... where baseSubscription
is of course some static data.
Also, unless you expect other tests to use this constructor, I'd suggest leaving it in subscriptionsReducer-test.js
rather than in the shared-data file.
]); | ||
}); | ||
|
||
test('when no `subscriptions` data is given reset state', () => { |
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 not sure how this test is "redundant with proper typechecking".
(Consider an implementation that appends subscriptions provided by REALM_INIT
to those in the subscriptions-state. This would pass typechecking and even the preceding test, but would fail this one.)
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 sure makes sense.
In this commit, my reason for removing these two tests (and, whether or not it's right, I should have written it down) is simply that, in each of them, it's impossible to represent their test input without defying our type definitions. So, in live code, the same bad input will be caught by the type checker, making any Jest tests that use that input redundant. What do you think; is this wrong, in general?
On a closer look, something seems weird with the bit of the reducer this test is for. I would expect Flow to complain after the ||
, where action.data.subscriptions
is falsy —
case REALM_INIT:
return action.data.subscriptions
|| initialState; // should see a Flow error, but there is none
— but it doesn't. An inspection of RealmInitAction, following the aliases all the way down, shows that data.subscriptions
is not an optional property, nor is it unioned with void
.
Aha, and, indeed, a type-checked version of this test passes Flow with no errors, which I definitely did not expect, following my reading of RealmInitAction:
test('when no `subscriptions` data is given reset state', () => {
const initialState = deepFreeze([sub1]);
const action = deepFreeze({
...eg.action.realm_init,
data: {
...eg.action.realm_init.data,
subscriptions: undefined, // no problems here! Why?
},
});
const expectedState = [];
const actualState = subscriptionsReducer(initialState, action);
expect(actualState).toEqual(expectedState);
});
So, I guess, other questions:
- Am I reading our type definitions correctly, that
action.data.subscriptions
must never be missing, and - If so, how can we get away with expressing it as missing, anywhere? Maybe there's a Flow bug with spread types affecting the InitialData type? And, I guess,
- Assuming I'm reading the type definitions correctly, and that they are our best knowledge of what will come from the server, do we even need to defend against
data.subscriptions
being missing, in our live code?
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.
So let me pull the emergency brake here, because I completely missed that this test did not have data: {... subscriptions: []}
, but merely data: {}
.
(I do think a test with the former would be valuable, and that it would be better to turn this test into that test than to eliminate it entirely; but that's a very different thing than what I said earlier. 😓)
subscriptions: undefined, // no problems here! Why?
Oh, wow. That's horrifying. Let me poke at that for a bit.
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.
— found it. Your guess in point 2 was on the right track, although the bug is in spread values rather than in spread types.
Like several other Flow bugs involving spread, this is fixed in v0.111.1, which will be available to us in RN 0.62.
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 OK, no problem! Thanks for confirming this is a known Flow bug; glad there's an eventual solution, and good to be aware of the problem in the meantime.
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.
Please add a comment with that result, so we can memoize that archaeological work 🙂
I was just doing that as we speak. Thanks, Ray! 🙂
— as in, I was just putting it in a commit message. Would you say we should have a code comment as well?
I'm seeing a subtle lesson coming from this.
On the one hand, it was likely skepticism and caution, in healthy doses, that motivated adding the || initialState
check, in several places — skepticism and caution are good motivators that often drive the best design / code choices.
...but || initialState
is dead code, and dead code should not exist (particularly when it makes our types look funny), and it's not at all trivial to prove that it's dead. You can't just make it go away with some Boolean algebra, for example. While the check was well-intentioned, its net effect has been negative, causing us all to go and do these investigations.
So, I'll keep this case in mind when I try to add "crunchy shell" checks, in future, which might turn out to be dead code that takes work to uproot. I sincerely hope that 923e2b4 doesn't come back to bite us in this way; some small amount of context is in the latter half of #3966 (comment).
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 in, I was just putting it in a commit message. Would you say we should have a code comment as well?
Yeah, on the type definition. See examples in initialDataTypes.js
(for the few types there that are commented...) and in modelTypes.js
. That's a handy place to later look up what we know.
So, I'll keep this case in mind when I try to add "crunchy shell" checks, in future, which might turn out to be dead code that takes work to uproot.
Yeah, so I would describe this || initialState
check not as part of a "crunchy shell" but rather as the kind of thing we end up with in the middle of the app as a result of a soft, non-crunchy shell:
In our JS code and the data it handles from HTTP requests it makes to the Zulip server, there are a lot of spots that would benefit from a crunchy shell -- a lot of spots where the code in the middle of the app either has random checks and conditionals scattered through, or bugs from the lack of those.
And yep, this is exactly the kind of thing that can then happen with such code :)
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.
Yeah, so I would describe this
|| initialState
check not as part of a "crunchy shell" but rather as the kind of thing we end up with in the middle of the app
It sounds like the same would apply to 923e2b4, no? From rereading the "crunchy shell" doc, it sounds like the ultimate goal is to get things thoroughly vetted in a layer before the data even reaches our Redux reducers. If doing these checks in a reducer is considered to be "in the middle of the app", then 923e2b4 is thoroughly guilty.
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.
One small bug that story brings out is that we don't have
realm_user_groups
marked as optional in our API types, and we should:export type InitialDataRealmUserGroups = {| realm_user_groups: UserGroup[], |};
along with I guess a comment that it was new in 1.8.0 (or whatever).
There's lots of other stuff in that
initialDataTypes
module that needs a review for similar questions; the bulk of it is entirely uncommented. That's well afield from this PR, though.
Opened as #4073.
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 sounds like the same would apply to 923e2b4, no? From rereading the "crunchy shell" doc, it sounds like the ultimate goal is to get things thoroughly vetted in a layer before the data even reaches our Redux reducers. If doing these checks in a reducer is considered to be "in the middle of the app", then 923e2b4 is thoroughly guilty.
Yeah, it's true that isn't the ideal spot in the app for the check to be happening. It still feels better to me in the case of 923e2b4 than here. Maybe one reason is that for 923e2b4 we'd looked and determined that the guarantee we needed wasn't something clearly specified in the API; whereas here it is, and so adding the check gives me the sense that the author just hadn't bothered to determine what the API really was and base the code on that.
const ensureValidColor = (sub: Subscription): Subscription => { | ||
if (sub.color === '') { | ||
return { ...sub, color: HALF_COLOR }; | ||
} else { | ||
return sub; | ||
} | ||
}; |
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 should have a robust, reusable predicate to tell whether a given string is a valid color.
I believe we do – or at least the core component thereof:
import Color from 'color';
const isColor = (s: string) => { try { Color(s); return true; } catch (e) { return false; } }
const asColor = (s: string): Color | void => { try { return Color(s).toString(); } catch (e) { return; } }
This is an NPM library that we're already using in various places. It depends on color-string
(by the same author), which might be easier to use directly, but isn't a direct dependency of ours at the 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.
Following from Greg's comment at #4046 (comment) I think we don't need this (nor the empty-string check in this first revision).
src/common/Screen.js
Outdated
@@ -5,7 +5,9 @@ import type { Node as React$Node } from 'react'; | |||
import { StyleSheet, View, ScrollView } from 'react-native'; | |||
import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet'; | |||
|
|||
import type { Context, Dimensions, LocalizableText, Dispatch } from '../types'; | |||
import type { ThemeColors } from '../styles'; |
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.
Addressing the commit message:
I'd be happy to move all of
componentStyles
to an instance field; I just think it's most natural for the non-theme-dependent styles to live together.
That's probably for the best, just for consistency's sake. If you're concerned about this not updating when the theme updates, that's something that should probably be addressed across the codebase...
Also, I don't think this component is even visible when the user goes into their settings to change the theme.
... and in particular, I would really prefer to never have to care whether or not this is true.
[...] Even so, I think this is the correct way to handle a value that, in theory, could change at any time.
What you might want to do, for testing purposes, is to set up a temporary component in (e.g.) AppEventHandlers
which basically just does this:
while (true) {
await sleep(2500);
await dispatch(settingsChange({theme: 'night'}));
await sleep(2500);
await dispatch(settingsChange({theme: 'default'}));
}
... and then browse through various screens with that running, to confirm that nothing breaks unexpectedly.
I've done exactly this as part of this review. Except for the things that already had problems with manual theme changes, everything seemed fine; I was even able to compose and send a message without having my work interrupted by the theme change.
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.
Also, I don't think this component is even visible when the user goes into their settings to change the theme.
... and in particular, I would really prefer to never have to care whether or not this is true.
This is precisely what I meant by what follows the sentence you've quoted: "Even so, I think this is the correct way to handle a value that, in theory, could change at any time." And what prompted me to avoid the latent bug in my own code. (Though, as we've seen, I neglected to fix the pre-existing instances I'd been aware of and forgot about.)
What you might want to do, for testing purposes, is to set up a temporary component in (e.g.)
AppEventHandlers
which basically just does [...]
Thanks for this idea and example code, I'll use it before I resubmit. 🙂
src/styles/theme.js
Outdated
export type ThemeColors = {| | ||
color: string, | ||
backgroundColor: string, | ||
cardColor: string, | ||
dividerColor: string, | ||
|}; | ||
|
||
export const themeColors: { [string]: ThemeColors } = { | ||
export const themeColors: { [name: ThemeName | 'light']: ThemeColors } = { |
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 not aware of any reason that anyone should be using the name 'light'
to access this theme. Is there any reason not to just replace light
with 'default'
here?
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.
If you mean 'light' as it occurs in the one line I changed, here, yes: the object literal assigned to const themeColors
contains a light
property.
If you're referring to the light
property in that object literal, below my diff, yeah, I don't see any reason this shouldn't just be default
. Looking at 6d5cf60^, there used to be an object-as-map called themeNameToObject
, but, at least at that commit, it was only used in one place, and, there, only an index of type ThemeName was used on it, and ThemeName has always and forever been 'default' | 'night'.
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.
Hmm. Taking a look at Apple's HIG document on Dark Mode, though, it makes it sound like "default" semantics shouldn't apply to either theme at the application level, but rather at the device level:
In Settings, people can choose Dark Mode as their default interface style and schedule automatic changes between the appearance modes. Because people make these choices at a systemwide level, they generally expect all apps to respect their preferences.
Perhaps we might actually deprecate the "default" name, and use "night" or "light" everywhere (with our ThemeName
type being 'light' | 'night'
instead of 'default' | 'night'
as it is currently), at or on the path to #4009.
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.
Perhaps we might actually deprecate the "default" name, and use "night" or "light" everywhere (with our
ThemeName
type being'light' | 'night'
instead of'default' | 'night'
as it is currently), at or on the path to #4009.
That reasoning makes sense to me.
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.
Just mentioned this plan at #4009 (comment).
src/common/RawLabel.js
Outdated
styles: () => null, | ||
}; | ||
static contextType = ThemeContext; | ||
context: ThemeColors; |
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 declares context
, but then never uses it.
(See top-level comment for the consequences of this.)
f465f5e StreamItem: Defend against empty strings for
Thanks for pinning that down! That makes a lot of sense, but I think I'd never quite noticed that was the case. (Because of that same convoluted plumbing, at least in part.) One quick step to help consolidate the gains from you having determined that would be: let's add some minimal jsdoc on this component and in particular document that That isn't as good as it would be to make that inherent in the way the API works, but it has some real value -- it sets a marker in the ground, facing both inward to the implementation and outward to the call sites, of what they should rely on and what would be a bug. (And then see thoughts below on making that inherent in the way the API works.)
This part isn't necessary. The purpose of the With what you've learned above, there isn't such a case, so we can simply drop these conditions. If we have a subscription with a
I think the direction I'd be inclined to go for further improving this
|
commit 6149a7e
I agree it'd be good for the two names to agree. It's unfortunate that this makes the name more verbose, though. And I kind of feel Or for that matter even while there isn't -- this object is the whole data of the theme. "Theme colors context" sounds like there's some other part of the theme, apart from the colors, which is being left out and is to be found somewhere else. Perhaps leave this edit: ... Or even just |
Thanks for the review!
But, regardless of the purpose, the effect of the I was reminded of a situation over at #3966 (comment), where I figured it would be easier to add such a defense than to verify the server's behavior (and the local branch I mention above, #4046 (comment), experiments with this case): I said:
It may be that I'm over- (or, alas, under-) thinking things in this particular case. And I can't think of a good motivation for the server to give us an empty string there. So I'm fine to leave out the defense against an empty string, I just wanted to explain my thought process a bit more. |
True! (and good question!) I guess one point of difference between that situation and this one is: in that one, the PR added some logic that completely enforced the structure we needed. Here, the structure we're assuming the The check against |
Quite true. It’s a bit hard to follow in the GitHub UI, but Ray’s mention of the
Also true. And in the sole case I know offhand where we do get an empty string from the server, when an empty value is meant, the server is moving toward the cleaner behavior of just dropping the property, IIRC (and this is easy to define in |
@gnprice, for the rest of your guidance in #4046 (comment):
I really appreciate that you've pointed out one simpler, but still valuable action, and another that's more complete and correct, but more involved. As I hope to do most of the time, I tended toward the latter. 🙂 After an attempt at this, though, I think it's probably best to do the more involved version after (or perhaps as part of) #3767, rather than in this PR. While I didn't find anything to contradict my discovery about the For now, I'll add that bit of JSDoc. |
That's reasonable. I'm not sure what other data, where the two themes are light and dark, but I'm fine to leave it open this way.
Hmm, indeed, this is a stronger argument for keeping
I'm inclined toward |
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.
... so apparently these two comments have been sitting here, "Pending
", ever since I wrote them.
This is plausibly GitHub's fault, but only plausibly. Sorry. 😓
]); | ||
}); | ||
|
||
test('when no `subscriptions` data is given reset state', () => { |
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.
So let me pull the emergency brake here, because I completely missed that this test did not have data: {... subscriptions: []}
, but merely data: {}
.
(I do think a test with the former would be valuable, and that it would be better to turn this test into that test than to eliminate it entirely; but that's a very different thing than what I said earlier. 😓)
subscriptions: undefined, // no problems here! Why?
Oh, wow. That's horrifying. Let me poke at that for a bit.
]); | ||
}); | ||
|
||
test('when no `subscriptions` data is given reset state', () => { |
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.
— found it. Your guess in point 2 was on the right track, although the bug is in spread values rather than in spread types.
Like several other Flow bugs involving spread, this is fixed in v0.111.1, which will be available to us in RN 0.62.
Thanks for the review, @ray-kraesig, if I haven't said so already! @ray-kraesig said:
I agree that "the parent" is an unnecessarily vague shorthand for "the parent commit". I think "the parent commit" is about equal to "the previous commit" in our rebase-oriented workflow; do you think that's wrong? For a different but related concern, I've been avoiding "the child commit" since Greg pointed out why I should, here:
|
@ray-kraesig said:
I'll make a new issue once the present issue is resolved, as fixing this will mean touching all of these components, as well as the ones that have already been using the new Context API. |
I don't have a strong preference between these two names, but on this point I do have a tip you might find useful for other things! Whatever search UI you're using should have an option to search for whole words only. For
Sometimes for longer names I'll lazily type just part of it and leave off the -w, but as a habit I usually use -w. Better yet is a search based on actually parsing the code, to find references that are really to the same thing and not to an unrelated thing that happens to have the same name. I regularly use Shift+F12 "Show References" in VS Code, which provides those semantics. |
Yay, thanks for pointing this out! I haven't been in the habit of using the whole-word setting in VSCode, so I don't usually remember it when it would be helpful. (I will from now on, though.) I haven't really used I very regularly use Given that I use But since you said you don't strongly favor one over the other, my next revision has it as |
Oh, if there's a functional difference between the terms, it eludes me. It's only that:
I sincerely doubt I will ever feel the need to comment on any choice you should make between these two hereafter, regardless of what that choice is or even how consistently you choose either. 🙂
You may also find something along the lines of |
I did a search through the whole project for all occurrences of the string "StreamItem". For each place in the JSX where the component was created, I did the following checks: A) By looking at the code, confirmed that we're not passing that prop ourselves, except for once, in `StreamList`. The value passed there is based on `StreamList`'s prop `selected`, which is similarly unused (so, remove that too). B) By looking at the React Native documentation, confirmed that FlatList and SectionList (under which StreamItem appears, and which we don't implement ourselves) don't inject this prop secretly, as would possible with, e.g., `React.Children` and `cloneElement`. C) Confirmed (A) and (B) empirically, via the `react-devtools` UI, by checking that isSelected isn't there. This was introduced in ec64098, for PR zulip#199. As Ray points out [1], much more recent discussion [2] suggests that we knew it was dead code, but wanted to preserve it for future functionality. As Ray summarizes, """ a) apparently we used to mimic the webapp's layout more strongly, with the left and right sidebars being in drawers you could pull out from either side of the screen, and b) Boris wanted to reuse this capability for another feature. Those are both gone, though. """ [1]: zulip#4046 (review) [2]: zulip#3462
A response to // TODO: confirm these '' cases are irrelevant, and remove. , added in 84977a9. With an effort, which was due to the somewhat convoluted plumbing, I just verified that, when we *do* pass a value for the `color` or `backgroundColor` prop to StreamItem, it is always the `color` property from a Subscription. We'd like to add this to an explicit contract for these props. Greg reports [1] that the `!== ''` checks from 84977a9 were there because we weren't sure whether there was an intentional use of an empty string in our code, to stand in for a missing/nullish color. Now that we know that there isn't, we can remove the checks. Contracts for `isMuted` and `isPrivate` are similarly induced from the call sites. So, add a JSDoc to that effect, to (paraphrasing Greg [1] again) mark, for the sake of the implementation and the call sites, of what they should rely on and what would be a bug. As Greg points out, this isn't as good as making these requirements inherent in the way the API works. We'd like to use a prop like `subOrStream: Subscription | Stream` and glean data like `name` and `isMuted` from that. Then the nested ternaries in `iconColor` and `textColor` (which I'm still not sure I fully understand, even after some pure refactors in recent commits) can be much simplified with, e.g., a prop with something like an enum: `colorStyle: 'background' | 'icon' | 'none'`. But this better solution will be much more accessible after (or as part of) zulip#3767. Currently, among StreamItem's three call sites, its data isn't only provided by a Stream or a Subscription object, but also a "PseudoSubscription" (I'm not sure what that is) and an UnreadStreamItem, so a relatively simple `subOrStream` prop isn't feasible. [1]: zulip#4046 (comment)
…_INIT. Our `RealmInitAction` type is quite clear that `subscriptions` is always present. That type is *meant* to represent our best knowledge of what will come from the server, but it's important to verify. Greg did some digging [1] and reported that the `|| initialState` check appeared to be modeled on the solution to a problem that didn't, after all, affect `subscriptionsReducer`: """ Looks like that originates in 462215c. Following links, that was from zulip#2878 which fixed zulip#2859. The latter issue was that, in a _different_ reducer, we needed exactly this because the data in question was about a recent feature that some servers didn't support and so the property (namely `realm_user_groups`) would be missing. """ Ray also did some archaeological investigation to see if it was reasonable to trust that the server has never handed out `null` or another falsy value, and concluded [2], """ As of at least 7a930afa0 (2017-02-20), and probably much earlier, it should always send at least `[]` for `subscriptions` so long as `subscription` is specified as a desired event type in the `/register` call. (I'm pretty sure that's true both at that commit and in today's code; but there _are_ a lot of intermediate changes that I haven't checked fully, so I'm less sure it's been true the entire time.) """ [1]: zulip#4046 (comment) [2]: zulip#4046 (comment)
e4647d9
to
b14011d
Compare
Best seen in the ThemeContext declaration, below, it's inconsistent for one name to refer to "colors" and one not to. Furthermore, "colors" makes it sound like this is the only theme-related data that exists (which isn't necessarily true), or, worse, that there's some other theme-related data, besides the colors, that's found elsewhere, under a different name. This type encompasses all data about the theme, so, rename it to ThemeData. Just `Theme` was considered. `ThemeData` was chosen, not because of any semantics in Data, but because it's friendlier for newer contributors who might not be using their "whole-word" setting when doing a text search through the codebase. With `Theme`, a search without using that setting would newly contain a lot of noise from, e.g., ThemeName, ThemeProvider, ThemeContext, etc. Greg explains some useful searching strategies at zulip#4046 (comment).
OK, @ray-kraesig, @gnprice, so I've pushed my next revision, with a [DO NOT MERGE] commit at the tip (see below). I believe I've considered and responded to everything here (there's been a lot; thanks, both of you!), either with code/commit message changes, or by replying here on the PR. I've done the testing Ray recommended above, with the example in a Gist, auto-cycling between the themes every few seconds so you can explore the app and see that things are live-updating properly; this is included at the tip, tagged [DO NOT MERGE]. My hope in including this is that it's convenient for Greg (or you, if you've lost your working setup) to be able to test with this nonce component out-of-the-box. Everything looks as expected, for me. Ray, I think you're inclined to maintain the invariant that any PR branch with a [DO NOT MERGE] commit should be treated entirely as a draft, with a [REVIEWABLE] tag in the title, as applicable. I've done both here — just know that that single commit is the only reason for these markers (EDIT: and, alas, the CI failures 😞 — I'll fix those if it's important to you, but I know you can run all tests on every candidate-for-mergeable commit, and see them pass, with @ray-kraesig said:
I did another pass of this, since the |
@ray-kraesig said:
Mm, true! And I guess I wasn't as tuned-in to the wild. I could see the argument for using "previous commit", to maintain consistency with (a) avoiding "child", and (b) that wild. |
It looks like Cmd+click is "Go to Definition". That's also very handy (I use F12 for it, and do that often), but different -- "Go to References" finds all the places where the thing is used, or referred to, instead of just the (usually one) place it's defined. Looks like you can get the latter from the right-click menu, as well as Shift+F12. There's also "Find All References", which gets the same results as "Go to References" but puts the results in the sidebar. The docs don't say so, but that's also in the right-click context menu (as well as being at Shift+Alt+F12.) |
It doesn’t seem to be documented, but the behavior of “Cmd+Click” seems to vary, so that it shows “references” if you use it on a definition, and it shows “definition(s?)” otherwise. (I’m not at the computer at the moment or I’d double-check.) |
Thanks again! I've gone and merged 10 of these commits, along with a small added commit at the end: The only other adjustment I made is that I reordered "StreamAutocomplete [nfc]: Rename Still reviewing the rest, but this will help move things forward and also reduce the work of future rebases and reviews 🙂 |
I did a search through the whole project for all occurrences of the string "StreamItem". For each place in the JSX where the component was created, I did the following checks: A) By looking at the code, confirmed that we're not passing that prop ourselves, except for once, in `StreamList`. The value passed there is based on `StreamList`'s prop `selected`, which is similarly unused (so, remove that too). B) By looking at the React Native documentation, confirmed that FlatList and SectionList (under which StreamItem appears, and which we don't implement ourselves) don't inject this prop secretly, as would possible with, e.g., `React.Children` and `cloneElement`. C) Confirmed (A) and (B) empirically, via the `react-devtools` UI, by checking that isSelected isn't there. This was introduced in ec64098, for PR #199. As Ray points out [1], much more recent discussion [2] suggests that we knew it was dead code, but wanted to preserve it for future functionality. As Ray summarizes, """ a) apparently we used to mimic the webapp's layout more strongly, with the left and right sidebars being in drawers you could pull out from either side of the screen, and b) Boris wanted to reuse this capability for another feature. Those are both gone, though. """ [1]: #4046 (review) [2]: #3462
The last use of this was removed in the parent commit.
Per zulip#1946, we're moving to the new React Context API (https://reactjs.org/docs/context.html). Use the same strategy as in recent commits, for Input and ChatScreen: put the constant parts of the style in an instance field, and compute the theme-dependent parts in the `render` method.
Per zulip#1946, we're moving to the new React Context API (https://reactjs.org/docs/context.html). Use the same strategy as in recent commits, for Label, Input, and ChatScreen: put the constant parts of the style in an instance field, and compute the theme-dependent parts in the `render` method.
The last two uses of this were removed in the two previous commits.
Continuing the work of recent commits toward zulip#1946. These last few consumers are simply using `background` or `backgroundColor` from the old style context, and no theme-independent styles, so, fix them together in one commit. Then, to help verify that all consumers are removed: Atomically, in this commit, remove the default-exported function of `miscStyles`, and its only call site, at `stylesFromTheme`. `stylesFromTheme` is the single point where all the data is collected and distributed as `styles` via the legacy Context API. (A similar removal, for `navStyles`, is bfdde6f.)
Compute theme-dependent styles in the render method. As foreshadowed in a previous commit that touched src/common/Screen.js, fix the remaining latent bugs where the placement of theme-dependent styles in a class initializer, instead of the `render` method, meant that these styles don't live-update when the theme changes, unless the component is unmounted and a new instance is created to replace it. The `key={theme}` hack accomplished this replacement, so this bug hasn't been visible. But that hack will disappear in an upcoming commit, so this change needs to be made before then. These bugs were introduced in 51dd1b3: LineSeparator.js a4e0f23: OptionButton.js and OptionRow.js f6ddc2d: OptionDivider.js
As foreshadowed in this series, we get to remove the `key={theme}` hack (along with some boilerplate to get things all working with the old Context API). So, also, remove a bit of documentation that explained the hack. The section was necessary, previously, but it hindered an easy understanding of the "Pure Component Principle" and our use of it, which is a main idea of that doc. Fixes: zulip#1946
We'll want to deprecate the 'default' name; as I explain on zulip#4009 [1], any 'default' semantics should only apply the device level, at least on iOS, not within the app. [1]: zulip#4009
Best seen in the ThemeContext declaration, below, it's inconsistent for one name to refer to "colors" and one not to. Furthermore, "colors" makes it sound like this is the only theme-related data that exists (which isn't necessarily true), or, worse, that there's some other theme-related data, besides the colors, that's found elsewhere, under a different name. This type encompasses all data about the theme, so, rename it to ThemeData. Just `Theme` was considered. `ThemeData` was chosen, not because of any semantics in Data, but because it's friendlier for newer contributors who might not be using their "whole-word" setting when doing a text search through the codebase. With `Theme`, a search without using that setting would newly contain a lot of noise from, e.g., ThemeName, ThemeProvider, ThemeContext, etc. Greg explains some useful searching strategies at zulip#4046 (comment).
b14011d
to
6b5f32e
Compare
Yep, that's right! Thanks for the review and merging. |
|
||
const subscriptionPropertiesFromStream: $Shape<Subscription> = deepFreeze(eg.stream); | ||
|
||
export const makeSubscription = (args: $Shape<Subscription> = {}): Subscription => |
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.
With Flow v0.113.0, which we'll get in the RN v0.61 -> v0.62 upgrade (zulip#3782), red flags are raised about this test. Strangely [1], the test was allowed to expect the `zulipVersion` on an Account to be undefined. In fact, the type for it is `ZulipVersion | null`. The test is based on the false premise that a REALM_INIT action might not have a `zulipVersion` property. `RealmInitAction` states that it will always be there, as a ZulipVersion instance. So, delete the test. If we ever find that there's a legitimate case where it is missing, we'll fix the RealmInitAction type accordingly, and a new test can proceed from there. [1] I think the culprit is very likely facebook/flow#6715, which Ray reported as being fixed in Flow v0.111.1, at zulip#4046 (comment)
Best seen in the ThemeContext declaration, below, it's inconsistent for one name to refer to "colors" and one not to. Furthermore, "colors" makes it sound like this is the only theme-related data that exists (which isn't necessarily true), or, worse, that there's some other theme-related data, besides the colors, that's found elsewhere, under a different name. This type encompasses all data about the theme, so, rename it to ThemeData. Just `Theme` was considered. `ThemeData` was chosen, not because of any semantics in Data, but because it's friendlier for newer contributors who might not be using their "whole-word" setting when doing a text search through the codebase. With `Theme`, a search without using that setting would newly contain a lot of noise from, e.g., ThemeName, ThemeProvider, ThemeContext, etc. Greg explains some useful searching strategies at zulip#4046 (comment).
I'm opening a new PR for the remaining commits, and closing this one, as it has gathered several (helpful! but alas) irrelevant conversations. EDIT: Opened as #4199. |
Best seen in the ThemeContext declaration, below, it's inconsistent for one name to refer to "colors" and one not to. Furthermore, "colors" makes it sound like this is the only theme-related data that exists (which isn't necessarily true), or, worse, that there's some other theme-related data, besides the colors, that's found elsewhere, under a different name. This type encompasses all data about the theme, so, rename it to ThemeData. Just `Theme` was considered. `ThemeData` was chosen, not because of any semantics in Data, but because it's friendlier for newer contributors who might not be using their "whole-word" setting when doing a text search through the codebase. With `Theme`, a search without using that setting would newly contain a lot of noise from, e.g., ThemeName, ThemeProvider, ThemeContext, etc. Greg explains some useful searching strategies at zulip#4046 (comment).
With Flow v0.113.0, which we'll get in the RN v0.61 -> v0.62 upgrade (zulip#3782), red flags are raised about this test. Strangely [1], the test was allowed to expect the `zulipVersion` on an Account to be undefined. In fact, the type for it is `ZulipVersion | null`. The test is based on the false premise that a REALM_INIT action might not have a `zulipVersion` property. `RealmInitAction` states that it will always be there, as a ZulipVersion instance. So, delete the test. If we ever find that there's a legitimate case where it is missing, we'll fix the RealmInitAction type accordingly, and a new test can proceed from there. [1] I think the culprit is very likely facebook/flow#6715, which Ray reported as being fixed in Flow v0.111.1, at zulip#4046 (comment)
Best seen in the ThemeContext declaration, below, it's inconsistent for one name to refer to "colors" and one not to. Furthermore, "colors" makes it sound like this is the only theme-related data that exists (which isn't necessarily true), or, worse, that there's some other theme-related data, besides the colors, that's found elsewhere, under a different name. This type encompasses all data about the theme, so, rename it to ThemeData. Just `Theme` was considered. `ThemeData` was chosen, not because of any semantics in Data, but because it's friendlier for newer contributors who might not be using their "whole-word" setting when doing a text search through the codebase. With `Theme`, a search without using that setting would newly contain a lot of noise from, e.g., ThemeName, ThemeProvider, ThemeContext, etc. Greg explains some useful searching strategies at zulip#4046 (comment).
Best seen in the ThemeContext declaration, below, it's inconsistent for one name to refer to "colors" and one not to. Furthermore, "colors" makes it sound like this is the only theme-related data that exists (which isn't necessarily true), or, worse, that there's some other theme-related data, besides the colors, that's found elsewhere, under a different name. This type encompasses all data about the theme, so, rename it to ThemeData. Just `Theme` was considered. `ThemeData` was chosen, not because of any semantics in Data, but because it's friendlier for newer contributors who might not be using their "whole-word" setting when doing a text search through the codebase. With `Theme`, a search without using that setting would newly contain a lot of noise from, e.g., ThemeName, ThemeProvider, ThemeContext, etc. Greg explains some useful searching strategies at zulip#4046 (comment).
Best seen in the ThemeContext declaration, below, it's inconsistent for one name to refer to "colors" and one not to. Furthermore, "colors" makes it sound like this is the only theme-related data that exists (which isn't necessarily true), or, worse, that there's some other theme-related data, besides the colors, that's found elsewhere, under a different name. This type encompasses all data about the theme, so, rename it to ThemeData. Just `Theme` was considered. `ThemeData` was chosen, not because of any semantics in Data, but because it's friendlier for newer contributors who might not be using their "whole-word" setting when doing a text search through the codebase. With `Theme`, a search without using that setting would newly contain a lot of noise from, e.g., ThemeName, ThemeProvider, ThemeContext, etc. Greg explains some useful searching strategies at zulip#4046 (comment).
Best seen in the ThemeContext declaration, below, it's inconsistent for one name to refer to "colors" and one not to. Furthermore, "colors" makes it sound like this is the only theme-related data that exists (which isn't necessarily true), or, worse, that there's some other theme-related data, besides the colors, that's found elsewhere, under a different name. This type encompasses all data about the theme, so, rename it to ThemeData. Just `Theme` was considered. `ThemeData` was chosen, not because of any semantics in Data, but because it's friendlier for newer contributors who might not be using their "whole-word" setting when doing a text search through the codebase. With `Theme`, a search without using that setting would newly contain a lot of noise from, e.g., ThemeName, ThemeProvider, ThemeContext, etc. Greg explains some useful searching strategies at zulip#4046 (comment).
Best seen in the ThemeContext declaration, below, it's inconsistent for one name to refer to "colors" and one not to. Furthermore, "colors" makes it sound like this is the only theme-related data that exists (which isn't necessarily true), or, worse, that there's some other theme-related data, besides the colors, that's found elsewhere, under a different name. This type encompasses all data about the theme, so, rename it to ThemeData. Just `Theme` was considered. `ThemeData` was chosen, not because of any semantics in Data, but because it's friendlier for newer contributors who might not be using their "whole-word" setting when doing a text search through the codebase. With `Theme`, a search without using that setting would newly contain a lot of noise from, e.g., ThemeName, ThemeProvider, ThemeContext, etc. Greg explains some useful searching strategies at zulip#4046 (comment).
EDIT: This isn't fixed yet; I've closed this PR and opened #4199, as this one has gathered several irrelevant conversations.
Fixes: #1946
Fixes: #3994
If you get to the commit "StreamItem: Defend against empty strings for
color
on Subscription." and you're wondering how I might approach testing for this insubscriptionsReducer-test.js
, let me know, and let's chat on CZO or video. It's pretty tangential to this PR, but I have a local branch exploring a way to thoroughly test that bad/unexpected server data in anAction
doesn't fool a reducer into corrupting its piece of state — without having to repeat a lot of code from the existing n tests, where n is the minimum number of tests required to establish correct output given expected input in theAction
. Basically, it usesjest.spyOn
to replay all the calls to the reducer from those n tests, but with the passedAction
transformed into a bad one (with a function you define once), and checks that the resulting state is clean (with a function you define once). A whole new interface like this will be less welcome where n is small, obviously.