123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- // 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(globalObject);
- expect(globalArrow()).toBe(globalThisValue);
- const arrowInGlobalFunction = () => this;
- expect(arrowInGlobalFunction()).toBe(globalObject);
- return arrowInGlobalFunction;
- }
- expect(globalArrow()).toBe(globalThisValue);
- expect(globalFunction()()).toBe(globalObject);
- 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(globalObject);
- });
- 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(globalObject);
- test("nested test with normal function should get global object", function () {
- expect(this).toBe(globalObject);
- });
- test("nested test with arrow function should get same this value as enclosing function", () => {
- expect(this).toBe(globalObject);
- });
- });
- describe("basic behavior", () => {
- expect(this).toBe(globalThisValue);
- expect(arrowFromGlobalFunction()).toBe(globalObject);
- 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()).toBe(globalObject);
- return this;
- }
- function functionWithArrow() {
- expect(arrowFromGlobalFunction()).toBe(globalObject);
- return () => {
- expect(arrowFromGlobalFunction()).toBe(globalObject);
- return this;
- };
- }
- function strictFunction() {
- "use strict";
- return this;
- }
- test("functions get globalObject as this value", () => {
- expect(functionInArrow()).toBe(globalObject);
- expect(functionWithArrow()()).toBe(globalObject);
- });
- 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()).toBe(globalObject);
- return this;
- },
- funcWithArrow: function () {
- expect(arrowFromGlobalFunction()).toBe(globalObject);
- return () => this;
- },
- arrow: () => {
- expect(arrowFromGlobalFunction()).toBe(globalObject);
- 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()).toBe(globalObject);
- // It is bound below
- expect(this).toBe(customThisValue);
- const obj2 = {
- func: function () {
- expect(arrowFromGlobalFunction()).toBe(globalObject);
- return this;
- },
- arrow: () => {
- expect(arrowFromGlobalFunction()).toBe(globalObject);
- 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()).toBe(globalObject);
- }
- func() {
- expect(arrowFromGlobalFunction()).toBe(globalObject);
- 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()).toBe(globalObject);
- return this;
- }
- }
- class Derived extends Base {
- constructor(value) {
- expect(arrowFromGlobalFunction()).toBe(globalObject);
- 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()).toBe(globalObject);
- 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.skip("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("with statements", () => {
- test("this value is still the global object", () => {
- const obj = { haveValue: true, hello: "friends" };
- with (obj) {
- expect(this).toBe(globalThisValue);
- expect(hello).toBe("friends");
- }
- });
- test("with gets this value form outer scope", () => {
- const obj = { haveValue: true, hello: "friends" };
- function callme() {
- with (obj) {
- expect(this).toBe(customThisValue);
- expect(hello).toBe("friends");
- }
- }
- const boundMe = callme.bind(customThisValue);
- boundMe();
- });
- });
- describe("in non strict mode primitive this values are 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("object,object");
- 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("object,object");
- expect(count).toBe(2);
- });
- });
|