mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-03 16:16:43 +00:00
LibJS: Implement the AsyncDisposableStack interface
This is very similar to the DisposableStack interface, except disposal of resources is promise-based.
This commit is contained in:
parent
5ea0aa5f08
commit
26c2484c2f
Notes:
github-actions[bot]
2025-01-17 19:47:28 +00:00
Author: https://github.com/trflynn89
Commit: 26c2484c2f
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3290
22 changed files with 873 additions and 0 deletions
|
@ -0,0 +1,21 @@
|
|||
test("constructor properties", () => {
|
||||
expect(AsyncDisposableStack).toHaveLength(0);
|
||||
expect(AsyncDisposableStack.name).toBe("AsyncDisposableStack");
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("called without new", () => {
|
||||
expect(() => {
|
||||
AsyncDisposableStack();
|
||||
}).toThrowWithMessage(
|
||||
TypeError,
|
||||
"AsyncDisposableStack constructor must be called with 'new'"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("typeof", () => {
|
||||
expect(typeof new AsyncDisposableStack()).toBe("object");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
test("length is 0", () => {
|
||||
expect(AsyncDisposableStack.prototype[Symbol.asyncDispose]).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("is the same as disposeAsync", () => {
|
||||
expect(AsyncDisposableStack.prototype[Symbol.asyncDispose]).toBe(
|
||||
AsyncDisposableStack.prototype.disposeAsync
|
||||
);
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
test("basic functionality", () => {
|
||||
expect(AsyncDisposableStack.prototype[Symbol.toStringTag]).toBe("AsyncDisposableStack");
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
test("length is 2", () => {
|
||||
expect(AsyncDisposableStack.prototype.adopt).toHaveLength(2);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("adopted dispose method gets called when stack is disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposedCalled = 0;
|
||||
let disposeArgument = undefined;
|
||||
expect(disposedCalled).toBe(0);
|
||||
const result = stack.adopt(null, arg => {
|
||||
disposeArgument = arg;
|
||||
++disposedCalled;
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
|
||||
expect(disposedCalled).toBe(0);
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
expect(disposeArgument).toBeNull();
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
});
|
||||
|
||||
test("can adopt any value", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
const disposed = [];
|
||||
function dispose(value) {
|
||||
disposed.push(value);
|
||||
}
|
||||
|
||||
const values = [null, undefined, 1, "a", Symbol.dispose, () => {}, new WeakMap(), [], {}];
|
||||
|
||||
values.forEach(value => {
|
||||
stack.adopt(value, dispose);
|
||||
});
|
||||
|
||||
await stack.disposeAsync();
|
||||
|
||||
expect(disposed).toEqual(values.reverse());
|
||||
});
|
||||
|
||||
test("adopted stack is already disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.adopt(stack, value => {
|
||||
expect(stack).toBe(value);
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
await stack.disposeAsync();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("if call back is not a function throws type error", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
[
|
||||
1,
|
||||
1n,
|
||||
"a",
|
||||
Symbol.dispose,
|
||||
NaN,
|
||||
0,
|
||||
{},
|
||||
[],
|
||||
{ f() {} },
|
||||
{ [Symbol.dispose]() {} },
|
||||
{
|
||||
get [Symbol.dispose]() {
|
||||
return () => {};
|
||||
},
|
||||
},
|
||||
].forEach(value => {
|
||||
expect(() => stack.adopt(null, value)).toThrowWithMessage(TypeError, "not a function");
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("adopt throws if stack is already disposed (over type errors)", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
[{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
|
||||
expect(() => stack.adopt(value, () => {})).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"AsyncDisposableStack already disposed values"
|
||||
);
|
||||
expect(() => stack.adopt(null, value)).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"AsyncDisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
test("length is 1", () => {
|
||||
expect(AsyncDisposableStack.prototype.defer).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("deferred function gets called when stack is disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposedCalled = 0;
|
||||
expect(disposedCalled).toBe(0);
|
||||
const result = stack.defer((...args) => {
|
||||
expect(args.length).toBe(0);
|
||||
++disposedCalled;
|
||||
});
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
expect(disposedCalled).toBe(0);
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
});
|
||||
|
||||
test("deferred stack is already disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.defer(() => {
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
await stack.disposeAsync();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("if call back is not a function throws type error", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
[
|
||||
1,
|
||||
1n,
|
||||
"a",
|
||||
Symbol.dispose,
|
||||
NaN,
|
||||
0,
|
||||
{},
|
||||
[],
|
||||
{ f() {} },
|
||||
{ [Symbol.dispose]() {} },
|
||||
{
|
||||
get [Symbol.dispose]() {
|
||||
return () => {};
|
||||
},
|
||||
},
|
||||
].forEach(value => {
|
||||
expect(() => stack.defer(value)).toThrowWithMessage(TypeError, "not a function");
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("defer throws if stack is already disposed (over type errors)", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
[{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
|
||||
expect(() => stack.defer(value)).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"AsyncDisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,88 @@
|
|||
test("length is 0", () => {
|
||||
expect(AsyncDisposableStack.prototype.disposeAsync).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("make the stack marked as disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
const result = await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("call dispose on objects in stack when called", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposedCalled = false;
|
||||
stack.use({
|
||||
[Symbol.asyncDispose]() {
|
||||
disposedCalled = true;
|
||||
},
|
||||
});
|
||||
|
||||
expect(disposedCalled).toBeFalse();
|
||||
const result = await stack.disposeAsync();
|
||||
expect(disposedCalled).toBeTrue();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("disposed the objects added to the stack in reverse order", async () => {
|
||||
const disposed = [];
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.use({
|
||||
[Symbol.asyncDispose]() {
|
||||
disposed.push("a");
|
||||
},
|
||||
});
|
||||
stack.use({
|
||||
[Symbol.asyncDispose]() {
|
||||
disposed.push("b");
|
||||
},
|
||||
});
|
||||
|
||||
expect(disposed).toEqual([]);
|
||||
const result = await stack.disposeAsync();
|
||||
expect(disposed).toEqual(["b", "a"]);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("does not dispose anything if already disposed", async () => {
|
||||
const disposed = [];
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.use({
|
||||
[Symbol.asyncDispose]() {
|
||||
disposed.push("a");
|
||||
},
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
expect(disposed).toEqual([]);
|
||||
|
||||
let result = await stack.disposeAsync();
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(disposed).toEqual(["a"]);
|
||||
|
||||
result = await stack.disposeAsync();
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(disposed).toEqual(["a"]);
|
||||
});
|
||||
|
||||
test("throws if dispose method throws", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposedCalled = false;
|
||||
stack.use({
|
||||
[Symbol.asyncDispose]() {
|
||||
disposedCalled = true;
|
||||
expect().fail("fail in dispose");
|
||||
},
|
||||
});
|
||||
|
||||
expect(async () => await stack.disposeAsync()).toThrowWithMessage(
|
||||
ExpectationError,
|
||||
"fail in dispose"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
test("is getter without setter", () => {
|
||||
const property = Object.getOwnPropertyDescriptor(AsyncDisposableStack.prototype, "disposed");
|
||||
expect(property.get).not.toBeUndefined();
|
||||
expect(property.set).toBeUndefined();
|
||||
expect(property.value).toBeUndefined();
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("is not a property on the object itself", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
expect(Object.hasOwn(stack, "disposed")).toBeFalse();
|
||||
});
|
||||
|
||||
test("starts off as false", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
expect(stack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("becomes true after being disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
test("length is 0", () => {
|
||||
expect(AsyncDisposableStack.prototype.move).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("stack is disposed after moving", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
|
||||
const newStack = stack.move();
|
||||
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(newStack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("move does not dispose resource but only move them", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposeCalled = false;
|
||||
stack.defer(() => {
|
||||
disposeCalled = true;
|
||||
});
|
||||
|
||||
expect(disposeCalled).toBeFalse();
|
||||
expect(stack.disposed).toBeFalse();
|
||||
|
||||
const newStack = stack.move();
|
||||
|
||||
expect(disposeCalled).toBeFalse();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(newStack.disposed).toBeFalse();
|
||||
|
||||
await stack.disposeAsync();
|
||||
|
||||
expect(disposeCalled).toBeFalse();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(newStack.disposed).toBeFalse();
|
||||
|
||||
await newStack.disposeAsync();
|
||||
|
||||
expect(disposeCalled).toBeTrue();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(newStack.disposed).toBeTrue();
|
||||
});
|
||||
|
||||
test("can add stack to itself", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.move(stack);
|
||||
await stack.disposeAsync();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("move throws if stack is already disposed (over type errors)", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
expect(() => stack.move()).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"AsyncDisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
test("length is 1", () => {
|
||||
expect(AsyncDisposableStack.prototype.use).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("added objects dispose method gets when stack is disposed", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let disposedCalled = 0;
|
||||
const obj = {
|
||||
[Symbol.dispose]() {
|
||||
++disposedCalled;
|
||||
},
|
||||
};
|
||||
expect(disposedCalled).toBe(0);
|
||||
const result = stack.use(obj);
|
||||
expect(result).toBe(obj);
|
||||
|
||||
expect(disposedCalled).toBe(0);
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
await stack.disposeAsync();
|
||||
expect(disposedCalled).toBe(1);
|
||||
});
|
||||
|
||||
test("can add null and undefined", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
|
||||
expect(stack.use(null)).toBeNull();
|
||||
expect(stack.use(undefined)).toBeUndefined();
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
|
||||
test("can add stack to itself", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
stack.use(stack);
|
||||
await stack.disposeAsync();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("if added value is not an object or null or undefined throws type error", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
[1, 1n, "a", Symbol.dispose, NaN, 0].forEach(value => {
|
||||
expect(() => stack.use(value)).toThrowWithMessage(TypeError, "not an object");
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("if added object does not have a dispose method throws type error", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
[{}, [], { f() {} }].forEach(value => {
|
||||
expect(() => stack.use(value)).toThrowWithMessage(
|
||||
TypeError,
|
||||
"does not have dispose method"
|
||||
);
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("if added object has non function dispose method it throws type error", () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
let calledGetter = false;
|
||||
[
|
||||
{ [Symbol.dispose]: 1 },
|
||||
{
|
||||
get [Symbol.dispose]() {
|
||||
calledGetter = true;
|
||||
return 1;
|
||||
},
|
||||
},
|
||||
].forEach(value => {
|
||||
expect(() => stack.use(value)).toThrowWithMessage(TypeError, "is not a function");
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
expect(calledGetter).toBeTrue();
|
||||
});
|
||||
|
||||
test("use throws if stack is already disposed (over type errors)", async () => {
|
||||
const stack = new AsyncDisposableStack();
|
||||
await stack.disposeAsync();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
[{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
|
||||
expect(() => stack.use(value)).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"AsyncDisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue