mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 05:27:45 +00:00
LibJS: Add DisposableStack{, Prototype, Constructor}
Since the async parts of the spec are not stage 3 at this point we don't add AsyncDisposableStack.
This commit is contained in:
parent
bff038411a
commit
6255ca4a42
22 changed files with 868 additions and 0 deletions
|
@ -0,0 +1,18 @@
|
|||
test("constructor properties", () => {
|
||||
expect(DisposableStack).toHaveLength(0);
|
||||
expect(DisposableStack.name).toBe("DisposableStack");
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("called without new", () => {
|
||||
expect(() => {
|
||||
DisposableStack();
|
||||
}).toThrowWithMessage(TypeError, "DisposableStack constructor must be called with 'new'");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("typeof", () => {
|
||||
expect(typeof new DisposableStack()).toBe("object");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
test("length is 0", () => {
|
||||
expect(DisposableStack.prototype[Symbol.dispose]).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("is the same as dispose", () => {
|
||||
expect(DisposableStack.prototype[Symbol.dispose]).toBe(DisposableStack.prototype.dispose);
|
||||
});
|
||||
|
||||
describe("used in using functionality", () => {
|
||||
test("make the stack marked as disposed", () => {
|
||||
let innerStack;
|
||||
{
|
||||
using stack = new DisposableStack();
|
||||
innerStack = stack;
|
||||
expect(stack.disposed).toBeFalse();
|
||||
}
|
||||
expect(innerStack.disposed).toBeTrue();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
test("basic functionality", () => {
|
||||
expect(DisposableStack.prototype[Symbol.toStringTag]).toBe("DisposableStack");
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
test("length is 2", () => {
|
||||
expect(DisposableStack.prototype.adopt).toHaveLength(2);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("adopted dispose method gets called when stack is disposed", () => {
|
||||
const stack = new DisposableStack();
|
||||
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);
|
||||
stack.dispose();
|
||||
expect(disposedCalled).toBe(1);
|
||||
expect(disposeArgument).toBeNull();
|
||||
stack.dispose();
|
||||
expect(disposedCalled).toBe(1);
|
||||
});
|
||||
|
||||
test("can adopt any value", () => {
|
||||
const stack = new DisposableStack();
|
||||
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);
|
||||
});
|
||||
|
||||
stack.dispose();
|
||||
|
||||
expect(disposed).toEqual(values.reverse());
|
||||
});
|
||||
|
||||
test("adopted stack is already disposed", () => {
|
||||
const stack = new DisposableStack();
|
||||
stack.adopt(stack, value => {
|
||||
expect(stack).toBe(value);
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
stack.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("if call back is not a function throws type error", () => {
|
||||
const stack = new DisposableStack();
|
||||
[
|
||||
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)", () => {
|
||||
const stack = new DisposableStack();
|
||||
stack.dispose();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
[{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
|
||||
expect(() => stack.adopt(value, () => {})).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"DisposableStack already disposed values"
|
||||
);
|
||||
expect(() => stack.adopt(null, value)).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"DisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
test("length is 1", () => {
|
||||
expect(DisposableStack.prototype.defer).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("deferred function gets called when stack is disposed", () => {
|
||||
const stack = new DisposableStack();
|
||||
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);
|
||||
stack.dispose();
|
||||
expect(disposedCalled).toBe(1);
|
||||
stack.dispose();
|
||||
expect(disposedCalled).toBe(1);
|
||||
});
|
||||
|
||||
test("deferred stack is already disposed", () => {
|
||||
const stack = new DisposableStack();
|
||||
stack.defer(() => {
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
stack.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("if call back is not a function throws type error", () => {
|
||||
const stack = new DisposableStack();
|
||||
[
|
||||
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)", () => {
|
||||
const stack = new DisposableStack();
|
||||
stack.dispose();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
[{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
|
||||
expect(() => stack.defer(value)).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"DisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
test("length is 0", () => {
|
||||
expect(DisposableStack.prototype.dispose).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("make the stack marked as disposed", () => {
|
||||
const stack = new DisposableStack();
|
||||
const result = stack.dispose();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("call dispose on objects in stack when called", () => {
|
||||
const stack = new DisposableStack();
|
||||
let disposedCalled = false;
|
||||
stack.use({
|
||||
[Symbol.dispose]() {
|
||||
disposedCalled = true;
|
||||
},
|
||||
});
|
||||
|
||||
expect(disposedCalled).toBeFalse();
|
||||
const result = stack.dispose();
|
||||
expect(disposedCalled).toBeTrue();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("disposed the objects added to the stack in reverse order", () => {
|
||||
const disposed = [];
|
||||
const stack = new DisposableStack();
|
||||
stack.use({
|
||||
[Symbol.dispose]() {
|
||||
disposed.push("a");
|
||||
},
|
||||
});
|
||||
stack.use({
|
||||
[Symbol.dispose]() {
|
||||
disposed.push("b");
|
||||
},
|
||||
});
|
||||
|
||||
expect(disposed).toEqual([]);
|
||||
const result = stack.dispose();
|
||||
expect(disposed).toEqual(["b", "a"]);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("does not dispose anything if already disposed", () => {
|
||||
const disposed = [];
|
||||
const stack = new DisposableStack();
|
||||
stack.use({
|
||||
[Symbol.dispose]() {
|
||||
disposed.push("a");
|
||||
},
|
||||
});
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
expect(disposed).toEqual([]);
|
||||
|
||||
expect(stack.dispose()).toBeUndefined();
|
||||
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(disposed).toEqual(["a"]);
|
||||
|
||||
expect(stack.dispose()).toBeUndefined();
|
||||
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(disposed).toEqual(["a"]);
|
||||
});
|
||||
|
||||
test("throws if dispose method throws", () => {
|
||||
const stack = new DisposableStack();
|
||||
let disposedCalled = false;
|
||||
stack.use({
|
||||
[Symbol.dispose]() {
|
||||
disposedCalled = true;
|
||||
expect().fail("fail in dispose");
|
||||
},
|
||||
});
|
||||
|
||||
expect(() => stack.dispose()).toThrowWithMessage(ExpectationError, "fail in dispose");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
test("is getter without setter", () => {
|
||||
const property = Object.getOwnPropertyDescriptor(DisposableStack.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 DisposableStack();
|
||||
expect(Object.hasOwn(stack, "disposed")).toBeFalse();
|
||||
});
|
||||
|
||||
test("starts off as false", () => {
|
||||
const stack = new DisposableStack();
|
||||
expect(stack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("becomes true after being disposed", () => {
|
||||
const stack = new DisposableStack();
|
||||
stack.dispose();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
test("length is 0", () => {
|
||||
expect(DisposableStack.prototype.move).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("stack is disposed after moving", () => {
|
||||
const stack = new DisposableStack();
|
||||
|
||||
const newStack = stack.move();
|
||||
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(newStack.disposed).toBeFalse();
|
||||
});
|
||||
|
||||
test("move does not dispose resource but only move them", () => {
|
||||
const stack = new DisposableStack();
|
||||
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();
|
||||
|
||||
stack.dispose();
|
||||
|
||||
expect(disposeCalled).toBeFalse();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(newStack.disposed).toBeFalse();
|
||||
|
||||
newStack.dispose();
|
||||
|
||||
expect(disposeCalled).toBeTrue();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
expect(newStack.disposed).toBeTrue();
|
||||
});
|
||||
|
||||
test("can add stack to itself", () => {
|
||||
const stack = new DisposableStack();
|
||||
stack.move(stack);
|
||||
stack.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("move throws if stack is already disposed (over type errors)", () => {
|
||||
const stack = new DisposableStack();
|
||||
stack.dispose();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
expect(() => stack.move()).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"DisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
test("length is 1", () => {
|
||||
expect(DisposableStack.prototype.use).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe("basic functionality", () => {
|
||||
test("added objects dispose method gets when stack is disposed", () => {
|
||||
const stack = new DisposableStack();
|
||||
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);
|
||||
stack.dispose();
|
||||
expect(disposedCalled).toBe(1);
|
||||
stack.dispose();
|
||||
expect(disposedCalled).toBe(1);
|
||||
});
|
||||
|
||||
test("can add null and undefined", () => {
|
||||
const stack = new DisposableStack();
|
||||
|
||||
expect(stack.use(null)).toBeNull();
|
||||
expect(stack.use(undefined)).toBeUndefined();
|
||||
|
||||
expect(stack.disposed).toBeFalse();
|
||||
stack.dispose();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
});
|
||||
|
||||
test("can add stack to itself", () => {
|
||||
const stack = new DisposableStack();
|
||||
stack.use(stack);
|
||||
stack.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
describe("throws errors", () => {
|
||||
test("if added value is not an object or null or undefined throws type error", () => {
|
||||
const stack = new DisposableStack();
|
||||
[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 DisposableStack();
|
||||
[{}, [], { 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 DisposableStack();
|
||||
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)", () => {
|
||||
const stack = new DisposableStack();
|
||||
stack.dispose();
|
||||
expect(stack.disposed).toBeTrue();
|
||||
|
||||
[{ [Symbol.dispose]() {} }, 1, null, undefined, "a", []].forEach(value => {
|
||||
expect(() => stack.use(value)).toThrowWithMessage(
|
||||
ReferenceError,
|
||||
"DisposableStack already disposed values"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue