ladybird/Userland/Libraries/LibJS/Tests/using-declaration.js

386 lines
12 KiB
JavaScript

describe("basic usage", () => {
test.xfail("disposes after block exit", () => {
let disposed = false;
let inBlock = false;
{
expect(disposed).toBeFalse();
using a = { [Symbol.dispose]() { disposed = true; } };
inBlock = true;
expect(disposed).toBeFalse();
}
expect(inBlock).toBeTrue();
expect(disposed).toBeTrue();
});
test.xfail("disposes in reverse order after block exit", () => {
const disposed = [];
{
expect(disposed).toHaveLength(0);
using a = { [Symbol.dispose]() { disposed.push('a'); } };
using b = { [Symbol.dispose]() { disposed.push('b'); } };
expect(disposed).toHaveLength(0);
}
expect(disposed).toEqual(['b', 'a']);
});
test.xfail("disposes in reverse order after block exit even in same declaration", () => {
const disposed = [];
{
expect(disposed).toHaveLength(0);
using a = { [Symbol.dispose]() { disposed.push('a'); } },
b = { [Symbol.dispose]() { disposed.push('b'); } };
expect(disposed).toHaveLength(0);
}
expect(disposed).toEqual(['b', 'a']);
});
});
describe("behavior with exceptions", () => {
function ExpectedError(name) { this.name = name; }
test.xfail("is run even after throw", () => {
let disposed = false;
let inBlock = false;
let inCatch = false;
try {
expect(disposed).toBeFalse();
using a = { [Symbol.dispose]() { disposed = true; } };
inBlock = true;
expect(disposed).toBeFalse();
throw new ExpectedError();
expect().fail();
} catch (e) {
expect(disposed).toBeTrue();
expect(e).toBeInstanceOf(ExpectedError);
inCatch = true;
}
expect(disposed).toBeTrue();
expect(inBlock).toBeTrue();
expect(inCatch).toBeTrue();
});
test.xfail("throws error if dispose method does", () => {
let disposed = false;
let endOfTry = false;
let inCatch = false;
try {
expect(disposed).toBeFalse();
using a = { [Symbol.dispose]() {
disposed = true;
throw new ExpectedError();
} };
expect(disposed).toBeFalse();
endOfTry = true;
} catch (e) {
expect(disposed).toBeTrue();
expect(e).toBeInstanceOf(ExpectedError);
inCatch = true;
}
expect(disposed).toBeTrue();
expect(endOfTry).toBeTrue();
expect(inCatch).toBeTrue();
});
test.xfail("if block and using throw get suppressed error", () => {
let disposed = false;
let inCatch = false;
try {
expect(disposed).toBeFalse();
using a = { [Symbol.dispose]() {
disposed = true;
throw new ExpectedError('dispose');
} };
expect(disposed).toBeFalse();
throw new ExpectedError('throw');
} catch (e) {
expect(disposed).toBeTrue();
expect(e).toBeInstanceOf(SuppressedError);
expect(e.error).toBeInstanceOf(ExpectedError);
expect(e.error.name).toBe('dispose');
expect(e.suppressed).toBeInstanceOf(ExpectedError);
expect(e.suppressed.name).toBe('throw');
inCatch = true;
}
expect(disposed).toBeTrue();
expect(inCatch).toBeTrue();
});
test.xfail("multiple throwing disposes give suppressed error", () => {
let inCatch = false;
try {
{
using a = { [Symbol.dispose]() {
throw new ExpectedError('a');
} };
using b = { [Symbol.dispose]() {
throw new ExpectedError('b');
} };
}
expect().fail();
} catch (e) {
expect(e).toBeInstanceOf(SuppressedError);
expect(e.error).toBeInstanceOf(ExpectedError);
expect(e.error.name).toBe('a');
expect(e.suppressed).toBeInstanceOf(ExpectedError);
expect(e.suppressed.name).toBe('b');
inCatch = true;
}
expect(inCatch).toBeTrue();
});
test.xfail("3 throwing disposes give chaining suppressed error", () => {
let inCatch = false;
try {
{
using a = { [Symbol.dispose]() {
throw new ExpectedError('a');
} };
using b = { [Symbol.dispose]() {
throw new ExpectedError('b');
} };
using c = { [Symbol.dispose]() {
throw new ExpectedError('c');
} };
}
expect().fail();
} catch (e) {
expect(e).toBeInstanceOf(SuppressedError);
expect(e.error).toBeInstanceOf(ExpectedError);
expect(e.error.name).toBe('a');
expect(e.suppressed).toBeInstanceOf(SuppressedError);
const inner = e.suppressed;
expect(inner.error).toBeInstanceOf(ExpectedError);
expect(inner.error.name).toBe('b');
expect(inner.suppressed).toBeInstanceOf(ExpectedError);
expect(inner.suppressed.name).toBe('c');
inCatch = true;
}
expect(inCatch).toBeTrue();
});
test.xfail("normal error and multiple disposing erorrs give chaining suppressed errors", () => {
let inCatch = false;
try {
using a = { [Symbol.dispose]() {
throw new ExpectedError('a');
} };
using b = { [Symbol.dispose]() {
throw new ExpectedError('b');
} };
throw new ExpectedError('top');
} catch (e) {
expect(e).toBeInstanceOf(SuppressedError);
expect(e.error).toBeInstanceOf(ExpectedError);
expect(e.error.name).toBe('a');
expect(e.suppressed).toBeInstanceOf(SuppressedError);
const inner = e.suppressed;
expect(inner.error).toBeInstanceOf(ExpectedError);
expect(inner.error.name).toBe('b');
expect(inner.suppressed).toBeInstanceOf(ExpectedError);
expect(inner.suppressed.name).toBe('top');
inCatch = true;
}
expect(inCatch).toBeTrue();
});
});
describe("works in a bunch of scopes", () => {
test.xfail("works in block", () => {
let dispose = false;
expect(dispose).toBeFalse();
{
expect(dispose).toBeFalse();
using a = { [Symbol.dispose]() { dispose = true; } }
expect(dispose).toBeFalse();
}
expect(dispose).toBeTrue();
});
test.xfail("works in static class block", () => {
let dispose = false;
expect(dispose).toBeFalse();
class A {
static {
expect(dispose).toBeFalse();
using a = { [Symbol.dispose]() { dispose = true; } }
expect(dispose).toBeFalse();
}
}
expect(dispose).toBeTrue();
});
test.xfail("works in function", () => {
let dispose = [];
function f(val) {
const disposeLength = dispose.length;
using a = { [Symbol.dispose]() { dispose.push(val); } }
expect(dispose.length).toBe(disposeLength);
}
expect(dispose).toEqual([]);
f(0);
expect(dispose).toEqual([0]);
f(1);
expect(dispose).toEqual([0, 1]);
});
test.xfail("switch block is treated as full block in function", () => {
let disposeFull = [];
let disposeInner = false;
function pusher(val) {
return {
val,
[Symbol.dispose]() { disposeFull.push(val); }
};
}
switch (2) {
case 3:
using notDisposed = { [Symbol.dispose]() { expect().fail("not-disposed 1"); } };
case 2:
expect(disposeFull).toEqual([]);
using a = pusher('a');
expect(disposeFull).toEqual([]);
using b = pusher('b');
expect(disposeFull).toEqual([]);
expect(b.val).toBe('b');
expect(disposeInner).toBeFalse();
// fallthrough
case 1: {
expect(disposeFull).toEqual([]);
expect(disposeInner).toBeFalse();
using inner = { [Symbol.dispose]() { disposeInner = true; } }
expect(disposeInner).toBeFalse();
}
expect(disposeInner).toBeTrue();
using c = pusher('c');
expect(c.val).toBe('c');
break;
case 0:
using notDisposed2 = { [Symbol.dispose]() { expect().fail("not-disposed 2"); } };
}
expect(disposeInner).toBeTrue();
expect(disposeFull).toEqual(['c', 'b', 'a']);
});
});
describe("invalid using bindings", () => {
test.xfail("nullish values do not throw", () => {
using a = null, b = undefined;
expect(a).toBeNull();
expect(b).toBeUndefined();
});
test.xfail("non-object throws", () => {
[0, "a", true, NaN, 4n, Symbol.dispose].forEach(value => {
expect(() => {
using v = value;
}).toThrowWithMessage(TypeError, "is not an object");
});
});
test.xfail("object without dispose throws", () => {
expect(() => {
using a = {};
}).toThrowWithMessage(TypeError, "does not have dispose method");
});
test.xfail("object with non callable dispose throws", () => {
[0, "a", true, NaN, 4n, Symbol.dispose, [], {}].forEach(value => {
expect(() => {
using a = { [Symbol.dispose]: value };
}).toThrowWithMessage(TypeError, "is not a function");
});
});
});
describe("using is still a valid variable name", () => {
test("var", () => {
"use strict";
var using = 1;
expect(using).toBe(1);
});
test("const", () => {
"use strict";
const using = 1;
expect(using).toBe(1);
});
test("let", () => {
"use strict";
let using = 1;
expect(using).toBe(1);
});
test.xfail("using", () => {
"use strict";
using using = null;
expect(using).toBeNull();
});
test("function", () => {
"use strict";
function using() { return 1; }
expect(using()).toBe(1);
});
});
describe("syntax errors / werid artifacts which remain valid", () => {
test("no patterns in using", () => {
expect("using {a} = {}").not.toEval();
expect("using a, {a} = {}").not.toEval();
expect("using a = null, [b] = [null]").not.toEval();
});
test("using with array pattern is valid array access", () => {
const using = [0, 9999];
const a = 1;
expect(eval("using [a] = 1")).toBe(1);
expect(using[1]).toBe(1);
expect(eval("using [{a: a}, a] = 2")).toBe(2);
expect(using[1]).toBe(2);
expect(eval("using [a, a] = 3")).toBe(3);
expect(using[1]).toBe(3);
expect(eval("using [[a, a], a] = 4")).toBe(4);
expect(using[1]).toBe(4);
expect(eval("using [2, 1, a] = 5")).toBe(5);
expect(using[1]).toBe(5);
});
test("declaration without initializer", () => {
expect("using a").not.toEval();
});
test("no repeat declarations in single using", () => {
expect("using a = null, a = null;").not.toEval();
});
test("cannot have a using declaration named let", () => {
expect("using let = null").not.toEval();
});
});