mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-27 14:22:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			424 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			424 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| 
 | |
| // Note the globalThisValue and globalObject do not need to be the same.
 | |
| const globalThisValue = this;
 | |
| const globalObject = (0, eval)("this");
 | |
| 
 | |
| // These tests are done in global state to ensure that is possible
 | |
| const globalArrow = () => {
 | |
|     expect(this).toBe(globalThisValue);
 | |
|     return this;
 | |
| };
 | |
| 
 | |
| function globalFunction() {
 | |
|     expect(this).toBe(undefined);
 | |
| 
 | |
|     expect(globalArrow()).toBe(globalThisValue);
 | |
| 
 | |
|     const arrowInGlobalFunction = () => this;
 | |
|     expect(arrowInGlobalFunction()).toBe(undefined);
 | |
| 
 | |
|     return arrowInGlobalFunction;
 | |
| }
 | |
| 
 | |
| expect(globalArrow()).toBe(globalThisValue);
 | |
| expect(globalFunction()()).toBe(undefined);
 | |
| 
 | |
| const arrowFromGlobalFunction = globalFunction();
 | |
| 
 | |
| const customThisValue = {
 | |
|     isCustomThis: true,
 | |
|     variant: 0,
 | |
| };
 | |
| 
 | |
| const otherCustomThisValue = {
 | |
|     isCustomThis: true,
 | |
|     variant: 1,
 | |
| };
 | |
| 
 | |
| describe("describe with arrow function", () => {
 | |
|     expect(this).toBe(globalThisValue);
 | |
| 
 | |
|     test("nested test with normal function should get global object", function () {
 | |
|         expect(this).toBe(undefined);
 | |
|     });
 | |
| 
 | |
|     test("nested test with arrow function should get same this value as enclosing function", () => {
 | |
|         expect(this).toBe(globalThisValue);
 | |
|     });
 | |
| });
 | |
| 
 | |
| describe("describe with normal function", function () {
 | |
|     expect(this).toBe(undefined);
 | |
|     test("nested test with normal function should get global object", function () {
 | |
|         expect(this).toBe(undefined);
 | |
|     });
 | |
| 
 | |
|     test("nested test with arrow function should get same this value as enclosing function", () => {
 | |
|         expect(this).toBe(undefined);
 | |
|     });
 | |
| });
 | |
| 
 | |
| describe("basic behavior", () => {
 | |
|     expect(this).toBe(globalThisValue);
 | |
| 
 | |
|     expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
| 
 | |
|     expect(customThisValue).not.toBe(otherCustomThisValue);
 | |
| 
 | |
|     test("binding arrow function does not influence this value", () => {
 | |
|         const boundGlobalArrow = globalArrow.bind({ shouldNotBeHere: true });
 | |
| 
 | |
|         expect(boundGlobalArrow()).toBe(globalThisValue);
 | |
|     });
 | |
| 
 | |
|     function functionInArrow() {
 | |
|         expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|         return this;
 | |
|     }
 | |
| 
 | |
|     function functionWithArrow() {
 | |
|         expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|         return () => {
 | |
|             expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|             return this;
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     function strictFunction() {
 | |
|         "use strict";
 | |
|         return this;
 | |
|     }
 | |
| 
 | |
|     test("functions get globalObject as this value", () => {
 | |
|         expect(functionInArrow()).toBeUndefined();
 | |
|         expect(functionWithArrow()()).toBeUndefined();
 | |
|     });
 | |
| 
 | |
|     test("strict functions get undefined as this value", () => {
 | |
|         expect(strictFunction()).toBeUndefined();
 | |
|     });
 | |
| 
 | |
|     test("bound function gets overwritten this value", () => {
 | |
|         const boundFunction = functionInArrow.bind(customThisValue);
 | |
|         expect(boundFunction()).toBe(customThisValue);
 | |
| 
 | |
|         const boundFunctionWithArrow = functionWithArrow.bind(customThisValue);
 | |
|         expect(boundFunctionWithArrow()()).toBe(customThisValue);
 | |
| 
 | |
|         // However we cannot bind the arrow function itself
 | |
|         const failingArrowBound = boundFunctionWithArrow().bind(otherCustomThisValue);
 | |
|         expect(failingArrowBound()).toBe(customThisValue);
 | |
| 
 | |
|         const boundStrictFunction = strictFunction.bind(customThisValue);
 | |
|         expect(boundStrictFunction()).toBe(customThisValue);
 | |
|     });
 | |
| });
 | |
| 
 | |
| describe("functions on created objects", () => {
 | |
|     const obj = {
 | |
|         func: function () {
 | |
|             expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|             return this;
 | |
|         },
 | |
| 
 | |
|         funcWithArrow: function () {
 | |
|             expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|             return () => this;
 | |
|         },
 | |
| 
 | |
|         arrow: () => {
 | |
|             expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|             return this;
 | |
|         },
 | |
|         otherProperty: "yes",
 | |
|     };
 | |
| 
 | |
|     test("function get this value of associated object", () => {
 | |
|         expect(obj.func()).toBe(obj);
 | |
|     });
 | |
| 
 | |
|     test("arrow function on object get above this value", () => {
 | |
|         expect(obj.arrow()).toBe(globalThisValue);
 | |
|     });
 | |
| 
 | |
|     test("arrow function from normal function from object has object as this value", () => {
 | |
|         expect(obj.funcWithArrow()()).toBe(obj);
 | |
|     });
 | |
| 
 | |
|     test("bound overwrites value of normal object function", () => {
 | |
|         const boundFunction = obj.func.bind(customThisValue);
 | |
|         expect(boundFunction()).toBe(customThisValue);
 | |
| 
 | |
|         const boundFunctionWithArrow = obj.funcWithArrow.bind(customThisValue);
 | |
|         expect(boundFunctionWithArrow()()).toBe(customThisValue);
 | |
| 
 | |
|         const boundArrowFunction = obj.arrow.bind(customThisValue);
 | |
|         expect(boundArrowFunction()).toBe(globalThisValue);
 | |
|     });
 | |
| 
 | |
|     test("also works for object defined in function", () => {
 | |
|         (function () {
 | |
|             expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
| 
 | |
|             // It is bound below
 | |
|             expect(this).toBe(customThisValue);
 | |
| 
 | |
|             const obj2 = {
 | |
|                 func: function () {
 | |
|                     expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|                     return this;
 | |
|                 },
 | |
| 
 | |
|                 arrow: () => {
 | |
|                     expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|                     return this;
 | |
|                 },
 | |
|                 otherProperty: "also",
 | |
|             };
 | |
| 
 | |
|             expect(obj2.func()).toBe(obj2);
 | |
|             expect(obj2.arrow()).toBe(customThisValue);
 | |
|         }.bind(customThisValue)());
 | |
|     });
 | |
| });
 | |
| 
 | |
| describe("behavior with classes", () => {
 | |
|     class Basic {
 | |
|         constructor(value) {
 | |
|             expect(this).toBeInstanceOf(Basic);
 | |
|             this.arrowFunctionInClass = () => {
 | |
|                 return this;
 | |
|             };
 | |
| 
 | |
|             this.value = value;
 | |
| 
 | |
|             expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|         }
 | |
| 
 | |
|         func() {
 | |
|             expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|             return this;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     const basic = new Basic(14);
 | |
|     const basic2 = new Basic(457);
 | |
| 
 | |
|     expect(basic).not.toBe(basic2);
 | |
| 
 | |
|     test("calling functions on class should give instance as this value", () => {
 | |
|         expect(basic.func()).toBe(basic);
 | |
|         expect(basic2.func()).toBe(basic2);
 | |
|     });
 | |
| 
 | |
|     test("calling arrow function created in constructor should give instance as this value", () => {
 | |
|         expect(basic.arrowFunctionInClass()).toBe(basic);
 | |
|         expect(basic2.arrowFunctionInClass()).toBe(basic2);
 | |
|     });
 | |
| 
 | |
|     test("can bind function in class", () => {
 | |
|         const boundFunction = basic.func.bind(customThisValue);
 | |
|         expect(boundFunction()).toBe(customThisValue);
 | |
| 
 | |
|         const boundFunction2 = basic2.func.bind(otherCustomThisValue);
 | |
|         expect(boundFunction2()).toBe(otherCustomThisValue);
 | |
|     });
 | |
| });
 | |
| 
 | |
| describe("derived classes behavior", () => {
 | |
|     class Base {
 | |
|         baseFunction() {
 | |
|             expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
| 
 | |
|             return this;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     class Derived extends Base {
 | |
|         constructor(value) {
 | |
|             expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|             const arrowMadeBeforeSuper = () => {
 | |
|                 expect(this).toBeInstanceOf(Derived);
 | |
|                 return this;
 | |
|             };
 | |
|             super();
 | |
|             expect(arrowMadeBeforeSuper()).toBe(this);
 | |
| 
 | |
|             this.arrowMadeBeforeSuper = arrowMadeBeforeSuper;
 | |
|             this.arrowMadeAfterSuper = () => {
 | |
|                 expect(this).toBeInstanceOf(Derived);
 | |
|                 return this;
 | |
|             };
 | |
|             this.value = value;
 | |
|         }
 | |
| 
 | |
|         derivedFunction() {
 | |
|             expect(arrowFromGlobalFunction()).toBeUndefined();
 | |
|             return this;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     test("can create derived with arrow functions using this before super", () => {
 | |
|         const testDerived = new Derived(-89);
 | |
|         expect(testDerived.arrowMadeBeforeSuper()).toBe(testDerived);
 | |
|         expect(testDerived.arrowMadeAfterSuper()).toBe(testDerived);
 | |
|     });
 | |
| 
 | |
|     test("base and derived functions get correct this values", () => {
 | |
|         const derived = new Derived(12);
 | |
| 
 | |
|         expect(derived.derivedFunction()).toBe(derived);
 | |
|         expect(derived.baseFunction()).toBe(derived);
 | |
|     });
 | |
| 
 | |
|     test("can bind derived and base functions", () => {
 | |
|         const derived = new Derived(846);
 | |
| 
 | |
|         const boundDerivedFunction = derived.derivedFunction.bind(customThisValue);
 | |
|         expect(boundDerivedFunction()).toBe(customThisValue);
 | |
| 
 | |
|         const boundBaseFunction = derived.baseFunction.bind(otherCustomThisValue);
 | |
|         expect(boundBaseFunction()).toBe(otherCustomThisValue);
 | |
|     });
 | |
| });
 | |
| 
 | |
| describe("proxy behavior", () => {
 | |
|     test("with no handler it makes no difference", () => {
 | |
|         const globalArrowProxyNoHandler = new Proxy(globalArrow, {});
 | |
|         expect(globalArrowProxyNoHandler()).toBe(globalThisValue);
 | |
|     });
 | |
| 
 | |
|     test("proxy around global arrow still gives correct this value", () => {
 | |
|         let lastThisArg = null;
 | |
| 
 | |
|         const handler = {
 | |
|             apply(target, thisArg, argArray) {
 | |
|                 expect(target).toBe(globalArrow);
 | |
|                 lastThisArg = thisArg;
 | |
|                 expect(this).toBe(handler);
 | |
| 
 | |
|                 return target(...argArray);
 | |
|             },
 | |
|         };
 | |
| 
 | |
|         const globalArrowProxy = new Proxy(globalArrow, handler);
 | |
|         expect(globalArrowProxy()).toBe(globalThisValue);
 | |
|         expect(lastThisArg).toBeUndefined();
 | |
| 
 | |
|         const boundProxy = globalArrowProxy.bind(customThisValue);
 | |
|         expect(boundProxy()).toBe(globalThisValue);
 | |
|         expect(lastThisArg).toBe(customThisValue);
 | |
| 
 | |
|         expect(globalArrowProxy.call(15)).toBe(globalThisValue);
 | |
|         expect(lastThisArg).toBe(15);
 | |
|     });
 | |
| });
 | |
| 
 | |
| describe("derived classes which access this before super should fail", () => {
 | |
|     class Base {}
 | |
| 
 | |
|     test("direct access of this should throw reference error", () => {
 | |
|         class IncorrectConstructor extends Base {
 | |
|             constructor() {
 | |
|                 this.something = "this will fail";
 | |
|                 super();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         expect(() => {
 | |
|             new IncorrectConstructor();
 | |
|         }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
 | |
|     });
 | |
| 
 | |
|     test("access of this via a arrow function", () => {
 | |
|         class IncorrectConstructor extends Base {
 | |
|             constructor() {
 | |
|                 const arrow = () => this;
 | |
|                 arrow();
 | |
|                 super();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         expect(() => {
 | |
|             new IncorrectConstructor();
 | |
|         }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
 | |
|     });
 | |
| 
 | |
|     test("access of this via a eval", () => {
 | |
|         class IncorrectConstructor extends Base {
 | |
|             constructor() {
 | |
|                 eval("this.foo = 'bar'");
 | |
|                 super();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         expect(() => {
 | |
|             new IncorrectConstructor();
 | |
|         }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
 | |
|     });
 | |
| 
 | |
|     test("access of this via a eval in arrow function", () => {
 | |
|         class IncorrectConstructor extends Base {
 | |
|             constructor() {
 | |
|                 const arrow = () => eval("() => this")();
 | |
|                 arrow();
 | |
|                 super();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         expect(() => {
 | |
|             new IncorrectConstructor();
 | |
|         }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
 | |
|     });
 | |
| 
 | |
|     test("access of this via arrow function even if bound with something else", () => {
 | |
|         class IncorrectConstructor extends Base {
 | |
|             constructor() {
 | |
|                 const arrow = () => this;
 | |
|                 const boundArrow = arrow.bind(customThisValue);
 | |
|                 boundArrow();
 | |
|                 super();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         expect(() => {
 | |
|             new IncorrectConstructor();
 | |
|         }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
 | |
|     });
 | |
| });
 | |
| 
 | |
| describe("in strict mode primitive this values are not converted to objects", () => {
 | |
|     const array = [true, false];
 | |
| 
 | |
|     // Technically the comma is implementation defined here. (Also for tests below.)
 | |
|     expect(array.toLocaleString()).toBe("true,false");
 | |
| 
 | |
|     test("directly overwriting toString", () => {
 | |
|         let count = 0;
 | |
|         Boolean.prototype.toString = function () {
 | |
|             count++;
 | |
|             return typeof this;
 | |
|         };
 | |
| 
 | |
|         expect(array.toLocaleString()).toBe("boolean,boolean");
 | |
|         expect(count).toBe(2);
 | |
|     });
 | |
| 
 | |
|     test("overwriting toString with a getter", () => {
 | |
|         let count = 0;
 | |
| 
 | |
|         Object.defineProperty(Boolean.prototype, "toString", {
 | |
|             get() {
 | |
|                 count++;
 | |
|                 const that = typeof this;
 | |
|                 return function () {
 | |
|                     return that;
 | |
|                 };
 | |
|             },
 | |
|         });
 | |
| 
 | |
|         expect(array.toLocaleString()).toBe("boolean,boolean");
 | |
|         expect(count).toBe(2);
 | |
|     });
 | |
| });
 | 
