mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 14:32:46 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			386 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			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();
 | |
|     });
 | |
| });
 | 
