| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 |
- //-------------------------------------------------------------------------------------------------------
- // Copyright (C) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
- //-------------------------------------------------------------------------------------------------------
- // ES6 String Template tests -- verifies the API shape and basic functionality
- WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
- function ReturnString(str) {
- return str;
- }
- function GetCallsite(callsite) {
- return callsite;
- }
- function GetExpectedCachedCallsite() {
- return GetCallsite`some string template ${''} with replacements ${''}`;
- }
- function GetRawStringValue(callsite, idx) {
- return callsite.raw[idx];
- }
- function GetStringValue(callsite, idx) {
- return callsite[idx];
- }
- function tagReturnConstructor(callsite) {
- switch(callsite[0]) {
- case 'string':
- return String;
- case 'symbol':
- return Symbol;
- case 'array':
- return Array;
- case 'number':
- return Number;
- }
- return function() {
- return {
- name: 'constructed object'
- }
- };
- }
- function tagReturnConstructorWrapper() {
- return tagReturnConstructor;
- }
- var ObjectWithToString = {
- toString: function() { return "ObjectWithToString.toString() called!"; },
- value: 'ObjectWithToString.value (should not show up)'
- };
- var tests = [
- {
- name: "String constructor should have spec defined built-ins with correct lengths",
- body: function () {
- assert.isTrue(String.hasOwnProperty('raw'), "String constructor should have a raw method");
- assert.isTrue(String.raw.length === 1, "String.raw method takes 1 arguments");
- }
- },
- {
- name: "String.raw takes a callsite object and replacement values and returns a string that is the combination of the callsite raw strings and replacement values",
- body: function () {
- assert.throws(function () { String.raw.call(); }, TypeError, "String.raw throws TypeError if it is given no arguments", "String.raw: argument is not an Object");
- assert.throws(function () { String.raw.call('non-object'); }, TypeError, "String.raw throws TypeError if it is given non-object parameter", "String.raw: argument is not an Object");
- assert.throws(function () { String.raw.call({}); }, TypeError, "String.raw throws TypeError if it is given object which doesn't contain a raw property", "String.raw: argument is not an Object");
- assert.throws(function () { String.raw.call({ raw: undefined }); }, TypeError, "String.raw throws TypeError if it is given object which contains a raw property which is undefined", "String.raw: argument is not an Object");
- assert.throws(function () { String.raw.call({ raw: 'non-object' }); }, TypeError, "String.raw throws TypeError if it is given object which contains a raw property which is not an object", "String.raw: argument is not an Object");
- assert.areEqual("", String.raw``, "String.raw of empty string is the empty string");
- assert.areEqual("no replacement", String.raw`no replacement`, "no replacement expressions");
- assert.areEqual("\\u1234", String.raw`\u1234`, "unicode escape sequence");
- assert.areEqual("str\\ning1 simple replacement... \\r\\nstring end", String.raw`str\ning1 ${'simple replacement...'} \r\nstring end`, "string with replacement and escape sequences");
- assert.areEqual("str1\\\\ str2 str3\\\\ str4 str5\\\\ str6 str7\\\\", String.raw`str1\\ ${'str2'} str3\\ ${'str4'} str5\\ ${'str6'} str7\\`, "several replacements and escape sequences");
- assert.areEqual("begin (nested template here) end", String.raw`begin ${`(nested ${ReturnString('template')} here)`} end`, "nested string template literal");
- assert.areEqual("call toString... ObjectWithToString.toString() called! success...?", String.raw`call toString... ${ObjectWithToString} success...?`, "replacement expression is object with toString");
- assert.areEqual("simple raw string", String.raw({ raw: ["simple raw string"] }), "pass a custom object with raw property");
- assert.areEqual("string1 replacement string2", String.raw({ raw: ["string1 ", " string2"] }, ReturnString('replacement')), "pass a custom object with raw property and replacement parameter");
- assert.areEqual("string1 r1 string2", String.raw({ raw: ["string1 ", " string2"] }, 'r1', 'r2'), "pass object manually with more replacement parameters than expected");
- assert.areEqual("string1 string2", String.raw({ raw: ["string1 ", " string2"] }), "pass object manually with fewer replacement parameters than expected");
- assert.areEqual("string1 string2 string3", String.raw({ raw: ["string1 ", " string2 ", " string3"] }), "pass object manually with fewer replacement parameters than expected");
- assert.areEqual("", String.raw({ raw: [] }), "pass object manually containing with no raw strings");
- }
- },
- {
- name: "Callsite object passed to tag functions should have correct attributes",
- body: function() {
- var callsite = GetCallsite`simple template ${'with'} some ${'replacement'} expressions`;
- var descriptor;
- for (var i = 0; i < callsite.length; i++) {
- descriptor = Object.getOwnPropertyDescriptor(callsite, i);
- assert.isFalse(descriptor.writable, `callsite[${i}].descriptor.writable == false`);
- assert.isTrue(descriptor.enumerable, `callsite[${i}].descriptor.enumerable == true`);
- assert.isFalse(descriptor.configurable, `callsite[${i}].descriptor.configurable == false`);
- descriptor = Object.getOwnPropertyDescriptor(callsite.raw, i);
- assert.isFalse(descriptor.writable, `callsite.raw[${i}].descriptor.writable == false`);
- assert.isTrue(descriptor.enumerable, `callsite.raw[${i}].descriptor.enumerable == true`);
- assert.isFalse(descriptor.configurable, `callsite.raw[${i}].descriptor.configurable == false`);
- }
- descriptor = Object.getOwnPropertyDescriptor(callsite, 'raw');
- assert.isFalse(descriptor.writable, `callsite.raw.descriptor.writable == false`);
- assert.isFalse(descriptor.enumerable, `callsite.raw.descriptor.enumerable == false`);
- assert.isFalse(descriptor.configurable, `callsite.raw.descriptor.configurable == false`);
- assert.isTrue(Object.isFrozen(callsite), "callsite object is frozen");
- assert.isTrue(Object.isFrozen(callsite.raw), "callsite.raw object is frozen");
- }
- },
- {
- name: "Each string template literal corresponds to exactly one (cached) callsite object",
- body: function() {
- var callsite1 = GetCallsite`simple template literal 1`;
- var callsite2 = GetCallsite`simple template literal 2`;
- assert.isFalse(callsite1 === callsite2, "different string template literals create different callsite objects");
- var callsite3 = GetCallsite`simple template literal 3`;
- var callsite4 = GetCallsite`simple template literal 3`;
- assert.isTrue(callsite3 === callsite4, "different string template literals with the same string literal value create identical callsite objects");
- var loopCallsite = undefined;
- for (var i = 0; i < 10; i++) {
- var c = GetCallsite`loop template literal ${i}`;
- if (loopCallsite === undefined) {
- loopCallsite = c;
- } else {
- assert.isTrue(loopCallsite === c, "string template literal used in a loop reuses the same callsite object.");
- }
- assert.areEqual(2, c.length, "loop callsite has expected count of string literals");
- assert.areEqual("loop template literal ", c[0], "loop callsite has expected first string literal value");
- assert.areEqual("", c[1], "loop callsite has expected second string literal value");
- assert.areEqual(2, c.raw.length, "loop callsite.raw has expected count of string literals");
- assert.areEqual("loop template literal ", c.raw[0], "loop callsite.raw has expected first string literal value");
- assert.areEqual("", c.raw[1], "loop callsite.raw has expected second string literal value");
- }
- loopCallsite = undefined
- for (var i = 0; i < 10; i++) {
- var c = GetExpectedCachedCallsite();
- if (loopCallsite === undefined) {
- loopCallsite = c;
- } else {
- assert.isTrue(loopCallsite === c, "string template declared in other function returns same callsite object when function called.");
- }
- assert.areEqual(3, c.length, "loop callsite has expected count of string literals");
- assert.areEqual("some string template ", c[0], "loop callsite has expected first string literal value");
- assert.areEqual(" with replacements ", c[1], "loop callsite has expected second string literal value");
- assert.areEqual("", c[2], "loop callsite has expected third string literal value");
- assert.areEqual(3, c.raw.length, "loop callsite.raw has expected count of string literals");
- assert.areEqual("some string template ", c.raw[0], "loop callsite.raw has expected first string literal value");
- assert.areEqual(" with replacements ", c.raw[1], "loop callsite.raw has expected second string literal value");
- assert.areEqual("", c.raw[2], "loop callsite.raw has expected third string literal value");
- }
- }
- },
- {
- name: "BLUE: 490848 - string templates do not allow substitution expressions which contain '}' characters",
- body: function() {
- var foo;
- assert.areEqual("function () { return 'foo called'; }", `${foo = function() { return 'foo called'; }}`, "Function declaration (+assignment) in string template");
- assert.areEqual('function', typeof foo, "Assignment inside string template substitution expression");
- assert.areEqual('foo called', foo(), "Function declared in template expression can be called later");
- assert.areEqual("ObjectLiteral.toString() called", `${{toString:function(){return 'ObjectLiteral.toString() called';}}}`, "Object literal declared in string template expression");
- foo = undefined;
- assert.areEqual("foo: comma syntax", `${(foo = function() { return 'foo: comma syntax'; }, foo())}`, "Comma syntax in string template");
- assert.areEqual('function', typeof foo, "Assignment inside string template substitution expression");
- assert.areEqual('foo: comma syntax', foo(), "Function declared in template expression can be called later");
- assert.areEqual('{toString}', `{${{toString:function(){return 'toString';}}}}`, "Test all the permutations of '}' characters in a string template");
- }
- },
- {
- name: "BLUE: 525727 - using a let declaration inside a string template causes an assertion",
- body: function() {
- assert.throws(function () { eval('`${let x = 5}`;'); }, SyntaxError, "Let returns an identifier - can't be a substitution expression", "Expected '}'");
- assert.throws(function () { eval('`${a a}`;'); }, SyntaxError, "ParseExpr returns the first word as an identifier.", "Expected '}'");
- }
- },
- {
- name: "BLUE: 557210 - string templates do not check for escaped uses of substitution expressions",
- body: function() {
- // None of these should cause AV
- new Function("function z() {}; `z`;")();
- new Function("function z() {}; `${z}`;")();
- new Function("function z() {}; `${z}${z}`;")();
- new Function("function z() {}; `${z}${z}${z}`;")();
- new Function("function z() {}; `${'z'}${z}${z}`;")();
- new Function("function z() {}; `${'z'}${'z'}${z}`;")();
- new Function("function z() {}; '' + z + '';")();
- new Function("function z() {}; z`${`${z}`}`;")();
- new Function("function z() {}; z``;")();
- new Function("function z() {}; ``;")();
- new Function("(`${function(id) { return id }}`);")();
- new Function("function y() {} y`${`${'z'}${`${function(id) { return id }})${ /x/g >= 'c'}`}`}`;")();
- }
- },
- {
- name: "BLUE: 560073 - parsing a tagged string template does not respect the fInNew flag",
- body: function() {
- assert.throws(function() { new tagReturnConstructor`symbol`; }, TypeError, "Calling 'new Symbol()' throws so we should see a throw if we try to new the return from the tag function if it is Symbol", "Function is not a constructor");
- assert.isTrue(Array.isArray(new tagReturnConstructor`array`), "Simple case of calling tagged string template as constructor");
- assert.areEqual(6, new tagReturnConstructor`array${`string1`}${`string2`}`(6).length, "Calling tagged string template as constructor with replacements and arguments");
- assert.areEqual(`string`, new tagReturnConstructor`string`(`STRING`).toLowerCase(), "Create a string and pass parameters via String constructor returned from tag function");
- assert.throws(function() { new tagReturnConstructorWrapper`unused``symbol`; }, TypeError, "Calling 'new Symbol()' throws so we should see a throw if we try to new the return from the tag function if it is Symbol", "Function is not a constructor");
- assert.areEqual(new Number(0), new tagReturnConstructorWrapper`unused``number`, "Create a boxed number object via Number constructor returned from previous string template tag function");
- assert.areEqual(Object(`actual string value`), new tagReturnConstructorWrapper`unused``string`(`actual string value`), "Create a string via String constructor returned from previous string template tag function");
- }
- },
- {
- name: "String template tag function returning a constructor function should be constructable in a new expression",
- body: function() {
- function tagReturnNestedConstructorFunction(name) {
- return tagReturnConstructor([name]);
- }
- function tagReturnNestedConstructorObject(callsite) {
- return {
- constructor_array : [ Symbol, String, Array, Number ],
- constructor_tag_function : tagReturnConstructor,
- constructor_ordinary_function : tagReturnNestedConstructorFunction,
- constructor_object : { symbol : Symbol, string : String, array : Array, number : Number },
- constructor_nested : function() { return tagReturnNestedConstructorObject; }
- };
- }
- function tagReturnNestedConstructorArray() {
- return tagReturnNestedConstructorObject().constructor_array;
- }
- function tagReturnNestedConstructorArrayWrapper() {
- return [function() { return tagReturnNestedConstructorArray(); }];
- }
- assert.throws(function() { new tagReturnNestedConstructorObject`ignored`.constructor_array[0]; }, TypeError, "Calling 'new Symbol()' throws so we should see a throw if we try to new the return from the tag function if it is Symbol", "Function is not a constructor");
- assert.throws(function() { new tagReturnNestedConstructorObject`ignored`.constructor_tag_function`symbol`; }, TypeError, "Calling 'new Symbol()' throws so we should see a throw if we try to new the return from the tag function if it is Symbol", "Function is not a constructor");
- assert.isTrue(Symbol === new tagReturnNestedConstructorObject`ignored`.constructor_ordinary_function(`symbol`), "Mixing MemberExpression token types");
- assert.throws(function() { new new tagReturnNestedConstructorObject`ignored`.constructor_ordinary_function(`symbol`); }, TypeError, "new constructor_ordinary_function returns Symbol which should throw if new'd", "Function is not a constructor");
- assert.isTrue(Symbol === new tagReturnNestedConstructorObject`ignored`.constructor_nested(`also ignored`)`one more ignored thing`.constructor_object.symbol, `Mixing MemberExpression token types`);
- assert.isTrue(Array.isArray(new new tagReturnNestedConstructorObject`ignored`.constructor_nested(`also ignored`)`one more ignored thing`.constructor_object.array), `Mixing MemberExpression token types`);
- assert.areEqual(`object`, typeof new new tagReturnNestedConstructorObject`ignored`.constructor_nested(`also ignored`)`one more ignored thing`.constructor_object.number(42), `Mixing MemberExpression token types`);
- assert.areEqual(`real string`, new new tagReturnNestedConstructorObject`ignored`.constructor_nested(`also ignored`)`one more ignored thing`.constructor_object.string(`Real String`).toLowerCase(), `Mixing MemberExpression token types`);
- assert.isTrue(Symbol === new tagReturnNestedConstructorObject`ignored`[`constructor_nested`](`also ignored`)`one more ignored thing`.constructor_object.symbol, `Mixing MemberExpression token types`);
- assert.isTrue(Array.isArray(new new tagReturnNestedConstructorObject`ignored`[`constructor_nested`](`also ignored`)`one more ignored thing`.constructor_object.array), `Mixing MemberExpression token types`);
- assert.areEqual(`object`, typeof new new tagReturnNestedConstructorObject`ignored`[`constructor_nested`](`also ignored`)`one more ignored thing`.constructor_object.number(42), `Mixing MemberExpression token types`);
- assert.areEqual(`real string`, new new tagReturnNestedConstructorObject`ignored`[`constructor_nested`](`also ignored`)`one more ignored thing`.constructor_object.string(`Real String`).toLowerCase(), `Mixing MemberExpression token types`);
- assert.isTrue(Symbol === tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`].symbol, `Mixing MemberExpression token types`);
- assert.isTrue(Array.isArray(new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`].array), `Mixing MemberExpression token types`);
- assert.areEqual(`object`, typeof new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`].number(42), `Mixing MemberExpression token types`);
- assert.areEqual(`real string`, new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`].string(`Real String`).toLowerCase(), `Mixing MemberExpression token types`);
- assert.isTrue(Symbol === tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`][`symbol`], `Mixing MemberExpression token types`);
- assert.isTrue(Array.isArray(new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`][`array`]), `Mixing MemberExpression token types`);
- assert.areEqual(`object`, typeof new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`][`number`](42), `Mixing MemberExpression token types`);
- assert.areEqual(`real string`, new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`][`string`](`Real String`).toLowerCase(), `Mixing MemberExpression token types`);
- assert.throws(function() { new tagReturnNestedConstructorArray`ignored`[0]; }, TypeError, `Array access returns Symbol which throws when called as new expression`, `Function is not a constructor`);
- assert.isTrue(Array.isArray(new tagReturnNestedConstructorArray`ignored`[2]), `Array access returning Array used in a new expression`);
- assert.areEqual(Object(42), new tagReturnNestedConstructorArray`ignored`[3](42), `Array access returning Number used in a new expression`);
- assert.isTrue(Array.isArray(new tagReturnNestedConstructorArrayWrapper`ignored`[0]`also ignored`[2]), `Array access returning Array used in a new expression`);
- assert.areEqual(`string`, new tagReturnNestedConstructorArrayWrapper`ignored`[0]`also ignored`[1](`STRING`).toLowerCase(), `Array access returning String used in a new expression`);
- assert.areEqual(`string objectstring literal,,,`, String.raw`${new tagReturnNestedConstructorArray`ignored${String.raw`nothing`}`[1](`string object`)}string literal${new tagReturnNestedConstructorObject`ignored`[`constructor_object`][`array`](4)}`, `Nested string templates can have their own new expressions`);
- function tagReturnNestedConstructorObjectWrapper(callsite, expr1, expr2) {
- assert.areEqual([`string literal (pre)`,`string literal (middle)`,`string literal (post)`], callsite, `Callsite cooked string constants are correct`);
- assert.areEqual([`string literal (pre)`,`string literal (middle)`,`string literal (post)`], callsite.raw, `Callsite raw string constants are correct`);
- assert.areEqual(Object(`s1bprereplacement1s1bpost`), expr1, `First replacement expression is created via 'new String("...")'`);
- assert.isTrue(Array.isArray(expr2), `Second replacement expression is created via Array constructor`)
- assert.areEqual(new Array(6), expr2, `Second replacement expression has the right size and contents`);
- return function() { return tagReturnNestedConstructorObject(); }
- }
- assert.isTrue(Array.isArray(
- new tagReturnNestedConstructorObjectWrapper
- `string literal (pre)${new tagReturnNestedConstructorArray`ignored${String.raw`ignored replacement`}ignored`[1](`s1bpre${String.raw`replacement1`}s1bpost`)}string literal (middle)${new tagReturnNestedConstructorObject`s2pre${new Number(42)}s2post`[`constructor_object`][`array`](6)}string literal (post)``ignored`
- [`constructor_object`][`array`]
- ),
- `Many-levels of nested string templates can all have different expression types - NewExpressions, MemberExpressions, PrimaryExpressions`
- )
- var _counter = 0;
- function nestedNewOperatorFunction(callsite) {
- if (_counter > 2) {
- return tagReturnNestedConstructorFunction(callsite ? callsite[0] : ``);
- } else {
- _counter++;
- return nestedNewOperatorFunction;
- }
- }
- var obj = new new new new new nestedNewOperatorFunction
- assert.areEqual(`constructed object`, obj.name, `Calling nested constructor helper with no arguments eventually returns a constructor which returns an object with name property`);
- _counter = 0;
- assert.areEqual(new String(`help...`), new new new new new nestedNewOperatorFunction(`1`)(`2`)(`3`)([`string`])(`help...`), `Nested constructor wrapper chooses String builtin constructor`)
- _counter = 0;
- assert.isTrue(Array.isArray(new nestedNewOperatorFunction`1``2``3``array`), `Nested constructor wrapper is called inherently by string template evaluation - empty arguments`)
- _counter = 0;
- assert.areEqual(new String(`help...`), new nestedNewOperatorFunction`1``2``3``string`(`help...`), `Nested constructor wrapper is called inherently by string template evaluation - passing arguments to NewExpression`)
- }
- },
- {
- name: "Multiple post-fix operators in a row including string templates",
- body: function() {
- function foo() {
- return { 'something' : function() { return ['v1','v2','v3']; } };
- }
- assert.areEqual('v3', foo`'nothing'`['something']()[2], "Multiple post-fix operators after a string template");
- function tag(callsite, replacement) {
- return {
- v : callsite[0] + " " + replacement,
- f : function() { return ['a1', function(s) { return s; }]; }
- };
- }
- assert.areEqual('another string template - ', tag`a ${'b'} c`.f()[1]`another string template - ${'with replacements'}`[0], "Multiple post-fix operators including multiple string templates");
- }
- },
- {
- name: "String template callsite objects are unique per-Realm and indexed by the raw strings",
- body: function() {
- var callsite = undefined;
- var counter = 0;
- function tag(c)
- {
- counter++;
- assert.areEqual('uniquestringforrealmcachetest\\n', c.raw[0], 'String template callsite has correct raw value');
- if (callsite === undefined) {
- callsite = c;
- } else {
- assert.isTrue(c === callsite, 'Callsite is correctly cached per-Realm');
- }
- }
- function foo() {
- tag`uniquestringforrealmcachetest\n`;
- tag`uniquestringforrealmcachetest\n`;
- }
- foo();
- foo();
- function foo2() {
- tag`uniquestringforrealmcachetest\n`;
- tag`uniquestringforrealmcachetest\n`;
- }
- foo2();
- foo2();
- function foo3() {
- eval('tag`uniquestringforrealmcachetest\\n`');
- eval('tag`uniquestringforrealmcachetest\\n`');
- }
- foo3();
- foo3();
- counter = 0;
- var foo4 = new Function('t','t`uniquestringforrealmcachetest\\n`;');
- foo4(tag);
- foo4(tag);
- assert.areEqual(2, counter, "tag function is called correct number of times");
- counter = 0;
- var foo5 = new Function('t','eval("t`uniquestringforrealmcachetest\\\\n`;");');
- foo5(tag);
- foo5(tag);
- assert.areEqual(2, counter, "tag function is called correct number of times");
- }
- },
- {
- name: "Callsite objects are not strict equal if raw strings differ (even when cooked strings are strict equal)",
- body: function() {
- var callsite1 = GetCallsite`before
- after`;
- var callsite2 = GetCallsite`before\nafter`;
- assert.areEqual("before\nafter", callsite1[0], 'Explicit line terminator character is copied directly into cooked strings');
- assert.areEqual("before\nafter", callsite2[0], 'Escaped line terminator is translated into cooked strings');
- assert.areEqual("before\nafter", callsite1.raw[0], 'Explicit line terminator character is copied directly into raw strings');
- assert.areEqual("before\\nafter", callsite2.raw[0], 'Escaped line terminator is re-escaped into raw strings');
- assert.isTrue(callsite1[0] === callsite2[0], 'Cooked strings are strictly equal');
- assert.isFalse(callsite1.raw[0] === callsite2.raw[0], 'Raw strings are not strictly equal');
- assert.isFalse(callsite1 === callsite2, 'Callsite objects are not the same ');
- }
- },
- {
- name: "Callsite objects are constant even if replacement values differ",
- body: function() {
- var callsite1 = GetCallsite`string1${'r1'}string2${'r2'}string3`;
- var callsite2 = GetCallsite`string1${'r3'}string2${'r4'}string3`;
- assert.isTrue(callsite1 === callsite2, "Callsite objects are strictly equal");
- }
- },
- {
- name: "Octal escape sequences are not allowed in string template literals",
- body: function() {
- assert.throws(function () { eval('print(`\\00`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\01`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\1`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\2`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\3`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\4`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\5`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\6`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\7`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\10`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\50`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\30`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\70`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\123`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\377`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\0123`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- assert.throws(function () { eval('print(`\\567`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
- }
- },
- {
- name: "Extended unicode escape sequences",
- body: function() {
- assert.areEqual("a\u{d}c", `a\u{d}c`, "Cooked value for extended unicode escape sequence of 1 hex character");
- assert.areEqual("a\\u{d}c", GetRawStringValue`a\u{d}c${0}`, "Raw value for extended unicode escape sequence of 1 hex character");
- assert.areEqual("a\u{62}c", `a\u{62}c`, "Cooked value for extended unicode escape sequence of 2 hex characters");
- assert.areEqual("a\\u{62}c", GetRawStringValue`a\u{62}c${0}`, "Raw value for extended unicode escape sequence of 2 hex characters");
- assert.areEqual("a\u{062}c", `a\u{062}c`, "Cooked value for extended unicode escape sequence of 3 hex characters");
- assert.areEqual("a\\u{062}c", GetRawStringValue`a\u{062}c${0}`, "Raw value for extended unicode escape sequence of 3 hex characters");
- assert.areEqual("a\u{0062}c", `a\u{0062}c`, "Cooked value for extended unicode escape sequence of 4 hex characters");
- assert.areEqual("a\\u{0062}c", GetRawStringValue`a\u{0062}c${0}`, "Raw value for extended unicode escape sequence of 4 hex characters");
- assert.areEqual("a\u{00062}c", `a\u{00062}c`, "Cooked value for extended unicode escape sequence of 5 hex characters");
- assert.areEqual("a\\u{00062}c", GetRawStringValue`a\u{00062}c${0}`, "Raw value for extended unicode escape sequence of 5 hex characters");
- assert.areEqual("a\u{000062}c", `a\u{000062}c`, "Cooked value for extended unicode escape sequence of 6 hex characters");
- assert.areEqual("a\\u{000062}c", GetRawStringValue`a\u{000062}c${0}`, "Raw value for extended unicode escape sequence of 6 hex characters");
- assert.areEqual("a\u{00000062}c", `a\u{00000062}c`, "Cooked value for extended unicode escape sequence of 7 hex characters");
- assert.areEqual("a\\u{00000062}c", GetRawStringValue`a\u{00000062}c${0}`, "Raw value for extended unicode escape sequence of 7 hex characters");
- assert.areEqual("a\u{000000062}c", `a\u{000000062}c`, "Cooked value for extended unicode escape sequence of 8 hex characters");
- assert.areEqual("a\\u{000000062}c", GetRawStringValue`a\u{000000062}c${0}`, "Raw value for extended unicode escape sequence of 8 hex characters");
- }
- },
- {
- name: "Template value for escaped null characters",
- body: function() {
- assert.areEqual("\0", eval("GetRawStringValue`\0${0}`"), "The template raw value of <NULL> is <NULL>");
- assert.areEqual("\\\u0030", eval("GetRawStringValue`\\\0${0}`"), "The template raw value of \\<NULL> is \\ + \u0030");
- assert.areEqual("\0", eval("GetStringValue`\0${0}`"), "The template cooked value of <NULL> is <NULL>");
- assert.areEqual("\0", eval("GetStringValue`\\\0${0}`"), "The template cooked value of \\<NULL> is <NULL>");
- }
- },
- {
- name: "String template line terminator sequence normalization",
- body: function() {
- assert.isTrue(eval("GetStringValue`\n${0}`") === "\n", "String template literal doesn't normalize <LF> line terminator sequence"); // `<LF>` => '<LF>'
- assert.isTrue(eval("GetStringValue`\\\n${0}`") === "", "String template literal ignores escaped <LF> line continuation token"); // `\<LF>` => ''
- assert.isTrue(eval("GetStringValue`\\n${0}`") === "\n", "String template literal doesn't normalize <LF> escape sequence"); // `\n` => '<LF>'
- assert.isTrue(eval("GetStringValue`\r${0}`") === "\n", "String template literal normalizes <CR> line terminator sequence to <LF>"); // `<CR>` => '<LF>'
- assert.isTrue(eval("GetStringValue`\\\r${0}`") === "", "String template literal ignores escaped <CR> line continuation token"); // `\<CR>` => ''
- assert.isTrue(eval("GetStringValue`\\r${0}`") === "\r", "String template literal doesn't normalize <CR> escape sequence"); // `\r` => '<CR>'
- assert.isTrue(eval("GetStringValue`\r\n${0}`") === "\n", "String template literal normalizes <CR><LF> line terminator sequence to <LF>"); // `<CR><LF>` => '<LF>'
- assert.isTrue(eval("GetStringValue`\\\r\\\n${0}`") === "", "String template literal ignores separately escaped <CR><LF> line continuation sequence tokens"); // `\<CR>\<LF>` => ''
- assert.isTrue(eval("GetStringValue`\\\r\n${0}`") === "", "String template literal ignores escaped <CR><LF> line continuation sequence"); // `\<CR><LF>` => ''
- assert.isTrue(eval("GetStringValue`\r\\\n${0}`") === "\n", "String template literal normalizes <CR> line terminator sequence if the next character is <LF> line continuation sequence"); // `<CR>\<LF>` => '<LF>'
- assert.isTrue(eval("GetStringValue`\\r\\n${0}`") === "\r\n", "String template literal doesn't normalize <CR><LF> as escape sequences"); // `\r\n` => '<CR><LF>'
- assert.isTrue(eval("GetStringValue`\\r\n${0}`") === "\r\n", "String template literal doesn't normalize <CR> as an escape sequence if the next character is <LF> line terminator sequence"); // `\r<LF>` => '<CR><LF>'
- assert.isTrue(eval("GetStringValue`\r\\n${0}`") === "\n\n", "String template literal normalizes <CR> as a line terminator sequence if the next character is <LF> escape sequence"); // `<CR>\n` => '<LF><LF>'
- assert.isTrue(eval("GetStringValue`\\\r\\n${0}`") === "\n", "String template literal ignores <CR> as a line continuation if the next character is <LF> escape sequence"); // `\<CR>\n` => '<LF>'
- assert.isTrue(eval("GetStringValue`\\r\\\n${0}`") === "\r", "String template literal doesn't normalize <CR> as an escape sequence if the next character is <LF> line continuation token"); // `\r\<LF>` => '<CR>'
- assert.isTrue(eval("GetStringValue`\u2028${0}`") === "\u2028", "String template literal doesn't normalize <LS> line terminator sequence"); // `<LS>` => '<LS>'
- assert.isTrue(eval("GetStringValue`\\\u2028${0}`") === "", "String template literal ignores escaped <LS> line continuation token"); // `\<LS>` => ''
- assert.isTrue(eval("GetStringValue`\u2029${0}`") === "\u2029", "String template literal doesn't normalize <PS> line terminator sequence"); // `<PS>` => '<PS>'
- assert.isTrue(eval("GetStringValue`\\\u2029${0}`") === "", "String template literal ignores escaped <PS> line continuation token"); // `\<PS>` => ''
- }
- },
- {
- name: "String template line terminator sequence normalization in raw string values",
- body: function() {
- assert.isTrue(eval("GetRawStringValue`\n${0}`") === '\n', "String template raw literal doesn't normalize <LF> line terminator sequence"); // `<LF>`.raw => '<LF>'
- assert.isTrue(eval("GetRawStringValue`\\\n${0}`") === "\\\n", "String template raw literal doesn't normalize escaped <LF> line continuation token"); // `\<LF>`.raw => '\<LF>'
- assert.isTrue(eval("GetRawStringValue`\\n${0}`") === "\\n", "String template raw literal doesn't evaluate <LF> escape sequence"); // `\n`.raw => '\n'
- assert.isTrue(eval("GetRawStringValue`\r${0}`") === "\n", "String template raw literal normalizes <CR> line terminator sequence to <LF>"); // `<CR>`.raw => '<LF>'
- assert.isTrue(eval("GetRawStringValue`\\\r${0}`") === "\\\n", "String template raw literal normalizes escaped <CR> line continuation token to <LF>"); // `\<CR>`.raw => '\<LF>'
- assert.isTrue(eval("GetRawStringValue`\\r${0}`") === "\\r", "String template raw literal doesn't evaluate <CR> escape sequence"); // `\r`.raw => '\r'
- assert.isTrue(eval("GetRawStringValue`\r\n${0}`") === "\n", "String template raw literal normalizes <CR><LF> line terminator sequence to <LF>"); // `<CR><LF>`.raw => '<LF>'
- assert.isTrue(eval("GetRawStringValue`\\\r\\\n${0}`") === "\\\n\\\n", "String template raw literal normalizes separately escaped <CR><LF> line continuation sequence tokens"); // `\<CR>\<LF>`.raw => '\<LF>\<LF>'
- assert.isTrue(eval("GetRawStringValue`\\\r\n${0}`") === "\\\n", "String template raw literal normalizes escaped <CR><LF> line continuation sequence to <LF>"); // `\<CR><LF>`.raw => '\<LF>'
- assert.isTrue(eval("GetRawStringValue`\r\\\n${0}`") === "\n\\\n", "String template raw literal normalizes <CR> line terminator sequence if the next character is <LF> line continuation sequence"); // `<CR>\<LF>`.raw => '<LF>\<LF>'
- assert.isTrue(eval("GetRawStringValue`\\r\\n${0}`") === "\\r\\n", "String template raw literal doesn't evaluate <CR><LF> as escape sequences"); // `\r\n`.raw => '\r\n'
- assert.isTrue(eval("GetRawStringValue`\\r\n${0}`") === "\\r\n", "String template raw literal doesn't evaluate <CR> as an escape sequence if the next character is <LF> line terminator sequence"); // `\r<LF>`.raw => '\r<LF>'
- assert.isTrue(eval("GetRawStringValue`\r\\n${0}`") === "\n\\n", "String template raw literal normalizes <CR> as a line terminator sequence if the next character is <LF> escape sequence"); // `<CR>\n`.raw => '<LF>\n'
- assert.isTrue(eval("GetRawStringValue`\\\r\\n${0}`") === "\\\n\\n", "String template raw literal normalizes <CR> as a line continuation if the next character is <LF> escape sequence"); // `\<CR>\n`.raw => '\<LF>\n'
- assert.isTrue(eval("GetRawStringValue`\\r\\\n${0}`") === "\\r\\\n", "String template raw literal doesn't evaluate <CR> as an escape sequence if the next character is <LF> line continuation token"); // `\r\<LF>`.raw => '\r\<LF>'
- assert.isTrue(eval("GetRawStringValue`\u2028${0}`") === "\u2028", "String template raw literal doesn't normalize <LS> line terminator sequence"); // `<LS>`.raw => '<LS>'
- assert.isTrue(eval("GetRawStringValue`\\\u2028${0}`") === "\\\u2028", "String template raw literal doesn't normalize escaped <LS> line continuation token"); // `\<LS>`.raw => '\<LS>'
- assert.isTrue(eval("GetRawStringValue`\u2029${0}`") === "\u2029", "String template raw literal doesn't normalize <PS> line terminator sequence"); // `<PS>`.raw => '<PS>'
- assert.isTrue(eval("GetRawStringValue`\\\u2029${0}`") === "\\\u2029", "String template raw literal doesn't normalize escaped <PS> line continuation token"); // `\<PS>`.raw => '\<PS>'
- }
- },
- {
- name: "Bug fix : 4532336 String Template should trigger ToString on the substitution expression",
- body: function() {
- var a = {
- toString: function (){ return "foo";},
- valueOf: function() { return "bar";}
- };
- assert.areEqual(`${a}`, "foo", "toString should be called instead of valueOf on the substitution expression");
- }
- },
- {
- name: "String template converts `\\0` into a null character - not an octal escape sequence",
- body: function() {
- assert.areEqual('\0', `\0`, "Simple null escape sequence in string template is treated the same as in a normal string");
- assert.areEqual('\08', `\08`, "Null escape sequence followed by non-octal number is valid");
- assert.areEqual('\0abc', `\0abc`, "Null escape sequence followed by non-number is valid");
- assert.areEqual('\0\0', `\0\0`, "Null escape sequence followed by another null escape sequence is valid");
- var called = false;
- function tag(obj) {
- assert.areEqual('\0', obj[0], "Null escape sequence is cooked into null character");
- assert.areEqual('\\0', obj.raw[0], "Null escape sequence is not cooked in raw string");
- called = true;
- }
- tag`\0`;
- assert.isTrue(called);
- }
- },
- ];
- testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });
|