let calledToStringError = {};
let throwingToString = {
    toString: () => {
        throw calledToStringError;
    },
};
let calledValueOfError = {};
let throwingValueOf = {
    toString: undefined,
    valueOf: () => {
        throw calledValueOfError;
    },
};
let calledToStringAccessorError = {};
let throwingToStringAccessor = {
    get toString() {
        throw calledToStringAccessorError;
    },
};
let calledValueOfAccessorError = {};
let throwingValueOfAccessor = {
    toString: undefined,
    get valueOf() {
        throw calledValueOfAccessorError;
    },
};

test("Exceptions thrown by computed properties are caught", () => {
    var i = 0;
    var j = 0;
    var k = 0;
    // ToPropertyKey of the computed key happens before the value expression is evaluated,
    // so when toString() throws, the value expression (i++) should NOT have executed.
    expect(() => {
        return { first: k++, [throwingToString]: i++, second: j++ };
    }).toThrow(calledToStringError);
    expect(i).toBe(0);
    expect(j).toBe(0);
    expect(k).toBe(1);
    expect(() => {
        return { first: k++, [throwingValueOf]: i++, second: j++ };
    }).toThrow(calledValueOfError);
    expect(i).toBe(0);
    expect(j).toBe(0);
    expect(k).toBe(2);
    expect(() => {
        return { first: k++, [throwingToStringAccessor]: i++, second: j++ };
    }).toThrow(calledToStringAccessorError);
    expect(i).toBe(0);
    expect(j).toBe(0);
    expect(k).toBe(3);
    expect(() => {
        return { first: k++, [throwingValueOfAccessor]: i++, second: j++ };
    }).toThrow(calledValueOfAccessorError);
    expect(i).toBe(0);
    expect(j).toBe(0);
    expect(k).toBe(4);
});

test("Test toString and valueOf are only called once", () => {
    var counter = 0;
    var key1 = {
        valueOf: function () {
            expect(counter++).toBe(0);
            return "a";
        },
        toString: null,
    };
    var key2 = {
        valueOf: function () {
            expect(counter++).toBe(1);
            return "b";
        },
        toString: null,
    };
    var key3 = {
        get toString() {
            expect(counter++).toBe(2);
            return function () {
                return "c";
            };
        },
    };
    var key4 = {
        get valueOf() {
            expect(counter++).toBe(3);
            return function () {
                return "d";
            };
        },
        toString: null,
    };
    var key5 = {
        valueOf: function () {
            expect(counter++).toBe(4);
            return 0;
        },
        toString: null,
    };
    var key6 = {
        valueOf: function () {
            expect(counter++).toBe(5);
            return 2;
        },
        toString: null,
    };
    var key7 = {
        get toString() {
            expect(counter++).toBe(6);
            return function () {
                return 4;
            };
        },
    };
    var key8 = {
        get valueOf() {
            expect(counter++).toBe(7);
            return function () {
                return 8;
            };
        },
        toString: null,
    };

    var object = {
        [key1]: "a",
        [key2]: "b",
        [key3]: "c",
        [key4]: "d",
        [key5]: "e",
        [key6]: "f",
        [key7]: "g",
        [key8]: "h",
    };
    expect(counter).toBe(8);
    expect(object.a).toBe("a");
    expect(object.b).toBe("b");
    expect(object.c).toBe("c");
    expect(object.d).toBe("d");
    expect(object[0]).toBe("e");
    expect(object[2]).toBe("f");
    expect(object[4]).toBe("g");
    expect(object[8]).toBe("h");
    expect(Object.getOwnPropertyNames(object) + "").toBe(["0", "2", "4", "8", "a", "b", "c", "d"] + "");
});
