function runUnitTests()

in Clients/AmbrosiaJS/Ambrosia-Node/src/UnitTests.ts [147:479]


function runUnitTests(args: string[]): void
{
    _passCount = _failCount = 0;

    const startTime: number = Date.now();
    let componentTestPassCount: number = 0;

    Utils.log("RUNNING UNIT TESTS...");

    runTest("Compare simple type - basic",
        () => (Meta.Type.compareTypes("string", "string") || "Match"), "Match");
    runTest("Compare simple type - missing aray suffix",
        () => (Meta.Type.compareTypes("string", "string[][]") || "ShouldNotMatch"), "expected 'string[][]', not 'string'");

    runTest("Compare object type - basic (1)",
        () => (Meta.Type.compareTypes("object", "{ p1: string }") || "Match"), "Match");
    runTest("Compare object type - basic (2)",
        () => (Meta.Type.compareTypes("{ p1: string }", "object") || "Match"), "Match");
    runTest("Compare object type - array (1)",
        () => (Meta.Type.compareTypes("object[]", "{ p1: string }[]") || "Match"), "Match");
    runTest("Compare object type - array (2)",
        () => (Meta.Type.compareTypes("{ p1: string }[]", "object[]") || "Match"), "Match");
    runTest("Compare object type - array (3)",
        () => (Meta.Type.compareTypes("{ p1: string }[]", "object[][]") || "ShouldNotMatch"), "expected 'object[][]', not '{p1:string}[]'");

    runTest("Compare any type - basic (1)",
        () => (Meta.Type.compareTypes("any", "{ p1: string }") || "Match"), "Match");
    runTest("Compare any type - basic (2)",
        () => (Meta.Type.compareTypes("{ p1: string }", "any") || "Match"), "Match");
    runTest("Compare any type - array (1)",
        () => (Meta.Type.compareTypes("any[]", "{ p1: string }[]") || "Match"), "Match");
    runTest("Compare any type - array (2)",
        () => (Meta.Type.compareTypes("{ p1: string }[]", "any[]") || "Match"), "Match");
    runTest("Compare any type - array dimension mismatch (1)",
        () => (Meta.Type.compareTypes("any[]", "{ p1: string }[][]") || "Match"), "Match"); // "any" here is a valid match for the type "{ p1: string }[]"
    runTest("Compare any type - array dimension mismatch (2)",
        () => (Meta.Type.compareTypes("{ p1: string }[][]", "any[]") || "Match"), "Match"); // "any" here is a valid match for the type "{ p1: string }[]"

    runTest("Compare basic complex type array",
        () => (Meta.Type.compareTypes("{ p1: string }[]", "{ p1: string }[]") || "Match"), "Match");
    runTest("Compare basic complex type array - incorrect array suffix ",
        () => (Meta.Type.compareTypes("{ p1: string }[]", "{ p1: string }[][]") || "ShouldNotMatch"), "{ p1: string }[] should have array suffix [][], not []");

    runTest("Compare complex type - basic",
        () => (Meta.Type.compareTypes("{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: number }", "{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: number }") || "Match"), "Match");
    runTest("Compare complex type - detect misnamed root-level property name",
        () => (Meta.Type.compareTypes("{ p1: string, p2: { p4: number, p5: { p6: string } }, p31: number }", "{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: number }") || "ShouldNotMatch"), "property #3 should be 'p3:', not 'p31:'");
    runTest("Compare complex type - detect misnamed nested property name",
        () => (Meta.Type.compareTypes("{ p1: string, p2: { p4: number, p5: { p61: string } }, p3: number }", "{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: number }") || "ShouldNotMatch"), "property #1 (in 'p2.p5') should be 'p6:', not 'p61:'");
    runTest("Compare complex type - detect incorrect root-level property type",
        () => (Meta.Type.compareTypes("{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: string }", "{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: number }") || "ShouldNotMatch"), "type of 'p3' should be 'number', not 'string'");
    runTest("Compare complex type - detect incorrect nested property type",
        () => (Meta.Type.compareTypes("{ p1: string, p2: { p4: number, p5: { p6: boolean } }, p3: number }", "{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: number }") || "ShouldNotMatch"), "type of 'p6' (in 'p2.p5') should be 'string', not 'boolean'");
    runTest("Compare complex type - mismatched structure (missing expected property)",
        () => (Meta.Type.compareTypes("{ p1: string, p2: { p4: number, p5: { p6: string } } }", "{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: number }") || "ShouldNotMatch"), "mismatched structure: expected member 'p3:' not provided");
    runTest("Compare complex type - mismatched structure (unexpected additional property)",
        () => (Meta.Type.compareTypes("{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: number, pExtra: boolean }", "{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: number }") || "ShouldNotMatch"), "mismatched structure: unexpected member 'pExtra:' provided");
    runTest("Compare complex type - unexpected array suffix",
        () => (Meta.Type.compareTypes("{ p1: string, p2: { p4: number, p5: { p6: string }[][] }, p3: number }", "{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: number }") || "ShouldNotMatch"), "type of 'p2.p5' ({ p6: string }[][]) should have array suffix (None), not [][]");
    runTest("Compare complex type - missing array suffix",
        () => (Meta.Type.compareTypes("{ p1: string, p2: { p4: number, p5: { p6: string } }, p3: number }", "{ p1: string, p2: { p4: number, p5: { p6: string }[] }, p3: number }") || "ShouldNotMatch"), "type of 'p2.p5' ({ p6: string }) should have array suffix [], not (None)");
    runTest("Compare complex type - with generics",
        () => (Meta.Type.compareTypes(Meta.Type.getRuntimeType({ p1: new Set<boolean>([true, false, true]), p2: new Map<number, string>([[123, "a"]]) }), "{ p1: Set, p2: Map }") || "Match"), "Match");

    runTest("Get runtime type - basic",
        () => Meta.Type.getRuntimeType([{addressLines: { lines: ["Line1", "Line"] }, zip: 98052}]), "{ addressLines: { lines: string[] }, zip: number }[]");
    runTest("Get runtime type - \"mixed\" array",
        () => Meta.Type.getRuntimeType([{addressLines: { lines: ["Line1", 123] }, zip: 98052}]), "{ addressLines: { lines: any[] }, zip: number }[]");
    runTest("Get runtime type - basic generic",
        () => Meta.Type.getRuntimeType(new Set<string>(["a", "b", "c"])), "Set");
    runTest("Get runtime type - object with generics",
        () => Meta.Type.getRuntimeType({ p1: new Set<string>(["a", "b", "c"]), p2: new Map<number, string>([[123, "a"]]) }), "{ p1: Set, p2: Map }");
    runTest("Get runtime type - object with various built-in types",
        () => Meta.Type.getRuntimeType({ m: new Map<number, string>(), s: new Set<number>(), e: new Error("e"), d: new Date(), r: RegExp(/a/g) }), "{ m: Map, s: Set, e: Error, d: Date, r: RegExp }");

    let genericSpecifiers: string[] = Meta.Type.getGenericsSpecifiers("{ p1: Bar<Foo, {p2: string}, [string[], () => number], Map<number, Set<{p3: string, p4: Set<boolean>}>>>, p12: Set<Date> }");
    runTest("Get generic-type specifiers",
        () => genericSpecifiers.join(", "), "<Foo, {p2: string}, [string[], () => number], Map<number, Set<{p3: string, p4: Set<boolean>}>>>, <Date>");
    runTest("Parse generic-type specifiers",
        () => Meta.Type.parseGenericsSpecifier(genericSpecifiers[0]).join(", "), "Foo, {p2: string}, [string[], () => number], Map<number, Set<{p3: string, p4: Set<boolean>}>>");

    runTest("Published type - check expanded definition removes generics",
        () => Meta.publishType("GenericsTest", "{ p1: Set<Foo>, p2: Map<number, Set<{ p3: Set<string[][]> }>[]>[][] }").expandedDefinition, "{ p1: Set, p2: Map[][] }");
    runTest("Published type - check user-defined generic type is not allowed",
        () => { Meta.publishType("GenericsTest2<T>", "Set<T>"); return (""); }, "The published type 'GenericsTest2<T>' has an invalid name ('GenericsTest2<T>')");

    runTest("Published type - check expanded definition of forward reference is initially empty",
        () => Meta.publishType("ForwardReferenceTest", "{ p1: Foo }").expandedDefinition, "");
    Meta.publishType("Foo", "{ p2: string }");
    runTest("Published type - check expanded definition of forward reference is auto-fixed",
        () => Meta.getPublishedType("ForwardReferenceTest")?.expandedDefinition || "TypeNotFound", "{ p1: { p2: string } }");

    Meta.publishType("FooArray", "Foo[][]");
    runTest("Published type - check expanded definition for a type that's an array of a published type",
        () => Meta.getPublishedType("FooArray")?.expandedDefinition || "TypeNotFound", "{ p2: string }[][]");

    runTest("Published type - null type is not supported",
        () => Meta.publishType("NullTypeTest", "null").toString(), "The published type 'NullTypeTest' has a type ('null') that's not supported in this context");
    runTest("Published type - null[] type is not supported",
        () => Meta.publishType("NullArrayTypeTest", "null[]").toString(), "The published type 'NullArrayTypeTest' has an unsupported type ('null[]')");
    runTest("Published type - null is supported in a union type",
        () => Meta.publishType("NullableUnionTypeTest", "string | null").expandedDefinition, "any");

    runTest("Published type - undefined type is not supported",
        () => Meta.publishType("UndefinedTypeTest", "undefined").toString(), "The published type 'UndefinedTypeTest' has a type ('undefined') that's not supported in this context");
    runTest("Published type - undefined[] type is not supported",
        () => Meta.publishType("UndefinedArrayTypeTest", "undefined[]").toString(), "The published type 'UndefinedArrayTypeTest' has an unsupported type ('undefined[]')");
    runTest("Published type - undefined is supported in a union type",
        () => Meta.publishType("UndefinedableUnionTypeTest", "string | undefined").expandedDefinition, "any");

    runTest("Published type - using 'any' generates a warning",
        () => { Meta.publishType("AnyWarningTest", "any"); return (Utils.getLastMessageLogged() as string); }, "The published type 'AnyWarningTest' uses type 'any' which is too general to determine if it can be safely serialized", false);

    runTest("Published type - tuple types are not supported",
        () => Meta.publishType("TupleTest", "[string, number]").toString(), "tuple types are not supported", false);
    runTest("Published type - tuple types are not supported (in a generic)",
        () => Meta.publishType("TupleTestInGeneric", "Set<[string, number]>").toString(), "tuple types are not supported", false);

    runTest("Published type - union types are supported (as 'any')",
        () => Meta.publishType("UnionTest", "string | (number | boolean)[] | (string & number) | { p1: boolean | number, p2: boolean & number} | boolean").expandedDefinition, "any");
    runTest("Published type - union types are supported (in a generic)",
        () => Meta.publishType("UnionTestGeneric", "Set<string | number>").expandedDefinition, "Set");
    runTest("Published type - intersection types are supported (as 'any')",
        () => Meta.publishType("IntersectionTest", "string & (number & boolean)[] & (string | number) & { p1: boolean & number, p2: boolean | number } & boolean").expandedDefinition, "any");
    runTest("Published type - intersection types are supported (in a generic)",
        () => Meta.publishType("IntersectionTestGeneric", "Set<string & number>").expandedDefinition, "Set");

    runTest("Published type - union type emits a type simplification warning",
        () => { Meta.publishType("UnionTypeWarningTest", "string | number");  return (Utils.getLastMessageLogged() as string); }, "Warning: The expanded definition for type 'UnionTypeWarningTest' was simplified to \"any\", which will bypass runtime type checking");
    runTest("Published type - type using an in-line union type emits a type simplification warning",
        () => { Meta.publishType("TypeUsingInlineUnionWarningTest", "{ p1: string | number }"); return (Utils.getLastMessageLogged() as string); }, "Warning: The expanded definition for type 'TypeUsingInlineUnionWarningTest' was simplified to \"any\", which will bypass runtime type checking; the strongly recommended fix is to publish all in-line compound types (unions and intersections)");
    runTest("Published type - intersection type emits a type simplification warning",
        () => { Meta.publishType("IntersectionTypeWarningTest", "string & number");  return (Utils.getLastMessageLogged() as string); }, "Warning: The expanded definition for type 'IntersectionTypeWarningTest' was simplified to \"any\", which will bypass runtime type checking");
    runTest("Published type - type using an in-line intersection type emits a type simplification warning",
        () => { Meta.publishType("TypeUsingInLineIntersectionWarningTest", "{ p1: string & number }"); return (Utils.getLastMessageLogged() as string); }, "Warning: The expanded definition for type 'TypeUsingInLineIntersectionWarningTest' was simplified to \"any\", which will bypass runtime type checking; the strongly recommended fix is to publish all in-line compound types (unions and intersections)");
    runTest("Published type - intersection types containing object literals are supported",
        () => Meta.publishType("IntersectionWithObjectLiteralTest", "string & { p1: number }").expandedDefinition, "any");
    runTest("Published type - union types containing object literals are supported",
        () => Meta.publishType("UnionWithObjectLiteralTest", "string | { p1: number }").expandedDefinition, "any");

    runTest("Published type - intersection types cannot use 'null'",
        () => Meta.publishType("IntersectionNullTest", "string & null").toString(), "The intersection-type component #2 of published type 'IntersectionNullTest' has a type ('null') that's not supported in this context");
    runTest("Published type - intersection types cannot use 'undefined'",
        () => Meta.publishType("IntersectionUndefinedTest", "string & undefined").toString(), "The intersection-type component #2 of published type 'IntersectionUndefinedTest' has a type ('undefined') that's not supported in this context");

    runTest("Published type - unions of string literals are supported",
        () => Meta.publishType("FirstNames", "'Rahee' | \"Jonathan\" | \"Darren\" | \"Richard\"").definition, "'Rahee' | \"Jonathan\" | \"Darren\" | \"Richard\"");
    runTest("Published type - template string types are supported",
        () => Meta.publishType("TemplateStringTest", "`Hello ${FirstNames} at ${'MSR' | 'Microsoft'}`").expandedDefinition, "string");
    runTest("Published type - complex type that references a published compound type has localized simplification",
        () => Meta.publishType("LocalizedTypeSimplification", "{ p1: string, p2: FirstNames, p3: TemplateStringTest }").expandedDefinition, "{ p1: string, p2: any, p3: string }");

    runTest("Published method - rest params are supported in post method",
        () => Meta.publishPostMethod("RestFnPostTest", 1, ["p1: string", "...p2: number[]"], "number").parameterNames.join(", "), "p1, ...p2");
    runTest("Published method - rest params are supported in non-post method",
        () => Meta.publishMethod(1, "RestFnNonPostTest", ["np1: string", "...np2: number[]"]).parameterNames.join(", "), "np1, ...np2");
    runTest("Published method - bad rest params syntax is caught (too many dots)",
        () => Meta.publishPostMethod("RestFnBadSyntax", 1, ["....p1: number[]"], "number").toString(), "has an invalid name ('....p1')", false);
    runTest("Published method - bad rest params syntax is caught (not an array)",
        () => Meta.publishPostMethod("RestFnBadSyntax", 1, ["...p1: number"], "number").toString(), "Rest parameter '...p1' of method 'RestFnBadSyntax' must be an array");
    runTest("Published method - bad rest params syntax is caught (must be last parameter)",
        () => Meta.publishPostMethod("RestFnBadSyntax", 1, ["...p1: number[]", "p2: string"], "number").toString(), "Rest parameter '...p1' of method 'RestFnBadSyntax' must be specified after all other parameters");

    runTest("Published type - functions types are not supported",
        () => Meta.publishType("FunctionTest", "() => number").toString(), "function types are not supported", false);
    runTest("Published type - functions types are not supported (in a generic)",
        () => Meta.publishType("FunctionTestGeneric", "Map<string, () => number>").toString(), "function types are not supported", false);
    runTest("Published type - the 'never' type is not supported",
        () => Meta.publishType("NeverTest", "string | never").toString(), "has an unsupported type ('never')", false);
    runTest("Published method - the 'unknown' type is not supported",
        () => Meta.publishPostMethod("UnknownTest", 1, ["p1: string"], "unknown").toString(), "has an unsupported type ('unknown')", false);
    runTest("Published type - TypeScript utility types are not supported",
        () => Meta.publishType("TSUtilityTypeTest", "NonNullable<string[] | null>").toString(), "utility types are not supported", false);
    runTest("Published type - \"Other\" TypeScript types are not supported",
        () => Meta.publishType("TSOtherTypeTest", "string extends null ? never: string").toString(), "conditional types are not supported", false);
    runTest("Published type - Space in-and-around array suffixes is removed",
        () => Meta.publishType("SpaceRemovalTest", "string [ ] [    ]   []   ").expandedDefinition, "string[][][]", true);

    runTest("Published method - method name must be valid",
        () => Meta.publishMethod(2, "TestFn<T, V>", ["p1: string"]).toString(), "The method has an invalid name ('TestFn<T, V>')", false);
    runTest("Published method - cannot reference an unpublished type",
        () => Meta.publishPostMethod("MethodUsingUnpublishedType", 1, ["p1: SomeNotYetPublishedType"], "void").toString(), "references an unpublished type ('SomeNotYetPublishedType')", false);
    runTest("Published method - method using a union type emits a type simplification warning",
        () => { Meta.publishPostMethod("MethodUsingUnionWarningTest", 1, ["p1: string | number"], "void"); return (Utils.getLastMessageLogged() as string); }, "Warning: The expanded definition for the type of parameter 'p1' of method 'MethodUsingUnionWarningTest' was simplified to \"any\", which will bypass runtime type checking");
    runTest("Published method - method using a union type member emits a type simplification warning",
        () => { Meta.publishPostMethod("MethodUsingUnionMemberWarningTest", 1, ["p1: { a: number, b: string | number }"], "void"); return (Utils.getLastMessageLogged() as string); }, "Warning: The expanded definition for the type of parameter 'p1' of method 'MethodUsingUnionMemberWarningTest' was simplified to \"any\", which will bypass runtime type checking; the strongly recommended fix is to publish all in-line compound types (unions and intersections)");
    
    let oA = { x: {} };
    let oB = { y: oA };
    oA.x = oB;
    runTest("JSON serialization - check for circular reference (simple)", // oA.x -> oB, oB.y -> oA
        () => Utils.jsonStringify(oA), "Unable to serialize object to JSON (reason: (object) [Object] has a property ((object).x.y) that points back to (object), creating a circular reference)");

    let o3 = { z: [{}, {}] };
    let o2 = { y: o3 };
    let o1 = { x: o2 };
    o3.z[1] = o2;
    runTest("JSON serialization - check for circular reference (via array element)", // o1.x -> o2, o2.y -> o3, o3.z[1] -> o2
        () => Utils.jsonStringify(o1), "Unable to serialize object to JSON (reason: (object).x [Object] has an array element ((object).x.y.z[1]) that points back to (object).x, creating a circular reference)");

    let oWithMap1 = { x: new Map<object, string>() };
    oWithMap1.x.set(oWithMap1, "Test");
    runTest("JSON serialization - check for circular reference (via Map key)",
        () => Utils.jsonStringify(oWithMap1), "Unable to serialize object to JSON (reason: (object) [Object] has a Map key ((object).x[0].key) that points back to (object), creating a circular reference)");

    let oWithMap2 = { x: new Map<string, object>() };
    oWithMap2.x.set("test", oWithMap2);
    runTest("JSON serialization - check for circular reference (via Map value)",
        () => Utils.jsonStringify(oWithMap2), "Unable to serialize object to JSON (reason: (object) [Object] has a Map value ((object).x[0].value) that points back to (object), creating a circular reference)");

    let oWithSet = { x: new Set<object>() };
    oWithSet.x.add(oWithSet);
    runTest("JSON serialization - check for circular reference (via Set entry)",
        () => Utils.jsonStringify(oWithSet), "Unable to serialize object to JSON (reason: (object) [Object] has a Set entry ((object).x[0]) that points back to (object), creating a circular reference)");

    runTest("JSON serialization - quick test", () => quickTest().toString(), "true");
    runTest("JSON serialization - object test", () => objectTest().toString(), "true");
    runTest("JSON serialization - special values test", () => specialValuesTest().toString(), "true");
    runTest("JSON serialization - set test", () => setTest().toString(), "true");

    runTest("JSON serialization - check that Error sub-types serialize to Error",
        () => (Utils.jsonParse(Utils.jsonStringify({ p1: new Error(), p2: new EvalError("e2") })).p1 instanceof Error).toString(), "true");
    runTest("JSON serialization - check that root-level empty object serializes",
        () => Utils.jsonStringify(Utils.jsonParse(Utils.jsonStringify({}))), "{}");
    runTest("JSON serialization - check that undefined object property is removed", // Note: This is standard JSON.stringify() behavior
        () => (Utils.jsonParse(Utils.jsonStringify({ p1: 123, p2: undefined })).hasOwnProperty("p2")).toString(), "false");
    runTest("JSON serialization - check that undefined array element becomes null", // Note: This is standard JSON.stringify() behavior
        () => (Utils.jsonParse(Utils.jsonStringify([1, null, 2], false))[1] === null).toString(), "true");
    runTest("JSON serialization - check nested custom serialization",
        () => ([...Utils.jsonParse(Utils.jsonStringify({ p1: new Date(2021, 1, 10), p2: new Set([ new Uint8Array([1, 2, 3]) ]) } )).p2.values()][0][2] === 3).toString(), "true");

    componentTestPassCount = 0;
    for (const token of Utils._serializationTokens)
    {
        if (runTest(`JSON serialization - check for rejection of string values that overlap with internal token '${token}' (in a Set)`,
            () => Utils.jsonStringify(new Set<string>(["test", token]), false), `Found a Set entry ((Set)[1]) with a value ('${token}') which cannot be serialized because it's used as an internal token`, false, true))
        { componentTestPassCount++; }
    }
    runTest(`JSON serialization - [${componentTestPassCount} sub-tests passed] check for rejection of string values that overlap with internal tokens [in a Set]`,
        () => (componentTestPassCount === Utils._serializationTokens.length).toString(), "true");

    componentTestPassCount = 0;
    for (const token of Utils._serializationTokens)
    {
        if (runTest(`JSON serialization - check for rejection of string values that overlap with internal token '${token}' (in a Map key)`,
            () => Utils.jsonStringify(new Map<string, number>([[token, 123]]), false), `Found a Map key ((Map)[0].key) with a value ('${token}') which cannot be serialized because it's used as an internal token`, false, true))
        { componentTestPassCount++; }
    }
    runTest(`JSON serialization - [${componentTestPassCount} sub-tests passed] check for rejection of string values that overlap with internal tokens [in a Map key]`,
        () => (componentTestPassCount === Utils._serializationTokens.length).toString(), "true");

    componentTestPassCount = 0;
    for (const token of Utils._serializationTokens)
    {
        if (runTest(`JSON serialization - check for rejection of string values that overlap with internal token '${token}' (in a Map value)`,
            () => Utils.jsonStringify(new Map<number, string>([[123, token]]), false), `Found a Map value ((Map)[0].value) with a value ('${token}') which cannot be serialized because it's used as an internal token`, false, true))
        { componentTestPassCount++; }
    }
    runTest(`JSON serialization - [${componentTestPassCount} sub-tests passed] check for rejection of string values that overlap with internal tokens [in a Map value]`,
        () => (componentTestPassCount === Utils._serializationTokens.length).toString(), "true");

    componentTestPassCount = 0;
    for (const token of Utils._serializationTokens)
    {
        if (runTest(`JSON serialization - check for rejection of string values that overlap with internal token '${token}' (in an array)`,
            () => Utils.jsonStringify(["test", token], false), `Found an array element ((Array)[1]) with a value ('${token}') which cannot be serialized because it's used as an internal token`, false, true))
        { componentTestPassCount++; }
    }
    runTest(`JSON serialization - [${componentTestPassCount} sub-tests passed] check for rejection of string values that overlap with internal tokens [in an array]`,
        () => (componentTestPassCount === Utils._serializationTokens.length).toString(), "true");

    componentTestPassCount = 0;
    for (const token of Utils._serializationTokens)
    {
        if (runTest(`JSON serialization - check for rejection of string values that overlap with internal token '${token}' (in an object)`,
            () => Utils.jsonStringify({ p0: "test", p1: token }), `Found a property ((object).p1) with a value ('${token}') which cannot be serialized because it's used as an internal token`, false, true))
        { componentTestPassCount++; }
    }
    runTest(`JSON serialization - [${componentTestPassCount} sub-tests passed] check for rejection of string values that overlap with internal tokens [in an object]`,
        () => (componentTestPassCount === Utils._serializationTokens.length).toString(), "true");

    runTest("JSON serialization - check that 'Int8Array' serializes",
        () => (Utils.jsonParse(Utils.jsonStringify(new Int8Array([1, 2, 3]), false))[1] === 2).toString(), "true");
    runTest("JSON serialization - check that 'Uint8Array' serializes",
        () => (Utils.jsonParse(Utils.jsonStringify(new Uint8Array([1, 2, 3]), false))[1] === 2).toString(), "true");
    runTest("JSON serialization - check that 'Uint8Array' serializes (case #2)",
        () => Utils.jsonParse(Utils.jsonStringify(new Uint8Array([0, 7, 10, 63, 127, 255]), false)).toString(), "0,7,10,63,127,255");
    runTest("JSON serialization - check that 'Uint8ClampedArray' serializes",
        () => (Utils.jsonParse(Utils.jsonStringify(new Uint8ClampedArray([1, 2, 3]), false))[1] === 2).toString(), "true");
    runTest("JSON serialization - check that 'Int16Array' serializes",
        () => (Utils.jsonParse(Utils.jsonStringify(new Int16Array([1, 2, 3]), false))[1] === 2).toString(), "true");
    runTest("JSON serialization - check that 'Uint16Array' serializes",
        () => (Utils.jsonParse(Utils.jsonStringify(new Uint16Array([1, 2, 3]), false))[1] === 2).toString(), "true");
    runTest("JSON serialization - check that 'Int32Array' serializes",
        () => (Utils.jsonParse(Utils.jsonStringify(new Int32Array([1, 2, 3]), false))[1] === 2).toString(), "true");
    runTest("JSON serialization - check that 'Uint32Array' serializes",
        () => (Utils.jsonParse(Utils.jsonStringify(new Uint32Array([1, 2, 3]), false))[1] === 2).toString(), "true");
    runTest("JSON serialization - check that 'Float32Array' serializes",
        () => (Utils.jsonParse(Utils.jsonStringify(new Float32Array([1, 2.123, 3]), false))[1].toFixed(3) === "2.123").toString(), "true");
    runTest("JSON serialization - check that 'Float64Array' serializes",
        () => (Utils.jsonParse(Utils.jsonStringify(new Float64Array([1, 2.123, 3]), false))[1].toFixed(3) === "2.123").toString(), "true");
    runTest("JSON serialization - check that 'BigInt64Array' serializes",
        () => (Utils.jsonParse(Utils.jsonStringify(new BigInt64Array([BigInt(-1), BigInt(-2), BigInt(-3)]), false))[1] === BigInt(-2)).toString(), "true");
    runTest("JSON serialization - check that 'BigUint64Array' serializes",
        () => (Utils.jsonParse(Utils.jsonStringify(new BigUint64Array([BigInt(1), BigInt(2), BigInt(3)]), false))[1] === BigInt(2)).toString(), "true");

    runTest("JSON serialization - check that unsupported type 'WeakSet' does not serialize",
        () => Utils.jsonStringify({ p1: new WeakSet<String>() }, false), "(object).p1 (a property) is of type 'WeakSet' which is not supported for serialization", false);
    runTest("JSON serialization - check that unsupported type 'WeakMap' does not serialize",
        () => Utils.jsonStringify({ p1: new WeakMap<Number, String>() }, false), "(object).p1 (a property) is of type 'WeakMap' which is not supported for serialization", false);
    runTest("JSON serialization - check that unsupported type 'function' does not serialize",
        () => Utils.jsonStringify({ p1: [function foo (): void {}] }, false), "(object).p1[0] (an array element) is of type 'function' which is not supported for serialization", false);
    runTest("JSON serialization - check that unsupported type 'symbol' does not serialize",
        () => Utils.jsonStringify({ p1: new Set<symbol>([Symbol("sym")]) }, false), "(object).p1[0] (a Set entry) is of type 'symbol' which is not supported for serialization", false);

    const oVin = { m: new Map<number, string>([[1, "abc"], [2, "def"]]), s: new Set<number>([1,2,3]), e: new Error("e"), d: new Date(2021, 0, 27), r: RegExp(/a/g) };
    const oVout = Utils.jsonParse(Utils.jsonStringify(oVin));
    runTest("JSON serialization - check fidelity of various built-in types",
        () => ((oVout.m.get(2) === "def") && oVout.s.has(3) && (oVout.e.message === "e") && (oVout.d.toLocaleDateString() === "1/27/2021") && oVout.r.test("cat")).toString(), "true");

    class Foo {}
    const misc: any[] = [null, undefined, BigInt(123), new Error(), new EvalError(), new Foo(), new Date(), {}, new Object({}), 123, new Number(123), 
                         new Uint8Array([1,2,3]), true, new Boolean(false), "Hello", new String("Hello"), Symbol("symbol")];
    runTest("Get native type - miscellaneous check",
        () => misc.map(element => Meta.Type.getNativeType(element)).join(", "), "object, undefined, bigint, Error, EvalError, object, Date, object, object, number, number, Uint8Array, boolean, boolean, string, string, symbol");

    const testCount: number = _passCount + _failCount;
    const elapsedMs: number = Date.now() - startTime;

    // Attention: The AmbrosiaTest VS solution looks for "UNIT TESTS COMPLETE", so if you change it here you must change it there too (in \AmbrosiaTest\AmbrosiaTest\JS_Tests.cs)
    Utils.log(`...${testCount} UNIT TESTS COMPLETE (in ${elapsedMs}ms)`);
    reportTestSummary();
}