Compare commits

...

915 Commits

Author SHA1 Message Date
Tim Abbott
e5cde05710 docker: Set has_appserver=0 correctly. 2018-05-15 08:16:39 -07:00
Eeshan Garg
3a68f998a7 integration-docs-guide: Add a more detailed writing style guide.
Most of the recommendations in this guide were written by Rishi
Gupta (rishig), with a few contributions from Eeshan Garg.
2018-05-15 10:06:08 -04:00
Eeshan Garg
fa28ccb952 integration-docs-guide: Trim text documenting Markdown macros.
We were devoting too much space/text to documenting our Markdown
macro. It is much more concise to just have a description and a
link to an example doc for each major macro.
2018-05-14 20:14:08 -02:30
Tim Abbott
a2ed06314d send_test_email: Throw an error if email not configured.
This should help minimize confusion when folks try to use this before
properly configuring outgoing email.

Thanks to Bruce Eckel for the report.
2018-05-14 12:02:36 -07:00
RobbieClarken
2cca5dc79f zerver/tests: Test upload when mimetype is supplied as a query param. 2018-05-14 10:58:24 -07:00
Joshua Pan
df84e1d7eb tests: Reach 100% coverage for zerver/liv/management.py.
Cleaned up add_user_list_args(). The "help" and
"all_users_help" have all default values. As noted in
an earlier commit, "all_users_help" is always passed in,
so we can get rid of "all_users_arg". We keep the default
for "all_users_help" so we don't have to change variable order
in function definition.
2018-05-14 10:46:21 -07:00
Joshua Pan
ef098d2223 management: Remove the parameter required from add_user_list_args.
We remove an unecessary "required" paramter from this function
because as seen in the get_users() function right below, you have
to pass either -u/--users or -a/--all-users, meaning there should
never be a reason to require --users.
2018-05-14 10:46:21 -07:00
Joshua Pan
231b487bca tests: Cover check_config() in zerver/lib/management.py.
Add a comment to code to clarify what the config check does.
2018-05-14 10:46:21 -07:00
Tim Abbott
dc7f6c8a48 bots: Fix incorrect maximum lengths for outgoing webhook URLs.
2083 is the correct maximum length for a URL.
2018-05-14 10:42:23 -07:00
Darshan Markandaiah
607cab2a53 tests: Add 100% test coverage to zerver/apps.py.
Tweaked by tabbott to rename to test_cache.py and remove the
sender_name argument (Since it was kinda confusing).
2018-05-14 10:36:10 -07:00
Tim Abbott
1f837340d1 api: Fix confusing documentation about services for botserver.
This fixes several super-confusing things in these docs.  Bot services
aren't a user-facing concept, and also, you need the URL before
creating the bot users.
2018-05-14 09:21:45 -07:00
Tim Abbott
485d5b6335 api: Fix incorrectly coded bullet for zulip_botserver config. 2018-05-14 09:21:45 -07:00
Vishnu Ks
b73603a97c droplets: Do shutdown command along with clear history.
Otherwise shutdown command would be still present in the
history.
2018-05-14 08:12:43 -07:00
Vishnu Ks
861001f7b3 droplets: Update snapshot id.
Upgraded zulip and python-zulip-api repos.
Installed flask in python-zulip-api.
2018-05-14 08:12:43 -07:00
Tim Abbott
f5ea661c1f tools: Upgrade pycodestyle.
We have to add a few rules that we're ignoring for now.
2018-05-14 06:15:13 -07:00
Joshua Pan
e591668d60 bot-docs: Rewrite last section of "Running bots". 2018-05-14 04:27:36 -07:00
Joshua Pan
3952f94157 minor: Add missing period to bot-docs. 2018-05-14 04:27:36 -07:00
Joshua Pan
128aa8b7ee bot-docs: Remove hint on "Running bots" doc.
We already explain testing bot output in
"Writing bots" doc, and the placement of
the hint itself doesn't make sense.
2018-05-14 04:27:36 -07:00
Joshua Pan
0b112cf5e9 bot-docs: Streamline introduction to 'Running a bot'. 2018-05-14 04:27:36 -07:00
Joshua Pan
2b06e1cec7 bot-docs: Renumber list to use all 1's. 2018-05-14 04:27:36 -07:00
Tim Abbott
726017f682 actions: Remove dead do_change_bot_type function.
This is a tiny fraction of a feature we don't support, so we should
just kill it.
2018-05-13 17:58:49 -07:00
Tim Abbott
8b09118009 actions: Rename pick_color_helper to pick_color.
Now that there's only one function here, it's weird to have the
unnecessary _helper suffix.
2018-05-13 17:52:36 -07:00
Tim Abbott
234b5fa21b actions: Remove dead pick_color function.
We've been using pick_color_helper only for a long time.
2018-05-13 17:52:04 -07:00
Tim Abbott
60cfc210ce address: Remove dead msg_type function.
This was never used; we instead use `.is_stream` and `.is_private`
instead.
2018-05-13 17:47:21 -07:00
Tim Abbott
e4131fb708 docs: Remove a bunch of content from mypy docs.
All of this content had become obsolete due to our successful adoption
of mypy.
2018-05-13 17:23:18 -07:00
Aditya Bansal
64678b459c linter: Add rule to lint against use of typing.Text and promote str.
This commit is the last in a series of commits which migrated our
entire codebase to use str instead of typing.Text.

Fixes: #9203.
2018-05-13 17:17:32 -07:00
Aditya Bansal
addfde7374 tools: Change use of typing.Text to str. 2018-05-13 17:17:32 -07:00
Aditya Bansal
a00f3b5843 docs: Update for change from typing.Text to str. 2018-05-14 05:23:36 +05:30
Aditya Bansal
bd063b86c4 request.pyi: Remove unused import of typing.Text. 2018-05-14 05:16:22 +05:30
Aditya Bansal
cb9d8f6d48 scripts: Change use of typing.Text to str. 2018-05-14 05:16:22 +05:30
Yago González
610f48dcbc docs: Explain how to configure the Python bindings.
The Python bindings (which are used for bots, amongst other things) can
be configured either with a .zuliprc file or with environment variables
in the host machine.

This new page in the user docs explains how to set the bindings up using
both techniques, and is a good reference on the setup required by Zulip
bots.
2018-05-13 15:09:23 -07:00
Yago González
a575d69eec docs: Improve dev env setup for Debian. 2018-05-13 15:02:48 -07:00
Yago González
c6eee1c9da docs: Rename rest_error_handling with dashes. 2018-05-13 15:02:48 -07:00
Yago González
7ae51a4ec6 docs: Update API key-related screenshots. 2018-05-13 15:02:48 -07:00
Eeshan Garg
5c0d4660c1 models: Add history_public_to_subscribers to Stream.to_dict().
This commit also updates all the relevant parts where this attribute
could be useful, e.g. payloads for user subscriptions.
2018-05-13 09:15:07 -07:00
Shubham Padia
295fcb8536 models: Add is_announcement_only to stream_dict in actions.py.
Adds `is_announcement_only` to `stream_dict`s in order to access
the property in the frontend.
2018-05-13 09:11:51 -07:00
Shubham Padia
897ed17f0c api: Allow realm_admins to make a stream announcement_only. 2018-05-13 09:11:51 -07:00
Shubham Padia
bb8577ba94 stream: Only realm admins can post to an announcement_only streams.
If a non-admin tries to post to an announcement_only stream, error
message will be shown.
2018-05-13 09:11:51 -07:00
Shubham Padia
a5759108d3 models: Add field is_announcement_only to stream. 2018-05-13 09:06:20 -07:00
Vishnu Ks
08d20dce23 docs: Cover the missing case in DocPageTest. 2018-05-13 08:56:04 -07:00
Vishnu Ks
99186952f6 integrations: Make integration_doc endpoint work only on ajax. 2018-05-13 08:56:04 -07:00
Steve Howell
b5904b264d minor: Pull statements out of loops in unread.js.
We were recalculating the same sub for every iteration
in these two loops.  It's a pretty fast operation
but still nice to do only one time.
2018-05-13 08:47:55 -07:00
Steve Howell
0c9cf12933 Avoid some server fetches for sender:foo queries.
If we find unread messages for a sender, we will
try to render locally narrow for sender searches.

Note that our current implementation brute forces
through all the unread ids.  We can improve this,
although it's not really a bottleneck until we
also support buckets for general filtering.
2018-05-13 08:47:55 -07:00
Steve Howell
6ca145b2ed Add unread.get_all_msg_ids(). 2018-05-13 08:47:55 -07:00
Joshua Pan
073ecaac66 provision: Give concrete NFS error message on older OSX versions. 2018-05-13 08:47:19 -07:00
Yago González
184bd8304e i18n: Tag missing strings for translation. 2018-05-12 16:44:56 -07:00
Max Nussenbaum
bf27ed2b1b portico: Swap annual and monthly pricing on plans page.
This swaps the annual and monthly pricing on the plans page
(with the .67 cents properly aligned).
2018-05-12 15:29:28 -07:00
Max Nussenbaum
c9bcb2ef92 portico: Add link to Why Zulip on landing page.
This adds a link to the Why Zulip page to the landing page,
at the end of the "Organized" column.
2018-05-12 15:29:28 -07:00
Aditya Bansal
a68376e2ba zerver/lib: Change use of typing.Text to str. 2018-05-12 15:22:39 -07:00
Aditya Bansal
5416d137d3 zerver/tests: Change use of typing.Text to str. 2018-05-12 15:22:39 -07:00
Aditya Bansal
993d50f5ab zerver: Change use of typing.Text to str. 2018-05-12 15:22:39 -07:00
Aditya Bansal
67bf71472a zilencer: Change use of typing.Text to str. 2018-05-12 15:22:39 -07:00
Aditya Bansal
ade00dd954 confirmation/models.py: Change use of typing.Text to str. 2018-05-12 15:22:39 -07:00
Aditya Bansal
63dff4549f zerver/lib/test_classes.py: Change use of typing.Text to str. 2018-05-12 15:22:39 -07:00
Aditya Bansal
c02011d7f5 zerver/lib/bugdown/__init__.py: Change use of typing.Text to str. 2018-05-12 15:22:39 -07:00
Aditya Bansal
97b7075dd2 zerver/lib/actions.py: Change use of typing.Text to str. 2018-05-12 15:22:39 -07:00
Aditya Bansal
e9f87671e2 zerver/models.py: Change use of typing.Text to str. 2018-05-12 15:22:38 -07:00
Aditya Bansal
e79a2f2707 zerver/tornado: Change use of typing.Text to str. 2018-05-12 15:22:38 -07:00
Aditya Bansal
a40ae4cae5 zerver/webhooks: Change use of typing.Text to str. 2018-05-12 15:21:24 -07:00
Tim Abbott
d18b193b5b why-zulip: Fix a buggy/broken link.
This was accidentally introduced in
fc6833e46a.
2018-05-10 14:43:06 -07:00
Tim Abbott
eee221123f CircleCI: Add apt-get update. 2018-05-10 14:27:35 -07:00
Aditya Bansal
83d422d5bc zproject: Change use of typing.Text to str. 2018-05-10 14:19:49 -07:00
Aditya Bansal
e14974ff2c scripts: Change use of typing.Text to str. 2018-05-10 14:19:49 -07:00
Aditya Bansal
64ddfc6ac0 zerver/webhooks: Change use of typing.Text to str. 2018-05-10 14:19:49 -07:00
Aditya Bansal
e8506b5020 zerver/management: Change use of typing.Text to str. 2018-05-10 14:19:49 -07:00
Aditya Bansal
1f9244e060 zerver/lib: Change use of typing.Text to str. 2018-05-10 14:19:49 -07:00
Aditya Bansal
2f3b2fbf59 zerver/tests: Change use of typing.Text to str. 2018-05-10 14:19:49 -07:00
Aditya Bansal
5adf983c3c analytics: Change use of typing.Text to str. 2018-05-10 14:19:49 -07:00
Max Nussenbaum
fc6833e46a portico: Add quote to Why Zulip page.
Adds a quote from Grahame Grieve to the Why Zulip page.

With tweaks by tabbott to make the linter pass.
2018-05-10 14:17:21 -07:00
Tim Abbott
4abbfe9154 people: Fix issues with client_gravatar and upper-case emails.
We weren't properly canonicalizing user email addresses when consuming
gravatar URLs.

See http://en.gravatar.com/site/implement/hash/ for the specification.

Fixes #9357.
2018-05-10 12:35:20 -07:00
Max Nussenbaum
39e461a31b portico: Improve readability of FAQ on Plans page.
This restyles the FAQ text on the Plans page to match the recent
changes on the Why Zulip page: narrower text width and increased
contrast.
2018-05-10 07:34:35 -07:00
Max Nussenbaum
49ee6e65c2 portico: Improve readability on Why Zulip page.
This increases the font size, decreases the paragraph width, and
makes the text darker on the Why Zulip page to improve
readability.
2018-05-10 07:34:35 -07:00
Max Nussenbaum
caf6870a54 portico: Break up long paragraphs on Why Zulip.
This breaks long paragraphs on the Why Zulip page into multiple
smaller paragraphs to improve readability.
2018-05-10 07:34:34 -07:00
Steve Howell
815f54cda4 Use local messages for more narrow searches.
We now try harder to find the first unread message in an
upcoming narrow, which has the user-visible effect that we
select the unread message before waiting for search results.

Before this change, we only applied this logic to searches
that were things like exactly stream/topic or exactly is-private.

Now we will also handle things like stream/topic/sender.  For
the stream/topic piece we look up candidate unread ids using
the steam/topic buckets in unread.js, but then we still filter
those messages by stream/topic/sender as we look for the first
unread id.
2018-05-10 06:36:13 -04:00
Steve Howell
124192a3b9 node tests: Clean up narrow_unread messages.
This gives variable names to each of the messages we use.
2018-05-10 06:36:13 -04:00
Steve Howell
5d6d1ca8f9 refactor: Rename narrow_state.get_unread_ids().
I renamed get_unread_ids() to _possible_unread_message_ids().
The name is deliberately verbose, since we're about
to make it have kind of unusual semantics that only make sense
for its one caller.

The outside code will continue to call get_first_unread_info().

In the tests I wrap this function in a wrapper with the more
pleasant name of "candidate_ids", since in the test there's
less worry about unwittingly exposing a kind of janky function.
2018-05-10 06:36:13 -04:00
Steve Howell
9b15c2cd46 minor: Re-order code blocks.
This is setting up for a subsequent commit to have a smaller
diff.  The current ordering of the code blocks doesn't matter,
since only one of the conditions will be true, so this won't
change any behavior.  (Later commits will make the order matter.)
2018-05-10 06:36:13 -04:00
Steve Howell
19ac0b23ab Add filter.can_bucket_by(). 2018-05-10 06:36:13 -04:00
Tim Abbott
0a39eb2a58 export: Convert a bunch of error cases to AssertionError.
This reflects the fact that these are just defensive programming (we
don't expect them to ever happen) and also nicely makes these lines
not show up in our missing test coverage reports.
2018-05-09 20:49:13 -07:00
Tim Abbott
ff2157c787 coverage: Exclude api_test_helpers.py from backend test coverage.
These files are tested by a different test suite, and in practice have
100% coverage; no need to double-measure it.
2018-05-09 20:49:13 -07:00
Tim Abbott
3872e5521e tornado: Mark setup_tornado_rabbitmq for nocoverage.
We only use this in the direct management command, and it involves
some autoreload process setup stuff that we probably don't want to do
in our unit tests regardless.
2018-05-09 20:49:13 -07:00
Tim Abbott
7e2841b358 bulk_create: Mark bulk_create_streams as nocoverage.
It's only used in `populate_db`, so it gets tested that way.
2018-05-09 20:49:12 -07:00
Tim Abbott
81e17c7d47 test_i18n: Stop using six.moves for SimpleCookie.
This requires a 'type: ignore' because of a typeshed bug.
2018-05-09 20:45:45 -07:00
Tim Abbott
4c1777f146 tests: Add a good set of unit tests for UserActivityInterval.
This system was written years ago and has been working well the whole
time, but having unit tests for it will help future developers in
understanding what the intent is.
2018-05-09 20:45:02 -07:00
Raymond Akornor
1a215d5504 zproject: Migrate away from six. 2018-05-09 18:57:01 +00:00
Raymond Akornor
f67efd5291 zerver: Migrate away from six. 2018-05-09 18:38:25 +00:00
Rishi Gupta
01a7ed952d user docs: Update a few articles in account basics. 2018-05-08 17:30:08 -07:00
Tim Abbott
c7b44d44e4 third: Remove now-unused LazyLoad library.
We don't reference this anymore (it was only ever used by the Dropbox
integration, which was hardcoded-off for years before being removed in
e6833b6427)
2018-05-08 14:38:27 -07:00
Steve Howell
ea581c546c Add filter.is_valid_id_from().
We will use this to find the first id from a list of
message ids that matches a filter.  (This will help us
during narrowing to determine whether we have at least
one good message locally, so that we can render something
useful before waiting for the server.)
2018-05-08 12:13:55 -07:00
Steve Howell
a176380df5 refactor: Introduce filter.is_exactly().
This new API replaces some more specific functions that were
only recently introduced:

        is_stream_only
        is_stream_topic_only
        is_pm_with_only
        is_for_only

We use the deterministically sorted "term_type" values for
matching.  (Notably "stream" will come before "topic".)
2018-05-08 12:13:55 -07:00
Steve Howell
60e399f717 Add filter.sorted_term_types. 2018-05-08 12:13:55 -07:00
Steve Howell
081e789405 Add static Filter.sorted_term_types(). 2018-05-08 12:13:55 -07:00
Steve Howell
c267d3a6ba Add Filter.term_type. 2018-05-08 12:13:55 -07:00
Steve Howell
27b7461e0a Revert "js: Implement DynamicText class."
This reverts commit 6e7305f784.

We never ending up using this class.
2018-05-08 12:13:55 -07:00
Tim Abbott
fba45bb9d3 archive: Add functions for accessing web-public streams.
These will be helpful for variable upcoming projects to support the
web-public streams feature.
2018-05-08 12:12:15 -07:00
Tim Abbott
f24630fd4a events: Include data for whether push notifications are enabled.
This is primarily useful for the mobile app, but could also be used to
control whether we display push-notifications related settings to
users in the web UI.
2018-05-08 11:45:13 -07:00
Priyank Patel
28682ad83e zulip.scss: Use scss nesting syntax for .email_tooltip. 2018-05-08 10:35:40 -07:00
Priyank Patel
073407bf90 zulip.scss: Use scss nesting syntax for .user_popover_email. 2018-05-08 10:35:40 -07:00
Priyank Patel
0cc5c6985a zulip.scss: Use scss nesting syntax for #searchbox .input-append. 2018-05-08 10:35:40 -07:00
Priyank Patel
046924ee73 zulip.scss: Use nesting for #navbar-buttons ul.nav. 2018-05-08 10:35:40 -07:00
Priyank Patel
c9de28b185 css: Use mixins in zulip.scss.
This uses scss mixins, this are functions you can pass parameter in it and
return css/scss. It made repeating vendored transistion, and user-select property
more easier to use with less repetation. This also includes a scss file called reuseable_components.scss
which can be used anywhere else.
2018-05-08 10:35:40 -07:00
Tim Abbott
d4d268529e templates: Clean up formatting of FAQ content.
This adds some paragraph splits and curly quotes.
2018-05-08 09:46:50 -07:00
Max Nussenbaum
0904a327ff portico: Improve spacing of headers on plans page.
This adjusts the spacing on the plans page so there is more
breathing room between "Get started today" and the headings
below it.
2018-05-08 09:32:54 -07:00
Max Nussenbaum
3c6cccbfd6 portico: Add link to API on home.
Adds a link to the API on the integrations section of the
homepage.
2018-05-08 09:32:54 -07:00
Max Nussenbaum
e1dfee50b1 portico: Fix keyboard shortcuts link.
Changes the color of the link to learn more about keyboard
shortcuts in /features to be more readable.
2018-05-08 09:30:09 -07:00
Max Nussenbaum
a0dacea811 portico: Remove transition on page load.
Removes the fade in and fade out that used to happen when a
page was loaded.
2018-05-08 09:28:39 -07:00
Max Nussenbaum
99b1dec92a portico: Align "Create Organization" button.
This aligns the Create Organization button on /new with the email
address input.
2018-05-08 09:26:25 -07:00
Tim Abbott
ee697f9090 docs: Document the topic-link feature of our realm filters.
This isn't the best writeup, but having this here will help ensure we
do document this in a future edit pass on this document.
2018-05-07 20:30:48 -07:00
Tim Abbott
7208ad0ae0 version: Update for Zulip Server 1.8.1 release. 2018-05-07 16:01:41 -07:00
Tim Abbott
1fb576b858 Add changelog for 1.8.1 release. 2018-05-07 16:01:10 -07:00
Max Nussenbaum
985e8e4a9a portico: Move open source image above text on mobile.
This moves the image that accompanies the open source section to
be above the text (instead of smushed to the side) on small
windows.
2018-05-07 14:41:37 -07:00
Max Nussenbaum
bb639b3752 portico: Use smart apostrophe in header
This changes the landing page header to use a curly apostrophe
instead of a straight one.
2018-05-07 14:39:20 -07:00
Max Nussenbaum
83b20488a7 portico: Fix subhead line break on mobile.
The line break in the homepage subhead looked weird on mobile.
The line now only breaks on display widths greater than 767px.
2018-05-07 14:39:20 -07:00
Max Nussenbaum
5f94c7dab5 portico: Adjust integration padding.
The integrations box on the landing page had some extra padding
at the bottom. This removes it so the spacing is more even.
2018-05-07 14:36:16 -07:00
Max Nussenbaum
992abdeccf portico: Adjust integration box spacing on mobile.
This gives the integration boxes on the homepage a little more
space on very small viewports (<450px).
2018-05-07 14:36:16 -07:00
Max Nussenbaum
b7974a4923 portico: Improve spacing for app icons.
The app icons (web/mobile/desktop) got all squished together when
they broke to a new line in smaller windows. They now have better
spacing and all break at the same time.
2018-05-07 14:31:21 -07:00
Max Nussenbaum
f05bd2fdad portico: Align CTA buttons on plans page.
This adjusts the spacing on the CTA buttons on the plan page
so that they are all vertically aligned.
2018-05-07 14:27:05 -07:00
Max Nussenbaum
e31d7d2d83 portico: Fix white border around integration icon widgets on homepage
The integration widgets on the homepage had a weird-looking white
border. That border has now been updated to match the better-
looking border on the integrations page.
2018-05-07 14:18:06 -07:00
Yago González
5e85701a42 docs: Add references to dev installation in Debian. 2018-05-07 08:53:01 -07:00
Vishnu Ks
49b3cb9da5 droplets: Remove the step to activate virtualenv manually.
I didn't had to activate this manually during the
last few droplet upgrades.
2018-05-07 08:42:18 -07:00
Vishnu Ks
c684333051 droplets: Fix the command for clearing bash history.
history -c only clears the history from memory and
would be populated back during the next SSH session.
2018-05-07 08:42:18 -07:00
Vishnu Ks
18314e57f8 droplets: Add origin remote to python-zulip-api as well. 2018-05-07 08:42:18 -07:00
Vishnu Ks
13f6cbeefd droplets: Change droplet region from SFO1 to NYC3.
Since the region of base.zulipdev.org is
NYC3 we had to add SFO1 as an additional
region each time a snapshot of base droplet
is created. This is required as droplets
can be created in SFO1 only if there is
an image present in that region. Adding
of droplet image to SFO1 takes a lot of
time as well as cost 2X as we are storing
2 images. It's better to just create new
droplets in NYC3 instead. Alternatively we
can create a new base droplet in SFO1 if
we want all the droplets to be created in
SFO1.
2018-05-07 08:42:18 -07:00
Vishnu Ks
d6c5635550 droplets: Update snapshot id. 2018-05-07 08:42:18 -07:00
Tim Abbott
6317e4d63c events: Extract post_process_state.
This is a preparatory refactor for potentially being able to call this
from the web-public archive feature's code path.
2018-05-06 23:21:29 -07:00
Tim Abbott
112805251f fetch_initial_state_data: Use realm local variable.
This is just a simple code cleanup.
2018-05-06 23:20:52 -07:00
Tim Abbott
602b13db34 home: Make emojiset an explicit parameter.
This is cleaner than accessing the UserProfile object directly (we
don't really do that anywhere else).
2018-05-06 22:36:29 -07:00
Tim Abbott
b3e4c702d1 generate_secrets: Fix handling of an empty secrets file.
This is now a condition that happens during installation, because we
now create an empty file for this in puppet.
2018-05-06 22:12:32 -07:00
Tim Abbott
dc0696af74 puppet: Ensure zulip user owns key /etc/zulip files.
The main purpose of this change is to make it guaranteed that
`manage.py register_server --rotate-key` can edit the
/etc/zulip/zulip-secrets.conf configuration via crudini.

But it also adds value by ensuring zulip-secrets.conf is not readable
by other users.
2018-05-06 21:54:02 -07:00
Tim Abbott
f648bc1eae register_server: Remove broken, unused import.
This import preventing this script from working in most production
environments.
2018-05-06 21:53:49 -07:00
Tim Abbott
3ac7f01e4b puppet: Replace dockervoyager supervisord hack with a better one.
This hack is still pretty bad, but at least it still works on puppet 4.
2018-05-06 21:28:03 -07:00
Tim Abbott
eab1d1d9e7 puppet: Fix puppet-apt bug with modern apt-key.
With modern apt-key, the fingerprints are displayed in the more fully
written-out format with spaces, and so `apt-key add` was being run
every time.

This fixes some unnecessary work being done on each puppet run on
Debian stretch.

I would have preferred to not need to do this by upgrading to
upstream, but see #7423 for notes on why that isn't going to work
(basically they broke support for puppet older than 4).
2018-05-06 21:10:24 -07:00
neiljp (Neil Pilgrim)
202063e030 requirements: Upgrade mypy to 0.600. 2018-05-06 20:55:21 -07:00
Tim Abbott
7fe19ef8e7 puppet: Remove </% example characters from sample postgres config.
Apparently, these confused the puppet template parser, since they are
somewhat similar to its syntax, resulting in errors trying to use
these templates.  It's easy enough to just remove the example
content from the base postgres config file.
2018-05-06 19:50:38 -07:00
Yago González
6837fc5d56 i18n: Add missing strings for custom profile fields and fix capitalization.
The "Short/Long Text" option for custom profile fields wasn't properly
capitalized (i.e. "Text" should have been all lowercase), and also
wasn't properly tagged for translation.

For the sake of consistency, the change to proper capitalization has
also been applied to the models and any tests involving this feature.

Due to a bug in Django, it complained about the models having changed
and thus not being consistent with the migrations. That isn't actually
true (since the database stores the numeric values for each key), but
the migrations have been modified to avoid this error. This does not
affect the migrations' behaviour in any way.
2018-05-06 19:44:36 -07:00
Tim Abbott
6e149a7594 lint: Add JS indentation eslint rules for node_tests.
The only difference between this as the main project's lint rules is
that we dont have the OuterIIFE setting.
2018-05-06 19:35:18 -07:00
Tim Abbott
ed299feb00 lint: Check eslint indentation for casper tests.
Now we just limit this rule for the node tests themselves.
2018-05-06 19:35:18 -07:00
Tim Abbott
d3f8208715 eslint: Add whitespace indentation configuration.
This should help prevent problems with folks introducing new code that
doesn't match our whitespace style.

There's a couple things I don't like about this configuration:

* How it handles multi-line JS lists (i.e. the [] syntax)
* That we ended up having to turn off indentation on a half-dozen
  files that apparently don't use our standard IIFE indentation style.
* That we have it completely turned off for the node tests; ideally,
  we'd just have slightly different rules around the IIFE identation story.

But otherwise, this works pretty well, and should catch a pretty wide
range of indentation regressions.
2018-05-06 19:35:18 -07:00
Tim Abbott
716a4a967d js: Fix indentation issues in casper tests.
Our casper tests now pass eslint cleanly.
2018-05-06 19:35:18 -07:00
Tim Abbott
063d11b139 js: Standardize indentation of switch/case statements.
This gets my current draft eslint indentation configuration passing
cleaning on static/js.
2018-05-06 19:35:18 -07:00
Tim Abbott
d6db335f68 js: Reindent case clauses consistently. 2018-05-06 19:35:18 -07:00
Greg Price
9f3052b0ef mypy: Move remaining options on type-check semantics to config file.
This puts all of this config in one place, and also needs a lot fewer
lines to describe it; which, combined, makes it a lot clearer what our
normal config actually is.  (I'd been looking at this script for a few
minutes without realizing that we have `--disallow-untyped-defs` *on*
by default, not off.)

Experimenting with different values is still easy; just comment the
line in the config.
2018-05-06 19:33:55 -07:00
Greg Price
cc9acbe50d mypy: Organize config file a bit, since it's grown. 2018-05-06 19:33:55 -07:00
Greg Price
6b1e76c1b1 mypy: Move cache-dir to config file. 2018-05-06 19:33:55 -07:00
Greg Price
724e849e2b mypy: Move follow-imports to config file.
It's cleaner to have this configuration in a nice declarative
config file than embedded in a script.
2018-05-06 19:33:55 -07:00
Greg Price
ff178bb27a mypy: Drop now-redundant -i option.
This used to be a synonym for `--incremental`.  Since mypy 0.590,
incremental mode is the default, and the flag is ignored; so we
can happily drop it.
2018-05-06 19:33:55 -07:00
Tim Abbott
df98fd5cd9 hotkey: Make it possible to use ctrl+K from inside compose.
This works for other text boxes as well, but compose is the main one
that one would want to do a search from.

It's possible we'll find after doing this that "getting back into
compose" becomes a problem, but I guess we can handle that when the
time comes.
2018-05-06 19:30:31 -07:00
Aastha Gupta
66edc003ca hotkey: Make 'Ctrl' hotkeys work with 'CMD' on MacOS.
We only have a couple hotkeys with this model, but they should both
do the correct corresponding thing on MacOS.
2018-05-06 19:21:36 -07:00
Aastha Gupta
19806a0283 keyboard UI: Add Ctrl + k hotkey.
Fixes #8216
This commit binds the Ctrl + k to go to the search bar.
2018-05-06 19:19:00 -07:00
Tim Abbott
63fe39e381 zulip_ops: Disable Ubuntu's built-in update-motd.d files.
We can't really do this in the zulip manifests (since it's sorta a
sysadmin policy decision), but these scripts can cause significant
load when Nagios logs into a server (because many of them take 50ms or
more of work to run).  So we just get rid of them.
2018-05-06 18:47:40 -07:00
Tim Abbott
7ab8a8e820 js: Fix a bunch of indentation issues found by eslint.
This is preparation for enabling an eslint indentation configuration.
90% of these changes are just fixes for indentation errors that have
snuck into the codebase over the years; the others are more
significant reformatting to make eslint happy (that are not otherwise
actually improvements).

The one area that we do not attempt to work on here is the
"switch/case" indentation.
2018-05-06 16:25:02 -07:00
Tim Abbott
4d0e64ee41 js: Fix some invalid whitespace.
These were detected using eslint.
2018-05-06 12:38:44 -07:00
Tim Abbott
a9dc83d78e register_server: Fix a typo. 2018-05-05 16:42:11 -07:00
Tim Abbott
cfd22c6832 zilencer: Clean up logic around mobile push notifications signup.
This fixes exceptions when sending PMs in development (where we were
trying to connect to the localhost push bouncer, which we weren't
authorized for, but even if we were, it wouldn't work, since there's
no APNS/GCM certs).

At the same time, we also set and order of operations that ensures one
has the opportunity to adjust the server URL before submitting
anything to us.
2018-05-05 16:42:00 -07:00
Eeshan Garg
6817232a67 webhooks/bitbucket2: Support events of type "repo:updated". 2018-05-05 15:48:38 -07:00
Eeshan Garg
9fb9c0d901 webhooks: Migrate to validate_extract_webhook_http_header.
This is a part of our efforts to close #6213.
2018-05-05 15:48:38 -07:00
Eeshan Garg
34d1b0ebf1 webhooks: Add helper to extract and validate HTTP event headers.
This is a part of our efforts to close #6213.
2018-05-05 15:48:37 -07:00
Eeshan Garg
19fa73891e actions: Refactor rate-limiting logic out of send_pm_if_empty_stream.
It makes sense to refactor out the last_reminder logic out of
send_pm_if_empty_stream and have a generic function that can send
rate-limited PM notifications to a bot owner and can be used by
methods other than send_pm_if_empty_stream.
2018-05-05 15:46:27 -07:00
Tim Abbott
384a8f2e9f custom fields: Clean up template logic for field ID.
This makes a few important cleanup changes:
* Using the more standard data-field-id name for the ID value.
* Using $(e.target).closest() rather than `.parent`, which is more
  robust to future changes in markup.
2018-05-05 15:40:16 -07:00
Yashashvi Dave
06e63af4b4 custom fields: Add UI for choice type custom fields. 2018-05-05 11:59:08 -07:00
Yashashvi Dave
512ab5dbaf js/settings_account.js: Remove hard-coded field no, clean template context. 2018-05-05 11:59:08 -07:00
Tim Abbott
427b404b9b puppet: Fix detection of Debian/Ubuntu version.
The previous configuration, missing a ^, had the 8.x for Debian Jessie
match Ubuntu 18.04 by accident.
2018-05-05 11:52:45 -07:00
Tim Abbott
41841221ee scripts: Remove obsolete zesty configuration.
Zesty already reached end-of-life, so we'll never support it.

And in one place, we add support for bionic.
2018-05-05 11:41:57 -07:00
Tim Abbott
cf90b9cec0 puppet: Make memory computations work with Puppet 4.
The actual approach for achieving this goal is to take our manual
parsing and move it to the central base.pp.
2018-05-05 11:25:48 -07:00
Tim Abbott
8a3522e8e4 docs: Update development docs for Debian support.
While we're at it, we also update some direct download links for
Debian/Ubuntu packages.
2018-05-05 11:08:07 -07:00
Tim Abbott
d178c53a10 provision: Add official support for provisioning directly on Debian. 2018-05-05 11:00:47 -07:00
Tim Abbott
0c1a0a35ec static_asset_compiler: Assume non-trusty is newer.
It seems unlikely we're going to add support for additional older
Debian-based distributions, so it makes sense to just use an else
statement.  This should save a bit of busywork every time we add a new
distro.
2018-05-05 10:50:17 -07:00
Tim Abbott
1b3b298fa8 install: Allow installing with Debian 9.
For now we just change the script, not the documentation.
2018-05-05 10:49:09 -07:00
Tim Abbott
8ea8bfe285 puppet: Add basic configuration for Ubuntu bionic. 2018-05-05 10:49:09 -07:00
Tim Abbott
b193330474 provision: Add tsearch_extras package for stretch. 2018-05-05 10:49:09 -07:00
Tim Abbott
a03e4784c7 puppet: Add Zulip specific postgres configuration for 10.
Mostly, this involves adding the big block at the bottom and making
10 a variable so that it's easier to compare different versions of
these.

I did an audit of the configuration changes between 9.6 and 10, so
this should be fine, but it hasn't been tested yet.
2018-05-05 10:48:46 -07:00
Tim Abbott
964a1ac8a7 puppet: Commit an upstream version of postgres 10 config. 2018-05-05 10:48:37 -07:00
Tim Abbott
78f3cff151 provision: Fix unescaped reference to vagrant.
Previously, this would actually try to run `vagrant ssh`, rather than
printing it :(.
2018-05-05 10:48:37 -07:00
Tim Abbott
76fa29085a setup-apt-repo: Clean up setup code for apt repo.
This fixes adding the Ubuntu repositories for Debian, as well as makes
sure that we install the debian-archive-keyring package on Debian,
which is only priority important (and thus might be missing).
2018-05-05 10:03:39 -07:00
Tim Abbott
4ee762a52c apt: Add packagecloud repository for Debian. 2018-05-05 10:03:03 -07:00
Tim Abbott
06cfc591fe setup-apt-repo: Require apt-transport-https be installed.
Doing our apt operations over HTTPS has better security properties.
2018-05-05 10:02:50 -07:00
Shubham Dhama
38630295ac upload/jquery-filedrop: Fix progress bar for paste upload. 2018-05-05 06:32:10 -07:00
Steve Howell
320425fde3 node tests: Add tests for message_list_data. 2018-05-05 06:31:51 -07:00
Steve Howell
264dcb6f40 refactor: Extract MessageListData class.
Most of this was straightforward.

Most functions that were grabbed verbatim and whole from
the original class still have one-line wrappers.

Many functions are just-the-data versions of functions that
remain in MessageList:  see add, append, prepend, remove as
examples.  In a typical pattern the MessageList code becomes
super simple:

    prepend: function MessageList_prepend(messages) {
        var viewable_messages = this.data.prepend(messages);
        this.view.prepend(viewable_messages);
    },

Two large functions had some minor surgery:

    triage_messages =
            top half of add_messages +
            API to pass three lists back

    change_message_id =
            original version +
            two simple callbacks to list

For the function update_muting_and_rerender(), we continue
to early-exit if this.muting_enabled is false, and we copied
that same defensive check to the new function
named update_items_for_muting(), even though it's technically
hidden from that codepath by the caller.
2018-05-05 06:31:51 -07:00
Vishwesh Jainkuniya
3a514c7e41 stream-edit: Fix typo in comment. 2018-05-05 06:25:06 -07:00
Steve Howell
0463bb2c5e Fix corner case with recent narrowing optimization.
For a commit that was just merged I had the "back-out" case
at the wrong nesting level.  It was a pretty obscure failure
scenario that never came up in practice, but basically if you
were starting at a message that was not in your narrow, but
we did have some messages in your narrow, we would try to
go near the old message instead of talking to the server to
find the next unread message in that narrow.
2018-05-04 17:34:34 -07:00
Steve Howell
4573bdd005 narrow: Load messages locally for most sidebar clicks.
Barring a few minor edge cases, when we now do a narrow
that is based on a sidebar-like search (e.g. stream/topic,
no extra conditions), we now go directly to either the
first unread message we know about locally or the last
message if we're all caught up.

We of course used to do this in master until recently; this behavior
was broken by Tim's narrowing refactor branch (ending with
26ac1d237b) which moved us to always
using the select_first_unread flag, by default (fixing issues where if
you clicked around while your pointer was behind, you'd land in the
wrong place).

We now have arguably the best of both worlds:
* The pointer is not considered when computing narrowing positioning
* We only go to the server for sidebar clicks if the data isn't
  available in the browser.
2018-05-04 16:36:51 -07:00
Steve Howell
da0c01e4ba Fix regression with topic edits clearing narrows.
We had a recent regression that had kind of a funny symptom.
If somebody else edited a topic while you were in a topic
narrow, even if wasn't your topic, then your narrow would
mysteriously go empty upon the event coming to you.

The root cause of this is that when topic names change,
we often want to rerender a lot of the world due to muting.
But we want to suppress the re-render for topic narrows that
don't support the internal data structures.

This commit restores a simple guard condition that got lost
in a recent refactoring:

        see 3f736c9b06

From tabbott: This is not the correct ultimate place to end up,
because if a topic-edit moves messages in or out of a topic, the new
behavior is wrong.  But the bug this fixes is a lot worse than that,
and no super local change would do the right thing here.
2018-05-04 16:33:23 -07:00
Tim Abbott
43fa0aacbf attachments: Implement frontend for real-time sync.
We now do real-time sync to update the attachments UI when new
attachments are uploaded/deleted.

While we're at it, we fix the UI for the delete option to not do a
weird local echo thing.

This completes the work of a couple issues.  There's still useful
performance work to do here (see the TODO), but it's a minor issue in
a rarely-used screen.

Fixes #6731.
Fixes #3710.
2018-05-04 16:22:27 -07:00
Tim Abbott
19737aab3e attachments: Store the attachments state in a global. 2018-05-04 16:22:27 -07:00
Tim Abbott
802636fbde attachments: Extract format_attachments_data. 2018-05-04 16:22:27 -07:00
Tim Abbott
62a92764f1 attachments: Move one-time-rendering logic out of set_up_attachments. 2018-05-04 16:22:27 -07:00
Tim Abbott
02ad498aa2 attachments: Use data-attachment-id to refer to attachments.
This may fix an issue with the delete button not actually sending the
right data to the server; I'm not sure.  Extracted from a patch by
Aastha Gupta.
2018-05-04 16:22:27 -07:00
Tim Abbott
956bd74905 attachments: Send events for attachment updates.
We send add events on upload, update events when sending a message
referencing it, and delete updates on removal.

This should make it possible to do real-time sync for the attachments
UI.

Based in part on work by Aastha Gupta.
2018-05-04 16:22:27 -07:00
Tim Abbott
69c4645bd2 attachments: Stop fetching attachments in / endpoint.
We only use this data in a rarely-used settings screen, and it can be
large after years of posting screenshots.

So optimize the performance of / by just loading these data when we
actually visit the page.

This saves about 300ms of runtime for loading the home view for my
user account on chat.zulip.org.
2018-05-04 16:22:26 -07:00
Tim Abbott
60185dddfd bots: Fix buggy database queries in loop.
A typo in my reading of 6cc2e8bbff meant
that we were incorrectly doing database queries for each Service
object, just to get the user_profile.id, which we already had.
2018-05-04 15:23:13 -07:00
Tim Abbott
59d3fefc07 bots: Improve performance of fetching core bot data.
This eliminates the need to call user_ids_to_users inside the
get_service_dicts_for_bots code path, saving a database query.

This completes my refactor to fix backend performance issues in this
code path.  Previously, our messy layering of queries that resulted in
Zulip doing work even if none of the bots actually had Services or
config_data.
2018-05-04 13:44:43 -07:00
Tim Abbott
0e81353ce0 bots: Merged get_services_for_bots into its caller.
This function is now only a few lines of simple code called in one
place, and is better eliminated.
2018-05-04 13:41:41 -07:00
Tim Abbott
6cc2e8bbff bots: Remove unnecessary select_related on Service objects.
Given how we're using these, there's no need for the full UserProfile
object to be fetched from the server.
2018-05-04 13:41:29 -07:00
Tim Abbott
186152bfc0 bots: Pass a list of user IDs into get_services_for_bots.
We weren't using the full profile objects.
2018-05-04 13:41:15 -07:00
Tim Abbott
4eb3c72c74 bots: Move get_services_for_bots into actions.py.
It's better to just have this bundle of code all in one place; also,
after some cleanup, we'll be inlining it into
get_service_dicts_for_bots.
2018-05-04 13:36:25 -07:00
Tim Abbott
894a952f6f bots: Compute embedded bots list using bot_dicts. 2018-05-04 13:34:54 -07:00
Tim Abbott
13f1f6a388 bots: Pass the full bot_dicts objects into get_service_dicts_for_bots.
This is preparatory refactoring for not needing to fetch the
bot_profile objects from the database.
2018-05-04 13:33:32 -07:00
Tim Abbott
447f8db8cb get_bot_configs: Adjust API to accept a list of bot user IDs.
This is preparatory refactoring for removing user_profile objects from
the get_service_dicts_for_bots code path.
2018-05-04 13:31:28 -07:00
Tim Abbott
fdc1182a76 events: Optimize query for custom profile fields.
Our query for Custom Profile fields was for no good reason passing the
list of all users in the realm (potentially many thousands) into a
database query, rather than letting the database do that join.

Fixing this saves 100ms-200ms in the loading time for / on
chat.zulip.org for all users, since we were previously doing a ton of
work even if the feature wasn't being used.
2018-05-04 12:59:30 -07:00
Steve Howell
0067ccb931 refactor: Add filtering helpers to message_list.js. 2018-05-04 10:59:56 -07:00
Steve Howell
af24f51f0d Add MessageList.is_search().
This prevents MessageListView from having to know about
list.filter.
2018-05-04 10:59:56 -07:00
Vishnu Ks
733da0ac07 settings: Fix 500 when trying to change email to disposable email.
Fixes #9240
2018-05-04 10:52:38 -07:00
Tim Abbott
76fba19d20 docs: Add a basic guide on the current guest users implementation.
This will likely change pretty quickly as we build this feature; I
wrote this just to have a central place to keep track of what we'll
need to document when we ship this feature.
2018-05-04 10:27:40 -07:00
Tim Abbott
508dc5b6ed decorators: Add new decorators for guest users.
These decorators will be part of the process for disabling access to
various features for guest users.

Adding this decorator to the subscribe endpoint breaks the guest users
test we'd just added for the subscribe code path; we address this by
adding a more base-level test on filter_stream_authorization.
2018-05-04 10:25:52 -07:00
Tim Abbott
8b26f912af streams: Limit access to public streams for guest users.
With most of the tests tests written by Shubham Dhama.
2018-05-04 09:47:58 -07:00
Tim Abbott
7cbff8b521 push registration: Use standard error message for auth problems.
This avoids adding an unnecessary new translated string.
2018-05-04 09:04:39 -07:00
Tim Abbott
9d6233a457 capitalization: Add an exclude rule for zulip_org_id. 2018-05-03 22:37:24 -07:00
Tim Abbott
43098a6f7c zilencer: Add automated signup system for push notifications.
Based on an initial version by Rishi Gupta.

Fixes #7325.
2018-05-03 21:27:49 -07:00
Tim Abbott
b1ad7593ba validators: Improve mypy type annotations.
The main thing here is writing check_string_fixed_length and
check_capped_string as returning a Validator, but we also fix issues
around passing default=None.
2018-05-03 21:27:43 -07:00
Tim Abbott
51517fa188 request: Add new str_validator validator type.
This is helpful for cases where an argument is supposed to be a normal
string, and we want to use a Zulip validator function to do basic
things like check its length.
2018-05-03 21:11:02 -07:00
Tim Abbott
f2e84f25a0 management: Refactor checkconfig code to live in library.
This makes it possible to call this from other management commands.
2018-05-03 21:11:02 -07:00
Tim Abbott
d1b9e06cb4 version: Bump PROVISION_VERSION for Python package updates. 2018-05-03 20:44:00 -07:00
Vishnu Ks
606d73751a requirements: Upgrade setuptools to 39.1.0. 2018-05-03 20:39:56 -07:00
Vishnu Ks
fb0e054d7c requirements: Upgrade stripe to 1.80.0. 2018-05-03 20:39:56 -07:00
Vishnu Ks
e6ce781006 requirements: Upgrade sphinx-rtd-theme to 0.3.1. 2018-05-03 20:39:56 -07:00
Vishnu Ks
dcf7a14ba7 requirements: Upgrade Twisted to 18.4.0. 2018-05-03 20:39:56 -07:00
Vishnu Ks
0988232725 requirements: Upgrade twilio to 6.13.0. 2018-05-03 20:39:56 -07:00
Vishnu Ks
94d58cb545 requirements: Upgrade SQLAlchemy to 1.2.7. 2018-05-03 20:39:56 -07:00
Vishnu Ks
1eaaecb69a requirements: Upgrade Sphinx to 1.7.4. 2018-05-03 20:39:56 -07:00
Vishnu Ks
27193b0ecc requirements: Upgrade premailer to 3.2.0. 2018-05-03 20:39:56 -07:00
Vishnu Ks
a1821fd27d requirements: Upgrade pip-tools to 2.0.2. 2018-05-03 20:39:56 -07:00
Vishnu Ks
bf610f6570 requirements: Upgrade Pillow to 5.1.0. 2018-05-03 20:39:56 -07:00
Vishnu Ks
0e9fda033a requirements: Upgrade ndg-httpsclient to 0.5.0. 2018-05-03 20:39:56 -07:00
Vishnu Ks
8182a3bf4c requirements: Upgrade moto to 1.3.3. 2018-05-03 20:39:56 -07:00
Vishnu Ks
440d458313 requirements: Upgrade ipython to 6.3.1. 2018-05-03 20:39:56 -07:00
Vishnu Ks
1b0c647ded requirements: Upgrade google-api-python-client to 1.6.7. 2018-05-03 20:39:56 -07:00
Vishnu Ks
0542af1ec2 requirements: Upgrade gitlint to 0.10.0. 2018-05-03 20:39:56 -07:00
Vishnu Ks
bb5dabb33c requirements: Upgrade disposable-email-domains to 0.0.26. 2018-05-03 20:39:56 -07:00
Vishnu Ks
69fe367771 requirements: Upgrade certifi to 2018.1.18. 2018-05-03 20:39:56 -07:00
Armaan Ahluwalia
cca10beb78 css/webpack: Moved archive styles to webpack bundle.
This commit moves the stylesheets under the archive bundle in
the Django pipeline to being compiled by webpack instead. It
also removes a remaining call to a portico stylesheet that no
longer exists.
2018-05-03 19:15:49 -07:00
Armaan Ahluwalia
54bf2a6231 css/webpack: Transition landing-page.css to webpack.
This commit transitions landing-page.css from the Django pipeline
to being compiled by webpack as landing-page.scss under the
'landing-page' and 'integration' bundles.
2018-05-03 19:15:23 -07:00
Armaan Ahluwalia
1c016e990d css/webpack: Transition common.css to webpack.
This commit transitions common.css from the Django pipeline
to being compiled by webpack under the common bundle.
2018-05-03 19:15:23 -07:00
Lyla Fischer
aee02e2695 user docs: Add styling for keyboard_tip admonition. 2018-05-03 18:33:35 -07:00
Rohitt Vashishtha
7fef91a405 zblueslip: Convert node_tests/settings_user_groups.js to zblueslip. 2018-05-03 16:27:05 -07:00
Rohitt Vashishtha
22ca18f59c zblueslip: Convert node_tests/pm_list.js to zblueslip. 2018-05-03 16:27:05 -07:00
Rohitt Vashishtha
c5d9a052c0 zblueslip: Convert node_tests/people.js to zblueslip. 2018-05-03 16:27:05 -07:00
Rohitt Vashishtha
8219d2dcf4 zblueslip: Convert node_tests/messge_store.js to zblueslip. 2018-05-03 16:27:05 -07:00
Rohitt Vashishtha
f51e151e62 zblueslip: Convert node_tests/markdown.js to zblueslip.
Also allows comparing in zblueslip using toString() for cases like
comparing an `Error('hello')` object and a `'hello'`.
2018-05-03 16:27:05 -07:00
Rohitt Vashishtha
cba2c529f9 zblueslip: Convert node_tests/activity.js to zblueslip. 2018-05-03 16:27:05 -07:00
Steve Howell
a68fa980d3 Add starred messages to our new API for unread ids.
Even though starred messages are never unread, it's useful
for us to have helper functions for them.

This change makes it so that clicking on "Starred Messages"
takes you to the last read message immediately, without a
server delay.
2018-05-03 14:36:34 -07:00
Tim Abbott
ad0cfb3512 validators: Add check_string_fixed_length.
This will be useful in some upcoming work on zilencer.
2018-05-03 14:30:03 -07:00
Tim Abbott
594451707d check_capped_string: Clean up corner case.
We were rejecting strings of length equal to the max.

While we're at it, fix the unnecessary period in the error message,
which doesn't align with similar validators.
2018-05-03 14:30:03 -07:00
Rishi Gupta
d18102d1c6 zilencer: Remove uniqueness constraint on RemoteZulipServer.hostname.
Enforcing the unique constraint adds an unnecessary support burden for
figuring out who actually controls a given hostname, and in particular, for
verifying updates to the org id/key on a re-install of the Zulip server.
2018-05-03 12:50:29 -07:00
Rishi Gupta
42a99e8c1d zilencer: Rename remote_server_unregister_push. 2018-05-03 12:50:29 -07:00
Rishi Gupta
76650f5930 zilencer: Rename remote_server_register_push to register_remote_push_device. 2018-05-03 12:50:29 -07:00
Steve Howell
3e19efca36 Change narrow.get_first_read_id -> get_first_read_info.
This function, which is only used in tests so far, needs
to return something more meaningful than undefined when
we don't find an id.
2018-05-03 12:44:30 -07:00
Steve Howell
4eb033964e Add fetch_status.has_found_newest(). 2018-05-03 12:44:30 -07:00
Tim Abbott
bd110ccb3c test-migrations: Add django_two_factor migrations to exclude list.
We don't control the names for these migrations.
2018-05-03 12:23:25 -07:00
Tim Abbott
42fe331093 echo: Fix exception when topic-editing locally echoed messages.
Previously, if you topic-edited locally echoed messages, it would
throw this exception:

TypeError: Cannot read property 'indexOf' of undefined
    at Object.h.is_status_message (https://chat.zulip.org/static/min/app.274582375298.js:1742:154)
           = static/js/markdown.js line 125 column 13

    at Object.h.apply_markdown (https://chat.zulip.org/static/min/app.274582375298.js:1741:291)
           = static/js/markdown.js line 93 column 29

    at Object.a.edit_locally (https://chat.zulip.org/static/min/app.274582375298.js:1750:195)
           = static/js/echo.js line 176 column 5

    at Object.d.save (https://chat.zulip.org/static/min/app.274582375298.js:1921:376)
           = static/js/message_edit.js line 132 column 13

The behavior with this patch is still not ideal, in that we don't ever
send the edit to the server (if the issue was lag, not an actual error
on sending), but this at least makes the on-error case correct.
2018-05-03 12:14:43 -07:00
Umair Khan
9a3b310db9 2FA: Enable apps and middlewares unconditionally. 2018-05-03 11:59:32 -07:00
Shubham Dhama
9f78540bd0 org settings: Make save-discard widget look better.
This fixes some minor glitches with buttons:
* Movement of the organization-settings-parent block on the
  appearance of widgets.
* Large and odd look of save button.
* Use of fadeIn and fadeOut rather than changing opacity as
  opacity don't actually remove them.
2018-05-03 10:32:34 -07:00
Eeshan Garg
d28d08e7da streams: Add get_default_value_for_history_public_to_subscribers().
This commit adds a function that makes it easier to get a default
value for Stream.history_public_to_subscribers when one isn't
explicitly provided.
2018-05-03 10:24:27 -07:00
Eeshan Garg
7e379bbb76 writing-bots: Recommend using Python 3 to run tools/provision. 2018-05-03 10:11:49 -07:00
Tim Abbott
0ada5fa9d8 stream_data: Fix exception when notifications_stream is private.
If notifications_stream is private and the current user has never been
subscribed, then we would throw an exception when trying to look up
notifications_stream.  In this situation, we should just treat it like
the stream doesn't exist for the purposes of this user.
2018-05-03 08:33:07 -07:00
Priyank Patel
1e1b72f6c8 webpack: Transition stats.css to use webpack.
The stats.scss file is added to activity bundle.
2018-05-03 08:08:23 -07:00
Priyank Patel
c780bc33ba webpack: Transition activity.css to use webpack.
The activity.scss file is added to activity bundle.
2018-05-03 07:53:24 -07:00
Steve Howell
c00a054893 minor: Update comment in narrow.activate(). 2018-05-03 07:52:15 -07:00
Steve Howell
93ce1fa95c refactor: Extract save_pre_narrow_offset_for_reload().
This is purely to make it easier to read narrow.activate()
without having to page past lots of unnecessary detail when
you're trying to understand things like how we set the
selection.
2018-05-03 07:52:15 -07:00
Steve Howell
e0557046f3 refactor: Extract narrow.update_selection().
The maybe_select_closest helper, when first introduced, was
tiny and close to its callers.

As it's grown, it's become kind of a big hurdle to reading
narrow.activate(), because it's out of chronological order
and it's hard to tell at a glance which variables it's closing
on.

Now we just move it out to module scope.

It's mostly moving code, with these minor changes:

        * we pass in opts for the old closure vars
        * we rename then_select_offset -> select_offset
        * we early-exit on empty lists
2018-05-03 07:52:15 -07:00
Steve Howell
d359c89b0c refactor: Introduce select_strategy in narrow.activate.
We replace these variables in narrow.activate:

        then_select_id (int w/-1 as a sentinel)
        select_first_unread (boolean)

The main goal here is to get away from the boolean, since
we are about to introduce a third select strategy.

The new var is select_strategy and it has a union
type with these flavors:

        "exact" (was select_first_unread === false)
        "first_unread" (was select_first_unread === true)

The new flavor will be something like "last_id".

Eliminating then_select_id is also nice, since the -1
sentinel value could be a pitfall, and it's semantically
cleaner to encapsulate behind a check for
select_strategy.flavor.
2018-05-03 07:52:15 -07:00
Steve Howell
b046b158d9 refactor: Add a fetch_message IIFE in narrow.activate.
We use an IIFE (immediately invoked function expression)
to fetch messages.  This will allow us to introduce some
local vars in a subsequent commit without creating an ugly
diff and without cluttering an already crowded namespace.
2018-05-03 07:52:15 -07:00
Steve Howell
0bbbdb65b4 minor: Introduce msg_id in maybe_select_closest().
This cleans up a subsequent diff.  Within the context of
`maybe_select_closest`, there's only one `msg_id` we care about,
so the more convoluted name `then_select_id` makes much less
sense than it does in the enclosing scope, and it will make
even less sense after some future changes.

There's also some cosmetic cleanup here.
2018-05-03 07:52:15 -07:00
Steve Howell
df0e4a73fa refactor: Remove brittle check for !select_first_unread.
When we are deciding whether to preserve scroll position, we
mainly care that then_select_offset is set to a value.  If
we had no intention of preserving scroll offset, we would have
never bothered to set it.  The check for !select_first_unread
is always redundant, as verified by lots of clicking around
with some print debugging.  And it's a brittle check,
because it couples the decision of scrolling destination to
the mechanism by which we decide our selection.  While those
things are closely related, it's possible in the future that
we'll decide to advance to an unread message and still want
to set then_select_offset, but we might forget to mutate
select_first_unread.

Long story short, the code is simpler and safer now.
2018-05-03 07:52:15 -07:00
Steve Howell
3e08270d48 refactor: Simplify then_select_offset calculation.
We move the var declaration of then_select_offset closer to
where it gets calculated, and we avoid code duplication in
calling current_msg_list.get_row().
2018-05-03 07:52:15 -07:00
Steve Howell
d021a51047 Avoid brittle attempt to get messages with id === -1.
Even when then_select_id has the sentinel value of -1, we were
trying to look it up in our message_list.all object.  This would
have returned undefined, which is fine, but it's more explicit
to just bypass the check.
2018-05-03 07:52:15 -07:00
Steve Howell
6bab68ff6a node tests: Fix flaws with narrow_activate tests.
The original version of this function was simulating kind
of an illogical code path, where -1 was sort of pointing to
a real message, which doesn't make sense.

Now we pass in an explicit then_select_id.
2018-05-03 07:52:15 -07:00
Akash Nimare
954a04c52a stream-settings: Improve selection behavior around the stream-settings.
Fixes: #9276.
2018-05-02 21:05:41 -07:00
Puneeth Chaganti
e6ac98cc9b vagrant: Get vagrant up and running with lxc 3.0.0 2018-05-02 21:04:27 -07:00
Armaan Ahluwalia
8fe54a533a test_home: Change requiring pygments.css to requiring app-styles.js.
This commit changes the tests based on the fact that pygments.css
will no longer be found in the template during testing. pygemnts.css
is being compiled by webpack under app-styles and so we look for the
stubentry for app-styles instead.

Tweaked by tabbott to do a cleaner test.
2018-05-02 17:18:11 -07:00
Armaan Ahluwalia
5f7b47e20c css: Transition 'app.css' to SCSS.
This commit transitions all styles in app.css in the Django pipeline
to being compiled by webpack in an app-styles bundle, and renames the
various files to now be processed as SCSS.

To implement this transition, we move the old CSS file refernces in
settings.py and replace them with a bundle declared in
`webpack.assets.json` and includedn in the index.html template

Tweaked by tabbott to keep the list of files in `app.css` in
`webpack.assets.json`, and to preserve the ordering from the old
`settings.py`.
2018-05-02 17:13:16 -07:00
Armaan Ahluwalia
64dadae697 webpack: Add css-hot-loader to remove flash on unstyled content.
This commit removes the flash on unstyled content while in dev
mode that was caused by the use of style-loader. Instead it
enables mini-css-extract-plugin in dev in combination with
css-hot-loader which enables HMR for development.

This is because mini-css-extract-plugin does not currently support
HMR out of the box. It also adds a SourceMapDevtoolPlugin to enable
sourcemaps with css since mini-css breaks sourcemaps when used in
combination with the cheap-module-evel-source-map setting.

Related issues:
https://github.com/webpack-contrib/mini-css-extract-plugin/issues/34
https://github.com/webpack-contrib/mini-css-extract-plugin/issues/29
2018-05-02 16:55:58 -07:00
Aditya Bansal
c38b70566c views/archive.py: Make 'StreamDoesNotExist' return a error msg page. 2018-05-02 15:23:33 -07:00
Aditya Bansal
583f50179c public_archives: Add floating recipient bar. 2018-05-02 15:23:33 -07:00
Aditya Bansal
898c281692 public_archives: Add styling to the public topics. 2018-05-02 15:23:33 -07:00
Aditya Bansal
5d7907b59f public_archives: Add basic infra for displaying topics.
We add very basic infra so that we can view any discussion which
happened under a topic of a global public stream without
authorization.
2018-05-02 15:23:33 -07:00
Aditya Bansal
59cd440d39 portico: Change CSS class to float-left and float-right.
This is done because the current column-left and column-right were
actually just floating left and right and making use of float-left
and float-right makes more sense. This also helps with the upcoming
public archives feature which will try to include portico content
with main app content.
2018-05-02 15:23:33 -07:00
Aditya Bansal
75d76e4eb3 lib/streams.py: Extract get_stream_by_id as a separate function.
We extract get_stream_by_id function out of the body of
access_stream_by_id function to help us access streams for archives.
2018-05-02 15:23:33 -07:00
Aditya Bansal
a62efd55df linter: Make duplicate html tag id detection work with archives.
We modify check-templates to check for duplicate id's in archive
templates and app templates separately. This means we are allowing
app and archive templates to potentially use same id's. This is
needed because we intend to re use some js from the main app and
having same id's help achieve that.
Note: We haven't up until this point actually added archive
templates. This commit is more of a preparatory commit for merging
the basic archive infra.
2018-05-02 15:23:33 -07:00
Aditya Bansal
9629be689b populate_db: Add a web public stream to dev database.
We flip the Stream "Rome" to be a web public stream. Also we add
attribute is_web_public in various stream dicts and in the
bulk_create_streams function of bulk_create.py responsible for
default stream creation in dev environment.
2018-05-02 15:23:33 -07:00
Aditya Bansal
1f358954be web_public_streams: Add is_web_public to Stream table.
Also add function do_change_stream_web_public in lib/actions.py
to help in changing a streams web public status.
2018-05-02 15:23:33 -07:00
Umair Khan
29e3a1d576 two_factor: Add templates for 2-factor-auth setup.
Note from Tim: We'll likely need to do some work on the strings in
these before translating, so I removed some translation tags.
2018-05-02 15:20:49 -07:00
Umair Khan
b778259547 login-page: Return early if resp is a redirect.
Redirect response does not need a context.
2018-05-02 15:15:37 -07:00
Umair Khan
e8b0ae821b two_factor: Fix namespace error.
If you specify namespace, Django throws an error.
2018-05-02 14:59:14 -07:00
Umair Khan
aef2234e97 login_page: Create update_login_page_context().
This will also be used from two factor login.
2018-05-02 14:30:02 -07:00
Steve Howell
1941a0eb51 refactor: Swap conditions for pre-filling narrows.
This mostly sets up the next commit.  The two conditions here
are both inexpensive to check, but we want to bypass an upcoming
expensive operation if can_apply_locally() returns false.
2018-05-02 13:34:54 -07:00
Steve Howell
66cd2edee4 Add narrow_state.get_first_unread_id(). 2018-05-02 13:34:54 -07:00
LuisFSilva
a072b2a153 right-sidebar: Fix group PMs online indicator.
People found it confusing that it would show up at light-green when
the users in the thread were idle.

Fixes #8242.
2018-05-02 12:41:01 -07:00
Shubham Dhama
02c3223985 upload: Improve logic for hiding progress bar for fast upload.
With past logic, on fast upload progress bar don't appears because
uploadFinished is called as soon as upload is finished so
progress bar get disappeared. To make these hiding of progress bar
smooth we set setTimeout for every hiding of progress bar as well
as complete status element.
2018-05-02 12:32:13 -07:00
Shubham Dhama
c67897ba5b upload: Make progress bar for each file independent.
Here `file.lastModified` is unique for each upload so it is used
to track each upload individually.
Also, we have used `uploadStarted` function because it is
called for each file during an upload.

Fixes: #9068.
2018-05-02 12:32:13 -07:00
Shubham Dhama
6d33e73b5f upload: Remove progress bar only when upload is finished.
Previous logic was little buggy, as many time there can be considerable
difference between uploadFinished and progressUpdated as progressUpdated
can finish much earlier(on a slow connection) and the "uploaded file"
markdown text is inserted with some delay.
It is also a preliminary commit for making each progress bar independent
as currently progressUpdated may close upload_bar even after only
one file out of many files is uploaded.
2018-05-02 12:32:13 -07:00
Shubham Dhama
9d575ffd1c upload: Add/enable uploadStarted option in jquery-filedrop plugin.
We were missing it but it is added in the upstream, so just added it
at the appropriate place in plugin code(in the upstream there is some
code refactoring but this seems to be the most appropriate place).
2018-05-02 12:32:13 -07:00
novokrest
0fb13eed2f outgoing_webhook: Extract success response handling to separate method.
Extract success response handling in do_rest_call() method to
separate method process_success_response()
2018-05-02 11:57:26 -07:00
novokrest
036bc120c3 outgoing_webhook: Extend process_success() return value to tuple.
Change return value type of OutgoingWebhookServiceInterface.process_success
to 2-elements tuple as (success_message, failure_message)
2018-05-02 11:57:08 -07:00
Shubham Dhama
093c212b0c org settings: Change realm_org_join_restrictions label text.
This change with label text is done to make things similar the one for
realm_user_invite_restriction.
2018-05-02 11:42:28 -07:00
Shubham Dhama
4c28a79815 org settings: Clean up the context for admin pages.
This is a minor clean up of some contexts for admin pages
which we don't need anymore.
2018-05-02 11:42:28 -07:00
Shubham Dhama
c293bb82c4 org settings: Change user joining invitation setting to dropdown. 2018-05-02 11:42:26 -07:00
Steve Howell
230ecb24ed Add narrow_state.get_unread_ids(). 2018-05-02 11:23:58 -07:00
Steve Howell
ceee49c075 Add filter.is_for_only(operand). 2018-05-02 11:23:58 -07:00
Steve Howell
7bc95efb41 Add unread.get_msg_ids_for_mentions(). 2018-05-02 11:23:58 -07:00
Steve Howell
9987ea525f Add unread.get_msg_ids_for_private(). 2018-05-02 11:23:58 -07:00
Tim Abbott
4e8487c886 nagios: Bump maximum processes limits.
These seemed to be flapping for no good reason.
2018-05-02 11:12:47 -07:00
Rishi Gupta
0e702a15eb portico: Add Slack import to /features.
As a followup project it might make sense to link things on our /features
pages to things in /help.
2018-05-02 10:12:30 -07:00
Tim Abbott
3702131b6d hello.html: Add additional media-query for carousel. 2018-05-02 09:56:46 -07:00
gooca
c81b103a5b hello.html: Better handling of carousel's mobile display. 2018-05-02 09:53:54 -07:00
Armaan Ahluwalia
9f80418d12 webpack: Add support for png file imports in file-loader.
Adds support for importing png files using file-loader in webpack.
Changes the name of the output directory to be files instead of
fonts for better readability.
2018-05-02 09:45:04 -07:00
Armaan Ahluwalia
fce6882eb9 webpack: Move the styles consumed by 5xx.html to webpack.
This commit removes the need for portico.css to be generated
by the Django pipeline and makes the error page use the css
file compiled by webpack instead.
2018-05-02 09:45:01 -07:00
Steve Howell
7222dbd99b refactor: Simplify narrow_state.is_for_stream_id().
This takes advantage of the new function narrow_state.stream_id().

We now assume the incoming stream_id is a valid stream_id, so we
no longer need to test some of the error checking.  (It's possible that
the incoming stream_id may no longer be for a stream you subscribe
to, but the nice benefit of working more in "id space" is that if
it doesn't match the narrow's stream id, we know false is a safe
return value.)
2018-05-02 09:16:24 -07:00
Steve Howell
a62c85c015 Add narrow_state.stream_sub() and narrow_state.stream_id(). 2018-05-02 09:16:24 -07:00
Steve Howell
0232e92038 Add filter.is_pm_with_only(). 2018-05-02 09:16:24 -07:00
Steve Howell
2a70f0dba4 Add filter.is_stream_topic_only(). 2018-05-02 09:16:24 -07:00
Steve Howell
08307c0e87 Add filter.is_stream_only(). 2018-05-02 09:16:24 -07:00
Steve Howell
861cb16c3e minor: Remove redundant function names in Filter class.
We no longer use the style of giving a function name to a
function that already has a name from the object it gets
attached to.
2018-05-02 09:16:24 -07:00
Steve Howell
0150c01027 Add unread.get_msg_ids_for_person(). 2018-05-02 09:16:24 -07:00
Steve Howell
2b35f26b88 Add unread.get_msg_ids_for_stream(). 2018-05-02 09:16:24 -07:00
Steve Howell
c432acb436 Add unread.get_msg_ids_for_topic().
This will be useful for some narrowing related changes.
2018-05-02 09:16:24 -07:00
Steve Howell
76b97d8b54 refactor: Add util.sorted_ids().
We borrowed this from typing_data.js and gave it a slightly
different name (sorted -> sorted_ids).
2018-05-02 09:16:24 -07:00
Tim Abbott
a0e8a37e7f topics: Fix get_topics_backend logic for zephyr realms.
This removes a check on invite_only, that should have been a check on
history_public_to_subscribers.  In addition to fixing a bug for zephyr
realms, it also makes "more topics" work correctly for realms using
the new settings for stream history being public to subscribers.
2018-05-02 09:02:57 -07:00
Tim Abbott
4df886f36f populate_db: Fix initialization of history_public_to_subscribers.
This was being incorrectly not initialized properly in the test suite,
because we neglected to update the bulk_create code path for creating
streams.
2018-05-02 09:02:57 -07:00
Tim Abbott
866cb38270 test_classes: Compute history_public_to_subscribers correctly.
We apparently missed updating this when we split out this database field.
2018-05-02 09:02:57 -07:00
Rohitt Vashishtha
2f6da2661f push_notifications: Format blockquotes properly in text_output.
New output is of the format:

Hamlet said:
> Polonius said:
> > This is the.
> > Second layer of nesting.
> First layer of nesting.

Fixes #9251.
2018-05-02 08:57:17 -07:00
gooca
a8830ec8da hello.html: Add testimonial carousel.
Replaces single testimonial and replaces it with multi-testimonial
slider.

Quote added by Tim Abbott with permission from Jacinda.
2018-05-01 16:45:52 -07:00
Vishnu Ks
8705ac1091 portico: Add noindex tags for non root realms.
There may be further work required for the /integrations pages.
2018-05-01 15:28:30 -07:00
Tim Abbott
77e57dd033 index.html: Clean up indentation and whitespace in HEAD.
This is mostly to make our linter happy after removing the conditional
in the previous commit.
2018-05-01 09:49:28 -07:00
Tim Abbott
d92edb8ea5 css: Remove support for legacy desktop app.
We haven't seen significant traffic from the legacy desktop app in
over a year, and users using it get a warning to upgrade since last
summer, so it's probably OK to stop providing special fonts for it.
2018-05-01 09:49:28 -07:00
Rohitt Vashishtha
19b228bca4 sidebars: Disable autocomplete for user and stream search inputs.
Fixes #9269.
2018-05-01 09:24:40 -07:00
Rohitt Vashishtha
6c96ba79e0 test_bugdown: Fix ignore-testcase feature for markdown_test_cases.
We accidentally were 'return'ing on encountering an ignored case, and thus
exiting the loop, not running further testcases.
2018-05-01 09:24:15 -07:00
Tim Abbott
c1432d9dfc slack import: Reformat UserProfile to one-line-per-field.
This should be more readable and convenient for future editing.
2018-05-01 09:16:12 -07:00
Tim Abbott
a2f49b425b slack import: Fix some erroneous UserProfile field values.
* tutorial_status was the invalid value 'T'; should be the default of 'W'.
* last_reminder can be just the default None
* enable_desktop_notifications was just the model default of True.
2018-05-01 09:14:47 -07:00
Rhea Parekh
f00b80058d slack import: Remove unwanted comments. 2018-05-01 09:09:36 -07:00
Rhea Parekh
e579bef8fd slack import: Improve how we construct user_profile objects.
Fixes #9260
2018-05-01 09:09:36 -07:00
Vishnu Ks
c80babdf95 provision: Increment version for generating email templates.
Email source files were modified for adding translation tags
but version was not incremented.
2018-05-01 17:57:46 +05:30
Angelika Serwa
f4f64243dd custom_profile_fields: Support changing the sort order of the fields.
Tweaked by tabbott for variable naming and the URL.

Closes #8879.
2018-04-30 18:17:41 -07:00
Vishnu Ks
acf00c1130 why-zulip: Include cartoon of Julia Evans. 2018-04-30 17:46:02 -07:00
Tim Abbott
a4ff917789 emails: Fix spelling of "Unknown IP" and tag for translation. 2018-04-30 12:04:39 -07:00
Vishnu Ks
b72874226f enails: Pass new login details as separate variables.
Refactored by tabbott to eliminate some unnecessary complexity.
2018-04-30 12:03:10 -07:00
Vishnu Ks
363d17f2bb emails: Add translation tags to notify_change_in_email. 2018-05-01 00:11:44 +05:30
Vishnu Ks
69b0783b35 emails: Pass realm_name instead of realm in notify_change_in_email context. 2018-05-01 00:11:44 +05:30
Shubham Padia
23e82315b5 stream-settings: Remove redundant actually_filter_streams function call.
After adding a newly created stream to the top of the stream list,
call to actually_filter_streams in stream_events.mark_subscribed
rerendered the filter_table and the stream list was refreshed.  The
call to actually_filter streams was introduced to rerender the
subscriber list but stream_edit.rerender_subscribers_list takes care
of it already.

Fixes #9033.
2018-04-30 11:38:47 -07:00
Yashashvi Dave
dbd24c5c93 create stream: Fix preview btn not showing on private stream creation.
Fixes #9028
2018-04-30 11:15:07 -07:00
Yashashvi Dave
86eddd79bc stream settings: Fix scrollbar bug on unsubscribing themselves.
Fix scrollbar not updating when user unsubscribe themselves
from "Stream members" list, by clicking "Unsubscribe" button.

Fixes #9029
2018-04-30 11:15:07 -07:00
Tim Abbott
de30474ddd test_custom_profile_data: Don't do a database query on import.
This is a general code cleanlyness improvement.

While we're at it, we combine together two test classes that didn't
have a particularly good reason for existing.
2018-04-30 10:57:23 -07:00
Yashashvi Dave
0a9fbe2ce6 zerver/tests/test_custom_profile_data.py: Refactor tests.
Refactor tests to include generic function for invalid
assertion and remove repetitive assertion code.
2018-04-30 10:53:23 -07:00
Yashashvi Dave
a97a00a4c6 custom fields: Replace field id with field name in error message. 2018-04-30 10:53:23 -07:00
Yashashvi Dave
0d7d94d0db custom fields: Add support for custom URL field type. 2018-04-30 10:53:23 -07:00
Yashashvi Dave
d2128105dd custom fields: Fix bug in real time sync for user settings.
Currently when admin add/remove/update custom fields, changes
are not reflected in user settings page, if settings tab
is already open. This might be rare case, but it looks like
an error when admin go to user settings page just after
updating custom fields in org settings.

Fix this by re-rendering custom fields in user settings
on custom_profile_fields event.
2018-04-30 23:04:25 +05:30
Yashashvi Dave
4033f210af custom fields: Add support for custom date field type. 2018-04-30 23:04:25 +05:30
Yashashvi Dave
94d787aa2e zerver/test/test_custom_profile_data.py: Remove hard-coded field no's. 2018-04-30 23:04:25 +05:30
Yashashvi Dave
576920bf8c custom fields: Add frontend validations in textual custom fields.
Add validations for short and long textual custom fields in
frontend.
2018-04-30 23:04:25 +05:30
Priyank Patel
50b13219a3 webpack: Combine both js and css into one portico bundle.
Combines, both portico js and css into one bundle. This for now solve
the issue of an empty js bundle being generated by webpack for the
portico-styles stylesheet.
2018-04-30 10:23:39 -07:00
Yashashvi Dave
5e63e6061b custom fields: Add UI for custom field hint.
Fixes #8876
2018-04-30 10:14:50 -07:00
Rohitt Vashishtha
639fa0db77 zblueslip: Convert node_tests/narrow_state.js to zblueslip. 2018-04-30 10:12:55 -07:00
Rohitt Vashishtha
a87123ec23 zblueslip: Convert node_tests/input_pill.js to zblueslip. 2018-04-30 10:12:55 -07:00
Rohitt Vashishtha
aa0c9a1a2a zblueslip: Convert node_tests/dict.js to zblueslip. 2018-04-30 10:12:55 -07:00
Rohitt Vashishtha
65bb2f3c40 zblueslip: Convert node_tests/channel.js to zblueslip. 2018-04-30 10:12:55 -07:00
Yashashvi Dave
66759358e2 create stream: Add maxlength restriction on name and description. 2018-04-30 10:11:25 -07:00
Yashashvi Dave
a28adb0ba3 stream settings: Reset stream description on error.
Reset old value of stream description on error. This commit
also update the error message on updating stream name and
description.
2018-04-30 10:11:25 -07:00
Yashashvi Dave
7e9ccead2e stream settings: Fix server error on long stream description.
Add backend validations to check stream description length.
2018-04-30 10:11:23 -07:00
Tim Abbott
976e61d687 validators: Improve error messages for check_capped_string. 2018-04-30 10:07:06 -07:00
Yashashvi Dave
8729c9001d static/js/settings_streams.js: Move delete_stream func to stream_edit.js. 2018-04-30 17:48:16 +05:30
Yashashvi Dave
7bbe44d7a0 org settings: Remove "Delete streams" administrative tab from settings.
Fixes #9227
2018-04-30 17:47:34 +05:30
Eeshan Garg
0a7d1bc746 webhooks/freshdesk: Update docs to conform to new style guide. 2018-04-29 18:06:14 -07:00
Eeshan Garg
057ff9c91e models: Add Stream.history_public_to_subscribers.
This commit adds a new field history_public_to_subscribers to the
Stream model, which serves a similar function to the old
settings.PRIVATE_STREAM_HISTORY_FOR_SUBSCRIBERS; we still use that
setting as the default value for new streams to avoid breaking
backwards-compatibility for those users before we are ready with an
actual UI for users to choose directly.

This also comes with a migration to set the value of the new field for
existing streams with an algorithm matching that used at runtime.

With significant changes by Tim Abbott.

This is an initial part of our efforts on #9232.
2018-04-28 22:54:04 -07:00
Tim Abbott
9729b1a4ad search: Remove buggy double-call of set_message_booleans.
In a refactor last fall, we changed `set_message_booleans` to mutate
state (specifically, destroying msg.flags in favor of setting
properties like `msg.unread`).  This was fine for most code paths, but
the maybe_add_narrowed_messages code path called
`message_store.add_message_metadata` twice (once after talking to the
server to find out whether the messages go into the current narrow),
and so when we extracted set_message_booleans from that, the second
call didn't properly short-circuit.

We fix this by just removing the second call, and also add a comment
warning about the add_message_metadata call there as being dangerous.

Fixes #8184.
2018-04-28 14:39:24 -07:00
Tim Abbott
e087be6630 home: Ignore handlebars errors in the test suite.
The handlebars error message is just for the manual development
environment; this prevents the state of compiling handlebars templates
from run-dev.py from potentially causing the unit tests to fail.
2018-04-28 13:49:24 -07:00
Tim Abbott
7d6bb3dcb4 settings: Remove obsolete default_desktop_notifications setting.
This actually hasn't been hooked up to do anything in years.

While we're at it, we remove the entire "Zulip Labs" settings page.
2018-04-28 13:46:07 -07:00
Shubham Dhama
7f679bcdce org settings: Make allowed domain table status element fade out. 2018-04-28 13:24:59 -07:00
Shubham Dhama
689c717284 message_edit: Don't offer UI to edit topics after time limit.
This fixes a couple places where with community topic editing, we'd
show the edit-topic UI basically indefinitely.
2018-04-28 13:14:27 -07:00
Shubham Dhama
da8157d414 message: Extract function for editability of topic. 2018-04-28 13:00:29 -07:00
Shubham Dhama
30b1ec9433 org settings: Change default allow_community_topic_editing value.
This changes the default value of allow_community_topic_editing to
`True` which was merged with `False` default value temporarily.
2018-04-28 13:00:29 -07:00
Shubham Dhama
7f9cfab15a org settings: Add frontend for allow_community_topic_editing. 2018-04-28 13:00:29 -07:00
guaca
3c01ea78b0 settings: Add modal fade.
This implements the modal fade idea suggested here:

https://chat.zulip.org/#narrow/stream/6-frontend/subject/Visual.20Design.20Strategy.20for.20Settings.20UI/near/559763
2018-04-28 12:55:15 -07:00
Steve Howell
160931377f node tests: Test deactivating streams. 2018-04-28 11:15:14 -07:00
Steve Howell
6e851f98f6 node tests: Test refreshing pinned streams. 2018-04-28 11:15:14 -07:00
Steve Howell
cf24445809 node tests: Add test for stream_list.rename_stream. 2018-04-28 11:15:14 -07:00
Steve Howell
74e7c81c94 node tests: Add coverage for topic zooming.
These tests cover the stream_list side of the interaction.
2018-04-28 11:15:14 -07:00
Steve Howell
c22a1d1f23 refactor: Simplify return values for would_receive_message().
Instead of treating false differently from undefined, our
function is now a regular boolean function, and we limit our
code comments to the one corner case where the true/false
decision is kind of arbitrary and possibly confusing.
2018-04-28 11:15:14 -07:00
Steve Howell
2efae10c7c refactor: Remove dead code from would_receive_message().
The buddy list never includes yourself nor bots, so we
remove the special case handling for those situations.

If we were to put bots or the current user back in the list,
I'm not convinced the old logic was what we'd want in either
case going forward.

For example, we might want to fade bots that aren't subscribed
to public streams, since they might otherwise confuse people,
but then again they would receive messages.  And then "yourself"
is a recipient in the technical sense but they're kinda
not and either way it doesn't provide much signal either way.
2018-04-28 11:15:14 -07:00
Steve Howell
fe62dacee0 node tests: Add coverage for stream sidebar search. 2018-04-28 11:15:14 -07:00
Steve Howell
d1bf6028ef Remove hack for stream cog.
We don't need to special-case the stream cog handler when we
handle the click event for the surrounding header.  The browser
will fire the event for the cog first, which stops propagation.
2018-04-28 11:15:14 -07:00
Steve Howell
e9c6f3a07d stream list: Use newer code for the list cursor.
The new list_cursor class is more generic and saves the state
of your cursor across redraws.

Note that we no longer cycle from bottom to top or vice versa.

The node test code that was removed here was kind of complex
and didn't actually assert useful things after calling methods.
2018-04-28 11:15:14 -07:00
Steve Howell
779535fda3 minor: Extract some vars in stream_sort tests.
This is just to make the next commit a bit more convenient.
2018-04-28 11:15:14 -07:00
Steve Howell
605a90ce36 node tests: Reach 100% coverage for scroll_util.js. 2018-04-28 11:15:14 -07:00
Steve Howell
97b9367d20 refactor: Extract scroll_util.js.
We now use scroll_element_into_container() in two different
places, so it's worth extracting.
2018-04-28 11:15:14 -07:00
Steve Howell
068e4bf32b buddy list: Populate user-fade via templates.
When we populate the buddy list or update it for activity, we now
have buddy_data set a faded flag that is rendered in the template.
This avoids some re-rendering overhead and is on the eventual path
to having our widget be more data-oriented (and all rendering happens
"behind" the widget).

We still do direct DOM updates when the compose state changes or
when we get peer subscription events.
2018-04-28 11:15:14 -07:00
Steve Howell
d88d6df53b refactor: Fix naming conventions in compose_fade.js.
We don't really need the "_" prefix for private functions, since
public functions are exported, and it was a bit distracting.
2018-04-28 11:15:14 -07:00
Steve Howell
d4fc92c1c7 refactor: Rename method to compose_fade.update_all().
The function was misnamed before.
2018-04-28 11:15:14 -07:00
Steve Howell
5d6c9c1b47 compose_fade: Extract user_fade_config.
This commit extracts the key UI elements of updating the buddy
list for compose fade into a configuration, and we interact with
the buddy_list API.
2018-04-28 11:15:14 -07:00
Steve Howell
fb712027bf buddy list: Fix and simplify up/down navigation.
This introduces a generic class called list_cursor to handle the
main details of navigating the buddy list and wires it into
activity.js.  It replaces some fairly complicated code that
was coupled to stream_list and used lots of jQuery.

The new code interacts with the buddy_list API instead of jQuery
directly.  It also persists the key across redraws, so we don't
lose our place when a focus ping happens or we type more characters.

Note that we no longer cycle to the top when we hit the bottom, or
vice versa.  Cycling can be kind of an anti-feature when you want to
just lay on the arrow keys until they hit the end.

The changes to stream_list.js here do not affect the left sidebar;
they only remove code that was used for the right sidebar.
2018-04-28 11:15:14 -07:00
Steve Howell
c63f2db25b hotkeys: Remove call to activity.blur_search().
The blur_search() function was removed in this commit:

See da06832837

We now no longer attempt to call it.  It's not completely clear
to me what this did before, but we are rewriting a lot of the
keyboard navigation for search anyway.
2018-04-28 11:15:14 -07:00
Steve Howell
f11b3c9934 buddy_list: Clean up selector references.
In this cleanup I make it so that all jQuery selector references
are toward the top of the module, and we do all finds relative
to the container ('#user_presences').

This will make it easier to make a better list abstraction for
the buddy list, for things like progressive rendering.
2018-04-28 11:15:14 -07:00
Steve Howell
65d8eb3189 buddy list: Extract user_search.js.
This was a bit more than moving code.  I extracted the
following things:

        $widget (and three helper methods)
        $input
        text()
        empty()
        expand_column
        close_widget
        activity.clear_highlight

There was a minor bug before this commit, where we were inconsistent
about trimming spaces.  The introduction of text() and empty() should
prevent bugs where users type the space bar into search.
2018-04-28 11:15:14 -07:00
Steve Howell
2879a63bcc buddy list: Relax count limit when doing searches.
A recent change filtered out offline users from the buddy list
whenever the list size would otherwise exceed 600.

This commit reverts half that change--we can now show 600+ users
again, but only when searching.
2018-04-28 11:15:14 -07:00
Tim Abbott
127ac0df54 auth: Remove unnecessary case from validate_email_for_realm.
The removed code path was only needed due to buggy setup code in the
test_cross_realm_scenarios test.  We address that with a less buggy
workaround, and which lets us remove unnecessary complexity from this
important validation function.

Thanks for Umair Waheed for some preliminary work on this.

Fixes #7561.
2018-04-28 11:03:03 -07:00
Tim Abbott
16873cd1ff static: Remove unnecessary handlebars README. 2018-04-28 10:46:16 -07:00
Tim Abbott
955ef3b18c bot settings: Fix spelling of data-user-id. 2018-04-28 10:23:46 -07:00
Shubham Dhama
ccd5581bcd org settings: Handle floating point durations better for time limits.
Fixes: #9253.
2018-04-28 09:33:31 -07:00
Armaan Ahluwalia
fb0a421b8c webpack: Silence most webpack output for tests.
This commit adds a --quiet argument to tools/webpack which removes
the verbose output from webpack and replaces it with showing only
errors. It also makes tools/run-dev --tests use this argument while
running webpack for testing.

Tweaked by tabbott to clean up the code a bit.
2018-04-28 09:32:10 -07:00
Shubham Dhama
fdc4de9435 org settings: Remove redundant case in update_dependent_subsettings.
This is because we cover the case of `realm_allow_message_editing` by
`realm_msg_edit_limit_setting` after the conversion into dropdown.

This commit also contains a minor variable renaming.
2018-04-27 19:35:22 -07:00
Tim Abbott
a3fc7d1371 message_edit: Fix spelling of "has passed". 2018-04-27 19:24:14 -07:00
YJDave
6bef44a9fa org setting: Add time limits for message deleting.
Add realm setting to set time limit for message deleitng.
Set default value of message_content_delete_limit_seconds
to 600 seconds(10 min).

Thanks to Shubham Dhama for rebasing and reworking this.  Some final
edits also done by Tim Abbott.

Fixes #7344.
2018-04-27 19:22:28 -07:00
Tim Abbott
2d5d6a1fd1 message_edit: Fix buggy error string for message deletion.
The previous model resulted in an ugyl `.:` sequence.
2018-04-27 19:12:18 -07:00
Akash Nimare
bfcff052fe desktop-app: Update app to latest v2.0.0. 2018-04-27 17:23:17 -07:00
Tim Abbott
699c4381f2 test-api: Add a PROVISION_VERSION --force option.
This brings this in line with the Casper and other tests.
2018-04-27 17:21:12 -07:00
Tim Abbott
6cca334271 api: Document the /register API with a lot more detail. 2018-04-27 17:01:41 -07:00
Tim Abbott
9fc1458924 api: Improve documentation for real-time-events API. 2018-04-27 16:36:54 -07:00
Tim Abbott
be3804f505 api: Fix incorrectly documented event types.
It's actually "subscription" and "message" (neither is plural).

While we're at it, we should also remove the "pointer" event type,
since that's of generally low interest.
2018-04-27 15:16:24 -07:00
Eeshan Garg
7d14ce2cb6 pypi packaging: Upgrade to release 0.4.6.
As a part of the upgrade, we had to update our API tests in
zerver/lib/api_test_helpers.
2018-04-27 14:50:25 -07:00
Tim Abbott
abef9f203b api: Don't use ujson library unecessarily.
ujson is very fast, but doesn't provide good error messages for
parsing errors.
2018-04-27 14:49:46 -07:00
Eeshan Garg
bd2270eecb test-api: Add test helpers that pretty-print JSON output. 2018-04-27 14:49:34 -07:00
Eeshan Garg
3db515b306 webhooks/pivotal: Add "epic_update_activity" to unsupported types.
Epics are a way to further organize Pivotal Stories and are a
somewhat advanced feature that would take a significant amount of
work to properly implement. Unless we get requests for supporting
epics, I don't think we should support them.
2018-04-27 14:26:14 -07:00
Tim Abbott
a0fcc6ceb5 compose: Don't auto-scroll very tall messages on click.
This fixes an issue where with very tall messages (more than about a
screen in height), one would end up scrolled to the bottom of the
message if you clicked on it, which usually felt annoying.

Fixes #8941.
2018-04-27 14:19:58 -07:00
Tim Abbott
224acb8256 email_mirror: Add a test for sending to a private stream.
This verifies an important case.  We still have an open bug for why in
some production environments, the email_gateway_bot seems to not be
tagged as an API super user (resulting in this code path not working).
2018-04-27 13:56:06 -07:00
Tim Abbott
8344bd171e populate_db: Properly initialize the email_gateway_bot.
Apparently, this bot account was not properly being tagged as an API
super user in the test database; resulting in incorrect behavior if we
tried to send to a private stream in a test.

(Note that there seems to also be a similar issue in production, that
we don't understand the cause of; that is unrelated).
2018-04-27 13:56:04 -07:00
Tim Abbott
a77c61e8c1 invitation: Fix styling of Cheers message. 2018-04-27 12:05:50 -07:00
Vishnu Ks
f140b0e870 emails: Add translation tags to invitation. 2018-04-27 11:59:36 -07:00
Vishnu Ks
185811f436 emails: Add translation tags to find_team. 2018-04-27 11:59:36 -07:00
Vishnu Ks
1a34cd919c emails: Add translation tags to invitation_reminder. 2018-04-27 11:59:36 -07:00
Vishnu Ks
078dac9496 emails: Add translation tags to confirm_registration. 2018-04-27 11:59:36 -07:00
Vishnu Ks
e3314be114 emails: Add translation tags to confirm_new_email. 2018-04-27 11:59:36 -07:00
Vishnu Ks
d504c336dc portico: Mention about thirdparty desktop apps. 2018-04-27 11:14:17 -07:00
Tim Abbott
158890cb9b gitattributes: Treat .ogg files as binary. 2018-04-27 09:59:19 -07:00
Armaan Ahluwalia
93ac40105f CSS: Move portico styles to webpack compilation.
static/styles/scss/portico.scss is now compiled by webpack
and supports SCSS syntax.

Changed the server-side templates to render the portico-styles
bundle instead of directly requiring the portico stylesheet. This
allows webpack to handle stylesheet compilation and minification.

We use the mini-css-extract-plugin to extract out css from the
includes in webpack and let webpacks production mode handle
minification. Currently we're not able to use it for dev mode
because it does not support HMR so we use style-loader instead.
Once the plugin supports HMR we can go on to use it for both
dev and prod.

The downside of this is that when reloading pages in the development
environment, there's an annoying flash of unstyled content :(.

It is now possible to make a change in any of the styles included
by static/styles/scss/portico.scss and see the code reload live
in the browser. This is because style-loader which we currently
use has the module.accept code built-in.
2018-04-27 09:04:50 -07:00
Armaan Ahluwalia
f20671a509 webpack: Fix Hot Module Reloading in webpack.
This commit fixes hot module replacement in webpack. To do this
we open port 9994 used by webpack to communicate between browser
and devserver. The attempts to forward the proxy from 9991 failed
so the last resort was to open up the webpack port.
It also removes an uncessary plugin in the webpack config and moves
the --hot flag to tools/webpack.
2018-04-27 09:04:49 -07:00
Armaan Ahluwalia
bda9f3e3ea CSS: Install dependencies for SCSS Integration.
This commit installs the dependencies required for SCSS compilation
by webpack.
2018-04-27 08:52:37 -07:00
Tim Abbott
31f2c5e385 message_list: Fix hiding messages edited to a muted topic.
Previously, we did a rerender without first re-computing which
messages were muted; this was incorrect, because whether a message is
muted can change if the topic changes.

Fixes #9241.
2018-04-27 08:52:24 -07:00
Tim Abbott
3f736c9b06 message_list: Clean up API for rerender_after_muting_changes.
This was only called from two places in one function, and we can just
check muting_enabled in the caller.

This refactor is important, because we might need to update muting
after other changes (specifically, message editing to move a topic to
be muted/non-muted).
2018-04-27 08:52:24 -07:00
Tim Abbott
88951d627a viewport: Hide right-sidebar buddy list below 1024px.
This is a slight change in the responsive design, moving the 975px
cutoff to 1025px; the main effect is that for windows that just barely
had a right sidebar, we now hide the ride sidebar.  This is pretty
beneficial for the user experience specifically in the common size of
1024px, where that sidebar was making things feel a bit too
constrained.
2018-04-26 14:33:28 -07:00
Tim Abbott
0ea46e06c9 signals: Remove an unused import. 2018-04-26 11:11:45 -07:00
Tim Abbott
2cdd367d49 email_mirror: Fix handling of empty topic.
Also fixs some corner cases around pure-whitespace topics, and
migrates from the years-obsolete "no subject".

Fixes #9207.
2018-04-26 10:21:29 -07:00
Tim Abbott
3392c607c7 github_legacy: Use a client string for Legacy webhook.
This should make it easier to ensure we're getting all users migrated
off this webhook.
2018-04-26 10:21:29 -07:00
Tim Abbott
32c841dfbc github_legacy: Suppress certain TypeError exceptions. 2018-04-26 10:21:29 -07:00
Shubham Dhama
330d75efb7 settings: Make bot-settings tabs look better in dark mode.
Fixes: #9230.
2018-04-26 09:01:11 -07:00
Steve Howell
e5885fa8e4 Add compose.needs_subscribe_warning.
This function replaces part of compose_fade.would_receive_message(),
which has a real janky interface of returning true, false, or
undefined.

We don't need to couple the semantics of compose fading to whether
we help subscribe a mentioned user.  They're mostly similar, but they
will probably diverge for things like bots, and the coupling makes
it difficult to do email -> user_id conversions.

One thing that changes here is that we get the stream name from
compose_state, instead of compose_fade.focused_recipient.  The
compose_fade code uses focused_recipient for kind of complicated
reasons that don't concern us here.
2018-04-26 08:42:47 -07:00
Steve Howell
9ece6d2be4 lint: Exempt "opts." from addClass checks.
If we use something like "opts.highlight_class", it's probably
in a generic widget.
2018-04-26 08:42:47 -07:00
Steve Howell
ebe6144326 node tests: Avoid sneaky throttle/debounce delays.
For all of our current tests, we want to just execute
throttled functions immediately.
2018-04-26 08:42:47 -07:00
Steve Howell
58c67d8cba zjquery: Improve errors when handlers aren't set correctly. 2018-04-26 08:42:47 -07:00
Shubham Dhama
4a67bdea07 org settings: Fix word-wrapping CSS in "users" section.
Fixes: #9225.
2018-04-26 08:34:44 -07:00
Tim Abbott
d862bf7b48 casper: Attempt to fix nondeterministic failures in profile fields. 2018-04-26 08:33:56 -07:00
Shubham Dhama
790a9fd0b9 settings: Fix escaping of HTML in checkbox labels.
Some labels like one for `translate_emoticons` which contains HTML
get escaped because of use of `{{ label }}` syntax, which escapes
the string for XSS security purpose but since labels aren't any
threat to any such security cases, we can use triple curly brackets
`{{{ label }}}` syntax.

Fixes: #9231.
2018-04-26 00:36:34 -07:00
Umair Khan
93bb3e8d6e profile: Add UI for Choice field. 2018-04-26 00:35:54 -07:00
Umair Khan
cf2f6b38dd profile: Add choice field.
Fixes part of #8878
2018-04-26 00:35:51 -07:00
Umair Khan
4ea3e8003a profile: Create mypy types for profile data.
This makes the code more readable.
2018-04-25 23:28:27 -07:00
Umair Khan
a1b384039c profile: Check field_type against FIELD_TYPE_CHOICES. 2018-04-25 23:28:27 -07:00
Tim Abbott
9c04db1c66 slack import: Clarify what --import-into-nonempty does. 2018-04-25 22:49:12 -07:00
Tim Abbott
1ff909d971 coverage: Exclude Zephyr ccache logic from test coverage.
That code path is pretty constrained in how it's used, and is only for
the legacy Zephyr integration which we don't expect to spend effort
on again.
2018-04-25 22:41:31 -07:00
Tim Abbott
47bdf5ecba coverage: Exclude generate_test_data from testing.
That file is just used for generating manual testing data; it is
actually tested by our suite, just before coverage starts running.
2018-04-25 22:38:54 -07:00
Tim Abbott
d5946de718 decorator: Add nocoverage comments for rate_limit decorator.
We've already got a bunch of other comments on work we need to do for
this decorator and an open issue that will ensure we at some point
rework this and add tests for it.  In the meantime, I'd like to lock
down the rest of decorator.py at 100% coverage.

Fixes #1000.
2018-04-25 22:37:12 -07:00
Tim Abbott
18e7ef23fc decorator: Add a test to more fully cover require_post.
We also add a nocoverage for what is currently an impossible code
path.
2018-04-25 22:37:09 -07:00
Tim Abbott
2afec13074 decorator: Don't require coverage on @asynchronous @csrf_exempt case.
This line is potentially valuable if we ever do more with this code
path, and doesn't really cost us anything.
2018-04-25 22:36:48 -07:00
Tim Abbott
dfb946d84b decorator: Test error cases for authenticated_rest_api_view.
We now have 100% coverage on this important function.
2018-04-25 22:36:48 -07:00
Tim Abbott
6f87091120 test_push_notifications: Cover the last lines of validate_api_key.
This push notification bouncer error case wasn't previously tested.
2018-04-25 22:36:48 -07:00
Tim Abbott
2ac67a9c2f decorator: Add nocoverage markings for a few functions. 2018-04-25 22:02:09 -07:00
Tim Abbott
c6b062f26e test_decorators: Add coverage to to_not_negative_int_or_none. 2018-04-25 21:59:48 -07:00
Tim Abbott
e78b11e920 decorator: Move flexible_boolean to github webhook.
We don't really intend to use this hacky function elsewhere in the
codebase, so it's best to move it out of a core file.
2018-04-25 21:59:48 -07:00
Tim Abbott
2217285ac0 test_push_notifications: Add a better test for auth code path.
This is mostly to prevent an issue similar to the one fixed in the
last commit.
2018-04-25 21:51:24 -07:00
Tim Abbott
2fa58fe9ad decorator: Fix exception format for invalid API key.
This exception class was clearly missing the part where `role` gets
stored, which was intended to be inherited from
InvalidZulipServerError.

This fixes an unnecessary 500 error in the push notifications bouncer.
2018-04-25 21:44:31 -07:00
Shubham Dhama
b411bc050e org settings: Change join org permissions to dropdown. 2018-04-25 16:00:55 -07:00
Tim Abbott
b40780d003 mypy: Fix errors in new bugdown module.
I introduced these when making final changes before merging.
2018-04-25 15:56:46 -07:00
Lyla Fischer
0f3cb14aae user docs: Use {settings_tab} macro for more organization settings. 2018-04-25 14:39:30 -07:00
Lyla Fischer
dbc573584b user docs: Use the {settings_tab} macro for four organization settings. 2018-04-25 14:39:30 -07:00
Lyla Fischer
b2be1a67f8 help docs: Add {settings_tab} for the first three org settings. 2018-04-25 14:39:30 -07:00
Lyla Fischer
68f68bf56d help docs: Use {settings_tab} macro for some user settings. 2018-04-25 14:39:30 -07:00
Lyla Fischer
a4ea71ec0f help docs: Use {settings_tab|notifications} macro. 2018-04-25 14:39:30 -07:00
Greg Price
a70816c76e docs: Stop linking to specific versions of our docs entirely.
We started doing this for install docs in de2a2d0df, because `latest`
wasn't suitable and because I didn't know about readthedocs's `stable`
feature.  The result has been that even with a checklist item, we
don't reliably update the link.

Instead, use the special `stable` version identifier on readthedocs to
link automatically to the highest version it knows about.
2018-04-25 14:36:56 -07:00
Lyla Fischer
d40f246599 help docs: Use {settings_tab|display-settings}. 2018-04-25 13:51:29 -07:00
Tim Abbott
ec878d01ba docs: Document how to write email templates correctly. 2018-04-25 13:50:14 -07:00
Lyla Fischer
b24659b005 bugdown: Add {settings|my-setting} macro.
Tweaked by tabbott to add a test and fix a super subtle issue with the
relative_settings_link variable having been set once the first time a
/help article was rendered.
2018-04-25 13:41:24 -07:00
Tim Abbott
6e5d6da7f9 README: Fix link to now- obsolete 1.7.1 release.
We should really be linking to the latest stable release.
2018-04-25 12:00:07 -07:00
Tim Abbott
dcadded1a4 emoji settings: Remove emoji style indentation.
We got rid of that indentation everywhere else in these settings.
2018-04-25 11:44:16 -07:00
Tim Abbott
ccb1a00e0a casper: Fix test sequencing for profile tests.
We check data only updated after the get_events call returned, without
actually waiting for that to happen.
2018-04-25 10:21:27 -07:00
Tim Abbott
ff9371d63c slack import: Fix issues with Slack empty files.
Fixes #9217.
2018-04-25 10:20:55 -07:00
neiljp (Neil Pilgrim)
f5ec2639b7 requirements: Update mypy to 0.590. 2018-04-25 08:58:55 -07:00
neiljp (Neil Pilgrim)
9692a8572d mypy: Add assertion in timeout.py. 2018-04-25 08:58:55 -07:00
Rishi Gupta
718a87bd47 emails: Update followup_day1. 2018-04-25 08:52:29 -07:00
Rishi Gupta
62d5166b7b docs: Add tools/inline-email-css to email subsystem doc. 2018-04-25 08:52:29 -07:00
Rishi Gupta
d57e10158c portico: Update text on confirm_continue_registration.
A common path is a new user goes to realm_uri, which redirects to
realm_uri/login, and clicks the google auth button thinking it is a
registration button.

This commit just changes the wording on the page they land on to be
friendlier for that use case.
2018-04-25 08:50:24 -07:00
Priyank Patel
bc454bab88 webpack: Disable host check for webpack-dev-server.
Webpack dev server by default does host checking for requests. so
in dev enviorment if the the request came for zulipdev.com it would not
send js files which caused dev envoirment to not work.
2018-04-24 14:14:20 -07:00
Tim Abbott
62fb139af7 Revert "test_fixtures: Add settings files to things that require reprovision."
This reverts commit 2bc51931a8.

See #9210 for the follow-up work needed before we can re-add this.
2018-04-24 11:01:20 -07:00
Emilio Schadt
b6f5ea0fd2 docs: Add README.md to zulip/docs to prevent reading on GitHub.
This should help avoid confusing about broken links.

Tweaked by tabbott to fix formatting and the linter.
2018-04-24 10:50:39 -07:00
Tim Abbott
e4ca6e947b ldap: Disable django-auth-ldap caching of users.
This shouldn't have a material performance impact, since we don't
query these except during login, and meanwhile this fixes an issue
where users needed to restart memcached (which usually manifested as
"needing to reboot the whole server" after updating their LDAP
configuration before a user who was migrated from one OU to another
could login).

Fixes #9057.
2018-04-24 09:57:55 -07:00
Tim Abbott
697fd3c69b templates: Fix duplicate inclusion of portico CSS.
Apparently, essentially every one of our landing pages extending
portico.html had two copies of portico.css included in their head
section; one from porticocustomhead (or the super of customhead) and
the other directly included.

Clean this up by removing all these duplicate inclusions of the
portico stylesheet.
2018-04-24 08:45:31 -07:00
Sampriti Panda
e86d5139bb copy_and_paste.js: Remove excess newlines while pasting markdown html.
Fixes #8963
2018-04-24 08:27:43 -07:00
Tim Abbott
79e8bff8fa views: Change use of typing.Text to str.
This is the first part of a general migration of our typing codebase
to use the simpler `str` for strings.
2018-04-23 18:51:23 -07:00
Tim Abbott
2f937d81e2 puppet: Add Zulip specific postgres configuration for 9.6.
Mostly, this involves adding the big block at the bottom and making
9.6 a variable so that it's easier to compare different versions of
these.
2018-04-23 18:33:49 -07:00
Tim Abbott
9930e3de09 puppet: Add a stock Postgres 9.6 configuration file from Debian.
This will make it easier to see what we customize.
2018-04-23 18:29:02 -07:00
Shubham Dhama
f19b0b3254 org settings: Change message_edit settings to dropdown.
This coverts the "checkbox" for `realm_allow_message_editing`  and
"input" for `realm_message_content_edit_limit_seconds` into a
dropdown with the option for custom time limit option.
2018-04-23 18:22:35 -07:00
Aditya Bansal
8e38b8462b csp_nonce: Add nonce to webpack rendered js.
We use the attrs property provided by render_bundle function of
django-webpack-loader to add `nonce="<csp_nonce_val_here>" to
js scripts being rendered by webpack.
2018-04-24 06:13:21 +05:30
Aditya Bansal
ae398dc48b csp_nonce: Add nonce to script tags loading minified JS. 2018-04-24 06:13:21 +05:30
Aditya Bansal
e9f2efedb5 minified_js.py: Remove dead code used for handling js minification.
This piece of code was used when we used Django template engine. Since
we moved to Jinja2 template engine, we wrote a newer version of this
function (minified_js) in 'zproject/jinja2/compressors.py' which is
now used in our templates. This newer function essentially retired the
old function defination and thus the old code became dead. We probably
missed out this clean up at the time we migrated to Jinja2 template
engine.
2018-04-24 06:13:21 +05:30
Aditya Bansal
8c0a5c69f3 csp: Dynamically generate and add nonce to script tags. 2018-04-24 06:13:21 +05:30
Tim Abbott
3e49850d6b popovers: Fix broken node test.
This seems to have been introduced when rebasing
7533796ea9.
2018-04-23 17:29:15 -07:00
Preston Hansen
efc7967355 slack import: Update build_zerver_realm to use Realm defaults.
Fixes #9131.
2018-04-23 17:15:51 -07:00
Vishnu Ks
134fdd8fd0 bugdown: Replace vimeo link with video title. 2018-04-23 17:04:24 -07:00
Vishnu Ks
5671cef6d0 bugdown: Cleanup Vimeo preview. 2018-04-23 17:04:24 -07:00
Vishnu Ks
7533796ea9 popover: Add an option to show user profile.
Fixes #8880
2018-04-23 16:56:24 -07:00
Tim Abbott
1703e23980 templates: Move all core app templates into a subdirectory.
This should make it easier to find the templates that are actually
part of the core webapp, instead of having them all mixed together
with the portico pages.
2018-04-23 16:46:37 -07:00
Tim Abbott
8fc04a074d test-backend: Update coverage excludes for new import_realm.py. 2018-04-23 16:26:01 -07:00
Rohitt Vashishtha
c43f4e509c zblueslip: Improve usage documentation in test file. 2018-04-23 16:18:35 -07:00
Rohitt Vashishtha
bddb6a1a14 zblueslip: Log output for all function calls.
Also adds asserts on blueslip.log() call in node_tests/people_errors.js.
2018-04-23 16:18:35 -07:00
Tim Abbott
47d53107a1 version: Bump PROVISION_VERSION for webpack upgrade. 2018-04-23 15:51:56 -07:00
Armaan Ahluwalia
97ec5e0aaa webpack: Tweak config file for stats and faster recomp on HMR.
Update the config file to show slightly more information while
compiling webpack. Also decreased the time webpack waits before
recompiling in order to speed up HMR.
2018-04-23 15:49:33 -07:00
Armaan Ahluwalia
c70d26224d webpack: Change devtool sourcemap strategy.
Changed the devtoool setting for development from 'eval' to
'cheap-module-eval-source-map' as it has better support for
breakpoints in Google Chrome and the difference is time is
negligible at the number and size of files currently being
consumed by webpack. This stragtegy can be reviewed in the
future as the size of files grows or Chrome adds better
support.
2018-04-23 15:49:33 -07:00
Armaan Ahluwalia
fc7aa1a771 webpack: Upgrade webpack version 4.5.0.
Upgrade webpack to latest version at the time of authoring. This
involves upgrading webpack version and its loaders to compatible
versions. It also involved editing tools/webpack to use the
executable for webpack-cli instead because of a change in how the
webpack package wants you to handle shell execution.
It also fixes the confugration for TypeScript in the webpack config
as that was previously broken. Including TypeScript files in JS
files compiled by webpack now works.
2018-04-23 15:49:33 -07:00
Tim Abbott
c4b886d8ae import: Split out import.py into its own module.
This should make it a bit easier to find the code.
2018-04-23 15:21:12 -07:00
Eeshan Garg
1e217ed4e4 pypi: Upgrade to release 0.4.5. 2018-04-23 15:20:02 -07:00
Eeshan Garg
8158342ad3 decorators: Log webhook error payloads in authenticated_rest_api_view.
This completes the effort to ensure that all of our webhooks that do
parsing of the third-party message format log something that we can
use to debug cases where we're not parsing the payloads correctly.
2018-04-23 14:14:42 -07:00
Rishi Gupta
cf735042b7 docs: Make minor fixes to mobile-push-notifications.html. 2018-04-23 12:37:29 -07:00
Priyank Patel
7046409e12 Revert "quick fix: Revert ES6 idiom that istanbul warns about."
This reverts commit 55ff9a6806, the issue
about istanbul coverage not working for spread operator is fixed by using
nyc instead.
2018-04-23 15:27:16 -04:00
Priyank Patel
29f04511c0 node_tests: Use nyc instead of deceprated istanbul package. 2018-04-23 15:27:16 -04:00
Tim Abbott
322fc52cd5 people: Suppress reporting late user additions when reloading.
If the browser is in the progress of reloading when it finishes
fetching some messages, it's not really a bug, and we shouldn't report
it as such.

This should help make Zulip's browser error reporting less spammy.
2018-04-23 12:20:27 -07:00
Eeshan Garg
ad1b043098 webhooks/updown: Update docs to conform to style guide. 2018-04-23 12:03:34 -07:00
Eeshan Garg
c4bfb5022c webhooks/sentry: Update docs to conform to new style guide. 2018-04-23 12:03:34 -07:00
Preston Hansen
0258d7db0d slack import: Be less strict in check_subdomain_available.
If the sysadmin is doing something explicit in a management command,
it's OK to take a reserved or short subdomain.

Fixes #9166.
2018-04-23 11:48:12 -07:00
Tim Abbott
048f15e975 unread: Fix messages that cannot be marked as read in narrows.
If you visit a narrow that has unread messages on it that aren't part
of the home view (e.g. in a muted stream), then we were never calling
`message_util.do_unread_count_updates`, and more importantly,
`unread.process_loaded_messages` on those messages.  As a result, they
would be unread, and moving the cursor over them would never mark
those messages as read (which was visible through the little green
marker never disappearing).

I can't tell whether this fixes #8042 and/or #8236; neither of them
exactly fits the description of this issue unless the PM threads in
question were muted or something, but this does feel related.
2018-04-23 11:46:46 -07:00
Eeshan Garg
3df759337d integrations/capistrano: Update docs to conform to style guide.
Thanks to Wes of TurboVote for submitting this integration!
https://gist.github.com/cap10morgan/5100822
2018-04-23 11:43:15 -07:00
Tim Abbott
718492638b puppet: Fix name for dhcpcd5 package.
Apparently the name dhcpcd isn't installable.
2018-04-23 11:32:07 -07:00
Tim Abbott
3f4f94d111 check-capitalization: Add "G Suite" to proper nouns list.
This fixes an error caught by CI.
2018-04-23 10:21:10 -07:00
Vishnu Ks
c9e932a7ce settings: Add support for Hangouts as the video chat provider.
The only thing that's annoying about this feature is that you need to
be a paying G Suite customer to use it.
2018-04-23 09:39:47 -07:00
Tim Abbott
01be6b01b1 validate_domain: Add verification of domain length. 2018-04-23 09:29:03 -07:00
Vishnu Ks
d1c143de42 models: Add attribute for video provider in realm. 2018-04-23 09:15:12 -07:00
Tim Abbott
de691e8564 casper: Fix race condition in stars test.
I have no idea why this started failing just now, but the test was
written without a proper wait in between actions, and fixing that
fixes the failure I'd been seeing.
2018-04-23 09:15:12 -07:00
Tim Abbott
56b0479656 puppet: Clean up indentation in various manifests.
These are inspired by puppet-lint, though we didn't take all of their
changes, since some seem to be bugs in the tool.
2018-04-23 00:15:54 -07:00
Tim Abbott
b493748ddb puppet: Use single quotes where valid.
This brings our puppet codebase more in line with the standard puppet
style guide.  Changes done via `puppet-lint --fix`.
2018-04-23 00:15:54 -07:00
Tim Abbott
871078db30 puppet: Fix alignment of arrow operators.
This was done using puppet-lint --fix.
2018-04-23 00:15:54 -07:00
Eeshan Garg
980218aea2 webhooks/papertrail: Update docs to conform to style guide. 2018-04-23 00:07:19 -07:00
Eeshan Garg
58e70ec858 webhooks/transifex: Update docs to conform to style guide. 2018-04-23 00:07:19 -07:00
Eeshan Garg
81f0f2ebd3 webhooks/semaphore: Update docs to conform to style guide. 2018-04-23 00:07:19 -07:00
Eeshan Garg
ed719c7d5a webhooks/wordpress: Update docs to conform to style guide. 2018-04-23 00:07:19 -07:00
Tim Abbott
19cee30bf8 puppet: Fix use of under-scoped variables. 2018-04-22 23:53:34 -07:00
Tim Abbott
6e55aa2ce6 puppet: Fix mispelled variable name.
Apparently, we weren't uninstalling the old WSGI module properly.
2018-04-22 23:53:34 -07:00
Tim Abbott
6988f13201 puppet: Move safepackage definitions out of class definitions.
Also, deduplicate it while we're at it.

This fixes a puppet-lint issue that becomes an error with puppet 4.
2018-04-22 23:53:22 -07:00
Tim Abbott
a6aa7042a2 puppet: Fix some unnecessarily quoted strings.
Flagged by puppet-lint.
2018-04-22 23:42:35 -07:00
Tim Abbott
35aa4f0377 puppet: Sort ensure attributes to be always first.
This inconsistency was flagged by puppet-lint.
2018-04-22 23:41:49 -07:00
Tim Abbott
a56968ce68 puppet: Fix variables not clearly enclosed.
This improves readability and robustness.  Found and fixed via puppet-lint.
2018-04-22 23:35:33 -07:00
Tim Abbott
169ee5d8a1 puppet: Fix use of tab-based whitespace. 2018-04-22 23:34:30 -07:00
Tim Abbott
e103c2ff2d puppet: Switch to modern quoted, octal file modes.
This is one of the prerequisite tasks for Puppet 4 support.

Constructed using puppet-lint.
2018-04-22 23:30:48 -07:00
Tim Abbott
26ac1d237b narrow: Remove now-unnecessary use_initial_narrow_pointer option.
We replace it with the much more intuitive then_select_offset option.
2018-04-22 21:33:34 -07:00
Tim Abbott
ccd546cc75 narrow: Migrate code for use_initial_narrow_pointer. 2018-04-22 21:33:33 -07:00
Tim Abbott
a0c6930ca9 narrow: Remove confusing code to unset use_initial_narrow_pointer.
Nothing reads that variable after this point, so we don't need to mutate it.
2018-04-22 21:33:33 -07:00
Tim Abbott
35585f75d9 narrow: Move select_first_unread to be a local variable.
This makes the API more obvious that this is not a parameter to be
passed into narrow.activate.
2018-04-22 21:33:33 -07:00
Tim Abbott
e4c50ff4fd narrow: Remove unnecessary select_first_unread option.
We consistently either pass a `then_select_id` into narrow.activate,
or were using the select_first_unread option.  Now, we just compute
select_first_unread based on the value of then_select_id.
2018-04-22 21:33:33 -07:00
Tim Abbott
0c9b1dc9ff search: Use select_first_unread when narrowing.
Apparently, our search code was the last holdout that was still using
the pointer to determine where to place a narrow.
2018-04-22 21:33:33 -07:00
Tim Abbott
6bab4e0aad narrow: Fix narrowing behavior when loading a new tab.
In the very early days of Zulip, we didn't have unread counts; just
the pointer, and the correct behavior when opening a new tab was to
place you near the pointer.  That doesn't make any sense now that we
do have unread counts, and this corner case has been a wart for a long
time.

This commit does the main behavior change here.  However, there's a
bug we need to fix, where we might end up trying to pre-render a view
of the narrow based on the `all_msg_list` data before `all_msg_list`
is caught up).  We need to fix that bug before we can merge this; it
should be possible to determine that using `FetchStatus` on
`all_msg_list`, or with better performance by using the `unread_msgs`
structure to determine whether the message we should be selecting is
present locally.

Fixes #789.
Fixes #9070.
2018-04-22 21:33:17 -07:00
Tim Abbott
1d5204c82b narrow: Fix incorrect values for use_first_unread_anchor.
Apparently, we were incorrectly passing through something related to
opts.use_initial_narrow_pointer as the value for `use_first_anchor`.

If you read the logic in narrow.js carefully,
use_initial_narrow_pointer was unconditionally false.

The correct value for this attribute is when we're trying to narrow to
the first unread message in a given context.  There are two things to
check:

* then_select_id is -1; i.e. we don't have a specific message ID we're
  trying to narrow around.
* select_first_unread is True, i.e. we're trying to narrow to the
  first unread message.

A bit more work should allow us to get rid of the second condition,
but I'm not quite confident enough to do that yet.
2018-04-22 21:33:14 -07:00
Steve Howell
6d4855bd6a lint: Prevent accidental use of const.
This prevents us from using const in our JS code, with exceptions
for test code and the portico.  Hopefully this is just a temporary
rule until we make our pipelines with work with ES6.

I tried to prevent "let", but that was too noisy.

This adjusts the one false-negative case of using const in a comment.
2018-04-22 20:11:38 -07:00
Steve Howell
4b3d07c805 node tests: Localize focus logic for activity.js.
This does a few things:
        * removes some unnecessary setup
        * puts some jQuery setup closer to where it's needed
        * renames some variables
        * adds an assertion about highlighting
2018-04-22 20:08:08 -07:00
Steve Howell
76d83af62b buddy list: Extract activity.narrow_for_user.
This change makes a common code path for these two operations:
        * clicking on a user
        * hitting enter when a user is highlighted

The newer codepath, for the enter key, had some differences that
were just confusing.  For example, there's no need to open the
compose box, since that's already handled by the narrowing code.

For possibly dubious reasons, I let each handler still call
popovers.hide_all() on its own, since it makes the code a bit
more consistent with existing code patterns.
2018-04-22 20:08:08 -07:00
Steve Howell
54389f7b41 buddy list: Shrink overly large lists.
If we would have more than 600 people in a buddy list, it's kind of
cumbersome to scroll through it, and it's also expensive to render
it (short of doing progressive rendering, which adds a lot of
complexity).

So, as a short term measure, we filter out offline users whenever the
list would exceed 600 users.  Note that if you are doing a search that
narrows to fewer 600 users, the offline users will appear again.
2018-04-22 20:08:08 -07:00
Steve Howell
3f1930f9c5 buddy list: Extract buddy_data.js. 2018-04-22 20:08:08 -07:00
Steve Howell
536236d9b1 buddy list: Extract buddy_list.js. 2018-04-22 20:08:08 -07:00
Tim Abbott
a9fb02b712 test_auth_backends: Add a test for GitHub auth mobile_flow_otp. 2018-04-22 19:55:05 -07:00
Tim Abbott
c88163eea8 auth: Make "Continue to registration" actually register you.
The main change here is to send a proper confirmation link to the
frontend in the `confirm_continue_registration` code path even if the
user didn't request signup, so that we don't need to re-authenticate
the user's control over their email address in that flow.

This also lets us delete some now-unnecessary code: The
`invalid_email` case is now handled by HomepageForm.is_valid(), which
has nice error handling, so we no longer need logic in the context
computation or template for `confirm_continue_registration` for the
corner case where the user somehow has an invalid email address
authenticated.

We split one GitHub auth backend test to now cover both corner cases
(invalid email for realm, and valid email for realm), and rewrite the
Google auth test for this code path as well.

Fixes #5895.
2018-04-22 19:55:05 -07:00
Tim Abbott
c65a4e8f0b test_signup: Remove unnecessary LoginOrAskForRegistrationTestCase.
This test class is basically a poor version of the end-to-end tests
that we have in `test_auth_backends.py`, and didn't really add any
value other than making it difficult to refactor.
2018-04-22 19:41:18 -07:00
Tim Abbott
2dcec3704c auth: Introduce confirmation_link local variable.
This is just a prepartory refactor in maybe_send_to_registration.
2018-04-22 16:24:43 -07:00
Tim Abbott
8026b4f9db auth: Refactor login_or_register_remote_user interface.
By moving all of the logic related to the is_signup flag into
maybe_send_to_registration, we make the login_or_register_remote_user
function quite clean and readable.

The next step is to make maybe_send_to_registration less of a
disaster.
2018-04-22 16:24:43 -07:00
Tim Abbott
64023fc563 auth: Fix incorrect use of get_realm_from_request.
The code in maybe_send_to_registration incorrectly used the
`get_realm_from_request` function to fetch the subdomain.  This usage
was incorrect in a way that should have been irrelevant, because that
function only differs if there's a logged-in user, and in this code
path, a user is never logged in (it's the code path for logged-out
users trying to sign up).

This this bug could confuse unit tests that might run with a logged-in
client session.  This made it possible for several of our GitHub auth
tests to have a totally invalid subdomain value (the root domain).

Fixing that bug in the tests, in turn, let us delete a code path in
the GitHub auth backend logic in `backends.py` that is impossible in
production, and had just been left around for these broken tests.
2018-04-22 16:24:43 -07:00
Tim Abbott
b36298efda mypy: Fix a missing type annotation in auth code. 2018-04-22 16:24:43 -07:00
Tim Abbott
00c9f45821 auth: Remove dead invalid_subdomain code path.
This code path has actually been dead for a while (since
`invalid_subdomain` gets set to True only when `user_profile` is
`None`).  We might want to re-introduce it later, but for now, we
eliminate it and the artificial test that provided it with test
coverage.
2018-04-22 14:44:06 -07:00
Tim Abbott
65025e8327 auth: Add return_data for RemoteUserBackend.
This is done mainly because this backend has the simplest code path
for calling login_or_register_remote_user, more than because we expect
this case to come up.  It'll make it easier to write unit tests for
the `invalid_subdomain` corner case.
2018-04-22 14:44:06 -07:00
Tim Abbott
6df821a40f tests: Replace messy direct test of login_or_register_remote_user.
This code path is much more naturally tested with the existing
end-to-end test for the function that we have for the RemoteUser auth
backend.
2018-04-22 14:44:06 -07:00
Tim Abbott
f806526551 auth: Remove unused request.verified_email variable.
This has never been used since it was introduced in the very first
version of our Google auth code in April 2013.
2018-04-22 14:44:06 -07:00
Tim Abbott
fb6cc4cb65 secrets: Make it a bit easier to do GitHub auth in development. 2018-04-22 14:44:05 -07:00
Tim Abbott
b91de0e283 i18n: Pass the name of the single-sign on system into strings.
This should avoid us needing to add a new set of translated strings
every time we add a new authentication method.
2018-04-22 13:34:37 -07:00
Utkarsh Patil
955d03b8a0 emoji: Prefix sort for emojis.
Emoji prefix sort for "popular emojis first". Fixes #7625.
2018-04-21 22:28:45 -07:00
Utkarsh Patil
59f5af6b62 emoji: Export popular emojis list.
Provisions to export popular emojis list.
2018-04-21 22:24:07 -07:00
Cynthia Lin
ff88712db8 portico: Fix broken mobile responsive design on Plans page. 2018-04-21 22:15:56 -07:00
Cynthia Lin
323e2faac8 css: Change *-device-width to *-width selectors.
See https://stackoverflow.com/questions/18500836/should-i-use-max-device-width-or-max-width
for an explanation on why this isn't optimal.
2018-04-21 22:15:56 -07:00
Eeshan Garg
ca5ea20ab7 doc tests: Add portico pages to tools/test-help-documentation.
Fixes #9117.
2018-04-21 21:51:32 -07:00
Tim Abbott
f90b765824 docs: Document what's involved in doing your own push notifications.
We've had a few folks ask about this recently, and it seemed like it'd
be good to cover this a bit more explicitly.
2018-04-21 21:42:24 -07:00
Priyank Patel
55ff9a6806 quick fix: Revert ES6 idiom that istanbul warns about. 2018-04-20 16:33:33 -07:00
Shubham Dhama
26d2ffa821 populate_db: Add new user of "guest" type.
The purpose of this user is to act as a guest.
(This is a preliminary step in adding the guest type of user
and is a part of #8385.)
2018-04-20 16:20:00 -07:00
Shubham Dhama
1191f1730a guest: Add a model field for guest users.
This adds new field `is_guest` to UserProfile model and
is meant for the new type of user i.e. "Guest Users".

(Part of #8385).
2018-04-20 16:08:29 -07:00
Steve Howell
5f53fb1561 refactor: Remove factory code for toggle component.
We now have components.toggle simply return an object, without
putting the object into a lookup table.  The consumers of the
objects have all been changed to just store the object in their
own module scope.

The diff is a bit hard to read here, but it's mostly de-denting
code and removing these things:

        - we don't have opts.name
        - we don't have __toggle.lookup
        - we don't have keys
        - we don't create a sibling object to the prototype object
2018-04-20 13:45:58 -07:00
Steve Howell
f73bfd2a5c refactor: Extract settings_toggle.js.
This mostly moves code, and we also abandon the lookup mechanism
for finding our toggler.
2018-04-20 13:45:58 -07:00
Priyank
02dddd6b35 node_test: Use let/const instead of var in activity.js.
This was left in previous cleanup.
2018-04-20 15:46:28 -04:00
Priyank
2dd2ab4f7e node_tests: Update alert_words.js to use es6. 2018-04-20 15:46:28 -04:00
Priyank
3174827f93 node_tests: Update channel.js to use es6. 2018-04-20 15:46:28 -04:00
Priyank
b1c5f6c07d node_tests: Update bot_data.js to use es6.
bot_data.js uses let/const for variables, and uses rest operator
instead of _.extends.
2018-04-20 15:46:28 -04:00
Priyank
6c8753ef25 eslint: Update eslint config to have es6 env.
This will allow us to use spread operator.
2018-04-20 15:46:28 -04:00
Rishi Gupta
6c8a266119 portico: Update meta tags. 2018-04-20 12:13:50 -07:00
Tim Abbott
49f58583a4 models: Introduce can_access_public_streams field.
This is a simple computed field.  It's intended to more clearly
capture the meaning of this restriction for the users in zephyr mirror
realms, and eventually support guest user accounts in normal Zulip
realms.
2018-04-20 12:06:57 -07:00
Tim Abbott
dc6d7d0d12 actions: Use presence_disabled for presence zephyr_mirror code path.
This is part of the effort to remove the use of is_zephyr_mirror_realm
across the code path for situations that might be relevant for other
users.  It helps keep the code readable.
2018-04-20 12:06:57 -07:00
Tim Abbott
09f995e966 left sidebar: Fix clipping of private message users with "g" in name.
This fixes an issue where users whose names had a "g" in them would
have the "g" clipped in the "private messages" section in the left sidebar.

We avoid a change in the effective visible line-height by shrinking
the margin.
2018-04-20 11:28:18 -07:00
Tim Abbott
7ef23a0139 slack import: Document how to send password resets to all users.
This is likely to be an important follow-up step after one finishes
the Slack import.
2018-04-20 10:56:06 -07:00
Tim Abbott
7e91e66987 import: Fix ordering of subdomain availability check.
When you're importing with --destroy-rebuild-database, we need to
check subdomain availability after we've cleared out the database;
otherwise, trying to reuse the same subdomain doesn't work.
2018-04-20 10:39:30 -07:00
Cynthia Lin
0886424ed3 analytics: Eliminate slider-focused text selection in Firefox.
Fixes #9151.
2018-04-20 09:19:43 -07:00
Tim Abbott
360d708340 gitlab: Document the local network security setting.
This should help users debug issues with the GitLab webhook not
working with recent GitLab releases.
2018-04-20 08:34:38 -07:00
Tim Abbott
e25d6968a5 docs: Expand documentation on our other test suites.
This is long overdue, and I was actually kinda surprised how many of
these we have at this point :).

Fixes #7461.
2018-04-19 23:49:28 -07:00
Cynthia Lin
d963fd8180 night-mode: Change coloring for compose close button. 2018-04-19 23:04:15 -07:00
Tim Abbott
d1cc442404 compose: Move colorblock CSS to compose.css. 2018-04-19 22:51:25 -07:00
Cynthia Lin
f1db3a681a compose: Refactor compose box from <table> to <div> structure.
`<td>` elements are fixed-width, so we refactor the entire
`<table>` structure for responsive design.

This fixes a bug with how the `To:` block looks in other languages.

Fixes #9152.
2018-04-19 22:51:13 -07:00
Akash Nimare
1a124ad865 portico: Add open graph and twitter meta tags to all pages.
This PR adds few basic meta tags which are required for a better SEO.

Fixes: #8255.
2018-04-19 22:10:27 -07:00
Roman Godov
d99758129e subs: Notify organization admins when private streams are renamed.
This commit sends the event for renaming of a private stream to
organization admins of the realm, in addition to the obvious list of
subscribers of the private stream.

Normally, admins can manage a private stream (e.g. unsubscribing a
user).  But when the admin tried to unsubscribes a user from a
previously renamed stream, we previously were throwing a JS error, as
the webapp hadn't been notified about the new stream name.

Fixes #9034.
2018-04-19 22:00:18 -07:00
Preston Hansen
e168f9938c tests: Refactor use of test and webhook data fixtures. 2018-04-19 21:50:29 -07:00
Preston Hansen
76d6c71595 tests: Move zerver/fixtures to zerver/tests/fixtures for clarity.
Fixes #9153.
2018-04-19 21:50:17 -07:00
Shubham Dhama
d0801dd602 settings: Make saving spinner visible in night mode.
Fixes: #9154.
2018-04-19 21:47:04 -07:00
Shubham Dhama
9aa9ed9472 org settings: Refactor code for get_property_value.
This just makes the code for getting the property value more readable
and less confusing.
2018-04-19 21:47:04 -07:00
Tim Abbott
a06c7bc247 puppet: Allow manual configuration of postfix_mailname.
This allows users to configure a mailname for postfix in
/etc/zulip/zulip.conf
2018-04-19 14:41:05 -07:00
Tim Abbott
62b12e0c34 zulip_ops: Add missing dependency on dhcpcd. 2018-04-19 14:27:48 -07:00
Tim Abbott
e53c0fe273 docs: Further clarify privacy details for push notifications. 2018-04-19 14:20:57 -07:00
Rishi Gupta
3f94a62309 portico: Add contact info to FAQ. 2018-04-19 12:48:27 -07:00
Natalie Totonchy
bc7c3df621 drafts: Fix confusing disconnect between focused element and enter hotkey.
The enter hotkey targets the focused message, not the one the user is
hovering over.

Fixes #9093.
2018-04-19 12:44:03 -07:00
Rohitt Vashishtha
ba21afe9a6 zblueslip: Convert node_tests/people_errors.js to zblueslip. 2018-04-19 15:02:00 -04:00
Rohitt Vashishtha
a836473746 zblueslip: Convert node_tests/compose.js to zblueslip. 2018-04-19 15:02:00 -04:00
Rohitt Vashishtha
25c75b66d3 zblueslip: Convert node_tests/emoji.js to zblueslip. 2018-04-19 15:02:00 -04:00
Rohitt Vashishtha
4738644339 zblueslip: Create zblueslip.js for improved error handling.
Adds a basic API for controlling the allowed error messages and
documents the usage in the form of unit tests, similar to zjquery.

Fixes #8675.
2018-04-19 15:02:00 -04:00
Tarun Kumar
55f830c345 server_events_dispatch: Add node coverage for user groups. 2018-04-19 14:59:08 -04:00
Tarun Kumar
deab3ac541 hash_util: Add 100% node-coverage for hash_util.js. 2018-04-19 14:59:08 -04:00
Shubham Dhama
9c096840be node tests: Increase coverage for settings_org. 2018-04-19 14:58:12 -04:00
Shubham Dhama
b41a204dbb org settings: Remove redundant property type from org_permissions object.
`add_emoji_by_admins_only` is already covered in
`get_complete_data_for_subsection` function.
2018-04-19 14:58:12 -04:00
Shubham Dhama
48fe77c61e node tests: Remove repeated zjquery code in settings_org tests. 2018-04-19 14:58:12 -04:00
Joshua Pan
99f07fe2e2 tests: Fully cover top_left_corner.js. 2018-04-19 14:56:55 -04:00
Joshua Pan
09469026c6 zjquery: Allow removeClass() to remove multiple classes simultaneously.
removeClass() now splits class_names by spaces to get multiple
class names. Then removes each individual class name.
2018-04-19 14:56:55 -04:00
Tim Abbott
e539e966c9 profile field settings: Disable autocomplete. 2018-04-19 11:37:15 -07:00
Umair Khan
79ff89ed8b profile: Add hint field in the settings UI.
We still don't actually display it in the "Edit your profile" UI.
2018-04-19 11:32:45 -07:00
Umair Khan
0420b89468 profile: Add hint attribute to custom profile fields.
This is the model for #8876.
2018-04-19 11:32:35 -07:00
Umair Khan
00ffa808da check_capped_string: Return a validator.
This allows us to create custom validators.
2018-04-19 11:21:50 -07:00
Tim Abbott
f34b72b830 docs: Fix command for accessibility label. 2018-04-19 11:13:09 -07:00
Greg Price
e8be968250 install: Expand error message for missing SSL cert slightly.
It wasn't obvious reading this message that you can perfectly well
bring your own SSL/TLS certificate; unless you read quite a bit
between the lines where we say "could not find", or followed the link
to the detailed docs, the message sounded like you had to either use
--certbot or --self-signed-cert.

So, explicitly mention the BYO option.  Because the "complete chain"
requirement is a bit tricky, don't try to give instructions for it
in this message; just refer the reader to the docs.

Also, drop the logic to identify which of the files is missing; it
certainly makes the code more complex, and I think even the error
message is actually clearer when it just gives the complete list of
required files -- it's much more likely that the reader doesn't know
what's required than that they do and have missed one, and even then
it's easy for them to look for themselves.
2018-04-19 11:08:22 -07:00
Shubham Dhama
6a11ff5b28 settings: Deduplicate the template code for checkboxes. 2018-04-19 11:02:30 -07:00
Shubham Dhama
a3ba484c99 settings: Wrap subsections into seperate divs.
Following templates are affected: display-settings.handlebars and
ui-settings.handlebars.
There will be no UI change, it is just to make code more readable.
2018-04-19 11:02:30 -07:00
Eeshan Garg
19177a4aff webhooks: Move github_webhook/ to github/. 2018-04-19 11:00:55 -07:00
Eeshan Garg
48b8558c02 webhooks: Move github/ to github_legacy/ and remove docs. 2018-04-19 11:00:55 -07:00
Tim Abbott
2cc3fb7564 slack import: Fix documentation on path to run manage.py. 2018-04-19 10:32:58 -07:00
Cynthia Lin
71e6b93277 night-mode: Add borders to input pill containers. 2018-04-19 10:03:37 -07:00
Cynthia Lin
20f14508ff compose: Fix styling of PM recipient input pill.
Fixes #9128.
2018-04-19 10:03:37 -07:00
Tim Abbott
ea23297d79 left sidebar: Fix line-height causing clipping.
Fixes #8209.
2018-04-19 09:57:32 -07:00
Rishi Gupta
cf40aa4763 portico: Add export advertisement to FAQ. 2018-04-19 09:40:11 -07:00
Akash Nimare
9d9d84ffd2 portico: Minor UI fix in plans page.
Renamed zulip-cloud class to responsive-title so that
we can re-use this class in other sections also.

Fixes: #9133.
2018-04-19 03:05:57 +05:30
Tim Abbott
aa4b067e68 avatars: Fix 500 with the /avatar/ URL to be a 404.
Apparently, we had a somewhat sloppy regular expression for the URL
for this endpoint.
2018-04-18 12:43:57 -07:00
Tim Abbott
bad04c761f docs: Recommend upgrading when doing Slack import. 2018-04-18 12:28:52 -07:00
Tim Abbott
7dfa0edfa6 slack import: Don't try to import pinned/unpinned items.
There isn't a corresponding Zulip concept, and they don't have a
"text" attribute, so there's no message content to import.
2018-04-18 11:36:31 -07:00
Tim Abbott
acd3a364e1 slack import: Refactor handling of dropped messages.
This is a more coherent ordering, because some messages we skip lack a
"text" attribute.
2018-04-18 11:35:59 -07:00
Tim Abbott
e759fd9be4 slack import: Improve error handling for invalid messages. 2018-04-18 11:29:40 -07:00
Shubham Dhama
03a2a9c792 activity: Add realm stats link to "realm activtiy table". 2018-04-18 11:07:00 -07:00
Shubham Dhama
b26c38bc47 analytics: Make stats of all realms accessible to server admins.
In this commit:
Two new URLs are added, to make all realms accessible for server
admins. One is for the stats page itself and another for getting
chart data i.e. chart data API requests.
For the above two new URLs corresponding two view functions are
added.
2018-04-18 11:06:50 -07:00
Shubham Dhama
40dc48a033 decorator: Add decorator for checking whether user is server admin.
This is just variabnt of `require_server_admin` for JSON/api views.
2018-04-18 11:01:46 -07:00
Tim Abbott
3e117fba21 footer: Add a link to the blog.
This doesn't look amazing, but it's better than not linking to the
blog, and adding a 6th line both makes the footer excessive and also
breaks some styling in pages like /login that assume a fixed footer
height.
2018-04-18 10:44:18 -07:00
Tim Abbott
63ca175991 hello: Fix link to legacy GitHub integration. 2018-04-18 10:40:35 -07:00
Tim Abbott
1410a1e460 slack import: Remove unnecessary zerver_realm_skeleton.json.
This was stored as a fixture file under zerver/fixtures, which caused
problems, since we don't show that directory under production (as its
part of the test system).

The simplest emergency fix here would be to just move the file, but
when looking at it, it's clear that we don't need or want a fixture
file here; we want a Python object, so we just do that.

A valuable follow-up improvement to this block would be to create an
actual new Realm object (not saved to the database), and dump it the
same code we use in the export tool; that should handle the vast
majority of these correctly.

Fixes #9123.
2018-04-18 10:33:53 -07:00
Tim Abbott
77ca9e7eca hotspots: Tag strings for translation. 2018-04-18 09:33:09 -07:00
Greg Price
e78158bcc3 activity: Refactor code for getting chart data.
This simply refactored the repetitive code for getting stats chart
data.
(commit message tweaked by shubhamdhama)
2018-04-17 17:58:01 -07:00
Steve Howell
6c4f02218e node tests: Add some coverage to list_render.js. 2018-04-17 17:52:19 -07:00
Steve Howell
6b44cdb286 zjquery: Extract make_event_store().
This pushes some of the most complex code of zjquery into its own
object.
2018-04-17 17:52:19 -07:00
Steve Howell
10d1c2fafa zjquery: Extract make_new_elem().
There was really no reason for this to be a nested function, since
we weren't closing on any variables.  Flatter is better.  Also, it
is plausible that folks will want more control over creating
individual jQuery elements (but still want this helper).
2018-04-17 17:52:19 -07:00
Steve Howell
ae92ec2b57 node docs: Simplify and fix node docs.
This is a first pass at fixing node docs.  This commit eliminates
some text that is either obsolete or just overly confusing, and
it fixes some of the code samples to reflect how the API has
evolved in the last couple years.  We also prominently tell
you how to run the tests.
2018-04-17 17:52:19 -07:00
Steve Howell
f8fd169a7d node tests: Allow --coverage for individual files.
We now allow you to run --coverage on individual files.  This helps
when you want to make sure a file is being covered directly and not
just getting incidental coverage from higher level tests.

Before this commit, we were conflating wanting coverage reports with
wanting coverage checks.  For individual files, we now solve that by
simply eliminating the coverage checks.  This required some minor
refactoring to extract some functions.
2018-04-17 17:52:19 -07:00
Steve Howell
fca5cec2af node tests: Remove features to output HTML to files.
I don't think anybody ever really used this feature, which I
developed but don't even use myself.  It kind of runs counter
to the minimalist approach of the rest of node tests.

I would eventually like to re-think the template tests altogether.
They're slow, and we could solve that somewhat by replacing
jsdon/jquery with an HTML parser library to verify structural
things.

It's also possible that we can just rely on our template linters
to catch the biggest class of errors (malformed tags) and let
code review do the rest.

And it's also possible that we should make a second attempt to
ramp up tooling on making it easy to verify templates, but it
doesn't have to be part of the node tests.  If we did that, we
would also potentially use tooling for Python-side templates.
2018-04-17 17:52:19 -07:00
Steve Howell
74c939264b node tests: Add general.js.
This node test module is intended as a way for somebody to
quickly immerse themselves in our node testing methodologies,
plus it has the nice side effect of introducing several modules
(albeit very briefly).
2018-04-17 17:52:19 -07:00
Steve Howell
b3cd29a63e Move stream filter code into stream_list.initialize().
This is just moving code around.  The diff is a little big
due to appeasing the linter, which doesn't like out-of-order
function declarations.
2018-04-17 17:52:19 -07:00
Steve Howell
da60d9c757 node tests: Remove unneeded zrequire in typeahead_helper.js. 2018-04-17 17:52:19 -07:00
Steve Howell
617bfa9275 node tests: Add coverage for popovers.js. 2018-04-17 17:52:19 -07:00
Steve Howell
94e9b85042 node tests: Add narrow_activate.js. 2018-04-17 17:52:19 -07:00
Steve Howell
b1fd86c5c7 node tests: Add coverage to hashchange.js. 2018-04-17 17:52:19 -07:00
Steve Howell
1568df352d zjsunit: Extract read_fixture_data(). 2018-04-17 17:52:19 -07:00
Steve Howell
e70203ad55 node tests: Use zjquery in hotkey.js. 2018-04-17 17:52:19 -07:00
Steve Howell
c1a3c85a33 node tests: Clean up emoji tests.
A few things here:

    * Use _.each to follow our convention.
    * Just use new locals to avoid overwriting template and
      avoid strange Object.assign hack.
    * Just use simple string concatenation.
    * Use better var names: full_name, shortcut
    * Use chaining syntax.
2018-04-17 17:52:19 -07:00
Steve Howell
07591f03e2 node tests: Remove unneeded $ stub in compose_fade.js. 2018-04-17 17:52:19 -07:00
Steve Howell
b3101ca41b node tests: Remove extra $ declaration. 2018-04-17 17:52:19 -07:00
Steve Howell
3a5b0841e4 node tests: Remove minor cruft from bots_data.js. 2018-04-17 17:52:19 -07:00
Tim Abbott
4f1d3f302b docs: Fix a typo in the changelog. 2018-04-17 17:30:00 -07:00
Tim Abbott
a03fbea25b website: Fix a few out-of-date numbers. 2018-04-17 17:17:54 -07:00
Tim Abbott
1ec276b3a8 release: Update version strings following 1.8.0 release. 2018-04-17 17:12:20 -07:00
Tim Abbott
a6a5636a32 Release Zulip server 1.8.0. 2018-04-17 16:59:07 -07:00
Tim Abbott
9f844ff681 tornado: Fix logging of tornado activity level.
This logging was apparently broken when sorting imports; it's a fairly
unique thing in our codebase that this would be a problem.  Prevent
future regressions by adding this exception explicitly to the isort
configuration.
2018-04-17 15:59:01 -07:00
Tim Abbott
d340c8d46d docs: Fix broken link in /for/companies.
I guess these pages are not covered by tools/test-documentation.
2018-04-17 13:36:35 -07:00
Tim Abbott
60fe92ff13 docs: Make some small tweaks to the changelog. 2018-04-17 13:36:35 -07:00
Tim Abbott
7e187676c6 docs: Update changelog discussion of uploads auth and trusty. 2018-04-17 13:19:34 -07:00
Tim Abbott
c1af2d805c i18n: Update translations. 2018-04-17 12:41:06 -07:00
Aditya Bansal
4898fe7ebc uploads: Change Content-Security-Policy to fix issue with pdf's.
Our recent addition of Content-Security-Policy to the file uploads
backend broke in-browser previews of PDFs.

The content-types change in the last commit fixed loading PDFs for
most users; but the result was ugly, because e.g. Chrome would put the
PDF previewer into a frame (so there were 2 left scrollbars).

There were two changes needed to fix this:
* Loading the style to use the plugin.  We corrected this by adding
  `style-src 'self' 'unsafe-inline';`
* Loading the plugin.  Our CSP blocked loading the PDf viewer plugin.
  To correct this, we add object-src 'self', and then limit the
  plugin-type to just the one for application/pdf.

We verified this new CSP using https://csp-evaluator.withgoogle.com/
in addition to manual testing.
2018-04-17 12:23:24 -07:00
Tim Abbott
568a12e254 nginx: Add PDF files to the content-types list.
Previously, user-uploaded PDF files were not properly rendered by
browsers with the local uploads backend, because we weren't setting
the correct content-type.
2018-04-17 11:50:10 -07:00
Cynthia Lin
ad6fbbed62 night mode: Remove white borders in collapsible sidebars. 2018-04-17 11:18:09 -07:00
Cynthia Lin
45293a18c6 right-sidebar: Align group PM unread counts with user PM unread counts. 2018-04-17 11:06:33 -07:00
Cynthia Lin
b4c977fc6b sidebars: Fix regressions in input fields.
Standardizes their width and margins, while moving an unrelated
selector to a different file.
2018-04-17 11:06:33 -07:00
Vishnu Ks
cc93ac34a8 coverage: Add coverage to estimate_recent_messages.
With this message.py is fully covered and can be
removed from not_yet_fully_covered in test-backend.
2018-04-17 11:01:20 -07:00
Priyank
26d8d98319 frontend_tests: Add prefer-const rule.
This rule checks for use of const wherever needed, currently does
nothing since we don't use `let`, instead we use `var`. This rule
can be used to use refactor a file to use const easily by replaceing
var with let using a editor and then by running
`./node_modules/.bin/eslint frontend_tests --fix --cache`. And then revert
those `let`'s back to `var`.
2018-04-17 12:56:25 -04:00
Priyank
6faa6f96e9 node_test: Convert function to arrow function where needed. 2018-04-17 12:56:25 -04:00
Priyank
7490932e1b node_tests: Use const for constants. 2018-04-17 12:56:25 -04:00
Eeshan Garg
4fbdfef63b webhooks/stripe: Update docs to conform to new style guide. 2018-04-17 09:07:27 -07:00
Greg Price
dace7cacc8 docs changelog: Mention there are security fixes since 1.7.
Can't hurt to make this clear right in the 1.8 notes.
2018-04-16 18:37:55 -07:00
Greg Price
8630eb43b3 docs: Sort changelog entries for 1.8 into categories.
These aren't perfect -- in particular "core chat experience" can
probably be broken up -- but I think they help in making a quick skim
work for getting some sense of what the changes are.

This change just reorders and adds headings, with virtually no wording
changes.
2018-04-16 18:37:38 -07:00
Puneeth Chaganti
26dfa3266b ci: Make cache keys depend on checksums of dependency manifests
Caches in Circle are immutable -- even if a path in a cache changes in builds
after the cache was created, the cache is not updated if it already exists. This
was making the zulip-venv-cache and zulip-npm-cache directories useless on
Circle.

This commit changes the cache keys to depend on the checksums of the dependency
manifests (requirements/{dev,thumbor}.txt, package.json and yarn.lock). This
would ensure that the caches are updated when the environments change. It may
result in the occasional build being "uncached" -- when a dependency manifest
changes -- but builds without such changes will be much faster, and such builds
are a majority.
2018-04-16 16:49:41 -07:00
Eeshan Garg
da4ac38e37 css: Stop rendering code blocks as inline-blocks in webhook docs.
Previously, a code block with a small width would be displayed
inline with the previous paragraph's text.

To fix this, now every p inside an li element except the first is
a block instead of an inline-block. However, this only applies to
li elements for integration instructions.

This makes sense intuitively because if there are multiple p's
in a list element, not all of those should be inline-blocks. The
first one should be because it needs to be inline with the list
number. The rest should be treated (and displayed) as separate
paragraphs.

Another thing to keep in mind is that the way Markdown code
blocks get converted to HTML is such that every code block
becomes <p><code></code></p> when converted to HTML.
2018-04-16 16:42:07 -07:00
Eeshan Garg
dde9bb448f webhooks/circleci: Add steps instead of linking to CircleCI docs.
We let Markdown increment the list step numbers, which is more
reliable than keeping track of numbered-steps manually.

Also, instead of linking to the CircleCI docs, we now have full
instructions for how to setup a webhook by modifying the circle.yml
file.
2018-04-16 16:39:23 -07:00
Tim Abbott
310b451dc2 Revert "requirements: Use pypi versions of zulip and zulip_bots."
This reverts commit 6b142b35e6.
2018-04-16 16:39:01 -07:00
Tim Abbott
6b142b35e6 requirements: Use pypi versions of zulip and zulip_bots. 2018-04-16 16:14:43 -07:00
Tim Abbott
5cc70675c6 webhooks: Suppress errors from very old GitLab versions.
Ancient GitLab from several years ago doesn't include the
HTTP_X_GITLAB_EVENT header (and seems to have a different format), so
we should ignore its requests.

Might be good to document the version threshhold, but it's very hard
to tell from Googling what it is.
2018-04-16 16:13:20 -07:00
Tim Abbott
e2f8bc9eac /api: Fix tests for /api homepage. 2018-04-16 16:13:20 -07:00
Eeshan Garg
6782f2b76a pypi: Upgrade to release 0.4.4.
This is the latest release after pip 10 was launched.
2018-04-16 16:04:46 -07:00
Tim Abbott
c224114287 /api: Clean up the API documentation homepage. 2018-04-16 15:54:39 -07:00
Tim Abbott
d09071bbc9 /api: Add an overview doc for the REST API. 2018-04-16 15:51:13 -07:00
Tim Abbott
89704df167 /api: Move list of REST endpoints to a template. 2018-04-16 15:50:53 -07:00
Tim Abbott
ea266f1b80 /api: Expand "common errors" page to more generally cover error handling. 2018-04-16 15:50:52 -07:00
Rhea Parekh
a2070fb7e5 slack importer: Add comment on size information of avatars.
The size information of an avatar is not required during the import.
Check function 'import_uploads_local' and 'import_uploads_s3'
in 'export.py' for this.
2018-04-16 14:44:57 -07:00
Tim Abbott
636390104a css: Fix glitchy white line in recipient headers.
The intent had always been for this to be just a color change; a white
boundary didn't look good for either the day or night theme.
2018-04-16 13:37:21 -07:00
Tim Abbott
f6709cc888 i18n: Update translations from transifex. 2018-04-16 13:25:06 -07:00
Nikhil Kumar Mishra
91412e5843 test_upload: Add test for get_realm_for_filename. 2018-04-16 11:52:44 -07:00
Nikhil Kumar Mishra
c96dc1652e test_upload: Add tests for resize_emoji. 2018-04-16 11:52:44 -07:00
Tim Abbott
0c30a26d81 bulk_create: Remove some long-dead code.
We used to use these in populate_db, but haven't done so in a long
time, and it doesn't seem likely that will change anytime in the
future.
2018-04-16 11:41:42 -07:00
Greg Price
21045d8cf0 prod docs: Call out more the need for a chained cert bundle.
This is kind of easy to gloss over, especially with the framing
as a "format"; surely if things work at all, the file format
must have been right, right?  It's really a bit more substantive
than that; say so and also add a bit more description.
2018-04-16 11:34:23 -07:00
Ben Reeves
fdfbd45208 soft_deactivation: Change < to <= in add_missing_messages.
We should still short-circuit the iteration in
`add_missing_messages` if the unsubscription was the last
thing to happen to the user before unsubscription and
soft deactivation.
2018-04-16 11:28:08 -07:00
Alyssa Wagenmaker
d4e5777296 tests: Test user unsubscribing before soft deactivation.
Brings lib/soft_deactivation.py up to 100% test coverage.

Improves: #7089.
2018-04-16 11:28:08 -07:00
Shubham Dhama
03f95ba993 upload: Rename uploadStarted to drop to match original convention.
We used uploadStarted for drop callback which is kind of confusing
for new contributors as there is a big difference between uploadStarted
and drop like uploadStarted is called for each file in an upload whereas
the drop is called once when the file(s) are uploaded.
2018-04-16 11:16:42 -07:00
Shubham Dhama
0d0f971ae1 upload: Fix stacking of progress bar on canceling the upload.
This fixes stacking of upload progress bar when upload is canceled
and later made another upload.
2018-04-16 23:00:21 +05:30
Tim Abbott
593201a107 css: Cleanup CSS for sidebars.
This fixes a handful of minor issues:

* Non-uniform padding for the right sidebar unread count bubbles.
* Weird vertical positioning of unread counts in the right sidebar due
  to a slightly off line height.
* Missing padding between long stream names and the unread count for the stream.
* Removes a duplicate border-radius command in the left sidebar CSS.
2018-04-16 10:04:37 -07:00
Cynthia Lin
d0f5ae38cc right-sidebar: Properly align unread counts with topic names. 2018-04-16 09:48:04 -07:00
Cynthia Lin
47d50c6b86 left-sidebar: Properly align unread counts with topic names.
Fixes #7492.
2018-04-16 09:48:01 -07:00
Cynthia Lin
7cbc9f40bf compose: Change styling of upload progress bar.
Related to #9095.
2018-04-16 09:46:35 -07:00
Cynthia Lin
983deff5da compose: Align recipient bar icons properly.
Fixes #9094.
2018-04-16 09:45:30 -07:00
Cynthia Lin
02d122bed5 input-pill: Wrap input pills when they overflow pill container.
Fixes #9096.
2018-04-16 09:44:22 -07:00
Rhea Parekh
f6b6aa1e75 slack import: Implement threading as a management command. 2018-04-15 19:53:02 +05:30
Rhea Parekh
7c0c3930a8 slack importer: Thread avatar downloads. 2018-04-15 19:53:01 +05:30
Rhea Parekh
ebc2ee28e9 slack importer: Thread emoji downloads. 2018-04-15 19:52:59 +05:30
Rhea Parekh
8a291d0232 slack importer: Thread attachment downloads.
Use Zulip's run_parallel method to run thread downloads.
2018-04-15 19:51:58 +05:30
Steve Howell
7666e9c7a9 stream settings: Simplify how we select streams tabs.
This commit introduces a helper function called
maybe_select_tab() that goes to the correct tab in the
toggler widget.

It avoids the "lookup" mechanism, which I am hoping to
deprecate, and it handles hypothetical startup issues
by warning instead of crashing.
2018-04-14 11:40:03 -07:00
Steve Howell
9319da8e1d stream settings: Fix bug with Subscribed/All.
Before this commit, this sequence would lead to errors:

        * Open streams page via the gear menu.
        * Go to "All" tab.
        * Leave streams settings.
        * Re-open stream settings via the gear menu.

After doing this, the tab would show "Subscribed" but the list
would be of all messages.

Now we explicitly goto the first tab.

I added a long comment explaining how subs.js contributed
to this bug--in short, we re-build the widget instead of just
re-opening this.

We may also want the toggle component to simply default the
initial tab to the first tab.
2018-04-14 11:40:03 -07:00
Steve Howell
a2354ce699 Prevent traceback with info overlays.
We now make sure our toggler exists before invoking its `goto`
method.  Usually a toggler exists pretty early during app
startup, but _setup_info_overlay is wrapped in i18n.ensure_i18n,
which asynchronously fetches translation data.

This commit also simplifies how we find the toggler, by just
storing it in the module where it gets created and consumed.

Fixes #9085.
2018-04-14 11:40:03 -07:00
Eeshan Garg
6d86c83966 webhooks/solano: Update docs to conform to style guide. 2018-04-14 09:38:22 -07:00
Eeshan Garg
eec7e17e70 webhook/raygun: Update docs to conform to style guide. 2018-04-14 09:38:22 -07:00
Eeshan Garg
c51a3dce62 webhooks/pivotal: Update docs to conform to style guide. 2018-04-14 09:38:22 -07:00
Eeshan Garg
911b9582bd webhooks/opbeat: Update docs to conform to style guide. 2018-04-14 09:38:22 -07:00
Eeshan Garg
3e0eb9530c webhooks: Remove the Facebook integration.
Rishi and I decided that it makes sense to get rid of the Facebook
integration for a few reasons, some of which are:

* The setup process is too complicated on Facebook's end. The users
  will surely have to browse Facebook's huge API reference before even
  having a vague idea of what they want.
* Slack chooses not to have a Facebook integration, but relies on
  Zapier for it. Zaps that integrate with Facebook are much more
  streamlined and the setup process isn't as much of a pain. Zapier's
  Facebook Zaps are much more fine-tuned and there are different Zaps
  for different parts of the FB API, a luxury that would likely span
  2K+ lines of code on our end if we were to implement it from
  scratch. So, I think we should relegate integration with Facebook to
  Zapier as well!
* After thoroughly testing the setup process, we concluded that the
  person who submitted the FB integration didn't really test it
  thoroughly because there were some gaping holes in the docs (missing
  steps, user permissions, etc.).
2018-04-14 09:38:22 -07:00
Tim Abbott
5ddf2614f0 uploads: Add new way of querying for mobile uploads endpoint.
This extends the /user_uploads API endpoint to support passing the
authentication credentials via the URL, not the HTTP_AUTHORIZATION
headers.  This is an important workaround for the fact that React
Native's Webview system doesn't support setting HTTP_AUTHORIZATION;
the app will be responsible for rewriting URLs for uploaded files
directly to add this parameter.
2018-04-13 17:51:45 -07:00
Tim Abbott
012115c9e0 message_events: Fix updating compose fade after topic editing.
If you started composing a message to a topic, and then the topic was
edited, we would update the compose box and message list state, but we
didn't correctly update the fade state after updating your compose box
(and the message list), resulting in the messages being incorrectly
faded.
2018-04-13 16:31:18 -07:00
Tim Abbott
5ae9505fdc message_list: Move set_message_offset into message_list_view.
The refactor in 12509515ae had a subtle
bug, which is that we switched from accessing the message list "this"
(aka the message list being rerendered) to current_msg_list.  This
meant that when the narrowed_msg_list was in view and code needed to
modify home_msg_list, we accessed the wrong `selected_row` to preserve
the scroll position of (namely, the one in current_msg_list, not the
one in home_msg_list).

Fix this, by moving the function to be a property of the
message_list_view object, which makes more sense structurally, anyway.

We may, in the future, want to do a similar migration for more of
message_viewport.js.

Fixes #8854.
2018-04-13 16:31:18 -07:00
Tim Abbott
dbbd3627b5 message_list: Show empty narrow notice after emptying narrow.
If muting, topic editing, or deletion causes a narrow loses its last
message, we should show the empty-narrow notice; similarly, if
un-muting adds the first message, we should hide the notice.

We do this in `rerender()` since that's the common code path for
re-rendering the message list after events that might change this.

This has likely been broken since the very first muting
and topic editing implementations.
2018-04-13 16:31:18 -07:00
Tim Abbott
a8d237b252 message_list: Extract rerender_with_target_scrolltop.
We may find this useful in upcoming edits to the muting code.
2018-04-13 16:31:18 -07:00
Aastha Gupta
0c219a1905 invites ui: fixed rerendering of invites list
Earlier it was re-initialising invites list on every load.
Now, if the list is already initialised, it will reutilise
those resources.
2018-04-13 12:07:42 -07:00
Tarun Kumar
0b62410f5e pm_pill: Achieve 100% node-test coverage for compose_pm_pill.js. 2018-04-13 11:59:57 -07:00
Tarun Kumar
51601fab44 muting: Achieve 100% node-test coverage for settings_muting.js. 2018-04-13 11:59:57 -07:00
Aditya Bansal
113c1a81ea index.html: Refactor 'javascript:' URI to be written as JavaScript block.
This is important because we intent to start using CSP in our main
app which means dropping of any inline event handlers
(onclick="...", onerror="...") and <a href="javascript:...">
links that can be used to run scripts.
2018-04-13 11:10:06 -07:00
Rohitt Vashishtha
7a4788b364 node-tests: Add basic tests for notifications API.
This commit exposes some inner variables of notifications.js to make
them easily testable. The first test added simply checks whether the
showing and closing of notifications works properly, and doesn't yet
verify the main code logic of the notification generation.
2018-04-13 09:13:50 -07:00
Rohitt Vashishtha
703351288a zjquery: Add replaceWith() and selector.location. 2018-04-13 09:13:50 -07:00
Rohitt Vashishtha
2e7f215f44 zjquery: Add option to silently ignore find() errors. 2018-04-13 09:13:50 -07:00
Tim Abbott
db830c4085 bugdown: Replace link to old pre-open-source Zulip trac. 2018-04-13 08:41:44 -07:00
Rohitt Vashishtha
9e7929417d markdown: Increase rendered_content length limit.
This commit increases the rendered_content limit from 2x to 10x of the
original message length.

Earlier, we had placed a limit of MAX_MESSAGE_LENGTH * 2 for the
rendered content (explained in commit
77addc5456).  That limit was based on
the assumption that in most cases, the rendered content wouldn't cause
a large increase in message length. However, quite prominently in
syntax highlighted codeblocks, that wasn't true and this caused the
limit condition to be hit for long messages composed primarily of code
blocks.

Example: The following message would render close to 10x it's original size.

```py
if:
def:
print("x", var)
x = y
```

Because the syntax highlighted logic is extremely compressible, having
rendered_content reach up to 100KB doesn't create a network
performance problem.
2018-04-13 08:39:51 -07:00
Tim Abbott
cc927774af docs: Add an initial document explaining our presence protocol. 2018-04-12 17:04:51 -07:00
novokrest
0d164fab1b message_edit_form: Show default cursor instead of pointer.
Fixes part of #3938.

(This only changes the sections within the message-edit form; the top
and left chrome are still cursor: pointer).
2018-04-12 15:08:38 -07:00
Gooca
c4e2899f99 settings: Pull nested radio-buttons closer together.
Exact spacing tweaked by tabbott.

Fixes #9066.
2018-04-12 14:46:23 -07:00
Tim Abbott
105eed049e install-node: Fix leaking of $HOME.
This fixes a bug where provision was failing since our most recent
upgrade to yarn/nvm/node.

It turns out my original fix was the correct fix, but to the wrong
third-party tool: nvm, not yarn, was the offender.
2018-04-12 14:32:36 -07:00
Steve Howell
f56b4b7ec2 zjquery: Simplify validation for $(...).
We flatten the code a bit by removing a check that type is object,
and we replace it later with a check that type is string.

We also no longer allow document-like objects to be wrapped based
on the location-attribute-is-present hack.  Instead, we want the
tests to just set document to 'document-stub'.
2018-04-12 11:37:01 -07:00
Steve Howell
368b37e198 zjquery: Add a selector attribute to our wrapped elements.
We might as well have this for debugging purposes and to simplify
the code that happens when we double-wrap DOM elements.
2018-04-12 11:37:01 -07:00
Steve Howell
f78947f2ba zjquery: Fix minor typo in comments.
This typo came from s/impl/self/ at some point :)
2018-04-12 11:37:01 -07:00
Steve Howell
174d065b2e zjquery: Add to_$ hook so elements can wrap themselves. 2018-04-12 11:37:01 -07:00
Steve Howell
f505f3de04 zjquery: Support $.fn.foo mechanism.
We can now extend zjquery using the $.fn mechanism.  This isn't
necessarily recommended for test code (since you can just stub
individual objects directly), but some of our real code does this.
2018-04-12 11:37:00 -07:00
Steve Howell
f5e0794d5c zjsquery: Give special treatment to "body" tag.
We don't do things like $('table') much in our code, but $('body')
is in the code.
2018-04-12 11:37:00 -07:00
Steve Howell
3971fae05d node tests: Fix some leaks with $(...). 2018-04-12 11:37:00 -07:00
Tim Abbott
041fd802b7 Revert "yarn: Revert back to v0.27.5."
This reverts commit d4b88e86cc.
2018-04-12 11:37:00 -07:00
Tim Abbott
f6ae57fa70 install-node: Correctly fix yarn installation.
Apparently, new versions of yarn use the HOME environment variable to
figure out where to access their configuration, and sudo apparently
doesn't clear that variable, so install-node was being run with HOME
set to something under /home/vagrant (e.g.).

Fix this by just setting that environment variable correctly.

This replaces 250a036ff8, which
misdiagnosed the issue.
2018-04-12 11:37:00 -07:00
Steve Howell
205bcb8ef9 Fix recently broken reactions tests with s/html/text/.
In the real code, as part of a quick security patch, we started
using text() to set values instead of html().  The tests now
reflect this.
2018-04-12 11:29:08 -07:00
Shubham Dhama
50545a3571 settings: Revert checkmark icon to fontawesome and checkbox-green.
In 7b8da9b we have introduced some other checkmark icons
which aren't necessary as old icons still make sense there.

So removing them as they don't add any extra value.

Fixes: #8995.
2018-04-12 11:06:43 -07:00
Tim Abbott
250a036ff8 install-node: Fix yarn installation.
It appears that some change in yarn's versioning system means that
installing yarn itself ends up chowning its config directory
incorrectly to be owned by root, preventing `yarn install` from
working later.
2018-04-12 10:42:27 -07:00
Tim Abbott
fea65cbb01 Update changelog and versions following Zulip Server 1.7.2. 2018-04-12 10:20:49 -07:00
Priyank
d4b88e86cc yarn: Revert back to v0.27.5.
Revert yarn version back due to some issue with new version that causes
permission issues in ~/.config/yarn directory.

Related discussion: https://chat.zulip.org/#narrow/stream/21-provision-help/topic/EACCES.3A.20permission.20denied.2C.20scandir.20'.2Fhome.2Fvagrant.2F.2Econfig.2Fya
2018-04-12 10:18:59 -07:00
Tim Abbott
feef35bf25 linter: Add checks for sloppy use of .html().
Since jQuery's .html() can be a source of security bugs, we add a new
lint rule that tries to catch common problematic uses.
2018-04-12 09:50:14 -07:00
Tim Abbott
5f0f492205 unread: Clean up variable names around bankruptcy modal.
unread_info was a particularly bad name for the HTML.
2018-04-12 09:48:02 -07:00
Tim Abbott
a6d80969f5 subs: Clean up variable name for rendered subscription count. 2018-04-12 09:48:02 -07:00
Tim Abbott
ab8fb23164 emoji: Clean up variable names for rendered template content. 2018-04-12 09:48:02 -07:00
Tim Abbott
c90fbff703 settings: Clarify variable names for tab template rendering. 2018-04-12 09:48:02 -07:00
Tim Abbott
dbb62ba5cb compose: Clean up variable names for preview logic.
This makes it more clear that the content has already been rendered.
2018-04-12 09:48:02 -07:00
Tim Abbott
f4aea3ec22 invite: Clarify variable names for rendered_email_msg. 2018-04-12 09:48:02 -07:00
Tim Abbott
0db715d222 search_suggestion: Add escaping for email addresses.
This is probably unnecessary, but makes me feel better about every
code path in this file doing proper escaping to avoid XSS issues.
2018-04-12 09:47:01 -07:00
Tim Abbott
65b9d9e0f3 CVE-2018-9990: Fix XSS issue with stream names in topic typeahead.
Zulip's search typeahead had a security bug, where when autocompleting
a specially crafted stream name, and then hitting space, code within
the stream name would be executed.

Zulip was doing HTML escaping correctly in the main code path using
Filter.describe to describe a narrow, but the escaping function was
not called in a few parallel code paths.  We fix this in a way that
should protect all of these code paths, by making Filter.describe
return properly escaped HTML, rather than depending on its callers to
do so.

Thanks to w2w for reporting this issue.
2018-04-12 09:46:54 -07:00
Rohitt Vashishtha
3bdc8bbaa5 CVE-2018-9986: Fix XSS issues with frontend markdown processor.
This fixes a set of XSS issues with Zulip's frontend markdown
processor, which is used in a limited set of contexts, such as local
echo of messages and the drafts feature.

The implementation of several syntax elements, including the <em>
syntax, user and stream mentions, and some others failed to properly
escape the content inside the syntax.

Fix this, and add tests for each corrected code path.

Thanks to w2w for reporting this issue.
2018-04-12 09:46:37 -07:00
Tim Abbott
1207a08b36 CVE-2018-9987: Fix XSS issue with muting notifications.
This fixes an XSS issue with Zulip's muting UI, where if a stream or
topic name contained malicious HTML containing JavaScript, and the
user did a muting interaction, the malicious JavaScript could run when
rendering the "you just muted a topic" notification.

We did an audit for similarly problematic use of `.html`, and found
none; for the next release we'll be merging a series of changes to our
linter to prevent future instances of this being added.

Thanks to Suhas Sunil Gaikwad for reporting this issue.
2018-04-12 09:46:03 -07:00
YJDave
92a04b31a0 custom fields: Clean custom fields to use existing defined function. 2018-04-12 09:40:09 -07:00
guaca
a19daf0ab2 Settings: Fix vertical spacing.
Removed the top margin of input-group css
 to prevent the double margins. Also fixed the
 default-language positioning, and maintained
margin consistency in organization settings.

Fixes #8890.
2018-04-12 09:38:24 -07:00
Axel Tietje
8f984be457 docs: Fix typo in production docs. 2018-04-12 09:19:26 -07:00
Lyla Fischer
d291def7a1 user-docs subsystem: Fix broken markdown. 2018-04-12 09:09:56 -07:00
Lyla Fischer
390eeaab5b help: Remove follow-steps doc macro. 2018-04-11 16:44:08 -07:00
Lyla Fischer
00255ad7c0 help: Remove the go-to-the macro. 2018-04-11 16:44:08 -07:00
Shubham Padia
55619cbe70 browser-support: Add string.prototype.endswith polyfill.
String.prototype.endsWith is not supported in ie11.
Adds string.prototype.endswith package to dependencies and places
it at `common` entry point in webpack.assets.json.
2018-04-11 15:40:57 -07:00
Tim Abbott
e6833b6427 cleanup: Remove the legacy Dropbox file upload integration.
This has been hard-disabled for years, we have no plans to re-enable
it, and it has some hacky code in it.
2018-04-11 11:39:48 -07:00
Aditya Bansal
6c1a50da76 csp_reports: Add endpoint to handle logging of reports sent by clients. 2018-04-11 23:01:13 +05:30
YJDave
95461761e4 subscription: Show current user on top of subscribers list if present.
Fixes #9027.
2018-04-11 09:54:42 -07:00
YJDave
c662867f14 subscription: Add comments for recent changes covering corner cases. 2018-04-11 09:51:52 -07:00
Vishnu Ks
132754f2ef requirements: Downgrade pika to 0.11.0.
Downgrading as issue #8466 is not fixed yet.
2018-04-11 09:31:10 -07:00
Vishwesh Jainkuniya
383c62fb03 dev_login: Identify each user's realm when listing them.
This is a mobile-specific endpoint used for logging into a dev server.
On mobile without this realm_uri it's impossible to send a login request
to the corresponding realm on the dev server and proceed further; we can
only guess, which doesn't work for using multiple realms.

Also rename the endpoint to reflect the additional data.

Testing Plan:
Sent a request to the endpoint, and inspected the result.

[greg: renamed function to match, squashed renames with data change,
 and adjusted commit message.]
2018-04-10 17:03:36 -07:00
Tim Abbott
a463743107 puppet: Add Content-Security-Policy for user avatars.
This adds a basic Content-Security-Policy for user-uploaded avatars
served by the LOCAL_UPLOADS backend.

I think this is for now an unnecessary follow-up to
d608a9d315, but is worth doing because
we may later change what can be uploaded in the avatars directory.
2018-04-10 14:43:08 -07:00
Rhea Parekh
f7398cbb09 slack import: Implement custom profile fields.
Add custom profile fields in the slack converted
data 'realm' file.
Added tests for the custom profile fields.

Fixes #8928
2018-04-10 13:28:53 -07:00
Rhea Parekh
852e8516b4 slack import: Add custom profile fields.
Build CustomProfileField and CustomProfileFieldValue
for every user and process the field type after getting an
entire list of the custom fields.
2018-04-10 13:28:53 -07:00
Rhea Parekh
ccefaf7b26 scripts: Remove the depreciated script 'postgres-reset-sequences'. 2018-04-10 13:07:14 -07:00
Marco Burstein
c36a658fee uploads: Fix the upload progress bar.
There was already a progress bar set up, but it became non-functional
after refactoring.  This fixes it.

The default animation was getting cut off when `uploadFinished` is
called, so we add a delay before removing the upload bar to make it
get to the end.

Tweaked by tabbott to have a more natural feeling animation setup
(where we don't animate the width adjustments; just the disappearance
of the bar).

Fixes #8863.
2018-04-09 22:53:06 -07:00
Tim Abbott
a4def8d409 copy_and_paste: Re-disable copy-paste handler in production.
This reverts commit 6e048c5d3f.

See #8963 for the main issue we need to fix before re-enabling this;
basically, some combination of toMarkdown and the way text/html gets
written was introducing a lot of bonus/bogus whitespace, both in the
form of newlines and spaces converted to `&nbsp;`.
2018-04-09 22:10:28 -07:00
rht
a183186672 slack importer: User session.get to recycle previous connections. 2018-04-09 22:02:01 -07:00
Shubham Dhama
b650b6b38c markdown: Add @stream as an alias for @all.
Fixes: #8930.
2018-04-09 16:35:14 -07:00
Shubham Dhama
771db7fb90 compose typeahead: Refactor repeating code for all and everyone mentions.
This is minor refactor with which we can simply add other aliases for
"all" and "everyone" mentions.
2018-04-09 16:35:14 -07:00
Marco Burstein
7c66d11781 compose: Show avatars for people in typeahead autocompletes.
`@everone` and `@all` will have a megaphone icon from FontAwesome in
place of the avatar.

Also, fix the `composebox_typeahead` tests to account for the images.

Fix #6635.
2018-04-09 15:47:11 -07:00
Tim Abbott
9b8dd4f125 install-yarn: Fix buggy status check for the signature.
Apparently, they added a new signing key instance, and so checking
whether the old key exists doesn't work anymore.
2018-04-09 15:09:37 -07:00
Aditya Bansal
d608a9d315 uploads: Add Content-Security-Policy for user uploads.
This adds a basic Content-Security-Policy for user-uploaded files with local uploads.

While over time, we plan to add CSP for the main site as well, this CSP is particularly
important for the local-uploads backend, which often shares a domain with the main site.
2018-04-09 14:43:02 -07:00
Priyank
ee078c372f install-node: Upgrade node, yarn, and nvm.
node -> v8.9.4
yarn -> 1.5.1
nvm -> 0.33.8

Also updates a test in timerender.js which depends on time
provided by node which is now changed in newer release.

Some changes have been made in circeci script, we just create ~/.config
directory and chown it to circleci user so installing new version of yarn
does not cause any ci failure on circleci during provision.
2018-04-09 13:56:48 -07:00
Shubham Padia
57a494f94d browser-support: Add codepointat polyfill to common entry point.
Adds string.prototype.codepointat which was added as a polyfill
earlier to the project but was not added to `common` entry point.
2018-04-09 12:16:19 -07:00
Shubham Padia
b906562f22 browser-support: Add string.prototype.startsWith polyfill.
Fixes #8944.
Adds string.prototype.startswith package to dependencies and places
it at `common` entry point in webpack.assets.json. As common.js is
loaded on all code paths first, there is no need to place this package
into other entry points.
2018-04-09 12:16:19 -07:00
Tim Abbott
37a83285c4 people: Clean up now-unnecessary url variable. 2018-04-09 12:12:44 -07:00
Tim Abbott
40421c5000 people: Refactor small_avatar_url logic for emails. 2018-04-09 12:12:44 -07:00
Tim Abbott
dfac0302fc people: Extract small_avatar_url_for_person.
This is intended to be used in places like compose typeahead to
display users' avatars.
2018-04-09 12:12:44 -07:00
Tim Abbott
3bfd96d8ed people: Use a return for message.avatar_url code path.
This helps clean up this code path a bit.
2018-04-09 12:07:41 -07:00
Tim Abbott
5bcfecd0dc people: Extract gravatar_url_for_email. 2018-04-09 12:07:41 -07:00
rht
7a8655cc50 Slack importer: Add test for Slack channel mention to Zulip stream mention. 2018-04-09 10:47:39 -07:00
rht
630adb406b Slack importer: Map Slack channel mentions to Zulip stream mentions. 2018-04-09 10:47:39 -07:00
Rhea Parekh
035c440ff3 import script: Support import custom profile fields.
Import of Custom profile fields is only supported for slack
import script for now.
2018-04-09 10:45:35 -07:00
Tim Abbott
c41d7ee300 slack import: Write reasonable multi-line JSON.
This is a lot better for debugging.
2018-04-09 10:45:35 -07:00
YJDave
025956482a subscription: Fix error in appending current user on top of subscriber list. 2018-04-08 16:54:12 -07:00
YJDave
f5a7d125c9 subscription: Clean functions peer_subscribe and peer_unsubscribe. 2018-04-08 16:54:12 -07:00
YJDave
dcf9355502 subs.js: Rename function to check_button_for_sub to be more specific. 2018-04-08 16:54:12 -07:00
YJDave
ed70a92ed3 subscription: Fix error in being re-subscribed to private stream.
Fixes #9023
2018-04-08 16:54:12 -07:00
YJDave
24f51739eb subscription: Add real time sync for user-just-deactivated case.
Currently, stream subscriptions aren't getting updated without
hard reload when user is deactivated in realm.

Fix this issue by updating stream subscription widgets on user
deactivation event.

Fixes #5623
2018-04-08 16:54:12 -07:00
YJDave
cf40536ed2 stream_edit.js: Add helper func to rerender all subscriptions settings.
This add the function to rerender subscriptions settings which includes
subscriber count and subscriber list only if subscriptions tab is active.
2018-04-08 16:54:12 -07:00
YJDave
211eba2c56 stream_edit.js: Add helper func to check if sub settings tab is active.
This commit adds a new helper func to check if sub settings tab
is active or not and remove function `add_me_to_member_list`
function from `static/js/stream_edit.js`, cause we don't need to
render subscribers for particular case, as we are already doing that.
2018-04-08 16:54:12 -07:00
YJDave
386c56b466 stream_data.js: Replace user_email with user_id in func is_user_subscribed. 2018-04-08 16:54:12 -07:00
Tim Abbott
c3df378ca1 slack import: Document that this is a new feature in 1.8. 2018-04-08 07:11:07 -07:00
Rhea Parekh
ed7127c8b4 import script: Delete medium sized avatars if it exists.
Deletion of medium sized image is done if it exists before calling the
function 'ensure_medium_avatar_image', to avoid potentially confusing
problems with left-over medium-size avatar images from a previous run
being used when repeatedly importing the same realm in a development
environment..

Fixes #8949.
2018-04-08 07:04:24 -07:00
Balaji2198
c63d1c9205 node tests: Cover compose_not_subscribed in compose.js.
This commit covers the node tests to close(X) button and
subscribe button click handlers in compose.js.
2018-04-07 20:23:21 -07:00
Balaji2198
47f9e8319c compose: Close the compose error message box on clicking X. 2018-04-07 20:23:21 -07:00
Shubham Dhama
f6d73a7444 settings: Fix label for message_content_in_email_notifications.
This was a regression introduced in deduplication of settings
template.
Fixes: #9021.
2018-04-07 20:22:33 -07:00
YJDave
21d1133c4f subscriptions: Clear email address on unsubscriptions from stream.
Currently, even after unsubscribing from private/public stream
email address of stream is still present in html widgets hidden.
Cause we don't clear email address on unsubscription event.

This clears email address from widget when user unsubscribe
from any stream.
2018-04-07 20:10:45 -07:00
YJDave
f15ddc93e0 create stream: Fix stream email not rendering on stream creation.
Fixes #8817
2018-04-07 20:10:45 -07:00
Aditya Bansal
b9f1acb300 linter: Enforce 2 space indents on tags spread over multiple lines.
We make some specific cases of tags use 2 space indents.
The case description:
* A tag with opening tag spread over multiple lines and closing tag
on the same line as of the closing angle bracket of the opening tag.
* A tag with opening tag spread over multiple lines and closing tag
not on the same line as of the closing angle bracket of the opening
tag.

Example:
Case 1:

Not linted:
<button type="button"
class="btn btn-primary btn-small">{{t "Yes" }}</button>

After linting:
<button type="button"
  class="btn btn-primary btn-small">{{t "Yes" }}</button>

Case 2:

Before linting:
<div class = "foo"
     id = "bar"
     role = "whatever">
     {{ bla }}
</div>

After linting:
<div class = "foo"
  id = "bar"
  role = "whatever">
    {{ bla }}
</div>
2018-04-07 20:08:44 -07:00
Aditya Bansal
550222dede linter: Make multiline handlebar singleton tags use 2 space indentation. 2018-04-07 20:08:38 -07:00
Aditya Bansal
2fe012ffff linter: Make html singleton tags use 2 space indentation. 2018-04-07 20:08:31 -07:00
Cynthia Lin
7eacf2aa9a org settings: Offset border to prevent adding extra height on hover.
Fixes #8996.
2018-04-07 20:06:40 -07:00
Gooca
3ed5a64e13 Dark-mode: Update rail-y to match dark theme. 2018-04-07 20:03:19 -07:00
rht
f6feac1316 Slack importer: Map Slack command for mentions to Zulip's all.
Fixes #9003.
2018-04-07 20:02:39 -07:00
Rhea Parekh
e037c2f93e import script: Fix upload links.
Rendered content is None for Slack imports, hence it is replaced only
for Zulip->Zulip imports.

Fixes #8959.
2018-04-07 20:01:20 -07:00
Rhea Parekh
b3f951d2cf import script: User profile ids should be allocated before allocating bot ids. 2018-04-07 13:28:33 +05:30
Vishnu Ks
e92838a31f registration: Catch email validation error and show error message. 2018-04-06 15:18:32 -07:00
Shubham Dhama
0e6757af5c org settings: Change default realm description to empty string.
This fixes a traceback that users would get when editing the realm
description just after creating a new organization.
2018-04-06 15:15:47 -07:00
Alyssa Wagenmaker
df666c3dfc test-backend: Report fully covered files still in not_yet_fully_covered. 2018-04-06 17:29:18 -04:00
Ben Reeves
29a079ebbf test-backend: Remove fully covered files from not_yet_fully_covered. 2018-04-06 17:29:18 -04:00
Tim Abbott
65c4a43a82 lint: Fix errors with stats.js with new eslint.
This (for ... in) syntax we shouldn't be using anyway, but this at
least fixes the worst aspect of it.
2018-04-06 12:42:19 -07:00
Shubham Dhama
4b7ce531c3 settings: Revert "loader" indicator from fontawesome to SVG.
This reverts loader indicator from the new fontawesome
`icon-button-loading` to previous SVG one, this change is only reflected
to those loaders which use `loader.handlebars` template for
loading indication(because there are some indicators like "Save changes"
in org settings which don't use loader.handlebars).

This main problem with this indicator is that it is bit
inconsistent with other places where we use `loader.handlebars` like
loading Zulip icon which appears while fetching old messages.
2018-04-06 12:32:45 -07:00
Aastha Gupta
c852185e9d stream settings: Make deactivate stream handler global.
Configure the click event handler for #do_deactivate_stream_button
once to avoid adding click handlers for it more than once.

Fixes #8979
2018-04-06 12:25:42 -07:00
Tim Abbott
d1c57df0ca estlint: Upgrade version of eslint. 2018-04-06 11:58:58 -07:00
Rhea Parekh
2baa9bc16e Import: Add subdomain in the import script.
Also remove user input of subdomain in the slack data
conversion script.
2018-04-06 09:12:56 -07:00
Tim Abbott
ad861c5fae messages: Improve comment on need_messages. 2018-04-06 08:57:46 -07:00
Priyank
3413faee14 provision: Bump provision version after clipboard package update.
The provision wasn't bumped in https://github.com/zulip/zulip/pull/8984
which caused some js exceptions.
2018-04-06 11:12:35 -04:00
Eeshan Garg
42bbfea775 webhooks/splunk: Update docs to conform to style guide. 2018-04-05 23:28:27 -07:00
Eeshan Garg
7b1ce446cf webhook/opsgenie: Update docs to conform to style guide. 2018-04-05 22:47:22 -07:00
Eeshan Garg
2e700477e3 webhooks/groove: Update docs to conform to style guide. 2018-04-05 22:47:21 -07:00
Armaan Ahluwalia
7b8da9b6c0 settings: Changed checkbox and close icons on settings.
Introduced a new checkmark icon in the settings page
from entypo ( www.entypo.com )  to make icons more
consistent between user and organization settings.
2018-04-05 21:49:13 -07:00
Armaan Ahluwalia
58d07fabef settings: Change save and discard button look and feel.
This commit changes the way the save and discard buttons on the
organization profile, settings and permissions tabs look and fades
them out after a delay. It also cleans up the code a bit in the
settings_org.js file. It introduces changes to the css in
settings.css as well as the template for save-discard buttons.

It also fixes a bug on the user settings whereby if an option
that requires reload is clicked before clicking an option that does
not require reload, the reload message is erased. This could create
an issue where the user is not aware that a reload is required.
The loader is also changed to using fa-icon as loading spinner on
user settings and the colors are tweaked a little bit.
2018-04-05 21:49:12 -07:00
Greg Price
9a0fdf5b8d settings: Add a few more section headings.
This should help a bit more in making this file navigable.

I think there's further work that could be done to organize the
settings better: e.g., group LDAP with the auth section; separate
resource limits, from debugging and error reporting, from configuring
service dependencies like Redis and Rabbit.  That'd require reordering
many settings, and also taking a closer look at many settings one by
one in order to do a good job.  Leaving that for another day.
2018-04-05 21:24:48 -07:00
Greg Price
9a9d3097be settings: Add some visual weight to the section headings.
I've found this file hard to navigate for a while.  We actually have a
little hierarchy of section headings which applies to a lot of the
file already; make the boundaries bolder.
2018-04-05 21:24:48 -07:00
Greg Price
f597f0b52e settings: Revise block comment at top of file.
Hopefully this is a bit clearer to read.
2018-04-05 21:24:48 -07:00
Greg Price
6396b3aef7 docs/production: Make an editing pass through the SMTP doc.
In addition to many small edits for formatting and clarity, a few more
significant changes:

* In the main instructions, refer specifically to restarting the
  server and to testing that the config works.

* Add SendGrid to the recommended list, as it seems like people
  give it a somewhat stronger reputation these days than Mailgun.

* Discuss EMAIL_USE_TLS and EMAIL_PORT along with host, user, and
  password in the "free services" section.  Though those bullets feel
  kind of duplicative to me already.
2018-04-05 21:24:48 -07:00
Greg Price
b9f1f9c0ae docs/production: Reorganize SMTP docs a bit.
Let's get right to the point of how to configure SMTP once you know
what you want.  That section is pretty short anyway; and we can have
a first step direct the reader to our suggestions if they don't know
what service they want to use.

Also adjust the hierarchy of the headings: group the various
alternative email services under one heading, and group
troubleshooting together under an independent heading.

Also correct what we say about EMAIL_PORT: the Django default is
apparently 25, so if the provider *does* use the usual port 587
then we'll need the port to be set.
2018-04-05 21:24:48 -07:00
Greg Price
9956d61e20 settings: Revise comments on SMTP / outgoing email settings.
Add a clear heading, and use fewer words and simpler sentences.  Also
explain the password thing a bit more, and put that more inline next
to the username.

Also, on checking the Django docs, the default for EMAIL_USE_TLS
is False and for EMAIL_PORT is 25.  So most admins, certainly any that
are using an SMTP service on the public Internet (that is at all
decently run), will need to set those settings.  Mention that.
2018-04-05 21:24:48 -07:00
Tarun Kumar
c53458c9c0 user-groups: Add template for non-editable groups. 2018-04-05 17:40:12 -07:00
Tarun Kumar
5c11ab857e pills: Add exportable function for creating non-editable pills. 2018-04-05 17:40:12 -07:00
Armaan Ahluwalia
9a6a82516d settings: Make sticky feedback not disappear after delay.
This commit adds the ability to pass the sticky option to
in the change_display_setting function in order to have the
feedback element remain visible instead of fading out which
is the default behavior. Also passes true for that option in
two instances on the page.
2018-04-05 17:18:39 -07:00
Armaan Ahluwalia
381e498343 settings: Fixes spinners and fades out feedback in settings.
This commit changes the do_settings_change function so that it
defaults to showing the loading spinner for 500ms before fading
out the the feedback element. It also adds a sticky option so you
can override the fading out of the feedback element and have it
remain visible.
2018-04-05 17:18:35 -07:00
Armaan Ahluwalia
95634b9d17 ui: Add ability to hide ui feedback messages.
This adds the option to hide the container element after a given
duration in the message and success functions in the ui_report module.
2018-04-05 17:17:08 -07:00
Balaji2198
605916f6d7 compose: Add subscribe button to the not subscribed stream error message.
Before that, we needed to go the stream settings to subscribe to a
particular stream.

Fixes #3877.
2018-04-05 17:15:18 -07:00
Shubham Dhama
d3627ab419 docs: Update new-feature-tutorial.
This updates the new-feature-tutorial for the recent changes done
in org settings subsystem.

Edited significantly by tabbott, mostly for grammar.
2018-04-05 15:51:51 -07:00
Cynthia Lin
c5d5efa9be portico-signin: Remove fixed-width styling for OR lines.
Fixes #8977.
2018-04-05 15:25:36 -07:00
Tim Abbott
b12368aec5 compose: Fix fading when topic changes on re-narrow.
Now that we're changing the topic on re-narrow more frequently, we
need to ensure that we update the compose_fade state when we do so.
2018-04-05 15:21:02 -07:00
Tim Abbott
5a5b4730f1 compose_actions: Keep the compose box open on topic change.
This tweak to our compose on-topic-narrow logic may help make it a bit
easier to do quick replies without needing to re-open compose.  I'm
not 100% confident this actually makes Zulip better, but it's worth
testing and getting some feedback.

Fixes #6473.
2018-04-05 15:17:40 -07:00
Tim Abbott
b9acdd947a compose_state: Re-fade message list when switching topics.
For a non-empty compose box, we previously considered closing the
compose box when switching topic narrows with content in the compose
box; now we leave it open unconditionally.

Part of #6473.
2018-04-05 15:17:40 -07:00
Priyank
f5acbcb4c8 clipboard: Update clipboard to v2.0.0 to avoid variable name conflict.
It turns out, now we have a new standard way to access clipboard by
`Clipboard` method and currently this conflict with the constructor
exported by clipboard package. The new update v2.0.0 was released to address
this issue. The new version just exports the constructor as `ClipboardJS`.

Ref: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard
Ref: https://github.com/zenorocha/clipboard.js/issues/468
2018-04-05 15:09:00 -07:00
Eeshan Garg
d03d2808b2 integrations css: Increase top/bottom margin for h3 headings.
The margin between the first sentence/heading for the
instructions and the numbered list that follows was too small,
which made headings look very awkward.
2018-04-05 14:48:37 -07:00
Eeshan Garg
231f1b3492 integrations css: Properly indent <ul>'s nested inside <ol>'s.
The indentation was off because there was no CSS anywhere that
properly indented <ul> elements that were nested inside <ol>
lists with custom numbering.
2018-04-05 14:48:37 -07:00
Eeshan Garg
eb9902e77f integrations css: Properly indent multi-paragraph list items.
Since every <li> element is prefixed by a custom list number, in
<li> elements with multiple <p> elements inside, the <p> elements
after the first one did NOT take into account the space occupied
by the custom list number, which resulted in inconsistent
indentation. Now, it does!
2018-04-05 14:48:37 -07:00
Eeshan Garg
d6cc1cfbc9 integrations: Render unordered lists with bullets.
Previously, the CSS for ordered lists also applied to unordered
lists, so unordered lists were rendered without any kind of bullets
or markers.
2018-04-05 14:48:37 -07:00
Eeshan Garg
42fb91de33 macros: Update link to installation instructions for API bindings.
This changed a while ago when we restructured our API docs.
2018-04-05 14:48:37 -07:00
Eeshan Garg
902ab01785 webhooks/trello: Update docs to conform to style guide.
This one is one of the most tedious to set up and get working.

We now also rely on the Trello scripts available as part of the
`python-zulip-api/zulip` API package to make the setup process
easier.
2018-04-05 14:48:37 -07:00
Tim Abbott
b0b134cb4c help: Clean up settings_html and subscriptions_html.
After some thinking, I don't think there's any actual value to doing
the ../ style relative links here, whereas there is actual harm from
the links being slightly broken in the current model.  We fix this by
just using /#settings as the URL.

Fixes #8978.
2018-04-05 14:48:26 -07:00
Rishi Gupta
a29b1c1569 help: Update change-your-name. 2018-04-05 14:46:32 -07:00
Rishi Gupta
f4737e77b0 help: Update view-an-image-at-full-size. 2018-04-05 14:46:32 -07:00
Rishi Gupta
efecad2355 help: Update view-and-edit-your-message-drafts. 2018-04-05 14:46:32 -07:00
Ben Reeves
05d3073960 vagrant: Fix link to testing docs in motd.
Change https://zulip.readthedocs.io/en/latest/testing.html to the new 
https://zulip.readthedocs.io/en/latest/testing/testing.html
2018-04-05 14:41:38 -07:00
Steve Howell
36844418e9 bug fix: Respect include_history for certain queries.
For certain queries where both include_history and
use_first_unread_anchor are set to True, we were excluding
historical rows.  Now we only use the use_first_unread_anchor
flag to filter rows that we use to find the anchor, without
having it filter the actual search results.

The bug went unreported for a long time, because it only
affected mobile users who had newly subscribed to streams.

Note that we make a small change to the test called
test_use_first_unread_anchor_with_muted_topics, which has
a very scary comment about being "arcane" and "be
absolutely sure you know what you're doing."  I think it's
fine.

Also, the new test code would fail before this fix, so it
should help prevent future regressions.

Fixes #8958
2018-04-05 17:16:41 -04:00
Steve Howell
b64117d872 refactor: Build query in find_first_unread_anchor().
This is a bit more than a pure refactor, because we duplicate a
chunk of code to calculate a query inside of
find_first_unread_anchor(), so we're doing a bit more work
than before.

We need this refactoring to start decoupling find_first_unread_anchor
from get_messages_backend for the case where include_history is
True.  This will happen in a subsequent commit.

The only test that changes here is a direct test on
find_first_unread_anchor().  All other tests pass without
modification, and we have decent coverage on get_messages_backend.
2018-04-05 17:16:41 -04:00
Steve Howell
345d44b5f1 Extract get_base_query_for_search(). 2018-04-05 17:16:41 -04:00
Steve Howell
59a9b69c25 Simplify search code for keyword searches.
We use an array now to build up the list of search operands and
then consolidate the special search handling after the loop (which
means setting the flag, putting two more columns in the query, and
using ' '.join to build the string).
2018-04-05 13:27:31 -07:00
Steve Howell
d521906fb6 search refactor: Extract add_narrow_conditions().
This code was basically pulled from two near-to-each-other
sections of get_messages_backend, and it does an early
return for narrow-is-None.
2018-04-05 13:27:31 -07:00
Steve Howell
3ac660d972 minor: Show narrow for UnicodeDecodeError.
We have a debugging statement for some obscure errors we get
when narrows have search terms.  We now show all the narrow
operators.  This isn't really to improve debugging; it's more
to make it easier in the next commit to extract a function
that would make search_term have to be passed back in a tuple.
But it shouldn't hurt debugging either.
2018-04-05 13:27:31 -07:00
Tim Abbott
ed5b374ffa help docs: Fix a sorta buggy URL test. 2018-04-05 13:07:34 -07:00
Tim Abbott
d7658bbec5 test_docs: Add an end-to-end test for HTML settings links.
This would have caught the issue fixed in the last few commits.
2018-04-05 12:22:41 -07:00
Tim Abbott
57ca19392e help: Make API context available to sidebars.
I am not a fan of this API (where we need to pass context into
`render_markdown_path` directly), but it seems more robust to pass
this in directly.
2018-04-05 12:22:41 -07:00
Tim Abbott
98889608a2 help: Fix structure of markdown context logic.
Refactoring in this file had resulted in the logic for
html_settings_link being duplicated and extra logic being needed to
ensure these variables were set where they were needed.

This fixes subscriptions_html not being rendered properly in the /help
and /api pages, in addition to removing duplicate code.
2018-04-05 12:22:41 -07:00
Tim Abbott
f1ece37455 help: Fix context not being passed properly to rendering.
This contributed to settings_html, subscriptions_html and friends not
being rendered properly in the /help/ views.
2018-04-05 11:43:48 -07:00
Nikhil Kumar Mishra
2cf32bda12 embed link: Add test for link_embed_data_from_cache. 2018-04-05 10:48:40 -07:00
Steve Howell
a0aa8d4b11 Add test for find_first_unread_anchor(). 2018-04-05 09:55:54 -07:00
Steve Howell
4cba679d38 Move code for find_first_unread_anchor().
This is a pure refactoring and just pulls the function out
to the top level of the module.  (The prior commit extracted
it inside a larger function to make a nicer diff.)
2018-04-05 09:55:54 -07:00
Steve Howell
d8a95c6517 Extract find_first_unread_anchor().
This is a pure refactoring.
2018-04-05 09:55:54 -07:00
Shubham Padia
6262460773 refactor: Rename mark_message(s)_as_read to notify_server_message(s)_read.
Fixes #8965.
Mark_message(s)_as_read is used in marking a message as having been
read by the browser, rename it to notify_server_message(s)_read to
avoid any confusion.
2018-04-05 09:54:48 -07:00
Rishi Gupta
5740af27d6 help: Update import-data-from-slack. 2018-04-05 08:49:40 -07:00
Steve Howell
c164d07baa zjquery: Enforce only one arg for $(...) function. 2018-04-05 10:46:45 -04:00
Steve Howell
4216b81e93 Fix Subscribed/All Streams bug.
We've had a longstanding bug where the streams settings code
was getting an i18n'ed value in the middle of a callback from
the toggle component, so it would have been broken for
non-English sites.  And then a recent cleanup of the toggle
code introduced a bug where the callback-in-the-callback was
getting stale state, so English sites broke too.

This fix just simplifies everything by using the key that
comes into our callback to determine whether we filter or not.

Fixes #8945
2018-04-04 16:37:39 -07:00
Steve Howell
27770d7f6b Fix recent pitfall in toggle component.
This is a recent regression where we I refactored the toggle
component.  For some reason the old code was waiting until
after the callback to set some of its state, and I did the
same thing when I simplified how the state was stored.

Under the old code, this didn't manifest as a bug, although
the old code was problematic for other reasons.

This "fix" doesn't actually change anything user facing, as the
follow up commit fixes the proximal problem more directly. And
the toggle component is still prone to people writing code that
tries to inspect the state of the widget as it's being built.
2018-04-04 16:37:39 -07:00
Steve Howell
0e7073ec29 Fix keyboard handling for info overlays.
For info overlays (keyboard/markdown/search help) we now let
the modal portions of the widget have focus, so that you can
page around.  And then tab switching still works with the arrow
keys.
2018-04-04 16:37:39 -07:00
Steve Howell
bd591424e2 Add keydown_util.js module.
This is a pretty thin abstraction to prevent having to put
magic numbers in code, doing the which/keyCode hack, and remembering
to all preventDefault.

Hopefully we'll expand it to handle things like shift/alt keys
for components that want their own keyboard handlers (vs. going
through hotkey.js).
2018-04-04 16:37:39 -07:00
Tim Abbott
c06565d909 users: Improve testing for user_ids_to_users. 2018-04-04 16:31:30 -07:00
novokrest
4d2082ab14 actions.py: Obtain bot profiles by using users.user_ids_to_users().
Remove models.get_user_profiles_by_ids() and
obtain user's bots profiles in actions.get_service_dicts_for_bots() by
users.user_ids_to_users() instead of models.get_user_profiles_by_ids().
Fixes #8939
2018-04-04 16:24:55 -07:00
novokrest
807a6ccf2c users.py: Implement user_ids_to_users() by generic_bulk_cached_fetch().
Optimize users.user_ids_to_users() implementation
by using generic_bulk_cached_fetch() to obtain user profiles
2018-04-04 16:24:55 -07:00
Tim Abbott
53e47e6991 messages: Modify access_message for is_history_public_to_subscribers.
This completes the Message side of #2745.
2018-04-04 16:18:47 -07:00
Tim Abbott
bec71d7a50 messages: Add a server-level setting to control private stream history.
We don't indend for this server-level setting to exist in the long
term; the purpose of this is just to make it easy to test this code
path for development purposes.

This implements much of the Message side part of #2745.
2018-04-04 16:18:46 -07:00
Tim Abbott
228f41e916 messages: Pass UserProfile to is_public_stream_by_name and rename.
The new name can_access_stream_history_by_name gets to the point of
what this function actually does.  And passing in a user object lets
us define what this does based on the user subscribed.
2018-04-04 15:13:11 -07:00
Tim Abbott
5e82d750c5 get_messages: Refactor ok_to_include_history to accept a UserProfile.
If we make history accessible to some stream subscribers of private
streams, we'll need the UserProfile to be available here.
2018-04-04 15:06:53 -07:00
Shubham Dhama
2aaad502b4 org settings: Hide "disable" option when setting already disabled.
Fixes: #8942.
2018-04-04 11:40:35 -07:00
Tim Abbott
721b4e8373 i18n: Fix strings for wildcard mentions.
First, "Notify stream" is a lot clearer than "Notify everyone";
second, these strings should be tagged for translation.
2018-04-04 11:26:36 -07:00
YJDave
aeef925b93 custom fields: Fix error in rendering long textual custom fields.
Currently, long textual fields are rendered as short textual fields
in UI, this bug was introduced because of our recent changes in
custom fields type.
2018-04-04 10:46:18 -07:00
YJDave
8bc181882a custom fields: Remove unused code for custom fields.
It removes code related to custom profile field's placeholder styling
and related to numeric custom fields, as recently we removed support
for numeric custom fields.
2018-04-04 10:46:18 -07:00
Tim Abbott
ecec489e7e docs: Further update the Slack import instructions.
* Removes the confusing notes about supporting only the Slack standard
  plan.
* Removes the confusing notes about importing users from a
  different organization.
* Clarifies a few sections, improves formatting, etc.
2018-04-04 10:38:29 -07:00
Rhea Parekh
48e219e880 docs: Update import data from slack doc. 2018-04-04 10:18:59 -07:00
Rhea Parekh
f4ad464d82 import script: Fix broken links to attachments.
The comments explain this pretty well, but basically because we
rewrite the realm ID during the import process, we need to edit all
the message bodies that link to an attachment to instead link to the
post-processed URL where that file will be hosted on the new server.

Fixes #8926.
2018-04-04 10:05:15 -07:00
Rhea Parekh
5a9cea4134 import script: re map foreign key of UserProfile.last_active_message_id. 2018-04-04 08:53:09 -07:00
Rhea Parekh
ed36314042 import script: Fix 're_map_foreign_keys' logging error. 2018-04-04 08:53:09 -07:00
Rhea Parekh
877c7760b7 import script: re_map Attachment foreign keys. 2018-04-04 08:53:09 -07:00
Eeshan Garg
96e01c0d27 pypi: Upgrade to release 0.4.3. 2018-04-03 18:11:25 -07:00
Balaji2198
591e152e38 org settings: Fix error handling for upload custom emoji. 2018-04-03 13:18:53 -07:00
Tim Abbott
9156591406 docs: Update changelog through current master. 2018-04-03 12:54:04 -07:00
Tim Abbott
682d4f2ea1 casper: Fix expected result for i18n test.
Now that this string is translated into German, the test should expect
it in German.
2018-04-03 11:05:48 -07:00
Aman Agrawal
38829032be docs: Correct broken link on gsoc-ideas.md.
Set correct link for summer-with-zulip.
2018-04-03 10:43:13 -07:00
1158 changed files with 32013 additions and 18206 deletions

View File

@@ -19,12 +19,13 @@ jobs:
dirs=(/srv/zulip-{npm,venv}-cache)
sudo mkdir -p "${dirs[@]}"
sudo chown -R circleci "${dirs[@]}"
- restore_cache:
keys:
- v1-npm-base.trusty.1
- v1-npm-base.trusty-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- restore_cache:
keys:
- v1-venv-base.trusty.1
- v1-venv-base.trusty-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
- run:
name: install dependencies
@@ -50,11 +51,11 @@ jobs:
- save_cache:
paths:
- /srv/zulip-npm-cache
key: v1-npm-base.trusty.1
key: v1-npm-base.trusty-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- save_cache:
paths:
- /srv/zulip-venv-cache
key: v1-venv-base.trusty.1
key: v1-venv-base.trusty-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
# TODO: in Travis we also cache ~/zulip-emoji-cache, ~/node, ~/misc
# The moment of truth! Run the tests.
@@ -99,16 +100,18 @@ jobs:
dirs=(/srv/zulip-{npm,venv}-cache)
sudo mkdir -p "${dirs[@]}"
sudo chown -R circleci "${dirs[@]}"
- restore_cache:
keys:
- v1-npm-base.xenial.1
- v1-npm-base.xenial-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- restore_cache:
keys:
- v1-venv-base.xenial.1
- v1-venv-base.xenial-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
- run:
name: install dependencies
command: |
sudo apt-get update
sudo apt-get install -y moreutils
rm -f /home/circleci/.gitconfig
mispipe "tools/travis/setup-backend" ts
@@ -116,11 +119,11 @@ jobs:
- save_cache:
paths:
- /srv/zulip-npm-cache
key: v1-npm-base.xenial.1
key: v1-npm-base.xenial-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- save_cache:
paths:
- /srv/zulip-venv-cache
key: v1-venv-base.xenial.1
key: v1-venv-base.xenial-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
- run:
name: run backend tests

View File

@@ -14,14 +14,11 @@
"Handlebars": false,
"XDate": false,
"zxcvbn": false,
"LazyLoad": false,
"Dropbox": false,
"SockJS": false,
"marked": false,
"md5": false,
"moment": false,
"i18n": false,
"DynamicText": false,
"LightboxCanvas": false,
"bridge": false,
"page_params": false,
@@ -33,6 +30,7 @@
"server_events": false,
"server_events_dispatch": false,
"message_scroll": false,
"keydown_util": false,
"info_overlay": false,
"ui": false,
"ui_report": false,
@@ -47,11 +45,11 @@
"user_groups": false,
"navigate": false,
"toMarkdown": false,
"settings_toggle": false,
"settings_account": false,
"settings_display": false,
"settings_notifications": false,
"settings_muting": false,
"settings_lab": false,
"settings_bots": false,
"settings_sections": false,
"settings_emoji": false,
@@ -99,6 +97,7 @@
"flatpickr": false,
"pointer": false,
"util": false,
"MessageListData": false,
"MessageListView": false,
"blueslip": false,
"rows": false,
@@ -107,6 +106,7 @@
"Socket": false,
"channel": false,
"components": false,
"scroll_util": false,
"message_viewport": false,
"upload_widget": false,
"avatar": false,
@@ -142,6 +142,10 @@
"tab_bar": false,
"emoji": false,
"presence": false,
"user_search": false,
"buddy_data": false,
"buddy_list": false,
"list_cursor": false,
"activity": false,
"invite": false,
"colorspace": false,
@@ -167,7 +171,7 @@
"emoji_codes": false,
"drafts": false,
"katex": false,
"Clipboard": false,
"ClipboardJS": false,
"emoji_picker": false,
"hotspots": false,
"compose_ui": false,
@@ -202,6 +206,15 @@
"eqeqeq": 2,
"func-style": [ "off", "expression" ],
"guard-for-in": 2,
"indent": ["error", 4, {
"ArrayExpression": "first",
"outerIIFEBody": 0,
"ObjectExpression": "first",
"SwitchCase": 0,
"CallExpression": {"arguments": "first"},
"FunctionExpression": {"parameters": "first"},
"FunctionDeclaration": {"parameters": "first"}
}],
"keyword-spacing": [ "error",
{
"before": true,

1
.gitattributes vendored
View File

@@ -10,4 +10,5 @@
*.png binary
*.otf binary
*.tif binary
*.ogg binary
yarn.lock binary

View File

@@ -6,3 +6,5 @@ known_third_party = django, ujson, sqlalchemy
known_first_party = zerver, zproject, version, confirmation, zilencer, analytics, frontend_tests, scripts, corporate
sections = FUTURE, STDLIB, THIRDPARTY, FIRSTPARTY, LOCALFOLDER
lines_after_imports = 1
# See the comment related to ioloop_logging for why this is skipped.
skip = zerver/management/commands/runtornado.py

View File

@@ -58,7 +58,7 @@ You might be interested in:
* **Running a Zulip server**. Setting up a server takes just a couple of
minutes. Zulip runs on Ubuntu 16.04 Xenial and Ubuntu 14.04 Trusty. The
installation process is
[documented here](https://zulip.readthedocs.io/en/1.7.1/prod.html).
[documented here](https://zulip.readthedocs.io/en/stable/prod.html).
Commercial support is available; see <https://zulipchat.com/plans> for
details.

8
Vagrantfile vendored
View File

@@ -121,15 +121,19 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
end
config.vm.network "forwarded_port", guest: 9991, host: host_port, host_ip: host_ip_addr
config.vm.network "forwarded_port", guest: 9994, host: host_port + 3, host_ip: host_ip_addr
# Specify LXC provider before VirtualBox provider so it's preferred.
config.vm.provider "lxc" do |lxc|
if command? "lxc-ls"
LXC_VERSION = `lxc-ls --version`.strip unless defined? LXC_VERSION
if LXC_VERSION >= "1.1.0"
if LXC_VERSION >= "1.1.0" and LXC_VERSION < "3.0.0"
# Allow start without AppArmor, otherwise Box will not Start on Ubuntu 14.10
# see https://github.com/fgrehm/vagrant-lxc/issues/333
lxc.customize 'aa_allow_incomplete', 1
end
if LXC_VERSION >= "3.0.0"
lxc.customize 'apparmor.allow_incomplete', 1
end
if LXC_VERSION >= "2.0.0"
lxc.backingstore = 'dir'
end
@@ -167,7 +171,7 @@ Welcome to the Zulip development environment! Popular commands:
* tools/lint - Run the linter (quick and catches many problmes)
* tools/test-* - Run tests (use --help to learn about options)
Read https://zulip.readthedocs.io/en/latest/testing.html to learn
Read https://zulip.readthedocs.io/en/latest/testing/testing.html to learn
how to run individual test suites so that you can get a fast debug cycle.
EndOfMessage'

View File

@@ -3,7 +3,7 @@ from collections import OrderedDict, defaultdict
from datetime import datetime, timedelta
import logging
from typing import Any, Callable, Dict, List, \
Optional, Text, Tuple, Type, Union
Optional, Tuple, Type, Union
from django.conf import settings
from django.db import connection, models
@@ -48,7 +48,7 @@ class CountStat:
else: # frequency == CountStat.DAY
self.interval = timedelta(days=1)
def __str__(self) -> Text:
def __str__(self) -> str:
return "<CountStat: %s>" % (self.property,)
class LoggingCountStat(CountStat):

View File

@@ -1,6 +1,6 @@
from datetime import datetime, timedelta
from typing import Any, Dict, List, Mapping, Optional, Text, Type, Union
from typing import Any, Dict, List, Mapping, Optional, Type, Union
from django.core.management.base import BaseCommand
from django.utils.timezone import now as timezone_now
@@ -20,8 +20,8 @@ class Command(BaseCommand):
DAYS_OF_DATA = 100
random_seed = 26
def create_user(self, email: Text,
full_name: Text,
def create_user(self, email: str,
full_name: str,
is_staff: bool,
date_joined: datetime,
realm: Realm) -> UserProfile:

View File

@@ -1,5 +1,5 @@
import datetime
from typing import Any, Dict, Optional, Text, Tuple, Union
from typing import Any, Dict, Optional, Tuple, Union
from django.db import models
@@ -7,7 +7,7 @@ from zerver.lib.timestamp import floor_to_day
from zerver.models import Realm, Recipient, Stream, UserProfile
class FillState(models.Model):
property = models.CharField(max_length=40, unique=True) # type: Text
property = models.CharField(max_length=40, unique=True) # type: str
end_time = models.DateTimeField() # type: datetime.datetime
# Valid states are {DONE, STARTED}
@@ -17,7 +17,7 @@ class FillState(models.Model):
last_modified = models.DateTimeField(auto_now=True) # type: datetime.datetime
def __str__(self) -> Text:
def __str__(self) -> str:
return "<FillState: %s %s %s>" % (self.property, self.end_time, self.state)
# The earliest/starting end_time in FillState
@@ -36,17 +36,17 @@ def last_successful_fill(property: str) -> Optional[datetime.datetime]:
# would only ever make entries here by hand
class Anomaly(models.Model):
info = models.CharField(max_length=1000) # type: Text
info = models.CharField(max_length=1000) # type: str
def __str__(self) -> Text:
def __str__(self) -> str:
return "<Anomaly: %s... %s>" % (self.info, self.id)
class BaseCount(models.Model):
# Note: When inheriting from BaseCount, you may want to rearrange
# the order of the columns in the migration to make sure they
# match how you'd like the table to be arranged.
property = models.CharField(max_length=32) # type: Text
subgroup = models.CharField(max_length=16, null=True) # type: Optional[Text]
property = models.CharField(max_length=32) # type: str
subgroup = models.CharField(max_length=16, null=True) # type: Optional[str]
end_time = models.DateTimeField() # type: datetime.datetime
value = models.BigIntegerField() # type: int
anomaly = models.ForeignKey(Anomaly, on_delete=models.SET_NULL, null=True) # type: Optional[Anomaly]
@@ -59,7 +59,7 @@ class InstallationCount(BaseCount):
class Meta:
unique_together = ("property", "subgroup", "end_time")
def __str__(self) -> Text:
def __str__(self) -> str:
return "<InstallationCount: %s %s %s>" % (self.property, self.subgroup, self.value)
class RealmCount(BaseCount):
@@ -69,7 +69,7 @@ class RealmCount(BaseCount):
unique_together = ("realm", "property", "subgroup", "end_time")
index_together = ["property", "end_time"]
def __str__(self) -> Text:
def __str__(self) -> str:
return "<RealmCount: %s %s %s %s>" % (self.realm, self.property, self.subgroup, self.value)
class UserCount(BaseCount):
@@ -82,7 +82,7 @@ class UserCount(BaseCount):
# aggregating from users to realms
index_together = ["property", "realm", "end_time"]
def __str__(self) -> Text:
def __str__(self) -> str:
return "<UserCount: %s %s %s %s>" % (self.user, self.property, self.subgroup, self.value)
class StreamCount(BaseCount):
@@ -95,6 +95,6 @@ class StreamCount(BaseCount):
# aggregating from streams to realms
index_together = ["property", "realm", "end_time"]
def __str__(self) -> Text:
def __str__(self) -> str:
return "<StreamCount: %s %s %s %s %s>" % (
self.stream, self.property, self.subgroup, self.value, self.id)

View File

@@ -1,6 +1,6 @@
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Text, Tuple, Type, Union
from typing import Any, Dict, List, Optional, Tuple, Type, Union
import ujson
from django.apps import apps
@@ -91,8 +91,8 @@ class AnalyticsTestCase(TestCase):
return Message.objects.create(**kwargs)
# kwargs should only ever be a UserProfile or Stream.
def assertCountEquals(self, table: Type[BaseCount], value: int, property: Optional[Text]=None,
subgroup: Optional[Text]=None, end_time: datetime=TIME_ZERO,
def assertCountEquals(self, table: Type[BaseCount], value: int, property: Optional[str]=None,
subgroup: Optional[str]=None, end_time: datetime=TIME_ZERO,
realm: Optional[Realm]=None, **kwargs: models.Model) -> None:
if property is None:
property = self.current_property

View File

@@ -24,6 +24,24 @@ class TestStatsEndpoint(ZulipTestCase):
# Check that we get something back
self.assert_in_response("Zulip analytics for", result)
def test_stats_for_realm(self) -> None:
user_profile = self.example_user('hamlet')
self.login(user_profile.email)
result = self.client_get('/stats/realm/zulip/')
self.assertEqual(result.status_code, 302)
user_profile = self.example_user('hamlet')
user_profile.is_staff = True
user_profile.save(update_fields=['is_staff'])
result = self.client_get('/stats/realm/not_existing_realm/')
self.assertEqual(result.status_code, 302)
result = self.client_get('/stats/realm/zulip/')
self.assertEqual(result.status_code, 200)
self.assert_in_response("Zulip analytics for", result)
class TestGetChartData(ZulipTestCase):
def setUp(self) -> None:
self.realm = get_realm('zulip')
@@ -233,6 +251,28 @@ class TestGetChartData(ZulipTestCase):
{'chart_name': 'number_of_humans'})
self.assert_json_error_contains(result, 'No analytics data available')
def test_get_chart_data_for_realm(self) -> None:
user_profile = self.example_user('hamlet')
self.login(user_profile.email)
result = self.client_get('/json/analytics/chart_data/realm/zulip/',
{'chart_name': 'number_of_humans'})
self.assert_json_error(result, "Must be an server administrator", 400)
user_profile = self.example_user('hamlet')
user_profile.is_staff = True
user_profile.save(update_fields=['is_staff'])
stat = COUNT_STATS['realm_active_humans::day']
self.insert_data(stat, [None], [])
result = self.client_get('/json/analytics/chart_data/realm/not_existing_realm',
{'chart_name': 'number_of_humans'})
self.assert_json_error(result, 'Invalid organization', 400)
result = self.client_get('/json/analytics/chart_data/realm/zulip',
{'chart_name': 'number_of_humans'})
self.assert_json_success(result)
class TestGetChartDataHelpers(ZulipTestCase):
# last_successful_fill is in analytics/models.py, but get_chart_data is
# the only function that uses it at the moment

View File

@@ -11,6 +11,8 @@ i18n_urlpatterns = [
name='analytics.views.get_realm_activity'),
url(r'^user_activity/(?P<email>[\S]+)/$', analytics.views.get_user_activity,
name='analytics.views.get_user_activity'),
url(r'^stats/realm/(?P<realm_str>[\S]+)/$', analytics.views.stats_for_realm,
name='analytics.views.stats_for_realm'),
# User-visible stats page
url(r'^stats$', analytics.views.stats,
@@ -29,6 +31,8 @@ v1_api_and_json_patterns = [
# get data for the graphs at /stats
url(r'^analytics/chart_data$', rest_dispatch,
{'GET': 'analytics.views.get_chart_data'}),
url(r'^analytics/chart_data/realm/(?P<realm_str>[\S]+)$', rest_dispatch,
{'GET': 'analytics.views.get_chart_data_for_realm'}),
]
i18n_urlpatterns += [

View File

@@ -7,7 +7,7 @@ import time
from collections import defaultdict
from datetime import datetime, timedelta
from typing import Any, Callable, Dict, List, \
Optional, Set, Text, Tuple, Type, Union
Optional, Set, Tuple, Type, Union
import pytz
from django.conf import settings
@@ -26,9 +26,10 @@ from analytics.lib.counts import COUNT_STATS, CountStat, process_count_stat
from analytics.lib.time_utils import time_range
from analytics.models import BaseCount, InstallationCount, \
RealmCount, StreamCount, UserCount, last_successful_fill
from zerver.decorator import require_server_admin, \
from zerver.decorator import require_server_admin, require_server_admin_api, \
to_non_negative_int, to_utc_datetime, zulip_login_required
from zerver.lib.exceptions import JsonableError
from zerver.lib.json_encoder_for_html import JSONEncoderForHTML
from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_success
from zerver.lib.timestamp import ceiling_to_day, \
@@ -36,17 +37,48 @@ from zerver.lib.timestamp import ceiling_to_day, \
from zerver.models import Client, get_realm, Realm, \
UserActivity, UserActivityInterval, UserProfile
@zulip_login_required
def stats(request: HttpRequest) -> HttpResponse:
def render_stats(request: HttpRequest, realm: Realm) -> HttpRequest:
page_params = dict(
is_staff = request.user.is_staff,
stats_realm = realm.string_id,
debug_mode = False,
)
return render(request,
'analytics/stats.html',
context=dict(realm_name = request.user.realm.name))
context=dict(target_realm_name=realm.name,
page_params=JSONEncoderForHTML().encode(page_params)))
@zulip_login_required
def stats(request: HttpRequest) -> HttpResponse:
realm = request.user.realm
return render_stats(request, realm)
@require_server_admin
@has_request_variables
def stats_for_realm(request: HttpRequest, realm_str: str) -> HttpResponse:
realm = get_realm(realm_str)
if realm is None:
return HttpResponseNotFound("Realm %s does not exist" % (realm_str,))
return render_stats(request, realm)
@require_server_admin_api
@has_request_variables
def get_chart_data_for_realm(request: HttpRequest, user_profile: UserProfile,
realm_str: str, **kwargs: Any) -> HttpResponse:
realm = get_realm(realm_str)
if realm is None:
raise JsonableError(_("Invalid organization"))
return get_chart_data(request=request, user_profile=user_profile, realm=realm, **kwargs)
@has_request_variables
def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name: Text=REQ(),
def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name: str=REQ(),
min_length: Optional[int]=REQ(converter=to_non_negative_int, default=None),
start: Optional[datetime]=REQ(converter=to_utc_datetime, default=None),
end: Optional[datetime]=REQ(converter=to_utc_datetime, default=None)) -> HttpResponse:
end: Optional[datetime]=REQ(converter=to_utc_datetime, default=None),
realm: Optional[Realm]=None) -> HttpResponse:
if chart_name == 'number_of_humans':
stat = COUNT_STATS['realm_active_humans::day']
tables = [RealmCount]
@@ -62,10 +94,10 @@ def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name:
elif chart_name == 'messages_sent_by_message_type':
stat = COUNT_STATS['messages_sent:message_type:day']
tables = [RealmCount, UserCount]
subgroup_to_label = {'public_stream': 'Public streams',
'private_stream': 'Private streams',
'private_message': 'Private messages',
'huddle_message': 'Group private messages'}
subgroup_to_label = {'public_stream': _('Public streams'),
'private_stream': _('Private streams'),
'private_message': _('Private messages'),
'huddle_message': _('Group private messages')}
labels_sort_function = lambda data: sort_by_totals(data['realm'])
include_empty_subgroups = True
elif chart_name == 'messages_sent_by_client':
@@ -88,7 +120,8 @@ def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name:
raise JsonableError(_("Start time is later than end time. Start: %(start)s, End: %(end)s") %
{'start': start, 'end': end})
realm = user_profile.realm
if realm is None:
realm = user_profile.realm
if start is None:
start = realm.date_created
if end is None:
@@ -435,6 +468,7 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
# formatting
for row in rows:
row['stats_link'] = realm_stats_link(row['string_id'])
row['string_id'] = realm_activity_link(row['string_id'])
# Count active sites
@@ -456,6 +490,7 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
rows.append(dict(
string_id='Total',
stats_link = '',
date_created_day='',
realm_admin_email='',
dau_count=total_dau_count,
@@ -901,6 +936,12 @@ def realm_activity_link(realm_str: str) -> mark_safe:
realm_link = '<a href="%s">%s</a>' % (url, realm_str)
return mark_safe(realm_link)
def realm_stats_link(realm_str: str) -> mark_safe:
url_name = 'analytics.views.stats_for_realm'
url = reverse(url_name, kwargs=dict(realm_str=realm_str))
stats_link = '<a href="{}"><i class="fa fa-pie-chart"></i></a>'.format(url, realm_str)
return mark_safe(stats_link)
def realm_client_table(user_summaries: Dict[str, Dict[str, Dict[str, Any]]]) -> str:
exclude_keys = [
'internal',
@@ -972,7 +1013,7 @@ def user_activity_summary_table(user_summary: Dict[str, Dict[str, Any]]) -> str:
return make_table(title, cols, rows)
def realm_user_summary_table(all_records: List[QuerySet],
admin_emails: Set[Text]) -> Tuple[Dict[str, Dict[str, Any]], str]:
admin_emails: Set[str]) -> Tuple[Dict[str, Dict[str, Any]], str]:
user_records = {}
def by_email(record: QuerySet) -> str:

View File

@@ -22,7 +22,7 @@ from zerver.models import PreregistrationUser, EmailChangeStatus, MultiuseInvite
UserProfile, Realm
from random import SystemRandom
import string
from typing import Any, Dict, Optional, Text, Union
from typing import Any, Dict, Optional, Union
class ConfirmationKeyException(Exception):
WRONG_LENGTH = 1
@@ -102,7 +102,7 @@ class Confirmation(models.Model):
REALM_CREATION = 7
type = models.PositiveSmallIntegerField() # type: int
def __str__(self) -> Text:
def __str__(self) -> str:
return '<Confirmation: %s>' % (self.content_object,)
class ConfirmationType:
@@ -145,7 +145,7 @@ def validate_key(creation_key: Optional[str]) -> Optional['RealmCreationKey']:
raise RealmCreationKey.Invalid()
return key_record
def generate_realm_creation_url(by_admin: bool=False) -> Text:
def generate_realm_creation_url(by_admin: bool=False) -> str:
key = generate_key()
RealmCreationKey.objects.create(creation_key=key,
date_created=timezone_now(),

6
docs/README.md Normal file
View File

@@ -0,0 +1,6 @@
# Zulip markdown documentation hosted elsewhere
The markdown files in this directory ( /zulip/docs ) are not intended
to be read on GitHub. Instead, visit our
[ReadTheDocs](https://zulip.readthedocs.io/en/latest/index.html) to
read the Zulip documentation.

View File

@@ -149,10 +149,6 @@ Files: static/third/jquery-throttle-debounce/*
Copyright: 2010 "Cowboy" Ben Alman
License: Expat or GPL
Files: src/zulip/static/third/lazyload/*
Copyright: 2011 Ryan Grove
License: Expat
Files: static/third/marked/*
Copyright: 2011-2013, Christopher Jeffrey
License: Expat

View File

@@ -44,7 +44,7 @@ master_doc = 'index'
# General information about the project.
project = 'Zulip'
copyright = '2015-2017, The Zulip Team'
copyright = '2015-2018, The Zulip Team'
author = 'The Zulip Team'
# The version info for the project you're documenting, acts as replacement for
@@ -52,9 +52,9 @@ author = 'The Zulip Team'
# built documents.
#
# The short X.Y version.
version = '1.7+git'
version = '1.8+git'
# The full version, including alpha/beta/rc tags.
release = '1.7.1+git'
release = '1.8.1+git'
# This allows us to insert a warning that appears only on an unreleased
# version, e.g. to say that something is likely to have changed.

View File

@@ -60,7 +60,7 @@ Problems with Zulip's accessibility should be reported as
label. This label can be added by entering the following text in a separate
comment on the issue:
@zulipbot label "accessibility"
@zulipbot label "area: accessibility"
If you want to help make Zulip more accessible, here is a list of the
[currently open accessibility issues][accessibility-issues].

View File

@@ -22,15 +22,11 @@ You can learn more about it at:
* The
[mypy cheat sheet for Python 3](http://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html)
(and its
[python 2 version](https://github.com/python/mypy/blob/master/docs/source/cheat_sheet.rst))
are the best resources for quickly understanding how to write the
PEP 484 type annotations used by mypy correctly.
is the best resource for quickly understanding how to write the PEP
484 type annotations used by mypy correctly.
* The [Python 2 type annotation syntax spec in PEP
484](https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code)
* [Using mypy with Python 2 code](http://mypy.readthedocs.io/en/latest/python2.html)
* The
[Python type annotation spec in PEP 484](https://www.python.org/dev/peps/pep-0484/)
The mypy type checker is run automatically as part of Zulip's Travis
CI testing process in the `backend` build.
@@ -73,13 +69,6 @@ because a list can have many elements, which would make the output too large.
Similarly in dicts, one key's type and the corresponding value's type are printed.
So `{1: 'a', 2: 'b', 3: 'c'}` will be printed as `{int: str, ...}`.
## Zulip goals
Zulip is hoping to reach 100% of the codebase annotated with mypy
static types, and then enforce that it stays that way. Our current
coverage is shown in
[Codecov](https://codecov.io/gh/zulip/zulip).
## Installing mypy
If you installed Zulip's development environment correctly, mypy
@@ -112,31 +101,6 @@ test.py: note: In function "test":
test.py:200: error: Incompatible types in assignment (expression has type "str", variable has type "int")
```
If you need help interpreting or debugging mypy errors, please feel
free to mention @sharmaeklavya2 or @timabbott on your pull request (or
ask in [chat.zulip.org](https://chat.zulip.org)) to get help; we'd love to both
build a great troubleshooting guide in this doc and also help
contribute improvements to error messages upstream.
Since mypy is a new tool under rapid development and occasionally
makes breaking changes, Zulip is using a pinned version of mypy from
its [git repository](https://github.com/python/mypy) rather than
tracking the (older) latest mypy release on PyPI.
## Excluded files
Since several Python files in Zulip's code don't pass mypy's checks
(even for unannotated code) right now, a list of files to be excluded
from the check for CI is present in `tools/run-mypy`.
To run mypy on all Python files, ignoring the exclude list, you can
pass the `--all` option to `tools/run-mypy`.
tools/run-mypy --all
If you type annotate some of those files so that they pass without
errors, please remove them from the exclude list.
## Mypy is there to find bugs in Zulip before they impact users
For the purposes of Zulip development, you can treat `mypy` like a
@@ -164,48 +128,3 @@ developers by opening an issue on [Zulip's GitHub
repository](https://github.com/zulip/zulip/issues) or posting on
[zulip-devel](https://groups.google.com/d/forum/zulip-devel). If it's
indeed a mypy bug, we can help with reporting it upstream.
## Annotating strings
In Python 3, strings can have non-ASCII characters without any problems.
Such characters are required to support languages which use non-latin
scripts like Japanese and Hindi. They are also needed to support special
characters like mathematical symbols, musical symbols, etc.
In Python 2, however, `str` generally doesn't work well with non-ASCII
characters. That's why `unicode` was introduced in Python 2.
But there are problems with the `unicode` and `str` system. Implicit
conversions between `str` and `unicode` use the `ascii` codec, which
fails on strings containing non-ASCII characters. Such errors are hard
to detect by people who always write in English. To minimize such
implicit conversions, we should have a strict separation between `str`
and `unicode` in Python 2. It might seem that using `unicode` everywhere
will solve all problems, but unfortunately it doesn't. This is because
some parts of the standard library and the Python language (like keyword
argument unpacking) insist that parameters passed to them are `str`.
To make our code work correctly in Python 2, we have to identify strings
which contain data which could come from non-ASCII sources like stream
names, people's names, domain names, content of messages, emails, etc.
These strings should be `unicode`. We also have to identify strings
which should be `str` like Exception names, attribute names, parameter
names, etc.
Mypy can help with this. We just have to annotate each string as either
`str` or `unicode` and mypy's static type checking will tell us if we
are incorrectly mixing the two. However, `unicode` is not defined in
Python 3. We want our code to be Python 3 compatible in the future.
This can be achieved using 'typing.Text', a Python 2 and 3 compatibility type.
`typing.Text` is defined as `str` in Python 3 and as `unicode` in
Python 2. We'll be using `Text` (instead of `unicode`) and `str`
to annotate strings in Zulip's code. We follow the style of doing
`from typing import Text` and using `Text` for annotation instead
of doing `import typing` and using `typing.Text` for annotation, because
`Text` is used so extensively for type annotations that we don't
need to be that verbose.
Sometimes you'll find that you have to convert strings from one type to
another. `zerver/lib/str_utils.py` has utility functions to help with that.
It also has documentation (in docstrings) which explains the right way
to use them.

View File

@@ -13,8 +13,9 @@ internet connection throughout the entire installation processes.** You can
## Recommended setup (Vagrant)
**For first-time contributors on macOS, Windows, and Ubuntu, we recommend using
the [Vagrant development environment][install-vagrant]**.
**For first-time contributors on macOS, Windows, and most Debian-based distros
(like Ubuntu), we recommend using the [Vagrant development
environment][install-vagrant]**.
This method creates a virtual machine (for Windows and macOS) or a Linux
container (for Ubuntu) inside which the Zulip server and all related services
@@ -29,8 +30,9 @@ want to or can't use Vagrant, Zulip supports a wide range of ways to install
the Zulip development environment on **macOS and Linux (Ubuntu
recommended)**:
* On **Ubuntu** 16.04 Xenial and 14.04 Trusty, you can easily **[install
without using Vagrant][install-direct]**.
* On **Ubuntu** 16.04 Xenial and 14.04 Trusty and **Debian** 9
Stretch, you can easily
**[install without using Vagrant][install-direct]**.
* On **other Linux** distributions, you'll need to follow slightly different
instructions to **[install manually][install-generic]**.
* On **macOS and Linux** (Ubuntu recommended), you can install **[using

View File

@@ -2,24 +2,25 @@
Contents:
* [Installing directly on Ubuntu](#installing-directly-on-ubuntu)
* [Installing directly on Ubuntu or Debian](#installing-directly-on-ubuntu-or-debian)
* [Installing manually on Linux](#installing-manually-on-linux)
* [Installing directly on cloud9](#installing-on-cloud9)
* [Using Docker (experimental)](#using-docker-experimental)
## Installing directly on Ubuntu
## Installing directly on Ubuntu or Debian
Start by [cloning your fork of the Zulip repository][zulip-rtd-git-cloning]
and [connecting the Zulip upstream repository][zulip-rtd-git-connect]:
```
git clone --config pull.rebase https://github.com/YOURUSERNAME/zulip.git
cd zulip
git remote add -f upstream https://github.com/zulip/zulip.git
```
If you'd like to install a Zulip development environment on a computer
that's already running Ubuntu 16.04 Xenial or Ubuntu 14.04 Trusty, you
can do that by just running:
that's already running Ubuntu 16.04 Xenial, Ubuntu 14.04 Trusty, or
Debian 9 Stretch, you can do that by just running:
```
# From a clone of zulip.git
@@ -91,6 +92,8 @@ sudo apt-get install postgresql-9.3-pgroonga
sudo apt-get install postgresql-9.5-pgroonga
# On 17.04 or 17.10
sudo apt-get install postgresql-9.6-pgroonga
# On 18.04
sudo apt-get install postgresql-10-pgroonga
# If using Debian, follow the instructions here: http://pgroonga.github.io/install/debian.html
@@ -103,6 +106,8 @@ sudo apt-get update
sudo apt-get install postgresql-9.3-tsearch-extras
# On 16.04
sudo apt-get install postgresql-9.5-tsearch-extras
# On 18.04
sudo apt-get install postgresql-10-tsearch-extras
# Otherwise, you can download a .deb directly
@@ -118,9 +123,14 @@ sudo dpkg -i postgresql-9.3-tsearch-extras_0.1.3_amd64.deb
wget https://dl.dropboxusercontent.com/u/283158365/zuliposs/postgresql-9.4-tsearch-extras_0.1_amd64.deb
sudo dpkg -i postgresql-9.4-tsearch-extras_0.1_amd64.deb
# If on 16.04 or stretch
wget https://launchpad.net/~tabbott/+archive/ubuntu/zulip/+files/postgresql-9.5-tsearch-extras_0.2_amd64.deb
sudo dpkg -i postgresql-9.5-tsearch-extras_0.3_amd64.deb
# If on 16.04
wget https://launchpad.net/~tabbott/+archive/ubuntu/zulip/+files/postgresql-9.5-tsearch-extras_0.4_amd64.deb
sudo dpkg -i postgresql-9.5-tsearch-extras_0.4_amd64.deb
# If on Stretch
wget --content-disposition \
https://packagecloud.io/zulip/server/packages/debian/stretch/postgresql-9.6-tsearch-extras_0.4_amd64.deb/download.deb
sudo dpkg -i postgresql-9.6-tsearch-extras_0.4_amd64.deb
```
Alternatively, you can always build the package from [tsearch-extras

View File

@@ -1,7 +1,7 @@
## Vagrant environment setup tutorial
This section guides first-time contributors through installing the
Zulip development environment on Windows, macOS, and Ubuntu.
Zulip development environment on Windows, macOS, Ubuntu and Debian.
The recommended method for installing the Zulip development environment is to use
Vagrant with VirtualBox on Windows and macOS, and Vagrant with LXC on

View File

@@ -7,111 +7,95 @@ All notable changes to the Zulip server are documented in this file.
This section lists notable unreleased changes; it is generally updated
in bursts.
**Highlights:**
### 1.8.1 -- 2018-05-07
- Added a user setting to choose the emoji set used in Zulip: Google,
Twitter, Apple, or Emoji One.
- Added a video call integration powered by Jitsi.
- Added an automated tool (`manage.py register_server`) to sign up for
the [mobile push notifications service](../production/mobile-push-notifications.html).
- Improved rendering of block quotes in mobile push notifications.
- Improved some installer error messages.
- Fixed several minor bugs with the new Slack import feature.
- Fixed several visual bugs with the new compose input pills.
- Fixed several minor visual bugs with night mode.
- Fixed bug with visual clipping of "g" in the left sidebar.
- Fixed an issue with the LDAP backend users' Organization Unit (OU)
being cached, resulting in trouble logging in after a user was moved
between OUs.
- Fixed a couple subtle bugs with muting.
### 1.8.0 -- 2018-04-17
**Highlights:**
- Dramatically simplified the server installation process; it's now possible
to install Zulip without first setting up outgoing email.
- Added certbot support to the installer for getting certificates.
- Added support for mentioning groups of users.
- Added experimental support for importing an organization's history
from Slack.
- Added a new "night mode" theme for dark environments.
- Added experimental support for importing an organization from Slack.
- Overhauled our settings system to eliminate the ugly "save changes"
button system.
- Rewrote our API documentation to be much more friendly and
expansive; it now covers most important endpoints, with nice examples.
- Added a video call integration powered by Jitsi.
- Lots of visual polish improvements.
- Countless small bugfixes both in the backend and the UI.
**Security and privacy:**
- Several important security fixes since 1.7.0, which were released
already in 1.7.1 and 1.7.2.
- The security model for private streams has changed. Now
organization administrators can remove users, edit descriptions, and
rename private streams they are not subscribed to. See Zulip's
security model documentation for details.
- Lots of visual polish improvements.
**Full feature changelog:**
- New integrations: ErrBot, GoCD, Google Code-In, Opbeat, Groove, Raygun,
Insping, Dropbox, Front, Intercom, Statuspage.io, Flock and Beeminder.
- The local uploads backend now does the same security checks that the
S3 backend did before serving files to users.
- Added support for users in multiple realms having the same email.
- Added support for embedded interactive bots.
- Added inline preview + player for Vimeo videos.
- Added a setting to allow users to delete their messages.
- On Xenial, the local uploads backend now does the same security
checks that the S3 backend did before serving files to users.
Ubuntu Trusty's version of nginx is too old to support this and so
the legacy model is the default; we recommend upgrading.
- Added an organization setting to limit creation of bots.
- Refactored the authentication backends codebase to be much easier to
verify.
- Added a user setting to control whether email notifications include
message content (or just the fact that there are new messages).
**Visual and UI:**
- Added a user setting to translate emoticons/smileys to emoji.
- Added a user setting to choose the emoji set used in Zulip: Google,
Twitter, Apple, or Emoji One.
- Expanded setting for displaying emoji as text to cover all display
settings (previously only affected reactions).
- Overhauled our settings system to eliminate the old "save changes"
button system.
- Redesigned the "uploaded files" UI.
- Redesigned the "account settings" UI.
- Redesigned error pages for the various email confirmation flows.
- Our emoji now display at full resolution on retina displays.
- Improved placement of text when inserting emoji via picker.
- Improved the descriptions and UI for many settings.
- Improved visual design of the help center (/help/).
**Core chat experience:**
- Added support for mentioning groups of users.
- Added a setting to allow users to delete their messages.
- Added support for uploading files in the message-edit UI.
- Added new event types to several webhook integrations.
- Added a display for whether the user is logged-in in logged-out
pages.
- Added support for hosting multiple domains, not all as subdomains of
the same base domain.
- Added a new /team/ page explaining the team, with a nice
visualization of our contributors.
- Added support for default bots to receive messages when they're
mentioned, even if they are not subscribed.
- Added support for inviting a new user as an administrator.
- Added a new organization settings page for managing invites.
- Added a user setting to control whether the organization's name is
included in email subject lines.
- Added support for clicking on a mention to see a user's profile.
- Added new compose features for pasting HTML.
- Redesigned the compose are for private messages to use pretty pills
rather than raw email addresses to display recipients.
- Added new ctrl+B, ctrl+I, ctrl+L compose shortcuts for inserting
common syntax.
- Added warning when linking to a private stream via typeahead.
- Added rate-limiting on inviting users to join a realm (prevents spam).
- Added support for automatically-numbered markdown lists.
- Added a big warning when posting to #announce.
- Added a user setting to control whether email notifications include
message content (or just the fact that there are new messages).
- Added a notification when drafts are saved, to make them more
discoverable.
discoverable.
- Added a fast local echo to emoji reactions.
- Added new "basics" section to keyboard shortcuts documentation.
- Added a new ">" keyboard shortcut for quote-and-reply.
- Added a new "p" keyboard shortcut to just to next unread PM thread.
- Added support for overriding the topic is all incoming webhook integrations.
- Added a new nagios check for the Zulip analytics state.
- Added a menu item to mark all messages as read.
- Added support for logging into the mobile apps with RemoteUserBackend.
- Added an organization setting to disable welcome emails to new users.
- Added traffic statistics (messages/week) to the "Manage streams" UI.
- Added a display setting to translate emoticons/smileys to emoji.
- Added an organization setting to ban disposable email addresses
(I.e.. those from sites like mailinator.com).
- Added a server setting to control whether digest emails are sent.
- Links to logged-in content in Zulip now take the user to the
appropriate upload or view after a user logs in.
- Incoming webhooks now send a private message to the bot owner for
more convenient testing.
- Rewrote documentation for many integrations to use a cleaner
numbered-list format.
- Renamed "Home" to "All messages", to avoid users clicking on it too
early in using Zulip.
- Messages containing just a link to an image (or an uploaded image)
now don't clutter the feed with the URL: we just display the image.
- Refactored the authentication backends codebase to be much easier to
verify.
- Expanded setting for displaying emoji as text to cover all display
settings (previously only affected reactions).
- Redesigned the API for emoji reactions to support the full range of
how emoji reactions are used.
- Migrated the codebase to use the nice Python 3 typing syntax.
- Optimized how user avatar URLs are transmitted over the wire.
- Optimized message sending performance a bit more.
- Split the Notifications Stream setting in two settings, one for new
users, the other for new streams.
- Fixed numerous issues in the "stream settings" UI.
- Fixed most of the known (mostly obscure) bugs in how messages are
formatted in Zulip.
- Fixed "more topics" to correctly display all historical topics for
public streams, even though from before a user subscribed.
- Fixed several bugs around interacting with deactivated users.
- Added a menu item to mark all messages as read.
- Fixed image upload file pickers offering non-image files.
- Fixed some subtle bugs with full-text search and unicode.
- Fixed bugs in the "edit history" HTML rendering process.
- Fixed several hotkeys scope bugs.
- Fixed popovers being closed when new messages come in.
- Fixed unexpected code blocks when using the email mirror.
- Fixed clicking on links to a narrow opening a new window.
@@ -119,47 +103,132 @@ discoverable.
- Fixed layering issues with mobile Safari.
- Fixed several obscure real-time synchronization bugs.
- Fixed handling of messages with a very large HTML rendering.
- Fixed buggy APNs logic that could cause extra exception emails.
- Fixed several bugs around interacting with deactivated users.
- Fixed interaction bugs with unread counts and deleting messages.
- Fixed support for replacing deactivated custom emoji.
- Fixed a missing dependency for the localhost_sso auth backend.
- Fixed uploading user avatars encoded using the CMYK mode.
- Fixed scrolling downwards in narrows.
- Fixed numerous subtle bugs with the stream creation UI.
- Dramatically improved organization of developer docs.
- Statistics on translation percentages now include mobile apps.
- Optimized how user avatar URLs are transmitted over the wire.
- Optimized message sending performance a bit more.
- Fixed a subtle and hard-to-reproduce bug that resulted in every
message being condensed ([More] appearing on every message).
- Improved typeahead's handling of editing an already-completed mention.
- Improved syntax for inline LaTeX to be more convenient.
- Improve keyboard navigation of left and right sidebars with arrow keys.
- Changes the URL scheme for stream narrows to encode the stream ID,
so that they can be robust to streams being renamed. The change is
backwards-compatible; existing narrow URLs still work.
- APIs for fetching messages now provide more metadata to help clients.
- Clarified instructions for server settings (especially LDAP auth).
- Redesigned the "uploaded files" UI.
- Redesigned the "account settings" UI.
- Redesigned error pages for the various email confirmation flows.
- Added missing information on requesting user in many exception emails.
- Our emoji now display at full resolution on retina displays.
- Improved placement of text when inserting emoji via picker.
- Improved the password reset flow to be less confusing if you don't
have an account.
- Improved syntax for permanent links to streams in Zulip.
- Improved behavior of copy-pasting a large number of messages.
- Improved Tornado retry logic for connecting to RabbitMQ.
- Improved the descriptions and UI for many settings.
- Improved handling of browser undo in compose.
- Improved mobile notifications to support narrowing when one click a
mobile push notification.
- Improved visual design of the help center (/help/).
- Improved saved drafts system to garbage-collect old drafts and sort
by last modification, not creation.
- Removed the legacy "Zulip labs" autoscroll_forever setting. It was
enabled mostly by accident.
- Removed some long-deprecated markdown syntax for mentions.
- Added support for clicking on a mention to see a user's profile.
- Links to logged-in content in Zulip now take the user to the
appropriate upload or view after a user logs in.
- Renamed "Home" to "All messages", to avoid users clicking on it too
early in using Zulip.
- Added a user setting to control whether the organization's name is
included in email subject lines.
- Fixed uploading user avatars encoded using the CMYK mode.
**User accounts and invites:**
- Added support for users in multiple realms having the same email.
- Added a display for whether the user is logged-in in logged-out
pages.
- Added support for inviting a new user as an administrator.
- Added a new organization settings page for managing invites.
- Added rate-limiting on inviting users to join a realm (prevents spam).
- Added an organization setting to disable welcome emails to new users.
- Added an organization setting to ban disposable email addresses
(I.e.. those from sites like mailinator.com).
- Improved the password reset flow to be less confusing if you don't
have an account.
- Split the Notifications Stream setting in two settings, one for new
users, the other for new streams.
**Stream subscriptions and settings:**
- Added traffic statistics (messages/week) to the "Manage streams" UI.
- Fixed numerous issues in the "stream settings" UI.
- Fixed numerous subtle bugs with the stream creation UI.
- Changes the URL scheme for stream narrows to encode the stream ID,
so that they can be robust to streams being renamed. The change is
backwards-compatible; existing narrow URLs still work.
**API, bots, and integrations:**
- Rewrote our API documentation to be much more friendly and
expansive; it now covers most important endpoints, with nice examples.
- New integrations: ErrBot, GoCD, Google Code-In, Opbeat, Groove,
Raygun, Insping, Dialogflow, Dropbox, Front, Intercom,
Statuspage.io, Flock and Beeminder.
- Added support for embedded interactive bots.
- Added inline preview + player for Vimeo videos.
- Added new event types and fixed bugs in several webhook integrations.
- Added support for default bots to receive messages when they're
mentioned, even if they are not subscribed.
- Added support for overriding the topic is all incoming webhook integrations.
- Incoming webhooks now send a private message to the bot owner for
more convenient testing if a stream is not specified.
- Rewrote documentation for many integrations to use a cleaner
numbered-list format.
- APIs for fetching messages now provide more metadata to help clients.
**Keyboard shortcuts:**
- Added new "basics" section to keyboard shortcuts documentation.
- Added a new ">" keyboard shortcut for quote-and-reply.
- Added a new "p" keyboard shortcut to jump to next unread PM thread.
- Fixed several hotkeys scope bugs.
- Changed the hotkey for compose-private-message from "C" to "x".
- Improve keyboard navigation of left and right sidebars with arrow keys.
**Mobile apps backend:**
- Added support for logging into the mobile apps with RemoteUserBackend.
- Improved mobile notifications to support narrowing when one clicks a
mobile push notification.
- Statistics on the fraction of strings that are translated now
include strings in the mobile apps as well.
**For server admins:**
- Added certbot support to the installer for getting certificates.
- Added support for hosting multiple domains, not all as subdomains of
the same base domain.
- Added a new nagios check for the Zulip analytics state.
- Fixed buggy APNs logic that could cause extra exception emails.
- Fixed a missing dependency for the localhost_sso auth backend.
- Fixed subtle bugs in garbage-collection of old node_modules versions.
- Clarified instructions for server settings (especially LDAP auth).
- Added missing information on requesting user in many exception emails.
- Improved Tornado retry logic for connecting to RabbitMQ.
- Added a server setting to control whether digest emails are sent.
**For Zulip developers:**
- Migrated the codebase to use the nice Python 3 typing syntax.
- Added a new /team/ page explaining the team, with a nice
visualization of our contributors.
- Dramatically improved organization of developer docs.
- Backend test coverage is now 95%.
- Countless other little bug fixes both in the backend and the UI.
### 1.7.2 -- 2018-04-12
This is a security release, with a handful of cherry-picked changes
since 1.7.1. All Zulip server admins are encouraged to upgrade
promptly.
- CVE-2018-9986: Fix XSS issues with frontend markdown processor.
- CVE-2018-9987: Fix XSS issue with muting notifications.
- CVE-2018-9990: Fix XSS issue with stream names in topic typeahead.
- CVE-2018-9999: Fix XSS issue with user uploads. The fix for this
adds a Content-Security-Policy for the `LOCAL_UPLOADS_DIR` storage
backend for user-uploaded files.
Thanks to Suhas Sunil Gaikwad for reporting CVE-2018-9987 and w2w for
reporting CVE-2018-9986 and CVE-2018-9990.
### 1.7.1 -- 2017-11-21

View File

@@ -46,7 +46,7 @@ See [our docs](../subsystems/html-templates.html) for details on Zulip's
templating systems.
* `templates/zerver/` For [Jinja2](http://jinja.pocoo.org/) templates
for the backend (for zerver app).
for the backend (for zerver app; logged-in content is in `templates/zerver/app`).
* `static/templates/` [Handlebars](http://handlebarsjs.com/) templates for the frontend.

View File

@@ -61,7 +61,7 @@ contributors to the project today).
### Expectations for GSoC students
[Our guide for having a great summer with Zulip](../contributing/summer-with-zulip)
[Our guide for having a great summer with Zulip](../contributing/summer-with-zulip.html)
is focused on what one should know once doing a summer project with
Zulip. But it has a lot of useful advice on how we expect students to
interact, above and beyond what is discussed in Google's materials.

View File

@@ -1,30 +1,58 @@
# Outgoing email
This page documents everything you need to know about setting up
outgoing email in a Zulip production environment. It's pretty simple
if you already have an outgoing SMTP provider; just start reading from
[the configuration section](#configuration).
Zulip needs to be able to send email so it can confirm new users'
email addresses and send notifications.
## How to configure
1. Identify an outgoing email (SMTP) account where you can have Zulip
send mail. If you don't already have one you want to use, see
[Email services](#email-services) below.
2. Fill out the section of `/etc/zulip/settings.py` headed "Outgoing
email (SMTP) settings". This includes the hostname and typically
the port to reach your SMTP provider, and the username to log into
it as.
3. Put the password for the SMTP user account in
`/etc/zulip/zulip-secrets.conf` by setting `email_password`. For
example: `email_password = abcd1234`.
Like any other change to the Zulip configuration, be sure to
[restart the server](settings.html) to make your changes take
effect.
4. Test that your configuration is working. See the test command in
the [Troubleshooting](#troubleshooting) section below. If it's not
working, see the suggestions in that section.
## Email services
### Free outgoing email services
For sending outgoing email from your Zulip server, we highly recommend
using a "transactional email" service like
[Mailgun](https://documentation.mailgun.com/en/latest/quickstart-sending.html#send-via-smtp)
or for AWS users,
[SendGrid](https://sendgrid.com/docs/API_Reference/SMTP_API/integrating_with_the_smtp_api.html),
[Mailgun](https://documentation.mailgun.com/en/latest/quickstart-sending.html#send-via-smtp),
or, for AWS users,
[Amazon SES](http://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-email-smtp.html).
These services are designed to send email from servers, and are by far
the easiest way to get outgoing email working reliably.
If you don't have an existing outgoing SMTP provider, don't worry!
Both of the options we recommend above (as well as dozens of other
services) have free options; we recommend Mailgun as the easiest to
get setup with. Once you've signed up, you'll want to find the
service's provided "SMTP credentials", and configure Zulip as follows:
Each of the options we recommend above (as well as dozens of other
services) have free options. Once you've signed up, you'll want to
find the service's provided "SMTP credentials", and configure Zulip as
follows:
* The hostname as `EMAIL_HOST = 'smtp.mailgun.org'` in `/etc/zulip/settings.py`
* The username as `EMAIL_HOST_USER = 'username@example.com` in
* The hostname like `EMAIL_HOST = 'smtp.mailgun.org'` in `/etc/zulip/settings.py`
* The username like `EMAIL_HOST_USER = 'username@example.com` in
`/etc/zulip/settings.py`.
* The password as `email_password = abcd1234` in `/etc/zulip/zulip-secrets.conf`.
* The TLS setting as `EMAIL_USE_TLS = True` in
`/etc/zulip/settings.py`, for most providers
* The port as `EMAIL_PORT = 587` in `/etc/zulip/settings.py`, for most
providers
* The password like `email_password = abcd1234` in `/etc/zulip/zulip-secrets.conf`.
### Using Gmail for outgoing email
@@ -44,38 +72,28 @@ how to make it work:
* Note also that the rate limits for Gmail are also quite low
(e.g. 100 / day), so it's easy to get rate-limited if your server
has significant traffic. For more active servers, we recommend
moving to a free account from a transaction email service.
moving to a free account on a transactional email service.
### Logging outgoing email to a file for prototyping
If for prototyping, you don't want to bother setting up an email
provider, you can add to `/etc/zulip/settings.py` the following:
For prototyping, you might want to proceed without setting up an email
provider. If you want to see the emails Zulip would have sent, you
can log them to a file instead.
To do so, add these lines to `/etc/zulip/settings.py`:
```
EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_PATH = '/var/log/zulip/emails'
```
Outgoing emails that Zulip would have sent will just be written to
files in `/var/log/zulip/emails/`. This is enough to get you through
initial user registration without an SMTP provider.
Then outgoing emails that Zulip would have sent will just be written
to files in `/var/log/zulip/emails/`.
Remember to delete this configuration and restart the server if you
later setup a real SMTP provider!
Remember to delete this configuration (and restart the server) if you
later set up a real SMTP provider!
### Configuration
To configure outgoing SMTP, you will need to complete the following steps:
1. Fill out the outgoing email sending configuration block in
`/etc/zulip/settings.py`, including `EMAIL_HOST`, and
`EMAIL_HOST_USER`. You may also need to set `EMAIL_PORT` if your
provider doesn't use the standard SMTP submission port (587).
2. Put the SMTP password for `EMAIL_HOST_USER` in
`/etc/zulip/zulip-secrets.conf` as `email_password = yourPassword`.
#### Testing and troubleshooting
## Troubleshooting
You can quickly test your outgoing email configuration using:
@@ -87,42 +105,50 @@ su zulip
If it doesn't throw an error, it probably worked; you can confirm by
checking your email.
It's important to test, because outgoing email often doesn't work the
first time. Common causes of failures are:
If it doesn't work, check these common failure causes:
* Your hosting provider blocking outgoing SMTP traffic in its
default firewall rules. Check whether `EMAIL_PORT` is blocked in your
hosting provider's firewall.
* Forgetting to put the password in `/etc/zulip/zulip-secrets.conf`.
* Typos in transcribing the username or password.
* Your hosting provider may block outgoing SMTP traffic in its default
firewall rules. Check whether the port `EMAIL_PORT` is blocked in
your hosting provider's firewall.
Once you have it working from the management command, remember to
restart your Zulip server using
`/home/zulip/deployments/current/scripts/restart-server` so that the running
server is using the latest configuration.
* Make sure you set the password in `/etc/zulip/zulip-secrets.conf`.
#### Advanced troubleshooting
* Check the username and password for typos.
* Be sure to restart your Zulip server after editing either
`settings.py` or `zulip-secrets.conf`, using
`/home/zulip/deployments/current/scripts/restart-server` .
Note that the `manage.py` command above will read the latest
configuration from the config files, even if the server is still
running with an old configuration.
### Advanced troubleshooting
Here are a few final notes on what to look at when debugging why you
aren't receiving emails from Zulip:
* Most transactional email services have an "outgoing email" log where
you can inspect the emails that reached the service, whether it was
flagged as spam, etc.
you can inspect the emails that reached the service, whether an
email was flagged as spam, etc.
* Starting with Zulip 1.7, Zulip logs an entry in
`/var/log/zulip/send_email.log` whenever it attempts to send an
email, including whether the request succeeded or failed.
email. The log entry includes whether the request succeeded or failed.
* If attempting to send an email throws an exception, a traceback
should be in `/var/log/zulip/errors.log`, along with any other
exceptions Zulip encounters.
* Zulip's email sending configuration is based on the standard Django
[SMTP backend](https://docs.djangoproject.com/en/1.10/topics/email/#smtp-backend)
[SMTP backend](https://docs.djangoproject.com/en/2.0/topics/email/#smtp-backend)
configuration. So if you're having trouble getting your email
provider working, you may want to search for documentation related
to using your email provider with Django. The one thing we've
changed from the defaults is reading the email password from the
`email_password` entry in the Zulip secrets file, as part of our
policy of not having any secret information in the
`/etc/zulip/settings.py` file. In other words, if Django
documentation references setting `EMAIL_HOST_PASSWORD`, you should
instead set `email_password` in `/etc/zulip/zulip-secrets.conf`.
to using your email provider with Django.
The one thing we've changed from the Django defaults is that we read
the email password from the `email_password` entry in the Zulip
secrets file, as part of our policy of not having any secret
information in the `/etc/zulip/settings.py` file. In other words,
if Django documentation references setting `EMAIL_HOST_PASSWORD`,
you should instead set `email_password` in
`/etc/zulip/zulip-secrets.conf`.

View File

@@ -16,7 +16,7 @@ recommended, and you may break your server. Make sure you have backups
and a provisioning script ready to go to wipe and restore your
existing services if (when) your server goes down.
These instructions are only for experts. If you're not an experiecned
These instructions are only for experts. If you're not an experienced
Linux sysadmin, you will have a much better experience if you get a
dedicated VM to install Zulip on instead (or [use zulipchat.com](https://zulipchat.com).

View File

@@ -18,28 +18,28 @@ support forwarding push notifications to a central push notification
forwarding service. You can enable this for your Zulip server as
follows:
1. First, contact support@zulipchat.com with the `zulip_org_id` and
`zulip_org_key` values from your `/etc/zulip/zulip-secrets.conf` file, as
well as a hostname and contact email address you'd like us to use in case
of any issues (we hope to have a nice web flow available for this soon).
2. We'll enable push notifications for your server on our end. Look for a
reply from Zulipchat support within 24 hours.
3. Uncomment the `PUSH_NOTIFICATION_BOUNCER_URL = "https://push.zulipchat.com"`
line in your `/etc/zulip/settings.py` file, and
1. Uncomment the `PUSH_NOTIFICATION_BOUNCER_URL =
'https://push.zulipchat.com'` line in your `/etc/zulip/settings.py`
file (i.e. remove the `#` at the start of the line), and
[restart your Zulip server](../production/maintain-secure-upgrade.html#updating-settings).
Note that if you installed Zulip older than 1.6, you'll need to add
the line (it won't be there to uncomment).
If you installed your Zulip server with a version older than 1.6,
you'll need to add the line (it won't be there to uncomment).
4. If you or your users have already set up the Zulip mobile app,
1. If you're running Zulip 1.8.1 or newer, you can run `manage.py
register_server` from `/home/zulip/deployments/current`. This
command will print the registration data it would send to the
mobile push notifications service, ask you to accept the terms of
service, and if you accept, register your server. Otherwise, see
the [legacy signup instructions](#legacy-signup).
1. If you or your users have already set up the Zulip mobile app,
you'll each need to log out and log back in again in order to start
getting push notifications.
That should be all you need to do!
Congratulations! You've successful setup the service.
If you'd like to verify the full pipeline, you can do the following.
Please follow the instructions carefully:
If you'd like to verify that everything is working, you can do the
following. Please follow the instructions carefully:
* [Configure mobile push notifications to always be sent][notification-settings]
(normally they're only sent if you're idle, which isn't ideal for
@@ -57,9 +57,19 @@ in the Android notification area.
[notification-settings]: https://zulipchat.com/help/configure-mobile-notifications
Note that use of the push notification bouncer is subject to the
[Zulipchat Terms of Service](https://zulipchat.com/terms/). By using push
notifications, you agree to those terms.
## Updating your server's registration
Your server's registration includes the server's hostname and contact
email address (from `EXTERNAL_HOST` and `ZULIP_ADMINISTRATOR` in
`/etc/zulip/settings.py`, aka the `--hostname` and `--email` options
in the installer). You can update your server's registration data by
running `manage.py register_server` again.
If you'd like to rotate your server's API key for this service
(`zulip_org_key`), you need to use `manage.py register_server
--rotate-key` option; it will automatically generate a new
`zulip_org_key` and store that new key in
`/etc/zulip/zulip-secrets.conf`.
## Why this is necessary
@@ -77,11 +87,22 @@ notification forwarding service, which allows registered Zulip servers
to send push notifications to the Zulip app indirectly (through the
forwarding service).
## Security and privacy implications
## Security and privacy
Use of the push notification bouncer is subject to the
[Zulipchat Terms of Service](https://zulipchat.com/terms/). By using
push notifications, you agree to those terms.
We've designed this push notification bouncer service with security
and privacy in mind:
* A central design goal of the the Push Notification Service is to
avoid any message content being stored or logged by the service,
even in error cases. We store only the necessary metadata for
delivering the notifications. This includes the tokens needed to
push notifications to the devices, and user ID numbers generated by
your Zulip server. These user ID numbers are are opaque to the Push
Notification Service, since it has no other data about those users.
* All of the network requests (both from Zulip servers to the Push
Notification Service and from the Push Notification Service to the
relevant Google and Apple services) are encrypted over the wire with
@@ -89,17 +110,69 @@ and privacy in mind:
* The code for the push notification forwarding service is 100% open
source and available as part of the
[Zulip server project on GitHub](https://github.com/zulip/zulip).
The Push Notification Service is designed to avoid any message
content being stored or logged, even in error cases.
* The push notification forwarding servers are professionally managed
by a small team of security experts.
* There's a `PUSH_NOTIFICATION_REDACT_CONTENT` setting available to
disable any message content being sent via the push notification
bouncer (i.e. message content will be replaced with
`***REDACTED***`). Note that this setting makes push notifications
significantly less usable. We plan to
by a small team of security expert engineers.
* If you'd like an extra layer of protection, there's a
`PUSH_NOTIFICATION_REDACT_CONTENT` setting available to disable any
message content being sent via the push notification bouncer
(i.e. message content will be replaced with `***REDACTED***`). Note
that this setting makes push notifications significantly less
usable. We plan to
[replace this feature with end-to-end encryption](https://github.com/zulip/zulip/issues/6954)
which would eliminate that usability tradeoff.
If you have any questions about the security model, contact
support@zulipchat.com.
## Legacy signup
Here are the legacy instructions for signing a server up for push
notifications:
1. First, contact support@zulipchat.com with the `zulip_org_id` and
`zulip_org_key` values from your `/etc/zulip/zulip-secrets.conf` file, as
well as a `hostname` and `contact email` address you'd like us to use in case
of any issues (we hope to have a nice web flow available for this soon).
2. We'll enable push notifications for your server on our end. Look for a
reply from Zulipchat support within 24 hours.
## Sending push notifications directly from your server
As we discussed above, it is impossible for a single app in their
stores to receive push notifications from multiple, mutually
untrusted, servers. The Mobile Push Notification Service is one of
the possible solutions to this problem. The other possible solution
is for an individual Zulip server's administrators to build and
distribute their own copy of the Zulip mobile apps, hardcoding a key
that they possess.
This solution is possible with Zulip, but it requires the server
administrators to publish their own copies of
the Zulip mobile apps (and there's nothing the Zulip team can do to
eliminate this onorous requirement).
The main work is distributing your own copies of the Zulip mobile apps
configured to use APNS/GCM keys that you generate. This is not for
the faint of heart! If you haven't done this before, be warned that
one can easily spend hundreds of dollars (on things like a DUNS number
registration) and a week struggling through the hoops Apple requires
to build and distribute an app through the Apple app store, even if
you're making no code modifications to an app already present in the
store (as would be the case here). The Zulip mobile app also gets
frequent updates that you will have to either forgo or republish to
the app stores yourself.
If you've done that work, the Zulip server configuration for sending
push notifications through the new app is quite straightforward:
* Create a
[GCM push notifications](https://developers.google.com/cloud-messaging/android/client)
key in the Google Developer console and set `android_gcm_api_key` in
`/etc/zulip/zulip-secrets.conf` to that key.
* Register for a
[mobile push notification certificate][apple-docs]
from Apple's developer console. Set `APNS_SANDBOX=False` and
`APNS_CERT_FILE` to be the path of your APNS certificate file in
`/etc/zulip/settings.py`.
* Restart the Zulip server.
[apple-docs]: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html

View File

@@ -13,10 +13,14 @@ If you already have an SSL certificate, just install (or symlink) its
files into place at the following paths:
* `/etc/ssl/private/zulip.key` for the private key
* `/etc/ssl/certs/zulip.combined-chain.crt` for the certificate.
Because Zulip uses nginx as its web server, this should be in the
format of a [chained certificate bundle][nginx-https].
[nginx-https]: http://nginx.org/en/docs/http/configuring_https_servers.html
Your certificate file should contain not only your own certificate but
its full chain, including any intermediate certificates used by your
CA. See the [nginx documentation][nginx-chains] for details on what
this means and how to do it and test it. If you're missing part of
the chain, your server may work with some browsers but not others.
[nginx-chains]: http://nginx.org/en/docs/http/configuring_https_servers.html#chains
## Certbot (recommended)

View File

@@ -13,7 +13,7 @@ with only a few things you need to know to get started.
* All email templates are in `templates/zerver/emails/`. Each email has three
template files: `<template_prefix>.subject`, `<template_prefix>.txt`, and
`<template_prefix>.html`. Email templates, along with all other templates
`<template_prefix>.source.html`. Email templates, along with all other templates
in the `templates/` directory, are Jinja2 templates.
* Most of the CSS and HTML layout for emails is in `email_base.html`. Note
that email has to ship with all of its CSS and HTML, so nothing in
@@ -79,3 +79,68 @@ backend. The `locmem` backend stores messages in a special attribute
of the django.core.mail module, "outbox". The outbox attribute is
created when the first message is sent. Its a list with an
EmailMessage instance for each message that would be sent.
Other notes:
* After changing any HTML email or `email_base.html`, you need to run
`tools/inline-email-css` for the changes to be reflected in the dev
environment. The script generates files like
`templates/zerver/emails/compiled/<template_prefix>.html`.
## Email templates
Zulip's email templates live under `templates/zerver/emails`. Email
templates are a messy problem, because on the one hand, you want nice,
readable markup and styling, but on the other, email clients have very
limited CSS support and generaly require us to inject any CSS we're
using in the emails into the email as inline styles. And then you
also need both plain-text and HTML emails. We solve these problems
using a combination of the
[premailer](https://github.com/peterbe/premailer) library and having
two copies of each email (plain-text and HTML).
So for each email, there are two source templates: the `.txt` version
(for plain-text format) as well as a `.source.html` template. The
`.txt` version is used directly; while the `.source.html` template is
processed by `tools/inline-email-css` (generating a `.html` template
under `templates/zerver/emails/compiled`); that tool (powered by
`premailer`) injects the CSS we use for styling our emails
(`templates/zerver/emails/email.css`) into the templates inline.
What this means is that when you're editing emails, **you need to run
`tools/inline-email-css`** after making changes to see the changes
take effect. Our tooling automatically runs this as part of
`tools/provision` and production deployments; but you should bump
`PROVISION_VERSION` when making changes to emails that change test
behavior, or other developers will get test failures until they
provision.
While this model is great for the markup side, it isn't ideal for
[translations](../translating/translating.html). The Django
translation system works with exact strings, and having different new
markup can require translators to re-translate strings, which can
result in problems like needing 2 copies of each string (one for
plain-text, one for HTML) and/or needing to re-translate a bunch of
strings after making a CSS tweak. Re-translating these strings is
relatively easy in Transifex, but annoying.
So when writing email templates, we try to translate individual
sentences that are shared between the plain-text and HTML content
rather than larger blocks that might contain markup; this allows
translators to not have to deal with multiple versions of each string
in our emails.
One can test whether you did the translating part right by running
`tools/inline-email-css && manage.py makemessages` and then searching
for the strings in `static/locale/en/LC_MESSAGES/django.po`; if there
are multiple copies or they contain CSS colors, you did it wrong.
A final note for translating emails is that strings that are sent to
user accounts (where we know the user's language) are higher-priority
to translate than things sent to an email address (where we don't).
E.g. for password reset emails, it makes sense for the code path for
people with an actual account can be tagged for translation, while the
code path for the "you don't have an account email" might not be,
since we might not know what language to use in the second case.
Future work in this space could be to actually generate the plain-text
versions of emails from the `.source.html` markup, so that we don't
need to maintain two copies of each email's text.

View File

@@ -0,0 +1,27 @@
# Guest users
This page documents how guest users work in Zulip. This documentation
attempts to cover the current state first, and then the
to-be-implemented restrictions.
Guest users are like normal users in Zulip, except that they cannot do
the following:
* Join public streams without being added by another user.
* Access message history on public streams.
* Create streams (public or private)
* Create or own bots users
For many of these limitations, we haven't yet hidden the relevant
section(s) of the UI, and the prototype guest user experience will
feel buggy until we hide those features.
Limitations to be implemented:
* Manage streams (e.g. Add other users to a stream they are subscribed
to)
* See streams they are not subscribed to.
* Interact with user groups
* Send private messages to users not in a stream with them.
(And more, this is an initial high-level TODO list).

View File

@@ -32,8 +32,8 @@ renders the template. For example, if you want to find the context
passed to `index.html`, you can do:
```
$ git grep zerver/index.html '*.py'
zerver/views/home.py: response = render(request, 'zerver/index.html',
$ git grep zerver/app/index.html '*.py'
zerver/views/home.py: response = render(request, 'zerver/app/index.html',
```
The next line in the code being the context definition.
@@ -75,6 +75,14 @@ static/js/invite.js: $('#streams_to_add').html(templates.render('invite_subsc
The second argument to `templates.render` is the context.
### Toolchain
Handlebars is in our `package.json` and thus ends up in
`node_modules`; and then we have a script,
`tools/compile-handlebars-templates`, which is responsible for
compiling the templates, both in production and as they change in a
development environment.
### Translation
All user-facing strings (excluding pages only visible to sysadmins or

View File

@@ -30,11 +30,13 @@ Subsystems Documentation
logging
typing-indicators
users
guest-users
release-checklist
api-release-checklist
swagger-api-docs
documentation
conversion
input-pills
presence
unread_messages
user-docs

View File

@@ -38,7 +38,7 @@ The Python-Markdown implementation is tested by
`frontend_tests/node_tests/markdown.js`.
A shared set of fixed test data ("test fixtures") is present in
`zerver/fixtures/markdown_test_cases.json`, and is automatically used
`zerver/tests/fixtures/markdown_test_cases.json`, and is automatically used
by both test suites; as a result, it is the preferred place to add new
tests for Zulip's markdown system. Some important notes on reading
this file:
@@ -99,8 +99,8 @@ places:
`static/third/marked/lib/marked.js`), or `markdown.contains_backend_only_syntax` if
your changes won't be supported in the frontend processor.
* If desired, the typeahead logic in `static/js/composebox_typeahead.js`.
* The test suite, probably via adding entries to `zerver/fixtures/markdown_test_cases.json`.
* The in-app markdown documentation (`templates/zerver/markdown_help.html`).
* The test suite, probably via adding entries to `zerver/tests/fixtures/markdown_test_cases.json`.
* The in-app markdown documentation (`templates/zerver/app/markdown_help.html`).
* The list of changes to markdown at the end of this document.
Important considerations for any changes are:

View File

@@ -36,7 +36,7 @@ what you clicked on, and in fact the message you clicked on stays at
exactly the same scroll position in the window after the narrowing as
it was at before.
### Search or sidebar click: unread/recent matching narrow
### Search, sidebar click, or new narrowed tab: unread/recent matching narrow
If you instead narrow by clicking on something in the left sidebar or
typing some terms into the search box, Zulip will instead select
@@ -71,16 +71,6 @@ streams in your All messages view, this can lag.
We plan to change this to automatically advance the pointer in a way
similar to the unnarrow logic.
### Narrow in a new tab: closest to pointer
When you load a new browser tab or window to a narrowed view, Zulip
will select the message closest to your pointer, which is what you
would have got had you loaded the browser window to your All messages view and
then clicked on the nearest message matching your narrow (which might
have been offscreen).
We plan to change this to match the Search/sidebar behavior.
### Forced reload: state preservation
When the server forces a reload of a browser that's otherwise caught

View File

@@ -0,0 +1,55 @@
# Presence
This document explains the model for Zulip's presence.
In a chat tool like Zulip, users expect to see the “presence” status
of other users: is the person I want to talk to currently online? If
not, were they last online 5 minutes ago, or more like an hour ago, or
a week? Presence helps set expectations for whether someone is likely
to respond soon. To a user, this feature can seem like a simple thing
that should be easy. But presence is actually one of the hardest
scalability problems for a team chat tool like Zulip.
There's a lot of performance-related details in the backend and
network protocol design that we won't get into here. The focus of
this is what one needs to know to correctly implement a Zulip client's
presence implementation (e.g. webapp, mobile app, terminal client, or
other tool that's intended to represent whether a user is online and
using Zulip).
A client should report to the server every minute a `POST` request to
`/users/me/presence`, containing the current user's status. The
requests contains a few parameters. The most important is "status",
which had 2 valid values:
* "active" -- this means the user has interacted with the client
recently. We use this for the "green" state in the webapp.
* "idle" -- the user has not interacted with the client recently.
This is important for the case where a user left a Zulip tab open on
their desktop at work and went home for the weekend. We use this
for the "orange" state in the webapp.
The client receives in the response to that request a data set that,
for each user, contains their status and timestamp that we last heard
from that client. There are a few important details to understand
about that data structure:
* It's really important that the timestamp is the last time we heard
from the client. A client can only interpret the status to display
about another user by doing a simple computation using the (status,
timestamp) pair. E.g. a user who last used Zulip 1 week ago will
have a timestamp of 1 week ago and a status of "active". Why?
Because this correctly handles the race conditions. For example, if
the threshhold for displaying a user as "offline" was 5 minutes
since the user was last online, the client can at any time
accurately compute whether that user is offline (even if the last
data from the server was 45 seconds ago, and the user was last
online 4:30 before the client received that server data).
* The `status_from_timestamp` function in `static/js/presence.js` is
useful sample code; the `OFFLINE_THRESHOLD_SECS` check is critical
to correct output.
* We provide the data for e.g. whether the user was online on their
desktop or the mobile app, but for a basic client, you will likely
only want to parse the "aggregated" key, which shows the summary
answer for "is this user online".

View File

@@ -53,6 +53,4 @@ preparing a new release.
to master.
* Update `ZULIP_VERSION` in `version.py`, and `release` and `version` in
`docs/conf.py`, to e.g. `1.6.0+git`.
* Update the handful of places where we link to docs for the latest
release, rather than for master. See `git grep 'zulip.readthedocs.io/en/[0-9]'`.
* Consider removing a few old releases from ReadTheDocs.

View File

@@ -249,9 +249,9 @@ always create a new macro by adding a new file to that folder.
### **Organization settings** `{!admin.md!}` macro
* **About:** Links to the **Organization settings** documentation.
Usually preceded by the [**Go to the** macro](#go-to-the-go-to-the-md-macro)
and a link to a particular section on the **Organization settings** page.
* **About:** Links to the **Organization settings** documentation. Usually
preceded by a link to a particular section on the **Organization settings**
page.
* **Contents:**
```md
@@ -260,7 +260,7 @@ and a link to a particular section on the **Organization settings** page.
* **Example usage and rendering:**
```md
{!go-to-the.md!} [Organization settings](/#organization/organization-settings)
1. Go to the [Organization settings](/#organization/organization-settings)
{!admin.md!}
```
```md
@@ -284,7 +284,7 @@ immediately after the title.
```md
{!admin-only.md!}
{!follow-steps.md!} change who can join your stream by changing the stream's
Follow the following steps to change who can join your stream by changing the stream's
accessibility.
```
```md
@@ -348,27 +348,6 @@ macro](#message-actions-message-actions-md-macro).
down chevron (<i class="fa fa-chevron-down"></i>) icon to reveal an actions dropdown.
```
### **Go to the** `{!go-to-the.md}` macro
* **About:** Usually precedes the [**Settings** macro](#settings-settings-md-macro)
or the [**Organization settings** macro](#organization-settings-admin-md-macro). Transforms
following content into a step.
* **Contents:**
```md
1. Go to the
```
* **Example usage and rendering:**
```md
{!go-to-the.md!} [Notifications](/#settings/notifications)
{!settings.md!}
```
```md
1. Go to the [Notifications](/#settings/notifications) tab on the
[Settings](/help/edit-settings) page.
```
### **Filter streams** `{!filter-streams.md!}` macro
* **About:** Explains how to search for specific streams in the
@@ -392,24 +371,6 @@ following content into a step.
name of the stream in the **Filter streams** input.
```
### **Follow steps** `{!follow-steps.md!}` macro
* **About:** Prepends phrases with instructions to follow the following steps.
* **Contents:**
```md
Follow the following steps to
```
* **Example usage and rendering:**
```md
{!follow-steps.md!} change your mobile notification settings.
```
```md
Follow the following steps to change your mobile notification
settings.
```
### **Message actions** `{!message-actions.md!}` macro
* **About:** Explains how to view the actions of message. Usually followed by an instruction
@@ -456,8 +417,7 @@ describing the settings they modified.
### **Settings** `{!settings.md!}` macro
* **About:** Links to the **Edit Settings** documentation. Usually preceded by
the [**Go to the** macro](#go-to-the-go-to-the-md-macro) and a link to a
particular section on the **Settings** page.
a link to a particular section on the **Settings** page.
* **Contents:**
```md
@@ -466,7 +426,7 @@ particular section on the **Settings** page.
* **Example usage and rendering:**
```md
{!go-to-the.md!} [Notifications](/#settings/notifications)
1. Go to the [Notifications](/#settings/notifications)
{!settings.md!}
```
```md

View File

@@ -319,7 +319,7 @@ reads a bunch of sample inputs from a JSON fixture file, feeds them
to our GitHub integration code, and then verifies the output against
expected values from the same JSON fixture file.
Our fixtures live in `zerver/fixtures`.
Our fixtures live in `zerver/tests/fixtures`.
### Mocks and stubs

View File

@@ -3,9 +3,10 @@
As an alternative to the black-box whole-app testing, you can unit test
individual JavaScript files.
If you are writing JavaScript code that manipulates data (as opposed
to coordinating UI changes), then you probably modify existing unit test
modules to ensure the quality of your code and prevent regressions.
You can run tests as follow:
```
tools/test-js-with-node
```
The JS unit tests are written to work with node. You can find them
in `frontend_tests/node_tests`. Here is an example test from
@@ -35,34 +36,19 @@ see if there are corresponding test in `frontend_tests/node_tests`. If
there are, you should strive to follow the patterns of the existing tests
and add your own tests.
## HTML output
The JavaScript unit tests can generate output to be viewed in the
browser. The best examples of this are in `frontend_tests/node_tests/templates.js`.
The main use case for this mechanism is to be able to unit test
templates and see how they are rendered without the complications
of the surrounding app. (Obviously, you still need to test the
app itself!) The HTML output can also help to debug the unit tests.
Each test calls a method named `write_handlebars_output` after it
renders a template with similar data. This API is still evolving,
but you should be able to look at existing code for patterns.
When you run `tools/test-js-with-node`, it will present you with a
message like "To see more output, open var/test-js-with-node/index.html."
Basically, you just need to open the file in the browser. (If you are
running a VM, this might require switching to another terminal window
to launch the `open` command.)
## Coverage reports
You can automatically generate coverage reports for the JavaScript unit
tests like this:
> tools/test-js-with-node --coverage
```
tools/test-js-with-node --coverage
```
Then open `coverage/lcov-report/js/index.html` in your browser. Modules
If tests pass, you will get instructions to view coverage reports
in your browser.
Note that modules that
we don't test *at all* aren't listed in the report, so this tends to
overstate how good our overall coverage is, but it's accurate for
individual files. You can also click a filename to see the specific
@@ -75,12 +61,7 @@ good goal.
The following scheme helps avoid tests leaking globals between each
other.
First, if you can avoid globals, do it, and the code that is directly
under test can simply be handled like this:
> var search = require('js/search_suggestion.js');
For deeper dependencies, you want to categorize each module as follows:
You want to categorize each module as follows:
- Exercise the module's real code for deeper, more realistic testing?
- Stub out the module's interface for more control, speed, and
@@ -102,7 +83,7 @@ like this:
> });
>
> // then maybe further down
> global.page_params.email = 'alice@zulip.com';
> page_params.email = 'alice@zulip.com';
Finally, there's the hybrid situation, where you want to borrow some of
a module's real functionality but stub out other pieces. Obviously, this
@@ -110,51 +91,28 @@ is a pretty strong smell that the other module might be lacking in
cohesion, but that code might be outside your jurisdiction. The pattern
here is this:
> // Use real versions of parse/unparse
> var narrow = require('js/narrow.js');
> set_global('narrow', {
> parse: narrow.parse,
> unparse: narrow.unparse
> });
> // Import real code.
> zrequire('narrow');
>
> // But later, I want to stub the stream without having to call super-expensive
> // real code like narrow.activate().
> global.narrow.stream = function () {
> // And later...
> narrow.stream = function () {
> return 'office';
> };
## Creating new test modules
The test runner (`index.js`) automatically runs all .js files in the
`frontend_tests/node directory`, so you can simply start editing a file
in that directory to create a new test.
The nodes tests rely on JS files that use the module pattern. For example, to
test the `foobar.js` file, you would first add the following to the
bottom of `foobar.js`:
test the `foobar.js` file, you would first ensure that code like below
is at the bottom of `foobar.js`:
> if (typeof module !== 'undefined') {
> module.exports = foobar;
> }
This makes `foobar.js` follow the CommonJS module pattern, so it can be
This means `foobar.js` follow the CommonJS module pattern, so it can be
required in Node.js, which runs our tests.
Now create `frontend_tests/node_tests/foobar.js`. At the top, require
the [Node.js assert module](http://nodejs.org/api/assert.html), and the
module you're testing, like so:
> var assert = require('assert');
> var foobar = require('js/foobar.js');
And of course, if the module you're testing depends on other modules,
or modifies global state, you may need to review the
[section on handling dependencies](#handling-dependencies-in-unit-tests) above.
Define and call some tests using the [assert
module](http://nodejs.org/api/assert.html). Note that for "equal"
asserts, the *actual* value comes first, the *expected* value second.
> (function test_somefeature() {
> assert.strictEqual(foobar.somefeature('baz'), 'quux');
> assert.throws(foobar.somefeature('Invalid Input'));
> }());
The test runner (`index.js`) automatically runs all .js files in the
frontend\_tests/node directory.

View File

@@ -9,11 +9,12 @@ important components are documented in depth in their own sections:
- [Casper](../testing/testing-with-casper.html): end-to-end UI tests
- [Node](../testing/testing-with-node.html): unit tests for JS front end code
- [Linters](../testing/linters.html): Our parallel linter suite
- [Travis CI details](travis.html): How all of these run in Travis CI
- [CI details](travis.html): How all of these run in CI
- [Other test suites](#other-test-suites): Our various smaller test suites.
This document covers more general testing issues, such as how to run the
entire test suite, how to troubleshoot database issues, how to manually
test the front end, and how to plan for the future upgrade to Python3.
test the front end, etc.
We also document [how to manually test the app](manual-testing.html).
@@ -53,6 +54,50 @@ if you're working on new database migrations. To do this, run:
./tools/do-destroy-rebuild-test-database
```
## Other test suites
Zulip also has about a dozen smaller tests suites:
- `tools/test-migrations`: Checks whether the `zerver/migrations`
migration content the models defined in `zerver/models.py`. See our
[schema migration documentation](../subsystems/schema-migrations.html)
for details on how to do database migrations correctly.
- `tools/test-documentation`: Checks for broken links in this
ReadTheDocs documentation site.
- `tools/test-help-documentation`: Checks for broken links in the
`/help` user documentation site, and related pages.
- `tools/test-api`: Tests that the API documentation at `/api`
actually works; the actual code for this is defined in
`zerver/lib/api_test_helpers.py`.
- `test-locked-requirements`: Verifies that developers didn't forget
to run `tools/update-locked-requirements` after modifying
`requirements/*.in`. See
[our dependency documentation](../subsystems/dependencies.html) for
details on the system this is verifying.
- `tools/check-capitalization`: Checks whether translated strings (aka
user-facing strings) correctly follow Zulip's capitalization
conventions. This requires some maintainance of an exclude list of
proper nouns mentioned in the Zulip project, but helps a lot in
avoiding new strings being added that don't match our style.
- `tools/check-frontend-i18n`: Checks for a common bug in Handlebars
templates, of using the wrong syntax for translating blocks
containing variables.
- `./tools/test-run-dev`: Checks that `run-dev.py` starts properly;
this helps prevent bugs that break the development environment.
- `./tools/test-queue-worker-reload`: Verifies that Zulip's queue
processors properly reload themselves after code changes.
- `./tools/optimize-svg`: Checks whether all SVG files for integration
logos are properly optimized for size (since we're not going to edit
third-party logos, this helps keep the Zulip codebase from getting huge).
- `./tools/test-tools`: Automated tests for various parts of our
development tooling (mostly various linters) that are not used in
production.
Each of these has a reason (usually, performance or a need to do messy
things to the environment) why they are not part of the handful of
major test suites like `test-backend`, but they all contribute
something valuable to helping keep Zulip bug-free.
### Possible testing issues
- When running the test suite, if you get an error like this:

View File

@@ -121,7 +121,7 @@ feature requires UI changes, you may need to add additional CSS to this
file.
**Templates:** The initial page structure is rendered via Jinja2
templates located in `templates/zerver`. For JavaScript, Zulip uses
templates located in `templates/zerver/app`. For JavaScript, Zulip uses
Handlebars templates located in `static/templates`. Templates are
precompiled as part of the build/deploy process.
@@ -483,8 +483,9 @@ to be added to the admin page (and its value added to the data sent back
to server when a realm is updated) and the change event needs to be
handled on the client.
To add the checkbox to the admin page, modify the relevant template,
`static/templates/settings/organization-permissions-admin.handlebars`
To add the checkbox to the admin page, modify the relevant template in
`static/templates/settings/`, which can be
`organization-permissions-admin.handlebars` or `organization-settings-admin.handlebars`
(omitted here since it is relatively straightforward).
Then add the new form control in `static/js/admin.js`.
@@ -507,66 +508,81 @@ function _setup_page() {
The JavaScript code for organization settings and permissions can be found in
`static/js/settings_org.js`.
There is a front-end version of `property_types`, which reduces the code
needed on the front end for a new feature.
In frontend, we have split the `property_types` into three objects:
Add the new feature to the `property_types` object in `settings_org.js`.
The key should be the setting name and the value should be an object with
the following keys:
- `org_profile`: This contains properties for the "organization
profile" settings page.
* type
* checked_msg (what message the user sees when they enable the setting)
* unchecked_msg (what message the user sees when they disable the setting)
- `org_settings`: This contains properties for the "organization
settings" page. Settings belonging to this section generally
decide what features should be available to a user like deleting a
message, message edit history etc. Our `mandatory_topics` feature
belongs in this section.
- `org_permissions`: This contains properties for the "organization
permissions" section. These properties control security controls
like who can join the organization and whether normal users can
create streams or upload custom emoji.
Once you've determined wheter the new setting belongs, the next step
is to find the right subsection of that page to put the setting
in. For example in this case of `mandatory_topics` it will lie in
"Message feed" (`msg_feed`) subsection.
*If you're not sure in which section your feature belongs, it's is
better to discuss it in the [community](https://chat.zulip.org/)
before implementing it.*
When defining the property, you'll also need to specify the property
field type (i.e. whether it's a `bool`, `integer` or `text`).
``` diff
// static/js/settings_org.js
var property_types = {
settings: {
var org_settings = {
msg_editing: {
// ...
},
permissions: { // ...
msg_feed: {
// ...
+ mandatory_topics: {
+ type: 'bool',
+ checked_msg: i18n.t("Topics are required in messages to streams"),
+ unchecked_msg: i18n.t("Topics are not required in messages to streams"),
},
+ },
},
};
```
Additionally, any code needed to update the UI when the setting is changed
should be written in a function inside `settings_org.js`.
For example, when a realm description is updated, that value change should
occur in other windows where the description field is visible:
Note that some settings, like `realm_create_stream_permission`,
reuqire special treatment, because they don't match the common
pattern. We can't extract the property name and compare the value of
such input elements with those in `page_params`, so we have to
manually handle such situations in a couple key functions:
# static/js/settings_org.js
- `settings_org.get_property_value`: This processes the property name
when it doesn't match a corresponding key in `page_params`, and
returns the current value of that property, which we can use to
compare and set the values of corresponding DOM element.
exports.update_realm_description = function () {
if (!meta.loaded) {
return;
}
$('#id_realm_description').val(page_params.realm_description);
};
This ensures the appropriate code will run even if the
changes are made in another browser window.
In the example of updating a `mandatory_topics` setting, most of the changes
are on the backend, so no UI updates are required.
- `settings_org.update_dependent_subsettings`: This handles settings
whose value and state depend on other elements. For example,
`realm_waiting_period_threshold` is only shown for with the right
state of `realm_create_stream_permission`.
Finally, update `server_events_dispatch.js` to handle related events coming from
the server. There is an object, `realm_settings`, in the function
`dispatch_normal_event`. The keys in this object are setting names and the
values are the UI updating functions to run when an event has occurred.
If there is no relevant UI change to make, the value should be `noop`
(this is the case for `mandatory_topics`). However, if you had written
a function in `settings_org.js` to update UI, that function should
be the value in the `realm_settings` object.
If there is no relevant UI change to make other than in settings page
itself, the value should be `noop` (this is the case for
`mandatory_topics`, since this setting only has an effect on the
backend, so no UI updates are required.).
However, if you had written a function to update the UI after a given
setting has changed, your function should be referenced in the
`realm_settings` of `server_events_dispatch.js`. See for example
`settings_emoji.update_custom_emoji_ui`.
``` diff
@@ -585,10 +601,35 @@ function dispatch_normal_event(event) {
};
```
Checkboxes and other common input elements handle the UI updates
automatically through the logic in `settings_org.sync_realm_settings`.
The rest of the `dispatch_normal_events` function updates the state of the
application if an update event has occurred on a realm property and runs
the associated function to update the application's UI, if necessary.
Here are few important cases you should consider when testing your changes:
- For organization settings where we have a "save/discard" model, make
sure both the "Save" and "Discard changes" buttons are working
properly.
- If your setting is dependent on another setting, carefully check
that both are properly synchronized. For example, the input element
for `realm_waiting_period_threshold` is shown only when we have
selected the custom time limit option in the
`realm_create_stream_permission` dropdown.
- Do some manual testing for the real-time synchronization of input
elements across the browsers and just like "Discard changes" button,
check whether dependent settings are synchronized properly (this is
easy to do by opening two browser windows to the settings page, and
making changes in one while watching the other).
- Each subsection has independent "Save" and "Discard changes"
buttons, so changes and saving in one subsection shouldn't affect
the others.
### Front End Tests
A great next step is to write front end tests. There are two types of

View File

@@ -101,7 +101,7 @@ def home(request):
### Writing a template
Templates for the main website are found in
[templates/zerver](https://github.com/zulip/zulip/blob/master/templates/zerver).
[templates/zerver/app](https://github.com/zulip/zulip/blob/master/templates/zerver/app).
## Writing API REST endpoints
@@ -275,7 +275,7 @@ and in [zerver/lib/actions.py](https://github.com/zulip/zulip/blob/master/zerver
```py
def do_set_realm_name(realm, name):
# type: (Realm, Text) -> None
# type: (Realm, str) -> None
realm.name = name
realm.save(update_fields=['name'])
event = dict(

View File

@@ -1,7 +1,9 @@
{
"env": {
"shared-node-browser": true
"shared-node-browser": true,
"es6": true
},
"parserOptions": { "ecmaVersion": 2018 },
"globals": {
"assert": false,
"casper": false,
@@ -11,6 +13,7 @@
"zrequire": false
},
"rules": {
"no-sync": 0
"no-sync": 0,
"prefer-const": "error"
}
}

View File

@@ -17,8 +17,8 @@ casper.then(function () {
});
// Make sure confirmation email is send
this.waitWhileVisible('form[action^="/new/"]', function () {
var regex = new RegExp('^http://[^/]+/accounts/send_confirm/' + email);
this.test.assertUrlMatch(regex, 'Confirmation mail send');
var regex = new RegExp('^http://[^/]+/accounts/send_confirm/' + email);
this.test.assertUrlMatch(regex, 'Confirmation mail send');
});
});

View File

@@ -19,14 +19,14 @@ casper.then(function () {
msg.headings.forEach(function (heading) {
casper.test.assertMatch(common.normalize_spaces(heading),
/(^You and )|( )/,
'Heading is well-formed');
/(^You and )|( )/,
'Heading is well-formed');
});
msg.bodies.forEach(function (body) {
casper.test.assertMatch(body,
/^(<p>(.|\n)*<\/p>)?$/,
'Body is well-formed');
/^(<p>(.|\n)*<\/p>)?$/,
'Body is well-formed');
});
casper.test.info('Sending messages');

View File

@@ -337,30 +337,36 @@ casper.waitForSelector('#stream_filters .highlighted_stream', function () {
// Use arrow keys to navigate through suggestions
casper.then(function () {
// Down: Denmark -> Scotland
casper.sendKeys('.stream-list-filter', casper.page.event.key.Down, {keepFocus: true});
// Up: Scotland -> Denmark
casper.sendKeys('.stream-list-filter', casper.page.event.key.Up, {keepFocus: true});
// Up: Denmark -> Verona
casper.sendKeys('.stream-list-filter', casper.page.event.key.Up, {keepFocus: true});
function arrow(key) {
casper.sendKeys('.stream-list-filter',
casper.page.event.key[key],
{keepFocus: true});
}
arrow('Down'); // Denmark -> Scotland
arrow('Up'); // Scotland -> Denmark
arrow('Up'); // Denmark -> Denmark
arrow('Down'); // Denmark -> Scotland
});
casper.waitForSelector('#stream_filters [data-stream-name="Verona"].highlighted_stream', function () {
casper.waitForSelector('#stream_filters [data-stream-name="Scotland"].highlighted_stream', function () {
casper.test.info('Suggestion highlighting - after arrow key navigation');
casper.test.assertDoesntExist('#stream_filters [data-stream-name="Denmark"].highlighted_stream',
'Stream Denmark is not highlighted');
casper.test.assertDoesntExist('#stream_filters [data-stream-name="Scotland"].highlighted_stream',
'Stream Scotland is not highlighted');
casper.test.assertExist('#stream_filters [data-stream-name="Verona"].highlighted_stream',
'Stream Verona is highlighted');
casper.test.assertDoesntExist(
'#stream_filters [data-stream-name="Denmark"].highlighted_stream',
'Stream Denmark is not highlighted');
casper.test.assertExist(
'#stream_filters [data-stream-name="Scotland"].highlighted_stream',
'Stream Scotland is highlighted');
casper.test.assertDoesntExist(
'#stream_filters [data-stream-name="Verona"].highlighted_stream',
'Stream Verona is not highlighted');
});
// We search for the beginning of "Verona", not case sensitive
// We search for the beginning of "Scotland", not case sensitive
casper.then(function () {
casper.evaluate(function () {
$('.stream-list-filter').expectOne()
.focus()
.val('ver')
.val('sCoT')
.trigger($.Event('input'))
.trigger($.Event('click'));
});
@@ -373,16 +379,16 @@ casper.waitWhileVisible('#stream_filters [data-stream-name="Denmark"]', function
casper.test.assertDoesntExist('#stream_filters [data-stream-name="Denmark"]',
'Filtered stream list does not contain Denmark');
});
casper.waitWhileVisible('#stream_filters [data-stream-name="Scotland"]', function () {
casper.test.assertDoesntExist('#stream_filters [data-stream-name="Scotland"]',
'Filtered stream list does not contain Scotland');
casper.waitWhileVisible('#stream_filters [data-stream-name="Verona"]', function () {
casper.test.assertDoesntExist('#stream_filters [data-stream-name="Verona"]',
'Filtered stream list does not contain Verona');
});
casper.then(function () {
casper.test.assertExists('#stream_filters [data-stream-name="Verona"]',
'Filtered stream list does contain Verona');
casper.test.assertExists('#stream_filters [data-stream-name="Verona"].highlighted_stream',
'Stream Verona is highlighted');
casper.test.assertExists('#stream_filters [data-stream-name="Scotland"]',
'Filtered stream list does contain Scotland');
casper.test.assertExists('#stream_filters [data-stream-name="Scotland"].highlighted_stream',
'Stream Scotland is highlighted');
});
// Clearing the list should give us back all the streams in the list
@@ -470,22 +476,23 @@ casper.waitForSelector('#user_presences .highlighted_user', function () {
// Use arrow keys to navigate through suggestions
casper.then(function () {
// Down: Cordelia -> Hamlet
casper.sendKeys('.user-list-filter', casper.page.event.key.Down, {keepFocus: true});
// Up: Hamlet -> Cordelia
casper.sendKeys('.user-list-filter', casper.page.event.key.Up, {keepFocus: true});
// Up: Cordelia -> aaron
casper.sendKeys('.user-list-filter', casper.page.event.key.Up, {keepFocus: true});
function arrow(key) {
casper.sendKeys('.user-list-filter',
casper.page.event.key[key],
{keepFocus: true});
}
arrow('Down'); // Cordelia -> Hamlet
arrow('Up'); // Hamlet -> Cordelia
arrow('Up'); // already at top
arrow('Down'); // Cordelia -> Hamlet
});
casper.waitForSelector('#user_presences li.highlighted_user [data-name="aaron"]', function () {
casper.waitForSelector('#user_presences li.highlighted_user [data-name="King Hamlet"]', function () {
casper.test.info('Suggestion highlighting - after arrow key navigation');
casper.test.assertDoesntExist('#user_presences li.highlighted_user [data-name="Cordelia Lear"]',
'User Cordelia Lear not is selected');
casper.test.assertDoesntExist('#user_presences li.highlighted_user [data-name="King Hamlet"]',
'User King Hamlet is not selected');
casper.test.assertExist('#user_presences li.highlighted_user [data-name="aaron"]',
'User aaron is selected');
'User Cordelia Lear not is selected');
casper.test.assertExist('#user_presences li.highlighted_user [data-name="King Hamlet"]',
'User King Hamlet is selected');
});
common.then_log_out();

View File

@@ -160,8 +160,8 @@ casper.then(function () {
casper.then(function () {
casper.fill('form#add_new_subscription', {stream_name: 'was'});
casper.evaluate(function () {
$('#add_new_subscription input[type="text"]').expectOne()
.trigger($.Event('input'));
$('#add_new_subscription input[type="text"]').expectOne()
.trigger($.Event('input'));
});
});
casper.waitForSelectorTextChange('form#add_new_subscription', function () {

View File

@@ -166,15 +166,15 @@ casper.then(function () {
var form_sel = '.edit_bot_form[data-email="' + bot_email + '"]';
casper.test.info('Testing edit bot form values');
// casper.test.assertEqual(
// common.get_form_field_value(form_sel + ' [name=bot_name]'),
// 'Bot 1');
// casper.test.assertEqual(
// common.get_form_field_value(form_sel + ' [name=bot_default_sending_stream]'),
// 'Denmark');
// casper.test.assertEqual(
// common.get_form_field_value(form_sel + ' [name=bot_default_events_register_stream]'),
// 'Rome');
// casper.test.assertEqual(
// common.get_form_field_value(form_sel + ' [name=bot_name]'),
// 'Bot 1');
// casper.test.assertEqual(
// common.get_form_field_value(form_sel + ' [name=bot_default_sending_stream]'),
// 'Denmark');
// casper.test.assertEqual(
// common.get_form_field_value(form_sel + ' [name=bot_default_events_register_stream]'),
// 'Rome');
casper.test.assertEqual(
common.get_form_field_value(form_sel + ' [name=bot_name]'),
'Bot 1');
@@ -302,7 +302,7 @@ casper.thenClick('a[data-code="en"]');
* Changing the language back to English so that subsequent tests pass.
*/
casper.waitUntilVisible('#language-settings-status a', function () {
casper.test.assertSelectorHasText('#language-settings-status', 'Saved. Please reload for the change to take effect.');
casper.test.assertSelectorHasText('#language-settings-status', 'Gespeichert. Bitte lade die Seite neu um die Änderungen zu aktivieren.');
});
casper.thenOpen("http://zulip.zulipdev.com:9981/");

View File

@@ -19,9 +19,9 @@ casper.then(function () {
});
common.then_send_message('stream', {
stream: 'Verona',
subject: 'stars',
content: 'test star',
stream: 'Verona',
subject: 'stars',
content: 'test star',
});
casper.then(function () {
@@ -37,10 +37,15 @@ casper.then(function () {
// Clicking on a message star stars it.
toggle_last_star();
casper.test.assertEquals(star_count(), 1,
"Got expected single star count.");
});
casper.click('a[href^="#narrow/is/starred"]');
casper.then(function () {
casper.waitUntilVisible('#zhome .icon-vector-star', function () {
casper.test.assertEquals(star_count(), 1,
"Got expected single star count.");
casper.click('a[href^="#narrow/is/starred"]');
});
});
casper.waitUntilVisible('#zfilt', function () {

View File

@@ -193,16 +193,17 @@ casper.then(function () {
name: 'Teams',
field_type: '1',
});
casper.click('form.admin-profile-field-form button.button');
casper.click("form.admin-profile-field-form button[type='submit']");
});
});
casper.then(function () {
casper.waitUntilVisible('div#admin-profile-field-status', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status',
'Custom profile field added!');
casper.waitUntilVisible('#admin-profile-field-status img', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status', 'Saved');
});
casper.waitUntilVisible('.profile-field-row span.profile_field_name', function () {
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_name', 'Teams');
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_type', 'Short Text');
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_type', 'Short text');
casper.click('.profile-field-row button.open-edit-form');
});
});
@@ -217,19 +218,19 @@ casper.then(function () {
});
casper.then(function () {
casper.waitUntilVisible('div#admin-profile-field-status', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status',
'Custom profile field updated!');
casper.waitUntilVisible('#admin-profile-field-status img', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status', 'Saved');
});
casper.waitForSelectorTextChange('.profile-field-row span.profile_field_name', function () {
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_name', 'team');
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_type', 'Short Text');
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_type', 'Short text');
casper.click('.profile-field-row button.delete');
});
});
casper.then(function () {
casper.waitUntilVisible('div#admin-profile-field-status', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status',
'Custom profile field deleted!');
casper.waitUntilVisible('#admin-profile-field-status img', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status', 'Saved');
});
});
@@ -286,9 +287,9 @@ function get_suggestions(str) {
casper.then(function () {
casper.evaluate(function (str) {
$('.create_default_stream')
.focus()
.val(str)
.trigger($.Event('keyup', { which: 0 }));
.focus()
.val(str)
.trigger($.Event('keyup', { which: 0 }));
}, str);
});
}
@@ -349,7 +350,7 @@ casper.then(function () {
// Hack: Rather than submitting the form, we just fill the
// form and then trigger a click event by clicking the button.
casper.fill('form.admin-realm-form', {
realm_icon_file_input: 'static/images/logo/zulip-icon-128x128.png',
realm_icon_file_input: 'static/images/logo/zulip-icon-128x128.png',
}, false);
casper.click("#realm_icon_upload_button");
casper.waitWhileVisible("#upload_icon_spinner .loading_indicator_spinner", function () {

View File

@@ -47,7 +47,8 @@ casper.then(function () {
casper.then(function () {
common.expected_messages('zhome', ['Verona > Test mention all'],
["<p><span class=\"user-mention user-mention-me\" data-user-id=\"*\">@all</span></p>"]);
["<p><span class=\"user-mention user-mention-me\" " +
"data-user-id=\"*\">@all</span></p>"]);
});

View File

@@ -6,22 +6,6 @@ function heading(heading_str) {
});
}
function submit_checked() {
casper.then(function () {
casper.waitUntilVisible('input:checked[type="checkbox"][id="id_realm_allow_message_editing"] + span', function () {
casper.click('#org-submit-msg-editing');
});
});
}
function submit_unchecked() {
casper.then(function () {
casper.waitUntilVisible('input:not(:checked)[type="checkbox"][id="id_realm_allow_message_editing"] + span', function () {
casper.click('#org-submit-msg-editing');
});
});
}
common.start_and_log_in();
// For clarity these should be different than what 08-edit uses, until
@@ -94,23 +78,34 @@ casper.then(function () {
});
});
function submit_edit_limit_changed() {
casper.test.assertSelectorHasText('#org-submit-msg-editing', "Save");
casper.click('#org-submit-msg-editing');
}
// DEACTIVATE
heading("DEACTIVATE");
common.then_click("li[data-section='organization-settings']");
// deactivate "allow message editing"
common.then_click('input[type="checkbox"][id="id_realm_allow_message_editing"] + span');
submit_unchecked();
casper.then(function () {
casper.test.info("Changing message edit limit setting");
casper.waitUntilVisible("#id_realm_msg_edit_limit_setting", function () {
casper.evaluate(function () {
$("#id_realm_msg_edit_limit_setting").val("never").change();
});
submit_edit_limit_changed();
});
});
casper.then(function () {
casper.waitUntilVisible('#org-submit-msg-editing[data-status="saved"]', function () {
casper.test.assertSelectorHasText('#org-submit-msg-editing',
'Saved');
casper.test.assertEval(function () {
return !(document.querySelector('input[type="checkbox"][id="id_realm_allow_message_editing"]').checked);
}, 'Allow message editing Setting de-activated');
return (document.querySelector('#id_realm_msg_edit_limit_setting').value === "never");
}, 'Message editing Setting disabled');
});
});
@@ -155,76 +150,47 @@ heading("REACTIVATE");
common.then_click('#settings-dropdown');
common.then_click('a[href^="#organization"]');
common.then_click("li[data-section='organization-settings']");
common.then_click('input[type="checkbox"][id="id_realm_allow_message_editing"] + span');
submit_checked();
casper.then(function () {
casper.waitUntilVisible('#org-submit-msg-editing[data-status="saved"]', function () {
casper.test.assertSelectorHasText('#org-submit-msg-editing',
'Saved');
casper.test.assertEval(function () {
return document.querySelector('input[type="checkbox"][id="id_realm_allow_message_editing"]').checked;
}, 'Allow message editing Setting re-activated');
});
});
// DEACTIVATE
heading("DEACTIVATE");
// go to admin page
casper.then(function () {
casper.test.info('Organization page');
casper.click('a[href^="#organization"]');
casper.test.assertUrlMatch(/^http:\/\/[^\/]+\/#organization/, 'URL suggests we are on organization page');
casper.test.assertExists('#settings_overlay_container.show', 'Organization page is active');
});
casper.then(function () {
casper.waitUntilVisible('form.admin-realm-form button.button');
});
// deactivate message editing
casper.then(function () {
casper.waitUntilVisible('input[type="checkbox"][id="id_realm_allow_message_editing"] + span', function () {
casper.test.info("Changing message edit limit setting");
casper.waitUntilVisible("#id_realm_msg_edit_limit_setting", function () {
casper.evaluate(function () {
$('input[type="text"][id="id_realm_message_content_edit_limit_minutes"]').val('4');
$("#id_realm_msg_edit_limit_setting").val("upto_ten_min").change();
});
submit_edit_limit_changed();
});
});
common.then_click('input[type="checkbox"][id="id_realm_allow_message_editing"] + span');
submit_unchecked();
casper.then(function () {
casper.waitUntilVisible('#org-submit-msg-editing[data-status="saved"]', function () {
casper.test.assertSelectorHasText('#org-submit-msg-editing',
'Saved');
casper.test.assertEval(function () {
return !(document.querySelector('input[type="checkbox"][id="id_realm_allow_message_editing"]').checked);
}, 'Allow message editing Setting de-activated');
casper.test.assertEval(function () {
return $('input[type="text"][id="id_realm_message_content_edit_limit_minutes"]').val() === '4';
}, 'Message content edit limit now 4');
return (document.querySelector('#id_realm_msg_edit_limit_setting').value === "upto_ten_min");
}, 'Allow message editing Setting re-activated and set to 10 minutes');
});
});
// REACTIVATE
heading("REACTIVATE");
// SET LIMIT TO 1 WEEK
heading("LIMIT TO 1 WEEK");
common.then_click('input[type="checkbox"][id="id_realm_allow_message_editing"] + span');
submit_checked();
casper.then(function () {
casper.test.info("Changing message edit limit setting");
casper.waitUntilVisible("#id_realm_msg_edit_limit_setting", function () {
casper.evaluate(function () {
$("#id_realm_msg_edit_limit_setting").val("upto_one_week").change();
});
submit_edit_limit_changed();
});
});
casper.then(function () {
casper.waitUntilVisible('#org-submit-msg-editing[data-status="saved"]', function () {
casper.test.assertSelectorHasText('#org-submit-msg-editing',
'Saved');
casper.test.assertEval(function () {
return document.querySelector('input[type="checkbox"][id="id_realm_allow_message_editing"]').checked;
}, 'Allow message editing Setting activated');
casper.test.assertEval(function () {
return $('input[type="text"][id="id_realm_message_content_edit_limit_minutes"]').val() === '4';
}, 'Message content edit limit still 4');
return (document.querySelector('#id_realm_msg_edit_limit_setting').value === "upto_one_week");
}, 'Message edit limit set to one week');
});
});
@@ -232,52 +198,78 @@ casper.then(function () {
heading("NO LIMIT");
casper.then(function () {
// allow arbitrary message editing
casper.waitUntilVisible('input[type="checkbox"][id="id_realm_allow_message_editing"] + span', function () {
casper.test.info("Changing message edit limit setting");
casper.waitUntilVisible("#id_realm_msg_edit_limit_setting", function () {
casper.evaluate(function () {
$('input[type="text"][id="id_realm_message_content_edit_limit_minutes"]').val('0');
$("#id_realm_msg_edit_limit_setting").val("any_time").change();
});
submit_edit_limit_changed();
});
});
submit_checked();
casper.then(function () {
casper.waitUntilVisible('#org-submit-msg-editing[data-status="saved"]', function () {
casper.test.assertSelectorHasText('#org-submit-msg-editing',
'Saved');
casper.test.assertEval(function () {
return document.querySelector('input[type="checkbox"][id="id_realm_allow_message_editing"]').checked;
}, 'Allow message editing Setting still activated');
casper.test.assertEval(function () {
return $('input[type="text"][id="id_realm_message_content_edit_limit_minutes"]').val() === '0';
}, 'Message content edit limit is 0');
return (document.querySelector('#id_realm_msg_edit_limit_setting').value === "any_time");
}, 'Message can be edited any time');
});
});
// ILLEGAL LIMIT
heading("ILLEGAL LIMIT");
// CUSTOM LIMIT
heading("CUSTOM LIMIT");
casper.then(function () {
// disallow message editing, with illegal edit limit value. should be fixed by admin.js
casper.waitUntilVisible('input[type="checkbox"][id="id_realm_allow_message_editing"] + span', function () {
casper.test.info("Changing message edit limit setting");
casper.waitUntilVisible("#id_realm_msg_edit_limit_setting", function () {
casper.evaluate(function () {
$('input[type="text"][id="id_realm_message_content_edit_limit_minutes"]').val('moo');
$("#id_realm_msg_edit_limit_setting").val("custom_limit").change();
});
});
casper.waitUntilVisible('#id_realm_message_content_edit_limit_minutes', function () {
casper.evaluate(function () {
$('#id_realm_message_content_edit_limit_minutes').val("100");
});
submit_edit_limit_changed();
});
});
common.then_click('input[type="checkbox"][id="id_realm_allow_message_editing"] + span');
submit_unchecked();
casper.then(function () {
casper.waitUntilVisible('#org-submit-msg-editing[data-status="saved"]', function () {
casper.test.assertSelectorHasText('#org-submit-msg-editing',
'Saved');
casper.test.assertEval(function () {
return !(document.querySelector('input[type="checkbox"][id="id_realm_allow_message_editing"]').checked);
}, 'Allow message editing Setting de-activated');
return $('#id_realm_msg_edit_limit_setting').val() === "custom_limit";
}, 'Custom message edit limit set');
casper.test.assertEval(function () {
return $('input[type="text"][id="id_realm_message_content_edit_limit_minutes"]').val() === '10';
}, 'Message content edit limit has been reset to its default');
return $('#id_realm_message_content_edit_limit_minutes').val() === "100";
}, 'Message edit limit set to 100 minutes');
});
});
// INVALID LIMIT
heading("INVALID LIMIT");
casper.then(function () {
casper.test.info("Changing message edit limit setting");
casper.waitUntilVisible("#id_realm_msg_edit_limit_setting", function () {
casper.evaluate(function () {
$("#id_realm_msg_edit_limit_setting").val("custom_limit").change();
});
});
casper.waitUntilVisible('#id_realm_message_content_edit_limit_minutes', function () {
casper.evaluate(function () {
$('#id_realm_message_content_edit_limit_minutes').val("-100");
});
submit_edit_limit_changed();
});
});
casper.then(function () {
casper.waitUntilVisible('.admin-realm-failed-change-status', function () {
casper.test.assertSelectorHasText('#org-submit-msg-editing',
'Save');
});
});

View File

@@ -0,0 +1,12 @@
{
"rules": {
"indent": ["error", 4, {
"ArrayExpression": "first",
"ObjectExpression": "first",
"SwitchCase": 0,
"CallExpression": {"arguments": "first"},
"FunctionExpression": {"parameters": "first"},
"FunctionDeclaration": {"parameters": "first"}
}]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,30 +18,62 @@ global.people.add({
global.people.initialize_current_user(42);
var regular_message = { sender_email: 'another@zulip.com', content: '<p>a message</p>'};
var own_message = { sender_email: 'tester@zulip.com', content: '<p>hey this message alertone</p>',
alerted: true };
var other_message = { sender_email: 'another@zulip.com', content: '<p>another alertone message</p>',
alerted: true };
var caps_message = { sender_email: 'another@zulip.com', content: '<p>another ALERTtwo message</p>',
alerted: true };
var alertwordboundary_message = { sender_email: 'another@zulip.com',
content: '<p>another alertthreemessage</p>', alerted: false };
var multialert_message = { sender_email: 'another@zulip.com', content:
'<p>another alertthreemessage alertone and then alerttwo</p>',
alerted: true };
var unsafe_word_message = { sender_email: 'another@zulip.com', content: '<p>gotta al*rt.*s all</p>',
alerted: true };
var alert_in_url_message = { sender_email: 'another@zulip.com', content: '<p>http://www.google.com/alertone/me</p>',
alerted: true };
var question_word_message = { sender_email: 'another@zulip.com', content: '<p>still alertone? me</p>',
alerted: true };
const regular_message = {
sender_email: 'another@zulip.com',
content: '<p>a message</p>',
};
const own_message = {
sender_email: 'tester@zulip.com',
content: '<p>hey this message alertone</p>',
alerted: true,
};
const other_message = {
sender_email: 'another@zulip.com',
content: '<p>another alertone message</p>',
alerted: true,
};
const caps_message = {
sender_email: 'another@zulip.com',
content: '<p>another ALERTtwo message</p>',
alerted: true,
};
const alertwordboundary_message = {
sender_email: 'another@zulip.com',
content: '<p>another alertthreemessage</p>',
alerted: false,
};
const multialert_message = {
sender_email: 'another@zulip.com',
content: '<p>another alertthreemessage alertone and then alerttwo</p>',
alerted: true,
};
const unsafe_word_message = {
sender_email: 'another@zulip.com',
content: '<p>gotta al*rt.*s all</p>',
alerted: true,
};
const alert_in_url_message = {
sender_email: 'another@zulip.com',
content: '<p>http://www.google.com/alertone/me</p>',
alerted: true,
};
const question_word_message = {
sender_email: 'another@zulip.com',
content: '<p>still alertone? me</p>',
alerted: true,
};
var alert_domain_message = { sender_email: 'another@zulip.com', content: '<p>now with link <a href="http://www.alerttwo.us/foo/bar" target="_blank" title="http://www.alerttwo.us/foo/bar">www.alerttwo.us/foo/bar</a></p>',
alerted: true };
const alert_domain_message = {
sender_email: 'another@zulip.com',
content: '<p>now with link <a href="http://www.alerttwo.us/foo/bar" target="_blank" title="http://www.alerttwo.us/foo/bar">www.alerttwo.us/foo/bar</a></p>',
alerted: true,
};
// This test ensure we are not mucking up rendered HTML content.
var message_with_emoji = { sender_email: 'another@zulip.com', content: '<p>I <img alt=":heart:" class="emoji" src="/static/generated/emoji/images/emoji/unicode/2764.png" title="heart"> emoji!</p>',
alerted: true };
const message_with_emoji = {
sender_email: 'another@zulip.com',
content: '<p>I <img alt=":heart:" class="emoji" src="/static/generated/emoji/images/emoji/unicode/2764.png" title="heart"> emoji!</p>',
alerted: true,
};
(function test_notifications() {
assert(!alert_words.notifies(regular_message));
@@ -56,7 +88,7 @@ var message_with_emoji = { sender_email: 'another@zulip.com', content: '<p>I <im
}());
(function test_munging() {
var saved_content = regular_message.content;
let saved_content = regular_message.content;
alert_words.process_message(regular_message);
assert.equal(saved_content, regular_message.content);
@@ -82,8 +114,8 @@ var message_with_emoji = { sender_email: 'another@zulip.com', content: '<p>I <im
assert.equal(question_word_message.content, "<p>still <span class='alert-word'>alertone</span>? me</p>");
alert_words.process_message(alert_domain_message);
assert.equal(alert_domain_message.content, '<p>now with link <a href="http://www.alerttwo.us/foo/bar" target="_blank" title="http://www.alerttwo.us/foo/bar">www.<span class=\'alert-word\'>alerttwo</span>.us/foo/bar</a></p>');
assert.equal(alert_domain_message.content, `<p>now with link <a href="http://www.alerttwo.us/foo/bar" target="_blank" title="http://www.alerttwo.us/foo/bar">www.<span class='alert-word'>alerttwo</span>.us/foo/bar</a></p>`);
alert_words.process_message(message_with_emoji);
assert.equal(message_with_emoji.content, '<p>I <img alt=":heart:" class="emoji" src="/static/generated/emoji/images/emoji/unicode/2764.png" title="heart"> <span class=\'alert-word\'>emoji</span>!</p>');
assert.equal(message_with_emoji.content, `<p>I <img alt=":heart:" class="emoji" src="/static/generated/emoji/images/emoji/unicode/2764.png" title="heart"> <span class='alert-word'>emoji</span>!</p>`);
}());

View File

@@ -1,19 +1,12 @@
var patched_underscore = _.clone(_);
patched_underscore.debounce = function (f) { return f; };
global.patch_builtin('_', patched_underscore);
zrequire('people');
zrequire('bot_data');
set_global('$', function (f) {
if (f) {
return f();
}
return {trigger: function () {}};
set_global('$', () => {
return {trigger: () => {}};
});
set_global('document', null);
var page_params = {
const page_params = {
realm_bots: [{email: 'bot0@zulip.com', user_id: 42, full_name: 'Bot 0'},
{email: 'outgoingwebhook@zulip.com', user_id: 314, full_name: "Outgoing webhook",
services: [{base_url: "http://foo.com", interface: 1}]}],
@@ -35,7 +28,7 @@ assert.equal(bot_data.get(42).full_name, 'Bot 0');
assert.equal(bot_data.get(314).full_name, 'Outgoing webhook');
(function () {
var test_bot = {
const test_bot = {
email: 'bot1@zulip.com',
user_id: 43,
avatar_url: '',
@@ -44,7 +37,7 @@ assert.equal(bot_data.get(314).full_name, 'Outgoing webhook');
extra: 'Not in data',
};
var test_embedded_bot = {
const test_embedded_bot = {
email: 'embedded-bot@zulip.com',
user_id: 143,
avatar_url: '',
@@ -56,8 +49,8 @@ assert.equal(bot_data.get(314).full_name, 'Outgoing webhook');
(function test_add() {
bot_data.add(test_bot);
var bot = bot_data.get(43);
var services = bot_data.get_services(43);
const bot = bot_data.get(43);
const services = bot_data.get_services(43);
assert.equal('Bot 1', bot.full_name);
assert.equal('http://bar.com', services[0].base_url);
assert.equal(1, services[0].interface);
@@ -65,18 +58,15 @@ assert.equal(bot_data.get(314).full_name, 'Outgoing webhook');
}());
(function test_update() {
var bot;
var services;
bot_data.add(test_bot);
bot = bot_data.get(43);
let bot = bot_data.get(43);
assert.equal('Bot 1', bot.full_name);
bot_data.update(43, {full_name: 'New Bot 1',
services: [{interface: 2,
base_url: 'http://baz.com'}]});
bot = bot_data.get(43);
services = bot_data.get_services(43);
const services = bot_data.get_services(43);
assert.equal('New Bot 1', bot.full_name);
assert.equal(2, services[0].interface);
assert.equal('http://baz.com', services[0].base_url);
@@ -84,17 +74,17 @@ assert.equal(bot_data.get(314).full_name, 'Outgoing webhook');
(function test_embedded_bot_update() {
bot_data.add(test_embedded_bot);
var bot_id = 143;
var services = bot_data.get_services(bot_id);
const bot_id = 143;
const services = bot_data.get_services(bot_id);
assert.equal('12345678', services[0].config_data.key);
bot_data.update(bot_id, {services: [{config_data: {key: '87654321'}}]});
assert.equal('87654321', services[0].config_data.key);
}());
(function test_remove() {
var bot;
let bot;
bot_data.add(_.extend({}, test_bot, {is_active: true}));
bot_data.add({ ...test_bot, is_active: true });
bot = bot_data.get(43);
assert.equal('Bot 1', bot.full_name);
@@ -105,9 +95,9 @@ assert.equal(bot_data.get(314).full_name, 'Outgoing webhook');
}());
(function test_delete() {
var bot;
let bot;
bot_data.add(_.extend({}, test_bot, {is_active: true}));
bot_data.add({ ...test_bot, is_active: true });
bot = bot_data.get(43);
assert.equal('Bot 1', bot.full_name);
@@ -118,37 +108,36 @@ assert.equal(bot_data.get(314).full_name, 'Outgoing webhook');
}());
(function test_owner_can_admin() {
var bot;
let bot;
bot_data.add(_.extend({owner: 'owner@zulip.com'}, test_bot));
bot_data.add({owner: 'owner@zulip.com', ...test_bot});
bot = bot_data.get(43);
assert(bot.can_admin);
bot_data.add(_.extend({owner: 'notowner@zulip.com'}, test_bot));
bot_data.add({owner: 'notowner@zulip.com', ...test_bot});
bot = bot_data.get(43);
assert.equal(false, bot.can_admin);
}());
(function test_admin_can_admin() {
var bot;
page_params.is_admin = true;
bot_data.add(test_bot);
bot = bot_data.get(43);
const bot = bot_data.get(43);
assert(bot.can_admin);
page_params.is_admin = false;
}());
(function test_get_editable() {
var can_admin;
let can_admin;
bot_data.add(_.extend({}, test_bot, {user_id: 44, owner: 'owner@zulip.com', is_active: true}));
bot_data.add(_.extend({}, test_bot, {user_id: 45, email: 'bot2@zulip.com', owner: 'owner@zulip.com', is_active: true}));
bot_data.add(_.extend({}, test_bot, {user_id: 46, email: 'bot3@zulip.com', owner: 'not_owner@zulip.com', is_active: true}));
bot_data.add({...test_bot ,user_id: 44, owner: 'owner@zulip.com', is_active: true});
bot_data.add({...test_bot, user_id: 45, email: 'bot2@zulip.com', owner: 'owner@zulip.com', is_active: true});
bot_data.add({...test_bot, user_id: 46, email: 'bot3@zulip.com', owner: 'not_owner@zulip.com', is_active: true});
can_admin = _.pluck(bot_data.get_editable(), 'email');
assert.deepEqual(['bot1@zulip.com', 'bot2@zulip.com'], can_admin);
@@ -160,7 +149,7 @@ assert.equal(bot_data.get(314).full_name, 'Outgoing webhook');
}());
(function test_get_all_bots_for_current_user() {
var bots = bot_data.get_all_bots_for_current_user();
const bots = bot_data.get_all_bots_for_current_user();
assert.equal(bots.length, 2);
assert.equal(bots[0].email, 'bot1@zulip.com');

View File

@@ -0,0 +1,76 @@
zrequire('people');
zrequire('presence');
zrequire('util');
zrequire('buddy_data');
// The buddy_data module is mostly tested indirectly through
// activity.js, but we should feel free to add direct tests
// here.
set_global('page_params', {});
(function make_people() {
_.each(_.range(1000, 2000), (i) => {
const person = {
user_id: i,
full_name: `Human ${i}`,
email: `person${i}@example.com`,
};
people.add_in_realm(person);
});
}());
(function activate_people() {
const server_time = 9999;
const info = {
website: {
status: "active",
timestamp: server_time,
},
};
// Make 400 of the users active
_.each(_.range(1000, 1400), (user_id) => {
presence.set_user_status(user_id, info, server_time);
});
// And then 300 not active
_.each(_.range(1400, 1700), (user_id) => {
presence.set_user_status(user_id, {}, server_time);
});
}());
(function test_user_ids() {
var user_ids;
// Even though we have 1000 users, we only get the 400 active
// users. This is a consequence of buddy_data.maybe_shrink_list.
user_ids = buddy_data.get_filtered_and_sorted_user_ids();
assert.equal(user_ids.length, 400);
user_ids = buddy_data.get_filtered_and_sorted_user_ids('');
assert.equal(user_ids.length, 400);
// We don't match on "s", because it's not at the start of a
// word in the name/email.
user_ids = buddy_data.get_filtered_and_sorted_user_ids('s');
assert.equal(user_ids.length, 0);
// We match on "h" for the first name, and the result limit
// is relaxed for searches.
user_ids = buddy_data.get_filtered_and_sorted_user_ids('h');
assert.equal(user_ids.length, 1000);
// We match on "p" for the email.
user_ids = buddy_data.get_filtered_and_sorted_user_ids('p');
assert.equal(user_ids.length, 1000);
// Make our shrink limit higher, and go back to an empty search.
// We won't get all 1000 users, just the present ones.
buddy_data.max_size_before_shrinking = 50000;
user_ids = buddy_data.get_filtered_and_sorted_user_ids('');
assert.equal(user_ids.length, 700);
}());

View File

@@ -0,0 +1,14 @@
set_global('$', global.make_zjquery());
zrequire('buddy_list');
(function test_get_items() {
const alice_li = $.create('alice stub');
const sel = 'li.user_sidebar_entry';
buddy_list.container.set_find_results(sel, {
map: (f) => [f(0, alice_li)],
});
const items = buddy_list.get_items();
assert.deepEqual(items, [alice_li]);
}());

View File

@@ -2,19 +2,23 @@ zrequire('channel');
set_global('$', {});
set_global('reload', {});
set_global('blueslip', {});
set_global('blueslip', global.make_zblueslip());
var default_stub_xhr = 'default-stub-xhr';
const default_stub_xhr = 'default-stub-xhr';
function test_with_mock_ajax(test_params) {
var ajax_called;
var ajax_options;
const {
xhr = default_stub_xhr,
run_code,
check_ajax_options,
} = test_params;
let ajax_called;
let ajax_options;
$.ajax = function (options) {
$.ajax = undefined;
ajax_called = true;
ajax_options = options;
var xhr = test_params.xhr || default_stub_xhr;
options.simulate_success = function (data, text_status) {
options.success(data, text_status, xhr);
@@ -27,9 +31,9 @@ function test_with_mock_ajax(test_params) {
return xhr;
};
test_params.run_code();
run_code();
assert(ajax_called);
test_params.check_ajax_options(ajax_options);
check_ajax_options(ajax_options);
}
@@ -113,15 +117,15 @@ function test_with_mock_ajax(test_params) {
}());
(function test_normal_post() {
var data = {
const data = {
s: 'some_string',
num: 7,
lst: [1, 2, 4, 8],
};
var orig_success_called;
var orig_error_called;
var stub_xhr = 'stub-xhr-normal-post';
let orig_success_called;
let orig_error_called;
const stub_xhr = 'stub-xhr-normal-post';
test_with_mock_ajax({
xhr: stub_xhr,
@@ -158,9 +162,9 @@ function test_with_mock_ajax(test_params) {
}());
(function test_patch_with_form_data() {
var appended;
let appended;
var data = {
const data = {
append: function (k, v) {
assert.equal(k, 'method');
assert.equal(v, 'PATCH');
@@ -200,7 +204,7 @@ function test_with_mock_ajax(test_params) {
},
check_ajax_options: function (options) {
var reload_initiated;
let reload_initiated;
reload.initiate = function (options) {
reload_initiated = true;
assert.deepEqual(options, {
@@ -229,15 +233,10 @@ function test_with_mock_ajax(test_params) {
},
check_ajax_options: function (options) {
var has_error;
blueslip.error = function (msg) {
assert.equal(msg, 'Unexpected 403 response from server');
has_error = true;
};
blueslip.set_test_data('error', 'Unexpected 403 response from server');
options.simulate_error();
assert(has_error);
assert.equal(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
},
});
}());
@@ -252,12 +251,6 @@ function test_with_mock_ajax(test_params) {
},
check_ajax_options: function (options) {
var logged;
blueslip.log = function (msg) {
// Our log formatting is a bit broken.
assert.equal(msg, 'Retrying idempotent[object Object]');
logged = true;
};
global.patch_builtin('setTimeout', function (f, delay) {
assert.equal(delay, 0);
f();
@@ -273,39 +266,35 @@ function test_with_mock_ajax(test_params) {
},
});
assert(logged);
assert.equal(blueslip.get_test_logs('log').length, 1);
assert.equal(blueslip.get_test_logs('log')[0], 'Retrying idempotent[object Object]');
blueslip.clear_test_data();
},
});
}());
(function test_too_many_pending() {
$.ajax = function () {
var xhr = 'stub';
const xhr = 'stub';
return xhr;
};
var warned;
blueslip.warn = function (msg) {
assert.equal(
msg,
'The length of pending_requests is over 50. Most likely they are not being correctly removed.'
);
warned = true;
};
blueslip.set_test_data('warn',
'The length of pending_requests is over 50. ' +
'Most likely they are not being correctly removed.');
_.times(50, function () {
channel.post({});
});
assert(warned);
assert.equal(blueslip.get_test_logs('warn').length, 1);
blueslip.clear_test_data();
}());
(function test_xhr_error_message() {
var xhr = {
let xhr = {
status: '200',
responseText: 'does not matter',
};
var msg = 'data added';
let msg = 'data added';
assert.equal(channel.xhr_error_message(msg, xhr), 'data added');
xhr = {

View File

@@ -1,9 +1,12 @@
set_global('i18n', global.stub_i18n);
zrequire('keydown_util');
zrequire('components');
var LEFT_KEY = { which: 37 };
var RIGHT_KEY = { which: 39 };
var noop = function () {};
var LEFT_KEY = { which: 37, preventDefault: noop };
var RIGHT_KEY = { which: 39, preventDefault: noop };
(function test_basics() {
var keydown_f;
@@ -118,8 +121,10 @@ var RIGHT_KEY = { which: 39 };
}
});
var widget = components.toggle({
name: "info-overlay-toggle",
var callback_value;
var widget;
widget = components.toggle({
selected: 0,
values: [
{ label: i18n.t("Keyboard shortcuts"), key: "keyboard-shortcuts" },
@@ -129,6 +134,12 @@ var RIGHT_KEY = { which: 39 };
callback: function (name, key) {
assert.equal(callback_args, undefined);
callback_args = [name, key];
// The subs code tries to get a widget value in the middle of a
// callback, which can lead to obscure bugs.
if (widget) {
callback_value = widget.value();
}
},
});
@@ -145,7 +156,7 @@ var RIGHT_KEY = { which: 39 };
callback_args = undefined;
components.toggle.lookup("info-overlay-toggle").goto('markdown-help');
widget.goto('markdown-help');
assert.equal(focused_tab, 1);
assert.equal(tabs[0].class, 'first');
assert.equal(tabs[1].class, 'middle selected');
@@ -162,6 +173,7 @@ var RIGHT_KEY = { which: 39 };
assert.equal(tabs[2].class, 'last selected');
assert.deepEqual(callback_args, ['translated: Search operators', 'search-operators']);
assert.equal(widget.value(), 'translated: Search operators');
assert.equal(widget.value(), callback_value);
// try to crash the key handler
keydown_f.call(tabs[focused_tab], RIGHT_KEY);

View File

@@ -21,7 +21,9 @@ set_global('templates', {});
var noop = function () {};
set_global('blueslip', {});
set_global('blueslip', global.make_zblueslip({
error: false, // Ignore errors. We only check for warnings in this module.
}));
set_global('drafts', {
delete_draft_after_send: noop,
});
@@ -43,6 +45,7 @@ set_global('notifications', {
notify_above_composebox: noop,
clear_compose_notifications: noop,
});
set_global('subs', {});
// Setting these up so that we can test that links to uploads within messages are
// automatically converted to server relative links.
@@ -101,8 +104,12 @@ people.add(bob);
sub.subscribed = false;
stream_data.add_sub('social', sub);
templates.render = function (template_name) {
assert.equal(template_name, 'compose_not_subscribed');
return 'compose_not_subscribed_stub';
};
assert(!compose.validate_stream_message_address_info('social'));
assert.equal($('#compose-error-msg').html(), "translated: <p>You're not subscribed to the stream <b>social</b>.</p><p>Manage your subscriptions <a href='#streams/all'>on your Streams page</a>.</p>");
assert.equal($('#compose-error-msg').html(), 'compose_not_subscribed_stub');
global.page_params.narrow_stream = false;
channel.post = function (payload) {
@@ -121,7 +128,7 @@ people.add(bob);
payload.success(payload.data);
};
assert(!compose.validate_stream_message_address_info('Frontend'));
assert.equal($('#compose-error-msg').html(), "translated: <p>You're not subscribed to the stream <b>Frontend</b>.</p><p>Manage your subscriptions <a href='#streams/all'>on your Streams page</a>.</p>");
assert.equal($('#compose-error-msg').html(), 'compose_not_subscribed_stub');
channel.post = function (payload) {
assert.equal(payload.data.stream, 'Frontend');
@@ -290,9 +297,6 @@ people.add(bob);
}());
(function test_markdown_shortcuts() {
blueslip.error = noop;
blueslip.log = noop;
var queryCommandEnabled = true;
var event = {
keyCode: 66,
@@ -451,8 +455,6 @@ people.add(bob);
}());
(function test_send_message_success() {
blueslip.error = noop;
blueslip.log = noop;
$("#compose-textarea").val('foobarfoobar');
$("#compose-textarea").blur();
$("#compose-send-status").show();
@@ -514,18 +516,18 @@ people.add(bob);
};
transmit.send_message = function (payload, success) {
var single_msg = {
type: 'private',
content: '[foobar](/user_uploads/123456)',
sender_id: 101,
queue_id: undefined,
stream: '',
subject: '',
to: '["alice@example.com"]',
reply_to: 'alice@example.com',
private_message_recipient: 'alice@example.com',
to_user_ids: '31',
local_id: 1,
locally_echoed: true,
type: 'private',
content: '[foobar](/user_uploads/123456)',
sender_id: 101,
queue_id: undefined,
stream: '',
subject: '',
to: '["alice@example.com"]',
reply_to: 'alice@example.com',
private_message_recipient: 'alice@example.com',
to_user_ids: '31',
local_id: 1,
locally_echoed: true,
};
assert.deepEqual(payload, single_msg);
payload.id = stub_state.local_id_counter;
@@ -622,7 +624,7 @@ people.add(bob);
assert(!echo_error_msg_checked);
assert.equal($("#compose-send-button").prop('disabled'), false);
assert.equal($('#compose-error-msg').html(),
'Error sending message: Server says 408');
'Error sending message: Server says 408');
assert.equal($("#compose-textarea").val(), 'foobarfoobar');
assert($("#compose-textarea").is_focused());
assert($("#compose-send-status").visible());
@@ -631,6 +633,8 @@ people.add(bob);
}());
}());
set_global('document', 'document-stub');
(function test_enter_with_preview_open() {
// Test sending a message with content.
compose_state.set_message_type('stream');
@@ -697,12 +701,10 @@ people.add(bob);
};
var compose_finished_event_checked = false;
$.stub_selector(document, {
trigger: function (e) {
assert.equal(e.name, 'compose_finished.zulip');
compose_finished_event_checked = true;
},
});
$(document).trigger = function (e) {
assert.equal(e.name, 'compose_finished.zulip');
compose_finished_event_checked = true;
};
var send_message_called = false;
compose.send_message = function () {
send_message_called = true;
@@ -866,27 +868,27 @@ function test_raw_file_drop(raw_drop_func) {
var keyup_handler_func = $(selector).get_on_handler('keyup');
var set_focused_recipient_checked = false;
var update_faded_messages_checked = false;
var update_all_called = false;
global.compose_fade = {
set_focused_recipient: function (msg_type) {
assert.equal(msg_type, 'private');
set_focused_recipient_checked = true;
},
update_faded_messages: function () {
update_faded_messages_checked = true;
update_all: function () {
update_all_called = true;
},
};
compose_state.set_message_type(false);
keyup_handler_func();
assert(!set_focused_recipient_checked);
assert(!update_faded_messages_checked);
assert(!update_all_called);
compose_state.set_message_type('private');
keyup_handler_func();
assert(set_focused_recipient_checked);
assert(update_faded_messages_checked);
assert(update_all_called);
}());
(function test_trigger_submit_compose_form() {
@@ -915,7 +917,7 @@ function test_raw_file_drop(raw_drop_func) {
var data = {
mentioned: {
email: 'foo@bar.com',
email: 'foo@bar.com',
},
};
@@ -942,10 +944,10 @@ function test_raw_file_drop(raw_drop_func) {
var checks = [
(function () {
var called;
compose_fade.would_receive_message = function (email) {
compose.needs_subscribe_warning = function (email) {
called = true;
assert.equal(email, 'foo@bar.com');
return false;
return true;
};
return function () { assert(called); };
}()),
@@ -975,8 +977,8 @@ function test_raw_file_drop(raw_drop_func) {
data = {
mentioned: {
email: 'foo@bar.com',
full_name: 'Foo Barson',
email: 'foo@bar.com',
full_name: 'Foo Barson',
},
};
@@ -1033,7 +1035,7 @@ function test_raw_file_drop(raw_drop_func) {
(function test_compose_all_everyone_confirm_clicked() {
var handler = $("#compose-all-everyone")
.get_on_handler('click', '.compose-all-everyone-confirm');
.get_on_handler('click', '.compose-all-everyone-confirm');
setup_parents_and_mock_remove('compose-all-everyone',
'compose-all-everyone',
@@ -1057,7 +1059,7 @@ function test_raw_file_drop(raw_drop_func) {
(function test_compose_invite_users_clicked() {
var handler = $("#compose_invite_users")
.get_on_handler('click', '.compose_invite_link');
.get_on_handler('click', '.compose_invite_link');
var subscription = {
stream_id: 102,
name: 'test',
@@ -1082,9 +1084,7 @@ function test_raw_file_drop(raw_drop_func) {
assert(!container_removed);
// !sub will result false here and we check the failure code path.
blueslip.warn = function (err_msg) {
assert.equal(err_msg, 'Stream no longer exists: no-stream');
};
blueslip.set_test_data('warn', 'Stream no longer exists: no-stream');
$('#stream').val('no-stream');
container.data = function (field) {
assert.equal(field, 'useremail');
@@ -1101,6 +1101,8 @@ function test_raw_file_drop(raw_drop_func) {
assert(target.attr('disabled'));
assert(!invite_user_to_stream_called);
assert(!container_removed);
assert.equal(blueslip.get_test_logs('warn').length, 1);
blueslip.clear_test_data();
// !sub will result in true here and we check the success code path.
stream_data.add_sub('test', subscription);
@@ -1122,7 +1124,7 @@ function test_raw_file_drop(raw_drop_func) {
(function test_compose_invite_close_clicked() {
var handler = $("#compose_invite_users")
.get_on_handler('click', '.compose_invite_close');
.get_on_handler('click', '.compose_invite_close');
setup_parents_and_mock_remove('compose_invite_users_close',
'compose_invite_close',
@@ -1142,6 +1144,51 @@ function test_raw_file_drop(raw_drop_func) {
assert(!$("#compose_invite_users").visible());
}());
(function test_compose_not_subscribed_clicked() {
var handler = $("#compose-send-status")
.get_on_handler('click', '.sub_unsub_button');
var subscription = {
stream_id: 102,
name: 'test',
subscribed: false,
};
var compose_not_subscribed_called = false;
subs.sub_or_unsub = function () {
compose_not_subscribed_called = true;
};
setup_parents_and_mock_remove('compose-send-status',
'sub_unsub_button',
'.compose_not_subscribed');
handler(event);
assert(compose_not_subscribed_called);
stream_data.add_sub('test', subscription);
$('#stream').val('test');
$("#compose-send-status").show();
handler(event);
assert(!$("#compose-send-status").visible());
}());
(function test_compose_not_subscribed_close_clicked() {
var handler = $("#compose-send-status")
.get_on_handler('click', '#compose_not_subscribed_close');
setup_parents_and_mock_remove('compose_user_not_subscribed_close',
'compose_not_subscribed_close',
'.compose_not_subscribed');
$("#compose-send-status").show();
handler(event);
assert(!$("#compose-send-status").visible());
}());
event = {
preventDefault: noop,
};
@@ -1175,33 +1222,33 @@ function test_raw_file_drop(raw_drop_func) {
compose_state.set_message_type('stream');
var checks = [
(function () {
var called;
templates.render = function (template_name, context) {
called = true;
assert.equal(template_name, 'compose_private_stream_alert');
assert.equal(context.stream_name, 'Denmark');
return 'fake-compose_private_stream_alert-template';
};
return function () { assert(called); };
}()),
(function () {
var called;
templates.render = function (template_name, context) {
called = true;
assert.equal(template_name, 'compose_private_stream_alert');
assert.equal(context.stream_name, 'Denmark');
return 'fake-compose_private_stream_alert-template';
};
return function () { assert(called); };
}()),
(function () {
var called;
$("#compose_private_stream_alert").append = function (html) {
called = true;
assert.equal(html, 'fake-compose_private_stream_alert-template');
};
return function () { assert(called); };
}()),
(function () {
var called;
$("#compose_private_stream_alert").append = function (html) {
called = true;
assert.equal(html, 'fake-compose_private_stream_alert-template');
};
return function () { assert(called); };
}()),
];
data = {
stream: {
invite_only: true,
name: 'Denmark',
subscribers: Dict.from_array([1]),
},
stream: {
invite_only: true,
name: 'Denmark',
subscribers: Dict.from_array([1]),
},
};
handler({}, data);
@@ -1213,7 +1260,7 @@ function test_raw_file_drop(raw_drop_func) {
(function test_attach_files_compose_clicked() {
var handler = $("#compose")
.get_on_handler("click", "#attach_files");
.get_on_handler("click", "#attach_files");
$('#file_input').clone = function (param) {
assert(param);
};
@@ -1287,7 +1334,7 @@ function test_raw_file_drop(raw_drop_func) {
function test_post_error(error_callback) {
error_callback();
assert.equal($("#preview_content").html(),
'translated: Failed to generate preview');
'translated: Failed to generate preview');
}
function mock_channel_post(msg) {
@@ -1317,7 +1364,7 @@ function test_raw_file_drop(raw_drop_func) {
}
var handler = $("#compose")
.get_on_handler("click", "#markdown_preview");
.get_on_handler("click", "#markdown_preview");
// Tests start here
$("#compose-textarea").val('');
@@ -1326,7 +1373,7 @@ function test_raw_file_drop(raw_drop_func) {
handler(event);
assert.equal($("#preview_content").html(),
'translated: Nothing to preview');
'translated: Nothing to preview');
assert_visibilities();
var make_indicator_called = false;
@@ -1362,12 +1409,12 @@ function test_raw_file_drop(raw_drop_func) {
assert(apply_markdown_called);
assert_visibilities();
assert.equal($("#preview_content").html(),
'Server: foobarfoobar');
'Server: foobarfoobar');
}());
(function test_undo_markdown_preview_clicked() {
var handler = $("#compose")
.get_on_handler("click", "#undo_markdown_preview");
.get_on_handler("click", "#undo_markdown_preview");
$("#compose-textarea").hide();
$("#undo_markdown_preview").show();

View File

@@ -3,17 +3,13 @@ var return_false = function () { return false; };
var return_true = function () { return true; };
set_global('document', {
location: {
},
location: {}, // we need this to load compose.js
});
set_global('page_params', {
use_websockets: false,
});
set_global('$', function () {
});
set_global('$', global.make_zjquery());
set_global('compose_pm_pill', {
@@ -26,6 +22,8 @@ zrequire('util');
zrequire('compose_state');
zrequire('compose_actions');
set_global('document', 'document-stub');
var start = compose_actions.start;
var cancel = compose_actions.cancel;
var get_focus_area = compose_actions._get_focus_area;
@@ -71,7 +69,7 @@ set_global('narrow_state', {
});
set_global('unread_ops', {
mark_message_as_read: noop,
notify_server_message_read: noop,
});
set_global('common', {

View File

@@ -1,5 +1,3 @@
set_global('$', function () {
});
set_global('blueslip', {});
global.blueslip.warn = function () {};
@@ -26,11 +24,11 @@ var bob = {
full_name: 'Bob',
};
people.add(me);
people.add_in_realm(me);
people.initialize_current_user(me.user_id);
people.add(alice);
people.add(bob);
people.add_in_realm(alice);
people.add_in_realm(bob);
(function test_set_focused_recipient() {
@@ -62,9 +60,10 @@ people.add(bob);
compose_fade.set_focused_recipient('stream');
assert(compose_fade.would_receive_message('me@example.com'));
assert(compose_fade.would_receive_message('alice@example.com'));
assert(!compose_fade.would_receive_message('bob@example.com'));
assert.equal(compose_fade.would_receive_message('me@example.com'), true);
assert.equal(compose_fade.would_receive_message('alice@example.com'), true);
assert.equal(compose_fade.would_receive_message('bob@example.com'), false);
assert.equal(compose_fade.would_receive_message('nonrealmuser@example.com'), true);
var good_msg = {
type: 'stream',

View File

@@ -0,0 +1,144 @@
zrequire('compose_pm_pill');
zrequire('input_pill');
zrequire('user_pill');
set_global('$', global.make_zjquery());
set_global('people', {});
var pills = {
pill: {},
};
(function test_pills() {
var othello = {
user_id: 1,
email: 'othello@example.com',
full_name: 'Othello',
};
var iago = {
email: 'iago@zulip.com',
user_id: 2,
full_name: 'Iago',
};
var hamlet = {
email: 'hamlet@example.com',
user_id: 3,
full_name: 'Hamlet',
};
people.get_realm_persons = function () {
return [iago, othello, hamlet];
};
var recipient_stub = $("#private_message_recipient");
var pill_container_stub = $('.pill-container[data-before="You and"]');
recipient_stub.set_parent(pill_container_stub);
var create_item_handler;
var all_pills = {};
pills.appendValidatedData = function (item) {
var id = item.user_id;
assert.equal(all_pills[id], undefined);
all_pills[id] = item;
};
pills.items = function () {
return _.values(all_pills);
};
var text_cleared;
pills.clear_text = function () {
text_cleared = true;
};
var pills_cleared;
pills.clear = function () {
pills_cleared = true;
pills = {
pill: {},
};
all_pills= {};
};
var appendValue_called;
pills.appendValue = function (value) {
appendValue_called = true;
assert.equal(value, 'othello@example.com');
this.appendValidatedData(othello);
};
var get_by_email_called = false;
people.get_by_email = function (user_email) {
get_by_email_called = true;
if (user_email === iago.email) {
return iago;
}
if (user_email === othello.email) {
return othello;
}
};
var get_person_from_user_id_called = false;
people.get_person_from_user_id = function (id) {
get_person_from_user_id_called = true;
if (id === othello.user_id) {
return othello;
}
assert.equal(id, 3);
return hamlet;
};
function test_create_item(handler) {
(function test_rejection_path() {
var item = handler(othello.email, pills.items());
assert(get_by_email_called);
assert.equal(item, undefined);
}());
(function test_success_path() {
get_by_email_called = false;
var res = handler(iago.email, pills.items());
assert(get_by_email_called);
assert.equal(typeof(res), 'object');
assert.equal(res.user_id, iago.user_id);
assert.equal(res.display_value, iago.full_name);
}());
}
function input_pill_stub(opts) {
assert.equal(opts.container, pill_container_stub);
create_item_handler = opts.create_item_from_text;
assert(create_item_handler);
return pills;
}
set_global('input_pill', {
create: input_pill_stub,
});
compose_pm_pill.initialize();
assert(compose_pm_pill.my_pill);
compose_pm_pill.set_from_typeahead(othello);
compose_pm_pill.set_from_typeahead(hamlet);
var user_ids = compose_pm_pill.get_user_ids();
assert.deepEqual(user_ids, [othello.user_id, hamlet.user_id]);
var emails = compose_pm_pill.get_emails();
assert.equal(emails, 'othello@example.com,hamlet@example.com');
var items = compose_pm_pill.get_typeahead_items();
assert.deepEqual(items, [{email: 'iago@zulip.com', user_id: 2, full_name: 'Iago'}]);
test_create_item(create_item_handler);
compose_pm_pill.set_from_emails('othello@example.com');
assert(compose_pm_pill.my_pill);
assert(get_person_from_user_id_called);
assert(pills_cleared);
assert(appendValue_called);
assert(text_cleared);
}());

View File

@@ -1,6 +1,8 @@
set_global('i18n', global.stub_i18n);
zrequire('compose_state');
zrequire('ui_util');
zrequire('pm_conversations');
zrequire('emoji_picker');
zrequire('util');
zrequire('Handlebars', 'handlebars');
zrequire('templates');
@@ -11,6 +13,9 @@ zrequire('stream_data');
zrequire('user_pill');
zrequire('compose_pm_pill');
zrequire('composebox_typeahead');
set_global('md5', function (s) {
return 'md5-' + s;
});
var ct = composebox_typeahead;
var noop = function () {};
@@ -18,34 +23,57 @@ var noop = function () {};
var emoji_stadium = {
emoji_name: 'stadium',
emoji_url: 'TBD',
codepoint: '1f3df',
};
var emoji_tada = {
emoji_name: 'tada',
emoji_url: 'TBD',
codepoint: '1f389',
};
var emoji_moneybag = {
emoji_name: 'moneybag',
emoji_url: 'TBD',
codepoint: '1f4b0',
};
var emoji_japanese_post_office = {
emoji_name: 'japanese_post_office',
emoji_url: 'TBD',
codepoint: '1f3e3',
};
var emoji_panda_face = {
emoji_name: 'panda_face',
emoji_url: 'TBD',
codepoint: '1f43c',
};
var emoji_see_no_evil = {
emoji_name: 'see_no_evil',
emoji_url: 'TBD',
codepoint: '1f648',
};
var emoji_thumbs_up = {
emoji_name: '+1',
emoji_name: 'thumbs_up',
emoji_url: 'TBD',
codepoint: '1f44d',
};
var emoji_thermometer = {
emoji_name: 'thermometer',
emoji_url: 'TBD',
codepoint: '1f321',
};
var emoji_heart = {
emoji_name: 'heart',
emoji_url: 'TBD',
codepoint: '2764',
};
var emoji_headphones = {
emoji_name: 'headphones',
emoji_url: 'TBD',
codepoint: '1f3a7',
};
var emoji_list = [emoji_tada, emoji_moneybag, emoji_stadium, emoji_japanese_post_office,
emoji_panda_face, emoji_see_no_evil, emoji_thumbs_up];
emoji_panda_face, emoji_see_no_evil, emoji_thumbs_up, emoji_thermometer,
emoji_heart, emoji_headphones];
var stream_list = ['Denmark', 'Sweden', 'The Netherlands'];
var sweden_stream = {
name: 'Sweden',
@@ -218,6 +246,8 @@ user_pill.get_user_ids = function () {
expected_value = '{ :octopus: ';
assert.equal(actual_value, expected_value);
// mention
fake_this.completing = 'mention';
var document_stub_trigger1_called = false;
@@ -339,8 +369,10 @@ user_pill.get_user_ids = function () {
assert.deepEqual(actual_value, expected_value);
// options.highlighter()
options.query = 'De'; // Beginning of "Denmark", one of the streams
// provided in stream_list through .source().
// Beginning of "Denmark", one of the streams
// provided in stream_list through .source().
options.query = 'De';
actual_value = options.highlighter('Denmark');
expected_value = '<strong>Denmark</strong>';
assert.equal(actual_value, expected_value);
@@ -446,17 +478,17 @@ user_pill.get_user_ids = function () {
// corresponding parts in bold.
options.query = 'oth';
actual_value = options.highlighter(othello);
expected_value = '<strong>Othello, the Moor of Venice</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">othello@zulip.com</small>\n';
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-othello@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Othello, the Moor of Venice</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">othello@zulip.com</small>\n';
assert.equal(actual_value, expected_value);
options.query = 'Lear';
actual_value = options.highlighter(cordelia);
expected_value = '<strong>Cordelia Lear</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">cordelia@zulip.com</small>\n';
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-cordelia@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Cordelia Lear</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">cordelia@zulip.com</small>\n';
assert.equal(actual_value, expected_value);
options.query = 'othello@zulip.com, co';
actual_value = options.highlighter(cordelia);
expected_value = '<strong>Cordelia Lear</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">cordelia@zulip.com</small>\n';
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-cordelia@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Cordelia Lear</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">cordelia@zulip.com</small>\n';
assert.equal(actual_value, expected_value);
// options.matcher()
@@ -561,12 +593,12 @@ user_pill.get_user_ids = function () {
// content_highlighter.
fake_this = { completing: 'mention', token: 'othello' };
actual_value = options.highlighter.call(fake_this, othello);
expected_value = '<strong>Othello, the Moor of Venice</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">othello@zulip.com</small>\n';
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-othello@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Othello, the Moor of Venice</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">othello@zulip.com</small>\n';
assert.equal(actual_value, expected_value);
fake_this = { completing: 'mention', token: 'hamletcharacters' };
actual_value = options.highlighter.call(fake_this, hamletcharacters);
expected_value = '<strong>hamletcharacters</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">Characters of Hamlet</small>\n';
expected_value = ' <i class="typeahead-image icon icon-vector-group"></i>\n<strong>hamletcharacters</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">Characters of Hamlet</small>\n';
assert.equal(actual_value, expected_value);
// options.matcher()
@@ -599,6 +631,16 @@ user_pill.get_user_ids = function () {
expected_value = [emoji_tada, emoji_stadium];
assert.deepEqual(actual_value, expected_value);
fake_this = { completing: 'emoji', token: 'th' };
actual_value = options.sorter.call(fake_this, [emoji_thermometer, emoji_thumbs_up]);
expected_value = [emoji_thumbs_up, emoji_thermometer];
assert.deepEqual(actual_value, expected_value);
fake_this = { completing: 'emoji', token: 'he' };
actual_value = options.sorter.call(fake_this, [emoji_headphones, emoji_heart]);
expected_value = [emoji_heart, emoji_headphones];
assert.deepEqual(actual_value, expected_value);
fake_this = { completing: 'mention', token: 'co' };
actual_value = options.sorter.call(fake_this, [othello, cordelia]);
expected_value = [cordelia, othello];
@@ -711,9 +753,9 @@ user_pill.get_user_ids = function () {
pm_recipient_blur_called = true;
};
page_params.enter_sends = false; // We manually specify it the first
// time because the click_func
// doesn't exist yet.
page_params.enter_sends = false;
// We manually specify it the first time because the click_func
// doesn't exist yet.
$("#stream").select(noop);
$("#subject").select(noop);
$("#private_message_recipient").select(noop);
@@ -892,20 +934,14 @@ user_pill.get_user_ids = function () {
assert.deepEqual(returned, reference);
}
var all_items = [
{
special_item_text: 'all (Notify everyone)',
email: 'all',
var all_items = _.map(['all', 'everyone', 'stream'], function (mention) {
return {
special_item_text: 'translated: ' + mention +" (Notify stream)",
email: mention,
pm_recipient_count: Infinity,
full_name: 'all',
},
{
special_item_text: 'everyone (Notify everyone)',
email: 'everyone',
pm_recipient_count: Infinity,
full_name: 'everyone',
},
];
full_name: mention,
};
});
var people_with_all = global.people.get_realm_persons().concat(all_items);
var all_mentions = people_with_all.concat(global.user_groups.get_realm_user_groups());
@@ -1090,20 +1126,14 @@ user_pill.get_user_ids = function () {
}());
(function test_typeahead_results() {
var all_items = [
{
special_item_text: 'all (Notify everyone)',
email: 'all',
var all_items = _.map(['all', 'everyone', 'stream'], function (mention) {
return {
special_item_text: 'translated: ' + mention +" (Notify stream)",
email: mention,
pm_recipient_count: Infinity,
full_name: 'all',
},
{
special_item_text: 'everyone (Notify everyone)',
email: 'everyone',
pm_recipient_count: Infinity,
full_name: 'everyone',
},
];
full_name: mention,
};
});
var people_with_all = global.people.get_realm_persons().concat(all_items);
var all_mentions = people_with_all.concat(global.user_groups.get_realm_user_groups());
var stream_list = [denmark_stream, sweden_stream, netherland_stream];
@@ -1135,14 +1165,14 @@ user_pill.get_user_ids = function () {
assert.deepEqual(returned, expected);
}
assert_emoji_matches('da',[{emoji_name: "tada", emoji_url: "TBD"},
{emoji_name: "panda_face", emoji_url: "TBD"}]);
assert_emoji_matches('da',[{emoji_name: "tada", emoji_url: "TBD", codepoint: "1f389"},
{emoji_name: "panda_face", emoji_url: "TBD", codepoint: "1f43c"}]);
assert_emoji_matches('da_', []);
assert_emoji_matches('da ', []);
assert_emoji_matches('panda ', [{emoji_name: "panda_face", emoji_url: "TBD"}]);
assert_emoji_matches('panda_', [{emoji_name: "panda_face", emoji_url: "TBD"}]);
assert_emoji_matches('japanese_post_', [{emoji_name: "japanese_post_office", emoji_url: "TBD"}]);
assert_emoji_matches('japanese post ', [{emoji_name: "japanese_post_office", emoji_url: "TBD"}]);
assert_emoji_matches('panda ', [{emoji_name: "panda_face", emoji_url: "TBD", codepoint: "1f43c"}]);
assert_emoji_matches('panda_', [{emoji_name: "panda_face", emoji_url: "TBD", codepoint: "1f43c"}]);
assert_emoji_matches('japanese_post_', [{emoji_name: "japanese_post_office", emoji_url: "TBD", codepoint: "1f3e3"}]);
assert_emoji_matches('japanese post ', [{emoji_name: "japanese_post_office", emoji_url: "TBD", codepoint: "1f3e3"}]);
assert_emoji_matches('notaemoji', []);
// Autocomplete user mentions by user name.
assert_mentions_matches('cordelia', [cordelia]);

View File

@@ -1,5 +1,9 @@
global.stub_out_jquery();
set_global('page_params', {
development: true,
});
var jsdom = require("jsdom");
global.document = jsdom.jsdom('<!DOCTYPE html><p>Hello world</p>');
var window = jsdom.jsdom().defaultView;
@@ -37,4 +41,12 @@ var copy_and_paste = zrequire('copy_and_paste');
input = '<meta http-equiv="content-type" content="text/html; charset=utf-8"><i style="box-sizing: inherit; color: rgb(0, 0, 0); font-family: Verdana, sans-serif; font-size: 15px; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">This text is italic</i>';
assert.equal(copy_and_paste.paste_handler_converter(input),
'*This text is italic*');
input = '<div class="preview-content"><div class="comment"><div class="comment-body markdown-body js-preview-body" style="min-height: 131px;"><p>Test List:</p><ul><li>Item 1</li><li>Item 2</li></ul></div></div></div>';
assert.equal(copy_and_paste.paste_handler_converter(input),
'Test List:\n* Item 1\n* Item 2');
input = '<div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z ace-ltr focused-line" dir="auto" id="editor-3-ace-line-41"><span>Test List:</span></div><div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z line-list-type-bullet ace-ltr" dir="auto" id="editor-3-ace-line-42"><ul class="listtype-bullet listindent1 list-bullet1"><li><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="end"></span><span class="ace-line-pocket" data-faketext="" contenteditable="false"></span><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="start"></span><span>Item 1</span></li></ul></div><div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z line-list-type-bullet ace-ltr" dir="auto" id="editor-3-ace-line-43"><ul class="listtype-bullet listindent1 list-bullet1"><li><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="end"></span><span class="ace-line-pocket" data-faketext="" contenteditable="false"></span><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="start"></span><span>Item 2</span></li></ul></div>';
assert.equal(copy_and_paste.paste_handler_converter(input),
'Test List:\n* Item 1\n* Item 2');
}());

View File

@@ -1,4 +1,4 @@
set_global('blueslip', {});
set_global('blueslip', global.make_zblueslip());
(function test_basic() {
var d = new Dict();
@@ -57,9 +57,7 @@ set_global('blueslip', {});
}());
(function test_undefined_keys() {
global.blueslip.error = function (msg) {
assert.equal(msg, "Tried to call a Dict method with an undefined key.");
};
blueslip.set_test_data('error', 'Tried to call a Dict method with an undefined key.');
var d = new Dict();
@@ -70,6 +68,9 @@ set_global('blueslip', {});
assert.equal(d.has(undefined), false);
assert.strictEqual(d.get(undefined), undefined);
assert.equal(blueslip.get_test_logs('error').length, 4);
blueslip.clear_test_data();
}());
(function test_restricted_keys() {

View File

@@ -360,6 +360,7 @@ var event_fixtures = {
name: 'devel',
stream_id: 42,
subscribers: ['alice@example.com', 'bob@example.com'],
email_address: 'devel+0138515295f4@zulipdev.com:9991',
// etc.
},
],
@@ -491,6 +492,36 @@ var event_fixtures = {
{id: 2, name: 'hobbies', type: 1},
],
},
user_group__add: {
type: 'user_group',
op: 'add',
group: {
name: 'Mobile',
id: '1',
members: [1],
},
},
user_group__add_members: {
type: 'user_group',
op: 'add_members',
group_id: 1,
user_ids: [2],
},
user_group__remove_members: {
type: 'user_group',
op: 'remove_members',
group_id: 3,
user_ids: [99, 100],
},
user_group__update: {
type: 'user_group',
op: 'update',
group_id: 3,
data: {
name: 'Frontend',
description: 'All Frontend people',
},
},
};
function assert_same(actual, expected) {
@@ -511,6 +542,46 @@ with_overrides(function (override) {
});
with_overrides(function (override) {
// User groups
var event = event_fixtures.user_group__add;
override('settings_user_groups.reload', noop);
global.with_stub(function (stub) {
override('user_groups.add', stub.f);
dispatch(event);
var args = stub.get_args('group');
assert_same(args.group, event.group);
});
event = event_fixtures.user_group__add_members;
global.with_stub(function (stub) {
override('user_groups.add_members', stub.f);
dispatch(event);
var args = stub.get_args('group_id', 'user_ids');
assert_same(args.group_id, event.group_id);
assert_same(args.user_ids, event.user_ids);
});
event = event_fixtures.user_group__remove_members;
global.with_stub(function (stub) {
override('user_groups.remove_members', stub.f);
dispatch(event);
var args = stub.get_args('group_id', 'user_ids');
assert_same(args.group_id, event.group_id);
assert_same(args.user_ids, event.user_ids);
});
event = event_fixtures.user_group__update;
global.with_stub(function (stub) {
override('user_groups.update', stub.f);
dispatch(event);
var args = stub.get_args('event');
assert_same(args.event.group_id, event.group_id);
assert_same(args.event.data.name, event.data.name);
assert_same(args.event.data.description, event.data.description);
});
});
with_overrides(function (override) {
// custom profile fields
var event = event_fixtures.custom_profile_fields;
@@ -749,6 +820,7 @@ with_overrides(function (override) {
event = event_fixtures.realm_user__remove;
global.with_stub(function (stub) {
override('people.deactivate', stub.f);
override('stream_data.remove_deactivated_user_from_all_streams', noop);
dispatch(event);
var args = stub.get_args('person');
assert_same(args.person, event.person);
@@ -801,15 +873,21 @@ with_overrides(function (override) {
});
var event = event_fixtures.subscription__add;
global.with_stub(function (stub) {
override('stream_data.get_sub_by_id', function (stream_id) {
return {stream_id: stream_id};
global.with_stub(function (subscription_stub) {
global.with_stub(function (stream_email_stub) {
override('stream_data.get_sub_by_id', function (stream_id) {
return {stream_id: stream_id};
});
override('stream_events.mark_subscribed', subscription_stub.f);
override('stream_data.update_stream_email_address', stream_email_stub.f);
dispatch(event);
var args = subscription_stub.get_args('sub', 'subscribers');
assert_same(args.sub.stream_id, event.subscriptions[0].stream_id);
assert_same(args.subscribers, event.subscriptions[0].subscribers);
args = stream_email_stub.get_args('sub', 'email_address');
assert_same(args.email_address, event.subscriptions[0].email_address);
assert_same(args.sub.stream_id, event.subscriptions[0].stream_id);
});
override('stream_events.mark_subscribed', stub.f);
dispatch(event);
var args = stub.get_args('sub', 'subscribers');
assert_same(args.sub.stream_id, event.subscriptions[0].stream_id);
assert_same(args.subscribers, event.subscriptions[0].subscribers);
});
event = event_fixtures.subscription__peer_add;
@@ -935,7 +1013,7 @@ with_overrides(function (override) {
});
});
// mark_message_as_read requires message_store and these dependencies.
// notify_server_message_read requires message_store and these dependencies.
zrequire('unread_ops');
zrequire('unread');
zrequire('topic_data');

View File

@@ -95,33 +95,33 @@ var draft_2 = {
localStorage.clear();
(function test_addDraft() {
stub_timestamp(1, function () {
var expected = _.clone(draft_1);
expected.updatedAt = 1;
var id = draft_model.addDraft(_.clone(draft_1));
stub_timestamp(1, function () {
var expected = _.clone(draft_1);
expected.updatedAt = 1;
var id = draft_model.addDraft(_.clone(draft_1));
assert.deepEqual(ls.get("drafts")[id], expected);
});
assert.deepEqual(ls.get("drafts")[id], expected);
});
}());
localStorage.clear();
(function test_editDraft() {
stub_timestamp(2, function () {
ls.set("drafts", { id1: draft_1 });
var expected = _.clone(draft_2);
expected.updatedAt = 2;
draft_model.editDraft("id1", _.clone(draft_2));
stub_timestamp(2, function () {
ls.set("drafts", { id1: draft_1 });
var expected = _.clone(draft_2);
expected.updatedAt = 2;
draft_model.editDraft("id1", _.clone(draft_2));
assert.deepEqual(ls.get("drafts").id1, expected);
});
assert.deepEqual(ls.get("drafts").id1, expected);
});
}());
localStorage.clear();
(function test_deleteDraft() {
ls.set("drafts", { id1: draft_1 });
draft_model.deleteDraft("id1");
ls.set("drafts", { id1: draft_1 });
draft_model.deleteDraft("id1");
assert.deepEqual(ls.get("drafts"), {});
assert.deepEqual(ls.get("drafts"), {});
}());
}());

View File

@@ -3,6 +3,7 @@ set_global('page_params', {
emojiset: 'google',
});
set_global('upload_widget', {});
set_global('blueslip', global.make_zblueslip());
zrequire('emoji_codes', 'generated/emoji/emoji_codes');
zrequire('emoji');
@@ -66,15 +67,10 @@ zrequire('util');
canonical_name = emoji.get_canonical_name('+1');
assert.equal(canonical_name, '+1');
var errored = false;
set_global('blueslip', {
error: function (error) {
assert.equal(error, "Invalid emoji name: non_existent");
errored = true;
},
});
blueslip.set_test_data('error', 'Invalid emoji name: non_existent');
emoji.get_canonical_name('non_existent');
assert(errored);
assert.equal(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
}());
(function test_translate_emoticons_to_names() {
@@ -99,15 +95,16 @@ zrequire('util');
{name: 'between symbols', original: 'Hello.<original>! World.', expected: 'Hello.<original>! World.'},
{name: 'before end of sentence', original: 'Hello <original>!', expected: 'Hello <converted>!'},
];
Object.keys(emoji.EMOTICON_CONVERSIONS).forEach(key => {
testcases.forEach(t => {
var converted_value = `:${emoji.EMOTICON_CONVERSIONS[key]}:`;
t = Object.assign({}, t); // circumvent copy by reference.
t.original = t.original.replace(/(<original>)/g, key);
t.expected = t.expected.replace(/(<original>)/g, key);
t.expected = t.expected.replace(/(<converted>)/g, converted_value);
var result = emoji.translate_emoticons_to_names(t.original);
assert.equal(result, t.expected);
_.each(emoji.EMOTICON_CONVERSIONS, (full_name, shortcut) => {
_.each(testcases, (t) => {
var converted_value = ':' + full_name + ':';
var original = t.original;
var expected = t.expected;
original = original.replace(/(<original>)/g, shortcut);
expected = expected.replace(/(<original>)/g, shortcut)
.replace(/(<converted>)/g, converted_value);
var result = emoji.translate_emoticons_to_names(original);
assert.equal(result, expected);
});
});
}());

View File

@@ -22,6 +22,14 @@ function blocked_older() {
assert.equal(fetch_status.can_load_older_messages(), false);
}
function has_found_newest() {
assert.equal(fetch_status.has_found_newest(), true);
}
function has_not_found_newest() {
assert.equal(fetch_status.has_found_newest(), false);
}
(function test_basics() {
reset();
@@ -29,12 +37,14 @@ function blocked_older() {
blocked_newer();
blocked_older();
has_not_found_newest();
fetch_status.finish_initial_narrow({
found_oldest: true,
found_newest: true,
});
has_found_newest();
blocked_newer();
blocked_older();

View File

@@ -2,8 +2,10 @@ zrequire('util');
zrequire('unread');
zrequire('stream_data');
zrequire('people');
zrequire('Handlebars', 'handlebars');
zrequire('Filter', 'js/filter');
set_global('message_store', {});
set_global('page_params', {});
set_global('feature_flags', {});
@@ -77,6 +79,7 @@ function assert_same_operators(result, terms) {
assert(filter.is_search());
assert(! filter.can_apply_locally());
assert(! filter.is_exactly('stream'));
// If our only stream operator is negated, then for all intents and purposes,
// we don't consider ourselves to have a stream operator, because we don't
@@ -132,6 +135,7 @@ function assert_same_operators(result, terms) {
var filter = new Filter(operators);
assert.deepEqual(filter.operands('stream'), ['foo']);
assert(filter.is_exactly('stream'));
}());
(function test_public_operators() {
@@ -143,6 +147,7 @@ function assert_same_operators(result, terms) {
var filter = new Filter(operators);
assert_same_operators(filter.public_operators(), operators);
assert(!filter.is_exactly('stream'));
global.page_params.narrow_stream = 'default';
operators = [
@@ -585,7 +590,7 @@ function make_sub(name, stream_id) {
{operator: 'stream', operand: 'devel'},
{operator: 'topic', operand: 'JS'},
];
string = 'stream devel > JS';
string = 'stream devel &gt; JS';
assert.equal(Filter.describe(narrow), string);
narrow = [
@@ -665,6 +670,175 @@ function make_sub(name, stream_id) {
assert.equal(Filter.describe(narrow), string);
}());
(function test_is_functions() {
var terms = [
{operator: 'stream', operand: 'My Stream'},
];
var filter = new Filter(terms);
assert.equal(filter.is_exactly('stream'), true);
assert.equal(filter.is_exactly('stream', 'topic'), false);
assert.equal(filter.is_exactly('pm-with'), false);
terms = [
// try a non-orthodox ordering
{operator: 'topic', operand: 'My Topic'},
{operator: 'stream', operand: 'My Stream'},
];
filter = new Filter(terms);
assert.equal(filter.can_bucket_by('stream'), true);
assert.equal(filter.can_bucket_by('stream', 'topic'), true);
assert.equal(filter.is_exactly('stream'), false);
assert.equal(filter.is_exactly('stream', 'topic'), true);
assert.equal(filter.is_exactly('pm-with'), false);
assert.equal(filter.can_bucket_by('pm-with'), false);
terms = [
{operator: 'stream', operand: 'My Stream', negated: true},
{operator: 'topic', operand: 'My Topic'},
];
filter = new Filter(terms);
assert.equal(filter.can_bucket_by('stream'), false);
assert.equal(filter.can_bucket_by('stream', 'topic'), false);
assert.equal(filter.is_exactly('stream'), false);
assert.equal(filter.is_exactly('stream', 'topic'), false);
assert.equal(filter.is_exactly('pm-with'), false);
terms = [
{operator: 'pm-with', operand: 'foo@example.com', negated: true},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('stream'), false);
assert.equal(filter.is_exactly('stream', 'topic'), false);
assert.equal(filter.is_exactly('pm-with'), false);
terms = [
{operator: 'pm-with', operand: 'foo@example.com,bar@example.com'},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('stream'), false);
assert.equal(filter.is_exactly('stream', 'topic'), false);
assert.equal(filter.is_exactly('pm-with'), true);
assert.equal(filter.is_exactly('is-mentioned'), false);
assert.equal(filter.is_exactly('is-private'), false);
terms = [
{operator: 'is', operand: 'private'},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('is-mentioned'), false);
assert.equal(filter.is_exactly('is-private'), true);
terms = [
{operator: 'is', operand: 'mentioned'},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('is-mentioned'), true);
assert.equal(filter.is_exactly('is-private'), false);
terms = [
{operator: 'is', operand: 'mentioned'},
{operator: 'is', operand: 'starred'},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('is-mentioned'), false);
assert.equal(filter.is_exactly('is-private'), false);
assert.equal(filter.can_bucket_by('is-mentioned'), true);
assert.equal(filter.can_bucket_by('is-private'), false);
// The call below returns false for somewhat arbitrary
// reasons -- we say is-private has precedence over
// is-starred.
assert.equal(filter.can_bucket_by('is-starred'), false);
terms = [
{operator: 'is', operand: 'mentioned', negated: true},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('is-mentioned'), false);
assert.equal(filter.is_exactly('is-private'), false);
}());
(function test_term_type() {
function assert_term_type(term, expected_term_type) {
assert.equal(Filter.term_type(term), expected_term_type);
}
function term(operator, operand, negated) {
return {
operator: operator,
operand: operand,
negated: negated,
};
}
assert_term_type(term('stream', 'whatever'), 'stream');
assert_term_type(term('pm-with', 'whomever'), 'pm-with');
assert_term_type(term('pm-with', 'whomever', true), 'not-pm-with');
assert_term_type(term('is', 'private'), 'is-private');
assert_term_type(term('has', 'link'), 'has-link');
assert_term_type(term('has', 'attachment', true), 'not-has-attachment');
function assert_term_sort(in_terms, expected) {
const sorted_terms = Filter.sorted_term_types(in_terms);
assert.deepEqual(sorted_terms, expected);
}
assert_term_sort(
['topic', 'stream', 'sender'],
['stream', 'topic', 'sender']
);
assert_term_sort(
['has-link', 'near', 'is-unread', 'pm-with'],
['pm-with', 'near', 'is-unread', 'has-link']
);
assert_term_sort(
['bogus', 'stream', 'topic'],
['stream', 'topic', 'bogus']
);
assert_term_sort(
['stream', 'topic', 'stream'],
['stream', 'stream', 'topic']
);
const terms = [
{operator: 'topic', operand: 'lunch'},
{operator: 'sender', operand: 'steve@foo.com'},
{operator: 'stream', operand: 'Verona'},
];
const filter = new Filter(terms);
const term_types = filter.sorted_term_types();
assert.deepEqual(term_types, ['stream', 'topic', 'sender']);
}());
(function test_first_valid_id_from() {
const terms = [
{operator: 'is', operand: 'alerted'},
];
const filter = new Filter(terms);
const messages = {
5: { id: 5, alerted: true },
10: { id: 10 },
20: { id: 20, alerted: true },
30: { id: 30, type: 'stream' },
40: { id: 40, alerted: false },
};
const msg_ids = [10, 20, 30, 40];
message_store.get = () => {};
assert.equal(filter.first_valid_id_from(msg_ids), undefined);
message_store.get = (msg_id) => messages[msg_id];
assert.equal(filter.first_valid_id_from(msg_ids), 20);
}());
(function test_update_email() {
var terms = [
{operator: 'pm-with', operand: 'steve@foo.com'},

View File

@@ -0,0 +1,727 @@
// This is a general tour of how to write node tests that
// may also give you some quick insight on how the Zulip
// browser app is constructed. Let's start with testing
// a function from util.js.
//
// The most basic unit tests load up code, call functions,
// and assert truths:
zrequire('util');
assert(!util.is_all_or_everyone_mentioned('boring text'));
assert(util.is_all_or_everyone_mentioned('mention @**everyone**'));
// Let's test with people.js next. We'll show this technique:
// * get a false value
// * change the data
// * get a true value
zrequire('people');
const isaac = {
email: 'isaac@example.com',
user_id: 30,
full_name: 'Isaac Newton',
};
assert(!people.is_known_user_id(isaac.user_id));
people.add(isaac);
assert(people.is_known_user_id(isaac.user_id));
// The global.people object is a very fundamental object in the
// Zulip app. You can learn a lot more about it by reading
// the tests in people.js in the same directory as this file.
// Let's create the current user, which some future tests will
// require.
var me = {
email: 'me@example.com',
user_id: 31,
full_name: 'Me Myself',
};
people.add(me);
people.initialize_current_user(me.user_id);
// Let's look at stream_data next, and we will start by putting
// some data at module scope (since it may be useful for future
// tests):
const denmark_stream = {
color: 'blue',
name: 'Denmark',
stream_id: 101,
subscribed: false,
};
// We often use IIFEs (immediately invoked function expressions)
// to make our tests more self-containted.
zrequire('stream_data');
(function test_stream_data() {
assert.equal(stream_data.get_sub_by_name('Denmark'), undefined);
stream_data.add_sub('Denmark', denmark_stream);
const sub = stream_data.get_sub_by_name('Denmark');
assert.equal(sub.color, 'blue');
}());
// Hopefully the basic patterns for testing data-oriented modules
// are starting to become apparent. To reinforce that, we will present
// few more examples that also expose you to some of our core
// data objects. Also, we start testing some objects that have
// deeper dependencies.
const messages = {
isaac_to_denmark_stream: {
id: 400,
sender_id: isaac.user_id,
stream_id: denmark_stream.stream_id,
type: 'stream',
flags: ['has_alert_word'],
subject: 'copenhagen',
// note we don't have every field that a "real" message
// would have, and that can be fine
},
};
// We are going to test a core module called messages_store.js. It
// depends on some code that we aren't really interested in testing,
// so let's create some stub functions that do nothing.
const noop = () => undefined;
set_global('alert_words', {});
set_global('composebox_typeahead', {});
alert_words.process_message = noop;
composebox_typeahead.add_topic = noop;
// We can also bring in real code:
zrequire('recent_senders');
zrequire('topic_data');
// And finally require the module that we will test directly:
zrequire('message_store');
(function test_message_store() {
// Our test runner automatically sets _ for us.
// See http://underscorejs.org/ for help on that library.
var in_message = _.clone(messages.isaac_to_denmark_stream);
assert.equal(in_message.alerted, undefined);
message_store.set_message_booleans(in_message);
assert.equal(in_message.alerted, true);
// Let's add a message into our message_store via
// add_message_metadata.
assert.equal(message_store.get(in_message.id), undefined);
message_store.add_message_metadata(in_message);
const message = message_store.get(in_message.id);
assert.equal(message, in_message);
// There are more side effects.
const topic_names = topic_data.get_recent_names(denmark_stream.stream_id);
assert.deepEqual(topic_names, ['copenhagen']);
}());
// Tracking unread messages is a very fundamental part of the Zulip
// app, and we use the unread object to track unread messages.
zrequire('unread');
(function test_unread() {
const stream_id = denmark_stream.stream_id;
const topic_name = 'copenhagen';
assert.equal(unread.num_unread_for_topic(stream_id, topic_name), 0);
var in_message = _.clone(messages.isaac_to_denmark_stream);
message_store.set_message_booleans(in_message);
unread.process_loaded_messages([in_message]);
assert.equal(unread.num_unread_for_topic(stream_id, topic_name), 1);
}());
// In the Zulip app you can narrow your message stream by topic, by
// sender, by PM recipient, by search keywords, etc. We will discuss
// narrows more broadly, but first let's test out a core piece of
// code that makes things work.
// Some quick housekeeping: Let's clear page_params, which is a data
// structure that the server sends down to us when the app starts. We
// prefer to test with a clean slate.
set_global('page_params', {});
// We use the second argument of zrequire to find the location of the
// Filter class.
zrequire('Filter', 'js/filter');
(function test_filter() {
const filter_terms = [
{operator: 'stream', operand: 'Denmark'},
{operator: 'topic', operand: 'copenhagen'},
];
const filter = new Filter(filter_terms);
const predicate = filter.predicate();
// We don't need full-fledged messages to test the gist of
// our filter. If there are details that are distracting from
// your test, you should not feel guilty about removing them.
assert.equal(predicate({type: 'personal'}), false);
assert.equal(predicate({
type: 'stream',
stream_id: denmark_stream.stream_id,
subject: 'does not match filter',
}), false);
assert.equal(predicate({
type: 'stream',
stream_id: denmark_stream.stream_id,
subject: 'copenhagen',
}), true);
}());
// We have a "narrow" abstraction that sits roughly on top of the
// "filter" abstraction. If you are in a narrow, we track the
// state with the narrow_state module.
zrequire('narrow_state');
(function test_narrow_state() {
// As we often do, first make assertions about the starting
// state:
assert.equal(narrow_state.stream(), undefined);
// Now set the state.
const filter_terms = [
{operator: 'stream', operand: 'Denmark'},
{operator: 'topic', operand: 'copenhagen'},
];
const filter = new Filter(filter_terms);
narrow_state.set_current_filter(filter);
assert.equal(narrow_state.stream(), 'Denmark');
assert.equal(narrow_state.topic(), 'copenhagen');
}());
/*
Let's step back and review what we've done so far.
We've used fairly straightforward testing techniques
to explore the following modules:
filter
message_store
narrow_state
people
stream_data
util
We haven't gone deep on any of these objects, but if
you are interested, all of these objects have test
suites that have 100% line coverage on the modules
that implement those objects. For example, you can look
at people.js in this directory for more tests on the
people object.
We can quickly review some testing concepts:
zrequire - bring in real code
set_global - create stubs
IIFE - enclose tests in their own scope
assert.equal - verify results
------
It's time to elaborate a bit on set_global.
First, some context. When we test certain objects,
we don't always want to test all the code they
depend on. Often we want to completely ignore the
interactions with certain objects; other times, we
will want to simulate some behavior of the objects
we depend on without bringing in all the implementation
details.
Also, our test runner runs many tests back to back.
Between each test we need to essentially reset the global
object back to its original state, so that state doesn't
leak between tests.
That's where set_global comes in. When you call
set_global, it updates the global namespace with an
object that you specify in the **test**, not real
code. Using set_global explicitly tells your test
reader what your testing boundaries are between "real"
code and "simulated" code. Finally, and perhaps most
importantly, the test runner will prevent this state
from leaking into the next test (and "zrequire" has
the same behavior attached to it as well).
------
Let's talk about our next steps.
An app is pretty useless without an actual data source.
One of the primary ways that a Zulip client gets data
is through events. (We also get data at page load, and
we can also ask the server for data, but that's not in
the scope of this conversation yet.)
Chat systems are dynamic. If an admin adds a user, or
if a user sends a messages, the server immediately sends
events to all clients so that they can reflect appropriate
changes in their UI. We're not going to discuss the entire
"full stack" mechanism here. Instead, we'll focus on
the client code, starting at the boundary where we
process events.
Let's just get started...
*/
zrequire('server_events_dispatch');
// We will use Bob in several tests.
const bob = {
email: 'bob@example.com',
user_id: 33,
full_name: 'Bob Roberts',
};
(function test_add_user_event() {
const event = {
type: 'realm_user',
op: 'add',
person: bob,
};
assert(!people.is_known_user_id(bob.user_id));
server_events_dispatch.dispatch_normal_event(event);
assert(people.is_known_user_id(bob.user_id));
}());
/*
It's actually a little surprising that adding a user does
not have side effects beyond the people object. I guess
we don't immediately update the buddy list, but that's
because the buddy list gets updated on the next server
fetch.
Let's try an update next. To make this work, we will want
to put some stub objects into the global namespace (as
opposed to using the "real" code).
*/
set_global('activity', {});
set_global('message_live_update', {});
set_global('pm_list', {});
set_global('settings_users', {});
zrequire('user_events');
(function test_update_user_event() {
const new_bob = {
email: 'bob@example.com',
user_id: bob.user_id,
full_name: 'The Artist Formerly Known as Bob',
};
const event = {
type: 'realm_user',
op: 'update',
person: new_bob,
};
// We have to stub a few things:
activity.redraw = noop;
message_live_update.update_user_full_name = noop;
pm_list.update_private_messages = noop;
settings_users.update_user_data = noop;
// Dispatch the realm_user/update event, which will update
// data structures and have other side effects that are
// stubbed out above.
server_events_dispatch.dispatch_normal_event(event);
const user = people.get_person_from_user_id(bob.user_id);
// Verify that the code actually did its main job:
assert.equal(user.full_name, 'The Artist Formerly Known as Bob');
}());
/*
Our test verifies that the update events leads to a name change
inside the people object, but it obviously kind of glosses over
the other interactions.
We can go a step further and verify the sequence of of operations
that happen during an event. This concept is called "mocking",
and you can find libraries to help do mocking. Here we will
just build our own lightweight mocking system, which is almost
trivially easy to do in a language like Javascript.
*/
function test_helper() {
var events = [];
return {
redirect: (module_name, func_name) => {
const full_name = module_name + '.' + func_name;
global[module_name][func_name] = () => {
events.push(full_name);
};
},
events: events,
};
}
/*
Our test_helper will allow us to redirect methods to an
events array, and we can then later verify that the sequence
of side effect is as predicted.
(Note that for now we don't simulate return values nor do we
inspect the arguments to these functions. We could easily
extend our helper to do more.)
The forthcoming example is a pretty extreme example, where we
are calling a pretty high level method that dispatches
a lot of its work out to other objects.
*/
set_global('home_msg_list', {});
set_global('message_list', {});
set_global('message_util', {});
set_global('notifications', {});
set_global('resize', {});
set_global('stream_list', {});
set_global('unread_ops', {});
set_global('unread_ui', {});
zrequire('message_events');
(function test_insert_message() {
const helper = test_helper();
const new_message = {
sender_id: isaac.user_id,
id: 1001,
content: 'example content',
};
assert.equal(message_store.get(new_message.id), undefined);
helper.redirect('activity', 'process_loaded_messages');
helper.redirect('message_util', 'add_messages');
helper.redirect('message_util', 'insert_new_messages');
helper.redirect('notifications', 'received_messages');
helper.redirect('resize', 'resize_page_components');
helper.redirect('stream_list', 'update_streams_sidebar');
helper.redirect('unread_ops', 'process_visible');
helper.redirect('unread_ui', 'update_unread_counts');
narrow_state.reset_current_filter();
message_events.insert_new_messages([new_message]);
// Even though we have stubbed a *lot* of code, our
// tests can still verify the main "narrative" of how
// the code invokes various objects when a new message
// comes in:
assert.deepEqual(helper.events, [
'message_util.add_messages',
'message_util.add_messages',
'activity.process_loaded_messages',
'unread_ui.update_unread_counts',
'resize.resize_page_components',
'unread_ops.process_visible',
'notifications.received_messages',
'stream_list.update_streams_sidebar',
]);
// Despite all of our stubbing/mocking, the call to
// insert_new_messages will have created a very important
// side effect that we can verify:
const inserted_message = message_store.get(new_message.id);
assert.equal(inserted_message.id, new_message.id);
assert.equal(inserted_message.content, 'example content');
}());
/*
The previous example starts to get us out of the data layer of
the app and into more interesting interactions.
When a new message comes in, we update the three major
panes of the app:
* left sidebar - stream list
* middle pane - message view
* right sidebar - buddy list (aka "activity" list)
These are reflected by the following calls:
stream_list.update_streams_sidebar
message_util.add_messages
activity.process_loaded_messages
For now, though, let's focus on another side effect
of processing incoming messages:
unread_ops.process_visible
When new messages come in, they are often immediately
visible to users, so the app will communicate this
back to the server by calling unread_ops.process_visible.
In order to unit test this, we don't want to require
an actual server to be running. Instead, this example
will stub many of the "boundaries" to focus on the
core behavior.
*/
set_global('channel', {});
set_global('feature_flags', {});
set_global('home_msg_list', {});
set_global('message_list', {});
set_global('message_viewport', {});
zrequire('message_flags');
zrequire('unread_ops');
(function test_unread_ops() {
(function set_up() {
const test_messages = [
{
id: 50,
type: 'stream',
stream_id: denmark_stream.stream_id,
subject: 'copenhagen',
unread: true,
},
];
// Make our test message appear to be unread, so that
// we then need to subsequently process them as read.
unread.process_loaded_messages(test_messages);
// Make our window appear visible.
notifications.window_has_focus = () => true;
// Make our "test" message appear visible.
message_viewport.visible_messages = () => test_messages;
// Make us not be in a narrow (somewhat hackily).
message_list.narrowed = undefined;
set_global('current_msg_list', 'not-narrowed-stub');
// Ignore these interactions for now:
home_msg_list.show_message_as_read = noop;
message_list.all = {};
message_list.all.show_message_as_read = noop;
notifications.close_notification = noop;
}());
// Set up a way to capture the options passed in to channel.post.
var channel_post_opts;
channel.post = (opts) => {
channel_post_opts = opts;
};
// Do the main thing we're testing!
unread_ops.process_visible();
// The most important side effect of the above call is that
// we post info to the server. We can verify that the correct
// url and parameters are specified:
assert.deepEqual(channel_post_opts, {
url: '/json/messages/flags',
idempotent: true,
data: { messages: '[50]', op: 'add', flag: 'read' },
success: channel_post_opts.success,
});
}());
/*
Next we will explore this function:
stream_list.update_streams_sidebar
To make this test work, we will create a somewhat elaborate
function that fills in for jQuery (https://jquery.com/), so that
one boundary of our tests is how stream_list.js calls into
stream_list to manipulate DOM.
*/
set_global('topic_list', {});
zrequire('stream_sort');
zrequire('stream_list');
const social_stream = {
color: 'red',
name: 'Social',
stream_id: 102,
subscribed: true,
};
(function set_up_filter() {
stream_data.add_sub('Social', social_stream);
const filter_terms = [
{operator: 'stream', operand: 'Social'},
{operator: 'topic', operand: 'lunch'},
];
const filter = new Filter(filter_terms);
narrow_state.filter = () => filter;
narrow_state.active = () => true;
}());
function jquery_elem() {
// We create basic stubs for jQuery elements, so they
// just work. We can extend these in cases where we want
// more detailed testing.
var elem = {};
elem.expectOne = () => elem;
elem.removeClass = () => elem;
elem.empty = () => elem;
return elem;
}
function make_jquery_helper() {
const stream_list_filter = jquery_elem();
stream_list_filter.is = () => true;
stream_list_filter.val = () => '';
const stream_filters = jquery_elem();
var appended_data;
stream_filters.append = function (data) {
appended_data = data;
};
function fake_jquery(selector) {
switch (selector) {
case '#stream_filters li.highlighted_stream':
return jquery_elem();
case '.stream-list-filter':
return stream_list_filter;
case '#stream_filters li.narrow-filter':
return jquery_elem();
case 'ul#stream_filters li':
return jquery_elem();
case '.stream-filters-label':
return jquery_elem();
case '#stream_filters':
return stream_filters;
default:
throw Error('unknown selector: ' + selector);
}
}
set_global('$', fake_jquery);
return {
verify_actions: () => {
const expected_data_to_append = [
[
'stream stub',
],
];
assert.deepEqual(appended_data,
expected_data_to_append);
},
};
}
function make_topic_list_helper() {
// We want to make sure that updating a stream_list
// closes the topic list and then rebuilds it. We don't
// care about the implementation details of topic_list for
// now, just that it is invoked properly.
topic_list.active_stream_id = () => undefined;
var topic_list_closed;
topic_list.close = () => {
topic_list_closed = true;
};
var topic_list_rebuilt;
topic_list.rebuild = () => {
topic_list_rebuilt = true;
};
return {
verify_actions: () => {
assert(topic_list_closed);
assert(topic_list_rebuilt);
},
};
}
function make_sidebar_helper() {
var updated_whether_active;
function row_widget() {
return {
update_whether_active: () => {
updated_whether_active = true;
},
get_li: () => ['stream stub'],
};
}
stream_list.stream_sidebar.set_row(social_stream.stream_id, row_widget());
stream_list.stream_cursor = {
redraw: noop,
};
return {
verify_actions: () => {
assert(updated_whether_active);
},
};
}
(function test_stream_list() {
const jquery_helper = make_jquery_helper();
const sidebar_helper = make_sidebar_helper();
const topic_list_helper = make_topic_list_helper();
var streams_shown;
stream_list.show_all_streams = () => {
streams_shown = true;
};
// This is what we are testing!
stream_list.update_streams_sidebar();
assert(streams_shown);
jquery_helper.verify_actions();
sidebar_helper.verify_actions();
topic_list_helper.verify_actions();
}());

View File

@@ -0,0 +1,54 @@
zrequire('hash_util');
zrequire('stream_data');
zrequire('people');
var hamlet = {
user_id: 1,
email: 'hamlet@example.com',
full_name: 'Hamlet',
};
people.add_in_realm(hamlet);
var sub = {
stream_id: 99,
name: 'frontend',
};
stream_data.add_sub(sub.name, sub);
(function test_hash_util() {
// Test encodeHashComponent
var str = 'https://www.zulipexample.com';
var result1 = hash_util.encodeHashComponent(str);
assert.equal(result1, 'https.3A.2F.2Fwww.2Ezulipexample.2Ecom');
// Test decodeHashComponent
var result2 = hash_util.decodeHashComponent(result1);
assert.equal(result2, str);
// Test encode_operand and decode_operand
function encode_decode_operand(operator, operand, expected_val) {
var encode_result = hash_util.encode_operand(operator, operand);
assert.equal(encode_result, expected_val);
var new_operand = encode_result;
var decode_result = hash_util.decode_operand(operator, new_operand);
assert.equal(decode_result, operand);
}
var operator = 'sender';
var operand = hamlet.email;
encode_decode_operand(operator, operand, '1-hamlet');
operator = 'stream';
operand = 'frontend';
encode_decode_operand(operator, operand, '99-frontend');
operator = 'topic';
operand = 'testing 123';
encode_decode_operand(operator, operand, 'testing.20123');
}());

View File

@@ -3,6 +3,36 @@ zrequire('hash_util');
zrequire('hashchange');
zrequire('stream_data');
set_global('document', 'document-stub');
set_global('history', {});
set_global('window', {
location: {
protocol: 'http:',
host: 'example.com',
},
});
set_global('admin', {});
set_global('drafts', {});
set_global('favicon', {});
set_global('floating_recipient_bar', {});
set_global('info_overlay', {});
set_global('narrow', {});
set_global('overlays', {});
set_global('settings', {});
set_global('subs', {});
set_global('ui_util', {});
function blueslip_wrap(f) {
return function (e) {
return f(e);
};
}
set_global('blueslip', {
wrap_function: blueslip_wrap,
});
(function test_operators_round_trip() {
var operators;
var hash;
@@ -79,3 +109,229 @@ zrequire('stream_data');
hash = hashchange.operators_to_hash(operators);
assert.equal(hash, '#narrow/pm-with/42-alice');
}());
function stub_trigger(f) {
set_global('$', () => {
return {
trigger: f,
};
});
$.Event = (name) => {
assert.equal(name, 'zuliphashchange.zulip');
};
}
function test_helper() {
var events = [];
var narrow_terms;
function stub(module_name, func_name) {
global[module_name][func_name] = () => {
events.push(module_name + '.' + func_name);
};
}
stub('admin', 'setup_page');
stub('drafts', 'launch');
stub('favicon', 'reset');
stub('floating_recipient_bar', 'update');
stub('narrow', 'deactivate');
stub('overlays', 'close_for_hash_change');
stub('settings', 'setup_page');
stub('subs', 'launch');
stub('ui_util', 'blur_active_element');
stub_trigger(() => { events.push('trigger event'); });
ui_util.change_tab_to = (hash) => {
events.push('change_tab_to ' + hash);
};
narrow.activate = (terms) => {
narrow_terms = terms;
events.push('narrow.activate');
};
info_overlay.show = (name) => {
events.push('info: ' + name);
};
return {
clear_events: () => {
events = [];
},
assert_events: (expected_events) => {
assert.deepEqual(expected_events, events);
},
get_narrow_terms: () => {
return narrow_terms;
},
};
}
(function test_hash_interactions() {
var helper = test_helper();
window.location.hash = '#';
helper.clear_events();
hashchange.initialize();
helper.assert_events([
'overlays.close_for_hash_change',
'trigger event',
'change_tab_to #home',
'narrow.deactivate',
'floating_recipient_bar.update',
]);
helper.clear_events();
window.onhashchange();
helper.assert_events([
'overlays.close_for_hash_change',
'trigger event',
'change_tab_to #home',
'narrow.deactivate',
'floating_recipient_bar.update',
]);
window.location.hash = '#narrow/stream/Denmark';
helper.clear_events();
window.onhashchange();
helper.assert_events([
'overlays.close_for_hash_change',
'trigger event',
'change_tab_to #home',
'narrow.activate',
'floating_recipient_bar.update',
]);
var terms = helper.get_narrow_terms();
assert.equal(terms[0].operand, 'Denmark');
window.location.hash = '#narrow';
helper.clear_events();
window.onhashchange();
helper.assert_events([
'overlays.close_for_hash_change',
'trigger event',
'change_tab_to #home',
'narrow.activate',
'floating_recipient_bar.update',
]);
terms = helper.get_narrow_terms();
assert.equal(terms.length, 0);
window.location.hash = '#streams/whatever';
helper.clear_events();
window.onhashchange();
helper.assert_events([
'overlays.close_for_hash_change',
'subs.launch',
]);
window.location.hash = '#keyboard-shortcuts/whatever';
helper.clear_events();
window.onhashchange();
helper.assert_events([
'overlays.close_for_hash_change',
'trigger event',
'info: keyboard-shortcuts',
]);
window.location.hash = '#markdown-help/whatever';
helper.clear_events();
window.onhashchange();
helper.assert_events([
'overlays.close_for_hash_change',
'trigger event',
'info: markdown-help',
]);
window.location.hash = '#search-operators/whatever';
helper.clear_events();
window.onhashchange();
helper.assert_events([
'overlays.close_for_hash_change',
'trigger event',
'info: search-operators',
]);
window.location.hash = '#drafts';
helper.clear_events();
window.onhashchange();
helper.assert_events([
'overlays.close_for_hash_change',
'drafts.launch',
]);
window.location.hash = '#settings/alert-words';
helper.clear_events();
window.onhashchange();
helper.assert_events([
'overlays.close_for_hash_change',
'settings.setup_page',
'admin.setup_page',
]);
window.location.hash = '#organization/user-list-admin';
helper.clear_events();
window.onhashchange();
helper.assert_events([
'settings.setup_page',
'admin.setup_page',
]);
var called_back;
helper.clear_events();
hashchange.exit_overlay(() => {
called_back = true;
});
helper.assert_events([
'ui_util.blur_active_element',
]);
assert(called_back);
}());
(function test_save_narrow() {
var helper = test_helper();
var operators = [
{operator: 'is', operand: 'private'},
];
hashchange.save_narrow(operators);
helper.assert_events([
'trigger event',
'favicon.reset',
]);
assert.equal(window.location.hash, '#narrow/is/private');
var url_pushed;
global.history.pushState = (state, title, url) => {
url_pushed = url;
};
operators = [
{operator: 'is', operand: 'starred'},
];
helper.clear_events();
hashchange.save_narrow(operators);
helper.assert_events([
'trigger event',
'favicon.reset',
]);
assert.equal(url_pushed, 'http://example.com/#narrow/is/starred');
}());

View File

@@ -14,23 +14,23 @@
set_global('activity', {
});
set_global('navigator', {
userAgent: '',
});
set_global('page_params', {
});
set_global('overlays', {
});
set_global('$', function () {
return {
// Hack: Used for reactions hotkeys; may want to restructure.
find: function () {return ['target'];},
keydown: function () {},
keypress: function () {},
};
});
var noop = () => {};
set_global('document', {
});
// jQuery stuff should go away if we make an initialize() method.
set_global('document', 'document-stub');
set_global('$', global.make_zjquery());
$.fn.keydown = noop;
$.fn.keypress = noop;
var hotkey = zrequire('hotkey');
@@ -70,11 +70,12 @@ function stubbing(func_name_to_stub, test_function) {
});
}
function map_down(which, shiftKey, ctrlKey) {
function map_down(which, shiftKey, ctrlKey, metaKey) {
return hotkey.get_keydown_hotkey({
which: which,
shiftKey: shiftKey,
ctrlKey: ctrlKey,
metaKey: metaKey,
});
}
@@ -97,7 +98,8 @@ function stubbing(func_name_to_stub, test_function) {
assert.equal(map_press(47).name, 'search'); // slash
assert.equal(map_press(106).name, 'vim_down'); // j
assert.equal(map_down(219, false, true).name, 'escape');
assert.equal(map_down(219, false, true).name, 'escape'); // ctrl + [
assert.equal(map_down(75, false, true).name, 'search_with_k'); // ctrl + k
// More negative tests.
assert.equal(map_down(47), undefined);
@@ -117,6 +119,17 @@ function stubbing(func_name_to_stub, test_function) {
assert.equal(map_down(88, false, true), undefined); // ctrl + x
assert.equal(map_down(78, false, true), undefined); // ctrl + n
assert.equal(map_down(77, false, true), undefined); // ctrl + m
assert.equal(map_down(75, false, false, true), undefined); // cmd + k
// CMD tests for MacOS
global.navigator.userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36";
assert.equal(map_down(219, false, false, true).name, 'escape'); // cmd + [
assert.equal(map_down(75, false, false, true).name, 'search_with_k'); // cmd + k
assert.equal(map_down(75, false, true, false), undefined); // ctrl + k
// Reset userAgent
global.navigator.userAgent = '';
}());
(function test_basic_chars() {

View File

@@ -56,7 +56,6 @@ i18n.init({
enable_offline_push_notifications: false,
enable_online_push_notifications: false,
enable_digest_emails: false,
default_desktop_notifications: false,
},
};

View File

@@ -5,8 +5,7 @@ zrequire('Handlebars', 'handlebars');
zrequire('templates');
global.compile_template('input_pill');
set_global('blueslip', {
});
set_global('blueslip', global.make_zblueslip());
var noop = function () {};
@@ -37,32 +36,28 @@ function pill_html(value, data_id) {
}
(function test_basics() {
var error;
var config = {};
blueslip.error = function (err) {
error = err;
};
blueslip.set_test_data('error', 'Pill needs container.');
input_pill.create(config);
assert.equal(error, 'Pill needs container.');
assert.equal(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
var pill_input = $.create('pill_input');
var container = $.create('container');
container.set_find_results('.input', pill_input);
blueslip.set_test_data('error', 'Pill needs create_item_from_text');
config.container = container;
input_pill.create(config);
assert.equal(error, 'Pill needs create_item_from_text');
assert.equal(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
blueslip.set_test_data('error', 'Pill needs get_text_from_item');
config.create_item_from_text = noop;
input_pill.create(config);
assert.equal(error, 'Pill needs get_text_from_item');
blueslip.error = function () {
throw "unexpected error";
};
assert.equal(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
config.get_text_from_item = noop;
var widget = input_pill.create(config);

View File

@@ -0,0 +1,312 @@
// TODO: make an initialize function for list_render.
const initialize = (function () {
var initalize_function;
set_global('$', (f) => {
initalize_function = f;
});
// We can move this module scope when we have
// an explicit initialize function.
zrequire('list_render');
return initalize_function;
}());
// We need these stubs to get by instanceof checks.
// The list_render library allows you to insert objects
// that are either jQuery, Element, or just raw HTML
// strings. We initially test with raw strings.
set_global('jQuery', 'stub');
set_global('Element', function () {
return { };
});
// This function will be the anonymous click handler
// for clicking on any element that matches the
// CSS selector of "[data-sort]".
var handle_sort_click;
// We only need very simple jQuery wrappers for when the
// "real" code wraps html or sets up click handlers.
// We'll simulate most other objects ourselves.
set_global('$', (arg) => {
if (arg.to_jquery) {
return arg.to_jquery();
}
if (arg === 'body') {
return {
on: (event_name, selector, f) => {
assert.equal(event_name, 'click');
assert.equal(selector, '[data-sort]');
handle_sort_click = f;
},
};
}
return {
html: () => arg,
};
});
function make_containers() {
// We build objects here that simulate jQuery containers.
// The main thing to do at first is simulate that our
// parent container is the nearest ancestor to our main
// container that has a max-height attribute, and then
// the parent container will have a scroll event attached to
// it. This is a good time to read __set_events in the
// real code.
const parent_container = {};
const container = {};
container.parent = () => parent_container;
container.length = () => 1;
container.is = () => false;
container.css = (prop) => {
assert.equal(prop, 'max-height');
return 'none';
};
parent_container.is = () => false;
parent_container.length = () => 1;
parent_container.css = (prop) => {
assert.equal(prop, 'max-height');
return 100;
};
// Capture the scroll callback so we can call it in
// our tests.
parent_container.scroll = (f) => {
parent_container.call_scroll = () => {
f.call(parent_container);
};
};
// Make our append function just set a field we can
// check in our tests.
container.append = (data) => {
container.appended_data = data;
};
return {
container: container,
parent_container: parent_container,
};
}
function make_search_input() {
const $element = {};
// Allow ourselves to be wrapped by $(...) and
// return ourselves.
$element.to_jquery = () => $element;
$element.on = (event_name, f) => {
assert.equal(event_name, 'input');
$element.simulate_input_event = () => {
const elem = {
value: $element.val(),
};
f.call(elem);
};
};
return $element;
}
function div(item) {
return '<div>' + item + '</div>';
}
(function test_list_render() {
const {container, parent_container} = make_containers();
const search_input = make_search_input();
const list = [
'apple',
'banana',
'carrot',
'dog',
'egg',
'fence',
'grape',
];
const opts = {
filter: {
element: search_input,
},
load_count: 2,
modifier: (item) => div(item),
};
const widget = list_render(container, list, opts);
widget.render();
var expected_html = '<div>apple</div><div>banana</div>';
assert.deepEqual(container.appended_data.html(), expected_html);
// Set up our fake geometry so it forces a scroll action.
parent_container.scrollTop = 180;
parent_container.clientHeight = 100;
parent_container.scrollHeight = 260;
// Scrolling gets the next two elements from the list into
// our widget.
parent_container.call_scroll();
expected_html = '<div>carrot</div><div>dog</div>';
assert.deepEqual(container.appended_data.html(), expected_html);
// Filtering will pick out dog/egg/grape when we put "g"
// into our search input. (This uses the default filter, which
// is a glorified indexOf call.)
container.html = (html) => { assert.equal(html, ''); };
search_input.val = () => 'g';
search_input.simulate_input_event();
expected_html = '<div>dog</div><div>egg</div><div>grape</div>';
assert.deepEqual(container.appended_data.html(), expected_html);
// We can insert new data into the widget.
const new_data = [
'greta',
'faye',
'gary',
'frank',
'giraffe',
'fox',
];
widget.data(new_data);
widget.render();
expected_html = '<div>greta</div><div>gary</div>';
assert.deepEqual(container.appended_data.html(), expected_html);
}());
function sort_button(opts) {
// The complications here are due to needing to find
// the list via complicated HTML assumptions. Also, we
// don't have any abstraction for the button and its
// siblings other than direct jQuery actions.
function data(sel) {
switch (sel) {
case "sort": return opts.sort_type;
case "sort-prop": return opts.prop_name;
default: throw Error('unknown selector: ' + sel);
}
}
function lookup(sel, value) {
return (selector) => {
assert.equal(sel, selector);
return value;
};
}
var button;
const $button = {
data: data,
parents: lookup('table', {
next: lookup('.progressive-table-wrapper', {
data: lookup('list-render', opts.list_name),
}),
}),
hasClass: lookup('active', opts.active),
siblings: lookup('.active', {
removeClass: (sel) => {
assert.equal(sel, 'active');
button.siblings_deactivated = true;
},
}),
addClass: (sel) => {
assert.equal(sel, 'active');
button.activated = true;
},
};
button = {
to_jquery: () => $button,
siblings_deactivated: false,
activated: false,
};
return button;
}
(function test_sorting() {
const {container} = make_containers();
var cleared;
container.html = (html) => {
assert.equal(html, '');
cleared = true;
};
const alice = { name: 'alice', salary: 50 };
const bob = { name: 'bob', salary: 40 };
const cal = { name: 'cal', salary: 30 };
const dave = { name: 'dave', salary: 25 };
const list = [bob, dave, alice, cal];
const opts = {
name: 'my-list',
load_count: 2,
modifier: (item) => {
return div(item.name) + div(item.salary);
},
};
function html_for(people) {
return _.map(people, opts.modifier).join('');
}
list_render(container, list, opts);
initialize();
var button_opts;
var button;
var expected_html;
button_opts = {
sort_type: 'alphabetic',
prop_name: 'name',
list_name: 'my-list',
active: false,
};
button = sort_button(button_opts);
handle_sort_click.call(button);
assert(cleared);
assert(button.siblings_deactivated);
expected_html = html_for([alice, bob, cal, dave]);
assert.deepEqual(container.appended_data.html(), expected_html);
// Now try a numeric sort.
button_opts = {
sort_type: 'numeric',
prop_name: 'salary',
list_name: 'my-list',
active: false,
};
button = sort_button(button_opts);
cleared = false;
button.siblings_deactivated = false;
handle_sort_click.call(button);
assert(cleared);
assert(button.siblings_deactivated);
expected_html = html_for([dave, cal, bob, alice]);
assert.deepEqual(container.appended_data.html(), expected_html);
}());

View File

@@ -1,7 +1,4 @@
/*global Dict */
var path = zrequire('path', 'path');
var fs = zrequire('fs', 'fs');
zrequire('hash_util');
zrequire('katex', 'node_modules/katex/dist/katex.min.js');
zrequire('marked', 'third/marked/lib/marked');
@@ -47,10 +44,10 @@ set_global('page_params', {
translate_emoticons: false,
});
set_global('blueslip', {error: function () {}});
set_global('blueslip', global.make_zblueslip());
set_global('Image', function () {
return {};
return {};
});
emoji.initialize();
@@ -76,6 +73,12 @@ people.add({
email: 'leo@zulip.com',
});
people.add({
full_name: 'Bobby <h1>Tables</h1>',
user_id: 103,
email: 'bobby@zulip.com',
});
people.initialize_current_user(cordelia.user_id);
var hamletcharacters = {
@@ -92,8 +95,16 @@ var backend = {
members: [],
};
var edgecase_group = {
name: "Bobby <h1>Tables</h1>",
id: 3,
description: "HTML Syntax to check for Markdown edge cases.",
members: [],
};
global.user_groups.add(hamletcharacters);
global.user_groups.add(backend);
global.user_groups.add(edgecase_group);
var stream_data = global.stream_data;
var denmark = {
@@ -111,8 +122,16 @@ var social = {
in_home_view: true,
invite_only: true,
};
var edgecase_stream = {
subscribed: true,
color: 'green',
name: 'Bobby <h1>Tables</h1>',
stream_id: 3,
in_home_view: true,
};
stream_data.add_sub('Denmark', denmark);
stream_data.add_sub('social', social);
stream_data.add_sub('Bobby <h1>Tables</h1>', edgecase_stream);
// Check the default behavior of fenced code blocks
// works properly before markdown is initialized.
@@ -125,47 +144,46 @@ stream_data.add_sub('social', social);
markdown.initialize();
var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver/fixtures/markdown_test_cases.json'), 'utf8', 'r'));
var bugdown_data = global.read_fixture_data('markdown_test_cases.json');
(function test_bugdown_detection() {
var no_markup = [
"This is a plaintext message",
"This is a plaintext: message",
"This is a :plaintext message",
"This is a :plaintext message: message",
"Contains a not an image.jpeg/ok file",
"Contains a not an http://www.google.com/ok/image.png/stop file",
"No png to be found here, a png",
"No user mention **leo**",
"No user mention @what there",
"No group mention *hamletcharacters*",
"We like to code\n~~~\ndef code():\n we = \"like to do\"\n~~~",
"This is a\nmultiline :emoji: here\n message",
"This is an :emoji: message",
"User Mention @**leo**",
"User Mention @**leo f**",
"User Mention @**leo with some name**",
"Group Mention @*hamletcharacters*",
"Stream #**Verona**",
"This contains !gravatar(leo@zulip.com)",
"And an avatar !avatar(leo@zulip.com) is here",
];
"This is a plaintext message",
"This is a plaintext: message",
"This is a :plaintext message",
"This is a :plaintext message: message",
"Contains a not an image.jpeg/ok file",
"Contains a not an http://www.google.com/ok/image.png/stop file",
"No png to be found here, a png",
"No user mention **leo**",
"No user mention @what there",
"No group mention *hamletcharacters*",
"We like to code\n~~~\ndef code():\n we = \"like to do\"\n~~~",
"This is a\nmultiline :emoji: here\n message",
"This is an :emoji: message",
"User Mention @**leo**",
"User Mention @**leo f**",
"User Mention @**leo with some name**",
"Group Mention @*hamletcharacters*",
"Stream #**Verona**",
"This contains !gravatar(leo@zulip.com)",
"And an avatar !avatar(leo@zulip.com) is here",
];
var markup = [
"Contains a https://zulip.com/image.png file",
"Contains a https://zulip.com/image.jpg file",
"https://zulip.com/image.jpg",
"also https://zulip.com/image.jpg",
"https://zulip.com/image.jpg too",
"Contains a zulip.com/foo.jpeg file",
"Contains a https://zulip.com/image.png file",
"twitter url https://twitter.com/jacobian/status/407886996565016579",
"https://twitter.com/jacobian/status/407886996565016579",
"then https://twitter.com/jacobian/status/407886996565016579",
"twitter url http://twitter.com/jacobian/status/407886996565016579",
"youtube url https://www.youtube.com/watch?v=HHZ8iqswiCw&feature=youtu.be&a",
];
"Contains a https://zulip.com/image.png file",
"Contains a https://zulip.com/image.jpg file",
"https://zulip.com/image.jpg",
"also https://zulip.com/image.jpg",
"https://zulip.com/image.jpg too",
"Contains a zulip.com/foo.jpeg file",
"Contains a https://zulip.com/image.png file",
"twitter url https://twitter.com/jacobian/status/407886996565016579",
"https://twitter.com/jacobian/status/407886996565016579",
"then https://twitter.com/jacobian/status/407886996565016579",
"twitter url http://twitter.com/jacobian/status/407886996565016579",
"youtube url https://www.youtube.com/watch?v=HHZ8iqswiCw&feature=youtu.be&a",
];
no_markup.forEach(function (content) {
assert.equal(markdown.contains_backend_only_syntax(content), false);
@@ -286,14 +304,14 @@ var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver
{input: 'T\n#**Denmark**',
expected: '<p>T<br>\n<a class="stream" data-stream-id="1" href="http://zulip.zulipdev.com/#narrow/stream/1-Denmark">#Denmark</a></p>'},
{input: 'T\n@**Cordelia Lear**',
expected: '<p>T<br>\n<span class="user-mention" data-user-id="101">@Cordelia Lear</span></p>'},
expected: '<p>T<br>\n<span class="user-mention" data-user-id="101">@Cordelia Lear</span></p>'},
{input: 'T\n@hamletcharacters',
expected: '<p>T<br>\n@hamletcharacters</p>'},
{input: 'T\n@*hamletcharacters*',
expected: '<p>T<br>\n<span class="user-group-mention" data-user-group-id="1">@hamletcharacters</span></p>'},
{input: 'T\n@*notagroup*',
expected: '<p>T<br>\n@*notagroup*</p>'},
{input: 'T\n@*backend*',
{input: 'T\n@*backend*',
expected: '<p>T<br>\n<span class="user-group-mention" data-user-group-id="2">@Backend</span></p>'},
{input: '@*notagroup*',
expected: '<p>@*notagroup*</p>'},
@@ -305,6 +323,23 @@ var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver
{input: ':)',
expected: '<p><span class="emoji emoji-1f603" title="smiley">:smiley:</span></p>',
translate_emoticons: true},
// Test HTML Escape in Custom Zulip Rules
{input: '@**<h1>The Rogue One</h1>**',
expected: '<p>@**&lt;h1&gt;The Rogue One&lt;/h1&gt;**</p>'},
{input: '#**<h1>The Rogue One</h1>**',
expected: '<p>#**&lt;h1&gt;The Rogue One&lt;/h1&gt;**</p>'},
{input: '!avatar(<h1>The Rogue One</h1>)',
expected: '<p><img alt="&lt;h1&gt;The Rogue One&lt;/h1&gt;" class="message_body_gravatar" src="/avatar/&lt;h1&gt;The Rogue One&lt;/h1&gt;?s=30" title="&lt;h1&gt;The Rogue One&lt;/h1&gt;"></p>'},
{input: ':<h1>The Rogue One</h1>:',
expected: '<p>:&lt;h1&gt;The Rogue One&lt;/h1&gt;:</p>'},
{input: '@**O\'Connell**',
expected: '<p>@**O&#39;Connell**</p>'},
{input: '@*Bobby <h1>Tables</h1>*',
expected: '<p><span class="user-group-mention" data-user-group-id="3">@Bobby &lt;h1&gt;Tables&lt;/h1&gt;</span></p>'},
{input: '@**Bobby <h1>Tables</h1>**',
expected: '<p><span class="user-mention" data-user-id="103">@Bobby &lt;h1&gt;Tables&lt;/h1&gt;</span></p>'},
{input: '#**Bobby <h1>Tables</h1>**',
expected: '<p><a class="stream" data-stream-id="3" href="http://zulip.zulipdev.com/#narrow/stream/3-Bobby-.3Ch1.3ETables.3C.2Fh1.3E">#Bobby &lt;h1&gt;Tables&lt;/h1&gt;</a></p>'},
];
// We remove one of the unicode emoji we put as input in one of the test
@@ -322,7 +357,6 @@ var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver
var message = {raw_content: input};
markdown.apply_markdown(message);
var output = message.content;
assert.equal(expected, output);
});
}());
@@ -386,6 +420,20 @@ var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver
assert.equal(message.mentioned, true);
assert.equal(message.mentioned_me_directly, true);
input = "test @**everyone**";
message = {subject: "No links here", raw_content: input};
markdown.apply_markdown(message);
assert.equal(message.is_me_message, false);
assert.equal(message.mentioned, true);
assert.equal(message.mentioned_me_directly, false);
input = "test @**stream**";
message = {subject: "No links here", raw_content: input};
markdown.apply_markdown(message);
assert.equal(message.is_me_message, false);
assert.equal(message.mentioned, true);
assert.equal(message.mentioned_me_directly, false);
input = "test @all";
message = {subject: "No links here", raw_content: input};
markdown.apply_markdown(message);
@@ -443,12 +491,9 @@ var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver
(function test_katex_throws_unexpected_exceptions() {
katex.renderToString = function () { throw new Error('some-exception'); };
var blueslip_error_called = false;
blueslip.error = function (ex) {
assert.equal(ex.message, 'some-exception');
blueslip_error_called = true;
};
blueslip.set_test_data('error', 'Error: some-exception');
var message = { raw_content: '$$a$$' };
markdown.apply_markdown(message);
assert(blueslip_error_called);
assert(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
}());

View File

@@ -66,4 +66,27 @@ var editability_types = message_edit.editability_types;
assert.equal(get_editability(message, 45), editability_types.NO_LONGER);
// If we don't pass a second argument, treat it as 0
assert.equal(get_editability(message), editability_types.NO_LONGER);
message = {
sent_by_me: false,
type: 'stream',
};
global.page_params = {
realm_allow_community_topic_editing: true,
realm_allow_message_editing: true,
realm_message_content_edit_limit_seconds: 0,
};
message.timestamp = current_timestamp - 60;
assert.equal(get_editability(message), editability_types.TOPIC_ONLY);
// Test `message_edit.is_topic_editable()`
assert.equal(message_edit.is_topic_editable(message), true);
message.sent_by_me = true;
global.page_params.realm_allow_community_topic_editing = false;
assert.equal(message_edit.is_topic_editable(message), true);
message.sent_by_me = false;
global.page_params.realm_allow_community_topic_editing = false;
assert.equal(message_edit.is_topic_editable(message), false);
}());

View File

@@ -9,6 +9,7 @@ set_global('MessageListView', function () { return {}; });
zrequire('FetchStatus', 'js/fetch_status');
zrequire('Filter', 'js/filter');
zrequire('MessageListData', 'js/message_list_data');
zrequire('message_list');
zrequire('util');

View File

@@ -11,6 +11,7 @@ set_global('document', null);
zrequire('FetchStatus', 'js/fetch_status');
zrequire('util');
zrequire('muting');
zrequire('MessageListData', 'js/message_list_data');
zrequire('MessageListView', 'js/message_list_view');
var MessageList = zrequire('message_list').MessageList;
@@ -198,18 +199,18 @@ var with_overrides = global.with_overrides; // make lint happy
var list = new MessageList(table, filter);
var items = [
{
id: 1,
sender_id: 3,
},
{
id: 2,
sender_id: 3,
},
{
id: 3,
sender_id: 6,
},
{
id: 1,
sender_id: 3,
},
{
id: 2,
sender_id: 3,
},
{
id: 3,
sender_id: 6,
},
];
list.append(items);
@@ -245,8 +246,9 @@ var with_overrides = global.with_overrides; // make lint happy
list = new MessageList(table, filter);
list.append([{id:10}, {id:20}, {id:30}, {id:20.02}, {id:20.03}, {id:40},
{id:50}, {id: 50.01}, {id: 50.02}, {id:60}]);
list.append([
{id:10}, {id:20}, {id:30}, {id:20.02}, {id:20.03}, {id:40},
{id:50}, {id: 50.01}, {id: 50.02}, {id:60}]);
list._local_only= {20.02: {id:20.02}, 20.03: {id:20.03},
50.01: {id: 50.01}, 50.02: {id: 50.02}};
@@ -395,7 +397,7 @@ var with_overrides = global.with_overrides; // make lint happy
var messages = [{id: 1}, {id: 2}, {id: 3}];
list.unmuted_messages = function (m) { return m; };
list.data.unmuted_messages = function (msgs) { return msgs; };
global.with_stub(function (stub) {
list.view.rerender_the_whole_thing = stub.f;
list.add_and_rerender(messages);

View File

@@ -0,0 +1,113 @@
zrequire('unread');
zrequire('util');
zrequire('Filter', 'js/filter');
zrequire('MessageListData', 'js/message_list_data');
set_global('page_params', {});
set_global('blueslip', global.make_zblueslip());
global.patch_builtin('setTimeout', (f, delay) => {
assert.equal(delay, 0);
return f();
});
function make_msg(msg_id) {
return {
id: msg_id,
unread: true,
};
}
function make_msgs(msg_ids) {
return _.map(msg_ids, make_msg);
}
(function test_basics() {
const mld = new MessageListData({
muting_enabled: false,
filter: undefined,
});
assert.equal(mld.is_search(), false);
mld.add(make_msgs([35, 25, 15, 45]));
function assert_contents(msg_ids) {
const msgs = mld.all_messages();
assert.deepEqual(msgs, make_msgs(msg_ids));
}
assert_contents([15, 25, 35, 45]);
const new_msgs = make_msgs([10, 20, 30, 40, 50, 60, 70]);
const info = mld.triage_messages(new_msgs);
assert.deepEqual(info, {
top_messages: make_msgs([10]),
interior_messages: make_msgs([20, 30, 40]),
bottom_messages: make_msgs([50, 60, 70]),
});
mld.prepend(info.top_messages);
mld.append(info.bottom_messages);
assert_contents([10, 15, 25, 35, 45, 50, 60, 70]);
mld.add(info.interior_messages);
assert_contents([10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70]);
assert.equal(mld.selected_id(), -1);
assert.equal(mld.closest_id(8), 10);
assert.equal(mld.closest_id(27), 25);
assert.equal(mld.closest_id(72), 70);
mld.set_selected_id(50);
assert.equal(mld.selected_id(), 50);
assert.equal(mld.selected_idx(), 8);
mld.remove([mld.get(50)]);
assert_contents([10, 15, 20, 25, 30, 35, 40, 45, 60, 70]);
mld.update_items_for_muting();
assert_contents([10, 15, 20, 25, 30, 35, 40, 45, 60, 70]);
mld.reset_select_to_closest();
assert.equal(mld.selected_id(), 45);
assert.equal(mld.selected_idx(), 7);
assert.equal(mld.first_unread_message_id(), 10);
mld.get(10).unread = false;
assert.equal(mld.first_unread_message_id(), 15);
mld.clear();
assert_contents([]);
assert.equal(mld.closest_id(99), -1);
assert.equal(mld.get_last_message_sent_by_me(), undefined);
mld.add(make_msgs([120, 125.01, 130, 140]));
assert_contents([120, 125.01, 130, 140]);
mld.set_selected_id(125.01);
assert.equal(mld.selected_id(), 125.01);
mld.get(125.01).id = 145;
mld.change_message_id(125.01, 145, {
re_render: () => {},
});
assert_contents([120, 130, 140, 145]);
_.each(mld.all_messages(), (msg) => {
msg.unread = false;
});
assert.equal(mld.first_unread_message_id(), 145);
}());
(function test_errors() {
const mld = new MessageListData({
muting_enabled: false,
filter: undefined,
});
assert.equal(mld.get('bogus-id'), undefined);
}());

View File

@@ -5,13 +5,14 @@ zrequire('util');
zrequire('XDate', 'node_modules/xdate/src/xdate');
zrequire('Filter', 'js/filter');
zrequire('FetchStatus', 'js/fetch_status');
zrequire('MessageListData', 'js/message_list_data');
zrequire('MessageListView', 'js/message_list_view');
zrequire('message_list');
var noop = function () {};
set_global('page_params', {
twenty_four_hour_time: false,
twenty_four_hour_time: false,
});
set_global('home_msg_list', null);
set_global('feature_flags', {twenty_four_hour_time: false});
@@ -274,7 +275,7 @@ set_global('rows', {
assert_message_groups_list_equal(result.append_groups, []);
assert_message_groups_list_equal(result.prepend_groups, []);
assert_message_groups_list_equal(result.rerender_groups,
[build_message_group([message2, message1])]);
[build_message_group([message2, message1])]);
assert_message_list_equal(result.append_messages, []);
assert_message_list_equal(result.rerender_messages, []);
}());
@@ -376,7 +377,7 @@ set_global('rows', {
// Stub out functionality that is not core to the rendering window
// logic.
list.unmuted_messages = function (messages) {
list.data.unmuted_messages = function (messages) {
return messages;
};

View File

@@ -27,7 +27,7 @@ set_global('page_params', {
is_admin: true,
});
set_global('blueslip', {});
set_global('blueslip', global.make_zblueslip());
var me = {
email: 'me@example.com',
@@ -137,21 +137,20 @@ global.people.initialize_current_user(me.user_id);
display_recipient: [{user_id: 92714}],
};
var blueslip_errors = 0;
blueslip.error = function () {
blueslip_errors += 1;
};
blueslip.set_test_data('error', 'Unknown user_id in get_person_from_user_id: 92714');
blueslip.set_test_data('error', 'Unknown user id 92714'); // From person.js
// Expect each to throw two blueslip errors
// One from message_store.js, one from person.js
var emails = message_store.get_pm_emails(message);
assert.equal(emails, '?');
assert.equal(blueslip_errors, 2);
assert.equal(blueslip.get_test_logs('error').length, 2);
blueslip_errors = 0;
var names = message_store.get_pm_full_names(message);
assert.equal(names, '?');
assert.equal(blueslip_errors, 2);
assert.equal(blueslip.get_test_logs('error').length, 4);
blueslip.clear_test_data();
message = {
type: 'stream',

View File

@@ -77,9 +77,12 @@ function set_filter(operators) {
var hide_id;
var show_id;
global.$ = function (id) {
return {hide: function () {hide_id = id;}, show: function () {show_id = id;}};
};
set_global('$', (id) => {
return {
hide: () => {hide_id = id;},
show: () => {show_id = id;},
};
});
narrow_state.reset_current_filter();
narrow.show_empty_narrow_message();

View File

@@ -0,0 +1,215 @@
set_global('$', global.make_zjquery());
zrequire('narrow_state');
zrequire('stream_data');
zrequire('Filter', 'js/filter');
zrequire('unread');
zrequire('narrow');
set_global('blueslip', {});
set_global('channel', {});
set_global('compose_actions', {});
set_global('current_msg_list', {});
set_global('hashchange', {});
set_global('home_msg_list', {});
set_global('message_fetch', {});
set_global('message_list', {});
set_global('message_scroll', {});
set_global('message_util', {});
set_global('notifications', {});
set_global('page_params', {});
set_global('search', {});
set_global('stream_list', {});
set_global('top_left_corner', {});
set_global('ui_util', {});
set_global('unread_ops', {});
var noop = () => {};
//
// We have strange hacks in narrow.activate to sleep 0
// seconds.
global.patch_builtin('setTimeout', (f, t) => {
assert.equal(t, 0);
f();
});
function stub_trigger(f) {
set_global('document', 'document-stub');
$('document-stub').trigger = f;
$.Event = (name) => {
assert.equal(name, 'narrow_activated.zulip');
};
}
var denmark = {
subscribed: false,
color: 'blue',
name: 'Denmark',
stream_id: 1,
in_home_view: false,
};
stream_data.add_sub('Denmark', denmark);
function test_helper() {
var events = [];
function stub(module_name, func_name) {
global[module_name][func_name] = () => {
events.push(module_name + '.' + func_name);
};
}
stub('compose_actions', 'on_narrow');
stub('hashchange', 'save_narrow');
stub('message_scroll', 'hide_indicators');
stub('notifications', 'clear_compose_notifications');
stub('notifications', 'redraw_title');
stub('search', 'update_button_visibility');
stub('stream_list', 'handle_narrow_activated');
stub('top_left_corner', 'handle_narrow_activated');
stub('ui_util', 'change_tab_to');
stub('unread_ops', 'process_visible');
stub_trigger(() => { events.push('trigger event'); });
blueslip.debug = noop;
message_util.add_messages = (messages, target_list, opts) => {
// The real function here doesn't do any more than this
// that we care about here.
target_list.add_messages(messages, opts);
};
return {
clear: () => {
events = [];
},
push_event: (event) => {
events.push(event);
},
assert_events: (expected_events) => {
assert.deepEqual(expected_events, events);
},
};
}
function stub_message_list() {
message_list.MessageList = function (table_name, filter) {
var list = this;
this.messages = [];
this.filter = filter;
this.view = {
set_message_offset: function (offset) {
list.view.offset = offset;
},
};
return this;
};
message_list.MessageList.prototype = {
add_messages: function (messages) {
var predicate = this.filter.predicate();
messages = _.filter(messages, predicate);
this.messages = this.messages.concat(messages);
},
get: function (msg_id) {
var msg = _.find(this.messages, (msg) => {
return msg.id === msg_id;
});
return msg;
},
empty: function () {
return this.messages.length === 0;
},
select_id: function (msg_id) {
this.selected_id = msg_id;
},
};
}
(function test_basics() {
stub_message_list();
var helper = test_helper();
var terms = [
{ operator: 'stream', operand: 'Denmark' },
];
var selected_id = 1000;
var selected_message = {
id: selected_id,
type: 'stream',
stream_id: denmark.stream_id,
};
var messages = [selected_message];
var row = {
length: 1,
offset: () => { return {top: 25}; },
};
current_msg_list.selected_id = () => { return -1; };
current_msg_list.get_row = () => { return row; };
message_list.all = {
all_messages: () => {
return messages;
},
get: (msg_id) => {
assert.equal(msg_id, selected_id);
return selected_message;
},
};
var cont;
message_fetch.load_messages_for_narrow = (opts) => {
cont = opts.cont;
assert.deepEqual(opts, {
cont: opts.cont,
then_select_id: 1000,
use_first_unread_anchor: false,
});
};
narrow.activate(terms, {
then_select_id: selected_id,
});
assert.equal(message_list.narrowed.selected_id, selected_id);
assert.equal(message_list.narrowed.view.offset, 25);
helper.assert_events([
'notifications.clear_compose_notifications',
'notifications.redraw_title',
'ui_util.change_tab_to',
'message_scroll.hide_indicators',
'unread_ops.process_visible',
'hashchange.save_narrow',
'search.update_button_visibility',
'compose_actions.on_narrow',
'top_left_corner.handle_narrow_activated',
'stream_list.handle_narrow_activated',
'trigger event',
]);
channel.post = (opts) => {
assert.equal(opts.url, '/json/report/narrow_times');
helper.push_event('report narrow times');
};
helper.clear();
cont();
helper.assert_events([
'report narrow times',
]);
}());

View File

@@ -3,9 +3,8 @@ zrequire('Filter', 'js/filter');
zrequire('stream_data');
zrequire('narrow_state');
set_global('blueslip', {});
set_global('page_params', {
});
set_global('blueslip', global.make_zblueslip());
set_global('page_params', {});
function set_filter(operators) {
operators = _.map(operators, function (op) {
@@ -24,15 +23,6 @@ function set_filter(operators) {
assert(!narrow_state.is_for_stream_id(test_stream.stream_id));
var bad_stream_id = 1000000;
var called = false;
global.blueslip.error = function (msg) {
assert.equal(msg, 'Bad stream id ' + bad_stream_id);
called = true;
};
assert(!narrow_state.is_for_stream_id(bad_stream_id));
assert(called);
set_filter([
['stream', 'Test'],
['topic', 'Bar'],
@@ -41,6 +31,7 @@ function set_filter(operators) {
assert(narrow_state.active());
assert.equal(narrow_state.stream(), 'Test');
assert.equal(narrow_state.stream_id(), test_stream.stream_id);
assert.equal(narrow_state.topic(), 'Bar');
assert(narrow_state.is_for_stream_id(test_stream.stream_id));
@@ -65,6 +56,7 @@ function set_filter(operators) {
assert(!narrow_state.narrowed_to_search());
assert(!narrow_state.narrowed_to_topic());
assert(!narrow_state.narrowed_by_stream_reply());
assert.equal(narrow_state.stream_id(), undefined);
set_filter([['stream', 'Foo']]);
assert(!narrow_state.narrowed_to_pms());
@@ -204,9 +196,17 @@ function set_filter(operators) {
(function test_stream() {
set_filter(undefined);
assert.equal(narrow_state.stream(), undefined);
assert.equal(narrow_state.stream_id(), undefined);
set_filter([['stream', 'Foo'], ['topic', 'Bar']]);
assert.equal(narrow_state.stream(), 'Foo');
assert.equal(narrow_state.stream_sub(), undefined);
assert.equal(narrow_state.stream_id(), undefined);
const sub = {name: 'Foo', stream_id: 55};
stream_data.add_sub('Foo', sub);
assert.equal(narrow_state.stream_id(), 55);
assert.deepEqual(narrow_state.stream_sub(), sub);
set_filter([['sender', 'someone'], ['topic', 'random']]);
assert.equal(narrow_state.stream(), undefined);
@@ -225,15 +225,11 @@ function set_filter(operators) {
set_filter([['pm-with', '']]);
assert.equal(narrow_state.pm_string(), undefined);
var called = false;
blueslip.warn = function (error) {
assert.equal(error, 'Unknown emails: bogus@foo.com');
called = true;
};
blueslip.set_test_data('warn', 'Unknown emails: bogus@foo.com');
set_filter([['pm-with', 'bogus@foo.com']]);
assert.equal(narrow_state.pm_string(), undefined);
assert(called);
assert.equal(blueslip.get_test_logs('warn').length, 1);
blueslip.clear_test_data();
var alice = {
email: 'alice@foo.com',

View File

@@ -0,0 +1,197 @@
zrequire('Filter', 'js/filter');
zrequire('people');
zrequire('stream_data');
zrequire('unread');
zrequire('util');
set_global('blueslip', global.make_zblueslip());
set_global('message_store', {});
set_global('page_params', {});
set_global('muting', {
is_topic_muted: () => false,
});
// The main code we are testing lives here.
zrequire('narrow_state');
const alice = {
email: 'alice@example.com',
user_id: 11,
full_name: 'Alice',
};
people.init();
people.add(alice);
people.is_my_user_id = () => false;
function set_filter(terms) {
const filter = new Filter(terms);
narrow_state.set_current_filter(filter);
}
function assert_unread_info(expected) {
assert.deepEqual(narrow_state.get_first_unread_info(), expected);
}
function candidate_ids() {
return narrow_state._possible_unread_message_ids();
}
(function test_get_unread_ids() {
var unread_ids;
var terms;
const sub = {
name: 'My Stream',
stream_id: 55,
};
const stream_msg = {
id: 101,
type: 'stream',
stream_id: sub.stream_id,
subject: 'my topic',
unread: true,
mentioned: true,
};
const private_msg = {
id: 102,
type: 'private',
unread: true,
display_recipient: [
{user_id: alice.user_id},
],
};
stream_data.add_sub(sub.name, sub);
unread_ids = candidate_ids();
assert.equal(unread_ids, undefined);
terms = [
{operator: 'bogus_operator', operand: 'me@example.com'},
];
set_filter(terms);
unread_ids = candidate_ids();
assert.equal(unread_ids, undefined);
assert_unread_info({flavor: 'cannot_compute'});
terms = [
{operator: 'stream', operand: 'bogus'},
];
set_filter(terms);
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, []);
terms = [
{operator: 'stream', operand: sub.name},
];
set_filter(terms);
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, []);
assert_unread_info({flavor: 'not_found'});
unread.process_loaded_messages([stream_msg]);
message_store.get = (msg_id) => {
assert.equal(msg_id, stream_msg.id);
return stream_msg;
};
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, [stream_msg.id]);
assert_unread_info({
flavor: 'found',
msg_id: stream_msg.id,
});
terms = [
{operator: 'stream', operand: 'bogus'},
{operator: 'topic', operand: 'my topic'},
];
set_filter(terms);
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, []);
terms = [
{operator: 'stream', operand: sub.name},
{operator: 'topic', operand: 'my topic'},
];
set_filter(terms);
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, [stream_msg.id]);
terms = [
{operator: 'is', operand: 'mentioned'},
];
set_filter(terms);
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, [stream_msg.id]);
terms = [
{operator: 'sender', operand: 'me@example.com'},
];
set_filter(terms);
// note that our candidate ids are just "all" ids now
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, [stream_msg.id]);
// this actually does filtering
assert_unread_info({flavor: 'not_found'});
terms = [
{operator: 'pm-with', operand: 'alice@example.com'},
];
set_filter(terms);
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, []);
unread.process_loaded_messages([private_msg]);
message_store.get = (msg_id) => {
assert.equal(msg_id, private_msg.id);
return private_msg;
};
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, [private_msg.id]);
assert_unread_info({
flavor: 'found',
msg_id: private_msg.id,
});
terms = [
{operator: 'is', operand: 'private'},
];
set_filter(terms);
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, [private_msg.id]);
terms = [
{operator: 'pm-with', operand: 'bob@example.com'},
];
set_filter(terms);
blueslip.set_test_data('warn', 'Unknown emails: bob@example.com');
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, []);
terms = [
{operator: 'is', operand: 'starred'},
];
set_filter(terms);
unread_ids = candidate_ids();
assert.deepEqual(unread_ids, []);
terms = [
{operator: 'search', operand: 'needle'},
];
set_filter(terms);
blueslip.set_test_data('error', 'unexpected call to get_first_unread_info');
assert_unread_info({
flavor: 'cannot_compute',
});
}());

View File

@@ -1,7 +1,7 @@
// Dependencies
zrequire('muting');
zrequire('stream_data');
set_global('$', global.make_zjquery({
silent: true,
}));
set_global('document', {
hasFocus: function () {
return true;
@@ -12,6 +12,15 @@ set_global('page_params', {
is_admin: false,
realm_users: [],
});
// For people.js
set_global('md5', function (s) {
return 'md5-' + s;
});
zrequire('muting');
zrequire('stream_data');
zrequire('ui');
zrequire('people');
zrequire('notifications');
@@ -55,94 +64,201 @@ stream_data.add_sub('stream_two', two);
// Case 1: If the message was sent by this user,
// DO NOT notify the user
// In this test, all other circumstances should trigger notification
// EXCEPT sent_by_me, which should trump them
assert.equal(notifications.message_is_notifiable({
id: 10,
content: 'message number 1',
sent_by_me: true,
notification_sent: false,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_two',
stream_id: 20,
subject: 'topic_two',
}), false);
// In this test, all other circumstances should trigger notification
// EXCEPT sent_by_me, which should trump them
assert.equal(notifications.message_is_notifiable({
id: 10,
content: 'message number 1',
sent_by_me: true,
notification_sent: false,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_two',
stream_id: 20,
subject: 'topic_two',
}), false);
// Case 2: If the user has already been sent a notificaton about this message,
// DO NOT notify the user
// In this test, all other circumstances should trigger notification
// EXCEPT notification_sent, which should trump them
// (ie: it mentions user, it's not muted, etc)
assert.equal(notifications.message_is_notifiable({
id: 20,
content: 'message number 2',
sent_by_me: false,
notification_sent: true,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_two',
stream_id: 20,
subject: 'topic_two',
}), false);
// In this test, all other circumstances should trigger notification
// EXCEPT notification_sent, which should trump them
// (ie: it mentions user, it's not muted, etc)
assert.equal(notifications.message_is_notifiable({
id: 20,
content: 'message number 2',
sent_by_me: false,
notification_sent: true,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_two',
stream_id: 20,
subject: 'topic_two',
}), false);
// Case 3: If a message mentions the user directly,
// DO notify the user
// Mentioning trumps muting
assert.equal(notifications.message_is_notifiable({
id: 30,
content: 'message number three',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_one',
stream_id: 10,
subject: 'topic_three',
}), true);
// Mentioning trumps muting
assert.equal(notifications.message_is_notifiable({
id: 30,
content: 'message number three',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_one',
stream_id: 10,
subject: 'topic_three',
}), true);
// Mentioning should trigger notification in unmuted topic
assert.equal(notifications.message_is_notifiable({
id: 40,
content: 'message number 4',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_two',
stream_id: 20,
subject: 'topic_two',
}), true);
// Mentioning should trigger notification in unmuted topic
assert.equal(notifications.message_is_notifiable({
id: 40,
content: 'message number 4',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_two',
stream_id: 20,
subject: 'topic_two',
}), true);
// Case 4: If a message is in a muted stream
// and does not mention the user DIRECTLY,
// DO NOT notify the user
assert.equal(notifications.message_is_notifiable({
id: 50,
content: 'message number 5',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: false,
type: 'stream',
stream: 'stream_one',
stream_id: 10,
subject: 'topic_one',
}), false);
assert.equal(notifications.message_is_notifiable({
id: 50,
content: 'message number 5',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: false,
type: 'stream',
stream: 'stream_one',
stream_id: 10,
subject: 'topic_one',
}), false);
// Case 5
// If none of the above cases apply
// (ie: topic is not muted, message does not mention user,
// no notification sent before, message not sent by user),
// return true to pass it to notifications settings
assert.equal(notifications.message_is_notifiable({
id: 60,
content: 'message number 6',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: false,
type: 'stream',
stream: 'stream_two',
stream_id: 20,
subject: 'topic_two',
}), true);
// If none of the above cases apply
// (ie: topic is not muted, message does not mention user,
// no notification sent before, message not sent by user),
// return true to pass it to notifications settings
assert.equal(notifications.message_is_notifiable({
id: 60,
content: 'message number 6',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: false,
type: 'stream',
stream: 'stream_two',
stream_id: 20,
subject: 'topic_two',
}), true);
}());
(function test_basic_notifications() {
var n; // Object for storing all notification data for assertions.
var last_closed_message_id = null;
var last_shown_message_id = null;
// Notifications API stub
notifications.set_notification_api({
checkPermission: function checkPermission() {
if (window.Notification.permission === 'granted') {
return 0;
}
return 2;
},
requestPermission: function () {
return;
},
createNotification: function createNotification(icon, title, content, tag) {
var notification_object = {icon: icon, body: content, tag: tag};
// properties for testing.
notification_object.tests = {
shown: false,
};
notification_object.show = function () {
last_shown_message_id = this.tag;
};
notification_object.close = function () {
last_closed_message_id = this.tag;
};
notification_object.cancel = function () { notification_object.close(); };
return notification_object;
},
});
var message_1 = {
id: 1000,
content: '@-mentions the user',
avatar_url: 'url',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_one',
stream_id: 10,
subject: 'topic_two',
};
var message_2 = {
id: 1500,
avatar_url: 'url',
content: '@-mentions the user',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_one',
stream_id: 10,
subject: 'topic_four',
};
// Send notification.
notifications.process_notification({message: message_1, webkit_notify: true});
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_two' in n, true);
assert.equal(Object.keys(n).length, 1);
assert.equal(last_shown_message_id, message_1.id);
// Remove notification.
notifications.close_notification(message_1);
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_two' in n, false);
assert.equal(Object.keys(n).length, 0);
assert.equal(last_closed_message_id, message_1.id);
// Send notification.
message_1.id = 1001;
notifications.process_notification({message: message_1, webkit_notify: true});
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_two' in n, true);
assert.equal(Object.keys(n).length, 1);
assert.equal(last_shown_message_id, message_1.id);
// Process same message again. Notification count shouldn't increase.
message_1.id = 1002;
notifications.process_notification({message: message_1, webkit_notify: true});
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_two' in n, true);
assert.equal(Object.keys(n).length, 1);
assert.equal(last_shown_message_id, message_1.id);
// Send another message. Notification count should increase.
notifications.process_notification({message: message_2, webkit_notify: true});
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_four' in n, true);
assert.equal(Object.keys(n).length, 2);
assert.equal(last_shown_message_id, message_2.id);
// Remove notifications.
notifications.close_notification(message_1);
notifications.close_notification(message_2);
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_two' in n, false);
assert.equal(Object.keys(n).length, 0);
assert.equal(last_closed_message_id, message_2.id);
}());

View File

@@ -1,9 +1,9 @@
zrequire('util');
zrequire('people');
set_global('blueslip', {
error: function () { return; },
});
set_global('blueslip', global.make_zblueslip({
error: false, // We check for errors in people_errors.js
}));
set_global('page_params', {});
set_global('md5', function (s) {
return 'md5-' + s;
@@ -86,13 +86,10 @@ initialize();
assert.equal(people.is_active_user_for_popover(bot_botson.user_id), true);
// Invalid user ID returns false and warns.
var message;
blueslip.warn = function (msg) {
message = msg;
};
blueslip.set_test_data('warn', 'Unexpectedly invalid user_id in user popover query: 123412');
assert.equal(people.is_active_user_for_popover(123412), false);
assert.equal(message, 'Unexpectedly invalid user_id in user popover query: 123412');
assert.equal(blueslip.get_test_logs('warn').length, 1);
blueslip.clear_test_data();
// We can still get their info for non-realm needs.
person = people.get_by_email(email);
@@ -245,7 +242,7 @@ initialize();
}());
(function test_filtered_users() {
var charles = {
var charles = {
email: 'charles@example.com',
user_id: 301,
full_name: 'Charles Dickens',
@@ -374,13 +371,15 @@ initialize();
avatar_url: 'charles.com/foo.png',
};
var maria = {
email: 'athens@example.com',
email: 'Athens@example.com',
user_id: 452,
full_name: 'Maria Athens',
};
people.add(charles);
people.add(maria);
assert.equal(people.small_avatar_url_for_person(maria),
'https://secure.gravatar.com/avatar/md5-athens@example.com?d=identicon&s=50');
var message = {
type: 'private',
display_recipient: [
@@ -392,9 +391,9 @@ initialize();
};
assert.equal(people.pm_with_url(message), '#narrow/pm-with/451,452-group');
assert.equal(people.pm_reply_to(message),
'athens@example.com,charles@example.com');
'Athens@example.com,charles@example.com');
assert.equal(people.small_avatar_url(message),
'charles.com/foo.png&s=50');
'charles.com/foo.png&s=50');
message = {
type: 'private',
@@ -406,16 +405,16 @@ initialize();
};
assert.equal(people.pm_with_url(message), '#narrow/pm-with/452-athens');
assert.equal(people.pm_reply_to(message),
'athens@example.com');
'Athens@example.com');
assert.equal(people.small_avatar_url(message),
'legacy.png&s=50');
'legacy.png&s=50');
message = {
avatar_url: undefined,
sender_id: maria.user_id,
};
assert.equal(people.small_avatar_url(message),
'https://secure.gravatar.com/avatar/md5-athens@example.com?d=identicon&s=50'
'https://secure.gravatar.com/avatar/md5-athens@example.com?d=identicon&s=50'
);
message = {
@@ -424,7 +423,7 @@ initialize();
sender_id: 9999999,
};
assert.equal(people.small_avatar_url(message),
'https://secure.gravatar.com/avatar/md5-foo@example.com?d=identicon&s=50'
'https://secure.gravatar.com/avatar/md5-foo@example.com?d=identicon&s=50'
);
message = {
@@ -616,14 +615,13 @@ initialize();
// Test shim where we can still retrieve user info using the
// old email.
var warning;
global.blueslip.warn = function (w) {
warning = w;
};
blueslip.set_test_data('warn',
'Obsolete email passed to get_by_email: ' +
'FOO@example.com new email = bar@example.com');
person = people.get_by_email(old_email);
assert(/Obsolete email.*FOO.*bar/.test(warning));
assert.equal(person.user_id, user_id);
assert.equal(blueslip.get_test_logs('warn').length, 1);
blueslip.clear_test_data();
}());
initialize();

View File

@@ -1,6 +1,11 @@
zrequire('people');
set_global('reload', {
is_in_progress: false,
});
set_global('blueslip', {});
set_global('blueslip', global.make_zblueslip({
debug: true, // testing for debug is disabled by default.
}));
var me = {
email: 'me@example.com',
@@ -14,68 +19,68 @@ people.add(me);
people.initialize_current_user(me.user_id);
(function test_report_late_add() {
var message;
global.blueslip.error = function (msg) {
message = msg;
};
blueslip.set_test_data('error', 'Added user late: user_id=55 email=foo@example.com');
people.report_late_add(55, 'foo@example.com');
assert.equal(message, 'Added user late: user_id=55 email=foo@example.com');
assert.equal(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
reload.is_in_progress = true;
people.report_late_add(55, 'foo@example.com');
assert.equal(blueslip.get_test_logs('log').length, 1);
assert.equal(blueslip.get_test_logs('log')[0], 'Added user late: user_id=55 email=foo@example.com');
assert.equal(blueslip.get_test_logs('error').length, 0);
blueslip.clear_test_data();
}());
(function test_blueslip() {
var unknown_email = "alicebobfred@example.com";
global.blueslip.debug = function (msg) {
assert.equal(msg, 'User email operand unknown: ' + unknown_email);
};
var warning;
global.blueslip.warn = function (w) {
warning = w;
};
blueslip.set_test_data('debug', 'User email operand unknown: ' + unknown_email);
people.id_matches_email_operand(42, unknown_email);
assert.equal(blueslip.get_test_logs('debug').length, 1);
blueslip.clear_test_data();
global.blueslip.error = function (msg) {
assert.equal(msg, 'Unknown email for get_user_id: ' + unknown_email);
};
blueslip.set_test_data('error', 'Unknown email for get_user_id: ' + unknown_email);
people.get_user_id(unknown_email);
assert.equal(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
blueslip.set_test_data('warn', 'No user_id provided for person@example.com');
var person = {
email: 'person@example.com',
user_id: undefined,
full_name: 'Person Person',
};
people.add(person);
assert.equal(warning, 'No user_id provided for person@example.com');
assert.equal(blueslip.get_test_logs('warn').length, 1);
blueslip.clear_test_data();
global.blueslip.error = function (msg) {
assert.equal(msg, 'No user_id found for person@example.com');
};
blueslip.set_test_data('error', 'No user_id found for person@example.com');
var user_id = people.get_user_id('person@example.com');
assert.equal(user_id, undefined);
assert.equal(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
global.blueslip.error = function (msg) {
assert.equal(msg, 'Unknown user ids: 1,2');
};
blueslip.set_test_data('error', 'Unknown user ids: 1,2');
people.user_ids_string_to_emails_string('1,2');
assert.equal(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
global.blueslip.warn = function (msg) {
assert.equal(msg, 'Unknown emails: ' + unknown_email);
};
blueslip.set_test_data('warn', 'Unknown emails: ' + unknown_email);
people.email_list_to_user_ids_string(unknown_email);
assert.equal(blueslip.get_test_logs('warn').length, 1);
blueslip.clear_test_data();
var message = {
type: 'private',
display_recipient: [],
sender_id: me.user_id,
};
global.blueslip.error = function (msg) {
assert.equal(msg, 'Empty recipient list in message');
};
blueslip.set_test_data('error', 'Empty recipient list in message');
people.pm_with_user_ids(message);
people.group_pm_with_user_ids(message);
assert.equal(blueslip.get_test_logs('error').length, 2);
blueslip.clear_test_data();
var charles = {
email: 'charles@example.com',
@@ -100,18 +105,17 @@ people.initialize_current_user(me.user_id);
],
sender_id: charles.user_id,
};
global.blueslip.error = function (msg) {
assert.equal(msg, 'Unknown user id in message: 42');
};
blueslip.set_test_data('error', 'Unknown user id in message: 42');
var reply_to = people.pm_reply_to(message);
assert(reply_to.indexOf('?') > -1);
assert.equal(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
people.pm_with_user_ids = function () { return [42]; };
people.get_person_from_user_id = function () { return; };
global.blueslip.error = function (msg) {
assert.equal(msg, 'Unknown people in message');
};
blueslip.set_test_data('error', 'Unknown people in message');
var uri = people.pm_with_url({});
assert.equal(uri.indexOf('unk'), uri.length - 3);
assert.equal(blueslip.get_test_logs('error').length, 1);
blueslip.clear_test_data();
}());

View File

@@ -9,7 +9,7 @@ set_global('stream_popover', {
});
set_global('unread', {});
set_global('unread_ui', {});
set_global('blueslip', {});
set_global('blueslip', global.make_zblueslip());
set_global('popovers', {
hide_all: function () {},
});
@@ -45,12 +45,11 @@ global.people.initialize_current_user(me.user_id);
(function test_get_conversation_li() {
var test_conversation = 'foo@example.com,bar@example.com';
var error_msg;
global.blueslip.warn = function (error) {
error_msg = error;
};
blueslip.set_test_data('warn', 'Unknown conversation: ' + test_conversation);
blueslip.set_test_data('warn', 'Unknown emails: ' + test_conversation); // people.js
pm_list.get_conversation_li(test_conversation);
assert.equal(error_msg, 'Unknown conversation: ' + test_conversation);
assert.equal(blueslip.get_test_logs('warn').length, 2);
blueslip.clear_test_data();
}());
(function test_close() {

View File

@@ -0,0 +1,177 @@
set_global('$', global.make_zjquery());
set_global('i18n', global.stub_i18n);
zrequire('hashchange');
zrequire('hash_util');
zrequire('narrow');
zrequire('narrow_state');
zrequire('people');
zrequire('presence');
var noop = function () {};
$.fn.popover = noop; // this will get wrapped by our code
zrequire('popovers');
set_global('current_msg_list', {});
set_global('page_params', {
custom_profile_fields: [],
});
set_global('rows', {});
set_global('templates', {});
set_global('message_viewport', {
height: () => 500,
});
set_global('emoji_picker', {
hide_emoji_popover: noop,
});
set_global('stream_popover', {
hide_stream_popover: noop,
hide_topic_popover: noop,
hide_all_messages_popover: noop,
restore_stream_list_size: noop,
});
set_global('ClipboardJS', function (sel) {
assert.equal(sel, '.copy_link');
});
var alice = {
email: 'alice@example.com',
full_name: 'Alice Smith',
user_id: 42,
};
var me = {
email: 'me@example.com',
user_id: 30,
full_name: 'Me Myself',
timezone: 'US/Pacific',
};
function initialize_people() {
people.init();
people.add_in_realm(me);
people.add_in_realm(alice);
people.initialize_current_user(me.user_id);
}
initialize_people();
function make_image_stubber() {
var images = [];
function stub_image() {
var image = {};
image.to_$ = () => {
return {
on: (name, f) => {
assert.equal(name, "load");
image.load_f = f;
},
};
};
images.push(image);
return image;
}
set_global('Image', function () {
return stub_image();
});
return {
get: (i) => images[i],
};
}
(function test_sender_hover() {
popovers.register_click_handlers();
var handler = $('#main_div').get_on_handler('click', '.sender_info_hover');
var e = {
stopPropagation: noop,
};
var message = {
id: 999,
sender_id: alice.user_id,
};
var target = $.create('click target');
target.offset = () => {
return {
top: 10,
};
};
rows.id = () => message.id;
current_msg_list.get = (msg_id) => {
assert.equal(msg_id, message.id);
return message;
};
current_msg_list.select_id = (msg_id) => {
assert.equal(msg_id, message.id);
};
target.closest = (sel) => {
assert.equal(sel, '.message_row');
return {};
};
templates.render = function (fn, opts) {
switch (fn) {
case 'user_info_popover':
assert.deepEqual(opts, {
class: 'message-info-popover',
});
return 'popover-html';
case 'user_info_popover_title':
assert.deepEqual(opts, {
user_avatar: 'avatar/alice@example.com',
});
return 'title-html';
case 'user_info_popover_content':
assert.deepEqual(opts, {
user_full_name: 'Alice Smith',
user_email: 'alice@example.com',
user_id: 42,
user_time: undefined,
presence_status: 'offline',
user_last_seen_time_status: 'translated: Unknown',
pm_with_uri: '#narrow/pm-with/42-alice',
sent_by_uri: '#narrow/sender/42-alice',
narrowed: false,
private_message_class: 'respond_personal_button',
show_user_profile: false,
is_me: false,
is_active: true,
is_bot: undefined,
is_sender_popover: true,
});
return 'content-html';
default:
throw Error('unrecognized template: ' + fn);
}
};
$('.user_popover_email').each = noop;
var image_stubber = make_image_stubber();
handler.call(target, e);
var avatar_img = image_stubber.get(0);
assert.equal(avatar_img.src, 'avatar/42/medium');
// todo: load image
}());

View File

@@ -159,15 +159,15 @@ people.initialize_current_user(me.user_id);
presence.set_info(presences, base_time);
assert.deepEqual(presence.presence_info[alice.user_id],
{ status: 'active', mobile: false, last_active: 500}
{ status: 'active', mobile: false, last_active: 500}
);
assert.deepEqual(presence.presence_info[fred.user_id],
{ status: 'idle', mobile: false, last_active: 500}
{ status: 'idle', mobile: false, last_active: 500}
);
assert.deepEqual(presence.presence_info[zoe.user_id],
{ status: 'offline', mobile: false, last_active: undefined}
{ status: 'offline', mobile: false, last_active: undefined}
);
assert(!presence.presence_info[bot.user_id]);

View File

@@ -158,43 +158,43 @@ set_global('current_msg_list', {
result.sort(function (a, b) { return a.count - b.count; });
var expected_result = [
{
emoji_name: 'frown',
reaction_type: 'unicode_emoji',
emoji_code: '1f626',
local_id: 'unicode_emoji,frown,1f626',
count: 1,
user_ids: [7],
title: 'Cali reacted with :frown:',
emoji_alt_code: false,
class: 'message_reaction',
},
{
emoji_name: 'inactive_realm_emoji',
reaction_type: 'realm_emoji',
emoji_code: '992',
local_id: 'realm_emoji,inactive_realm_emoji,992',
count: 1,
user_ids: [5],
title: 'You (click to remove) reacted with :inactive_realm_emoji:',
emoji_alt_code: false,
is_realm_emoji: true,
url: 'TBD',
class: 'message_reaction reacted',
},
{
emoji_name: 'smile',
reaction_type: 'unicode_emoji',
emoji_code: '1f604',
local_id: 'unicode_emoji,smile,1f604',
count: 2,
user_ids: [5, 6],
title: 'You (click to remove) and Bob van Roberts reacted with :smile:',
emoji_alt_code: false,
class: 'message_reaction reacted',
},
];
assert.deepEqual(result, expected_result);
{
emoji_name: 'frown',
reaction_type: 'unicode_emoji',
emoji_code: '1f626',
local_id: 'unicode_emoji,frown,1f626',
count: 1,
user_ids: [7],
title: 'Cali reacted with :frown:',
emoji_alt_code: false,
class: 'message_reaction',
},
{
emoji_name: 'inactive_realm_emoji',
reaction_type: 'realm_emoji',
emoji_code: '992',
local_id: 'realm_emoji,inactive_realm_emoji,992',
count: 1,
user_ids: [5],
title: 'You (click to remove) reacted with :inactive_realm_emoji:',
emoji_alt_code: false,
is_realm_emoji: true,
url: 'TBD',
class: 'message_reaction reacted',
},
{
emoji_name: 'smile',
reaction_type: 'unicode_emoji',
emoji_code: '1f604',
local_id: 'unicode_emoji,smile,1f604',
count: 2,
user_ids: [5, 6],
title: 'You (click to remove) and Bob van Roberts reacted with :smile:',
emoji_alt_code: false,
class: 'message_reaction reacted',
},
];
assert.deepEqual(result, expected_result);
}());
(function test_sending() {
@@ -288,7 +288,7 @@ set_global('current_msg_list', {
reactions.set_reaction_count(reaction_element, 5);
assert.equal(count_element.html(), '5');
assert.equal(count_element.text(), '5');
}());
(function test_get_reaction_section() {
@@ -388,7 +388,7 @@ set_global('current_msg_list', {
reactions.add_reaction(bob_event);
assert(title_set);
assert.equal(count_element.html(), '2');
assert.equal(count_element.text(), '2');
// Now, remove Bob's 8ball emoji. The event has the same exact
// structure as the add event.
@@ -402,7 +402,7 @@ set_global('current_msg_list', {
reactions.remove_reaction(bob_event);
assert(title_set);
assert.equal(count_element.html(), '1');
assert.equal(count_element.text(), '1');
var current_emojis = reactions.get_emojis_used_by_user_for_message_id(1001);
assert.deepEqual(current_emojis, ['smile', 'inactive_realm_emoji', '8ball']);

View File

@@ -0,0 +1,93 @@
zrequire('scroll_util');
(function test_scroll_delta() {
// If we are entirely on-screen, don't scroll
assert.equal(0, scroll_util.scroll_delta({
elem_top: 1,
elem_bottom: 9,
container_height: 10,
}));
assert.equal(0, scroll_util.scroll_delta({
elem_top: -5,
elem_bottom: 15,
container_height: 10,
}));
// The top is offscreen.
assert.equal(-3, scroll_util.scroll_delta({
elem_top: -3,
elem_bottom: 5,
container_height: 10,
}));
assert.equal(-3, scroll_util.scroll_delta({
elem_top: -3,
elem_bottom: -1,
container_height: 10,
}));
assert.equal(-11, scroll_util.scroll_delta({
elem_top: -150,
elem_bottom: -1,
container_height: 10,
}));
// The bottom is offscreen.
assert.equal(3, scroll_util.scroll_delta({
elem_top: 7,
elem_bottom: 13,
container_height: 10,
}));
assert.equal(3, scroll_util.scroll_delta({
elem_top: 11,
elem_bottom: 13,
container_height: 10,
}));
assert.equal(11, scroll_util.scroll_delta({
elem_top: 11,
elem_bottom: 99,
container_height: 10,
}));
}());
(function test_scroll_element_into_container() {
const container = (function () {
var top = 3;
return {
height: () => 100,
scrollTop: (arg) => {
if (arg === undefined) {
return top;
}
top = arg;
},
};
}());
const elem1 = {
height: () => 25,
position: () => {
return {
top: 0,
};
},
};
scroll_util.scroll_element_into_container(elem1, container);
assert.equal(container.scrollTop(), 3);
const elem2 = {
height: () => 15,
position: () => {
return {
top: 250,
};
},
};
scroll_util.scroll_element_into_container(elem2, container);
assert.equal(container.scrollTop(), 250 - 100 + 3 + 15);
}());

View File

@@ -248,9 +248,9 @@ topic_data.reset();
};
set_global('activity', {
get_huddles: function () {
return [];
},
get_huddles: function () {
return [];
},
});
var ted =
@@ -382,9 +382,9 @@ topic_data.reset();
assert.deepEqual(suggestions.strings, expected);
set_global('activity', {
get_huddles: function () {
return ['101,42', '101,103,42'];
},
get_huddles: function () {
return ['101,42', '101,103,42'];
},
});
// Simulate a past huddle which should now prioritize ted over alice
@@ -560,8 +560,8 @@ init();
global.stream_data.get_stream_id = function (stream_name) {
switch (stream_name) {
case 'office': return office_id;
case 'devel': return devel_id;
case 'office': return office_id;
case 'devel': return devel_id;
}
};
@@ -596,7 +596,7 @@ init();
return suggestions.lookup_table[q].description;
}
assert.equal(describe('te'), "Search for te");
assert.equal(describe('stream:office topic:team'), "Stream office > team");
assert.equal(describe('stream:office topic:team'), "Stream office &gt; team");
suggestions = search.get_suggestions('topic:staplers stream:office');
expected = [
@@ -758,9 +758,9 @@ init();
return suggestions.lookup_table[q].description;
}
assert.equal(describe('pm-with:ted@zulip.com'),
"Private messages with <strong>Te</strong>d Smith &lt;<strong>te</strong>d@zulip.com&gt;");
"Private messages with <strong>Te</strong>d Smith &lt;<strong>te</strong>d@zulip.com&gt;");
assert.equal(describe('sender:ted@zulip.com'),
"Sent by <strong>Te</strong>d Smith &lt;<strong>te</strong>d@zulip.com&gt;");
"Sent by <strong>Te</strong>d Smith &lt;<strong>te</strong>d@zulip.com&gt;");
suggestions = search.get_suggestions('Ted '); // note space

Some files were not shown because too many files have changed in this diff Show More