From 172116ec5edfac678b9cd2a0649da13cc19521f9 Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Sun, 27 Dec 2020 15:43:39 +0100 Subject: [PATCH 01/10] Delete legacy-utils.ts --- test/legacy-utils.ts | 168 ------------------------------------------- 1 file changed, 168 deletions(-) delete mode 100644 test/legacy-utils.ts diff --git a/test/legacy-utils.ts b/test/legacy-utils.ts deleted file mode 100644 index 534c5849a..000000000 --- a/test/legacy-utils.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { lauxlib, lua, lualib, to_jsstring, to_luastring } from "fengari"; -import * as fs from "fs"; -import * as path from "path"; -import * as ts from "typescript"; -import * as tstl from "../src"; -import { formatPathToLuaPath } from "../src/utils"; - -export function transpileString( - str: string | { [filename: string]: string }, - options: tstl.CompilerOptions = {}, - ignoreDiagnostics = true -): string { - const { diagnostics, file } = transpileStringResult(str, options); - expect(file.lua).toBeDefined(); - - const errors = diagnostics.filter( - d => (!ignoreDiagnostics || d.source === "typescript-to-lua") && d.category === ts.DiagnosticCategory.Error - ); - expect(errors).not.toHaveDiagnostics(); - - return file.lua!.trim(); -} - -function transpileStringsAsProject(input: Record, options: tstl.CompilerOptions = {}) { - return tstl.transpileVirtualProject(input, { - luaTarget: tstl.LuaTarget.Lua53, - noHeader: true, - skipLibCheck: true, - target: ts.ScriptTarget.ESNext, - lib: ["lib.esnext.d.ts"], - experimentalDecorators: true, - ...options, - }); -} - -function transpileStringResult( - input: string | Record, - options: tstl.CompilerOptions = {} -): Required { - const { diagnostics, transpiledFiles } = transpileStringsAsProject( - typeof input === "string" ? { "main.ts": input } : input, - options - ); - - const file = transpiledFiles.find(({ sourceFiles }) => sourceFiles.some(f => /\bmain\.[a-z]+$/.test(f.fileName))); - if (file === undefined) { - throw new Error('Program should have a file named "main"'); - } - - return { diagnostics, file }; -} - -const lualibContent = fs.readFileSync(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), "utf8"); -const minimalTestLib = fs.readFileSync(path.join(__dirname, "json.lua"), "utf8") + "\n"; -export function executeLua(luaStr: string, withLib = true): any { - luaStr = luaStr.replace(/require\("lualib_bundle"\)/g, lualibContent); - if (withLib) { - luaStr = minimalTestLib + luaStr; - } - - const L = lauxlib.luaL_newstate(); - lualib.luaL_openlibs(L); - const status = lauxlib.luaL_dostring(L, to_luastring(luaStr)); - - if (status === lua.LUA_OK) { - // Read the return value from stack depending on its type. - if (lua.lua_isboolean(L, -1)) { - return lua.lua_toboolean(L, -1); - } else if (lua.lua_isnil(L, -1)) { - return undefined; - } else if (lua.lua_isnumber(L, -1)) { - return lua.lua_tonumber(L, -1); - } else if (lua.lua_isstring(L, -1)) { - return lua.lua_tojsstring(L, -1); - } else { - throw new Error("Unsupported lua return type: " + to_jsstring(lua.lua_typename(L, lua.lua_type(L, -1)))); - } - } else { - // If the lua VM did not terminate with status code LUA_OK an error occurred. - // Throw a JS error with the message, retrieved by reading a string from the stack. - - // Filter control characters out of string which are in there because ???? - throw new Error("LUA ERROR: " + to_jsstring(lua.lua_tostring(L, -1).filter(c => c >= 20))); - } -} - -export function transpileAndExecute( - tsStr: string, - compilerOptions?: tstl.CompilerOptions, - luaHeader?: string, - tsHeader?: string, - ignoreDiagnostics = false -): any { - const wrappedTsString = `${tsHeader ?? ""} - declare function JSONStringify(this: void, p: any): string; - function __runTest(this: void): any {${tsStr}}`; - - const lua = `${luaHeader ?? ""} - ${transpileString(wrappedTsString, compilerOptions, ignoreDiagnostics)} - return __runTest();`; - - return executeLua(lua); -} - -function getExportPath(fileName: string, options: ts.CompilerOptions): string { - const rootDir = options.rootDir ? path.resolve(options.rootDir) : path.resolve("."); - - const absolutePath = path.resolve(fileName.replace(/.ts$/, "")); - const absoluteRootDirPath = path.format(path.parse(rootDir)); - return formatPathToLuaPath(absolutePath.replace(absoluteRootDirPath, "").slice(1)); -} - -export function transpileAndExecuteProjectReturningMainExport( - typeScriptFiles: Record, - exportName: string, - options: tstl.CompilerOptions = {} -): [any, string] { - const mainFile = Object.keys(typeScriptFiles).find(typeScriptFileName => typeScriptFileName === "main.ts"); - if (!mainFile) { - throw new Error("An entry point file needs to be specified. This should be called main.ts"); - } - - const joinedTranspiledFiles = Object.keys(typeScriptFiles) - .filter(typeScriptFileName => typeScriptFileName !== "main.ts") - .map(typeScriptFileName => { - const modulePath = getExportPath(typeScriptFileName, options); - const tsCode = typeScriptFiles[typeScriptFileName]; - const luaCode = transpileString(tsCode, options); - return `package.preload["${modulePath}"] = function() - ${luaCode} - end`; - }) - .join("\n"); - - const luaCode = `return (function() - ${joinedTranspiledFiles} - ${transpileString(typeScriptFiles[mainFile])} - end)().${exportName}`; - - try { - return [executeLua(luaCode), luaCode]; - } catch (err) { - throw new Error(` - Encountered an error when executing the following Lua code: - - ${luaCode} - - ${err} - `); - } -} - -export function transpileExecuteAndReturnExport( - tsStr: string, - returnExport: string, - compilerOptions?: tstl.CompilerOptions, - luaHeader?: string -): any { - const wrappedTsString = `declare function JSONStringify(this: void, p: any): string; - ${tsStr}`; - - const lua = `return (function() - ${luaHeader ?? ""} - ${transpileString(wrappedTsString, compilerOptions, false)} - end)().${returnExport}`; - - return executeLua(lua); -} From 6adaa69a177f8f19091c7216ccf3eec77496b571 Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Sun, 27 Dec 2020 15:46:43 +0100 Subject: [PATCH 02/10] Updated some test suites to use new testbuilder --- .../__snapshots__/identifiers.spec.ts.snap | 8 + test/unit/builtins/array.spec.ts | 38 +- test/unit/builtins/string.spec.ts | 19 +- .../invalidFunctionAssignments.spec.ts.snap | 10 +- .../functionExpressionTypeInference.spec.ts | 77 ++-- .../validation/functionPermutations.ts | 2 +- .../validFunctionAssignments.spec.ts | 196 ++++++----- test/unit/identifiers.spec.ts | 331 ++++++++---------- test/unit/loops.spec.ts | 66 ++-- test/unit/printer/parenthesis.spec.ts | 13 +- test/util.ts | 6 +- 11 files changed, 353 insertions(+), 413 deletions(-) diff --git a/test/unit/__snapshots__/identifiers.spec.ts.snap b/test/unit/__snapshots__/identifiers.spec.ts.snap index 6ab8a4433..d955068e5 100644 --- a/test/unit/__snapshots__/identifiers.spec.ts.snap +++ b/test/unit/__snapshots__/identifiers.spec.ts.snap @@ -128,6 +128,14 @@ exports[`ambient identifier must be a valid lua identifier (object literal short exports[`ambient identifier must be a valid lua identifier (object literal shorthand) ("ɥɣɎɌͼƛಠ"): diagnostics 1`] = `"main.ts(3,27): error TSTL: Invalid ambient identifier name 'ɥɣɎɌͼƛಠ'. Ambient identifiers must be valid lua identifiers."`; +exports[`declaration-only variable with lua keyword as name is not renamed 1`] = ` +"local ____exports = {} +function ____exports.__main(self) + type(7) +end +return ____exports" +`; + exports[`undeclared identifier must be a valid lua identifier ("$$"): code 1`] = `"foo = _____24_24_24"`; exports[`undeclared identifier must be a valid lua identifier ("$$"): diagnostics 1`] = `"main.ts(2,21): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`; diff --git a/test/unit/builtins/array.spec.ts b/test/unit/builtins/array.spec.ts index ecaaaca53..a34272829 100644 --- a/test/unit/builtins/array.spec.ts +++ b/test/unit/builtins/array.spec.ts @@ -594,27 +594,25 @@ const genericChecks = [ ]; test.each(genericChecks)("array constrained generic foreach (%p)", signature => { - const code = ` - ${signature}: number { - let sum = 0; - array.forEach(item => { - if (typeof item === "number") { - sum += item; - } - }); - return sum; - } - return generic([1, 2, 3]); - `; - expect(util.transpileAndExecute(code)).toBe(6); + util.testFunction` + ${signature}: number { + let sum = 0; + array.forEach(item => { + if (typeof item === "number") { + sum += item; + } + }); + return sum; + } + return generic([1, 2, 3]); + `.expectToMatchJsResult(); }); test.each(genericChecks)("array constrained generic length (%p)", signature => { - const code = ` - ${signature}: number { - return array.length; - } - return generic([1, 2, 3]); - `; - expect(util.transpileAndExecute(code)).toBe(3); + util.testFunction` + ${signature}: number { + return array.length; + } + return generic([1, 2, 3]); + `.expectToMatchJsResult(); }); diff --git a/test/unit/builtins/string.spec.ts b/test/unit/builtins/string.spec.ts index 00f5c878d..6f588b7e8 100644 --- a/test/unit/builtins/string.spec.ts +++ b/test/unit/builtins/string.spec.ts @@ -164,9 +164,9 @@ test.each([ { inp: "hello test", start: 3, ignored: 0, end: 2 }, ])("string.substr with expression (%p)", ({ inp, start, ignored, end }) => { const paramStr = `2 > 1 && ${start} || ${ignored}` + (end ? `, ${end}` : ""); - const result = util.transpileAndExecute(`return "${inp}".substr(${paramStr})`); - - expect(result).toBe(inp.substr(start, end)); + util.testFunction` + return "${inp}".substr(${paramStr}); + `.expectToMatchJsResult(); }); test.each(["", "h", "hello"])("string.length (%p)", input => { @@ -282,13 +282,12 @@ test.each([ "function generic(string: T)", "type StringType = string; function generic(string: T)", ])("string constrained generic foreach (%p)", signature => { - const code = ` - ${signature}: number { - return string.length; - } - return generic("string"); - `; - expect(util.transpileAndExecute(code)).toBe(6); + util.testFunction` + ${signature}: number { + return string.length; + } + return generic("string"); + `.expectToMatchJsResult(); }); const trimTestCases = [ diff --git a/test/unit/functions/validation/__snapshots__/invalidFunctionAssignments.spec.ts.snap b/test/unit/functions/validation/__snapshots__/invalidFunctionAssignments.spec.ts.snap index 55e61f9ae..7ed8e7560 100644 --- a/test/unit/functions/validation/__snapshots__/invalidFunctionAssignments.spec.ts.snap +++ b/test/unit/functions/validation/__snapshots__/invalidFunctionAssignments.spec.ts.snap @@ -247,7 +247,7 @@ exports[`Invalid function argument ({"definition": "interface FuncPropInterface const funcPropInterface: FuncPropInterface = { funcProp: function(this: any, s: string) { return s; } };", "value": "funcPropInterface.funcProp"}): diagnostics 1`] = `"main.ts(5,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; exports[`Invalid function argument ({"definition": "interface MethodInterface { method(this: any, s: string): string; } - const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } }", "value": "methodInterface.method"}): diagnostics 1`] = `"main.ts(5,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; + const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } };", "value": "methodInterface.method"}): diagnostics 1`] = `"main.ts(5,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; exports[`Invalid function argument ({"definition": "interface NoSelfMethodInterface { /** @noSelf */ @@ -639,7 +639,7 @@ exports[`Invalid function assignment ({"definition": "interface FuncPropInterfac const funcPropInterface: FuncPropInterface = { funcProp: function(this: any, s: string) { return s; } };", "value": "funcPropInterface.funcProp"}): diagnostics 1`] = `"main.ts(5,18): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; exports[`Invalid function assignment ({"definition": "interface MethodInterface { method(this: any, s: string): string; } - const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } }", "value": "methodInterface.method"}): diagnostics 1`] = `"main.ts(5,18): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; + const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } };", "value": "methodInterface.method"}): diagnostics 1`] = `"main.ts(5,18): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; exports[`Invalid function assignment ({"definition": "interface NoSelfMethodInterface { /** @noSelf */ @@ -1031,7 +1031,7 @@ exports[`Invalid function generic argument ({"definition": "interface FuncPropIn const funcPropInterface: FuncPropInterface = { funcProp: function(this: any, s: string) { return s; } };", "value": "funcPropInterface.funcProp"}): diagnostics 1`] = `"main.ts(5,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; exports[`Invalid function generic argument ({"definition": "interface MethodInterface { method(this: any, s: string): string; } - const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } }", "value": "methodInterface.method"}): diagnostics 1`] = `"main.ts(5,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; + const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } };", "value": "methodInterface.method"}): diagnostics 1`] = `"main.ts(5,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; exports[`Invalid function generic argument ({"definition": "interface NoSelfMethodInterface { /** @noSelf */ @@ -1391,7 +1391,7 @@ exports[`Invalid function return ({"definition": "interface FuncPropInterface { const funcPropInterface: FuncPropInterface = { funcProp: function(this: any, s: string) { return s; } };", "value": "funcPropInterface.funcProp"}): diagnostics 1`] = `"main.ts(5,17): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; exports[`Invalid function return ({"definition": "interface MethodInterface { method(this: any, s: string): string; } - const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } }", "value": "methodInterface.method"}): diagnostics 1`] = `"main.ts(5,17): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; + const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } };", "value": "methodInterface.method"}): diagnostics 1`] = `"main.ts(5,17): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; exports[`Invalid function return ({"definition": "interface NoSelfMethodInterface { /** @noSelf */ @@ -1791,7 +1791,7 @@ exports[`Invalid function variable declaration ({"definition": "interface FuncPr const funcPropInterface: FuncPropInterface = { funcProp: function(this: any, s: string) { return s; } };", "value": "funcPropInterface.funcProp"}): diagnostics 1`] = `"main.ts(4,59): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; exports[`Invalid function variable declaration ({"definition": "interface MethodInterface { method(this: any, s: string): string; } - const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } }", "value": "methodInterface.method"}): diagnostics 1`] = `"main.ts(4,59): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; + const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } };", "value": "methodInterface.method"}): diagnostics 1`] = `"main.ts(4,59): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; exports[`Invalid function variable declaration ({"definition": "interface NoSelfMethodInterface { /** @noSelf */ diff --git a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts index e475f2216..65618201e 100644 --- a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts +++ b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts @@ -13,7 +13,7 @@ test.each(["noSelf", "noSelfInFile"])("noSelf function method argument (%p)", no const c = new NS.C(); return c.method(foo); `; - expect(util.transpileAndExecute(code, undefined, undefined, header)).toBe("foo"); + util.testFunction(code).setTsHeader(header).expectToMatchJsResult(); }); test("noSelfInFile works when first statement has other annotations", () => { @@ -32,18 +32,18 @@ test.each(["(this: void, s: string) => string", "(this: any, s: string) => strin funcType => { const header = `declare const undefinedFunc: ${funcType};`; const code = ` - let func: ${funcType} = s => s; - func = undefinedFunc || (s => s); - return func("foo"); - `; - expect(util.transpileAndExecute(code, undefined, undefined, header)).toBe("foo"); + let func: ${funcType} = s => s; + func = undefinedFunc || (s => s); + return func("foo"); + `; + util.testFunction(code).setTsHeader(header).expectToMatchJsResult(); } ); test.each(["s => s", "(s => s)", "function(s) { return s; }", "(function(s) { return s; })"])( "Function expression type inference in class (%p)", funcExp => { - const code = ` + util.testFunction` class Foo { func: (this: void, s: string) => string = ${funcExp}; method: (s: string) => string = ${funcExp}; @@ -52,8 +52,7 @@ test.each(["s => s", "(s => s)", "function(s) { return s; }", "(function(s) { re } const foo = new Foo(); return foo.func("a") + foo.method("b") + Foo.staticFunc("c") + Foo.staticMethod("d"); - `; - expect(util.transpileAndExecute(code)).toBe("abcd"); + `.expectToMatchJsResult(); } ); @@ -67,23 +66,21 @@ test.each([ { assignTo: "let foo: Foo; foo", funcExp: "function(s) { return s; }" }, { assignTo: "let foo: Foo; foo", funcExp: "(function(s) { return s; })" }, ])("Function expression type inference in object literal (%p)", ({ assignTo, funcExp }) => { - const code = ` + util.testFunction` interface Foo { func(this: void, s: string): string; method(this: this, s: string): string; } ${assignTo} = {func: ${funcExp}, method: ${funcExp}}; return foo.method("foo") + foo.func("bar"); - `; - expect(util.transpileAndExecute(code)).toBe("foobar"); + `.expectToMatchJsResult(); }); test("Function expression type inference in object literal assigned to narrower type", () => { - const code = ` + util.testFunction` let foo: {} = {bar: s => s}; return (foo as {bar: (a: any) => any}).bar("foobar"); - `; - expect(util.transpileAndExecute(code)).toBe("foobar"); + `.expectToMatchJsResult(); }); test.each([ @@ -96,14 +93,13 @@ test.each([ { assignTo: "let foo: Foo; foo", funcExp: "function(s) { return s; }" }, { assignTo: "let foo: Foo; foo", funcExp: "(function(s) { return s; })" }, ])("Function expression type inference in object literal (generic key) (%p)", ({ assignTo, funcExp }) => { - const code = ` - interface Foo { - [f: string]: (this: void, s: string) => string; - } - ${assignTo} = {func: ${funcExp}}; - return foo.func("foo"); - `; - expect(util.transpileAndExecute(code)).toBe("foo"); + util.testFunction` + interface Foo { + [f: string]: (this: void, s: string) => string; + } + ${assignTo} = {func: ${funcExp}}; + return foo.func("foo"); + `.expectToMatchJsResult(); }); test.each([ @@ -204,7 +200,7 @@ test.each([ funcExp: "(function(s) { return s; })", }, ])("Function expression type inference in tuple (%p)", ({ assignTo, func, method, funcExp }) => { - const code = ` + util.testFunction` interface Foo { method(s: string): string; } @@ -217,8 +213,7 @@ test.each([ ${assignTo} = [${funcExp}, ${funcExp}]; const foo: Foo = {method: ${method}}; return foo.method("foo") + ${func}("bar"); - `; - expect(util.transpileAndExecute(code)).toBe("foobar"); + `.expectToMatchJsResult(); }); test.each([ @@ -239,7 +234,7 @@ test.each([ { assignTo: "let meth: Method; [meth]", method: "meth", funcExp: "function(s) { return s; }" }, { assignTo: "let meth: Method; [meth]", method: "meth", funcExp: "(function(s) { return s; })" }, ])("Function expression type inference in array (%p)", ({ assignTo, method, funcExp }) => { - const code = ` + util.testFunction` interface Foo { method(s: string): string; } @@ -249,8 +244,7 @@ test.each([ ${assignTo} = [${funcExp}]; const foo: Foo = {method: ${method}}; return foo.method("foo"); - `; - expect(util.transpileAndExecute(code)).toBe("foo"); + `.expectToMatchJsResult(); }); test.each([ @@ -261,12 +255,11 @@ test.each([ { funcType: "(this: any, s: string) => string", funcExp: "function(s) { return s; }" }, { funcType: "(s: string) => string", funcExp: "function(s) { return s; }" }, ])("Function expression type inference in union (%p)", ({ funcType, funcExp }) => { - const code = ` + util.testFunction` type U = string | number | (${funcType}); const u: U = ${funcExp}; return (u as ${funcType})("foo"); - `; - expect(util.transpileAndExecute(code)).toBe("foo"); + `.expectToMatchJsResult(); }); test.each([ @@ -277,12 +270,11 @@ test.each([ { funcType: "(this: any, s: string) => string", funcExp: "function(s) { return s; }" }, { funcType: "(s: string) => string", funcExp: "function(s) { return s; }" }, ])("Function expression type inference in union tuple (%p)", ({ funcType, funcExp }) => { - const code = ` + util.testFunction` interface I { callback: ${funcType}; } let a: I[] | number = [{ callback: ${funcExp} }]; return a[0].callback("foo"); - `; - expect(util.transpileAndExecute(code)).toBe("foo"); + `.expectToMatchJsResult(); }); test.each([ @@ -293,11 +285,10 @@ test.each([ { funcType: "(this: any, s: string) => string", funcExp: "function(s) { return s; }" }, { funcType: "(s: string) => string", funcExp: "function(s) { return s; }" }, ])("Function expression type inference in as cast (%p)", ({ funcType, funcExp }) => { - const code = ` + util.testFunction` const fn: ${funcType} = (${funcExp}) as (${funcType}); return fn("foo"); - `; - expect(util.transpileAndExecute(code)).toBe("foo"); + `.expectToMatchJsResult(); }); test.each([ @@ -308,11 +299,10 @@ test.each([ { funcType: "(this: any, s: string) => string", funcExp: "function(s) { return s; }" }, { funcType: "(s: string) => string", funcExp: "function(s) { return s; }" }, ])("Function expression type inference in type assertion (%p)", ({ funcType, funcExp }) => { - const code = ` + util.testFunction` const fn: ${funcType} = <${funcType}>(${funcExp}); return fn("foo"); - `; - expect(util.transpileAndExecute(code)).toBe("foo"); + `.expectToMatchJsResult(); }); test.each([ @@ -323,13 +313,12 @@ test.each([ { funcType: "(this: any, s: string) => string", funcExp: "function(s) { return s; }" }, { funcType: "(s: string) => string", funcExp: "function(s) { return s; }" }, ])("Function expression type inference in constructor (%p)", ({ funcType, funcExp }) => { - const code = ` + util.testFunction` class C { result: string; constructor(fn: ${funcType}) { this.result = fn("foo"); } } const c = new C(${funcExp}); return c.result; - `; - expect(util.transpileAndExecute(code)).toBe("foo"); + `.expectToMatchJsResult(); }); diff --git a/test/unit/functions/validation/functionPermutations.ts b/test/unit/functions/validation/functionPermutations.ts index ebf00dfae..f95af8ab3 100644 --- a/test/unit/functions/validation/functionPermutations.ts +++ b/test/unit/functions/validation/functionPermutations.ts @@ -87,7 +87,7 @@ export const selfTestFunctions: TestFunction[] = [ { value: "methodInterface.method", definition: `interface MethodInterface { method(this: any, s: string): string; } - const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } }`, + const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } };`, }, { value: "anonMethodInterface.anonMethod", diff --git a/test/unit/functions/validation/validFunctionAssignments.spec.ts b/test/unit/functions/validation/validFunctionAssignments.spec.ts index a491ccd7b..7cc79daf6 100644 --- a/test/unit/functions/validation/validFunctionAssignments.spec.ts +++ b/test/unit/functions/validation/validFunctionAssignments.spec.ts @@ -15,54 +15,64 @@ import { } from "./functionPermutations"; test.each(validTestFunctionAssignments)("Valid function variable declaration (%p)", (testFunction, functionType) => { - const code = `const fn: ${functionType} = ${testFunction.value}; - return fn("foobar");`; - expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + util.testFunction` + const fn: ${functionType} = ${testFunction.value}; + return fn("foobar"); + ` + .setTsHeader(testFunction.definition) + .expectToMatchJsResult(); }); test.each(validTestFunctionAssignments)("Valid function assignment (%p)", (testFunction, functionType) => { - const code = `let fn: ${functionType}; - fn = ${testFunction.value}; - return fn("foobar");`; - expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + util.testFunction` + let fn: ${functionType}; + fn = ${testFunction.value}; + return fn("foobar"); + ` + .setTsHeader(testFunction.definition) + .expectToMatchJsResult(); }); test.each(validTestFunctionCasts)("Valid function assignment with cast (%p)", (testFunction, castedFunction) => { - const code = ` - let fn: typeof ${testFunction.value}; - fn = ${castedFunction}; - return fn("foobar"); - `; - expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + util.testFunction` + let fn: typeof ${testFunction.value}; + fn = ${castedFunction}; + return fn("foobar"); + ` + .setTsHeader(testFunction.definition) + .expectToMatchJsResult(); }); test.each(validTestFunctionAssignments)("Valid function argument (%p)", (testFunction, functionType) => { - const code = ` - function takesFunction(fn: ${functionType}) { - return fn("foobar"); - } - return takesFunction(${testFunction.value}); - `; - expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + util.testFunction` + function takesFunction(fn: ${functionType}) { + return fn("foobar"); + } + return takesFunction(${testFunction.value}); + ` + .setTsHeader(testFunction.definition) + .expectToMatchJsResult(); }); test("Valid lua lib function argument", () => { - const code = `let result = ""; + util.testFunction` + let result = ""; function foo(this: any, value: string) { result += value; } const a = ['foo', 'bar']; a.forEach(foo); - return result;`; - expect(util.transpileAndExecute(code)).toBe("foobar"); + return result; + `.expectToMatchJsResult(); }); test.each(validTestFunctionCasts)("Valid function argument with cast (%p)", (testFunction, castedFunction) => { - const code = ` - function takesFunction(fn: typeof ${testFunction.value}) { - return fn("foobar"); - } - return takesFunction(${castedFunction}); - `; - expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + util.testFunction` + function takesFunction(fn: typeof ${testFunction.value}) { + return fn("foobar"); + } + return takesFunction(${castedFunction}); + ` + .setTsHeader(testFunction.definition) + .expectToMatchJsResult(); }); test.each([ @@ -77,60 +87,66 @@ test.each([ ...selfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, selfTestFunctionType]), ...noSelfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType]), ])("Valid function generic argument (%p)", (testFunction, functionType) => { - const code = ` + util.testFunction` function takesFunction(fn: T) { return fn("foobar"); } return takesFunction(${testFunction.value}); - `; - expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + ` + .setTsHeader(testFunction.definition) + .expectToMatchJsResult(); }); test.each([ ...anonTestFunctionExpressions.map((f): [TestFunction, string[]] => [f, ["0", "'foobar'"]]), ...selfTestFunctionExpressions.map((f): [TestFunction, string[]] => [f, ["0", "'foobar'"]]), ...noSelfTestFunctionExpressions.map((f): [TestFunction, string[]] => [f, ["'foobar'"]]), -])("Valid function expression argument with no signature (%p)", (testFunction, args) => { - const code = ` +])("Valid function expression argument with no signature (%p, %p)", (testFunction, args) => { + util.testFunction` const takesFunction: any = (fn: (this: void, ...args: any[]) => any, ...args: any[]) => { return fn(...args); } return takesFunction(${testFunction.value}, ${args.join(", ")}); - `; - expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + ` + .setTsHeader(testFunction.definition) + .expectToMatchJsResult(); }); test.each(validTestFunctionAssignments)("Valid function return (%p)", (testFunction, functionType) => { - const code = ` - function returnsFunction(): ${functionType} { - return ${testFunction.value}; - } - const fn = returnsFunction(); - return fn("foobar"); - `; - expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + util.testFunction` + function returnsFunction(): ${functionType} { + return ${testFunction.value}; + } + const fn = returnsFunction(); + return fn("foobar"); + ` + .setTsHeader(testFunction.definition) + .expectToMatchJsResult(); }); test.each(validTestFunctionCasts)("Valid function return with cast (%p)", (testFunction, castedFunction) => { - const code = `function returnsFunction(): typeof ${testFunction.value} { - return ${castedFunction}; - } - const fn = returnsFunction(); - return fn("foobar");`; - expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + util.testFunction` + function returnsFunction(): typeof ${testFunction.value} { + return ${castedFunction}; + } + const fn = returnsFunction(); + return fn("foobar"); + ` + .setTsHeader(testFunction.definition) + .expectToMatchJsResult(); }); test("Valid function tuple assignment", () => { - const code = `interface Func { (this: void, s: string): string; } - function getTuple(): [number, Func] { return [1, s => s]; } - let [i, f]: [number, Func] = getTuple(); - return f("foo");`; - const result = util.transpileAndExecute(code); - expect(result).toBe("foo"); + util.testFunction` + interface Func { (this: void, s: string): string; } + function getTuple(): [number, Func] { return [1, s => s]; } + let [i, f]: [number, Func] = getTuple(); + return f("foo"); + `.expectToMatchJsResult(); }); test("Interface method assignment", () => { - const code = ` + util.testFunction` class Foo { method(s: string): string { return s + "+method"; } lambdaProp: (s: string) => string = s => s + "+lambdaProp"; @@ -141,46 +157,44 @@ test("Interface method assignment", () => { } const foo: IFoo = new Foo(); return foo.method("foo") + "|" + foo.lambdaProp("bar"); - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("foo+method|bar+lambdaProp"); + `.expectToMatchJsResult(); }); test("Valid interface method assignment", () => { - const code = `interface A { fn(this: void, s: string): string; } - interface B { fn(this: void, s: string): string; } - const a: A = { fn(this: void, s) { return s; } }; - const b: B = a; - return b.fn("foo");`; - const result = util.transpileAndExecute(code); - expect(result).toBe("foo"); + util.testFunction` + interface A { fn(this: void, s: string): string; } + interface B { fn(this: void, s: string): string; } + const a: A = { fn(this: void, s) { return s; } }; + const b: B = a; + return b.fn("foo"); + `.expectToMatchJsResult(); }); test("Valid method tuple assignment", () => { - const code = `interface Foo { method(s: string): string; } - interface Meth { (this: Foo, s: string): string; } - let meth: Meth = s => s; - function getTuple(): [number, Meth] { return [1, meth]; } - let [i, f]: [number, Meth] = getTuple(); - let foo: Foo = {method: f}; - return foo.method("foo");`; - const result = util.transpileAndExecute(code); - expect(result).toBe("foo"); + util.testFunction` + interface Foo { method(s: string): string; } + interface Meth { (this: Foo, s: string): string; } + let meth: Meth = s => s; + function getTuple(): [number, Meth] { return [1, meth]; } + let [i, f]: [number, Meth] = getTuple(); + let foo: Foo = {method: f}; + return foo.method("foo"); + `.expectToMatchJsResult(); }); test.each([ - { assignType: "(this: any, s: string) => string", args: ["foo"], expectResult: "foobar" }, - { assignType: "{(this: any, s: string): string}", args: ["foo"], expectResult: "foobar" }, - { assignType: "(this: any, s1: string, s2: string) => string", args: ["foo", "baz"], expectResult: "foobaz" }, - { assignType: "{(this: any, s1: string, s2: string): string}", args: ["foo", "baz"], expectResult: "foobaz" }, -])("Valid function overload assignment (%p)", ({ assignType, args, expectResult }) => { - const code = `interface O { - (s1: string, s2: string): string; - (s: string): string; - } - const o: O = (s1: string, s2?: string) => s1 + (s2 || "bar"); - let f: ${assignType} = o; - return f(${args.map(a => '"' + a + '"').join(", ")});`; - const result = util.transpileAndExecute(code); - expect(result).toBe(expectResult); + { assignType: "(this: any, s: string) => string", args: ["foo"] }, + { assignType: "{(this: any, s: string): string}", args: ["foo"] }, + { assignType: "(this: any, s1: string, s2: string) => string", args: ["foo", "baz"] }, + { assignType: "{(this: any, s1: string, s2: string): string}", args: ["foo", "baz"] }, +])("Valid function overload assignment (%p)", ({ assignType, args }) => { + util.testFunction` + interface O { + (s1: string, s2: string): string; + (s: string): string; + } + const o: O = (s1: string, s2?: string) => s1 + (s2 || "bar"); + let f: ${assignType} = o; + return f(${args.map(a => '"' + a + '"').join(", ")}); + `.expectToMatchJsResult(); }); diff --git a/test/unit/identifiers.spec.ts b/test/unit/identifiers.spec.ts index ba2bcd8a0..798763cb2 100644 --- a/test/unit/identifiers.spec.ts +++ b/test/unit/identifiers.spec.ts @@ -139,13 +139,13 @@ test.each(validTsInvalidLuaNames)("exported values with invalid lua identifier n }); test("exported identifiers referenced in namespace (%p)", () => { - const code = ` + util.testModule` export const foo = "foobar"; namespace NS { export const bar = foo; } - export const baz = NS.bar;`; - expect(util.transpileExecuteAndReturnExport(code, "baz")).toBe("foobar"); + export const baz = NS.bar; + `.expectToMatchJsResult(); }); test("exported namespace identifiers referenced in different namespace (%p)", () => { @@ -157,31 +157,31 @@ test("exported namespace identifiers referenced in different namespace (%p)", () } export const baz = B.bar; }`; - expect(util.transpileAndExecute("return A.baz", undefined, undefined, tsHeader)).toBe("foobar"); + util.testModule("return A.baz").setTsHeader(tsHeader).expectToMatchJsResult(); }); test("exported identifiers referenced in nested scope (%p)", () => { - const code = ` + util.testModule` export const foo = "foobar"; namespace A { export namespace B { export const bar = foo; } } - export const baz = A.B.bar;`; - expect(util.transpileExecuteAndReturnExport(code, "baz")).toBe("foobar"); + export const baz = A.B.bar; + `.expectToMatchJsResult(); }); test.each(validTsInvalidLuaNames)( "exported values with invalid lua identifier names referenced in different scope (%p)", name => { - const code = ` - export const ${name} = "foobar"; - namespace NS { - export const foo = ${name}; - } - export const bar = NS.foo;`; - expect(util.transpileExecuteAndReturnExport(code, "bar")).toBe("foobar"); + util.testModule` + export const ${name} = "foobar"; + namespace NS { + export const foo = ${name}; + } + export const bar = NS.foo; + `.expectToMatchJsResult(); } ); @@ -223,103 +223,92 @@ test.each(validTsInvalidLuaNames)("exported decorated class with invalid lua nam describe("lua keyword as identifier doesn't interfere with lua's value", () => { test("variable (nil)", () => { - const code = ` + util.testFunction` const nil = "foobar"; - return \`\${undefined}|\${nil}\``; - - expect(util.transpileAndExecute(code)).toBe("nil|foobar"); + return \`\${undefined}|\${nil}\` + `.expectToMatchJsResult(); }); test("variable (and)", () => { - const code = ` + util.testFunction` const and = "foobar"; - return true && and;`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + return true && and; + `.expectToMatchJsResult(); }); test("variable (elseif)", () => { - const code = ` + util.testFunction` const elseif = "foobar"; if (false) { } else if (elseif) { return elseif; - }`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + } + `.expectToMatchJsResult(); }); test("variable (end)", () => { - const code = ` + util.testFunction` const end = "foobar"; { return end; - }`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + } + `.expectToMatchJsResult(); }); test("variable (local)", () => { - const code = ` + util.testFunction` const local = "foobar"; - return local;`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + return local; + `.expectToMatchJsResult(); }); test("variable (not)", () => { - const code = ` + util.testFunction` const not = "foobar"; - return (!false) && not;`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + return (!false) && not; + `.expectToMatchJsResult(); }); test("variable (or)", () => { - const code = ` + util.testFunction` const or = "foobar"; - return false || or;`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + return false || or; + `.expectToMatchJsResult(); }); test("variable (repeat)", () => { - const code = ` + util.testFunction` const repeat = "foobar"; do {} while (false); - return repeat;`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + return repeat; + `.expectToMatchJsResult(); }); test("variable (then)", () => { - const code = ` + util.testFunction` const then = "foobar"; if (then) { return then; - }`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + } + `.expectToMatchJsResult(); }); test("variable (until)", () => { - const code = ` + util.testFunction` const until = "foobar"; do {} while (false); - return until;`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + return until; + `.expectToMatchJsResult(); }); test("variable (goto)", () => { - const code = ` + util.testFunction` const goto = "foobar"; switch (goto) { case goto: return goto; - }`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + } + `.expectToMatchJsResult(); }); test("variable (print)", () => { @@ -343,21 +332,21 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test("variable (type)", () => { - const code = ` + util.testFunction` function type(this: void, a: unknown) { return (typeof a) + "|foobar"; } - return type(7);`; - - expect(util.transpileAndExecute(code)).toBe("number|foobar"); + return type(7); + `.expectToMatchJsResult(); }); test("variable (error)", () => { - const code = ` + const executionResult = util.testFunction` const error = "foobar"; - throw error;`; + throw error; + `.getLuaExecutionResult(); - expect(() => util.transpileAndExecute(code)).toThrow(/^LUA ERROR: foobar$/); + expect(executionResult).toMatch(/^LUA ERROR: foobar$/); }); test("variable (assert)", () => { @@ -393,65 +382,58 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test("variable (string)", () => { - const code = ` + util.testFunction` const string = "foobar"; - return string[0];`; - - expect(util.transpileAndExecute(code)).toBe("f"); + return string[0]; + `.expectToMatchJsResult(); }); test("variable (math)", () => { - const code = ` + util.testFunction` const math = -17; - return Math.abs(math);`; - - expect(util.transpileAndExecute(code)).toBe(17); + return Math.abs(math); + `.expectToMatchJsResult(); }); test("variable (table)", () => { - const code = ` + util.testFunction` const table = ["foobar"]; - return table.pop();`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + return table.pop(); + `.expectToMatchJsResult(); }); test("variable (coroutine)", () => { - const code = ` + util.testFunction` const coroutine = "foobar"; function *foo() { yield coroutine; } - return foo().next().value;`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + return foo().next().value; + `.expectToMatchJsResult(); }); test("variable (pairs)", () => { - const code = ` + util.testFunction` const pairs = {foobar: "foobar"}; let result = ""; for (const key in pairs) { result += key; } - return result;`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + return result; + `.expectToMatchJsResult(); }); test("variable (pcall)", () => { - const code = ` + util.testFunction` const pcall = "foobar"; try {} finally {} - return pcall;`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + return pcall; + `.expectToMatchJsResult(); }); test("variable (rawget)", () => { - const code = ` + util.testFunction` const rawget = {foobar: "foobar"}; - return rawget.hasOwnProperty("foobar");`; - - expect(util.transpileAndExecute(code)).toBe(true); + return rawget.hasOwnProperty("foobar"); + `.expectToMatchJsResult(); }); test("variable (require)", () => { @@ -470,11 +452,10 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test("variable (tostring)", () => { - const code = ` + util.testFunction` const tostring = 17; - return tostring.toString();`; - - expect(util.transpileAndExecute(code)).toBe(17); + return tostring.toString(); + `.expectToMatchJsResult(); }); test("variable (unpack)", () => { @@ -491,88 +472,78 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test("variable (_G)", () => { - const code = ` + util.testFunction` const _G = "bar"; (globalThis as any).foo = "foo"; return (globalThis as any).foo + _G; - `; - - expect(util.transpileAndExecute(code)).toBe("foobar"); + `.expectToMatchJsResult(); }); test("function parameter", () => { - const code = ` + util.testFunction` function foo(type: unknown) { return \`\${typeof type}|\${type}\`; } - return foo("foobar");`; - - expect(util.transpileAndExecute(code)).toBe("string|foobar"); + return foo("foobar"); + `.expectToMatchJsResult(); }); test("destructured property function parameter", () => { - const code = ` + util.testFunction` function foo({type}: any) { return \`\${typeof type}|\${type}\`; } - return foo({type: "foobar"});`; - - expect(util.transpileAndExecute(code)).toBe("string|foobar"); + return foo({type: "foobar"}); + `.expectToMatchJsResult(); }); test("destructured array element function parameter", () => { - const code = ` + util.testFunction` function foo([type]: any) { return \`\${typeof type}|\${type}\`; } - return foo(["foobar"]);`; - - expect(util.transpileAndExecute(code)).toBe("string|foobar"); + return foo(["foobar"]); + `.expectToMatchJsResult(); }); test("property", () => { - const code = ` + util.testFunction` const type = "foobar"; const foo = { type: type }; - return type + "|" + foo.type + "|" + typeof type;`; - - expect(util.transpileAndExecute(code)).toBe("foobar|foobar|string"); + return type + "|" + foo.type + "|" + typeof type; + `.expectToMatchJsResult(); }); test("shorthand property", () => { - const code = ` + util.testFunction` const type = "foobar"; const foo = { type }; - return type + "|" + foo.type + "|" + typeof type;`; - - expect(util.transpileAndExecute(code)).toBe("foobar|foobar|string"); + return type + "|" + foo.type + "|" + typeof type; + `.expectToMatchJsResult(); }); test("destructured property", () => { - const code = ` + util.testFunction` const foo = { type: "foobar" }; const { type: type } = foo; - return type + "|" + foo.type + "|" + typeof type;`; - - expect(util.transpileAndExecute(code)).toBe("foobar|foobar|string"); + return type + "|" + foo.type + "|" + typeof type; + `.expectToMatchJsResult(); }); test("destructured shorthand property", () => { - const code = ` + util.testFunction` const foo = { type: "foobar" }; const { type } = foo; - return type + "|" + foo.type + "|" + typeof type;`; - - expect(util.transpileAndExecute(code)).toBe("foobar|foobar|string"); + return type + "|" + foo.type + "|" + typeof type; + `.expectToMatchJsResult(); }); test("destructured array element", () => { - const code = ` + util.testFunction` const foo = ["foobar"]; const [type] = foo; - return type + "|" + typeof type;`; - - expect(util.transpileAndExecute(code)).toBe("foobar|string"); + return type + "|" + typeof type; + `.expectToMatchJsResult(); }); test.each(["type", "type as type"])("imported variable (%p)", importName => { @@ -590,18 +561,13 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { expect(result).toBe("number|foobar"); }); - test.each([ - { returnExport: "type", expectResult: "foobar" }, - { returnExport: "mytype", expectResult: "foobar" }, - { returnExport: "result", expectResult: "string|foobar" }, - ])("separately exported variable (%p)", ({ returnExport, expectResult }) => { - const code = ` + test("separately exported variable (%p)", () => { + util.testModule` const type = "foobar"; export { type } export { type as mytype } - export const result = typeof type + "|" + type;`; - - expect(util.transpileExecuteAndReturnExport(code, returnExport)).toBe(expectResult); + export const result = typeof type + "|" + type; + `.expectToMatchJsResult(); }); test.each(["type", "type as type"])("re-exported variable with lua keyword as name (%p)", importName => { @@ -618,59 +584,49 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test("class", () => { - const code = ` + util.testFunction` class type { method() { return typeof 0; } static staticMethod() { return typeof "foo"; } } const t = new type(); - return t.method() + "|" + type.staticMethod();`; - - expect(util.transpileAndExecute(code)).toBe("number|string"); + return t.method() + "|" + type.staticMethod(); + `.expectToMatchJsResult(); }); test("subclass of class", () => { - const code = ` + util.testFunction` class type { method() { return typeof 0; } static staticMethod() { return typeof "foo"; } } class Foo extends type {} const foo = new Foo(); - return foo.method() + "|" + Foo.staticMethod();`; - - expect(util.transpileAndExecute(code)).toBe("number|string"); + return foo.method() + "|" + Foo.staticMethod(); + `.expectToMatchJsResult(); }); - test.each([ - { returnExport: "result", expectResult: "number|string" }, - { returnExport: "type ~= nil", expectResult: true }, - ])("exported class (%p)", ({ returnExport, expectResult }) => { - const code = ` + test("exported class (%p)", () => { + util.testModule` export class type { method() { return typeof 0; } static staticMethod() { return typeof "foo"; } } const t = new type(); - export const result = t.method() + "|" + type.staticMethod();`; - - expect(util.transpileExecuteAndReturnExport(code, returnExport)).toBe(expectResult); + export const result = t.method() + "|" + type.staticMethod(); + `.expectToMatchJsResult(); }); - test.each([ - { returnExport: "result", expectResult: "number|string" }, - { returnExport: "type ~= nil", expectResult: true }, - ])("subclass of exported class (%p)", ({ returnExport, expectResult }) => { - const code = ` + test("subclass of exported class (%p)", () => { + util.testModule` export class type { method() { return typeof 0; } static staticMethod() { return typeof "foo"; } } class Foo extends type {} const foo = new Foo(); - export const result = foo.method() + "|" + Foo.staticMethod();`; - - expect(util.transpileExecuteAndReturnExport(code, returnExport)).toBe(expectResult); + export const result = foo.method() + "|" + Foo.staticMethod(); + `.expectToMatchJsResult(); }); test("namespace", () => { @@ -682,20 +638,16 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { const code = ` return typeof type.foo + "|" + type.foo`; - expect(util.transpileAndExecute(code, undefined, undefined, tsHeader)).toBe("string|foobar"); + util.testFunction(code).setTsHeader(tsHeader).expectToMatchJsResult(); }); - test.each([ - { returnExport: "result", expectResult: "string|foobar" }, - { returnExport: "type ~= nil", expectResult: true }, - ])("exported namespace (%p)", ({ returnExport, expectResult }) => { - const code = ` + test("exported namespace (%p)", () => { + util.testModule` export namespace type { export const foo = "foobar"; } - export const result = typeof type.foo + "|" + type.foo;`; - - expect(util.transpileExecuteAndReturnExport(code, returnExport)).toBe(expectResult); + export const result = typeof type.foo + "|" + type.foo; + `.expectToMatchJsResult(); }); test("merged namespace", () => { @@ -717,14 +669,11 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { const t = new type(); return \`\${t.method()}|\${type.staticMethod()}|\${typeof type.foo}|\${type.foo}|\${type.bar}\`;`; - expect(util.transpileAndExecute(code, undefined, undefined, tsHeader)).toBe("number|boolean|string|foo|bar"); + util.testFunction(code).setTsHeader(tsHeader).expectToMatchJsResult(); }); - test.each([ - { returnExport: "result", expectResult: "number|boolean|string|foo|bar" }, - { returnExport: "type ~= nil", expectResult: true }, - ])("exported merged namespace (%p)", ({ returnExport, expectResult }) => { - const code = ` + test("exported merged namespace (%p)", () => { + util.testModule` export class type { method() { return typeof 0; } static staticMethod() { return typeof true; } @@ -739,25 +688,21 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { } const t = new type(); - export const result = \`\${t.method()}|\${type.staticMethod()}|\${typeof type.foo}|\${type.foo}|\${type.bar}\`;`; - - expect(util.transpileExecuteAndReturnExport(code, returnExport)).toBe(expectResult); + export const result = \`\${t.method()}|\${type.staticMethod()}|\${typeof type.foo}|\${type.foo}|\${type.bar}\`; + `.expectToMatchJsResult(); }); }); test("declaration-only variable with lua keyword as name is not renamed", () => { - const code = ` - declare function type(this: void, a: unknown): string; - type(7);`; - - expect(util.transpileString(code, undefined, false)).toBe("type(7)"); + util.testFunction("type(7)") + .setTsHeader("declare function type(this: void, a: unknown): string;") + .expectLuaToMatchSnapshot(); }); test("exported variable with lua keyword as name is not renamed", () => { - const code = ` - export const print = "foobar";`; - - expect(util.transpileExecuteAndReturnExport(code, "print")).toBe("foobar"); + util.testModule` + export const print = "foobar"; + `.expectToMatchJsResult(); }); // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/846 diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index b35bbd767..f0a89225b 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -198,18 +198,15 @@ test("for scope", () => { test.each([ { inp: { test1: 0, test2: 1, test3: 2 }, - expected: { test1: 1, test2: 2, test3: 3 }, }, -])("forin[Object] (%p)", ({ inp, expected }) => { - const result = util.transpileAndExecute( - `let objTest = ${JSON.stringify(inp)}; +])("forin[Object] (%p)", ({ inp }) => { + util.testFunctionTemplate` + let objTest = ${inp}; for (let key in objTest) { objTest[key] = objTest[key] + 1; } - return JSONStringify(objTest);` - ); - - expect(JSON.parse(result)).toEqual(expected); + return objTest; + `.expectToMatchJsResult(); }); test("forin[Array]", () => { @@ -219,11 +216,9 @@ test("forin[Array]", () => { `.expectDiagnosticsToMatchSnapshot([forbiddenForIn.code]); }); -test.each([{ inp: { a: 0, b: 1, c: 2, d: 3, e: 4 }, expected: { a: 0, b: 0, c: 2, d: 0, e: 4 } }])( - "forin with continue (%p)", - ({ inp, expected }) => { - const result = util.transpileAndExecute( - `let obj = ${JSON.stringify(inp)}; +test.each([{ inp: { a: 0, b: 1, c: 2, d: 3, e: 4 } }])("forin with continue (%p)", ({ inp }) => { + util.testFunctionTemplate` + let obj = ${inp}; for (let i in obj) { if (obj[i] % 2 == 0) { continue; @@ -231,24 +226,19 @@ test.each([{ inp: { a: 0, b: 1, c: 2, d: 3, e: 4 }, expected: { a: 0, b: 0, c: 2 obj[i] = 0; } - return JSONStringify(obj);` - ); - - expect(result).toBe(JSON.stringify(expected)); - } -); + return obj; + `.expectToMatchJsResult(); +}); -test.each([{ inp: [0, 1, 2], expected: [1, 2, 3] }])("forof (%p)", ({ inp, expected }) => { - const result = util.transpileAndExecute( - `let objTest = ${JSON.stringify(inp)}; +test.each([{ inp: [0, 1, 2] }])("forof (%p)", ({ inp }) => { + util.testFunctionTemplate` + let objTest = ${inp}; let arrResultTest = []; for (let value of objTest) { arrResultTest.push(value + 1) } - return JSONStringify(arrResultTest);` - ); - - expect(result).toBe(JSON.stringify(expected)); + return arrResultTest; + `.expectToMatchJsResult(); }); test("Tuple loop", () => { @@ -455,31 +445,29 @@ test.each(["", "abc", "a\0c"])("forof string (%p)", string => { describe("for...of empty destructuring", () => { const declareTests = (destructuringPrefix: string) => { test("array", () => { - const code = ` + util.testFunction` const arr = [["a"], ["b"], ["c"]]; let i = 0; for (${destructuringPrefix}[] of arr) { ++i; } return i; - `; - expect(util.transpileAndExecute(code)).toBe(3); + `.expectToMatchJsResult(); }); test("iterable", () => { - const code = ` + util.testFunction` const iter: Iterable = [["a"], ["b"], ["c"]]; let i = 0; for (${destructuringPrefix}[] of iter) { ++i; } return i; - `; - expect(util.transpileAndExecute(code)).toBe(3); + `.expectToMatchJsResult(); }); test("luaIterator", () => { - const code = ` + const luaResult = util.testFunction` const arr = [["a"], ["b"], ["c"]]; /** @luaIterator */ interface Iter extends Iterable {} @@ -492,12 +480,13 @@ describe("for...of empty destructuring", () => { ++i; } return i; - `; - expect(util.transpileAndExecute(code)).toBe(3); + `.getLuaExecutionResult(); + // Cant use expectToMatchJsResult because above is not valid TS/JS + expect(luaResult).toBe(3); }); test("luaIterator+tupleReturn", () => { - const code = ` + const luaResult = util.testFunction` const arr = [["a", "b"], ["c", "d"], ["e", "f"]]; /** * @luaIterator @@ -520,8 +509,9 @@ describe("for...of empty destructuring", () => { ++i; } return i; - `; - expect(util.transpileAndExecute(code)).toBe(3); + `.getLuaExecutionResult(); + // Cant use expectToMatchJsResult because above is not valid TS/JS + expect(luaResult).toBe(3); }); }; diff --git a/test/unit/printer/parenthesis.spec.ts b/test/unit/printer/parenthesis.spec.ts index ef08a6e3d..6c8e58156 100644 --- a/test/unit/printer/parenthesis.spec.ts +++ b/test/unit/printer/parenthesis.spec.ts @@ -1,7 +1,7 @@ import * as util from "../../util"; test("binary expression with 'as' type assertion wrapped in parenthesis", () => { - expect(util.transpileAndExecute("return 2 * (3 - 2 as number);")).toBe(2); + util.testFunction("return 2 * (3 - 2 as number);").expectToMatchJsResult(); }); test.each([ @@ -26,7 +26,7 @@ test.each([ declare function z(this: void): unknown; ${expression}`; - const lua = util.transpileString(code, undefined, false); + const lua = util.testExpression(code).getMainLuaCodeChunk(); expect(lua).not.toMatch(/\(.+\)/); }); @@ -50,15 +50,14 @@ test.each([ declare let y: {}; ${expression}`; - const lua = util.transpileString(code, undefined, false); + const lua = util.testExpression(code).getMainLuaCodeChunk(); expect(lua).toMatch(/\(.+\)/); }); test("not operator precedence (%p)", () => { - const code = ` + util.testFunction` const a = true; const b = false; - return !a && b;`; - - expect(util.transpileAndExecute(code)).toBe(false); + return !a && b; + `.expectToMatchJsResult(); }); diff --git a/test/util.ts b/test/util.ts index 7be98e727..e430af493 100644 --- a/test/util.ts +++ b/test/util.ts @@ -10,8 +10,6 @@ import * as vm from "vm"; import * as tstl from "../src"; import { createEmitOutputCollector } from "../src/transpilation/output-collector"; -export * from "./legacy-utils"; - // Using `test` directly makes eslint-plugin-jest consider this file as a test const defineTest = test; @@ -142,9 +140,9 @@ export abstract class TestBuilder { // TODO: Use testModule in these cases? protected tsHeader = ""; - public setTsHeader(tsHeader: string): this { + public setTsHeader(tsHeader: string | undefined): this { expect(this.hasProgram).toBe(false); - this.tsHeader = tsHeader; + this.tsHeader = tsHeader ?? ""; return this; } From eaa3f4ad36be0931b2030f38813cb93c4df072dc Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Fri, 8 Jan 2021 23:51:40 +0100 Subject: [PATCH 03/10] Added module capabilities to testbuilder --- test/json.lua | 6 +- test/util.ts | 216 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 140 insertions(+), 82 deletions(-) diff --git a/test/json.lua b/test/json.lua index e41c3798a..cd345c9b4 100644 --- a/test/json.lua +++ b/test/json.lua @@ -142,8 +142,4 @@ encode = function(val, stack) error("unexpected type '" .. t .. "'") end - --- TODO: Since it supports NaN and Infinity it is considered a superset of JSON, so it probably should be renamed -function JSONStringify(val) - return ( encode(val) ) -end +return {stringify = function(val) return ( encode(val) ) end} \ No newline at end of file diff --git a/test/util.ts b/test/util.ts index e430af493..709b8e4cb 100644 --- a/test/util.ts +++ b/test/util.ts @@ -10,6 +10,9 @@ import * as vm from "vm"; import * as tstl from "../src"; import { createEmitOutputCollector } from "../src/transpilation/output-collector"; +const minimalTestLib = fs.readFileSync(path.join(__dirname, "json.lua"), "utf8"); +const lualibContent = fs.readFileSync(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), "utf8"); + // Using `test` directly makes eslint-plugin-jest consider this file as a test const defineTest = test; @@ -39,76 +42,6 @@ export function testEachVersion( } } -function executeLua(code: string): any { - const L = lauxlib.luaL_newstate(); - lualib.luaL_openlibs(L); - const status = lauxlib.luaL_dostring(L, to_luastring(code)); - - if (status === lua.LUA_OK) { - if (lua.lua_isstring(L, -1)) { - const result = eval(`(${lua.lua_tojsstring(L, -1)})`); - return result === null ? undefined : result; - } else { - const returnType = to_jsstring(lua.lua_typename(L, lua.lua_type(L, -1))); - throw new Error(`Unsupported Lua return type: ${returnType}`); - } - } else { - // Filter out control characters appearing on some systems - const luaStackString = lua.lua_tostring(L, -1).filter(c => c >= 20); - const message = to_jsstring(luaStackString).replace(/^\[string "--\.\.\."\]:\d+: /, ""); - return new ExecutionError(message); - } -} - -const minimalTestLib = fs.readFileSync(path.join(__dirname, "json.lua"), "utf8"); -const lualibContent = fs.readFileSync(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), "utf8"); -export function executeLuaModule(code: string): any { - const lualibImport = code.includes('require("lualib_bundle")') - ? `package.preload.lualib_bundle = function()\n${lualibContent}\nend` - : ""; - - return executeLua(`${minimalTestLib}\n${lualibImport}\nreturn JSONStringify((function()\n${code}\nend)())`); -} - -function executeJsModule(code: string): any { - const exports = {}; - const context = vm.createContext({ exports, module: { exports } }); - context.global = context; - let result: unknown; - try { - result = vm.runInContext(code, context); - } catch (error) { - return new ExecutionError(error.message); - } - - function transform(currentValue: any): any { - if (currentValue === null) { - return undefined; - } - - if (Array.isArray(currentValue)) { - return currentValue.map(transform); - } - - if (typeof currentValue === "object") { - for (const [key, value] of Object.entries(currentValue)) { - currentValue[key] = transform(value); - if (currentValue[key] === undefined) { - delete currentValue[key]; - } - } - - if (Object.keys(currentValue).length === 0) { - return []; - } - } - - return currentValue; - } - - return transform(result); -} - const memoize: MethodDecorator = (_target, _propertyKey, descriptor) => { const originalFunction = descriptor.value as any; const memoized = new WeakMap(); @@ -160,7 +93,7 @@ export abstract class TestBuilder { return this; } - protected abstract getLuaCodeWithWrapper: (code: string) => string; + protected abstract getLuaCodeWithWrapper(code: string): string; public setLuaFactory(luaFactory: (code: string) => string): this { expect(this.hasProgram).toBe(false); this.getLuaCodeWithWrapper = luaFactory; @@ -198,7 +131,7 @@ export abstract class TestBuilder { return this; } - private extraFiles: Record = {}; + protected extraFiles: Record = {}; public addExtraFile(fileName: string, code: string): this { expect(this.hasProgram).toBe(false); this.extraFiles[fileName] = code; @@ -261,7 +194,7 @@ export abstract class TestBuilder { @memoize public getLuaExecutionResult(): any { - return executeLuaModule(this.getLuaCodeWithWrapper(this.getMainLuaCodeChunk())); + return this.executeLua(); } @memoize @@ -275,7 +208,7 @@ export abstract class TestBuilder { } @memoize - protected getMainJsCodeChunk(): string { + public getMainJsCodeChunk(): string { const { transpiledFiles } = this.getJsResult(); const code = transpiledFiles.find(({ sourceFiles }) => sourceFiles.some(f => f.fileName === this.mainFileName)) ?.js; @@ -289,7 +222,7 @@ export abstract class TestBuilder { @memoize public getJsExecutionResult(): any { - return executeJsModule(this.getJsCodeWithWrapper()); + return this.executeJs(); } // Utilities @@ -387,12 +320,142 @@ export abstract class TestBuilder { callback(this); return this; } + + private executeLua(): any { + // Main file + const mainFile = this.getMainLuaCodeChunk(); + + const L = lauxlib.luaL_newstate(); + lualib.luaL_openlibs(L); + + // Load modules + // Json + lua.lua_getglobal(L, "package"); + lua.lua_getfield(L, -1, "preload"); + lauxlib.luaL_loadstring(L, to_luastring(minimalTestLib)); + lua.lua_setfield(L, -2, "json"); + // Lua lib + if ( + this.options.luaLibImport === tstl.LuaLibImportKind.Require || + mainFile.includes('require("lualib_bundle")') + ) { + lua.lua_getglobal(L, "package"); + lua.lua_getfield(L, -1, "preload"); + lauxlib.luaL_loadstring(L, to_luastring(lualibContent)); + lua.lua_setfield(L, -2, "lualib_bundle"); + } + + // Extra files + const { transpiledFiles } = this.getLuaResult(); + + Object.keys(this.extraFiles).forEach(fileName => { + const transpiledExtraFile = transpiledFiles.find(({ sourceFiles }) => + sourceFiles.some(f => f.fileName === fileName) + ); + if (transpiledExtraFile?.lua) { + lua.lua_getglobal(L, "package"); + lua.lua_getfield(L, -1, "preload"); + lauxlib.luaL_loadstring(L, to_luastring(transpiledExtraFile.lua)); + lua.lua_setfield(L, -2, fileName.replace(".ts", "")); + } + }); + + // Execute Main + const wrappedMainCode = ` + local JSON = require("json"); + return JSON.stringify((function() + ${this.getLuaCodeWithWrapper(mainFile)} + end)());`; + + const status = lauxlib.luaL_dostring(L, to_luastring(wrappedMainCode)); + + if (status === lua.LUA_OK) { + if (lua.lua_isstring(L, -1)) { + const result = eval(`(${lua.lua_tojsstring(L, -1)})`); + return result === null ? undefined : result; + } else { + const returnType = to_jsstring(lua.lua_typename(L, lua.lua_type(L, -1))); + throw new Error(`Unsupported Lua return type: ${returnType}`); + } + } else { + // Filter out control characters appearing on some systems + const luaStackString = lua.lua_tostring(L, -1).filter(c => c >= 20); + const message = to_jsstring(luaStackString).replace(/^\[string "--\.\.\."\]:\d+: /, ""); + return new ExecutionError(message); + } + } + + private executeJs(): any { + const { transpiledFiles } = this.getJsResult(); + // Custom require for extra files. Really basic, does not handle globals currently + // and probably a lot of other details. + // TODO Should be replace with vm.Module https://nodejs.org/api/vm.html#vm_class_vm_module + // once stable + const requireFromExtraFile = (fileName: string) => { + const moduleExports = {}; + const moduleContext = vm.createContext({ exports: moduleExports, module: { exports: moduleExports } }); + const transpiledExtraFile = transpiledFiles.find(({ sourceFiles }) => + sourceFiles.some(f => f.fileName === fileName.replace("./", "") + ".ts") + ); + + if (transpiledExtraFile?.js) { + vm.runInContext(transpiledExtraFile.js, moduleContext); + } + + return moduleContext.module.exports; + }; + + const mainExports = {}; + const mainContext = vm.createContext({ + exports: mainExports, + module: { exports: mainExports }, + require: requireFromExtraFile, + }); + mainContext.global = mainContext; + let result: unknown; + try { + result = vm.runInContext(this.getJsCodeWithWrapper(), mainContext); + } catch (error) { + return new ExecutionError(error.message); + } + + function removeUndefinedFields(obj: any): any { + if (obj === null) { + return undefined; + } + + if (Array.isArray(obj)) { + return obj.map(removeUndefinedFields); + } + + if (typeof obj === "object") { + const copy: any = {}; + for (const [key, value] of Object.entries(obj)) { + if (obj[key] !== undefined) { + copy[key] = removeUndefinedFields(value); + } + } + + if (Object.keys(copy).length === 0) { + return []; + } + + return copy; + } + + return obj; + } + + return removeUndefinedFields(result); + } } class AccessorTestBuilder extends TestBuilder { protected accessor = ""; - protected getLuaCodeWithWrapper = (code: string) => `return (function()\n${code}\nend)()${this.accessor}`; + protected getLuaCodeWithWrapper(code: string) { + return `return (function()\n${code}\nend)()${this.accessor}`; + } @memoize protected getJsCodeWithWrapper(): string { @@ -418,7 +481,6 @@ class ModuleTestBuilder extends AccessorTestBuilder { return this; } } - class FunctionTestBuilder extends AccessorTestBuilder { protected accessor = ".__main()"; public getTsCode(): string { From c6ea148d6ddde9fb546548201d496de04fbe538f Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Fri, 8 Jan 2021 23:51:50 +0100 Subject: [PATCH 04/10] Fixed more test cases --- .../__snapshots__/expressions.spec.ts.snap | 12 ++ .../annotations/customConstructor.spec.ts | 8 +- test/unit/annotations/metaExtension.spec.ts | 16 +- test/unit/assignments.spec.ts | 3 +- test/unit/builtins/map.spec.ts | 108 +++++------ test/unit/builtins/weakSet.spec.ts | 37 ++-- test/unit/error.spec.ts | 70 +++---- test/unit/expressions.spec.ts | 11 +- test/unit/functions/functions.spec.ts | 15 +- .../functionExpressionTypeInference.spec.ts | 4 +- test/unit/hoisting.spec.ts | 22 +-- test/unit/identifiers.spec.ts | 147 ++++++++------- test/unit/loops.spec.ts | 4 +- test/unit/modules/modules.spec.ts | 171 ++++++++---------- test/unit/namespaces.spec.ts | 4 +- test/unit/printer/deadCodeAfterReturn.spec.ts | 24 +-- 16 files changed, 307 insertions(+), 349 deletions(-) diff --git a/test/unit/__snapshots__/expressions.spec.ts.snap b/test/unit/__snapshots__/expressions.spec.ts.snap index d3ff509a2..b743ed0c8 100644 --- a/test/unit/__snapshots__/expressions.spec.ts.snap +++ b/test/unit/__snapshots__/expressions.spec.ts.snap @@ -431,6 +431,12 @@ ____exports.__result = bit.bor(a, b) return ____exports" `; +exports[`Null Expression 1`] = ` +"local ____exports = {} +____exports.__result = nil +return ____exports" +`; + exports[`Unary expressions basic ("!a") 1`] = ` "local ____exports = {} function ____exports.__main(self) @@ -523,6 +529,12 @@ end return ____exports" `; +exports[`Undefined Expression 1`] = ` +"local ____exports = {} +____exports.__result = nil +return ____exports" +`; + exports[`Unsupported bitop 5.3 ("a>>=b"): code 1`] = ` "local ____exports = {} ____exports.__result = (function() diff --git a/test/unit/annotations/customConstructor.spec.ts b/test/unit/annotations/customConstructor.spec.ts index 007c64c3f..f832839b5 100644 --- a/test/unit/annotations/customConstructor.spec.ts +++ b/test/unit/annotations/customConstructor.spec.ts @@ -19,9 +19,13 @@ test("CustomCreate", () => { } `; - const result = util.transpileAndExecute("return new Point2D(1, 2).x;", undefined, luaHeader, tsHeader); + // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + const luaResult = util.testModule`export default new Point2D(1, 2).x;` + .setTsHeader(tsHeader) + .setLuaHeader(luaHeader) + .getLuaExecutionResult(); - expect(result).toBe(1); + expect(luaResult.default).toBe(1); }); test("IncorrectUsage", () => { diff --git a/test/unit/annotations/metaExtension.spec.ts b/test/unit/annotations/metaExtension.spec.ts index aec48f4cf..2407ef654 100644 --- a/test/unit/annotations/metaExtension.spec.ts +++ b/test/unit/annotations/metaExtension.spec.ts @@ -19,15 +19,15 @@ test("MetaExtension", () => { } `; - const result = util.transpileAndExecute( - 'return debug.getregistry()["_LOADED"].test();', - undefined, - undefined, - tsHeader, - true - ); + const luaResult = util.testModule` + export default debug.getregistry()["_LOADED"].test(); + ` + .setTsHeader(tsHeader) + .ignoreDiagnostics([annotationDeprecated.code]) + // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + .getLuaExecutionResult(); - expect(result).toBe(5); + expect(luaResult.default).toBe(5); }); test("IncorrectUsage", () => { diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index d2d80f08b..b1fa6f426 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -12,7 +12,8 @@ test.each(["const", "let"])("%s declaration not top-level is not global", declar }); test.each(["const", "let"])("top-level %s declaration is global", declarationKind => { - util.testBundle` + // TODO cant be tested with expectToMatchJsResult because in JS that would not be global + util.testModule` import './a'; export const result = foo; ` diff --git a/test/unit/builtins/map.spec.ts b/test/unit/builtins/map.spec.ts index e9edd2508..d85f15d8a 100644 --- a/test/unit/builtins/map.spec.ts +++ b/test/unit/builtins/map.spec.ts @@ -1,95 +1,79 @@ import * as util from "../../util"; test("map constructor", () => { - const result = util.transpileAndExecute("let mymap = new Map(); return mymap.size;"); - - expect(result).toBe(0); + util.testFunction("let mymap = new Map(); return mymap.size;").expectToMatchJsResult(); }); test("map iterable constructor", () => { - const result = util.transpileAndExecute( - `let mymap = new Map([["a", "c"],["b", "d"]]); - return mymap.has("a") && mymap.has("b");` - ); - - expect(result).toBe(true); + util.testFunction` + let mymap = new Map([["a", "c"],["b", "d"]]); + return mymap.has("a") && mymap.has("b"); + `.expectToMatchJsResult(); }); test("map iterable constructor map", () => { - const result = util.transpileAndExecute(` + util.testFunction` let mymap = new Map(new Map([["a", "c"],["b", "d"]])); return mymap.has("a") && mymap.has("b"); - `); - - expect(result).toBe(true); + `.expectToMatchJsResult(); }); test("map clear", () => { const mapTS = 'let mymap = new Map([["a", "c"],["b", "d"]]); mymap.clear();'; - const size = util.transpileAndExecute(mapTS + "return mymap.size;"); - expect(size).toBe(0); + util.testFunction(mapTS + "return mymap.size;").expectToMatchJsResult(); - const contains = util.transpileAndExecute(mapTS + 'return !mymap.has("a") && !mymap.has("b");'); - expect(contains).toBe(true); + util.testFunction(mapTS + 'return !mymap.has("a") && !mymap.has("b");').expectToMatchJsResult(); }); test("map delete", () => { - const mapTS = 'let mymap = new Map([["a", "c"],["b", "d"]]); mymap.delete("a");'; - const contains = util.transpileAndExecute(mapTS + 'return mymap.has("b") && !mymap.has("a");'); - expect(contains).toBe(true); + util.testFunction` + let mymap = new Map([["a", "c"],["b", "d"]]); + mymap.delete("a"); + return mymap.has("b") && !mymap.has("a"); + `.expectToMatchJsResult(); }); test("map entries", () => { - const result = util.transpileAndExecute( - `let mymap = new Map([[5, 2],[6, 3],[7, 4]]); + util.testFunction` + let mymap = new Map([[5, 2],[6, 3],[7, 4]]); let count = 0; for (const [key, value] of mymap.entries()) { count += key + value; } - return count;` - ); - expect(result).toBe(27); + return count; + `.expectToMatchJsResult(); }); test("map foreach", () => { - const result = util.transpileAndExecute( + util.testFunction( `let mymap = new Map([["a", 2],["b", 3],["c", 4]]); let count = 0; mymap.forEach(i => count += i); return count;` - ); - - expect(result).toBe(9); + ).expectToMatchJsResult(); }); test("map foreach keys", () => { - const result = util.transpileAndExecute( - `let mymap = new Map([[5, 2],[6, 3],[7, 4]]); + util.testFunction` + let mymap = new Map([[5, 2],[6, 3],[7, 4]]); let count = 0; mymap.forEach((value, key) => { count += key; }); - return count;` - ); - - expect(result).toBe(18); + return count; + `.expectToMatchJsResult(); }); test("map get", () => { - const result = util.transpileAndExecute('let mymap = new Map([["a", "c"],["b", "d"]]); return mymap.get("a");'); - - expect(result).toBe("c"); + util.testFunction('let mymap = new Map([["a", "c"],["b", "d"]]); return mymap.get("a");').expectToMatchJsResult(); }); test("map get missing", () => { - const result = util.transpileAndExecute('let mymap = new Map([["a", "c"],["b", "d"]]); return mymap.get("c");'); - expect(result).toBeUndefined(); + util.testFunction('let mymap = new Map([["a", "c"],["b", "d"]]); return mymap.get("c");').expectToMatchJsResult(); }); test("map has", () => { - const contains = util.transpileAndExecute('let mymap = new Map([["a", "c"]]); return mymap.has("a");'); - expect(contains).toBe(true); + util.testFunction('let mymap = new Map([["a", "c"]]); return mymap.has("a");').expectToMatchJsResult(); }); test("map has false", () => { - const contains = util.transpileAndExecute('let mymap = new Map(); return mymap.has("a");'); - expect(contains).toBe(false); + util.testFunction('let mymap = new Map(); return mymap.has("a");').expectToMatchJsResult(); }); test.each([ @@ -117,42 +101,36 @@ test.each([ }); test("map keys", () => { - const result = util.transpileAndExecute( - `let mymap = new Map([[5, 2],[6, 3],[7, 4]]); + util.testFunction` + let mymap = new Map([[5, 2],[6, 3],[7, 4]]); let count = 0; for (const key of mymap.keys()) { count += key; } - return count;` - ); - - expect(result).toBe(18); + return count; + `.expectToMatchJsResult(); }); test("map set", () => { const mapTS = 'let mymap = new Map(); mymap.set("a", 5);'; - const has = util.transpileAndExecute(mapTS + 'return mymap.has("a");'); - expect(has).toBe(true); + util.testFunction(mapTS + 'return mymap.has("a");').expectToMatchJsResult(); - const value = util.transpileAndExecute(mapTS + 'return mymap.get("a")'); - expect(value).toBe(5); + util.testFunction(mapTS + 'return mymap.get("a")').expectToMatchJsResult(); }); test("map values", () => { - const result = util.transpileAndExecute( - `let mymap = new Map([[5, 2],[6, 3],[7, 4]]); + util.testFunction` + let mymap = new Map([[5, 2],[6, 3],[7, 4]]); let count = 0; for (const value of mymap.values()) { count += value; } - return count;` - ); - - expect(result).toBe(9); + return count; + `.expectToMatchJsResult(); }); test("map size", () => { - expect(util.transpileAndExecute("let m = new Map(); return m.size;")).toBe(0); - expect(util.transpileAndExecute("let m = new Map(); m.set(1,3); return m.size;")).toBe(1); - expect(util.transpileAndExecute("let m = new Map([[1,2],[3,4]]); return m.size;")).toBe(2); - expect(util.transpileAndExecute("let m = new Map([[1,2],[3,4]]); m.clear(); return m.size;")).toBe(0); - expect(util.transpileAndExecute("let m = new Map([[1,2],[3,4]]); m.delete(3); return m.size;")).toBe(1); + util.testFunction("let m = new Map(); return m.size;").expectToMatchJsResult(); + util.testFunction("let m = new Map(); m.set(1,3); return m.size;").expectToMatchJsResult(); + util.testFunction("let m = new Map([[1,2],[3,4]]); return m.size;").expectToMatchJsResult(); + util.testFunction("let m = new Map([[1,2],[3,4]]); m.clear(); return m.size;").expectToMatchJsResult(); + util.testFunction("let m = new Map([[1,2],[3,4]]); m.delete(3); return m.size;").expectToMatchJsResult(); }); const iterationMethods = ["entries", "keys", "values"]; diff --git a/test/unit/builtins/weakSet.spec.ts b/test/unit/builtins/weakSet.spec.ts index fe6accf45..9d779f2f5 100644 --- a/test/unit/builtins/weakSet.spec.ts +++ b/test/unit/builtins/weakSet.spec.ts @@ -6,69 +6,58 @@ const initRefsTs = ` `; test("weakSet constructor", () => { - const result = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let myset = new WeakSet([ref]); return myset.has(ref) - `); - - expect(result).toBe(true); + `.expectToMatchJsResult(); }); test("weakSet iterable constructor", () => { - const result = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let myset = new WeakSet([ref, ref2]); return myset.has(ref) && myset.has(ref2); - `); - - expect(result).toBe(true); + `.expectToMatchJsResult(); }); test("weakSet iterable constructor set", () => { - const result = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let myset = new WeakSet(new Set([ref, ref2])); return myset.has(ref) && myset.has(ref2); - `); - - expect(result).toBe(true); + `.expectToMatchJsResult(); }); test("weakSet add", () => { - const result = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let myset = new WeakSet(); myset.add(ref); return myset.has(ref); - `); - - expect(result).toBe(true); + `.expectToMatchJsResult(); }); test("weakSet add different references", () => { - const result = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let myset = new WeakSet(); myset.add({}); return myset.has({}); - `); - - expect(result).toBe(false); + `.expectToMatchJsResult(); }); test("weakSet delete", () => { - const contains = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let myset = new WeakSet([ref, ref2]); myset.delete(ref); return myset.has(ref2) && !myset.has(ref); - `); - expect(contains).toBe(true); + `.expectToMatchJsResult(); }); test("weakSet has no set features (size)", () => { - expect(util.transpileAndExecute("return (new WeakSet() as any).size")).toBeUndefined(); + util.testFunction("return (new WeakSet() as any).size").expectToMatchJsResult(); }); test.each(["clear()", "keys()", "values()", "entries()", "forEach(() => {})"])( diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 27e0ce839..9174f941c 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -51,7 +51,7 @@ test("re-throw (no catch var)", () => { }); test("return from try", () => { - const code = ` + util.testFunction` function foobar() { try { return "foobar"; @@ -59,12 +59,11 @@ test("return from try", () => { } } return foobar(); - `; - expect(util.transpileAndExecute(code)).toBe("foobar"); + `.expectToMatchJsResult(); }); test("return nil from try", () => { - const code = ` + util.testFunction` let x = "unset"; function foobar() { try { @@ -75,12 +74,11 @@ test("return nil from try", () => { } foobar(); return x; - `; - expect(util.transpileAndExecute(code)).toBe("unset"); + `.expectToMatchJsResult(); }); test("tuple return from try", () => { - const code = ` + const testBuilder = util.testFunction` /** @tupleReturn */ function foobar() { try { @@ -91,12 +89,12 @@ test("tuple return from try", () => { const [foo, bar] = foobar(); return foo + bar; `; - expect(util.transpileString(code)).not.toMatch("unpack(foobar"); - expect(util.transpileAndExecute(code)).toBe("foobar"); + expect(testBuilder.getMainLuaCodeChunk()).not.toMatch("unpack(foobar"); + testBuilder.expectToMatchJsResult(); }); test("return from catch", () => { - const code = ` + util.testFunction` function foobar() { try { throw "foobar"; @@ -105,12 +103,11 @@ test("return from catch", () => { } } return foobar(); - `; - expect(util.transpileAndExecute(code)).toMatch(/foobar catch$/); + `.expectToMatchJsResult(); }); test("return nil from catch", () => { - const code = ` + util.testFunction` let x = "unset"; function foobar() { try { @@ -122,12 +119,11 @@ test("return nil from catch", () => { } foobar(); return x; - `; - expect(util.transpileAndExecute(code)).toBe("unset"); + `.expectToMatchJsResult(); }); test("tuple return from catch", () => { - const code = ` + const testBuilder = util.testFunction` /** @tupleReturn */ function foobar(): [string, string] { try { @@ -139,12 +135,12 @@ test("tuple return from catch", () => { const [foo, bar] = foobar(); return foo + bar; `; - expect(util.transpileString(code)).not.toMatch("unpack(foobar"); - expect(util.transpileAndExecute(code)).toMatch(/foobar catch$/); + expect(testBuilder.getMainLuaCodeChunk()).not.toMatch("unpack(foobar"); + testBuilder.expectToMatchJsResult(); }); test("return from nested try", () => { - const code = ` + util.testFunction` function foobar() { try { try { @@ -155,12 +151,11 @@ test("return from nested try", () => { } } return foobar(); - `; - expect(util.transpileAndExecute(code)).toBe("foobar"); + `.expectToMatchJsResult(); }); test("return from nested catch", () => { - const code = ` + util.testFunction` function foobar() { try { throw "foobar"; @@ -173,15 +168,11 @@ test("return from nested catch", () => { } } return foobar(); - `; - const result = util.transpileAndExecute(code); - expect(result).toMatch("catch1"); - expect(result).toMatch("catch2"); - expect(result).toMatch("foobar"); + `.expectToMatchJsResult(); }); test("return from try->finally", () => { - const code = ` + util.testFunction` let x = "unevaluated"; function evaluate(arg: unknown) { x = "evaluated"; @@ -196,12 +187,11 @@ test("return from try->finally", () => { } } return foobar() + " " + x; - `; - expect(util.transpileAndExecute(code)).toBe("finally evaluated"); + `.expectToMatchJsResult(); }); test("return from catch->finally", () => { - const code = ` + util.testFunction` let x = "unevaluated"; function evaluate(arg: unknown) { x = "evaluated"; @@ -217,12 +207,11 @@ test("return from catch->finally", () => { } } return foobar() + " " + x; - `; - expect(util.transpileAndExecute(code)).toBe("finally evaluated"); + `.expectToMatchJsResult(); }); test("tuple return from try->finally", () => { - const code = ` + util.testFunction` let x = "unevaluated"; function evaluate(arg: string) { x = "evaluated"; @@ -239,12 +228,11 @@ test("tuple return from try->finally", () => { } const [foo, bar] = foobar(); return foo + bar + " " + x; - `; - expect(util.transpileAndExecute(code)).toBe("finally evaluated"); + `.expectToMatchJsResult(); }); test("tuple return from catch->finally", () => { - const code = ` + util.testFunction` let x = "unevaluated"; function evaluate(arg: string) { x = "evaluated"; @@ -262,12 +250,11 @@ test("tuple return from catch->finally", () => { } const [foo, bar] = foobar(); return foo + bar + " " + x; - `; - expect(util.transpileAndExecute(code)).toBe("finally evaluated"); + `.expectToMatchJsResult(); }); test("return from nested finally", () => { - const code = ` + util.testFunction` let x = ""; function foobar() { try { @@ -282,8 +269,7 @@ test("return from nested finally", () => { } } return foobar() + " " + x; - `; - expect(util.transpileAndExecute(code)).toBe("finally AB"); + `.expectToMatchJsResult(); }); test.each([ diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 6720fe8fb..6f211f5e2 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -30,12 +30,7 @@ test.each(["1==1", "1===1", "1!=1", "1!==1", "1>1", "1>=1", "1<1", "1<=1", "1&&1 ); test.each(["'key' in obj", "'existingKey' in obj", "0 in obj", "9 in obj"])("Binary expression in (%p)", input => { - const tsHeader = "declare var obj: any;"; - const tsSource = `return ${input}`; - const luaHeader = "obj = { existingKey = 1 }"; - const result = util.transpileAndExecute(tsSource, undefined, luaHeader, tsHeader); - - expect(result).toBe(eval(`let obj = { existingKey: 1 }; ${input}`)); + util.testFunction(`let obj = { existingKey: 1 }; return ${input}`).expectToMatchJsResult(); }); test.each(["a+=b", "a-=b", "a*=b", "a/=b", "a%=b", "a**=b"])("Binary expressions overridden operators (%p)", input => { @@ -119,11 +114,11 @@ test("Binary Comma Statement in For Loop", () => { }); test("Null Expression", () => { - expect(util.transpileString("null")).toBe("local ____ = nil"); + util.testExpression("null").expectLuaToMatchSnapshot(); }); test("Undefined Expression", () => { - expect(util.transpileString("undefined")).toBe("local ____ = nil"); + util.testExpression("undefined").expectLuaToMatchSnapshot(); }); test.each(["i++", "i--", "++i", "--i"])("Incrementor value (%p)", expression => { diff --git a/test/unit/functions/functions.spec.ts b/test/unit/functions/functions.spec.ts index 20f76e273..056d62472 100644 --- a/test/unit/functions/functions.spec.ts +++ b/test/unit/functions/functions.spec.ts @@ -66,19 +66,12 @@ test("Function default parameter", () => { }); test.each([{ inp: [] }, { inp: [5] }, { inp: [1, 2] }])("Function Default Values (%p)", ({ inp }) => { - // Default value is 3 for v1 - const v1 = inp.length > 0 ? inp[0] : 3; - // Default value is 4 for v2 - const v2 = inp.length > 1 ? inp[1] : 4; - const callArgs = inp.join(","); - const result = util.transpileAndExecute( + util.testFunction( `let add = function(a: number = 3, b: number = 4) { return a+b; }; return add(${callArgs});` - ); - - expect(result).toBe(v1 + v2); + ).expectToMatchJsResult(); }); test("Function default array binding parameter", () => { @@ -476,10 +469,10 @@ test("missing declaration name", () => { }); test("top-level function declaration is global", () => { - util.testBundle` + util.testModule` import './a'; export const result = foo(); ` .addExtraFile("a.ts", 'function foo() { return "foo" }') - .expectToEqual({ result: "foo" }); + .expectToMatchJsResult(); }); diff --git a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts index 65618201e..265bfd7dd 100644 --- a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts +++ b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts @@ -36,7 +36,9 @@ test.each(["(this: void, s: string) => string", "(this: any, s: string) => strin func = undefinedFunc || (s => s); return func("foo"); `; - util.testFunction(code).setTsHeader(header).expectToMatchJsResult(); + // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + const luaResult = util.testFunction(code).setTsHeader(header).getLuaExecutionResult(); + expect(luaResult).toBe("foo"); } ); diff --git a/test/unit/hoisting.spec.ts b/test/unit/hoisting.spec.ts index 16172b410..eafe74348 100644 --- a/test/unit/hoisting.spec.ts +++ b/test/unit/hoisting.spec.ts @@ -204,34 +204,34 @@ test("Enum Hoisting", () => { }); test("Import hoisting (named)", () => { - util.testBundle` + util.testModule` export const result = foo; import { foo } from "./module"; ` .addExtraFile("module.ts", "export const foo = true;") - .expectToEqual({ result: true }); + .expectToMatchJsResult(); }); test("Import hoisting (namespace)", () => { - util.testBundle` + util.testModule` export const result = module.foo; import * as module from "./module"; ` .addExtraFile("module.ts", "export const foo = true;") - .expectToEqual({ result: true }); + .expectToMatchJsResult(); }); test("Import hoisting (side-effect)", () => { - util.testBundle` + util.testModule` export const result = (globalThis as any).result; import "./module"; ` .addExtraFile("module.ts", "(globalThis as any).result = true; export {};") - .expectToEqual({ result: true }); + .expectToMatchJsResult(); }); test("Import hoisted before function", () => { - util.testBundle` + util.testModule` export let result: any; baz(); @@ -242,17 +242,17 @@ test("Import hoisted before function", () => { import { foo } from "./module"; ` .addExtraFile("module.ts", "export const foo = true;") - .expectToEqual({ result: true }); + .expectToMatchJsResult(); }); test("Hoisting Shorthand Property", () => { - const code = ` + util.testFunction` function foo() { return { bar }.bar; } let bar = "foobar"; - return foo();`; - expect(util.transpileAndExecute(code)).toBe("foobar"); + return foo(); + `.expectToMatchJsResult(); }); // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/944 diff --git a/test/unit/identifiers.spec.ts b/test/unit/identifiers.spec.ts index 798763cb2..90ecccbe2 100644 --- a/test/unit/identifiers.spec.ts +++ b/test/unit/identifiers.spec.ts @@ -132,10 +132,11 @@ test.each(validTsInvalidLuaNames)( ); test.each(validTsInvalidLuaNames)("exported values with invalid lua identifier names (%p)", name => { - const code = `export const ${name} = "foobar";`; - const lua = util.transpileString(code); + const testBuilder = util.testModule(`export const ${name} = "foobar";`); + const lua = testBuilder.getMainLuaCodeChunk(); + const luaResult = testBuilder.getLuaExecutionResult(); expect(lua.indexOf(`"${name}"`)).toBeGreaterThanOrEqual(0); - expect(util.executeLua(`return (function() ${lua} end)()["${name}"]`)).toBe("foobar"); + expect(luaResult[name]).toBe("foobar"); }); test("exported identifiers referenced in namespace (%p)", () => { @@ -157,7 +158,7 @@ test("exported namespace identifiers referenced in different namespace (%p)", () } export const baz = B.bar; }`; - util.testModule("return A.baz").setTsHeader(tsHeader).expectToMatchJsResult(); + util.testFunction("return A.baz").setTsHeader(tsHeader).expectToMatchJsResult(); }); test("exported identifiers referenced in nested scope (%p)", () => { @@ -223,10 +224,12 @@ test.each(validTsInvalidLuaNames)("exported decorated class with invalid lua nam describe("lua keyword as identifier doesn't interfere with lua's value", () => { test("variable (nil)", () => { - util.testFunction` + const luaResult = util.testFunction` const nil = "foobar"; return \`\${undefined}|\${nil}\` - `.expectToMatchJsResult(); + `.getLuaExecutionResult(); + + expect(luaResult).toBe("nil|foobar"); }); test("variable (and)", () => { @@ -321,14 +324,19 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { const tsHeader = ` declare let result: string;`; - const code = ` + const compilerOptions = { lib: ["lib.es2015.d.ts", "lib.dom.d.ts"] }; + + const luaResult = util.testFunction` const print = "foobar"; console.log(print); - return result;`; - - const compilerOptions = { lib: ["lib.es2015.d.ts", "lib.dom.d.ts"] }; + return result; + ` + .setLuaHeader(luaHeader) + .setTsHeader(tsHeader) + .setOptions(compilerOptions) + .getLuaExecutionResult(); - expect(util.transpileAndExecute(code, compilerOptions, luaHeader, tsHeader)).toBe("foobar"); + expect(luaResult).toBe("foobar"); }); test("variable (type)", () => { @@ -346,17 +354,20 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { throw error; `.getLuaExecutionResult(); - expect(executionResult).toMatch(/^LUA ERROR: foobar$/); + expect(executionResult).toEqual(new util.ExecutionError("foobar")); }); test("variable (assert)", () => { - const code = ` - const assert = false; - console.assert(assert, "foobar");`; - const compilerOptions = { lib: ["lib.es2015.d.ts", "lib.dom.d.ts"] }; - expect(() => util.transpileAndExecute(code, compilerOptions)).toThrow(/^LUA ERROR: .+ foobar$/); + const luaResult = util.testFunction` + const assert = false; + console.assert(assert, "foobar"); + ` + .setOptions(compilerOptions) + .getLuaExecutionResult(); + + expect(luaResult).toEqual(new util.ExecutionError("foobar")); }); test("variable (debug)", () => { @@ -369,16 +380,19 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { const tsHeader = ` declare let result: string;`; - const code = ` + const compilerOptions = { lib: ["lib.es2015.d.ts", "lib.dom.d.ts"] }; + + const luaResult = util.testFunction` const debug = "foobar"; console.trace(debug); - return result;`; - - const compilerOptions = { lib: ["lib.es2015.d.ts", "lib.dom.d.ts"] }; + return result; + ` + .setTsHeader(tsHeader) + .setLuaHeader(luaHeader) + .setOptions(compilerOptions) + .getLuaExecutionResult(); - expect(util.transpileAndExecute(code, compilerOptions, luaHeader, tsHeader)).toMatch( - /^foobar\nstack traceback.+/ - ); + expect(luaResult).toMatch(/^foobar\nstack traceback.+/); }); test("variable (string)", () => { @@ -437,18 +451,17 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test("variable (require)", () => { - const code = ` + const luaHeader = 'package.loaded.someModule = {foo = "bar"}'; + + const luaResult = util.testModule` const require = "foobar"; export { foo } from "someModule"; - export const result = require;`; - - const lua = ` - package.loaded.someModule = {foo = "bar"} - return (function() - ${util.transpileString(code, undefined, true)} - end)().result`; + export const result = require; + ` + .setLuaHeader(luaHeader) + .getLuaExecutionResult(); - expect(util.executeLua(lua)).toBe("foobar"); + expect(luaResult.result).toBe("foobar"); }); test("variable (tostring)", () => { @@ -459,16 +472,18 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test("variable (unpack)", () => { - const code = ` - const unpack = ["foo", "bar"]; - const [foo, bar] = unpack;`; + // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + const luaHeader = "unpack = table.unpack"; - const lua = ` - unpack = table.unpack - ${util.transpileString(code, undefined, false)} - return foo .. bar`; + const luaResult = util.testFunction` + const unpack = ["foo", "bar"]; + const [foo, bar] = unpack; + return foo + bar; + ` + .setLuaHeader(luaHeader) + .getLuaExecutionResult(); - expect(util.executeLua(lua)).toBe("foobar"); + expect(luaResult).toBe("foobar"); }); test("variable (_G)", () => { @@ -547,18 +562,17 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test.each(["type", "type as type"])("imported variable (%p)", importName => { - const luaHeader = ` - package.loaded.someModule = {type = "foobar"}`; + // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + const luaHeader = 'package.loaded.someModule = {type = "foobar"}'; - const code = ` + const luaResult = util.testModule` import {${importName}} from "someModule"; export const result = typeof 7 + "|" + type; - `; - - const lua = util.transpileString(code); - const result = util.executeLua(`${luaHeader} return (function() ${lua} end)().result`); + ` + .setLuaHeader(luaHeader) + .getLuaExecutionResult(); - expect(result).toBe("number|foobar"); + expect(luaResult.result).toBe("number|foobar"); }); test("separately exported variable (%p)", () => { @@ -571,16 +585,17 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test.each(["type", "type as type"])("re-exported variable with lua keyword as name (%p)", importName => { - const code = ` - export { ${importName} } from "someModule"`; + // TODO Cant use expectToMatchJsResult because above is not valid TS/JS - const lua = ` - package.loaded.someModule = {type = "foobar"} - return (function() - ${util.transpileString(code)} - end)().type`; + const luaHeader = 'package.loaded.someModule = {type = "foobar"}'; - expect(util.executeLua(lua)).toBe("foobar"); + const luaResult = util.testModule` + export { ${importName} } from "someModule"; + ` + .setLuaHeader(luaHeader) + .getLuaExecutionResult(); + + expect(luaResult.type).toBe("foobar"); }); test("class", () => { @@ -606,7 +621,7 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { `.expectToMatchJsResult(); }); - test("exported class (%p)", () => { + test.each(["result", "type ~= nil"])("exported class (%p)", returnExport => { util.testModule` export class type { method() { return typeof 0; } @@ -614,10 +629,12 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { } const t = new type(); export const result = t.method() + "|" + type.staticMethod(); - `.expectToMatchJsResult(); + ` + .setReturnExport(returnExport) + .expectToMatchJsResult(); }); - test("subclass of exported class (%p)", () => { + test.each(["result", "type ~= nil"])("subclass of exported class (%p)", returnExport => { util.testModule` export class type { method() { return typeof 0; } @@ -626,7 +643,9 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { class Foo extends type {} const foo = new Foo(); export const result = foo.method() + "|" + Foo.staticMethod(); - `.expectToMatchJsResult(); + ` + .setReturnExport(returnExport) + .expectToMatchJsResult(); }); test("namespace", () => { @@ -672,7 +691,7 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { util.testFunction(code).setTsHeader(tsHeader).expectToMatchJsResult(); }); - test("exported merged namespace (%p)", () => { + test.each(["result", "type ~= nil"])("exported merged namespace (%p)", returnExport => { util.testModule` export class type { method() { return typeof 0; } @@ -689,7 +708,9 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { const t = new type(); export const result = \`\${t.method()}|\${type.staticMethod()}|\${typeof type.foo}|\${type.foo}|\${type.bar}\`; - `.expectToMatchJsResult(); + ` + .setReturnExport(returnExport) + .expectToMatchJsResult(); }); }); diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index f0a89225b..85107bc6b 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -481,7 +481,7 @@ describe("for...of empty destructuring", () => { } return i; `.getLuaExecutionResult(); - // Cant use expectToMatchJsResult because above is not valid TS/JS + // TODO Cant use expectToMatchJsResult because above is not valid TS/JS expect(luaResult).toBe(3); }); @@ -510,7 +510,7 @@ describe("for...of empty destructuring", () => { } return i; `.getLuaExecutionResult(); - // Cant use expectToMatchJsResult because above is not valid TS/JS + // TODO Cant use expectToMatchJsResult because above is not valid TS/JS expect(luaResult).toBe(3); }); }; diff --git a/test/unit/modules/modules.spec.ts b/test/unit/modules/modules.spec.ts index b6aefdc0c..dd3d1dd7a 100644 --- a/test/unit/modules/modules.spec.ts +++ b/test/unit/modules/modules.spec.ts @@ -66,130 +66,106 @@ test.each(["ke-bab", "dollar$", "singlequote'", "hash#", "s p a c e", "ɥɣɎɌ ); test.each(["export default value;", "export { value as default };"])("Export Default From (%p)", exportStatement => { - const [result] = util.transpileAndExecuteProjectReturningMainExport( - { - "main.ts": ` - export { default } from "./module"; - `, - "module.ts": ` + util.testModule` + export { default } from "./module"; + ` + .addExtraFile( + "module.ts", + ` export const value = true; ${exportStatement}; - `, - }, - "default" - ); - - expect(result).toBe(true); + ` + ) + .expectToMatchJsResult(); }); test("Default Import and Export Expression", () => { - const [result] = util.transpileAndExecuteProjectReturningMainExport( - { - "main.ts": ` - import defaultExport from "./module"; - export const value = defaultExport; - `, - "module.ts": ` + util.testModule` + import defaultExport from "./module"; + export const value = defaultExport; + ` + .addExtraFile( + "module.ts", + ` export default 1 + 2 + 3; - `, - }, - "value" - ); - - expect(result).toBe(6); + ` + ) + .expectToMatchJsResult(); }); test("Import and Export Assignment", () => { - const [result] = util.transpileAndExecuteProjectReturningMainExport( - { - "main.ts": ` - import * as m from "./module"; - export const value = m; - `, - "module.ts": ` + util.testModule` + // @ts-ignore + import * as m from "./module"; + export const value = m; + ` + .setOptions({ module: ts.ModuleKind.CommonJS }) + .addExtraFile( + "module.ts", + ` export = true; - `, - }, - "value" - ); - - expect(result).toBe(true); + ` + ) + .expectToMatchJsResult(); }); test("Mixed Exports, Default and Named Imports", () => { - const [result] = util.transpileAndExecuteProjectReturningMainExport( - { - "main.ts": ` - import defaultExport, { a, b, c } from "./module"; - export const value = defaultExport + b + c; - `, - "module.ts": ` + util.testModule` + import defaultExport, { a, b, c } from "./module"; + export const value = defaultExport + b + c; + ` + .addExtraFile( + "module.ts", + ` export const a = 1; export const b = 2; export const c = 3; export default a; - `, - }, - "value" - ); - - expect(result).toBe(6); + ` + ) + .expectToMatchJsResult(); }); test("Mixed Exports, Default and Namespace Import", () => { - const [result] = util.transpileAndExecuteProjectReturningMainExport( - { - "main.ts": ` - import defaultExport, * as ns from "./module"; - export const value = defaultExport + ns.b + ns.c; - `, - "module.ts": ` + util.testModule` + import defaultExport, * as ns from "./module"; + export const value = defaultExport + ns.b + ns.c; + ` + .addExtraFile( + "module.ts", + ` export const a = 1; export const b = 2; export const c = 3; export default a; - `, - }, - "value" - ); - - expect(result).toBe(6); + ` + ) + .expectToMatchJsResult(); }); test("Export Default Function", () => { - const [result] = util.transpileAndExecuteProjectReturningMainExport( - { - "main.ts": ` - import defaultExport from "./module"; - export const value = defaultExport(); - `, - "module.ts": ` + const mainCode = ` + import defaultExport from "./module"; + export const value = defaultExport(); + `; + util.testModule(mainCode) + .addExtraFile( + "module.ts", + ` export default function() { return true; } - `, - }, - "value" - ); - - expect(result).toBe(true); + ` + ) + .expectToMatchJsResult(); }); test("Export Equals", () => { - const [result] = util.transpileAndExecuteProjectReturningMainExport( - { - "main.ts": ` - import * as module from "./module"; - export const value = module; - `, - "module.ts": ` - export = true; - `, - }, - "value" - ); - - expect(result).toBe(true); + util.testModule` + export = true; + ` + .setOptions({ module: ts.ModuleKind.CommonJS }) + .expectToMatchJsResult(); }); const reassignmentTestCases = [ @@ -282,25 +258,26 @@ export const foo = "bar"; `; test("export all does not include default", () => { - util.testBundle` + util.testModule` export * from "./module"; ` + .setOptions({ module: ts.ModuleKind.CommonJS }) .addExtraFile("module.ts", moduleFile) - .expectToEqual({ foo: "bar" }); + .expectToMatchJsResult(); }); test("namespace export does not include default", () => { - util.testBundle` + util.testModule` export * as result from "./module"; ` .addExtraFile("module.ts", moduleFile) - .expectToEqual({ result: { default: true, foo: "bar" } }); + .expectToMatchJsResult(); }); test("namespace export with unsafe Lua name", () => { - util.testBundle` + util.testModule` export * as $$$ from "./module"; ` .addExtraFile("module.ts", moduleFile) - .expectToEqual({ $$$: { default: true, foo: "bar" } }); + .expectToMatchJsResult(); }); diff --git a/test/unit/namespaces.spec.ts b/test/unit/namespaces.spec.ts index 159fb8534..398497330 100644 --- a/test/unit/namespaces.spec.ts +++ b/test/unit/namespaces.spec.ts @@ -94,7 +94,7 @@ test("namespace merging across files", () => { } `; - util.testBundle` + util.testModule` import './a'; import './b'; @@ -102,7 +102,7 @@ test("namespace merging across files", () => { ` .addExtraFile("a.ts", a) .addExtraFile("b.ts", b) - .expectToEqual({ result: { foo: "foo", bar: "bar" } }); + .expectToMatchJsResult(); }); test("declared namespace function call", () => { diff --git a/test/unit/printer/deadCodeAfterReturn.spec.ts b/test/unit/printer/deadCodeAfterReturn.spec.ts index 0ccead50e..fba16387a 100644 --- a/test/unit/printer/deadCodeAfterReturn.spec.ts +++ b/test/unit/printer/deadCodeAfterReturn.spec.ts @@ -37,25 +37,25 @@ test("Method dead code after return", () => { }); test("for dead code after return", () => { - const result = util.transpileAndExecute("for (let i = 0; i < 10; i++) { return 3; const b = 8; }"); - - expect(result).toBe(3); + util.testFunction` + for (let i = 0; i < 10; i++) { return 3; const b = 8; } + `.expectToMatchJsResult(); }); test("for..in dead code after return", () => { - const result = util.transpileAndExecute('for (let a in {"a": 5, "b": 8}) { return 3; const b = 8; }'); - - expect(result).toBe(3); + util.testFunction` + for (let a in {"a": 5, "b": 8}) { return 3; const b = 8; } + `.expectToMatchJsResult(); }); test("for..of dead code after return", () => { - const result = util.transpileAndExecute("for (let a of [1,2,4]) { return 3; const b = 8; }"); - - expect(result).toBe(3); + util.testFunction` + for (let a of [1,2,4]) { return 3; const b = 8; } + `.expectToMatchJsResult(); }); test("while dead code after return", () => { - const result = util.transpileAndExecute("while (true) { return 3; const b = 8; }"); - - expect(result).toBe(3); + util.testFunction` + while (true) { return 3; const b = 8; } + `.expectToMatchJsResult(); }); From 248d36c239d9209b9ed3fa6a5e3bbc784562e796 Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Sat, 9 Jan 2021 13:11:44 +0100 Subject: [PATCH 05/10] Updated remaining test cases that used legacy utils --- test/transpile/bundle.spec.ts | 3 +- test/unit/annotations/luaIterator.spec.ts | 54 +++------ test/unit/annotations/luaTable.spec.ts | 7 +- test/unit/annotations/tupleReturn.spec.ts | 24 +--- test/unit/builtins/weakMap.spec.ts | 66 ++++------- test/unit/builtins/weakSet.spec.ts | 4 +- test/unit/functions/functions.spec.ts | 3 +- test/unit/hoisting.spec.ts | 131 ++++++++++------------ test/unit/namespaces.spec.ts | 11 +- test/unit/overloads.spec.ts | 60 ++++------ test/util.ts | 46 +++++--- 11 files changed, 175 insertions(+), 234 deletions(-) diff --git a/test/transpile/bundle.spec.ts b/test/transpile/bundle.spec.ts index 21e9f153a..4676d7fb8 100644 --- a/test/transpile/bundle.spec.ts +++ b/test/transpile/bundle.spec.ts @@ -15,5 +15,6 @@ test("should transpile into one file", () => { // Verify the name is as specified in tsconfig expect(name).toBe("bundle/bundle.lua"); // Verify exported module by executing - expect(util.executeLuaModule(text)).toEqual({ myNumber: 3 }); + // TODO this is a bit hacky + util.testModule("").setLuaHeader(text).expectToEqual({ myNumber: 3 }); }); diff --git a/test/unit/annotations/luaIterator.spec.ts b/test/unit/annotations/luaIterator.spec.ts index 46e324bc5..b1efc62ca 100644 --- a/test/unit/annotations/luaIterator.spec.ts +++ b/test/unit/annotations/luaIterator.spec.ts @@ -2,7 +2,7 @@ import * as util from "../../util"; import { luaIteratorForbiddenUsage } from "../../../src/transformation/utils/diagnostics"; test("forof lua iterator", () => { - const code = ` + util.testFunction` const arr = ["a", "b", "c"]; /** @luaIterator */ interface Iter extends Iterable {} @@ -13,13 +13,11 @@ test("forof lua iterator", () => { let result = ""; for (let e of luaIter()) { result += e; } return result; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("abc"); + `.expectToEqual("abc"); }); test("forof array lua iterator", () => { - const code = ` + util.testFunction` const arr = ["a", "b", "c"]; /** @luaIterator */ interface Iter extends Array {} @@ -30,13 +28,11 @@ test("forof array lua iterator", () => { let result = ""; for (let e of luaIter()) { result += e; } return result; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("abc"); + `.expectToEqual("abc"); }); test("forof lua iterator with existing variable", () => { - const code = ` + util.testFunction` const arr = ["a", "b", "c"]; /** @luaIterator */ interface Iter extends Iterable {} @@ -48,13 +44,11 @@ test("forof lua iterator with existing variable", () => { let e: string; for (e of luaIter()) { result += e; } return result; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("abc"); + `.expectToEqual("abc"); }); test("forof lua iterator destructuring", () => { - const code = ` + util.testFunction` const arr = ["a", "b", "c"]; /** @luaIterator */ interface Iter extends Iterable<[string, string]> {} @@ -65,13 +59,11 @@ test("forof lua iterator destructuring", () => { let result = ""; for (let [a, b] of luaIter()) { result += a + b; } return result; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("0a1b2c"); + `.expectToEqual("0a1b2c"); }); test("forof lua iterator destructuring with existing variables", () => { - const code = ` + util.testFunction` const arr = ["a", "b", "c"]; /** @luaIterator */ interface Iter extends Iterable<[string, string]> {} @@ -84,13 +76,11 @@ test("forof lua iterator destructuring with existing variables", () => { let b: string; for ([a, b] of luaIter()) { result += a + b; } return result; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("0a1b2c"); + `.expectToEqual("0a1b2c"); }); test("forof lua iterator tuple-return", () => { - const code = ` + util.testFunction` const arr = ["a", "b", "c"]; /** * @luaIterator @@ -106,13 +96,11 @@ test("forof lua iterator tuple-return", () => { let result = ""; for (let [a, b] of luaIter()) { result += a + b; } return result; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("0a1b2c"); + `.expectToEqual("0a1b2c"); }); test("forof lua iterator tuple-return with existing variables", () => { - const code = ` + util.testFunction` const arr = ["a", "b", "c"]; /** * @luaIterator @@ -130,9 +118,7 @@ test("forof lua iterator tuple-return with existing variables", () => { let b: string; for ([a, b] of luaIter()) { result += a + b; } return result; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("0a1b2c"); + `.expectToEqual("0a1b2c"); }); test("forof lua iterator tuple-return single variable", () => { @@ -161,7 +147,7 @@ test("forof lua iterator tuple-return single existing variable", () => { }); test("forof forwarded lua iterator", () => { - const code = ` + util.testFunction` const arr = ["a", "b", "c"]; /** @luaIterator */ interface Iter extends Iterable {} @@ -177,13 +163,11 @@ test("forof forwarded lua iterator", () => { let result = ""; for (let a of forward()) { result += a; } return result; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("abc"); + `.expectToEqual("abc"); }); test("forof forwarded lua iterator with tupleReturn", () => { - const code = ` + util.testFunction` const arr = ["a", "b", "c"]; /** * @luaIterator @@ -203,7 +187,5 @@ test("forof forwarded lua iterator with tupleReturn", () => { let result = ""; for (let [a, b] of forward()) { result += a + b; } return result; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("0a1b2c"); + `.expectToEqual("0a1b2c"); }); diff --git a/test/unit/annotations/luaTable.spec.ts b/test/unit/annotations/luaTable.spec.ts index 8829938bb..41739c4c0 100644 --- a/test/unit/annotations/luaTable.spec.ts +++ b/test/unit/annotations/luaTable.spec.ts @@ -59,13 +59,12 @@ test.each([tableLibClass, tableLibInterface])("LuaTables cannot have other membe test.each([tableLibClass])("LuaTable new", tableLib => { const content = tableLib + "tbl = new Table();"; - expect(util.transpileString(content)).toEqual("tbl = {}"); + expect(util.testFunction(content).getMainLuaCodeChunk()).toContain("tbl = {}"); }); test.each([tableLibClass])("LuaTable length", tableLib => { const content = tableLib + "tbl = new Table();\nreturn tbl.length;"; - const lua = util.transpileString(content); - expect(util.executeLua(lua)).toEqual(0); + expect(util.testFunction(content).getLuaExecutionResult()).toBe(0); }); test.each([tableLibClass, tableLibInterface])("Cannot set LuaTable length", tableLib => { @@ -136,6 +135,6 @@ test.each([tableLibClass])("LuaTable functional tests", tableLib => { ["const t = new Table(); t.set(t.length + 1, true); t.set(t.length + 1, true); return t.length", 2], ['const k = "k"; const t = { data: new Table() }; t.data.set(k, 3); return t.data.get(k);', 3], ])("LuaTable test (%p)", (code, expectedReturnValue) => { - expect(util.transpileAndExecute(code, undefined, undefined, tableLib)).toBe(expectedReturnValue); + expect(util.testFunction(code).setTsHeader(tableLib).getLuaExecutionResult()).toBe(expectedReturnValue); }); }); diff --git a/test/unit/annotations/tupleReturn.spec.ts b/test/unit/annotations/tupleReturn.spec.ts index 7c7c7ae9b..337e52c37 100644 --- a/test/unit/annotations/tupleReturn.spec.ts +++ b/test/unit/annotations/tupleReturn.spec.ts @@ -405,39 +405,27 @@ test("TupleReturn method assignment", () => { }); test("TupleReturn functional", () => { - const code = ` + util.testFunction` /** @tupleReturn */ function abc(): [number, string] { return [3, "a"]; } const [a, b] = abc(); return b + a; - `; - - const result = util.transpileAndExecute(code); - - expect(result).toBe("a3"); + `.expectToMatchJsResult(); }); test("TupleReturn single", () => { - const code = ` + util.testFunction` /** @tupleReturn */ function abc(): [number, string] { return [3, "a"]; } const res = abc(); return res.length - `; - - const result = util.transpileAndExecute(code); - - expect(result).toBe(2); + `.expectToMatchJsResult(); }); test("TupleReturn in expression", () => { - const code = ` + util.testFunction` /** @tupleReturn */ function abc(): [number, string] { return [3, "a"]; } return abc()[1] + abc()[0]; - `; - - const result = util.transpileAndExecute(code); - - expect(result).toBe("a3"); + `.expectToMatchJsResult(); }); diff --git a/test/unit/builtins/weakMap.spec.ts b/test/unit/builtins/weakMap.spec.ts index ab3ba12fb..74ab211a1 100644 --- a/test/unit/builtins/weakMap.spec.ts +++ b/test/unit/builtins/weakMap.spec.ts @@ -6,94 +6,76 @@ const initRefsTs = ` `; test("weakMap constructor", () => { - const result = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let mymap = new WeakMap([[ref, 1]]); return mymap.get(ref); - `); - - expect(result).toBe(1); + `.expectToMatchJsResult(); }); test("weakMap iterable constructor", () => { - const result = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let mymap = new WeakMap([[ref, 1], [ref2, 2]]); return mymap.has(ref) && mymap.has(ref2); - `); - - expect(result).toBe(true); + `.expectToMatchJsResult(); }); test("weakMap iterable constructor map", () => { - const result = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let mymap = new WeakMap(new Map([[ref, 1], [ref2, 2]])); return mymap.has(ref) && mymap.has(ref2); - `); - - expect(result).toBe(true); + `.expectToMatchJsResult(); }); test("weakMap delete", () => { - const contains = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let mymap = new WeakMap([[ref, true], [ref2, true]]); mymap.delete(ref2); return mymap.has(ref) && !mymap.has(ref2); - `); - - expect(contains).toBe(true); + `.expectToMatchJsResult(); }); test("weakMap get", () => { - const result = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let mymap = new WeakMap([[ref, 1], [{}, 2]]); return mymap.get(ref); - `); - - expect(result).toBe(1); + `.expectToMatchJsResult(); }); test("weakMap get missing", () => { - const result = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let mymap = new WeakMap([[{}, true]]); return mymap.get({}); - `); - - expect(result).toBeUndefined(); + `.expectToMatchJsResult(); }); test("weakMap has", () => { - const contains = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let mymap = new WeakMap([[ref, true]]); return mymap.has(ref); - `); - - expect(contains).toBe(true); + `.expectToMatchJsResult(); }); test("weakMap has false", () => { - const contains = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let mymap = new WeakMap([[ref, true]]); return mymap.has(ref2); - `); - - expect(contains).toBe(false); + `.expectToMatchJsResult(); }); test("weakMap has null", () => { - const contains = util.transpileAndExecute(` + util.testFunction` ${initRefsTs} let mymap = new WeakMap([[{}, true]]); return mymap.has(null); - `); - - expect(contains).toBe(false); + `.expectToMatchJsResult(); }); test("weakMap set", () => { @@ -103,20 +85,20 @@ test("weakMap set", () => { mymap.set(ref, 5); `; - const has = util.transpileAndExecute(init + "return mymap.has(ref);"); - expect(has).toBe(true); + util.testFunction(init + "return mymap.has(ref);").expectToMatchJsResult(); - const value = util.transpileAndExecute(init + "return mymap.get(ref)"); - expect(value).toBe(5); + util.testFunction(init + "return mymap.get(ref)").expectToMatchJsResult(); }); test("weakMap has no map features (size)", () => { - expect(util.transpileAndExecute("return (new WeakMap() as any).size")).toBeUndefined(); + util.testFunction("return (new WeakMap() as any).size").expectToMatchJsResult(); }); test.each(["clear()", "keys()", "values()", "entries()", "forEach(() => {})"])( "weakMap has no map features (%p)", call => { - expect(() => util.transpileAndExecute(`(new WeakMap() as any).${call}`)).toThrow(); + const testBuilder = util.testFunction(`(new WeakMap() as any).${call}`); + const luaResult = testBuilder.getLuaExecutionResult(); + expect(luaResult.message).toContain("attempt to call a nil value"); } ); diff --git a/test/unit/builtins/weakSet.spec.ts b/test/unit/builtins/weakSet.spec.ts index 9d779f2f5..ef082f567 100644 --- a/test/unit/builtins/weakSet.spec.ts +++ b/test/unit/builtins/weakSet.spec.ts @@ -63,6 +63,8 @@ test("weakSet has no set features (size)", () => { test.each(["clear()", "keys()", "values()", "entries()", "forEach(() => {})"])( "weakSet has no set features (%p)", call => { - expect(() => util.transpileAndExecute(`(new WeakSet() as any).${call}`)).toThrow(); + const testBuilder = util.testFunction(`(new WeakSet() as any).${call}`); + const luaResult = testBuilder.getLuaExecutionResult(); + expect(luaResult.message).toContain("attempt to call a nil value"); } ); diff --git a/test/unit/functions/functions.spec.ts b/test/unit/functions/functions.spec.ts index 056d62472..02ed4653e 100644 --- a/test/unit/functions/functions.spec.ts +++ b/test/unit/functions/functions.spec.ts @@ -469,10 +469,11 @@ test("missing declaration name", () => { }); test("top-level function declaration is global", () => { + // TODO cant be tested with expectToMatchJsResult because in JS that would not be global util.testModule` import './a'; export const result = foo(); ` .addExtraFile("a.ts", 'function foo() { return "foo" }') - .expectToMatchJsResult(); + .expectToEqual({ result: "foo" }); }); diff --git a/test/unit/hoisting.spec.ts b/test/unit/hoisting.spec.ts index eafe74348..bc505ebdc 100644 --- a/test/unit/hoisting.spec.ts +++ b/test/unit/hoisting.spec.ts @@ -2,110 +2,104 @@ import * as ts from "typescript"; import * as util from "../util"; test.each(["let", "const"])("Let/Const Hoisting (%p)", varType => { - const code = ` + util.testFunction` let bar: string; function setBar() { bar = foo; } ${varType} foo = "foo"; setBar(); return foo; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("foo"); + `.expectToMatchJsResult(); }); test.each(["let", "const"])("Exported Let/Const Hoisting (%p)", varType => { - const code = ` + util.testModule` let bar: string; function setBar() { bar = foo; } export ${varType} foo = "foo"; setBar(); - `; - const result = util.transpileExecuteAndReturnExport(code, "foo"); - expect(result).toBe("foo"); + `.expectToMatchJsResult(); }); test("Global Function Hoisting", () => { - const code = ` + util.testFunction` const foo = bar(); function bar() { return "bar"; } return foo; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("bar"); + `.expectToMatchJsResult(); }); test("Local Function Hoisting", () => { - const code = ` + util.testModule` export const foo = bar(); function bar() { return "bar"; } - `; - const result = util.transpileExecuteAndReturnExport(code, "foo"); - expect(result).toBe("bar"); + `.expectToMatchJsResult(); }); test("Exported Function Hoisting", () => { - const code = ` + util.testModule` const foo = bar(); export function bar() { return "bar"; } export const baz = foo; - `; - const result = util.transpileExecuteAndReturnExport(code, "baz"); - expect(result).toBe("bar"); + ` + .debug() + .expectToMatchJsResult(); }); test("Namespace Function Hoisting", () => { - const code = ` - let foo: string; - namespace NS { - foo = bar(); - function bar() { return "bar"; } - } - `; - const result = util.transpileAndExecute("return foo;", undefined, undefined, code); - expect(result).toBe("bar"); + util.testFunction` + return foo; + ` + .setTsHeader( + ` + let foo: string; + namespace NS { + foo = bar(); + function bar() { return "bar"; } + } + ` + ) + .expectToMatchJsResult(); }); test("Exported Namespace Function Hoisting", () => { - const code = ` - let foo: string; - namespace NS { - foo = bar(); - export function bar() { return "bar"; } - } - `; - const result = util.transpileAndExecute("return foo;", undefined, undefined, code); - expect(result).toBe("bar"); + util.testFunction("return foo;") + .setTsHeader( + ` + let foo: string; + namespace NS { + foo = bar(); + export function bar() { return "bar"; } + } + ` + ) + .expectToMatchJsResult(); }); test.each([ { varType: "let", expectResult: "bar" }, { varType: "const", expectResult: "bar" }, ])("Hoisting in Non-Function Scope (%p)", ({ varType, expectResult }) => { - const code = ` - function foo() { - ${varType} bar = "bar"; - for (let i = 0; i < 1; ++i) { - ${varType} bar = "foo"; - } - return bar; + util.testFunction` + function foo() { + ${varType} bar = "bar"; + for (let i = 0; i < 1; ++i) { + ${varType} bar = "foo"; } - return foo(); - `; - const result = util.transpileAndExecute(code); - expect(result).toBe(expectResult); + return bar; + } + return foo(); + `.expectToMatchJsResult(); }); test("Hoisting due to reference from hoisted function", () => { - const code = ` + util.testFunction` const foo = "foo"; const result = bar(); function bar() { return foo; } return result; - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("foo"); + `.expectToMatchJsResult(); }); test("Hoisting with synthetic source file node", () => { @@ -131,7 +125,7 @@ test("Hoisting with synthetic source file node", () => { }); test("Namespace Hoisting", () => { - const code = ` + util.testModule` function bar() { return NS.foo; } @@ -139,13 +133,11 @@ test("Namespace Hoisting", () => { export let foo = "foo"; } export const foo = bar(); - `; - const result = util.transpileExecuteAndReturnExport(code, "foo"); - expect(result).toBe("foo"); + `.expectToMatchJsResult(); }); test("Exported Namespace Hoisting", () => { - const code = ` + util.testModule` function bar() { return NS.foo; } @@ -153,9 +145,7 @@ test("Exported Namespace Hoisting", () => { export let foo = "foo"; } export const foo = bar(); - `; - const result = util.transpileExecuteAndReturnExport(code, "foo"); - expect(result).toBe("foo"); + `.expectToMatchJsResult(); }); test("Nested Namespace Hoisting", () => { @@ -176,7 +166,7 @@ test("Nested Namespace Hoisting", () => { }); test("Class Hoisting", () => { - const code = ` + util.testModule` function makeFoo() { return new Foo(); } @@ -184,13 +174,11 @@ test("Class Hoisting", () => { public bar = "foo"; } export const foo = makeFoo().bar; - `; - const result = util.transpileExecuteAndReturnExport(code, "foo"); - expect(result).toBe("foo"); + `.expectToMatchJsResult(); }); test("Enum Hoisting", () => { - const code = ` + util.testModule` function bar() { return E.A; } @@ -198,9 +186,7 @@ test("Enum Hoisting", () => { A = "foo" } export const foo = bar(); - `; - const result = util.transpileExecuteAndReturnExport(code, "foo"); - expect(result).toBe("foo"); + `.expectToHaveNoDiagnostics(); }); test("Import hoisting (named)", () => { @@ -214,8 +200,8 @@ test("Import hoisting (named)", () => { test("Import hoisting (namespace)", () => { util.testModule` - export const result = module.foo; - import * as module from "./module"; + export const result = m.foo; + import * as m from "./module"; ` .addExtraFile("module.ts", "export const foo = true;") .expectToMatchJsResult(); @@ -231,6 +217,7 @@ test("Import hoisting (side-effect)", () => { }); test("Import hoisted before function", () => { + // TODO Cant use expectToMatchJsResult because above is not valid TS/JS util.testModule` export let result: any; @@ -242,7 +229,7 @@ test("Import hoisted before function", () => { import { foo } from "./module"; ` .addExtraFile("module.ts", "export const foo = true;") - .expectToMatchJsResult(); + .expectToEqual({ result: true }); }); test("Hoisting Shorthand Property", () => { diff --git a/test/unit/namespaces.spec.ts b/test/unit/namespaces.spec.ts index 398497330..762152d67 100644 --- a/test/unit/namespaces.spec.ts +++ b/test/unit/namespaces.spec.ts @@ -11,14 +11,9 @@ test("legacy internal module syntax", () => { }); test("global scoping", () => { - const result = util.transpileAndExecute( - "return a.foo();", - undefined, - undefined, - 'namespace a { export function foo() { return "bar"; } }' - ); - - expect(result).toBe("bar"); + util.testFunction("return a.foo();") + .setTsHeader('namespace a { export function foo() { return "bar"; } }') + .expectToMatchJsResult(); }); test("nested namespace", () => { diff --git a/test/unit/overloads.spec.ts b/test/unit/overloads.spec.ts index f911eeb2f..d18daa000 100644 --- a/test/unit/overloads.spec.ts +++ b/test/unit/overloads.spec.ts @@ -1,8 +1,8 @@ import * as util from "../util"; test("overload function1", () => { - const result = util.transpileAndExecute( - `function abc(def: number): string; + util.testFunction` + function abc(def: number): string; function abc(def: string): string; function abc(def: number | string): string { if (typeof def == "number") { @@ -11,15 +11,13 @@ test("overload function1", () => { return def; } } - return abc(3);` - ); - - expect(result).toBe("jkl9"); + return abc(3); + `.expectToMatchJsResult(); }); test("overload function2", () => { - const result = util.transpileAndExecute( - `function abc(def: number): string; + util.testFunction` + function abc(def: number): string; function abc(def: string): string; function abc(def: number | string): string { if (typeof def == "number") { @@ -28,15 +26,13 @@ test("overload function2", () => { return def; } } - return abc("ghj");` - ); - - expect(result).toBe("ghj"); + return abc("ghj"); + `.expectToMatchJsResult(); }); test("overload method1", () => { - const result = util.transpileAndExecute( - `class myclass { + util.testFunction` + class myclass { static abc(def: number): string; static abc(def: string): string; static abc(def: number | string): string { @@ -47,15 +43,13 @@ test("overload method1", () => { } } } - return myclass.abc(3);` - ); - - expect(result).toBe("jkl9"); + return myclass.abc(3); + `.expectToMatchJsResult(); }); test("overload method2", () => { - const result = util.transpileAndExecute( - `class myclass { + util.testFunction` + class myclass { static abc(def: number): string; static abc(def: string): string; static abc(def: number | string): string { @@ -66,15 +60,13 @@ test("overload method2", () => { } } } - return myclass.abc("ghj");` - ); - - expect(result).toBe("ghj"); + return myclass.abc("ghj"); + `.expectToMatchJsResult(); }); test("constructor1", () => { - const result = util.transpileAndExecute( - `class myclass { + util.testFunction` + class myclass { num: number; str: string; @@ -89,15 +81,13 @@ test("constructor1", () => { } } const inst = new myclass(3); - return inst.num` - ); - - expect(result).toBe(3); + return inst.num; + `.expectToMatchJsResult(); }); test("constructor2", () => { - const result = util.transpileAndExecute( - `class myclass { + util.testFunction` + class myclass { num: number; str: string; @@ -112,8 +102,6 @@ test("constructor2", () => { } } const inst = new myclass("ghj"); - return inst.str` - ); - - expect(result).toBe("ghj"); + return inst.str + `.expectToMatchJsResult(); }); diff --git a/test/util.ts b/test/util.ts index 709b8e4cb..343cbdac0 100644 --- a/test/util.ts +++ b/test/util.ts @@ -276,9 +276,19 @@ export abstract class TestBuilder { return this; } + private expectNoJsExecutionError(): this { + const jsResult = this.getJsExecutionResult(); + if (jsResult instanceof ExecutionError) { + throw jsResult; + } + + return this; + } + public expectToMatchJsResult(allowErrors = false): this { this.expectToHaveNoDiagnostics(); if (!allowErrors) this.expectNoExecutionError(); + if (!allowErrors) this.expectNoJsExecutionError(); const luaResult = this.getLuaExecutionResult(); const jsResult = this.getJsExecutionResult(); @@ -380,41 +390,47 @@ export abstract class TestBuilder { } else { // Filter out control characters appearing on some systems const luaStackString = lua.lua_tostring(L, -1).filter(c => c >= 20); - const message = to_jsstring(luaStackString).replace(/^\[string "--\.\.\."\]:\d+: /, ""); + const message = to_jsstring(luaStackString).replace(/^\[string "(--)?\.\.\."\]:\d+: /, ""); return new ExecutionError(message); } } private executeJs(): any { const { transpiledFiles } = this.getJsResult(); - // Custom require for extra files. Really basic, does not handle globals currently - // and probably a lot of other details. + // Custom require for extra files. Really basic. Global support is hacky // TODO Should be replace with vm.Module https://nodejs.org/api/vm.html#vm_class_vm_module // once stable - const requireFromExtraFile = (fileName: string) => { + const globalContext: any = {}; + const mainExports = {}; + globalContext.exports = mainExports; + globalContext.module = { exports: mainExports }; + globalContext.require = (fileName: string) => { + // create clean export object for "module" const moduleExports = {}; - const moduleContext = vm.createContext({ exports: moduleExports, module: { exports: moduleExports } }); + globalContext.exports = moduleExports; + globalContext.module = { exports: moduleExports }; const transpiledExtraFile = transpiledFiles.find(({ sourceFiles }) => sourceFiles.some(f => f.fileName === fileName.replace("./", "") + ".ts") ); if (transpiledExtraFile?.js) { - vm.runInContext(transpiledExtraFile.js, moduleContext); + vm.runInContext(transpiledExtraFile.js, globalContext); } - return moduleContext.module.exports; + // Have to return globalContext.module.exports + // becuase module.exports might no longer be equal to moduleExports (export assignment) + const result = globalContext.module.exports; + // Reset module/export + globalContext.exports = mainExports; + globalContext.module = { exports: mainExports }; + return result; }; - const mainExports = {}; - const mainContext = vm.createContext({ - exports: mainExports, - module: { exports: mainExports }, - require: requireFromExtraFile, - }); - mainContext.global = mainContext; + vm.createContext(globalContext); + let result: unknown; try { - result = vm.runInContext(this.getJsCodeWithWrapper(), mainContext); + result = vm.runInContext(this.getJsCodeWithWrapper(), globalContext); } catch (error) { return new ExecutionError(error.message); } From 19f39f8a5e6acd9d4dca6398cc262c4280224836 Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Sat, 9 Jan 2021 13:16:43 +0100 Subject: [PATCH 06/10] Fixed unused variable --- test/unit/hoisting.spec.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/unit/hoisting.spec.ts b/test/unit/hoisting.spec.ts index bc505ebdc..9265ca3f0 100644 --- a/test/unit/hoisting.spec.ts +++ b/test/unit/hoisting.spec.ts @@ -75,10 +75,7 @@ test("Exported Namespace Function Hoisting", () => { .expectToMatchJsResult(); }); -test.each([ - { varType: "let", expectResult: "bar" }, - { varType: "const", expectResult: "bar" }, -])("Hoisting in Non-Function Scope (%p)", ({ varType, expectResult }) => { +test.each(["let", "const"])("Hoisting in Non-Function Scope (%p)", (varType) => { util.testFunction` function foo() { ${varType} bar = "bar"; From 08478487307cf1821247f2164097f178eb033ffe Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Sat, 9 Jan 2021 13:19:48 +0100 Subject: [PATCH 07/10] Fix prettier --- test/unit/hoisting.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/hoisting.spec.ts b/test/unit/hoisting.spec.ts index 9265ca3f0..c8f111665 100644 --- a/test/unit/hoisting.spec.ts +++ b/test/unit/hoisting.spec.ts @@ -75,7 +75,7 @@ test("Exported Namespace Function Hoisting", () => { .expectToMatchJsResult(); }); -test.each(["let", "const"])("Hoisting in Non-Function Scope (%p)", (varType) => { +test.each(["let", "const"])("Hoisting in Non-Function Scope (%p)", varType => { util.testFunction` function foo() { ${varType} bar = "bar"; From d168380b53e3f68b5fb10bc0cb4e60bbe2b73b43 Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Tue, 26 Jan 2021 23:28:54 +0100 Subject: [PATCH 08/10] Fixed remaining tests Fixed function assignment test according to discord discussion (https://discord.com/channels/515854149821267971/600291243523702805/800403280512417792) Fixed hoisting by hardcoding the expected value, because the current TestBuilder.executeJS() setup does not handle module hoisting --- .../validation/validFunctionAssignments.spec.ts | 2 +- test/unit/hoisting.spec.ts | 17 +++++++++++++---- test/util.ts | 10 +++++----- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/test/unit/functions/validation/validFunctionAssignments.spec.ts b/test/unit/functions/validation/validFunctionAssignments.spec.ts index 7cc79daf6..80ed83238 100644 --- a/test/unit/functions/validation/validFunctionAssignments.spec.ts +++ b/test/unit/functions/validation/validFunctionAssignments.spec.ts @@ -109,7 +109,7 @@ test.each([ return takesFunction(${testFunction.value}, ${args.join(", ")}); ` .setTsHeader(testFunction.definition) - .expectToMatchJsResult(); + .expectToEqual("foobar"); }); test.each(validTestFunctionAssignments)("Valid function return (%p)", (testFunction, functionType) => { diff --git a/test/unit/hoisting.spec.ts b/test/unit/hoisting.spec.ts index c8f111665..a682a5a24 100644 --- a/test/unit/hoisting.spec.ts +++ b/test/unit/hoisting.spec.ts @@ -41,7 +41,7 @@ test("Exported Function Hoisting", () => { export function bar() { return "bar"; } export const baz = foo; ` - .debug() + .setReturnExport("baz") .expectToMatchJsResult(); }); @@ -187,30 +187,39 @@ test("Enum Hoisting", () => { }); test("Import hoisting (named)", () => { + // TODO cant be tested with expectToEqualJSResult because of + // the scuffed module setup in TestBuilder.executeJs (module hoisting is not possible) + // should be updated once vm.module becomes stable util.testModule` export const result = foo; import { foo } from "./module"; ` .addExtraFile("module.ts", "export const foo = true;") - .expectToMatchJsResult(); + .expectToEqual({ result: true }); }); test("Import hoisting (namespace)", () => { + // TODO cant be tested with expectToEqualJSresult because of + // the scuffed module setup in TestBuilder.executeJs (module hoisting is not possible) + // should be updated once vm.module becomes stable util.testModule` export const result = m.foo; import * as m from "./module"; ` .addExtraFile("module.ts", "export const foo = true;") - .expectToMatchJsResult(); + .expectToEqual({ result: true }); }); test("Import hoisting (side-effect)", () => { + // TODO cant be tested with expectToEqualJSResult because of + // the scuffed module setup in TestBuilder.executeJs (module hoisting is not possible) + // should be updated once vm.module becomes stable util.testModule` export const result = (globalThis as any).result; import "./module"; ` .addExtraFile("module.ts", "(globalThis as any).result = true; export {};") - .expectToMatchJsResult(); + .expectToEqual({ result: true }); }); test("Import hoisted before function", () => { diff --git a/test/util.ts b/test/util.ts index 343cbdac0..b65360c2d 100644 --- a/test/util.ts +++ b/test/util.ts @@ -372,10 +372,10 @@ export abstract class TestBuilder { // Execute Main const wrappedMainCode = ` - local JSON = require("json"); - return JSON.stringify((function() - ${this.getLuaCodeWithWrapper(mainFile)} - end)());`; +local JSON = require("json"); +return JSON.stringify((function() + ${this.getLuaCodeWithWrapper(mainFile)} +end)());`; const status = lauxlib.luaL_dostring(L, to_luastring(wrappedMainCode)); @@ -398,7 +398,7 @@ export abstract class TestBuilder { private executeJs(): any { const { transpiledFiles } = this.getJsResult(); // Custom require for extra files. Really basic. Global support is hacky - // TODO Should be replace with vm.Module https://nodejs.org/api/vm.html#vm_class_vm_module + // TODO Should be replaced with vm.Module https://nodejs.org/api/vm.html#vm_class_vm_module // once stable const globalContext: any = {}; const mainExports = {}; From 109875a930c1134fa904785f25a4c0fe4090bbda Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Thu, 4 Feb 2021 16:51:38 +0100 Subject: [PATCH 09/10] Addressed review comments --- test/transpile/bundle.spec.ts | 2 +- .../annotations/customConstructor.spec.ts | 9 ++-- test/unit/annotations/luaTable.spec.ts | 51 +++++++++++-------- test/unit/annotations/metaExtension.spec.ts | 8 +-- test/unit/builtins/map.spec.ts | 4 +- test/unit/builtins/string.spec.ts | 4 +- test/unit/builtins/weakMap.spec.ts | 2 +- test/unit/builtins/weakSet.spec.ts | 2 +- test/unit/expressions.spec.ts | 5 +- test/unit/functions/functions.spec.ts | 2 +- .../functionExpressionTypeInference.spec.ts | 11 ++-- .../validFunctionAssignments.spec.ts | 18 +++---- test/unit/hoisting.spec.ts | 2 +- test/unit/identifiers.spec.ts | 18 +++---- test/unit/loops.spec.ts | 4 +- test/util.ts | 4 +- 16 files changed, 77 insertions(+), 69 deletions(-) diff --git a/test/transpile/bundle.spec.ts b/test/transpile/bundle.spec.ts index 4676d7fb8..0ccab7c70 100644 --- a/test/transpile/bundle.spec.ts +++ b/test/transpile/bundle.spec.ts @@ -15,6 +15,6 @@ test("should transpile into one file", () => { // Verify the name is as specified in tsconfig expect(name).toBe("bundle/bundle.lua"); // Verify exported module by executing - // TODO this is a bit hacky + // Use an empty TS string because we already transpiled the TS project util.testModule("").setLuaHeader(text).expectToEqual({ myNumber: 3 }); }); diff --git a/test/unit/annotations/customConstructor.spec.ts b/test/unit/annotations/customConstructor.spec.ts index f832839b5..86858f700 100644 --- a/test/unit/annotations/customConstructor.spec.ts +++ b/test/unit/annotations/customConstructor.spec.ts @@ -19,13 +19,12 @@ test("CustomCreate", () => { } `; - // TODO Cant use expectToMatchJsResult because above is not valid TS/JS - const luaResult = util.testModule`export default new Point2D(1, 2).x;` + // Can't use expectToMatchJsResult because above is not valid TS/JS + util.testModule`export default new Point2D(1, 2).x;` .setTsHeader(tsHeader) .setLuaHeader(luaHeader) - .getLuaExecutionResult(); - - expect(luaResult.default).toBe(1); + .setReturnExport("default") + .expectToEqual(1); }); test("IncorrectUsage", () => { diff --git a/test/unit/annotations/luaTable.spec.ts b/test/unit/annotations/luaTable.spec.ts index 41739c4c0..0f9cbbf36 100644 --- a/test/unit/annotations/luaTable.spec.ts +++ b/test/unit/annotations/luaTable.spec.ts @@ -35,40 +35,47 @@ declare let tbl: Table; `; test.each([tableLibClass])("LuaTables cannot be constructed with arguments", tableLib => { - util.testModule(tableLib + "const table = new Table(true);").expectDiagnosticsToMatchSnapshot([ - luaTableForbiddenUsage.code, - ]); + util.testModule("const table = new Table(true);") + .setTsHeader(tableLib) + .expectDiagnosticsToMatchSnapshot([luaTableForbiddenUsage.code]); }); test.each([tableLibClass, tableLibInterface])( "LuaTable set() cannot be used in a LuaTable call expression", tableLib => { - util.testModule(tableLib + 'const exp = tbl.set("value", 5)').expectDiagnosticsToMatchSnapshot([ - unsupportedProperty.code, - ]); + util.testModule('const exp = tbl.set("value", 5)') + .setTsHeader(tableLib) + .expectDiagnosticsToMatchSnapshot([unsupportedProperty.code]); } ); test.each([tableLibClass, tableLibInterface])("LuaTables cannot have other members", tableLib => { - util.testModule(tableLib + "tbl.other()").expectDiagnosticsToMatchSnapshot([unsupportedProperty.code]); + util.testModule("tbl.other()").setTsHeader(tableLib).expectDiagnosticsToMatchSnapshot([unsupportedProperty.code]); }); test.each([tableLibClass, tableLibInterface])("LuaTables cannot have other members", tableLib => { - util.testModule(tableLib + "let x = tbl.other()").expectDiagnosticsToMatchSnapshot([unsupportedProperty.code]); + util.testModule("let x = tbl.other()") + .setTsHeader(tableLib) + .expectDiagnosticsToMatchSnapshot([unsupportedProperty.code]); }); test.each([tableLibClass])("LuaTable new", tableLib => { - const content = tableLib + "tbl = new Table();"; - expect(util.testFunction(content).getMainLuaCodeChunk()).toContain("tbl = {}"); + expect(util.testFunction("tbl = new Table();").setTsHeader(tableLib).getMainLuaCodeChunk()).toContain("tbl = {}"); }); test.each([tableLibClass])("LuaTable length", tableLib => { - const content = tableLib + "tbl = new Table();\nreturn tbl.length;"; - expect(util.testFunction(content).getLuaExecutionResult()).toBe(0); + util.testFunction` + tbl = new Table(); + return tbl.length; + ` + .setTsHeader(tableLib) + .expectToEqual(0); }); test.each([tableLibClass, tableLibInterface])("Cannot set LuaTable length", tableLib => { - util.testModule(tableLib + "tbl.length = 2;").expectDiagnosticsToMatchSnapshot([luaTableForbiddenUsage.code]); + util.testModule("tbl.length = 2;") + .setTsHeader(tableLib) + .expectDiagnosticsToMatchSnapshot([luaTableForbiddenUsage.code]); }); test.each([tableLibClass, tableLibInterface])("Forbidden LuaTable use", tableLib => { @@ -81,7 +88,9 @@ test.each([tableLibClass, tableLibInterface])("Forbidden LuaTable use", tableLib 'tbl.set(...(["field", 0] as const))', 'tbl.set("field", ...([0] as const))', ])("Forbidden LuaTable use (%p)", invalidCode => { - util.testModule(tableLib + invalidCode).expectDiagnosticsToMatchSnapshot([luaTableForbiddenUsage.code]); + util.testModule(invalidCode) + .setTsHeader(tableLib) + .expectDiagnosticsToMatchSnapshot([luaTableForbiddenUsage.code]); }); }); @@ -89,7 +98,9 @@ test.each([tableLibClass])("Cannot extend LuaTable class", tableLib => { test.each(["class Ext extends Table {}", "const c = class Ext extends Table {}"])( "Cannot extend LuaTable class (%p)", code => { - util.testModule(tableLib + code).expectDiagnosticsToMatchSnapshot([luaTableCannotBeExtended.code]); + util.testModule(code) + .setTsHeader(tableLib) + .expectDiagnosticsToMatchSnapshot([luaTableCannotBeExtended.code]); } ); }); @@ -104,7 +115,7 @@ test.each([ test.each([tableLibClass])("Cannot extend LuaTable class", tableLib => { test.each(["tbl instanceof Table"])("Cannot use instanceof on a LuaTable class (%p)", code => { - util.testModule(tableLib + code).expectDiagnosticsToMatchSnapshot([luaTableInvalidInstanceOf.code]); + util.testModule(code).setTsHeader(tableLib).expectDiagnosticsToMatchSnapshot([luaTableInvalidInstanceOf.code]); }); }); @@ -112,9 +123,9 @@ test.each([tableLibClass, tableLibInterface])("Cannot use ElementAccessExpressio test.each(['tbl["get"]("field")', 'tbl["set"]("field")', 'tbl["length"]'])( "Cannot use ElementAccessExpression on a LuaTable (%p)", code => { - util.testModule(tableLib + code).expectDiagnosticsToMatchSnapshot([ - luaTableCannotBeAccessedDynamically.code, - ]); + util.testModule(code) + .setTsHeader(tableLib) + .expectDiagnosticsToMatchSnapshot([luaTableCannotBeAccessedDynamically.code]); } ); }); @@ -135,6 +146,6 @@ test.each([tableLibClass])("LuaTable functional tests", tableLib => { ["const t = new Table(); t.set(t.length + 1, true); t.set(t.length + 1, true); return t.length", 2], ['const k = "k"; const t = { data: new Table() }; t.data.set(k, 3); return t.data.get(k);', 3], ])("LuaTable test (%p)", (code, expectedReturnValue) => { - expect(util.testFunction(code).setTsHeader(tableLib).getLuaExecutionResult()).toBe(expectedReturnValue); + util.testFunction(code).setTsHeader(tableLib).expectToEqual(expectedReturnValue); }); }); diff --git a/test/unit/annotations/metaExtension.spec.ts b/test/unit/annotations/metaExtension.spec.ts index 2407ef654..9408f5377 100644 --- a/test/unit/annotations/metaExtension.spec.ts +++ b/test/unit/annotations/metaExtension.spec.ts @@ -19,15 +19,15 @@ test("MetaExtension", () => { } `; - const luaResult = util.testModule` + // Can't use expectToMatchJsResult because above is not valid TS/JS + util.testModule` export default debug.getregistry()["_LOADED"].test(); ` .setTsHeader(tsHeader) .ignoreDiagnostics([annotationDeprecated.code]) - // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + .setReturnExport("default") + .expectToEqual(5) .getLuaExecutionResult(); - - expect(luaResult.default).toBe(5); }); test("IncorrectUsage", () => { diff --git a/test/unit/builtins/map.spec.ts b/test/unit/builtins/map.spec.ts index d85f15d8a..4be25d3d5 100644 --- a/test/unit/builtins/map.spec.ts +++ b/test/unit/builtins/map.spec.ts @@ -20,9 +20,9 @@ test("map iterable constructor map", () => { test("map clear", () => { const mapTS = 'let mymap = new Map([["a", "c"],["b", "d"]]); mymap.clear();'; - util.testFunction(mapTS + "return mymap.size;").expectToMatchJsResult(); + util.testExpression("mymap.size;").setTsHeader(mapTS).expectToMatchJsResult(); - util.testFunction(mapTS + 'return !mymap.has("a") && !mymap.has("b");').expectToMatchJsResult(); + util.testExpression('!mymap.has("a") && !mymap.has("b");').setTsHeader(mapTS).expectToMatchJsResult(); }); test("map delete", () => { diff --git a/test/unit/builtins/string.spec.ts b/test/unit/builtins/string.spec.ts index 6f588b7e8..c0181c45f 100644 --- a/test/unit/builtins/string.spec.ts +++ b/test/unit/builtins/string.spec.ts @@ -164,8 +164,8 @@ test.each([ { inp: "hello test", start: 3, ignored: 0, end: 2 }, ])("string.substr with expression (%p)", ({ inp, start, ignored, end }) => { const paramStr = `2 > 1 && ${start} || ${ignored}` + (end ? `, ${end}` : ""); - util.testFunction` - return "${inp}".substr(${paramStr}); + util.testExpression` + "${inp}".substr(${paramStr}); `.expectToMatchJsResult(); }); diff --git a/test/unit/builtins/weakMap.spec.ts b/test/unit/builtins/weakMap.spec.ts index 74ab211a1..812e43622 100644 --- a/test/unit/builtins/weakMap.spec.ts +++ b/test/unit/builtins/weakMap.spec.ts @@ -91,7 +91,7 @@ test("weakMap set", () => { }); test("weakMap has no map features (size)", () => { - util.testFunction("return (new WeakMap() as any).size").expectToMatchJsResult(); + util.testExpression("(new WeakMap() as any).size").expectToMatchJsResult(); }); test.each(["clear()", "keys()", "values()", "entries()", "forEach(() => {})"])( diff --git a/test/unit/builtins/weakSet.spec.ts b/test/unit/builtins/weakSet.spec.ts index ef082f567..33c5e80c3 100644 --- a/test/unit/builtins/weakSet.spec.ts +++ b/test/unit/builtins/weakSet.spec.ts @@ -57,7 +57,7 @@ test("weakSet delete", () => { }); test("weakSet has no set features (size)", () => { - util.testFunction("return (new WeakSet() as any).size").expectToMatchJsResult(); + util.testExpression("(new WeakSet() as any).size").expectToMatchJsResult(); }); test.each(["clear()", "keys()", "values()", "entries()", "forEach(() => {})"])( diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 6f211f5e2..fd6c8ab0d 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -30,7 +30,10 @@ test.each(["1==1", "1===1", "1!=1", "1!==1", "1>1", "1>=1", "1<1", "1<=1", "1&&1 ); test.each(["'key' in obj", "'existingKey' in obj", "0 in obj", "9 in obj"])("Binary expression in (%p)", input => { - util.testFunction(`let obj = { existingKey: 1 }; return ${input}`).expectToMatchJsResult(); + util.testFunction` + let obj = { existingKey: 1 }; + return ${input}; + `.expectToMatchJsResult(); }); test.each(["a+=b", "a-=b", "a*=b", "a/=b", "a%=b", "a**=b"])("Binary expressions overridden operators (%p)", input => { diff --git a/test/unit/functions/functions.spec.ts b/test/unit/functions/functions.spec.ts index 02ed4653e..2d9d8f1e3 100644 --- a/test/unit/functions/functions.spec.ts +++ b/test/unit/functions/functions.spec.ts @@ -469,7 +469,7 @@ test("missing declaration name", () => { }); test("top-level function declaration is global", () => { - // TODO cant be tested with expectToMatchJsResult because in JS that would not be global + // Can't be tested with expectToMatchJsResult because in JS that would not be global util.testModule` import './a'; export const result = foo(); diff --git a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts index 265bfd7dd..abac4e483 100644 --- a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts +++ b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts @@ -30,15 +30,14 @@ test("noSelfInFile works when first statement has other annotations", () => { test.each(["(this: void, s: string) => string", "(this: any, s: string) => string", "(s: string) => string"])( "Function expression type inference in binary operator (%p)", funcType => { - const header = `declare const undefinedFunc: ${funcType};`; - const code = ` + const header = `let undefinedFunc: (${funcType}) | undefined;`; + util.testFunction` let func: ${funcType} = s => s; func = undefinedFunc || (s => s); return func("foo"); - `; - // TODO Cant use expectToMatchJsResult because above is not valid TS/JS - const luaResult = util.testFunction(code).setTsHeader(header).getLuaExecutionResult(); - expect(luaResult).toBe("foo"); + ` + .setTsHeader(header) + .expectToMatchJsResult(); } ); diff --git a/test/unit/functions/validation/validFunctionAssignments.spec.ts b/test/unit/functions/validation/validFunctionAssignments.spec.ts index 80ed83238..d9613692d 100644 --- a/test/unit/functions/validation/validFunctionAssignments.spec.ts +++ b/test/unit/functions/validation/validFunctionAssignments.spec.ts @@ -19,7 +19,7 @@ test.each(validTestFunctionAssignments)("Valid function variable declaration (%p const fn: ${functionType} = ${testFunction.value}; return fn("foobar"); ` - .setTsHeader(testFunction.definition) + .setTsHeader(testFunction.definition ?? "") .expectToMatchJsResult(); }); @@ -29,7 +29,7 @@ test.each(validTestFunctionAssignments)("Valid function assignment (%p)", (testF fn = ${testFunction.value}; return fn("foobar"); ` - .setTsHeader(testFunction.definition) + .setTsHeader(testFunction.definition ?? "") .expectToMatchJsResult(); }); @@ -39,7 +39,7 @@ test.each(validTestFunctionCasts)("Valid function assignment with cast (%p)", (t fn = ${castedFunction}; return fn("foobar"); ` - .setTsHeader(testFunction.definition) + .setTsHeader(testFunction.definition ?? "") .expectToMatchJsResult(); }); @@ -50,7 +50,7 @@ test.each(validTestFunctionAssignments)("Valid function argument (%p)", (testFun } return takesFunction(${testFunction.value}); ` - .setTsHeader(testFunction.definition) + .setTsHeader(testFunction.definition ?? "") .expectToMatchJsResult(); }); @@ -71,7 +71,7 @@ test.each(validTestFunctionCasts)("Valid function argument with cast (%p)", (tes } return takesFunction(${castedFunction}); ` - .setTsHeader(testFunction.definition) + .setTsHeader(testFunction.definition ?? "") .expectToMatchJsResult(); }); @@ -93,7 +93,7 @@ test.each([ } return takesFunction(${testFunction.value}); ` - .setTsHeader(testFunction.definition) + .setTsHeader(testFunction.definition ?? "") .expectToMatchJsResult(); }); @@ -108,7 +108,7 @@ test.each([ } return takesFunction(${testFunction.value}, ${args.join(", ")}); ` - .setTsHeader(testFunction.definition) + .setTsHeader(testFunction.definition ?? "") .expectToEqual("foobar"); }); @@ -120,7 +120,7 @@ test.each(validTestFunctionAssignments)("Valid function return (%p)", (testFunct const fn = returnsFunction(); return fn("foobar"); ` - .setTsHeader(testFunction.definition) + .setTsHeader(testFunction.definition ?? "") .expectToMatchJsResult(); }); @@ -132,7 +132,7 @@ test.each(validTestFunctionCasts)("Valid function return with cast (%p)", (testF const fn = returnsFunction(); return fn("foobar"); ` - .setTsHeader(testFunction.definition) + .setTsHeader(testFunction.definition ?? "") .expectToMatchJsResult(); }); diff --git a/test/unit/hoisting.spec.ts b/test/unit/hoisting.spec.ts index a682a5a24..9caefb300 100644 --- a/test/unit/hoisting.spec.ts +++ b/test/unit/hoisting.spec.ts @@ -223,7 +223,7 @@ test("Import hoisting (side-effect)", () => { }); test("Import hoisted before function", () => { - // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + // Can't use expectToMatchJsResult because above is not valid TS/JS util.testModule` export let result: any; diff --git a/test/unit/identifiers.spec.ts b/test/unit/identifiers.spec.ts index 90ecccbe2..9f72ad671 100644 --- a/test/unit/identifiers.spec.ts +++ b/test/unit/identifiers.spec.ts @@ -224,12 +224,10 @@ test.each(validTsInvalidLuaNames)("exported decorated class with invalid lua nam describe("lua keyword as identifier doesn't interfere with lua's value", () => { test("variable (nil)", () => { - const luaResult = util.testFunction` + util.testFunction` const nil = "foobar"; return \`\${undefined}|\${nil}\` - `.getLuaExecutionResult(); - - expect(luaResult).toBe("nil|foobar"); + `.expectToEqual("nil|foobar"); }); test("variable (and)", () => { @@ -326,7 +324,7 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { const compilerOptions = { lib: ["lib.es2015.d.ts", "lib.dom.d.ts"] }; - const luaResult = util.testFunction` + util.testFunction` const print = "foobar"; console.log(print); return result; @@ -334,9 +332,7 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { .setLuaHeader(luaHeader) .setTsHeader(tsHeader) .setOptions(compilerOptions) - .getLuaExecutionResult(); - - expect(luaResult).toBe("foobar"); + .expectToEqual("foobar"); }); test("variable (type)", () => { @@ -472,7 +468,7 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test("variable (unpack)", () => { - // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + // Can't use expectToMatchJsResult because above is not valid TS/JS const luaHeader = "unpack = table.unpack"; const luaResult = util.testFunction` @@ -562,7 +558,7 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test.each(["type", "type as type"])("imported variable (%p)", importName => { - // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + // Can't use expectToMatchJsResult because above is not valid TS/JS const luaHeader = 'package.loaded.someModule = {type = "foobar"}'; const luaResult = util.testModule` @@ -585,7 +581,7 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { }); test.each(["type", "type as type"])("re-exported variable with lua keyword as name (%p)", importName => { - // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + // Can't use expectToMatchJsResult because above is not valid TS/JS const luaHeader = 'package.loaded.someModule = {type = "foobar"}'; diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index 85107bc6b..c644776e5 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -481,7 +481,7 @@ describe("for...of empty destructuring", () => { } return i; `.getLuaExecutionResult(); - // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + // Can't use expectToMatchJsResult because above is not valid TS/JS expect(luaResult).toBe(3); }); @@ -510,7 +510,7 @@ describe("for...of empty destructuring", () => { } return i; `.getLuaExecutionResult(); - // TODO Cant use expectToMatchJsResult because above is not valid TS/JS + // Can't use expectToMatchJsResult because above is not valid TS/JS expect(luaResult).toBe(3); }); }; diff --git a/test/util.ts b/test/util.ts index b65360c2d..8cc36f6ca 100644 --- a/test/util.ts +++ b/test/util.ts @@ -73,9 +73,9 @@ export abstract class TestBuilder { // TODO: Use testModule in these cases? protected tsHeader = ""; - public setTsHeader(tsHeader: string | undefined): this { + public setTsHeader(tsHeader: string): this { expect(this.hasProgram).toBe(false); - this.tsHeader = tsHeader ?? ""; + this.tsHeader = tsHeader; return this; } From 985f3dc5ea543097c7687fee1bcba9b20ea3d646 Mon Sep 17 00:00:00 2001 From: Lorenz Junglas Date: Fri, 5 Feb 2021 13:36:01 +0100 Subject: [PATCH 10/10] Addressed 2nd review --- test/unit/annotations/metaExtension.spec.ts | 3 +-- test/unit/assignments.spec.ts | 2 +- test/unit/builtins/map.spec.ts | 29 ++++++++++++++++----- test/unit/builtins/string.spec.ts | 2 +- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/test/unit/annotations/metaExtension.spec.ts b/test/unit/annotations/metaExtension.spec.ts index 9408f5377..b0fa56578 100644 --- a/test/unit/annotations/metaExtension.spec.ts +++ b/test/unit/annotations/metaExtension.spec.ts @@ -26,8 +26,7 @@ test("MetaExtension", () => { .setTsHeader(tsHeader) .ignoreDiagnostics([annotationDeprecated.code]) .setReturnExport("default") - .expectToEqual(5) - .getLuaExecutionResult(); + .expectToEqual(5); }); test("IncorrectUsage", () => { diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index b1fa6f426..5f9209c41 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -12,7 +12,7 @@ test.each(["const", "let"])("%s declaration not top-level is not global", declar }); test.each(["const", "let"])("top-level %s declaration is global", declarationKind => { - // TODO cant be tested with expectToMatchJsResult because in JS that would not be global + // Can't be tested with expectToMatchJsResult because in JS that would not be global util.testModule` import './a'; export const result = foo; diff --git a/test/unit/builtins/map.spec.ts b/test/unit/builtins/map.spec.ts index 4be25d3d5..7eec8844d 100644 --- a/test/unit/builtins/map.spec.ts +++ b/test/unit/builtins/map.spec.ts @@ -1,7 +1,10 @@ import * as util from "../../util"; test("map constructor", () => { - util.testFunction("let mymap = new Map(); return mymap.size;").expectToMatchJsResult(); + util.testFunction` + let mymap = new Map(); + return mymap.size; + `.expectToMatchJsResult(); }); test("map iterable constructor", () => { @@ -20,9 +23,9 @@ test("map iterable constructor map", () => { test("map clear", () => { const mapTS = 'let mymap = new Map([["a", "c"],["b", "d"]]); mymap.clear();'; - util.testExpression("mymap.size;").setTsHeader(mapTS).expectToMatchJsResult(); + util.testExpression("mymap.size").setTsHeader(mapTS).expectToMatchJsResult(); - util.testExpression('!mymap.has("a") && !mymap.has("b");').setTsHeader(mapTS).expectToMatchJsResult(); + util.testExpression('!mymap.has("a") && !mymap.has("b")').setTsHeader(mapTS).expectToMatchJsResult(); }); test("map delete", () => { @@ -61,19 +64,31 @@ test("map foreach keys", () => { }); test("map get", () => { - util.testFunction('let mymap = new Map([["a", "c"],["b", "d"]]); return mymap.get("a");').expectToMatchJsResult(); + util.testFunction` + let mymap = new Map([["a", "c"],["b", "d"]]); + return mymap.get("a"); + `.expectToMatchJsResult(); }); test("map get missing", () => { - util.testFunction('let mymap = new Map([["a", "c"],["b", "d"]]); return mymap.get("c");').expectToMatchJsResult(); + util.testFunction` + let mymap = new Map([["a", "c"],["b", "d"]]); + return mymap.get("c"); + `.expectToMatchJsResult(); }); test("map has", () => { - util.testFunction('let mymap = new Map([["a", "c"]]); return mymap.has("a");').expectToMatchJsResult(); + util.testFunction` + let mymap = new Map([["a", "c"]]); + return mymap.has("a"); + `.expectToMatchJsResult(); }); test("map has false", () => { - util.testFunction('let mymap = new Map(); return mymap.has("a");').expectToMatchJsResult(); + util.testFunction` + let mymap = new Map(); + return mymap.has("a"); + `.expectToMatchJsResult(); }); test.each([ diff --git a/test/unit/builtins/string.spec.ts b/test/unit/builtins/string.spec.ts index c0181c45f..cfc3bda30 100644 --- a/test/unit/builtins/string.spec.ts +++ b/test/unit/builtins/string.spec.ts @@ -165,7 +165,7 @@ test.each([ ])("string.substr with expression (%p)", ({ inp, start, ignored, end }) => { const paramStr = `2 > 1 && ${start} || ${ignored}` + (end ? `, ${end}` : ""); util.testExpression` - "${inp}".substr(${paramStr}); + "${inp}".substr(${paramStr}) `.expectToMatchJsResult(); });