Commit Graph

2613 Commits

Author SHA1 Message Date
Anders Kaseorg 2d18424da0 message_list_data: Convert _hash from object to Map.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-12 10:39:01 -08:00
Anders Kaseorg 0936381cfd echo: Convert waiting_for_ack from object to Map.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-12 10:39:01 -08:00
Anders Kaseorg 2ddd3d046a echo: Further reduce local_id string vs. number confusion.
New rule: local_id is always a string, local_id_float is always a
number.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-12 10:39:01 -08:00
Anders Kaseorg 2834b051d2 js: Read zid with rows.id.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-12 10:39:01 -08:00
Anders Kaseorg 8892f463a8 zjquery: Fix zjquery.state.
Fixes an incorrect translation from _.map in #13850.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-12 10:39:01 -08:00
Anders Kaseorg d7d8632525 pygments_data: Replace JS module with JSON module.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-12 10:09:12 -08:00
Anders Kaseorg e257253e64 emoji_codes: Replace JS module with JSON module.
webpack optimizes JSON modules using JSON.parse("{…}"), which is
faster than the normal JavaScript parser.

Update the backend to use emoji_codes.json too instead of the three
separate JSON files.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-12 10:09:12 -08:00
Steve Howell dce7118ec7 node tests: Remove template/handlebars dependencies.
The settings_bots tests don't actually use
any templates.
2020-02-12 09:58:23 -08:00
Steve Howell 7ec5fbab2e node tests: Remove templates test.
I believe we can remove these and rely on
other parts of our testing/code-review
to ensure template quality.

These tests never really exercised our
app code, as evidenced by us not regressing
any of the 100%-line-coverage files.

We have a couple other ways that we verify
the correct format of the templates:

    - webpack (can they compile?)
    - check-templates (are they nicely indented?)

For deep testing, we have Casper, which
exercises most of our most important templates
in some meaningful way.

I think it's pretty rare that we get bugs
now that are directly caused by bad templates,
and an even smaller subset of them would
have been caught by the node tests.

If that trend changes in the future, I would prefer to
just do something "greenfield" to address
any common problems rather than resurrect
this code, but we could always resurrect it
from git.

The template node tests did check a little bit of
detail about which fields are there, but not
in an integrated way, so that aspect of the tests
wasn't very useful either.
2020-02-12 09:58:23 -08:00
Anders Kaseorg 72dddb7af6 zjsunit: Use assert in strict mode.
This makes assert.equal and assert.deepEqual compare using === rather
than ==, to catch more bugs.

https://nodejs.org/api/assert.html#assert_strict_mode

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-12 08:16:26 -05:00
Anders Kaseorg d16a730d81 zjquery: Mock text more accurately.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-12 08:16:26 -05:00
Anders Kaseorg 59d55d1e06 js: Use modern spread arguments syntax.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-11 17:43:35 -08:00
Steve Howell 795c3ad8a5 zjquery: Remove closest() implementation.
This effectively reverts the following
commit from May 2019:

be527905ca

The implementation of closest() was a bit
buggy and complex.  It's easy enough
to just stub the method yourself.  We may
want to eventually re-implement it, but we
should follow the template of parent/set_parent.

If you fail to stub `closest` zjquery gives
a fairly helpful error message:

Error: You must create a stub for $("link-stub").closest
2020-02-11 14:19:03 -05:00
Steve Howell 8f0018ee7c node tests: Use stubs in lightbox tests.
We stub out jquery elements rather than giving
the illusion of having real DOM.

Also, we make it so that the message_store
interaction has an assertion attached to it.
2020-02-11 14:19:03 -05:00
Anders Kaseorg 7ca52f1e80 js: Convert _.keys to Object.keys.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 15:57:20 -08:00
Anders Kaseorg 76e6ffe3c3 tests: Convert all_pills from object to Map.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 15:57:20 -08:00
Steve Howell 14396c3e32 presence: Prep for upcoming changes to server data.
In the next commit we're going to change what the
server sends for the following:

    - page_params
    - server responses to /json/users/me/presence

We will **not** yet be changing the format of the data
that we get in events when users update their presence.

It's also just a bit in flux what our final formats
will be for various presence payloads, and different
optimizations may lead us to use different data
structures in different payloads.

So for now we decouple these two things:

    raw_info: this is intended to represent a
        snapshot of the latest data from the
        server, including some data like
        timestamps that are only used
        in downstream calculations and not
        user-facing

    exports.presence_info: this is calculated
        info for modules like buddy_data that
        just need to know active vs. idle and
        last_active_date

Another change that happens here is we rename
set_info_for_user to update_info_for_event,
which just makes it clear that the function
expects data in the "event" format (as opposed
to the format for page_params or server
responses).

As of now keeping the intermediate raw_info data
around feels slightly awkward, because we just
immediately calculate presence_info for any kind
of update.  This may be sorta surprising if you
just skim the code and see the various timeout
constants.  You would think we might be automatically
expiring "active" statuses in the client due to
the simple passage of time, but in fact the precise
places we do this are all triggered by new data
from the server and we re-calculate statuses
immediately.

(There are indirect ways that clients
have timing logic, since they ask the
server for new data at various intervals, but a
smarter client could simply expire users on its
own, or at least with a more efficient transfer
of info between it and the server. One of
the thing that complicates client-side logic
is that server and client clocks may be out
of sync.  Also, it's not inherently super expensive
to get updates from the server.)
2020-02-10 14:37:44 -08:00
Steve Howell 75dbc4a2b1 presence: Extract presence.initialize(). 2020-02-10 14:37:44 -08:00
Steve Howell b63909ad41 node tests: Simplify activate_people().
The important details for the test setup here
are just the number of users who are active.
We don't need to simulate the currently awkward
way of populating this data.
2020-02-10 14:37:44 -08:00
Anders Kaseorg d0b0a64af3 js: Convert _.isEmpty(a) to a.length !== 0.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 14:08:12 -08:00
Anders Kaseorg e1cf5e4630 js: Convert _.reduce to less convoluted code.
reduce is almost never a better solution than the alternatives.  Avoid
it.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 14:08:12 -08:00
Anders Kaseorg 050915c46c js: Convert _.size(a) to a.length.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 14:08:12 -08:00
Anders Kaseorg 612b237cec js: Convert remaining _.each(a, …) to a.forEach(…).
The _.each calls with an inline function expression have already been
converted to for…of loops.  We could do that here, but using .forEach
when we’re just reusing an existing function seems like a good
guideline.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 14:08:12 -08:00
Anders Kaseorg b566d11d69 js: Convert _.findIndex(a, …) to a.findIndex(…).
And convert the corresponding function expressions to arrow style
while we’re here.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 14:08:12 -08:00
Anders Kaseorg 70ff164f89 js: Convert _.any(a, …), _.some(a, …) to a.some(…).
And convert the corresponding function expressions to arrow style
while we’re here.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 14:08:12 -08:00
Anders Kaseorg 2285ee922e js: Convert _.contains(a, …) to a.includes(…).
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 14:08:12 -08:00
Anders Kaseorg 4948240619 js: Convert _.filter(a, …) to a.filter(…).
And convert the corresponding function expressions to arrow style
while we’re here.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 14:08:12 -08:00
Anders Kaseorg ac7b09d57e js: Convert _.map(a, …) to a.map(…).
And convert the corresponding function expressions to arrow style
while we’re here.

import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import K from "ast-types/gen/kinds";
import fs from "fs";
import path from "path";
import process from "process";

const checkExpression = (node: n.Node): node is K.ExpressionKind =>
  n.Expression.check(node);

for (const file of process.argv.slice(2)) {
  console.log("Parsing", file);
  const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
    parser: path.extname(file) === ".ts" ? tsParser : babelParser,
  });
  let changed = false;

  recast.visit(ast, {
    visitCallExpression(path) {
      const { callee, arguments: args } = path.node;
      if (
        n.MemberExpression.check(callee) &&
        !callee.computed &&
        n.Identifier.check(callee.object) &&
        callee.object.name === "_" &&
        n.Identifier.check(callee.property) &&
        callee.property.name === "map" &&
        args.length === 2 &&
        checkExpression(args[0]) &&
        checkExpression(args[1])
      ) {
        const [arr, fn] = args;
        path.replace(
          b.callExpression(b.memberExpression(arr, b.identifier("map")), [
            n.FunctionExpression.check(fn) ||
            n.ArrowFunctionExpression.check(fn)
              ? b.arrowFunctionExpression(
                  fn.params,
                  n.BlockStatement.check(fn.body) &&
                    fn.body.body.length === 1 &&
                    n.ReturnStatement.check(fn.body.body[0])
                    ? fn.body.body[0].argument || b.identifier("undefined")
                    : fn.body
                )
              : fn,
          ])
        );
        changed = true;
      }
      this.traverse(path);
    },
  });

  if (changed) {
    console.log("Writing", file);
    fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
  }
}

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 14:08:12 -08:00
Anders Kaseorg 719546641f js: Convert a.indexOf(…) !== -1 to a.includes(…).
Babel polyfills this for us for Internet Explorer.

import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import K from "ast-types/gen/kinds";
import fs from "fs";
import path from "path";
import process from "process";

const checkExpression = (node: n.Node): node is K.ExpressionKind =>
  n.Expression.check(node);

for (const file of process.argv.slice(2)) {
  console.log("Parsing", file);
  const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
    parser: path.extname(file) === ".ts" ? tsParser : babelParser,
  });
  let changed = false;

  recast.visit(ast, {
    visitBinaryExpression(path) {
      const { operator, left, right } = path.node;
      if (
        n.CallExpression.check(left) &&
        n.MemberExpression.check(left.callee) &&
        !left.callee.computed &&
        n.Identifier.check(left.callee.property) &&
        left.callee.property.name === "indexOf" &&
        left.arguments.length === 1 &&
        checkExpression(left.arguments[0]) &&
        ((["===", "!==", "==", "!=", ">", "<="].includes(operator) &&
          n.UnaryExpression.check(right) &&
          right.operator == "-" &&
          n.Literal.check(right.argument) &&
          right.argument.value === 1) ||
          ([">=", "<"].includes(operator) &&
            n.Literal.check(right) &&
            right.value === 0))
      ) {
        const test = b.callExpression(
          b.memberExpression(left.callee.object, b.identifier("includes")),
          [left.arguments[0]]
        );
        path.replace(
          ["!==", "!=", ">", ">="].includes(operator)
            ? test
            : b.unaryExpression("!", test)
        );
        changed = true;
      }
      this.traverse(path);
    },
  });

  if (changed) {
    console.log("Writing", file);
    fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
  }
}

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 14:08:12 -08:00
Anders Kaseorg 8dee0ab1e0 tests: Fix predicate mock in simulate_narrow.
predicate is expected to return a function, not a boolean.  The
boolean true was causing _.filter to match items with a property named
"true", which is definitely not what was intended.  Matching no items
is probably also not intended, but matching every item causes the test
to fail.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-10 14:08:12 -08:00
Anders Kaseorg 11b5d80800 tests: Fix more undefined mocks.
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2020-02-10 14:08:12 -08:00
Chris Heald fb09bd5df6 node: Fix lightbox tests to use message_store.get().
While there are still a number of problems with these tests, they now
more effectively exercise the real code.
2020-02-10 14:00:10 -08:00
Steve Howell fa1059aa2e stream_data: Remove stream_name param from add_sub().
We just get the stream_name from the sub struct now.

This mostly affects node tests.

The only place in real code where we called add_sub()
was when we initialized data from the server.
2020-02-09 22:08:50 -08:00
Vishnu KS 4572be8c27 api: Rename subject_links to topic_links.
Fixes #13588
2020-02-07 14:35:22 -08:00
Steve Howell e9c6653852 node tests: Always enforce blueslip warn/error/fatal.
We now require all of our unit tests to handle
blueslip errors for warn/error/fatal.  This
simplifies the zblueslip code to not have any
options passed in.

Most of the places changed here fell into two
categories:

    - We were just missing a random piece of
      setup data in a happy path test.

    - We were testing error handling in just
      a lazy way to ensure 100% coverage.  Often
      these error codepaths were fairly
      contrived.

The one place where we especially lazy was
the stream_data tests, and those are now
more thorough.
2020-02-07 14:15:44 -08:00
Steve Howell 996d054fe9 messages: Send stream_id for stream messages.
This saves a tiny bit of bandwidth, but more
importantly, it protects us against races for
stream name changes.  There's some argument that
if the user is thinking they're sending to
old_stream_name, and unbeknownst to them, the
stream has changed to new_stream_name, then we
should fail.  But I think 99% of the time the
user just wants the message to go that stream
despite any renames.

In order to verify the blueslip error, we
had to turn on error checking, which required
a tiny fix to a place where we left out
a stream_id for add_sub.
2020-02-07 14:15:44 -08:00
Anders Kaseorg 02511bff1c js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.

import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";

const checkExpression = (node: n.Node): node is K.ExpressionKind =>
  n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
  n.Statement.check(node);

for (const file of process.argv.slice(2)) {
  console.log("Parsing", file);
  const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
    parser: path.extname(file) === ".ts" ? tsParser : babelParser,
  });
  let changed = false;
  let inLoop = false;
  let replaceReturn = false;

  const visitLoop = (...args: string[]) =>
    function(this: Context, path: NodePath) {
      for (const arg of args) {
        this.visit(path.get(arg));
      }
      const old = { inLoop };
      inLoop = true;
      this.visit(path.get("body"));
      inLoop = old.inLoop;
      return false;
    };

  recast.visit(ast, {
    visitDoWhileStatement: visitLoop("test"),

    visitExpressionStatement(path) {
      const { expression, comments } = path.node;
      let valueOnly;
      if (
        n.CallExpression.check(expression) &&
        n.MemberExpression.check(expression.callee) &&
        !expression.callee.computed &&
        n.Identifier.check(expression.callee.object) &&
        expression.callee.object.name === "_" &&
        n.Identifier.check(expression.callee.property) &&
        ["each", "forEach"].includes(expression.callee.property.name) &&
        [2, 3].includes(expression.arguments.length) &&
        checkExpression(expression.arguments[0]) &&
        (n.FunctionExpression.check(expression.arguments[1]) ||
          n.ArrowFunctionExpression.check(expression.arguments[1])) &&
        [1, 2].includes(expression.arguments[1].params.length) &&
        n.Identifier.check(expression.arguments[1].params[0]) &&
        ((valueOnly = expression.arguments[1].params[1] === undefined) ||
          n.Identifier.check(expression.arguments[1].params[1])) &&
        (expression.arguments[2] === undefined ||
          n.ThisExpression.check(expression.arguments[2]))
      ) {
        const old = { inLoop, replaceReturn };
        inLoop = false;
        replaceReturn = true;
        this.visit(
          path
            .get("expression")
            .get("arguments")
            .get(1)
            .get("body")
        );
        inLoop = old.inLoop;
        replaceReturn = old.replaceReturn;

        const [right, { body, params }] = expression.arguments;
        const loop = b.forOfStatement(
          b.variableDeclaration("let", [
            b.variableDeclarator(
              valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
            ),
          ]),
          valueOnly
            ? right
            : b.callExpression(
                b.memberExpression(right, b.identifier("entries")),
                []
              ),
          checkStatement(body) ? body : b.expressionStatement(body)
        );
        loop.comments = comments;
        path.replace(loop);
        changed = true;
      }
      this.traverse(path);
    },

    visitForStatement: visitLoop("init", "test", "update"),

    visitForInStatement: visitLoop("left", "right"),

    visitForOfStatement: visitLoop("left", "right"),

    visitFunction(path) {
      this.visit(path.get("params"));
      const old = { replaceReturn };
      replaceReturn = false;
      this.visit(path.get("body"));
      replaceReturn = old.replaceReturn;
      return false;
    },

    visitReturnStatement(path) {
      if (replaceReturn) {
        assert(!inLoop); // could use labeled continue if this ever fires
        const { argument, comments } = path.node;
        if (argument === null) {
          const s = b.continueStatement();
          s.comments = comments;
          path.replace(s);
        } else {
          const s = b.expressionStatement(argument);
          s.comments = comments;
          path.replace(s, b.continueStatement());
        }
        return false;
      }
      this.traverse(path);
    },

    visitWhileStatement: visitLoop("test"),
  });

  if (changed) {
    console.log("Writing", file);
    fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
  }
}

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00
Anders Kaseorg 0c8d199a3d tests: Mock empty lists correctly in page_params.
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2020-02-07 14:09:47 -08:00
Anders Kaseorg ece4d9344a tests: Add missing options argument to poll_data_holder.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00
Anders Kaseorg 19f7c6f012 zjsunit: Replace add_extensions with Object.assign.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00
Anders Kaseorg 940ff9e95f zjsunit: Use modern spread arguments syntax.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00
Anders Kaseorg dc742a5629 tests: Convert sub_row_data from object to array.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00
Anders Kaseorg dd10108c24 user_status: Iterate over page_params.user_status with Object.entries.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00
Anders Kaseorg 5564b39ea4 tests: Iterate over page_param_checkbox_options with Object.keys.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00
Anders Kaseorg 0d05decbe4 presence: Iterate over presence info with Object.entries.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00
Anders Kaseorg 3b4dc2d8d8 tests: Iterate over tabs with Object.values.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00
Anders Kaseorg 8bb515dbd9 emoji: Iterate over emoji_codes data with Object.{entries,values}.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00
Anders Kaseorg 5de013b11e emoji: Iterate over realm_emoji with Object.values.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00
Anders Kaseorg 52a8449a0e widgetize: Convert widget_contents from object to Map.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 17:24:43 -08:00
Anders Kaseorg 737efd1fac presence: Convert presence_info from object to Map.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 17:24:43 -08:00