mirror of
https://github.com/hanzoai/protobuf.js.git
synced 2026-09-24 17:02:08 +00:00
361 lines
13 KiB
JavaScript
361 lines
13 KiB
JavaScript
// Tests for pbts TypeScript declaration generation.
|
|
|
|
var tape = require("tape");
|
|
var path = require("path");
|
|
var fs = require("fs");
|
|
var EventEmitter = require("events").EventEmitter;
|
|
var child_process = require("child_process");
|
|
|
|
tape.test("pbts passes jsdoc arguments without a shell", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
var originalSpawn = child_process.spawn;
|
|
var file = "file with \"quotes\" `backticks` 'apostrophes' and ;.js";
|
|
|
|
test.plan(5);
|
|
|
|
child_process.spawn = function(cmd, args, options) {
|
|
var child = new EventEmitter();
|
|
child.stdout = new EventEmitter();
|
|
child.stderr = { pipe: function() {} };
|
|
var nodePath = typeof Bun !== "undefined"
|
|
? process.env.npm_node_execpath || "node"
|
|
: process.execPath;
|
|
|
|
test.equal(cmd, nodePath, "should execute node directly");
|
|
test.ok(/jsdoc[\\/]jsdoc\.js$/.test(args[0]), "should execute jsdoc directly");
|
|
test.equal(args[args.length - 1], file, "should pass file path as a single argument");
|
|
test.equal(options.stdio, "pipe", "should pipe jsdoc output");
|
|
|
|
process.nextTick(function() {
|
|
child.stdout.emit("data", "declare namespace test {}\n");
|
|
child.stdout.emit("end");
|
|
child.emit("close", 0);
|
|
});
|
|
|
|
return child;
|
|
};
|
|
|
|
pbts.main([file], function(err) {
|
|
child_process.spawn = originalSpawn;
|
|
test.error(err, "should generate definitions");
|
|
});
|
|
});
|
|
|
|
tape.test("pbts supports explicit import mappings", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * Reflected foo.",
|
|
" * @name Foo",
|
|
" * @type {$protobuf.Type}",
|
|
" * @const",
|
|
" */"
|
|
].join("\n"), ["--import", "\\$protobuf=.."], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("import * as $protobuf from \"..\";") >= 0, "overrides the protobuf import");
|
|
test.ok(tsCode.indexOf("import Long = require(\"long\");") >= 0, "keeps default Long import");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts supports explicit imports with main output", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * Reflected foo.",
|
|
" * @name Foo",
|
|
" * @type {$protobuf.Type}",
|
|
" * @const",
|
|
" */"
|
|
].join("\n"), ["--main", "--import", "\\$protobuf=.."], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("// DO NOT EDIT!") >= 0, "emits generated header");
|
|
test.ok(tsCode.indexOf("import * as $protobuf from \"..\";") >= 0, "emits explicit import");
|
|
test.equal(tsCode.indexOf("import Long = require(\"long\");"), -1, "does not emit default Long import");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts emits exported overloads", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * Makes a value.",
|
|
" * @name make",
|
|
" * @function",
|
|
" * @type {{",
|
|
" * (name: string): number;",
|
|
" * (id: number): number;",
|
|
" * }}",
|
|
" */"
|
|
].join("\n"), [], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("export function make(name: string): number;") >= 0, "exports first overload");
|
|
test.ok(tsCode.indexOf("export function make(id: number): number;") >= 0, "exports subsequent overload");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts emits overloads with nested parameter types", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * Makes a value.",
|
|
" * @name makeNested",
|
|
" * @function",
|
|
" * @type {{",
|
|
" * (callback: (value: string) => void): number;",
|
|
" * (name: string): number;",
|
|
" * }}",
|
|
" */"
|
|
].join("\n"), [], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("export function makeNested(callback: (value: string) => void): number;") >= 0, "keeps nested function parameter type");
|
|
test.ok(tsCode.indexOf("export function makeNested(name: string): number;") >= 0, "keeps second overload");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts preserves multiline TypeScript @type expressions", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * Reflected descriptor proto.",
|
|
" * @name DescriptorProto",
|
|
" * @type {$protobuf.Type & {",
|
|
" * ExtensionRange: $protobuf.Type,",
|
|
" * ReservedRange: $protobuf.Type",
|
|
" * }}",
|
|
" * @const",
|
|
" */"
|
|
].join("\n"), ["--import", "\\$protobuf=.."], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("export const DescriptorProto: $protobuf.Type & {\n ExtensionRange: $protobuf.Type,\n ReservedRange: $protobuf.Type\n};") >= 0, "preserves multiline object type");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts preserves array element union precedence", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * Repeated long-compatible field shape.",
|
|
" * @typedef {{",
|
|
" * ids?: Array.<number|Long>|null;",
|
|
" * combined?: Array.<Foo&Bar>|null;",
|
|
" * }} Shape",
|
|
" */"
|
|
].join("\n"), [], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("ids?: (number|Long)[]|null;") >= 0, "parenthesizes array element union");
|
|
test.equal(tsCode.indexOf("ids?: number|Long[]|null;"), -1, "does not emit union with array-only Long");
|
|
test.ok(tsCode.indexOf("combined?: (Foo&Bar)[]|null;") >= 0, "parenthesizes array element intersection");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts preserves qualified Object type names", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * Uses plain Object.",
|
|
" * @name PlainObjectUser",
|
|
" * @type {Object}",
|
|
" * @const",
|
|
" */",
|
|
"",
|
|
"/**",
|
|
" * Uses a generated message named Object.",
|
|
" * @name ObjectUser",
|
|
" * @type {foo.Object}",
|
|
" * @const",
|
|
" */",
|
|
""
|
|
].join("\n"), [], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("export const PlainObjectUser: object;") >= 0, "converts plain Object to object");
|
|
test.ok(tsCode.indexOf("export const ObjectUser: foo.Object;") >= 0, "preserves qualified Object names");
|
|
test.equal(tsCode.indexOf("foo.object"), -1, "does not lower-case qualified Object names");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts preserves type names starting with literal tokens", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * @name TruePrefixUser",
|
|
" * @type {trueWireless.$Properties}",
|
|
" * @const",
|
|
" */",
|
|
"",
|
|
"/**",
|
|
" * @name FalsePrefixUser",
|
|
" * @type {falseValue.$Properties}",
|
|
" * @const",
|
|
" */",
|
|
"",
|
|
"/**",
|
|
" * @name NullPrefixUser",
|
|
" * @type {nullable.$Properties}",
|
|
" * @const",
|
|
" */",
|
|
"",
|
|
"/**",
|
|
" * @name UndefinedPrefixUser",
|
|
" * @type {undefinedThing.$Properties}",
|
|
" * @const",
|
|
" */",
|
|
""
|
|
].join("\n"), [], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("export const TruePrefixUser: trueWireless.$Properties;") >= 0, "preserves true-prefixed type names");
|
|
test.ok(tsCode.indexOf("export const FalsePrefixUser: falseValue.$Properties;") >= 0, "preserves false-prefixed type names");
|
|
test.ok(tsCode.indexOf("export const NullPrefixUser: nullable.$Properties;") >= 0, "preserves null-prefixed type names");
|
|
test.ok(tsCode.indexOf("export const UndefinedPrefixUser: undefinedThing.$Properties;") >= 0, "preserves undefined-prefixed type names");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts ignores unknown JSDoc tags", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * Reflected tagged value.",
|
|
" * @customTag generated-by-tool",
|
|
" * @name TaggedValue",
|
|
" * @type {number}",
|
|
" * @const",
|
|
" */"
|
|
].join("\n"), [], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("export const TaggedValue: number;") >= 0, "emits tagged declaration");
|
|
test.equal(tsCode.indexOf("customTag"), -1, "does not emit unknown tag");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts preserves deprecated JSDoc tags", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * @deprecated",
|
|
" */",
|
|
"function emptyDeprecated() {}",
|
|
"",
|
|
"/**",
|
|
" * @deprecated Use textDeprecated instead.",
|
|
" */",
|
|
"function textDeprecated() {}"
|
|
].join("\n"), [], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf(" * @deprecated\n") >= 0, "keeps bare deprecated tag");
|
|
test.ok(tsCode.indexOf(" * @deprecated Use textDeprecated instead.") >= 0, "keeps deprecated tag text");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts preserves enum value comments", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * Example enum.",
|
|
" * @enum {number}",
|
|
" * @property {number} ONE=1 Custom comment.",
|
|
" * @property {number} TWO=2 TWO value",
|
|
" */",
|
|
"var Example;"
|
|
].join("\n"), [], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("/** Custom comment. */\n ONE = 1,") >= 0, "keeps custom enum value comment");
|
|
test.ok(tsCode.indexOf("/** TWO value */\n TWO = 2") >= 0, "keeps fallback enum value comment");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts emits abstract class methods", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.process([
|
|
"/**",
|
|
" * @abstract",
|
|
" * @class",
|
|
" */",
|
|
"function AbstractService() {}",
|
|
"",
|
|
"/**",
|
|
" * @abstract",
|
|
" * @function call",
|
|
" * @memberof AbstractService",
|
|
" * @instance",
|
|
" * @returns {number}",
|
|
" */",
|
|
"AbstractService.prototype.call = function() {};"
|
|
].join("\n"), [], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("abstract call(): number;") >= 0, "emits abstract instance method");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts emits qualified typedefs in namespaces", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
var file = path.join(".tmp", "pbts-qualified-typedef-" + process.pid + "-" + Date.now() + ".js");
|
|
|
|
if (!fs.existsSync(".tmp"))
|
|
fs.mkdirSync(".tmp");
|
|
|
|
fs.writeFileSync(file, [
|
|
"/** @constructor */",
|
|
"function Foo() {}",
|
|
"",
|
|
"/**",
|
|
" * Bar object.",
|
|
" * @typedef {Object} Foo.Bar",
|
|
" * @property {string} name",
|
|
" */",
|
|
"",
|
|
"/**",
|
|
" * Identifier.",
|
|
" * @typedef {string|number} Foo.Id",
|
|
" */"
|
|
].join("\n"));
|
|
|
|
pbts.main([file], function(err, tsCode) {
|
|
try {
|
|
fs.unlinkSync(file);
|
|
} catch (e) {
|
|
// best effort cleanup
|
|
}
|
|
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf("export namespace Foo") >= 0, "emits qualified typedef namespace");
|
|
test.ok(tsCode.indexOf("interface Bar") >= 0, "emits object typedef as interface");
|
|
test.ok(tsCode.indexOf("name: string;") >= 0, "emits object typedef properties");
|
|
test.ok(tsCode.indexOf("type Id = (string|number);") >= 0, "emits non-object typedef as type alias");
|
|
test.end();
|
|
});
|
|
});
|
|
|
|
tape.test("pbts emits class properties for extension fields", function(test) {
|
|
var pbts = require("../cli/pbts");
|
|
|
|
pbts.main(["tests/data/test.js"], function(err, tsCode) {
|
|
test.error(err, "definition generation worked");
|
|
test.ok(tsCode.indexOf('".jspb.test.IndirectExtension.str": string;') >= 0, "should emit scalar extension property on the class");
|
|
test.ok(tsCode.indexOf('".jspb.test.CloneExtension.extField"?: (jspb.test.CloneExtension.$Properties|null);') >= 0, "should emit message extension property on the class");
|
|
test.end();
|
|
});
|
|
});
|