|
| 1 | +//------------------------------------------------------------------------------------------------------- |
| 2 | +// Copyright (C) Microsoft. All rights reserved. |
| 3 | +// Copyright (c) ChakraCore Project Contributors. All rights reserved. |
| 4 | +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| 5 | +//------------------------------------------------------------------------------------------------------- |
| 6 | + |
| 7 | +// @ts-check |
| 8 | +/// <reference path="../UnitTestFramework/UnitTestFramework.js" /> |
| 9 | + |
| 10 | +WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); |
| 11 | + |
| 12 | +const simpleObj = { "null": null, "undefined": undefined }; |
| 13 | +Object.freeze(simpleObj); |
| 14 | + |
| 15 | +const tests = [ |
| 16 | + { |
| 17 | + name: "Simple properties", |
| 18 | + body() { |
| 19 | + // Verify normal behavior |
| 20 | + assert.throws(() => simpleObj.nothing.something, TypeError); |
| 21 | + assert.throws(() => simpleObj.null.something, TypeError); |
| 22 | + assert.throws(() => simpleObj.undefined.something, TypeError); |
| 23 | + |
| 24 | + // With optional-chains |
| 25 | + assert.isUndefined(simpleObj.nothing?.something, "OptChain should evaluated to 'undefined'"); |
| 26 | + assert.isUndefined(simpleObj.null?.something, "OptChain should evaluated to 'undefined'"); |
| 27 | + assert.isUndefined(simpleObj.undefined?.something, "OptChain should evaluated to 'undefined'"); |
| 28 | + } |
| 29 | + }, |
| 30 | + { |
| 31 | + name: "Simple indexers", |
| 32 | + body() { |
| 33 | + // Verify normal behavior |
| 34 | + assert.throws(() => simpleObj.nothing["something"], TypeError); |
| 35 | + assert.throws(() => simpleObj.null["something"], TypeError); |
| 36 | + assert.throws(() => simpleObj.undefined["something"], TypeError); |
| 37 | + |
| 38 | + // With optional-chains |
| 39 | + assert.isUndefined(simpleObj.nothing?.["something"], "OptChain should evaluated to 'undefined'"); |
| 40 | + assert.isUndefined(simpleObj.null?.["something"], "OptChain should evaluated to 'undefined'"); |
| 41 | + assert.isUndefined(simpleObj.undefined?.["something"], "OptChain should evaluated to 'undefined'"); |
| 42 | + } |
| 43 | + }, |
| 44 | + { |
| 45 | + name: "Simple method calls", |
| 46 | + body() { |
| 47 | + // Verify normal behavior |
| 48 | + assert.throws(() => simpleObj.nothing(), TypeError); |
| 49 | + assert.throws(() => simpleObj.null(), TypeError); |
| 50 | + assert.throws(() => simpleObj.undefined(), TypeError); |
| 51 | + |
| 52 | + // With optional-chains |
| 53 | + assert.isUndefined(simpleObj.nothing?.(), "OptChain should evaluated to 'undefined'"); |
| 54 | + assert.isUndefined(simpleObj.null?.(), "OptChain should evaluated to 'undefined'"); |
| 55 | + assert.isUndefined(simpleObj.undefined?.(), "OptChain should evaluated to 'undefined'"); |
| 56 | + } |
| 57 | + }, |
| 58 | + { |
| 59 | + name: "Simple properties before call", |
| 60 | + body() { |
| 61 | + // Verify normal behavior |
| 62 | + assert.throws(() => simpleObj.nothing.something(), TypeError); |
| 63 | + assert.throws(() => simpleObj.null.something(), TypeError); |
| 64 | + assert.throws(() => simpleObj.undefined.something(), TypeError); |
| 65 | + |
| 66 | + // With optional-chains |
| 67 | + assert.isUndefined(simpleObj.nothing?.something(), "OptChain should evaluated to 'undefined'"); |
| 68 | + assert.isUndefined(simpleObj.null?.something(), "OptChain should evaluated to 'undefined'"); |
| 69 | + assert.isUndefined(simpleObj.undefined?.something(), "OptChain should evaluated to 'undefined'"); |
| 70 | + } |
| 71 | + }, |
| 72 | + { |
| 73 | + name: "Simple indexers before call", |
| 74 | + body() { |
| 75 | + // Verify normal behavior |
| 76 | + assert.throws(() => simpleObj.nothing["something"], TypeError); |
| 77 | + assert.throws(() => simpleObj.null["something"], TypeError); |
| 78 | + assert.throws(() => simpleObj.undefined["something"], TypeError); |
| 79 | + |
| 80 | + // With optional-chains |
| 81 | + assert.isUndefined(simpleObj.nothing?.["something"](), "OptChain should evaluated to 'undefined'"); |
| 82 | + assert.isUndefined(simpleObj.null?.["something"](), "OptChain should evaluated to 'undefined'"); |
| 83 | + assert.isUndefined(simpleObj.undefined?.["something"](), "OptChain should evaluated to 'undefined'"); |
| 84 | + } |
| 85 | + }, |
| 86 | + { |
| 87 | + name: "Short-circuiting ignores indexer expression and method args", |
| 88 | + body() { |
| 89 | + let i = 0; |
| 90 | + |
| 91 | + assert.isUndefined(simpleObj.nothing?.[i++]); |
| 92 | + assert.isUndefined(simpleObj.null?.[i++]); |
| 93 | + assert.isUndefined(simpleObj.undefined?.[i++]); |
| 94 | + |
| 95 | + assert.isUndefined(simpleObj.nothing?.[i++]()); |
| 96 | + assert.isUndefined(simpleObj.null?.[i++]()); |
| 97 | + assert.isUndefined(simpleObj.undefined?.[i++]()); |
| 98 | + |
| 99 | + assert.isUndefined(simpleObj.nothing?.something(i++)); |
| 100 | + assert.isUndefined(simpleObj.null?.something(i++)); |
| 101 | + assert.isUndefined(simpleObj.undefined?.something(i++)); |
| 102 | + |
| 103 | + assert.strictEqual(0, i, "Indexer may never be evaluated"); |
| 104 | + |
| 105 | + // ToDo: How can I run async tests? |
| 106 | + // simpleObj.nothing?.[await Promise.reject()]; |
| 107 | + // simpleObj.null?.[await Promise.reject()]; |
| 108 | + // simpleObj.undefined?.[await Promise.reject()]; |
| 109 | + } |
| 110 | + }, |
| 111 | + { |
| 112 | + name: "Short-circuiting ignores nested properties", |
| 113 | + body() { |
| 114 | + assert.isUndefined(simpleObj.nothing?.a.b.c.d.e.f.g.h); |
| 115 | + assert.isUndefined(simpleObj.null?.a.b.c.d.e.f.g.h); |
| 116 | + assert.isUndefined(simpleObj.undefined?.a.b.c.d.e.f.g.h); |
| 117 | + } |
| 118 | + }, |
| 119 | + { |
| 120 | + name: "Short-circuiting multiple levels", |
| 121 | + body() { |
| 122 | + let i = 0; |
| 123 | + const specialObj = { |
| 124 | + get null() { |
| 125 | + i++; |
| 126 | + return null; |
| 127 | + }, |
| 128 | + get undefined() { |
| 129 | + i++; |
| 130 | + return undefined; |
| 131 | + } |
| 132 | + }; |
| 133 | + |
| 134 | + assert.isUndefined(specialObj?.null?.a.b.c.d?.e.f.g.h); |
| 135 | + assert.isUndefined(specialObj?.undefined?.a.b.c.d?.e.f.g.h); |
| 136 | + |
| 137 | + assert.areEqual(2, i, "Properties should be called") |
| 138 | + } |
| 139 | + }, |
| 140 | + { |
| 141 | + name: "Propagate 'this' correctly", |
| 142 | + body() { |
| 143 | + const specialObj = { |
| 144 | + b() { return this._b; }, |
| 145 | + _b: { c: 42 } |
| 146 | + }; |
| 147 | + |
| 148 | + assert.areEqual(42, specialObj.b().c); |
| 149 | + assert.areEqual(42, specialObj?.b().c); |
| 150 | + assert.areEqual(42, specialObj.b?.().c); |
| 151 | + assert.areEqual(42, specialObj?.b?.().c); |
| 152 | + assert.areEqual(42, (specialObj?.b)().c); |
| 153 | + assert.areEqual(42, (specialObj.b)?.().c); |
| 154 | + assert.areEqual(42, (specialObj?.b)?.().c); |
| 155 | + } |
| 156 | + }, |
| 157 | + // Null check |
| 158 | + { |
| 159 | + name: "Only check for 'null' and 'undefined'", |
| 160 | + body() { |
| 161 | + assert.areEqual(0, ""?.length, "Expected empty string length"); |
| 162 | + } |
| 163 | + }, |
| 164 | + // Parsing |
| 165 | + { |
| 166 | + name: "Parse ternary correctly", |
| 167 | + body() { |
| 168 | + assert.areEqual(0.42, eval(`"this is not falsy"?.42 : 0`)); |
| 169 | + } |
| 170 | + }, |
| 171 | + { |
| 172 | + name: "Tagged Template in OptChain is illegal", |
| 173 | + body() { |
| 174 | + assert.throws(() => eval("simpleObj.undefined?.`template`"), SyntaxError, "No TaggedTemplate here", "Invalid tagged template in optional chain."); |
| 175 | + assert.throws(() => eval(`simpleObj.undefined?. |
| 176 | + \`template\``), SyntaxError, "No TaggedTemplate here", "Invalid tagged template in optional chain."); |
| 177 | + } |
| 178 | + }, |
| 179 | + { |
| 180 | + name: "No new in OptChain", |
| 181 | + body() { |
| 182 | + assert.throws(() => eval(` |
| 183 | + class Test { } |
| 184 | + new Test?.(); |
| 185 | + `), SyntaxError, "'new' in OptChain is illegal", "Invalid optional chain in new expression."); |
| 186 | + } |
| 187 | + }, |
| 188 | + { |
| 189 | + name: "No super in OptChain", |
| 190 | + body() { |
| 191 | + assert.throws(() => eval(` |
| 192 | + class Base { } |
| 193 | + class Test extends Base { |
| 194 | + constructor(){ |
| 195 | + super?.(); |
| 196 | + } |
| 197 | + } |
| 198 | + `), SyntaxError, "Super in OptChain is illegal", "Invalid use of the 'super' keyword"); |
| 199 | + |
| 200 | + assert.throws(() => eval(` |
| 201 | + class Base { } |
| 202 | + class Test extends Base { |
| 203 | + constructor(){ |
| 204 | + super(); |
| 205 | +
|
| 206 | + super?.abc; |
| 207 | + } |
| 208 | + } |
| 209 | + `), SyntaxError, "Super in OptChain is illegal", "Invalid use of the 'super' keyword"); |
| 210 | + } |
| 211 | + }, |
| 212 | + { |
| 213 | + name: "No assignment", |
| 214 | + body() { |
| 215 | + const a = {}; |
| 216 | + assert.throws(() => eval(`a?.b++`), SyntaxError, "Assignment is illegal", "Invalid left-hand side in assignment."); |
| 217 | + assert.throws(() => eval(`a?.b += 1`), SyntaxError, "Assignment is illegal", "Invalid left-hand side in assignment."); |
| 218 | + assert.throws(() => eval(`a?.b = 5`), SyntaxError, "Assignment is illegal", "Invalid left-hand side in assignment."); |
| 219 | + } |
| 220 | + } |
| 221 | +]; |
| 222 | + |
| 223 | +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); |
0 commit comments