Files
meteor/tools/tests/apps/modules/tests.js
Ben Newman 8b04c25390 Emit multiple meteorInstall calls for distinct meteorInstallOptions.
I recently noticed a bug whereby modules transferred from the application
bundle to the modules bundle would lose their application-specific import
extensions, since all modules installed in the modules bundle have only
.js and .json as import extensions, matching default Node behavior.

This commit fixes that bug by emitting one meteorInstall call per distinct
meteorInstallOptions object. This logic would work if every module had a
different meteorInstallOptions object, but in practice the modules bundle
should end up with exactly two meteorInstall calls, because a single
options object is shared among all modules from the same source batch.
2018-06-28 18:07:34 -04:00

605 lines
16 KiB
JavaScript

import assert from "assert";
import fs from "fs";
import path from "path";
import moment from "moment";
import shared from "./imports/shared";
import { Meteor as ImportedMeteor } from "meteor/meteor";
describe("app modules", () => {
it("can be imported using absolute identifiers", () => {
assert.strictEqual(require("/tests"), exports);
});
it("can have different file extensions", () => {
assert.strictEqual(
require("./eager-jsx").extension,
".jsx"
);
assert.strictEqual(
require("./eager-coffee").extension,
".coffee"
);
assert.strictEqual(
require("/eager-jsx").extension,
".jsx"
);
assert.strictEqual(
require("/eager-coffee").extension,
".coffee"
);
});
it("are eagerly evaluated if outside imports/", () => {
assert.strictEqual(shared["/eager-jsx.jsx"], "eager jsx");
assert.strictEqual(shared["/eager-coffee.coffee"], "eager coffee");
});
it("are lazily evaluated if inside imports/", (done) => {
const delayMs = 200;
setTimeout(() => {
assert.strictEqual(shared["/imports/lazy1.js"], void 0);
assert.strictEqual(shared["/imports/lazy2.js"], void 0);
var reset1 = require("./imports/lazy1").reset;
assert.strictEqual(shared["/imports/lazy1.js"], 1);
assert.strictEqual(shared["/imports/lazy2.js"], 2);
// Make sure this test can run again without starting a new process.
require("./imports/lazy2").reset();
reset1();
done();
}, delayMs);
});
it("cannot import server modules on client", () => {
let error;
let result;
try {
result = require("./server/only").default;
} catch (expectedOnClient) {
error = expectedOnClient;
}
if (Meteor.isServer) {
assert.strictEqual(typeof error, "undefined");
assert.strictEqual(result, "/server/only.js");
assert.strictEqual(require("./server/only"),
require("/server/only"));
}
if (Meteor.isClient) {
assert.ok(error instanceof Error);
}
});
it("cannot import client modules on server", () => {
let error;
let result;
try {
result = require("./client/only").default;
} catch (expectedOnServer) {
error = expectedOnServer;
}
if (Meteor.isClient) {
assert.strictEqual(typeof error, "undefined");
assert.strictEqual(result, "/client/only.js");
assert.strictEqual(require("./client/only"),
require("/client/only"));
}
if (Meteor.isServer) {
assert.ok(error instanceof Error);
}
});
it("should not be parsed in strictMode", () => {
let foo = 1234;
delete foo;
});
it("should have access to filename and dirname", () => {
assert.strictEqual(require(__filename), exports);
assert.strictEqual(
require("path").relative(__dirname, __filename),
"tests.js"
);
});
it("can be implemented by directories", () => {
assert.strictEqual(
require("./imports/dir").fellBackTo,
"/imports/dir/index.js"
);
});
});
describe("template modules", () => {
Meteor.isClient &&
it("should be importable on the client", () => {
assert.strictEqual(typeof Template, "function");
assert.ok(! Object.prototype.hasOwnProperty.call(Template, "lazy"));
require("./imports/lazy.html");
assert.ok(Object.prototype.hasOwnProperty.call(Template, "lazy"));
assert.ok(Template.lazy instanceof Template);
});
Meteor.isServer &&
it("should not be importable on the server", () => {
let error;
try {
require("./imports/lazy.html");
} catch (expected) {
error = expected;
}
assert.ok(error instanceof Error);
});
});
Meteor.isClient &&
describe("css modules", () => {
it("should be loaded eagerly unless lazy", () => {
assert.strictEqual(
$(".app-eager-css").css("display"),
"none"
);
// Eager CSS is added unconditionally to a combined <style> tag at the
// beginning of the <head>. If the corresponding module ever gets
// imported, its module.exports object should be an empty stub, rather
// than a <style> node added dynamically to the <head>.
assert.deepEqual(Object.keys(require("./eager.css")), []);
});
it("should be importable by an app", () => {
assert.strictEqual($(".app-lazy-css").css("padding"), "0px");
require("./imports/lazy.css");
assert.strictEqual($(".app-lazy-css").css("padding"), "10px");
});
it("should be importable by a package", () => {
assert.strictEqual(
$(".pkg-lazy-css.imported").css("padding"),
"0px"
);
assert.strictEqual(
$(".pkg-lazy-css.not-imported").css("padding"),
"0px"
);
// Since client.js is a lazy main module, its side effects don't
// happen until we import it for the first time (here).
require("meteor/modules-test-package/client.js");
assert.strictEqual(
$(".pkg-lazy-css.imported").css("padding"),
"20px"
);
assert.strictEqual(
$(".pkg-lazy-css.not-imported").css("padding"),
"0px"
);
});
});
describe("native node_modules", () => {
Meteor.isServer &&
it("can be imported on the server", () => {
assert.strictEqual(typeof require("fs").readFile, "function");
});
Meteor.isClient &&
it("are imported as stubs on the client", () => {
const inherits = require("util").inherits;
assert.strictEqual(typeof inherits, "function");
});
Meteor.isServer &&
it("cannot be overridden on the server", () => {
assert.strictEqual(typeof require("repl").start, "function");
});
it("can be implemented by wrapper npm packages", () => {
const Stream = require("stream");
assert.strictEqual(typeof Stream, "function");
assert.strictEqual(typeof Stream.Readable, "function");
});
it("can all be imported", () => {
require("assert");
require("buffer");
require("child_process");
require("cluster");
require("console");
require("constants");
require("dgram");
require("dns");
require("domain");
require("events");
require("fs");
require("http");
require("https");
require("module");
require("net");
require("os");
require("path");
require("process");
require("querystring");
require("readline");
require("repl");
require("stream");
require("_stream_duplex");
require("_stream_passthrough");
require("_stream_readable");
require("_stream_transform");
require("_stream_writable");
require("string_decoder");
require("sys");
require("timers");
require("tls");
require("tty");
require("url");
require("util");
require("vm");
require("zlib");
// The crypto package automatically polyfills global.Buffer on the
// client, so save it for last to ensure that none of the packages
// above depend on global.Buffer.
require("crypto");
});
Meteor.isClient &&
it('should return Module from require("module")', () => {
assert.ok(module instanceof require("module"));
});
});
describe("local node_modules", () => {
it("should be importable", () => {
assert.strictEqual(require("moment"), moment);
const cal = moment().calendar();
assert.ok(cal.match(/\bat\b/));
});
it("can be imported using absolute identifiers", () => {
assert.strictEqual(
require("moment"),
require("/node_modules/moment")
);
});
it("should be importable by packages", () => {
// Defined in packages/modules-test-package/common.js.
assert.strictEqual(typeof regeneratorRuntime, "object");
});
it('should expose "version" field of package.json', () => {
const pkg = require("moment/package.json");
assert.strictEqual(pkg.version, "2.11.1");
});
it('should support object-valued package.json "browser" fields', () => {
const uuid = require("uuid");
const id = uuid();
assert.strictEqual(typeof id, "string");
assert.strictEqual(id.split("-").length, 5);
if (Meteor.isClient) {
assert.strictEqual(
require.resolve("uuid/lib/rng.js"),
"/node_modules/uuid/lib/rng-browser.js"
);
const { browser } = require(["uuid", "package.json"].join("/"));
assert.strictEqual(typeof browser, "object");
}
});
it('should support package.json without a "main" field', () => {
if (Meteor.isServer) {
// Only import mysql during server tests, but register the
// dependency in the client bundle, too.
assert.strictEqual(
typeof require("mysql").createQuery,
"function"
);
}
assert.strictEqual(
require.resolve("mysql"),
"/node_modules/mysql/index.js"
);
// Verify that the package.json stub is bundled even though it is not
// needed for looking up the index.js module (#9235).
const pkg = require(["mysql", "package.json"].join("/"));
assert.strictEqual(pkg.name, "mysql");
assert.strictEqual(pkg.main, void 0);
});
it('should support package.json with a directory "main" field', () => {
if (Meteor.isServer) {
assert.strictEqual(typeof require("cli-color"), "function");
}
assert.strictEqual(
require.resolve("cli-color"),
"/node_modules/cli-color/lib/index.js"
);
const pkg = require(["cli-color", "package.json"].join("/"));
assert.strictEqual(pkg.name, "cli-color");
assert.strictEqual(pkg.main, "lib");
// We have to use an older version of cli-color to get this unusual
// module layout, so we shouldn't let this change without notice.
assert.strictEqual(pkg.version, "0.2.3");
});
});
describe("Meteor packages", () => {
it("api.export should create named exports", () => {
assert.strictEqual(typeof ImportedMeteor, "object");
assert.strictEqual(Meteor, ImportedMeteor);
});
it("should be importable", () => {
assert.strictEqual(require("meteor/underscore")._, _);
const Blaze = require("meteor/blaze").Blaze;
assert.strictEqual(typeof Blaze, "object");
let error;
try {
require("meteor/nonexistent");
} catch (expected) {
error = expected;
}
assert.ok(error instanceof Error);
});
it("can be local", () => {
// ModulesTestPackage is only api.export-ed on the server.
if (Meteor.isServer) {
assert.strictEqual(ModulesTestPackage, "loaded");
}
if (Meteor.isClient) {
assert.strictEqual(typeof ModulesTestPackage, "undefined");
}
// But it is importable by both client and server.
const mtp = require("meteor/modules-test-package");
assert.strictEqual(mtp.ModulesTestPackage, "loaded");
assert.strictEqual(mtp.where, Meteor.isServer ? "server" : "client");
});
it("should expose their files for import", () => {
const osStub = require("meteor/modules-test-package/os-stub");
assert.strictEqual(
osStub.platform(),
"browser"
);
assert.strictEqual(
osStub.name,
"/node_modules/meteor/modules-test-package/os-stub.js"
);
});
it("should only include lazy files if platform uses modules", function () {
if (Meteor.isServer) {
const clientOnlyIsopackPath = path.join(
// Currently process.cwd is .meteor/local/build/programs/server.
"..", "..", "..", "isopacks", "client-only-ecmascript"
);
const webJsonPath = path.join(
clientOnlyIsopackPath,
"web.browser.json"
);
const osJsonPath = path.join(
clientOnlyIsopackPath,
"os.json"
);
const web = JSON.parse(fs.readFileSync(webJsonPath));
assert.deepEqual(web.resources.map(function (res) {
return res.path;
}), ["client.js", "imported.js", "server.js"]);
assert.strictEqual(web.resources[0].fileOptions.mainModule, true);
assert.strictEqual(web.resources[1].fileOptions.lazy, true);
assert.strictEqual(web.resources[2].fileOptions.lazy, true);
const os = JSON.parse(fs.readFileSync(osJsonPath));
assert.deepEqual(os.resources.map(function (res) {
return res.path;
}), ["server.js"]);
assert.deepEqual(os.resources[0].fileOptions, {
lazy: false
});
assert.deepEqual(ServerTypeof, {
require: "undefined",
exports: "undefined"
});
} else {
assert.deepEqual(require("meteor/client-only-ecmascript"), {
name: "client-only-ecmascript",
imported: "/node_modules/meteor/client-only-ecmascript/imported.js",
ClientTypeof: {
require: "function",
exports: "object"
}
});
}
});
it("can define complex compiler plugins", () => {
const { one, array } = require("meteor/modules-test-plugin");
assert.strictEqual(one.string, "one");
assert.strictEqual(one.number, 1);
assert.strictEqual(one.self, one)
assert.strictEqual(array[0], "asdf");
assert.strictEqual(array[1], array);
assert.strictEqual(array[2], Infinity);
});
});
describe("symlinking node_modules", () => {
it("should allow selective compilation of npm packages", () => {
import each from "lodash-es/each";
import range from "lodash-es/range";
const n = 100;
let sum = 0;
each(range(1, n + 1), n => sum += n);
assert.strictEqual(sum, n * (n + 1) / 2);
});
it("should preserve equivalence for require", async () => {
const pkg1 = require("immutable-tuple/package.json");
const pkg2 = require("./imports/links/immutable-tuple/package");
assert.strictEqual(pkg1.author.name, "Ben Newman");
assert.strictEqual(pkg1.author, pkg2.author);
});
it("should preserve equivalence for dynamic import()", async () => {
const { default: tuple1 } = await import("immutable-tuple/src/tuple");
const { tuple: tuple2 } =
await import("/imports/links/immutable-tuple/src/tuple");
assert.strictEqual(tuple1, tuple2);
});
it("should support application-compiled `npm link`ed packages", () => {
assert.strictEqual(
require.resolve("acorn"),
"/node_modules/acorn/src/index.js"
);
const { parse } = require("acorn");
assert.strictEqual(typeof parse, "function");
assert.strictEqual(
parse,
require("./imports/links/acorn").parse
);
});
it("should not break custom import extensions", () => {
import { jsx } from "jsx-import-test";
assert.strictEqual(jsx.type, "div");
assert.strictEqual(jsx.props.children, "oyez");
return import("./imports/links/jsx-import-test/child").then(ns => {
assert.strictEqual(ns.default, jsx);
});
});
});
describe("ecmascript miscellany", () => {
it("JSX should work in .js files on both client and server", () => {
const React = {
createElement: function (name, attrs, children) {
assert.strictEqual(name, "div");
assert.strictEqual(attrs, null);
assert.strictEqual(children, "hi");
return "all good";
}
};
assert.strictEqual(<div>hi</div>, "all good");
});
it("async functions should work on the client and server", async () => {
assert.strictEqual(
await 2 + 2,
await new Promise(resolve => resolve(4))
);
});
it("rest paramters in inner arrow functions (#6312)", () => {
const rev = (...args) => args.reverse();
assert.deepEqual(rev(1, 2, 3), [3, 2, 1]);
});
it("rest parameters in property arrow functions (#6514)", () => {
const obj = {
method: (a, b, ...rest) => {
rest.push(a, b);
return rest.reverse();
}
};
assert.deepEqual(
obj.method(1, 2, 3, 4),
[2, 1, 4, 3]
);
});
it('.babelrc "env" should be respected', () => {
assert.strictEqual(
require("./imports/babel-env.js").check(2),
4
);
});
it("should support plugins like proposal-optional-chaining", () => {
const { check } = require("./imports/chaining.js");
assert.strictEqual(check({
oyez: true
}), void 0);
assert.strictEqual(check({
foo: {
bar: {
baz: {
qux: 1234
}
}
}
}), 1234);
});
});
Meteor.isClient &&
describe("client/compatibility directories", () => {
it("should contain bare files", () => {
assert.strictEqual(topLevelVariable, 1234);
});
});
describe(".es5.js files", () => {
it("should not be transpiled", () => {
assert.strictEqual(require("./imports/plain.es5.js").let, "ok");
});
});
describe("return statements at top level", () => {
it("should be legal", () => {
var ret = require("./imports/return.js");
assert.strictEqual(ret.a, 1234);
assert.strictEqual(ret.b, void 0);
});
});
describe("circular package.json resolution chains", () => {
it("should be broken appropriately", () => {
assert.strictEqual(
require("./lib").aMain,
"/lib/a/index.js"
);
});
});