Skip to content

Commit 7a48a21

Browse files
committed
Add more optional-chaining tests
This patch open-sources additional optional-chaning tests done at Apple to ensure that it is working well with edge cases.
1 parent 7e74eb8 commit 7a48a21

File tree

3 files changed

+313
-0
lines changed

3 files changed

+313
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
obj?.aaa?.bbb;
2+
obj?.aaa.bbb;
3+
(obj?.aaa)?.bbb;
4+
(obj?.aaa).bbb;
5+
obj.aaa.bbb;
6+
obj.aaa?.bbb;
7+
func?.()?.bbb;
8+
func?.().bbb;
9+
(func?.())?.bbb;
10+
(func?.()).bbb;
11+
obj?.aaa?.();
12+
obj?.aaa();
13+
(obj?.aaa)?.();
14+
(obj?.aaa)();
15+
(obj?.aaa?.bbb.ccc?.ddd)?.eee;
16+
((obj?.aaa?.bbb.ccc)?.ddd)?.eee;
17+
function testBasicSuccessCases() {
18+
shouldBe(undefined?.valueOf(), undefined);
19+
shouldBe(null?.valueOf(), undefined);
20+
shouldBe(true?.valueOf(), true);
21+
shouldBe(false?.valueOf(), false);
22+
shouldBe(0?.valueOf(), 0);
23+
shouldBe(1?.valueOf(), 1);
24+
shouldBe(''?.valueOf(), '');
25+
shouldBe('hi'?.valueOf(), 'hi');
26+
shouldBe({}?.constructor, Object);
27+
shouldBe({ x: 'hi' }?.x, 'hi');
28+
shouldBe([]?.length, 0);
29+
shouldBe(['hi']?.length, 1);
30+
shouldBe(masquerader?.foo, 3);
31+
shouldBe(undefined?.['valueOf'](), undefined);
32+
shouldBe(null?.['valueOf'](), undefined);
33+
shouldBe(true?.['valueOf'](), true);
34+
shouldBe(false?.['valueOf'](), false);
35+
shouldBe(0?.['valueOf'](), 0);
36+
shouldBe(1?.['valueOf'](), 1);
37+
shouldBe(''?.['valueOf'](), '');
38+
shouldBe('hi'?.['valueOf'](), 'hi');
39+
shouldBe({}?.['constructor'], Object);
40+
shouldBe({ x: 'hi' }?.['x'], 'hi');
41+
shouldBe([]?.['length'], 0);
42+
shouldBe(['hi']?.[0], 'hi');
43+
shouldBe(masquerader?.['foo'], 3);
44+
shouldBe(undefined?.(), undefined);
45+
shouldBe(null?.(), undefined);
46+
shouldBe((() => 3)?.(), 3);
47+
}
48+
noInline(testBasicSuccessCases);
49+
function testBasicFailureCases() {
50+
shouldThrowTypeError(() => true?.(), 'true is not a function');
51+
shouldThrowTypeError(() => false?.(), 'false is not a function');
52+
shouldThrowTypeError(() => 0?.(), '0 is not a function');
53+
shouldThrowTypeError(() => 1?.(), '1 is not a function');
54+
shouldThrowTypeError(() => ''?.(), '\'\' is not a function');
55+
shouldThrowTypeError(() => 'hi'?.(), '\'hi\' is not a function');
56+
shouldThrowTypeError(() => ({}?.()), '({}) is not a function');
57+
shouldThrowTypeError(() => ({ x: 'hi' }?.()), '({ x: \'hi\' }) is not a function');
58+
shouldThrowTypeError(() => []?.(), '[] is not a function');
59+
shouldThrowTypeError(() => ['hi']?.(), '[\'hi\'] is not a function');
60+
shouldThrowTypeError(() => masquerader?.(), 'masquerader is not a function');
61+
}
62+
noInline(testBasicFailureCases);
63+
for (let i = 0; i < 100000; i++)
64+
testBasicSuccessCases();
65+
for (let i = 0; i < 100; i++)
66+
testBasicFailureCases();
67+
shouldThrowTypeError(() => ({}?.i()), '({})?.i is not a function');
68+
shouldBe({}.i?.(), undefined);
69+
shouldBe({}?.i?.(), undefined);
70+
shouldThrowTypeError(() => ({}?.['i']()), '({})?.[\'i\'] is not a function');
71+
shouldBe({}['i']?.(), undefined);
72+
shouldBe({}?.['i']?.(), undefined);
73+
shouldThrowTypeError(() => ({}?.a['b']), 'undefined is not an object');
74+
shouldBe({}?.a?.['b'], undefined);
75+
shouldBe(null?.a['b']().c, undefined);
76+
shouldThrowTypeError(() => ({}?.['a'].b), 'undefined is not an object');
77+
shouldBe({}?.['a']?.b, undefined);
78+
shouldBe(null?.['a'].b()['c'], undefined);
79+
shouldThrowTypeError(() => (() => {
80+
})?.()(), '(() => {})?.() is not a function');
81+
shouldBe((() => {
82+
})?.()?.(), undefined);
83+
shouldBe(null?.()().a['b'], undefined);
84+
const o0 = {
85+
a: {
86+
b() {
87+
return this._b.bind(this);
88+
},
89+
_b() {
90+
return this.__b;
91+
},
92+
__b: { c: 42 }
93+
}
94+
};
95+
shouldBe(o0?.a?.['b']?.()?.()?.c, 42);
96+
shouldBe(o0?.i?.['j']?.()?.()?.k, undefined);
97+
shouldBe((o0.a?._b)?.().c, 42);
98+
shouldBe((o0.a?._b)().c, 42);
99+
shouldBe({ undefined: 3 }?.[null?.a], 3);
100+
shouldBe((() => 3)?.(null?.a), 3);
101+
const o1 = {
102+
count: 0,
103+
get x() {
104+
this.count++;
105+
return () => {
106+
};
107+
}
108+
};
109+
o1.x?.y;
110+
shouldBe(o1.count, 1);
111+
o1.x?.['y'];
112+
shouldBe(o1.count, 2);
113+
o1.x?.();
114+
shouldBe(o1.count, 3);
115+
null?.(o1.x);
116+
shouldBe(o1.count, 3);
117+
shouldBe(delete undefined?.foo, true);
118+
shouldBe(delete null?.foo, true);
119+
shouldBe(delete undefined?.['foo'], true);
120+
shouldBe(delete null?.['foo'], true);
121+
shouldBe(delete undefined?.(), true);
122+
shouldBe(delete null?.(), true);
123+
const o2 = {
124+
x: 0,
125+
y: 0,
126+
z() {
127+
}
128+
};
129+
shouldBe(delete o2?.x, true);
130+
shouldBe(o2.x, undefined);
131+
shouldBe(delete o2?.x, true);
132+
shouldBe(delete o2?.['y'], true);
133+
shouldBe(o2.y, undefined);
134+
shouldBe(delete o2?.['y'], true);
135+
shouldBe(delete o2.z?.(), true);
136+
function greet(name) {
137+
return `hey, ${ name }${ this.suffix ?? '.' }`;
138+
}
139+
shouldBe(eval?.('greet("world")'), 'hey, world.');
140+
shouldBe(greet?.call({ suffix: '!' }, 'world'), 'hey, world!');
141+
shouldBe(greet.call?.({ suffix: '!' }, 'world'), 'hey, world!');
142+
shouldBe(null?.call({ suffix: '!' }, 'world'), undefined);
143+
shouldBe({}.call?.({ suffix: '!' }, 'world'), undefined);
144+
shouldBe(greet?.apply({ suffix: '?' }, ['world']), 'hey, world?');
145+
shouldBe(greet.apply?.({ suffix: '?' }, ['world']), 'hey, world?');
146+
shouldBe(null?.apply({ suffix: '?' }, ['world']), undefined);
147+
shouldBe({}.apply?.({ suffix: '?' }, ['world']), undefined);
148+
shouldBe(false ? 0.4 : 5, 5);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
obj?.aaa?.bbb;obj?.aaa.bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb;obj.aaa.bbb;obj.aaa?.bbb;func?.()?.bbb;func?.().bbb;(func?.())?.bbb;(func?.()).bbb;obj?.aaa?.();obj?.aaa();(obj?.aaa)?.();(obj?.aaa)();(obj?.aaa?.bbb.ccc?.ddd)?.eee;((obj?.aaa?.bbb.ccc)?.ddd)?.eee;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(masquerader?.foo,3);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(masquerader?.['foo'],3);shouldBe(undefined?.(),undefined);shouldBe(null?.(),undefined);shouldBe((()=>3)?.(),3)}noInline(testBasicSuccessCases);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']?.(),"['hi'] is not a function");shouldThrowTypeError(()=>masquerader?.(),'masquerader is not a function')}noInline(testBasicFailureCases);for(let i=0;i<1e5;i++)testBasicSuccessCases();for(let i=0;i<100;i++)testBasicFailureCases();shouldThrowTypeError(()=>({}?.i()),'({})?.i is not a function');shouldBe({}.i?.(),undefined);shouldBe({}?.i?.(),undefined);shouldThrowTypeError(()=>({}?.['i']()),"({})?.['i'] is not a function");shouldBe({}['i']?.(),undefined);shouldBe({}?.['i']?.(),undefined);shouldThrowTypeError(()=>({}?.a['b']),'undefined is not an object');shouldBe({}?.a?.['b'],undefined);shouldBe(null?.a['b']().c,undefined);shouldThrowTypeError(()=>({}?.['a'].b),'undefined is not an object');shouldBe({}?.['a']?.b,undefined);shouldBe(null?.['a'].b()['c'],undefined);shouldThrowTypeError(()=>(()=>{})?.()(),'(() => {})?.() is not a function');shouldBe((()=>{})?.()?.(),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({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);const o2={x:0,y:0,z(){}};shouldBe(delete o2?.x,true);shouldBe(o2.x,undefined);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);shouldBe(false?.4:5,5)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright (C) 2020 Sony Interactive Entertainment Inc.
3+
* Copyright (C) 2020 Apple Inc.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions
7+
* are met:
8+
* 1. Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* 2. Redistributions in binary form must reproduce the above copyright
11+
* notice, this list of conditions and the following disclaimer in the
12+
* documentation and/or other materials provided with the distribution.
13+
*
14+
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16+
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18+
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24+
* THE POSSIBILITY OF SUCH DAMAGE.
25+
*/
26+
27+
obj?.aaa?.bbb;
28+
obj?.aaa.bbb;
29+
(obj?.aaa)?.bbb;
30+
(obj?.aaa).bbb;
31+
obj.aaa.bbb;
32+
obj.aaa?.bbb;
33+
func?.()?.bbb;
34+
func?.().bbb;
35+
(func?.())?.bbb;
36+
(func?.()).bbb;
37+
obj?.aaa?.();
38+
obj?.aaa();
39+
(obj?.aaa)?.();
40+
(obj?.aaa)();
41+
(obj?.aaa?.bbb.ccc?.ddd)?.eee;
42+
((obj?.aaa?.bbb.ccc)?.ddd)?.eee;
43+
44+
function testBasicSuccessCases() {
45+
shouldBe(undefined?.valueOf(), undefined);
46+
shouldBe(null?.valueOf(), undefined);
47+
shouldBe(true?.valueOf(), true);
48+
shouldBe(false?.valueOf(), false);
49+
shouldBe(0?.valueOf(), 0);
50+
shouldBe(1?.valueOf(), 1);
51+
shouldBe(''?.valueOf(), '');
52+
shouldBe('hi'?.valueOf(), 'hi');
53+
shouldBe(({})?.constructor, Object);
54+
shouldBe(({ x: 'hi' })?.x, 'hi');
55+
shouldBe([]?.length, 0);
56+
shouldBe(['hi']?.length, 1);
57+
shouldBe(masquerader?.foo, 3);
58+
59+
shouldBe(undefined?.['valueOf'](), undefined);
60+
shouldBe(null?.['valueOf'](), undefined);
61+
shouldBe(true?.['valueOf'](), true);
62+
shouldBe(false?.['valueOf'](), false);
63+
shouldBe(0?.['valueOf'](), 0);
64+
shouldBe(1?.['valueOf'](), 1);
65+
shouldBe(''?.['valueOf'](), '');
66+
shouldBe('hi'?.['valueOf'](), 'hi');
67+
shouldBe(({})?.['constructor'], Object);
68+
shouldBe(({ x: 'hi' })?.['x'], 'hi');
69+
shouldBe([]?.['length'], 0);
70+
shouldBe(['hi']?.[0], 'hi');
71+
shouldBe(masquerader?.['foo'], 3);
72+
73+
shouldBe(undefined?.(), undefined);
74+
shouldBe(null?.(), undefined);
75+
shouldBe((() => 3)?.(), 3);
76+
}
77+
noInline(testBasicSuccessCases);
78+
79+
function testBasicFailureCases() {
80+
shouldThrowTypeError(() => true?.(), 'true is not a function');
81+
shouldThrowTypeError(() => false?.(), 'false is not a function');
82+
shouldThrowTypeError(() => 0?.(), '0 is not a function');
83+
shouldThrowTypeError(() => 1?.(), '1 is not a function');
84+
shouldThrowTypeError(() => ''?.(), '\'\' is not a function');
85+
shouldThrowTypeError(() => 'hi'?.(), '\'hi\' is not a function');
86+
shouldThrowTypeError(() => ({})?.(), '({}) is not a function');
87+
shouldThrowTypeError(() => ({ x: 'hi' })?.(), '({ x: \'hi\' }) is not a function');
88+
shouldThrowTypeError(() => []?.(), '[] is not a function');
89+
shouldThrowTypeError(() => ['hi']?.(), '[\'hi\'] is not a function');
90+
shouldThrowTypeError(() => masquerader?.(), 'masquerader is not a function');
91+
}
92+
noInline(testBasicFailureCases);
93+
94+
for (let i = 0; i < 1e5; i++)
95+
testBasicSuccessCases();
96+
97+
for (let i = 0; i < 100; i++)
98+
testBasicFailureCases();
99+
100+
shouldThrowTypeError(() => ({})?.i(), '({})?.i is not a function');
101+
shouldBe(({}).i?.(), undefined);
102+
shouldBe(({})?.i?.(), undefined);
103+
shouldThrowTypeError(() => ({})?.['i'](), '({})?.[\'i\'] is not a function');
104+
shouldBe(({})['i']?.(), undefined);
105+
shouldBe(({})?.['i']?.(), undefined);
106+
107+
shouldThrowTypeError(() => ({})?.a['b'], 'undefined is not an object');
108+
shouldBe(({})?.a?.['b'], undefined);
109+
shouldBe(null?.a['b']().c, undefined);
110+
shouldThrowTypeError(() => ({})?.['a'].b, 'undefined is not an object');
111+
shouldBe(({})?.['a']?.b, undefined);
112+
shouldBe(null?.['a'].b()['c'], undefined);
113+
shouldThrowTypeError(() => (() => {})?.()(), '(() => {})?.() is not a function');
114+
shouldBe((() => {})?.()?.(), undefined);
115+
shouldBe(null?.()().a['b'], undefined);
116+
117+
const o0 = { a: { b() { return this._b.bind(this); }, _b() { return this.__b; }, __b: { c: 42 } } };
118+
shouldBe(o0?.a?.['b']?.()?.()?.c, 42);
119+
shouldBe(o0?.i?.['j']?.()?.()?.k, undefined);
120+
shouldBe((o0.a?._b)?.().c, 42);
121+
shouldBe((o0.a?._b)().c, 42);
122+
123+
shouldBe(({ undefined: 3 })?.[null?.a], 3);
124+
shouldBe((() => 3)?.(null?.a), 3);
125+
126+
const o1 = { count: 0, get x() { this.count++; return () => {}; } };
127+
o1.x?.y;
128+
shouldBe(o1.count, 1);
129+
o1.x?.['y'];
130+
shouldBe(o1.count, 2);
131+
o1.x?.();
132+
shouldBe(o1.count, 3);
133+
null?.(o1.x);
134+
shouldBe(o1.count, 3);
135+
136+
shouldBe(delete undefined?.foo, true);
137+
shouldBe(delete null?.foo, true);
138+
shouldBe(delete undefined?.['foo'], true);
139+
shouldBe(delete null?.['foo'], true);
140+
shouldBe(delete undefined?.(), true);
141+
shouldBe(delete null?.(), true);
142+
143+
const o2 = { x: 0, y: 0, z() {} };
144+
shouldBe(delete o2?.x, true);
145+
shouldBe(o2.x, undefined);
146+
shouldBe(delete o2?.x, true);
147+
shouldBe(delete o2?.['y'], true);
148+
shouldBe(o2.y, undefined);
149+
shouldBe(delete o2?.['y'], true);
150+
shouldBe(delete o2.z?.(), true);
151+
152+
function greet(name) { return `hey, ${name}${this.suffix ?? '.'}`; }
153+
shouldBe(eval?.('greet("world")'), 'hey, world.');
154+
shouldBe(greet?.call({ suffix: '!' }, 'world'), 'hey, world!');
155+
shouldBe(greet.call?.({ suffix: '!' }, 'world'), 'hey, world!');
156+
shouldBe(null?.call({ suffix: '!' }, 'world'), undefined);
157+
shouldBe(({}).call?.({ suffix: '!' }, 'world'), undefined);
158+
shouldBe(greet?.apply({ suffix: '?' }, ['world']), 'hey, world?');
159+
shouldBe(greet.apply?.({ suffix: '?' }, ['world']), 'hey, world?');
160+
shouldBe(null?.apply({ suffix: '?' }, ['world']), undefined);
161+
shouldBe(({}).apply?.({ suffix: '?' }, ['world']), undefined);
162+
163+
// NOT an optional chain
164+
shouldBe(false?.4:5, 5);

0 commit comments

Comments
 (0)