diff options
Diffstat (limited to 'js/src/tests/non262/expressions/optional-chain.js')
-rw-r--r-- | js/src/tests/non262/expressions/optional-chain.js | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/js/src/tests/non262/expressions/optional-chain.js b/js/src/tests/non262/expressions/optional-chain.js new file mode 100644 index 000000000..04e090935 --- /dev/null +++ b/js/src/tests/non262/expressions/optional-chain.js @@ -0,0 +1,223 @@ +var BUGNUMBER = 1566143; +var summary = "Implement the Optional Chain operator (?.) proposal"; + +print(BUGNUMBER + ": " + summary); + +// These tests are originally from webkit. +// webkit specifics have been removed and error messages have been updated. +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`expected ${expected} but got ${actual}`); +} + +function shouldThrowSyntaxError(script) { + let error; + try { + eval(script); + } catch (e) { + error = e; + } + + if (!(error instanceof SyntaxError)) + throw new Error('Expected SyntaxError!'); +} + +function shouldNotThrowSyntaxError(script) { + let error; + try { + eval(script); + } catch (e) { + error = e; + } + + if ((error instanceof SyntaxError)) + throw new Error('Unxpected SyntaxError!'); +} + +function shouldThrowTypeError(func, messagePrefix) { + let error; + try { + func(); + } catch (e) { + error = e; + } + + if (!(error instanceof TypeError)) + throw new Error('Expected TypeError!'); +} +function testBasicSuccessCases() { + shouldBe(undefined?.valueOf(), undefined); + shouldBe(null?.valueOf(), undefined); + shouldBe(true?.valueOf(), true); + shouldBe(false?.valueOf(), false); + shouldBe(0?.valueOf(), 0); + shouldBe(1?.valueOf(), 1); + shouldBe(''?.valueOf(), ''); + shouldBe('hi'?.valueOf(), 'hi'); + shouldBe(({})?.constructor, Object); + shouldBe(({ x: 'hi' })?.x, 'hi'); + shouldBe([]?.length, 0); + shouldBe(['hi']?.length, 1); + + shouldBe(undefined?.['valueOf'](), undefined); + shouldBe(null?.['valueOf'](), undefined); + shouldBe(true?.['valueOf'](), true); + shouldBe(false?.['valueOf'](), false); + shouldBe(0?.['valueOf'](), 0); + shouldBe(1?.['valueOf'](), 1); + shouldBe(''?.['valueOf'](), ''); + shouldBe('hi'?.['valueOf'](), 'hi'); + shouldBe(({})?.['constructor'], Object); + shouldBe(({ x: 'hi' })?.['x'], 'hi'); + shouldBe([]?.['length'], 0); + shouldBe(['hi']?.[0], 'hi'); + + shouldBe(undefined?.(), undefined); + shouldBe(null?.(), undefined); + shouldBe((() => 3)?.(), 3); +} + +function testBasicFailureCases() { + shouldThrowTypeError(() => true?.(), 'true is not a function'); + shouldThrowTypeError(() => false?.(), 'false is not a function'); + shouldThrowTypeError(() => 0?.(), '0 is not a function'); + shouldThrowTypeError(() => 1?.(), '1 is not a function'); + shouldThrowTypeError(() => ''?.(), '"" is not a function'); + shouldThrowTypeError(() => 'hi'?.(), '"hi" is not a function'); + shouldThrowTypeError(() => ({})?.(), '({}) is not a function'); + shouldThrowTypeError(() => ({ x: 'hi' })?.(), '({x:"hi"}) is not a function'); + shouldThrowTypeError(() => []?.(), '[] is not a function'); + shouldThrowTypeError(() => ['hi']?.(), '[...] is not a function'); +} + +testBasicSuccessCases(); + +testBasicFailureCases(); + +shouldThrowTypeError(() => ({})?.i(), '(intermediate value).i is not a function'); +shouldBe(({}).i?.(), undefined); +shouldBe(({})?.i?.(), undefined); +shouldThrowTypeError(() => ({})?.['i'](), '(intermediate value)["i"] is not a function'); +shouldBe(({})['i']?.(), undefined); +shouldBe(({})?.['i']?.(), undefined); + +shouldThrowTypeError(() => ({})?.a['b'], 'can\'t access property "b", (intermediate value).a is undefined'); +shouldBe(({})?.a?.['b'], undefined); +shouldBe(null?.a['b']().c, undefined); +shouldThrowTypeError(() => ({})?.['a'].b, 'can\'t access property "b", (intermediate value)["a"] is undefined'); +shouldBe(({})?.['a']?.b, undefined); +shouldBe(null?.['a'].b()['c'], undefined); +shouldBe(null?.()().a['b'], undefined); + +const o0 = { a: { b() { return this._b.bind(this); }, _b() { return this.__b; }, __b: { c: 42 } } }; +shouldBe(o0?.a?.['b']?.()?.()?.c, 42); +shouldBe(o0?.i?.['j']?.()?.()?.k, undefined); +shouldBe((o0.a?._b)?.().c, 42); +shouldBe((o0.a?._b)().c, 42); +shouldBe((o0.a?.b?.())?.().c, 42); +shouldBe((o0.a?.['b']?.())?.().c, 42); + +shouldBe(({ undefined: 3 })?.[null?.a], 3); +shouldBe((() => 3)?.(null?.a), 3); + +const o1 = { count: 0, get x() { this.count++; return () => {}; } }; +o1.x?.y; +shouldBe(o1.count, 1); +o1.x?.['y']; +shouldBe(o1.count, 2); +o1.x?.(); +shouldBe(o1.count, 3); +null?.(o1.x); +shouldBe(o1.count, 3); + +shouldBe(delete undefined?.foo, true); +shouldBe(delete null?.foo, true); +shouldBe(delete undefined?.['foo'], true); +shouldBe(delete null?.['foo'], true); +shouldBe(delete undefined?.(), true); +shouldBe(delete null?.(), true); +shouldBe(delete ({}).a?.b?.b, true); +shouldBe(delete ({a : {b: undefined}}).a?.b?.b, true); +shouldBe(delete ({a : {b: undefined}}).a?.["b"]?.["b"], true); + +const o2 = { x: 0, y: 0, z() {} }; +shouldBe(delete o2?.x, true); +shouldBe(o2.x, undefined); +shouldBe(o2.y, 0); +shouldBe(delete o2?.x, true); +shouldBe(delete o2?.['y'], true); +shouldBe(o2.y, undefined); +shouldBe(delete o2?.['y'], true); +shouldBe(delete o2.z?.(), true); + +function greet(name) { return `hey, ${name}${this.suffix ?? '.'}`; } +shouldBe(eval?.('greet("world")'), 'hey, world.'); +shouldBe(greet?.call({ suffix: '!' }, 'world'), 'hey, world!'); +shouldBe(greet.call?.({ suffix: '!' }, 'world'), 'hey, world!'); +shouldBe(null?.call({ suffix: '!' }, 'world'), undefined); +shouldBe(({}).call?.({ suffix: '!' }, 'world'), undefined); +shouldBe(greet?.apply({ suffix: '?' }, ['world']), 'hey, world?'); +shouldBe(greet.apply?.({ suffix: '?' }, ['world']), 'hey, world?'); +shouldBe(null?.apply({ suffix: '?' }, ['world']), undefined); +shouldBe(({}).apply?.({ suffix: '?' }, ['world']), undefined); +shouldThrowSyntaxError('class C {} class D extends C { foo() { return super?.bar; } }'); +shouldThrowSyntaxError('class C {} class D extends C { foo() { return super?.["bar"]; } }'); +shouldThrowSyntaxError('class C {} class D extends C { constructor() { super?.(); } }'); +shouldThrowSyntaxError('const o = { C: class {} }; new o?.C();') +shouldThrowSyntaxError('const o = { C: class {} }; new o?.["C"]();') +shouldThrowSyntaxError('class C {} new C?.();') +shouldThrowSyntaxError('function foo() { new?.target; }'); +shouldThrowSyntaxError('function tag() {} tag?.``;'); +shouldThrowSyntaxError('const o = { tag() {} }; o?.tag``;'); + +// NOT an optional chain +shouldBe(false?.4:5, 5); + +function testSideEffectCountFunction() { + let count = 0; + let a = { + b: { + c: { + d: () => { + count++; + return a; + } + } + } + } + + a.b.c.d?.()?.b?.c?.d + + shouldBe(count, 1); +} + +function testSideEffectCountGetters() { + let count = 0; + let a = { + get b() { + count++; + return { c: {} }; + } + } + + a.b?.c?.d; + shouldBe(count, 1); + a.b?.c?.d; + shouldBe(count, 2); +} + +testSideEffectCountFunction(); +testSideEffectCountGetters(); + +// stress test SM +shouldBe(({a : {b: undefined}}).a.b?.()()(), undefined); +shouldBe(({a : {b: undefined}}).a.b?.()?.()(), undefined); +shouldBe(({a : {b: () => undefined}}).a.b?.()?.(), undefined); +shouldThrowTypeError(() => delete ({a : {b: undefined}}).a?.b.b.c, 'can\'t access property "b", (intermediate value).a.b is undefined'); +shouldBe(delete ({a : {b: undefined}}).a?.["b"]?.["b"], true); +shouldThrowTypeError(() => (({a : {b: () => undefined}}).a.b?.())(), 'undefined is not a function'); + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); |