async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create();
			db = new JsonlDB(testFilenameFull);
			await db.open();
		}


async () => {
			await db.close();
			expect(() => db.set("foo", 1)).toThrowError("not open");
		}


() => db.set("foo", 1)


async () => {
			db.set("key", true);
			expect(db.size).toBe(1);
			expect(db.has("key")).toBeTrue();

			// Force the stream to be flushed
			await db.close();

			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				`{"k":"key","v":true}\n`,
			);
		}


async () => {
			db.set("key2", true);
			db.set("key1", 1000);
			db.set("key3", "");

			// Force the stream to be flushed
			await db.close();

			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				`{"k":"key2","v":true}\n{"k":"key1","v":1000}\n{"k":"key3","v":""}\n`,
			);
		}


() => {
		const testFilename = "import.jsonl";
		let testFilenameFull: string;
		let jsonFilenameFull: string;
		let db: JsonlDB;
		let testFS: TestFS;
		let testFSRoot: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			jsonFilenameFull = path.join(testFSRoot, "jsonFile");
			await testFS.create({
				[testFilename]: '{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n',
				jsonFile: '{"key3": 1, "key4": true}',
			});
			db = new JsonlDB(testFilenameFull);
			await db.open();
		});
		afterEach(async () => {
			await testFS.remove();
		});

		it("both versions throw when the DB is not open", async () => {
			await db.close();
			expect(() => db.importJson({})).toThrowError("not open");
			await expect(db.importJson("")).toReject();
		});

		it("the object version adds all keys and values to the database", async () => {
			db.importJson({
				foo: "bar",
				baz: "inga",
				"1": 1,
			});
			// Force the stream to be flushed
			await db.close();

			// The order changes because Object.entries reads the entries in a different order
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				`{"k":"key1","v":1}
{"k":"key2","v":"2"}
{"k":"1","v":1}
{"k":"foo","v":"bar"}
{"k":"baz","v":"inga"}
`,
			);
		});

		it("the file version asynchronously adds all keys and values to the database", async () => {
			await db.importJson(jsonFilenameFull);
			// Force the stream to be flushed
			await db.close();

			// The order changes because Object.entries reads the entries in a different order
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				`{"k":"key1","v":1}
{"k":"key2","v":"2"}
{"k":"key3","v":1}
{"k":"key4","v":true}
`,
			);
		});
	}


async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			jsonFilenameFull = path.join(testFSRoot, "jsonFile");
			await testFS.create({
				[testFilename]: '{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n',
				jsonFile: '{"key3": 1, "key4": true}',
			});
			db = new JsonlDB(testFilenameFull);
			await db.open();
		}


async () => {
			await db.close();
			expect(() => db.importJson({})).toThrowError("not open");
			await expect(db.importJson("")).toReject();
		}


() => db.importJson({})


async () => {
			db.importJson({
				foo: "bar",
				baz: "inga",
				"1": 1,
			});
			// Force the stream to be flushed
			await db.close();

			// The order changes because Object.entries reads the entries in a different order
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				`{"k":"key1","v":1}
{"k":"key2","v":"2"}
{"k":"1","v":1}
{"k":"foo","v":"bar"}
{"k":"baz","v":"inga"}
`,
			);
		}


async () => {
			await db.importJson(jsonFilenameFull);
			// Force the stream to be flushed
			await db.close();

			// The order changes because Object.entries reads the entries in a different order
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				`{"k":"key1","v":1}
{"k":"key2","v":"2"}
{"k":"key3","v":1}
{"k":"key4","v":true}
`,
			);
		}


() => {
		const testFilename = "export.jsonl";
		let testFilenameFull: string;
		let jsonFilenameFull: string;
		let db: JsonlDB;
		let testFS: TestFS;
		let testFSRoot: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			jsonFilenameFull = path.join(testFSRoot, "jsonFile");
			await testFS.create({
				[testFilename]: '{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n',
				jsonFile: '{"key3": 1, "key4": true}',
			});
			db = new JsonlDB(testFilenameFull);
			await db.open();
		});
		afterEach(async () => {
			if (db) await db.close();
			await testFS.remove();
		});

		it("throws when the DB is not open", async () => {
			await db.close();
			await expect(db.exportJson(jsonFilenameFull)).toReject();
		});

		it("overwrites the given file with the DB contents as valid JSON", async () => {
			await db.exportJson(jsonFilenameFull);
			await expect(fs.readFile(jsonFilenameFull, "utf8")).resolves.toBe(
				`{"key1":1,"key2":"2"}\n`,
			);
		});

		it("honors the JSON formatting options", async () => {
			await db.exportJson(jsonFilenameFull, { spaces: "\t" });
			await expect(fs.readFile(jsonFilenameFull, "utf8")).resolves.toBe(
				`{
	"key1": 1,
	"key2": "2"
}\n`,
			);
		});
	}


async () => {
			if (db) await db.close();
			await testFS.remove();
		}


async () => {
			await db.close();
			await expect(db.exportJson(jsonFilenameFull)).toReject();
		}


async () => {
			await db.exportJson(jsonFilenameFull);
			await expect(fs.readFile(jsonFilenameFull, "utf8")).resolves.toBe(
				`{"key1":1,"key2":"2"}\n`,
			);
		}


async () => {
			await db.exportJson(jsonFilenameFull, { spaces: "\t" });
			await expect(fs.readFile(jsonFilenameFull, "utf8")).resolves.toBe(
				`{
	"key1": 1,
	"key2": "2"
}\n`,
			);
		}


() => {
		const testFilename = "close.jsonl";
		let testFilenameFull: string;
		// The basic functionality is tested in the other suites
		let db: JsonlDB;
		let testFS: TestFS;
		let testFSRoot: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create();
			db = new JsonlDB(testFilenameFull);
			await db.open();
		});
		afterEach(async () => {
			await testFS.remove();
		});

		it("may be called twice", async () => {
			await db.close();
			await db.close();
		});

		it("sets the isOpen property to false", async () => {
			await db.close();
			expect(db.isOpen).toBeFalse();
		});
	}


async () => {
			await db.close();
			await db.close();
		}


async () => {
			await db.close();
			expect(db.isOpen).toBeFalse();
		}


() => {
		const testFilename = "dump.jsonl";
		let testFilenameFull: string;
		let db: JsonlDB;
		let dumpdb: JsonlDB;
		let testFS: TestFS;
		let testFSRoot: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create({
				[testFilename]: "",
				[testFilename + ".dump"]: "",
			});
			db = new JsonlDB(testFilenameFull);
			dumpdb = new JsonlDB(testFilenameFull + ".dump");
			await db.open();
		});
		afterEach(async () => {
			await db.close();
			await dumpdb.close();
			await testFS.remove();
			mockAppendFileThrottle = 0;
		});

		it("writes a compressed version of the database", async () => {
			for (let i = 1; i < 20; i++) {
				if (i % 4 === 0) {
					db.delete(`${i - 1}`);
				} else {
					db.set(`${i}`, i);
				}
			}
			await db.dump();

			await dumpdb.close();
			await dumpdb.open();
			assertEqual(db, dumpdb);
		});

		it("when additional data is written during the dump, it is also dumped", async () => {
			// simulate a slow FS
			mockAppendFileThrottle = 50;
			let dumpPromise: Promise<void>;
			for (let i = 1; i < 20; i++) {
				if (i % 4 === 0) {
					db.delete(`${i - 1}`);
				} else {
					db.set(`${i}`, i);
				}
				if (i === 10) dumpPromise = db.dump();
				if (i > 10) await wait(20);
			}
			await dumpPromise!;

			await dumpdb.close();
			await dumpdb.open();
			assertEqual(db, dumpdb);
		});

		it("blocks the close() call", async () => {
			for (let i = 1; i < 20; i++) {
				if (i % 4 === 0) {
					db.delete(`${i - 1}`);
				} else {
					db.set(`${i}`, i);
				}
			}

			// simulate a slow FS
			mockAppendFileThrottle = 50;
			// dump without waiting
			db.dump();
			// wait a bit, so the files are being opened
			await wait(100);
			// and write something that will be put into the dump backlog
			db.set("21", 21);

			await db.close();
			// The dumpdb must be opened before the db, because the open call will delete the old dump
			await dumpdb.open();
			await db.open();
			assertEqual(db, dumpdb);
		});
	}


async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create({
				[testFilename]: "",
				[testFilename + ".dump"]: "",
			});
			db = new JsonlDB(testFilenameFull);
			dumpdb = new JsonlDB(testFilenameFull + ".dump");
			await db.open();
		}


async () => {
			await db.close();
			await dumpdb.close();
			await testFS.remove();
			mockAppendFileThrottle = 0;
		}


async () => {
			for (let i = 1; i < 20; i++) {
				if (i % 4 === 0) {
					db.delete(`${i - 1}`);
				} else {
					db.set(`${i}`, i);
				}
			}
			await db.dump();

			await dumpdb.close();
			await dumpdb.open();
			assertEqual(db, dumpdb);
		}


async () => {
			// simulate a slow FS
			mockAppendFileThrottle = 50;
			let dumpPromise: Promise<void>;
			for (let i = 1; i < 20; i++) {
				if (i % 4 === 0) {
					db.delete(`${i - 1}`);
				} else {
					db.set(`${i}`, i);
				}
				if (i === 10) dumpPromise = db.dump();
				if (i > 10) await wait(20);
			}
			await dumpPromise!;

			await dumpdb.close();
			await dumpdb.open();
			assertEqual(db, dumpdb);
		}


async () => {
			for (let i = 1; i < 20; i++) {
				if (i % 4 === 0) {
					db.delete(`${i - 1}`);
				} else {
					db.set(`${i}`, i);
				}
			}

			// simulate a slow FS
			mockAppendFileThrottle = 50;
			// dump without waiting
			db.dump();
			// wait a bit, so the files are being opened
			await wait(100);
			// and write something that will be put into the dump backlog
			db.set("21", 21);

			await db.close();
			// The dumpdb must be opened before the db, because the open call will delete the old dump
			await dumpdb.open();
			await db.open();
			assertEqual(db, dumpdb);
		}


() => {
		const testFilename = "compress.jsonl";
		let testFilenameFull: string;
		let db: JsonlDB;
		let testFS: TestFS;
		let testFSRoot: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create({
				[testFilename]: '{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n',
			});
			db = new JsonlDB(testFilenameFull);
			await db.open();
		});
		afterEach(async () => {
			await db.close();
			await testFS.remove();
			mockMoveFileThrottle = 0;
			mockAppendFileThrottle = 0;
		});

		it("overwrites the append-only file with a compressed version", async () => {
			db.set("key3", 3);
			db.delete("key2");
			db.set("key3", 3.5);

			await db.compress();
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n',
			);
			await expect(
				fs.pathExists(testFilenameFull + ".dump"),
			).resolves.toBeFalse();
			await expect(
				fs.pathExists(testFilenameFull + ".bak"),
			).resolves.toBeFalse();
		});

		it("after compresing, writing works as usual", async () => {
			db.set("key3", 3);
			db.delete("key2");
			db.set("key3", 3.5);
			await db.compress();

			db.set("key2", 1);
			// Force flush
			await db.close();
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n{"k":"key2","v":1}\n',
			);
		});

		it("does not do anything while the DB is being closed", async () => {
			db.set("key3", 3);
			db.delete("key2");
			db.set("key3", 3.5);
			const closePromise = db.close();
			await db.compress();
			await closePromise;

			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n{"k":"key3","v":3}\n{"k":"key2"}\n{"k":"key3","v":3.5}\n',
			);
		});

		it("works correctly while the DB is being compressed already", async () => {
			db.set("key3", 3);
			db.delete("key2");
			db.set("key3", 3.5);

			// simulate slow FS
			mockMoveFileThrottle = 10;
			mockAppendFileThrottle = 10;

			const compressPromise = db.compress();
			await db.compress();
			await compressPromise;

			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n',
			);
		});

		it("when additional data is written while the files are moved, it is appended to the main file", async () => {
			// simulate a slow FS
			mockMoveFileThrottle = 50;
			let compressPromise: Promise<void>;
			const map = new Map<any, any>([
				["key1", 1],
				["key2", "2"],
			]);
			for (let i = 1; i < 20; i++) {
				if (i % 4 === 0) {
					db.delete(`${i - 1}`);
					map.delete(`${i - 1}`);
				} else {
					db.set(`${i}`, i);
					map.set(`${i}`, i);
				}
				if (i === 10) compressPromise = db.compress();
				if (i > 10) await wait(20);
			}
			await compressPromise!;

			await db.close();
			await db.open();

			assertEqual(db, map);
		});

		it("blocks the close() call", async () => {
			// simulate a slow FS
			mockMoveFileThrottle = 50;
			const map = new Map<any, any>([
				["key1", 1],
				["key2", "2"],
			]);
			for (let i = 1; i < 20; i++) {
				if (i % 4 === 0) {
					db.delete(`${i - 1}`);
					map.delete(`${i - 1}`);
				} else {
					db.set(`${i}`, i);
					map.set(`${i}`, i);
				}
			}
			db.compress();
			await db.close();
			await db.open();

			assertEqual(db, map);
		});
	}


async () => {
			await db.close();
			await testFS.remove();
			mockMoveFileThrottle = 0;
			mockAppendFileThrottle = 0;
		}


async () => {
			db.set("key3", 3);
			db.delete("key2");
			db.set("key3", 3.5);

			await db.compress();
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n',
			);
			await expect(
				fs.pathExists(testFilenameFull + ".dump"),
			).resolves.toBeFalse();
			await expect(
				fs.pathExists(testFilenameFull + ".bak"),
			).resolves.toBeFalse();
		}


async () => {
			db.set("key3", 3);
			db.delete("key2");
			db.set("key3", 3.5);
			await db.compress();

			db.set("key2", 1);
			// Force flush
			await db.close();
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n{"k":"key2","v":1}\n',
			);
		}


async () => {
			db.set("key3", 3);
			db.delete("key2");
			db.set("key3", 3.5);
			const closePromise = db.close();
			await db.compress();
			await closePromise;

			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n{"k":"key3","v":3}\n{"k":"key2"}\n{"k":"key3","v":3.5}\n',
			);
		}


async () => {
			db.set("key3", 3);
			db.delete("key2");
			db.set("key3", 3.5);

			// simulate slow FS
			mockMoveFileThrottle = 10;
			mockAppendFileThrottle = 10;

			const compressPromise = db.compress();
			await db.compress();
			await compressPromise;

			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n',
			);
		}


async () => {
			// simulate a slow FS
			mockMoveFileThrottle = 50;
			let compressPromise: Promise<void>;
			const map = new Map<any, any>([
				["key1", 1],
				["key2", "2"],
			]);
			for (let i = 1; i < 20; i++) {
				if (i % 4 === 0) {
					db.delete(`${i - 1}`);
					map.delete(`${i - 1}`);
				} else {
					db.set(`${i}`, i);
					map.set(`${i}`, i);
				}
				if (i === 10) compressPromise = db.compress();
				if (i > 10) await wait(20);
			}
			await compressPromise!;

			await db.close();
			await db.open();

			assertEqual(db, map);
		}


async () => {
			// simulate a slow FS
			mockMoveFileThrottle = 50;
			const map = new Map<any, any>([
				["key1", 1],
				["key2", "2"],
			]);
			for (let i = 1; i < 20; i++) {
				if (i % 4 === 0) {
					db.delete(`${i - 1}`);
					map.delete(`${i - 1}`);
				} else {
					db.set(`${i}`, i);
					map.set(`${i}`, i);
				}
			}
			db.compress();
			await db.close();
			await db.open();

			assertEqual(db, map);
		}


() => {
		const testFilename = "compress-with-bak.jsonl";
		let testFilenameFull: string;
		let db: JsonlDB;
		let testFS: TestFS;
		let testFSRoot: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create({
				[testFilename]: '{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n',
				[`${testFilename}.bak`]:
					'{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n',
			});
			db = new JsonlDB(testFilenameFull);
			await db.open();
		});
		afterEach(async () => {
			await db.close();
			await testFS.remove();
			mockMoveFileThrottle = 0;
			mockAppendFileThrottle = 0;
		});

		it("does not crash", async () => {
			await db.compress();
			await db.close();
		});
	}


async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create({
				[testFilename]: '{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n',
				[`${testFilename}.bak`]:
					'{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n',
			});
			db = new JsonlDB(testFilenameFull);
			await db.open();
		}


async () => {
			await db.compress();
			await db.close();
		}


() => {
		const testFilename = "uncompressedSize.jsonl";
		let testFilenameFull: string;
		let db: JsonlDB;
		let testFS: TestFS;
		let testFSRoot: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create({
				[testFilename]: `
{"k":"key1","v":1}
{"k":"key2","v":"2"}
{"k":"key1"}
{"k":"key2"}
{"k":"key2","v":"2"}
{"k":"key3","v":3}
{"k":"key3"}
`,
			});
			db = new JsonlDB(testFilenameFull);
			await db.open();
		});
		afterEach(async () => {
			await db.close();
			await testFS.remove();
			mockMoveFileThrottle = 0;
		});

		it("throws when the DB is not open", async () => {
			await db.close();
			expect(() => db.uncompressedSize).toThrowError("not open");
		});

		it("returns the non-empty line count of the db file", async () => {
			expect(db.uncompressedSize).toBe(7);
		});

		it("increases by 1 for each set command", async () => {
			db.set("key4", 1);
			expect(db.uncompressedSize).toBe(8);
			db.set("key4", 1);
			expect(db.uncompressedSize).toBe(9);
			db.set("key4", 1);
			expect(db.uncompressedSize).toBe(10);
			db.set("key5", 2);
			expect(db.uncompressedSize).toBe(11);
		});

		it("increases by 1 for each non-noop delete", async () => {
			db.delete("key4");
			expect(db.uncompressedSize).toBe(7);
			db.delete("key2");
			expect(db.uncompressedSize).toBe(8);
			db.delete("key2");
			expect(db.uncompressedSize).toBe(8);
		});

		it("is reset to 0 after clear()", async () => {
			db.clear();
			expect(db.uncompressedSize).toBe(0);
		});

		it("is reset to the compressed size afer compress()", async () => {
			await db.compress();
			expect(db.uncompressedSize).toBe(1);
		});

		it("writes during compress are counted", async () => {
			// simulate a slow FS
			mockMoveFileThrottle = 50;
			const compressPromise = db.compress();
			await wait(20);

			db.set("key1", "value1");
			await compressPromise;

			expect(db.uncompressedSize).toBe(2);
		});
	}


async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create({
				[testFilename]: `
{"k":"key1","v":1}
{"k":"key2","v":"2"}
{"k":"key1"}
{"k":"key2"}
{"k":"key2","v":"2"}
{"k":"key3","v":3}
{"k":"key3"}
`,
			});
			db = new JsonlDB(testFilenameFull);
			await db.open();
		}


async () => {
			await db.close();
			await testFS.remove();
			mockMoveFileThrottle = 0;
		}


async () => {
			await db.close();
			expect(() => db.uncompressedSize).toThrowError("not open");
		}


() => db.uncompressedSize


async () => {
			expect(db.uncompressedSize).toBe(7);
		}


async () => {
			db.set("key4", 1);
			expect(db.uncompressedSize).toBe(8);
			db.set("key4", 1);
			expect(db.uncompressedSize).toBe(9);
			db.set("key4", 1);
			expect(db.uncompressedSize).toBe(10);
			db.set("key5", 2);
			expect(db.uncompressedSize).toBe(11);
		}


async () => {
			db.delete("key4");
			expect(db.uncompressedSize).toBe(7);
			db.delete("key2");
			expect(db.uncompressedSize).toBe(8);
			db.delete("key2");
			expect(db.uncompressedSize).toBe(8);
		}


async () => {
			db.clear();
			expect(db.uncompressedSize).toBe(0);
		}


async () => {
			await db.compress();
			expect(db.uncompressedSize).toBe(1);
		}


async () => {
			// simulate a slow FS
			mockMoveFileThrottle = 50;
			const compressPromise = db.compress();
			await wait(20);

			db.set("key1", "value1");
			await compressPromise;

			expect(db.uncompressedSize).toBe(2);
		}


() => {
		const testFilename = "autoCompress.jsonl";
		let testFilenameFull: string;
		const uncompressed = `
{"k":"key1","v":1}
{"k":"key2","v":"2"}
{"k":"key3","v":3}
{"k":"key2"}
{"k":"key3","v":3.5}`;

		let db: JsonlDB;
		let testFS: TestFS;
		let testFSRoot: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create({
				[testFilename]: `{"k":"key1","v":1}`,
				openClose: uncompressed,
			});
		});
		afterEach(async () => {
			await db.close();
			await testFS.remove();
		});

		it("triggers when uncompressedSize >= size * sizeFactor", async () => {
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					sizeFactor: 4,
				},
			});
			const compressSpy = jest.spyOn(db, "compress");
			await db.open();

			for (let i = 2; i <= 9; i++) {
				db.set("key1", i);
				// compress is async, so give it some time
				await wait(20);
				if (i <= 3) {
					expect(compressSpy).not.toBeCalled();
				} else if (i <= 6) {
					expect(compressSpy).toBeCalledTimes(1);
				} else {
					expect(compressSpy).toBeCalledTimes(2);
				}
			}

			await db.close();
		});

		it("..., but only above the minimum size", async () => {
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					sizeFactor: 4,
					sizeFactorMinimumSize: 20,
				},
			});
			const compressSpy = jest.spyOn(db, "compress");
			await db.open();

			for (let i = 2; i <= 20; i++) {
				db.set("key1", i);
				// compress is async, so give it some time
				await wait(20);
			}
			await db.close();
			expect(compressSpy).toBeCalledTimes(1);
		});

		it("doesn't trigger when different keys are added", async () => {
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					sizeFactor: 4,
				},
			});
			const compressSpy = jest.spyOn(db, "compress");
			await db.open();

			for (let i = 2; i <= 20; i++) {
				db.set("key" + i, i);
			}
			await db.close();
			expect(compressSpy).not.toBeCalled();
		});

		it("triggers after intervalMs", async () => {
			jest.retryTimes(3); // timeout-based tests are flaky. retry to be sure

			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					intervalMs: 100,
				},
			});
			const compressSpy = jest.spyOn(db, "compress");
			await db.open();

			for (let i = 1; i <= 3; i++) {
				// Don't test too often, or we'll run into a mismatch
				db.set("key1", i);
				// compress is async, so give it some time
				await wait(110);
				expect(compressSpy).toBeCalledTimes(i);
			}

			await db.close();
		});

		it("..., but only if there were at least intervalMinChanges changes", async () => {
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					intervalMs: 100,
					intervalMinChanges: 2,
				},
			});
			const compressSpy = jest.spyOn(db, "compress");
			await db.open();

			await wait(100);
			expect(compressSpy).not.toBeCalled();

			db.set("key1", 1);
			await wait(100);
			expect(compressSpy).not.toBeCalled(); // only 1 change

			db.set("key1", 1);
			await wait(100);
			expect(compressSpy).toBeCalledTimes(1); // two changes

			await db.close();
		});

		it("compresses after opening when onOpen is true", async () => {
			const testFilenameFull = path.join(testFSRoot, "openClose");
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					onOpen: true,
				},
			});

			// Cannot use this, since open calls compressInternal
			// expect(compressSpy).toBeCalledTimes(1);
			await db.open();
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n',
			);

			db.set("key3", 1);
			await db.close();
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n{"k":"key3","v":1}\n',
			);
		});

		it("compresses during close when onClose is true", async () => {
			const testFilenameFull = path.join(testFSRoot, "openClose");
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					onClose: true,
				},
			});
			// Cannot use this, since close calls compressInternal
			// expect(compressSpy).toBeCalledTimes(1);
			await db.open();
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				uncompressed,
			);

			await db.close();

			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n',
			);
		});
	}


async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create({
				[testFilename]: `{"k":"key1","v":1}`,
				openClose: uncompressed,
			});
		}


async () => {
			await db.close();
			await testFS.remove();
		}


async () => {
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					sizeFactor: 4,
				},
			});
			const compressSpy = jest.spyOn(db, "compress");
			await db.open();

			for (let i = 2; i <= 9; i++) {
				db.set("key1", i);
				// compress is async, so give it some time
				await wait(20);
				if (i <= 3) {
					expect(compressSpy).not.toBeCalled();
				} else if (i <= 6) {
					expect(compressSpy).toBeCalledTimes(1);
				} else {
					expect(compressSpy).toBeCalledTimes(2);
				}
			}

			await db.close();
		}


async () => {
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					sizeFactor: 4,
					sizeFactorMinimumSize: 20,
				},
			});
			const compressSpy = jest.spyOn(db, "compress");
			await db.open();

			for (let i = 2; i <= 20; i++) {
				db.set("key1", i);
				// compress is async, so give it some time
				await wait(20);
			}
			await db.close();
			expect(compressSpy).toBeCalledTimes(1);
		}


async () => {
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					sizeFactor: 4,
				},
			});
			const compressSpy = jest.spyOn(db, "compress");
			await db.open();

			for (let i = 2; i <= 20; i++) {
				db.set("key" + i, i);
			}
			await db.close();
			expect(compressSpy).not.toBeCalled();
		}


async () => {
			jest.retryTimes(3); // timeout-based tests are flaky. retry to be sure

			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					intervalMs: 100,
				},
			});
			const compressSpy = jest.spyOn(db, "compress");
			await db.open();

			for (let i = 1; i <= 3; i++) {
				// Don't test too often, or we'll run into a mismatch
				db.set("key1", i);
				// compress is async, so give it some time
				await wait(110);
				expect(compressSpy).toBeCalledTimes(i);
			}

			await db.close();
		}


async () => {
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					intervalMs: 100,
					intervalMinChanges: 2,
				},
			});
			const compressSpy = jest.spyOn(db, "compress");
			await db.open();

			await wait(100);
			expect(compressSpy).not.toBeCalled();

			db.set("key1", 1);
			await wait(100);
			expect(compressSpy).not.toBeCalled(); // only 1 change

			db.set("key1", 1);
			await wait(100);
			expect(compressSpy).toBeCalledTimes(1); // two changes

			await db.close();
		}


async () => {
			const testFilenameFull = path.join(testFSRoot, "openClose");
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					onOpen: true,
				},
			});

			// Cannot use this, since open calls compressInternal
			// expect(compressSpy).toBeCalledTimes(1);
			await db.open();
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n',
			);

			db.set("key3", 1);
			await db.close();
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n{"k":"key3","v":1}\n',
			);
		}


async () => {
			const testFilenameFull = path.join(testFSRoot, "openClose");
			db = new JsonlDB(testFilenameFull, {
				autoCompress: {
					onClose: true,
				},
			});
			// Cannot use this, since close calls compressInternal
			// expect(compressSpy).toBeCalledTimes(1);
			await db.open();
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				uncompressed,
			);

			await db.close();

			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n',
			);
		}


() => {
		const testFilename = "throttled.jsonl";
		let testFilenameFull: string;
		let db: JsonlDB;
		let testFS: TestFS;
		let testFSRoot: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create({
				[testFilename]: ``,
			});
		});
		afterEach(async () => {
			if (db) await db.close();
			await testFS.remove();
		});

		async function assertFileContent(content: string): Promise<void> {
			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				content,
			);
		}

		it("changes are only written after intervalMs has passed", async () => {
			jest.retryTimes(3); // timeout-based tests are flaky. retry to be sure

			db = new JsonlDB(testFilenameFull, {
				throttleFS: {
					intervalMs: 100,
				},
			});
			await db.open();

			// Trigger at least one scheduled cork before the first write
			await wait(110);
			await assertFileContent("");

			db.set("1", 1);
			let expected = `{"k":"1","v":1}\n`;
			await assertFileContent("");

			await wait(50);
			for (let i = 2; i <= 10; i++) {
				db.set(i.toString(), i);
				await assertFileContent("");
				expected += `{"k":"${i}","v":${i}}\n`;
			}

			// Give it a little more time than necessary
			await wait(60);
			await assertFileContent(expected);
		});

		it("or the maximum buffer size was reached", async () => {
			jest.retryTimes(3); // timeout-based tests are flaky. retry to be sure

			db = new JsonlDB(testFilenameFull, {
				throttleFS: {
					intervalMs: 100,
					maxBufferedCommands: 5,
				},
			});
			await db.open();

			db.set("1", 1);
			let expected = `{"k":"1","v":1}\n`;
			await assertFileContent("");

			await wait(50);
			for (let i = 2; i <= 6; i++) {
				db.set(i.toString(), i);
				expected += `{"k":"${i}","v":${i}}\n`;
				if (i <= 5) await assertFileContent("");
			}

			// Give it a little time to write
			await wait(10);
			await assertFileContent(expected);
		});

		it("works after compressing", async () => {
			jest.retryTimes(3); // timeout-based tests are flaky. retry to be sure

			db = new JsonlDB(testFilenameFull, {
				throttleFS: {
					intervalMs: 100,
				},
			});
			await db.open();
			await db.compress();

			db.set("1", 1);
			let expected = `{"k":"1","v":1}\n`;
			await assertFileContent("");

			await wait(50);
			for (let i = 2; i <= 100; i++) {
				db.set(i.toString(), i);
				await assertFileContent("");
				expected += `{"k":"${i}","v":${i}}\n`;
			}

			// Give it a little more time than necessary
			await wait(60);
			await assertFileContent(expected);
		});

		it("should still flush the buffer on close", async () => {
			jest.retryTimes(3); // timeout-based tests are flaky. retry to be sure

			db = new JsonlDB(testFilenameFull, {
				throttleFS: {
					intervalMs: 100,
				},
			});
			await db.open();

			db.set("1", 1);
			let expected = `{"k":"1","v":1}\n`;
			await assertFileContent("");

			await wait(50);
			for (let i = 2; i <= 100; i++) {
				db.set(i.toString(), i);
				await assertFileContent("");
				expected += `{"k":"${i}","v":${i}}\n`;
			}

			// Close the db before the next forced flush
			await db.close();

			await assertFileContent(expected);
		});
	}


async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create({
				[testFilename]: ``,
			});
		}


async () => {
			jest.retryTimes(3); // timeout-based tests are flaky. retry to be sure

			db = new JsonlDB(testFilenameFull, {
				throttleFS: {
					intervalMs: 100,
				},
			});
			await db.open();

			// Trigger at least one scheduled cork before the first write
			await wait(110);
			await assertFileContent("");

			db.set("1", 1);
			let expected = `{"k":"1","v":1}\n`;
			await assertFileContent("");

			await wait(50);
			for (let i = 2; i <= 10; i++) {
				db.set(i.toString(), i);
				await assertFileContent("");
				expected += `{"k":"${i}","v":${i}}\n`;
			}

			// Give it a little more time than necessary
			await wait(60);
			await assertFileContent(expected);
		}


async () => {
			jest.retryTimes(3); // timeout-based tests are flaky. retry to be sure

			db = new JsonlDB(testFilenameFull, {
				throttleFS: {
					intervalMs: 100,
					maxBufferedCommands: 5,
				},
			});
			await db.open();

			db.set("1", 1);
			let expected = `{"k":"1","v":1}\n`;
			await assertFileContent("");

			await wait(50);
			for (let i = 2; i <= 6; i++) {
				db.set(i.toString(), i);
				expected += `{"k":"${i}","v":${i}}\n`;
				if (i <= 5) await assertFileContent("");
			}

			// Give it a little time to write
			await wait(10);
			await assertFileContent(expected);
		}


async () => {
			jest.retryTimes(3); // timeout-based tests are flaky. retry to be sure

			db = new JsonlDB(testFilenameFull, {
				throttleFS: {
					intervalMs: 100,
				},
			});
			await db.open();
			await db.compress();

			db.set("1", 1);
			let expected = `{"k":"1","v":1}\n`;
			await assertFileContent("");

			await wait(50);
			for (let i = 2; i <= 100; i++) {
				db.set(i.toString(), i);
				await assertFileContent("");
				expected += `{"k":"${i}","v":${i}}\n`;
			}

			// Give it a little more time than necessary
			await wait(60);
			await assertFileContent(expected);
		}


async () => {
			jest.retryTimes(3); // timeout-based tests are flaky. retry to be sure

			db = new JsonlDB(testFilenameFull, {
				throttleFS: {
					intervalMs: 100,
				},
			});
			await db.open();

			db.set("1", 1);
			let expected = `{"k":"1","v":1}\n`;
			await assertFileContent("");

			await wait(50);
			for (let i = 2; i <= 100; i++) {
				db.set(i.toString(), i);
				await assertFileContent("");
				expected += `{"k":"${i}","v":${i}}\n`;
			}

			// Close the db before the next forced flush
			await db.close();

			await assertFileContent(expected);
		}


() => {
		const testFilename = "checks.jsonl";
		let testFilenameFull: string;
		let db: JsonlDB;
		let testFS: TestFS;
		let testFSRoot: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create();
			db = new JsonlDB(testFilenameFull);
			await db.open();
		});
		afterEach(async () => {
			if (db) await db.close();
			await testFS.remove();
		});

		it("opening a complex log restores the same structure as expected", async () => {
			const expected = [
				/* 0: */ { key: "foobar", value: new Date().toISOString() },
				/* 1: */ { key: "asdfasg", value: true },
				/* 2: */ {
					key: 'x"blub',
					value: { lets: "get", complicated: { "!": "?" } },
				},
				/* 3: */ { key: "sssssss", value: null },
				/* 4: */ { key: "1", value: 1 },
				/* 5: */ { key: "2", value: [[[]], {}] },
				/* 6: */ { key: "", value: "what up?!\u0000" },
			];
			db.set(expected[1].key, expected[1].value);
			// 1
			db.set(expected[3].key, expected[3].value);
			// 1, 3
			db.set(expected[2].key, expected[2].value);
			// 1, 3, 2
			db.delete(expected[1].key);
			db.set(expected[5].key, expected[5].value);
			// 3, 2, 5
			db.set(expected[6].key, expected[6].value);
			// 3, 2, 5, 6
			db.set(expected[0].key, expected[0].value);
			// 3, 2, 5, 6, 0
			db.delete(expected[0].key);
			db.set(expected[6].key, expected[6].value);
			db.set(expected[1].key, expected[1].value);
			db.set(expected[4].key, expected[4].value);
			// 3, 2, 5, 6, 1, 4
			db.set(expected[0].key, expected[0].value);
			// 3, 2, 5, 6, 1, 4, 0

			await db.close();
			await db.open();

			expect([...db.keys()].sort()).toEqual(
				expected.map((e) => e.key).sort(),
			);
			expect([...db.values()].sort()).toEqual(
				expected.map((e) => e.value).sort(),
			);
			expect([...db.entries()].sort()).toEqual(
				expected.map((e) => [e.key, e.value]).sort(),
			);
		});
	}


async () => {
			const expected = [
				/* 0: */ { key: "foobar", value: new Date().toISOString() },
				/* 1: */ { key: "asdfasg", value: true },
				/* 2: */ {
					key: 'x"blub',
					value: { lets: "get", complicated: { "!": "?" } },
				},
				/* 3: */ { key: "sssssss", value: null },
				/* 4: */ { key: "1", value: 1 },
				/* 5: */ { key: "2", value: [[[]], {}] },
				/* 6: */ { key: "", value: "what up?!\u0000" },
			];
			db.set(expected[1].key, expected[1].value);
			// 1
			db.set(expected[3].key, expected[3].value);
			// 1, 3
			db.set(expected[2].key, expected[2].value);
			// 1, 3, 2
			db.delete(expected[1].key);
			db.set(expected[5].key, expected[5].value);
			// 3, 2, 5
			db.set(expected[6].key, expected[6].value);
			// 3, 2, 5, 6
			db.set(expected[0].key, expected[0].value);
			// 3, 2, 5, 6, 0
			db.delete(expected[0].key);
			db.set(expected[6].key, expected[6].value);
			db.set(expected[1].key, expected[1].value);
			db.set(expected[4].key, expected[4].value);
			// 3, 2, 5, 6, 1, 4
			db.set(expected[0].key, expected[0].value);
			// 3, 2, 5, 6, 1, 4, 0

			await db.close();
			await db.open();

			expect([...db.keys()].sort()).toEqual(
				expected.map((e) => e.key).sort(),
			);
			expect([...db.values()].sort()).toEqual(
				expected.map((e) => e.value).sort(),
			);
			expect([...db.entries()].sort()).toEqual(
				expected.map((e) => [e.key, e.value]).sort(),
			);
		}


(e) => e.key


(e) => e.value


(e) => [e.key, e.value]


() => {
		const testFilename = "serializer.jsonl";
		let testFilenameFull: string;
		let db: JsonlDB;
		let testFS: TestFS;
		let testFSRoot: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create();
		});
		afterEach(async () => {
			await testFS.remove();
		});

		it("before saving, values are transformed using the serializer function if any is passed", async () => {
			const serializer = jest.fn().mockReturnValue("ffff");
			db = new JsonlDB(testFilenameFull, { serializer });
			await db.open();

			const map = new Map<any, any>([
				[1, "foo"],
				[2, "bar"],
			]);
			db.set("test", map);
			await db.close();

			await wait(10);

			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				`{"k":"test","v":"ffff"}\n`,
			);
		});
	}


async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
			await testFS.create();
		}


async () => {
			const serializer = jest.fn().mockReturnValue("ffff");
			db = new JsonlDB(testFilenameFull, { serializer });
			await db.open();

			const map = new Map<any, any>([
				[1, "foo"],
				[2, "bar"],
			]);
			db.set("test", map);
			await db.close();

			await wait(10);

			await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
				`{"k":"test","v":"ffff"}\n`,
			);
		}


() => {
		let testFS: TestFS;
		let testFSRoot: string;
		let db: JsonlDB;
		const testFilename = "recovery.jsonl";
		let testFilenameFull: string;

		beforeEach(async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
		});

		afterEach(async () => {
			if (db) await db.close();
			await testFS.remove();
		});

		async function assertCleanedUp() {
			// The other files should have been cleaned up
			await expect(fs.pathExists(testFilenameFull)).resolves.toBeTrue();
			await expect(
				fs.pathExists(testFilenameFull + ".bak"),
			).resolves.toBeFalse();
			await expect(
				fs.pathExists(testFilenameFull + ".dump"),
			).resolves.toBeFalse();
		}

		it("db truncated, .bak ok -> use .bak", async () => {
			await testFS.create({
				// Original, uncompressed db in the .bak file
				[testFilename + ".bak"]: `
{"k":"key1","v":1}
{"k":"key2","v":"2"}
{"k":"key3","v":3}
{"k":"key2"}
{"k":"key3","v":3.5}`,
				// empty, broken db file
				[testFilename]: "",
				// (probably) half-complete .dump file
				[testFilename + ".dump"]: `
{"k":"key1","v":1}
{"k":"key3","v":3}`,
			});

			const db = new JsonlDB(testFilenameFull);
			await db.open();

			expect(db.size).toBe(2);
			expect(db.has("key1")).toBeTrue();
			expect(db.has("key2")).toBeFalse();
			expect(db.has("key3")).toBeTrue();
			expect(db.get("key1")).toBe(1);
			expect(db.get("key3")).toBe(3.5);

			await assertCleanedUp();
			await db.close();
		});

		it("db missing, .bak ok -> use .bak", async () => {
			await testFS.create({
				// Original, uncompressed db in the .bak file
				[testFilename + ".bak"]: `
{"k":"key1","v":1}
{"k":"key2","v":"2"}
{"k":"key3","v":3}
{"k":"key2"}
{"k":"key3","v":3.5}`,
				// (probably) half-complete .dump file
				[testFilename + ".dump"]: `
{"k":"key1","v":1}
{"k":"key3","v":3}`,
			});

			const db = new JsonlDB(testFilenameFull);
			await db.open();

			expect(db.size).toBe(2);
			expect(db.has("key1")).toBeTrue();
			expect(db.has("key2")).toBeFalse();
			expect(db.has("key3")).toBeTrue();
			expect(db.get("key1")).toBe(1);
			expect(db.get("key3")).toBe(3.5);

			await assertCleanedUp();

			await db.close();
		});

		it("db truncated, .bak truncated, .dump ok -> use .dump", async () => {
			await testFS.create({
				// empty, broken .bak file
				[testFilename + ".bak"]: "",
				// empty, broken db file
				[testFilename]: "",
				// (probably) half-complete .dump file, but better than nothing
				[testFilename + ".dump"]: `
{"k":"key1","v":1}
{"k":"key3","v":3}`,
			});

			const db = new JsonlDB(testFilenameFull);
			await db.open();

			expect(db.size).toBe(2);
			expect(db.has("key1")).toBeTrue();
			expect(db.has("key2")).toBeFalse();
			expect(db.has("key3")).toBeTrue();
			expect(db.get("key1")).toBe(1);
			expect(db.get("key3")).toBe(3);

			await assertCleanedUp();

			await db.close();
		});

		it("db truncated, .bak missing, .dump ok -> use .dump", async () => {
			await testFS.create({
				// empty, broken db file
				[testFilename]: "",
				// (probably) half-complete .dump file, but better than nothing
				[testFilename + ".dump"]: `
{"k":"key1","v":1}
{"k":"key3","v":3}`,
			});

			const db = new JsonlDB(testFilenameFull);
			await db.open();

			expect(db.size).toBe(2);
			expect(db.has("key1")).toBeTrue();
			expect(db.has("key2")).toBeFalse();
			expect(db.has("key3")).toBeTrue();
			expect(db.get("key1")).toBe(1);
			expect(db.get("key3")).toBe(3);

			await assertCleanedUp();

			await db.close();
		});
	}


async () => {
			testFS = new TestFS();
			testFSRoot = await testFS.getRoot();
			testFilenameFull = path.join(testFSRoot, testFilename);
		}


async () => {
			await testFS.create({
				// Original, uncompressed db in the .bak file
				[testFilename + ".bak"]: `
{"k":"key1","v":1}
{"k":"key2","v":"2"}
{"k":"key3","v":3}
{"k":"key2"}
{"k":"key3","v":3.5}`,
				// empty, broken db file
				[testFilename]: "",
				// (probably) half-complete .dump file
				[testFilename + ".dump"]: `
{"k":"key1","v":1}
{"k":"key3","v":3}`,
			});

			const db = new JsonlDB(testFilenameFull);
			await db.open();

			expect(db.size).toBe(2);
			expect(db.has("key1")).toBeTrue();
			expect(db.has("key2")).toBeFalse();
			expect(db.has("key3")).toBeTrue();
			expect(db.get("key1")).toBe(1);
			expect(db.get("key3")).toBe(3.5);

			await assertCleanedUp();
			await db.close();
		}


async () => {
			await testFS.create({
				// Original, uncompressed db in the .bak file
				[testFilename + ".bak"]: `
{"k":"key1","v":1}
{"k":"key2","v":"2"}
{"k":"key3","v":3}
{"k":"key2"}
{"k":"key3","v":3.5}`,
				// (probably) half-complete .dump file
				[testFilename + ".dump"]: `
{"k":"key1","v":1}
{"k":"key3","v":3}`,
			});

			const db = new JsonlDB(testFilenameFull);
			await db.open();

			expect(db.size).toBe(2);
			expect(db.has("key1")).toBeTrue();
			expect(db.has("key2")).toBeFalse();
			expect(db.has("key3")).toBeTrue();
			expect(db.get("key1")).toBe(1);
			expect(db.get("key3")).toBe(3.5);

			await assertCleanedUp();

			await db.close();
		}


async () => {
			await testFS.create({
				// empty, broken .bak file
				[testFilename + ".bak"]: "",
				// empty, broken db file
				[testFilename]: "",
				// (probably) half-complete .dump file, but better than nothing
				[testFilename + ".dump"]: `
{"k":"key1","v":1}
{"k":"key3","v":3}`,
			});

			const db = new JsonlDB(testFilenameFull);
			await db.open();

			expect(db.size).toBe(2);
			expect(db.has("key1")).toBeTrue();
			expect(db.has("key2")).toBeFalse();
			expect(db.has("key3")).toBeTrue();
			expect(db.get("key1")).toBe(1);
			expect(db.get("key3")).toBe(3);

			await assertCleanedUp();

			await db.close();
		}


async () => {
			await testFS.create({
				// empty, broken db file
				[testFilename]: "",
				// (probably) half-complete .dump file, but better than nothing
				[testFilename + ".dump"]: `
{"k":"key1","v":1}
{"k":"key3","v":3}`,
			});

			const db = new JsonlDB(testFilenameFull);
			await db.open();

			expect(db.size).toBe(2);
			expect(db.has("key1")).toBeTrue();
			expect(db.has("key2")).toBeFalse();
			expect(db.has("key3")).toBeTrue();
			expect(db.get("key1")).toBe(1);
			expect(db.get("key3")).toBe(3);

			await assertCleanedUp();

			await db.close();
		}


/**
 *
Stops the specified human loop.

 * @example
 * Use a bare-bones client and the command you need to make an API call.
 * ```javascript
 * import { SageMakerA2IRuntimeClient, StopHumanLoopCommand } from "@aws-sdk/client-sagemaker-a2i-runtime"; // ES Modules import
 * // const { SageMakerA2IRuntimeClient, StopHumanLoopCommand } = require("@aws-sdk/client-sagemaker-a2i-runtime"); // CommonJS import
 * const client = new SageMakerA2IRuntimeClient(config);
 * const command = new StopHumanLoopCommand(input);
 * const response = await client.send(command);
 * ```
 *
 * @see {@link StopHumanLoopCommandInput} for command's `input` shape.
 * @see {@link StopHumanLoopCommandOutput} for command's `response` shape.
 * @see {@link SageMakerA2IRuntimeClientResolvedConfig | config} for SageMakerA2IRuntimeClient's `config` shape.
 *
 */
export class StopHumanLoopCommand extends $Command<
  StopHumanLoopCommandInput,
  StopHumanLoopCommandOutput,
  SageMakerA2IRuntimeClientResolvedConfig
> {
  // Start section: command_properties
  // End section: command_properties

  constructor(readonly input: StopHumanLoopCommandInput) {
    // Start section: command_constructor
    super();
    // End section: command_constructor
  }

  /**
   * @internal
   */
  resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: SageMakerA2IRuntimeClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<StopHumanLoopCommandInput, StopHumanLoopCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const { logger } = configuration;
    const clientName = "SageMakerA2IRuntimeClient";
    const commandName = "StopHumanLoopCommand";
    const handlerExecutionContext: HandlerExecutionContext = {
      logger,
      clientName,
      commandName,
      inputFilterSensitiveLog: StopHumanLoopRequest.filterSensitiveLog,
      outputFilterSensitiveLog: StopHumanLoopResponse.filterSensitiveLog,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }

  private serialize(input: StopHumanLoopCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_restJson1StopHumanLoopCommand(input, context);
  }

  private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<StopHumanLoopCommandOutput> {
    return deserializeAws_restJson1StopHumanLoopCommand(output, context);
  }

  // Start section: command_body_extra
  // End section: command_body_extra
}


export interface StopHumanLoopCommandInput extends StopHumanLoopRequest {}


export interface StopHumanLoopCommandOutput extends StopHumanLoopResponse, __MetadataBearer {}


/**
   * @internal
   */
resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: SageMakerA2IRuntimeClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<StopHumanLoopCommandInput, StopHumanLoopCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const { logger } = configuration;
    const clientName = "SageMakerA2IRuntimeClient";
    const commandName = "StopHumanLoopCommand";
    const handlerExecutionContext: HandlerExecutionContext = {
      logger,
      clientName,
      commandName,
      inputFilterSensitiveLog: StopHumanLoopRequest.filterSensitiveLog,
      outputFilterSensitiveLog: StopHumanLoopResponse.filterSensitiveLog,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }


private serialize(input: StopHumanLoopCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_restJson1StopHumanLoopCommand(input, context);
  }


private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<StopHumanLoopCommandOutput> {
    return deserializeAws_restJson1StopHumanLoopCommand(output, context);
  }


() => {

	test("File Change Event (simple)", function() {
		let origEvent: any = {};
		let oldValue: any = { foo: "bar" };
		let newValue: any = { foo: "foo" };
		let event = new LocalFileChangeEvent(oldValue, newValue, origEvent);

		assert.strictEqual(event.originalEvent, origEvent);
		assert.strictEqual(event.oldValue, oldValue);
		assert.strictEqual(event.newValue, newValue);
		assert(event.time);
	});

	test("File Upload Event", function() {
		let origEvent: any = {};
		let value: any = { foo: "bar" };
		let event = new FileImportedEvent(value, true, origEvent);

		assert.strictEqual(event.originalEvent, origEvent);
		assert.strictEqual(event.newValue, value);
		assert(event.time);
		assert(event.gotAdded());
		assert(!event.gotUpdated());
		assert(!event.gotMoved());
		assert(!event.gotDeleted());

		event = new FileImportedEvent(value, false, origEvent);

		assert(!event.gotAdded());
		assert(event.gotUpdated());
		assert(!event.gotMoved());
		assert(!event.gotDeleted());
	});
}


@polyfill
export class NonIdealState extends AbstractPureComponent2<INonIdealStateProps> {
    public static displayName = `${DISPLAYNAME_PREFIX}.NonIdealState`;

    public render() {
        const { action, children, className, description, title } = this.props;
        return (

                {this.maybeRenderVisual()}
                {title &&
{title}
}
                {description && ensureElement(description, "div")}
                {action}
                {children}

        );
    }

    private maybeRenderVisual() {
        const { icon } = this.props;
        if (icon == null) {
            return null;
        } else {
            return (

                    <Icon icon={icon} iconSize={Icon.SIZE_LARGE * 3} />

            );
        }
    }
}


export interface INonIdealStateProps extends IProps {
    /** An action to resolve the non-ideal state which appears after `description`. */
    action?: JSX.Element;

    /**
     * Advanced usage: React `children` will appear last (after `action`).
     * Avoid passing raw strings as they will not receive margins and disrupt the layout flow.
     */
    children?: React.ReactNode;

    /**
     * A longer description of the non-ideal state.
     * A string or number value will be wrapped in a `<div>` to preserve margins.
     */
    description?: React.ReactChild;

    /** The name of a Blueprint icon or a JSX Element (such as `<Spinner/>`) to render above the title. */
    icon?: IconName | MaybeElement;

    /** The title of the non-ideal state. */
    title?: React.ReactNode;
}


public render() {
        const { action, children, className, description, title } = this.props;
        return (

                {this.maybeRenderVisual()}
                {title &&
{title}
}
                {description && ensureElement(description, "div")}
                {action}
                {children}

        );
    }


private maybeRenderVisual() {
        const { icon } = this.props;
        if (icon == null) {
            return null;
        } else {
            return (
                <div className={Classes.NON_IDEAL_STATE_VISUAL}>
                    <Icon icon={icon} iconSize={Icon.SIZE_LARGE * 3} />
                </div>
            );
        }
    }


export function onNavigatingTo(args: NavigatedData) {
    const page = <Page>args.object;
    page.bindingContext = new SearchViewModel();
}


export function onDrawerButtonTap(args: EventData) {
    const sideDrawer = <RadSideDrawer>app.getRootView();
    sideDrawer.showDrawer();
}


(): ThemePreference => {
    const value = localStorage.getItem(LIGHT_THEME_LOCAL_STORAGE_KEY)
    // Handle both old and new preference values
    switch (value) {
        case 'true':
        case 'light':
            return ThemePreference.Light
        case 'false':
        case 'dark':
            return ThemePreference.Dark
        default:
            return ThemePreference.System
    }
}


location => getModeFromPath(location.pathname)


([extensions, languageID]) => {
                preloadExtensions({
                    extensions,
                    languages: new Set([languageID]),
                })
            }


([settingsCascade, authenticatedUser]) => {
                    this.setState(state => ({
                        settingsCascade,
                        authenticatedUser,
                        ...experimentalFeaturesFromSettings(settingsCascade),
                        globbing: globbingEnabledFromSettings(settingsCascade),
                        searchCaseSensitivity:
                            defaultCaseSensitiveFromSettings(settingsCascade) || state.searchCaseSensitivity,
                        searchPatternType: defaultPatternTypeFromSettings(settingsCascade) || state.searchPatternType,
                        viewerSubject: viewerSubjectFromSettings(settingsCascade, authenticatedUser),
                    }))
                }


state => ({
                        settingsCascade,
                        authenticatedUser,
                        ...experimentalFeaturesFromSettings(settingsCascade),
                        globbing: globbingEnabledFromSettings(settingsCascade),
                        searchCaseSensitivity:
                            defaultCaseSensitiveFromSettings(settingsCascade) || state.searchCaseSensitivity,
                        searchPatternType: defaultPatternTypeFromSettings(settingsCascade) || state.searchPatternType,
                        viewerSubject: viewerSubjectFromSettings(settingsCascade, authenticatedUser),
                    })


() => this.setState({ authenticatedUser: null })


([[oldSettings], [newSettings, authUser]]) => {
                    if (authUser) {
                        logInsightMetrics(oldSettings, newSettings, eventLogger)
                    }
                }


event => {
                this.setState({ systemIsLightTheme: !event.matches })
            }


([, authenticatedUser]) =>
                        authenticatedUser
                            ? combineLatest([
                                  listUserRepositories({ id: authenticatedUser.id, first: 1 }),
                                  queryExternalServices({ namespace: authenticatedUser.id, first: 1, after: null }),
                              ])
                            : of(null)


error => [asError(error)]


result => {
                    if (!isErrorLike(result) && result !== null) {
                        const [userRepositoriesResult, externalServicesResult] = result
                        this.setState({
                            hasUserAddedRepositories: userRepositoriesResult.nodes.length > 0,
                            hasUserAddedExternalServices: externalServicesResult.nodes.length > 0,
                        })
                    }
                }


authenticatedUser =>
                        authenticatedUser ? fromEvent<ErrorEvent>(window, 'error') : of(null)


event => {
                    if (event?.error instanceof HTTPStatusError && event.error.status === 401) {
                        location.reload()
                    }
                }


event => {
                // Disabling linter here because this is not yet used anywhere.
                // This can be re-enabled as soon as feature flags are leveraged.
                // eslint-disable-next-line react/no-unused-state
                this.setState({ featureFlags: event })
            }


error => {
            console.error('Error sending initial version context to extensions', error)
        }


error => {
            console.error('Error sending search context to extensions', error)
        }


(themePreference: ThemePreference): void => {
        this.setState({ themePreference })
    }


(navbarSearchQueryState: QueryState): void => {
        this.setState({ navbarSearchQueryState })
    }


(query: string): void => {
        this.setState({ parsedSearchQuery: query })
    }


(patternType: SearchPatternType): void => {
        this.setState({
            searchPatternType: patternType,
        })
    }


(caseSensitive: boolean): void => {
        this.setState({
            searchCaseSensitivity: caseSensitive,
        })
    }


async (versionContext: string | undefined): Promise<void> => {
        const resolvedVersionContext = resolveVersionContext(versionContext, this.state.availableVersionContexts)
        if (!resolvedVersionContext) {
            localStorage.removeItem(LAST_VERSION_CONTEXT_KEY)
            this.setState({ versionContext: undefined, previousVersionContext: null })
        } else {
            localStorage.setItem(LAST_VERSION_CONTEXT_KEY, resolvedVersionContext)
            this.setState({ versionContext: resolvedVersionContext, previousVersionContext: resolvedVersionContext })
        }

        const extensionHostAPI = await this.extensionsController.extHostAPI
        // Note: `setVersionContext` is now asynchronous since the version context
        // is sent directly to extensions in the worker thread. This means that when the Promise
        // is in a fulfilled state, we know that extensions have received the latest version context
        await extensionHostAPI.setVersionContext(resolvedVersionContext)
    }


(
        externalServicesCount: number,
        userRepoCount: number
    ): void => {
        this.setState({
            hasUserAddedExternalServices: externalServicesCount > 0,
            hasUserAddedRepositories: userRepoCount > 0,
        })
    }


(publicReposCount: number): void => {
        this.setState({
            hasUserSyncedPublicRepositories: publicReposCount > 0,
        })
    }


(): boolean =>
        this.state.hasUserAddedRepositories || this.state.hasUserSyncedPublicRepositories


(): string | undefined =>
        this.state.showSearchContext ? this.state.selectedSearchContextSpec : undefined


(spec: string): void => {
        const { defaultSearchContextSpec } = this.state
        this.subscriptions.add(
            getAvailableSearchContextSpecOrDefault({ spec, defaultSpec: defaultSearchContextSpec }).subscribe(
                availableSearchContextSpecOrDefault => {
                    this.setState({ selectedSearchContextSpec: availableSearchContextSpecOrDefault })
                    localStorage.setItem(LAST_SEARCH_CONTEXT_KEY, availableSearchContextSpecOrDefault)

                    this.setWorkspaceSearchContext(availableSearchContextSpecOrDefault).catch(error => {
                        console.error('Error sending search context to extensions', error)
                    })
                }
            )
        )
    }


availableSearchContextSpecOrDefault => {
                    this.setState({ selectedSearchContextSpec: availableSearchContextSpecOrDefault })
                    localStorage.setItem(LAST_SEARCH_CONTEXT_KEY, availableSearchContextSpecOrDefault)

                    this.setWorkspaceSearchContext(availableSearchContextSpecOrDefault).catch(error => {
                        console.error('Error sending search context to extensions', error)
                    })
                }


error => {
                        console.error('Error sending search context to extensions', error)
                    }


export interface SourcegraphWebAppProps extends CodeIntelligenceProps, BatchChangesProps, KeyboardShortcutsProps {
    extensionAreaRoutes: readonly ExtensionAreaRoute[]
    extensionAreaHeaderNavItems: readonly ExtensionAreaHeaderNavItem[]
    extensionsAreaRoutes: readonly ExtensionsAreaRoute[]
    extensionsAreaHeaderActionButtons: readonly ExtensionsAreaHeaderActionButton[]
    siteAdminAreaRoutes: readonly SiteAdminAreaRoute[]
    siteAdminSideBarGroups: SiteAdminSideBarGroups
    siteAdminOverviewComponents: readonly React.ComponentType[]
    userAreaHeaderNavItems: readonly UserAreaHeaderNavItem[]
    userAreaRoutes: readonly UserAreaRoute[]
    userSettingsSideBarItems: UserSettingsSidebarItems
    userSettingsAreaRoutes: readonly UserSettingsAreaRoute[]
    orgAreaHeaderNavItems: readonly OrgAreaHeaderNavItem[]
    orgAreaRoutes: readonly OrgAreaRoute[]
    repoContainerRoutes: readonly RepoContainerRoute[]
    repoRevisionContainerRoutes: readonly RepoRevisionContainerRoute[]
    repoHeaderActionButtons: readonly RepoHeaderActionButton[]
    repoSettingsAreaRoutes: readonly RepoSettingsAreaRoute[]
    repoSettingsSidebarGroups: readonly RepoSettingsSideBarGroup[]
    routes: readonly LayoutRouteProps<any>[]
}


interface SourcegraphWebAppState extends SettingsCascadeProps {
    error?: Error

    /** The currently authenticated user (or null if the viewer is anonymous). */
    authenticatedUser?: AuthenticatedUser | null

    viewerSubject: LayoutProps['viewerSubject']

    /** The user's preference for the theme (light, dark or following system theme) */
    themePreference: ThemePreference

    /**
     * Whether the OS uses light theme, synced from a media query.
     * If the browser/OS does not this, will default to true.
     */
    systemIsLightTheme: boolean

    /**
     * The current search query in the navbar.
     */
    navbarSearchQueryState: QueryState

    /**
     * The current parsed search query, with all UI-configurable parameters
     * (eg. pattern type, case sensitivity, version context) removed
     */
    parsedSearchQuery: string

    /**
     * The current search pattern type.
     */
    searchPatternType: SearchPatternType

    /**
     * Whether the current search is case sensitive.
     */
    searchCaseSensitivity: boolean

    /*
     * The version context the instance is in. If undefined, it means no version context is selected.
     */
    versionContext?: string

    /**
     * Available version contexts defined in the site configuration.
     */
    availableVersionContexts?: VersionContext[]

    /**
     * The previously used version context, as specified in localStorage.
     */
    previousVersionContext: string | null

    showRepogroupHomepage: boolean

    showOnboardingTour: boolean

    showEnterpriseHomePanels: boolean

    showSearchContext: boolean
    showSearchContextManagement: boolean
    selectedSearchContextSpec?: string
    defaultSearchContextSpec: string
    hasUserAddedRepositories: boolean
    hasUserSyncedPublicRepositories: boolean
    hasUserAddedExternalServices: boolean

    /**
     * Whether globbing is enabled for filters.
     */
    globbing: boolean

    /**
     * Whether we show the mulitiline editor at /search/console
     */
    showMultilineSearchConsole: boolean

    /**
     * Whether we show the search notebook.
     */
    showSearchNotebook: boolean

    /**
     * Whether we show the mulitiline editor at /search/query-builder
     */
    showQueryBuilder: boolean

    /**
     * Whether the code monitoring feature flag is enabled.
     */
    enableCodeMonitoring: boolean

    /**
     * Whether the API docs feature flag is enabled.
     */
    enableAPIDocs: boolean

    /**
     * Evaluated feature flags for the current viewer
     */
    featureFlags: FlagSet
}


/** Returns whether Sourcegraph should be in light theme */
private isLightTheme(): boolean {
        return this.state.themePreference === 'system'
            ? this.state.systemIsLightTheme
            : this.state.themePreference === 'light'
    }


public componentDidMount(): void {
        updateUserSessionStores()

        document.documentElement.classList.add('theme')

        this.subscriptions.add(
            combineLatest([from(this.platformContext.settings), authenticatedUser.pipe(startWith(null))]).subscribe(
                ([settingsCascade, authenticatedUser]) => {
                    this.setState(state => ({
                        settingsCascade,
                        authenticatedUser,
                        ...experimentalFeaturesFromSettings(settingsCascade),
                        globbing: globbingEnabledFromSettings(settingsCascade),
                        searchCaseSensitivity:
                            defaultCaseSensitiveFromSettings(settingsCascade) || state.searchCaseSensitivity,
                        searchPatternType: defaultPatternTypeFromSettings(settingsCascade) || state.searchPatternType,
                        viewerSubject: viewerSubjectFromSettings(settingsCascade, authenticatedUser),
                    }))
                },
                () => this.setState({ authenticatedUser: null })
            )
        )

        // Track static metrics fo code insights.
        // Insight count, insights settings, observe settings mutations for analytics
        // Track add delete and update events of code insights via
        this.subscriptions.add(
            combineLatest([from(this.platformContext.settings), authenticatedUser])
                .pipe(bufferCount(2, 1))
                .subscribe(([[oldSettings], [newSettings, authUser]]) => {
                    if (authUser) {
                        logInsightMetrics(oldSettings, newSettings, eventLogger)
                    }
                })
        )

        // React to OS theme change
        this.subscriptions.add(
            fromEvent<MediaQueryListEvent>(this.darkThemeMediaList, 'change').subscribe(event => {
                this.setState({ systemIsLightTheme: !event.matches })
            })
        )

        this.subscriptions.add(
            combineLatest([this.userRepositoriesUpdates, authenticatedUser])
                .pipe(
                    switchMap(([, authenticatedUser]) =>
                        authenticatedUser
                            ? combineLatest([
                                  listUserRepositories({ id: authenticatedUser.id, first: 1 }),
                                  queryExternalServices({ namespace: authenticatedUser.id, first: 1, after: null }),
                              ])
                            : of(null)
                    ),
                    catchError(error => [asError(error)])
                )
                .subscribe(result => {
                    if (!isErrorLike(result) && result !== null) {
                        const [userRepositoriesResult, externalServicesResult] = result
                        this.setState({
                            hasUserAddedRepositories: userRepositoriesResult.nodes.length > 0,
                            hasUserAddedExternalServices: externalServicesResult.nodes.length > 0,
                        })
                    }
                })
        )

        /**
         * Listens for uncaught 401 errors when a user when a user was previously authenticated.
         *
         * Don't subscribe to this event when there wasn't an authenticated user,
         * as it could lead to an infinite loop of 401 -> reload -> 401
         */
        this.subscriptions.add(
            authenticatedUser
                .pipe(
                    switchMap(authenticatedUser =>
                        authenticatedUser ? fromEvent<ErrorEvent>(window, 'error') : of(null)
                    )
                )
                .subscribe(event => {
                    if (event?.error instanceof HTTPStatusError && event.error.status === 401) {
                        location.reload()
                    }
                })
        )

        this.subscriptions.add(
            fetchFeatureFlags().subscribe(event => {
                // Disabling linter here because this is not yet used anywhere.
                // This can be re-enabled as soon as feature flags are leveraged.
                // eslint-disable-next-line react/no-unused-state
                this.setState({ featureFlags: event })
            })
        )

        if (this.state.parsedSearchQuery && !filterExists(this.state.parsedSearchQuery, FilterType.context)) {
            // If a context filter does not exist in the query, we have to switch the selected context
            // to global to match the UI with the backend semantics (if no context is specified in the query,
            // the query is run in global context).
            this.setSelectedSearchContextSpec('global')
        }
        if (!this.state.parsedSearchQuery) {
            // If no query is present (e.g. search page, settings page), select the last saved
            // search context from localStorage as currently selected search context.
            const lastSelectedSearchContextSpec = localStorage.getItem(LAST_SEARCH_CONTEXT_KEY) || 'global'
            this.setSelectedSearchContextSpec(lastSelectedSearchContextSpec)
        }

        // Send initial versionContext to extensions
        this.setVersionContext(this.state.versionContext).catch(error => {
            console.error('Error sending initial version context to extensions', error)
        })

        this.setWorkspaceSearchContext(this.state.selectedSearchContextSpec).catch(error => {
            console.error('Error sending search context to extensions', error)
        })

        this.userRepositoriesUpdates.next()
    }


public componentWillUnmount(): void {
        this.subscriptions.unsubscribe()
        document.documentElement.classList.remove('theme', 'theme-light', 'theme-dark')
    }


public componentDidUpdate(): void {
        localStorage.setItem(LIGHT_THEME_LOCAL_STORAGE_KEY, this.state.themePreference)
        document.documentElement.classList.toggle('theme-light', this.isLightTheme())
        document.documentElement.classList.toggle('theme-dark', !this.isLightTheme())
    }


public render(): React.ReactFragment | null {
        if (window.pageError && window.pageError.statusCode !== 404) {
            const statusCode = window.pageError.statusCode
            const statusText = window.pageError.statusText
            const errorMessage = window.pageError.error
            const errorID = window.pageError.errorID

            let subtitle: JSX.Element | undefined
            if (errorID) {
                subtitle = <FeedbackText headerText="Sorry, there's been a problem." />
            }
            if (errorMessage) {
                subtitle = (

                        {subtitle}
                        {subtitle &&
}

{errorMessage}

                )
            } else {
                subtitle =
{subtitle}

            }
            return <HeroPage icon={ServerIcon} title={`${statusCode}: ${statusText}`} subtitle={subtitle} />
        }


export async function getHealthOverview(): Promise<ServiceHealthIssues> {
    try {

        let serviceHealthIssues: ServiceHealthIssues =
        await MSGraph.Get("/admin/serviceAnnouncement/issues",
        "v1.0", ["id,title,service"], [], "isResolved eq false", true);
        console.debug("Service health issues response from Microsoft Graph %o", serviceHealthIssues);
        return serviceHealthIssues;

        // * Mock data
        /* let s: ServiceHealthIssues = {
            "@odata.count": 4,
            "value": [
                {
                    "title": "Admins see some users' Outlook Desktop activity isn't shown in usage reports",
                    "id": "MO262870",
                    "service": "Microsoft 365 suite"
                },
                {
                    "title": "Admins’ custom connectors on Data Loss Prevention (DLP) policies are removed when edited in Power Platform admin center",
                    "id": "MO275990",
                    "service": "Microsoft 365 suite"
                },
                {
                    "title": "Users are unable to toggle desktop notifications in Outlook on the web",
                    "id": "EX294175",
                    "service": "Exchange Online"
                },
                {
                    "title": "Some admins may be unable to access the SharePoint admin center from the left pane in the classic experience",
                    "id": "SP291527",
                    "service": "SharePoint Online"
                }
            ]
        }

        return s; */

    } catch (error) {
        console.error(error);
        return null;
    }
}


//TODO: Add icons for each service
export function getIconForService(servicename: string) {
    switch (servicename) {
        case "Exchange Online":
            return "https://upload.wikimedia.org/wikipedia/commons/e/ea/Microsoft_Exchange_%282019-present%29.svg";
        case "Microsoft 365 suite":
            return "https://static2.sharepointonline.com/files/fabric-cdn-prod_20200430.002/assets/brand-icons/product/svg/office_48x1.svg";
        case "SharePoint Online":
            return "https://static2.sharepointonline.com/files/fabric-cdn-prod_20200430.002/assets/brand-icons/product/svg/sharepoint_48x1.svg";

        default:
            return "https://static2.sharepointonline.com/files/fabric-cdn-prod_20200430.002/assets/brand-icons/product/svg/office_48x1.svg";
    }
}


/**
 *
Cancels the requested policy generation.

 * @example
 * Use a bare-bones client and the command you need to make an API call.
 * ```javascript
 * import { AccessAnalyzerClient, CancelPolicyGenerationCommand } from "@aws-sdk/client-accessanalyzer"; // ES Modules import
 * // const { AccessAnalyzerClient, CancelPolicyGenerationCommand } = require("@aws-sdk/client-accessanalyzer"); // CommonJS import
 * const client = new AccessAnalyzerClient(config);
 * const command = new CancelPolicyGenerationCommand(input);
 * const response = await client.send(command);
 * ```
 *
 * @see {@link CancelPolicyGenerationCommandInput} for command's `input` shape.
 * @see {@link CancelPolicyGenerationCommandOutput} for command's `response` shape.
 * @see {@link AccessAnalyzerClientResolvedConfig | config} for command's `input` shape.
 *
 */
export class CancelPolicyGenerationCommand extends $Command<
  CancelPolicyGenerationCommandInput,
  CancelPolicyGenerationCommandOutput,
  AccessAnalyzerClientResolvedConfig
> {
  // Start section: command_properties
  // End section: command_properties

  constructor(readonly input: CancelPolicyGenerationCommandInput) {
    // Start section: command_constructor
    super();
    // End section: command_constructor
  }

  /**
   * @internal
   */
  resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: AccessAnalyzerClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<CancelPolicyGenerationCommandInput, CancelPolicyGenerationCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const { logger } = configuration;
    const clientName = "AccessAnalyzerClient";
    const commandName = "CancelPolicyGenerationCommand";
    const handlerExecutionContext: HandlerExecutionContext = {
      logger,
      clientName,
      commandName,
      inputFilterSensitiveLog: CancelPolicyGenerationRequest.filterSensitiveLog,
      outputFilterSensitiveLog: CancelPolicyGenerationResponse.filterSensitiveLog,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }

  private serialize(input: CancelPolicyGenerationCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_restJson1CancelPolicyGenerationCommand(input, context);
  }

  private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<CancelPolicyGenerationCommandOutput> {
    return deserializeAws_restJson1CancelPolicyGenerationCommand(output, context);
  }

  // Start section: command_body_extra
  // End section: command_body_extra
}


export interface CancelPolicyGenerationCommandInput extends CancelPolicyGenerationRequest {}


export interface CancelPolicyGenerationCommandOutput extends CancelPolicyGenerationResponse, __MetadataBearer {}


/**
   * @internal
   */
resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: AccessAnalyzerClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<CancelPolicyGenerationCommandInput, CancelPolicyGenerationCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const { logger } = configuration;
    const clientName = "AccessAnalyzerClient";
    const commandName = "CancelPolicyGenerationCommand";
    const handlerExecutionContext: HandlerExecutionContext = {
      logger,
      clientName,
      commandName,
      inputFilterSensitiveLog: CancelPolicyGenerationRequest.filterSensitiveLog,
      outputFilterSensitiveLog: CancelPolicyGenerationResponse.filterSensitiveLog,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }


private serialize(input: CancelPolicyGenerationCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_restJson1CancelPolicyGenerationCommand(input, context);
  }


private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<CancelPolicyGenerationCommandOutput> {
    return deserializeAws_restJson1CancelPolicyGenerationCommand(output, context);
  }


export async function getBinaryPath(bin: string) {
  const binPath = path.resolve(`./node_modules/.bin/${bin}`);
  try {
    await accessAsync(binPath, fs.constants.F_OK);
  } catch (e) {
    throw new Error(`Can't access "${bin}" binary. Please check it has been properly installed`);
  }
  return binPath;
}


(reply: unknown) => {
            if ((this.#pubSubState.subscribing || this.#pubSubState.subscribed) && Array.isArray(reply)) {
                switch (reply[0]) {
                    case 'message':
                        return RedisCommandsQueue.#emitPubSubMessage(
                            this.#pubSubListeners.channels.get(reply[1])!,
                            reply[2],
                            reply[1]
                        );

                    case 'pmessage':
                        return RedisCommandsQueue.#emitPubSubMessage(
                            this.#pubSubListeners.patterns.get(reply[1])!,
                            reply[3],
                            reply[2]
                        );

                    case 'subscribe':
                    case 'psubscribe':
                        if (--this.#waitingForReply.head!.value.channelsCounter! === 0) {
                            this.#shiftWaitingForReply().resolve();
                        }
                        return;
                }
            }

            this.#shiftWaitingForReply().resolve(reply);
        }


(err: Error) => this.#shiftWaitingForReply().reject(err)


(resolve, reject) => {
            const node = new LinkedList.Node<CommandWaitingToBeSent>({
                args,
                chainId: options?.chainId,
                bufferMode,
                resolve,
                reject,
            });

            if (options?.signal) {
                const listener = () => {
                    this.#waitingToBeSent.removeNode(node);
                    node.value.reject(new AbortError());
                };

                node.value.abort = {
                    signal: options.signal,
                    listener
                };
                options.signal.addEventListener('abort', listener, {
                    once: true
                });
            }

            if (options?.asap) {
                this.#waitingToBeSent.unshiftNode(node);
            } else {
                this.#waitingToBeSent.pushNode(node);
            }
        }


() => {
                    this.#waitingToBeSent.removeNode(node);
                    node.value.reject(new AbortError());
                }


(resolve, reject) => {
            const isSubscribe = command === PubSubSubscribeCommands.SUBSCRIBE || command === PubSubSubscribeCommands.PSUBSCRIBE,
                inProgressKey = isSubscribe ? 'subscribing' : 'unsubscribing',
                commandArgs: Array<string> = [command];

            let channelsCounter: number;
            if (typeof channels === 'number') { // unsubscribe only
                channelsCounter = channels;
            } else {
                commandArgs.push(...channels);
                channelsCounter = channels.length;
            }

            this.#pubSubState[inProgressKey] += channelsCounter;

            this.#waitingToBeSent.push({
                args: commandArgs,
                channelsCounter,
                resolve: () => {
                    this.#pubSubState[inProgressKey] -= channelsCounter;
                    this.#pubSubState.subscribed += channelsCounter * (isSubscribe ? 1 : -1);
                    resolve();
                },
                reject: () => {
                    this.#pubSubState[inProgressKey] -= channelsCounter;
                    reject();
                }
            });
        }


() => {
                    this.#pubSubState[inProgressKey] -= channelsCounter;
                    this.#pubSubState.subscribed += channelsCounter * (isSubscribe ? 1 : -1);
                    resolve();
                }


() => {
                    this.#pubSubState[inProgressKey] -= channelsCounter;
                    reject();
                }


export interface QueueCommandOptions {
    asap?: boolean;
    chainId?: symbol;
    signal?: any; // TODO: `AbortSignal` type is incorrect
}


interface CommandWaitingToBeSent extends CommandWaitingForReply {
    args: Array<string | Buffer>;
    chainId?: symbol;
    abort?: {
        signal: any; // TODO: `AbortSignal` type is incorrect
        listener(): void;
    };
}


interface CommandWaitingForReply {
    resolve(reply?: any): void;
    reject(err: Error): void;
    channelsCounter?: number;
    bufferMode?: boolean;
}


export enum PubSubSubscribeCommands {
    SUBSCRIBE = 'SUBSCRIBE',
    PSUBSCRIBE = 'PSUBSCRIBE'
}


export enum PubSubUnsubscribeCommands {
    UNSUBSCRIBE = 'UNSUBSCRIBE',
    PUNSUBSCRIBE = 'PUNSUBSCRIBE'
}


export type PubSubListener = (message: string, channel: string) => unknown;


export type PubSubListenersMap = Map<string, Set<PubSubListener>>;


static #flushQueue<T extends CommandWaitingForReply>(queue: LinkedList<T>, err: Error): void {
        while (queue.length) {
            queue.shift()!.reject(err);
        }
    }


static #emitPubSubMessage(listeners: Set<PubSubListener>, message: string, channel: string): void {
        for (const listener of listeners) {
            listener(message, channel);
        }
    }


addCommand<T = RedisCommandRawReply>(args: Array<string | Buffer>, options?: QueueCommandOptions, bufferMode?: boolean): Promise<T> {
        if (this.#pubSubState.subscribing || this.#pubSubState.subscribed) {
            return Promise.reject(new Error('Cannot send commands in PubSub mode'));
        } else if (this.#maxLength && this.#waitingToBeSent.length + this.#waitingForReply.length >= this.#maxLength) {
            return Promise.reject(new Error('The queue is full'));
        } else if (options?.signal?.aborted) {
            return Promise.reject(new AbortError());
        }

        return new Promise((resolve, reject) => {
            const node = new LinkedList.Node<CommandWaitingToBeSent>({
                args,
                chainId: options?.chainId,
                bufferMode,
                resolve,
                reject,
            });

            if (options?.signal) {
                const listener = () => {
                    this.#waitingToBeSent.removeNode(node);
                    node.value.reject(new AbortError());
                };

                node.value.abort = {
                    signal: options.signal,
                    listener
                };
                options.signal.addEventListener('abort', listener, {
                    once: true
                });
            }

            if (options?.asap) {
                this.#waitingToBeSent.unshiftNode(node);
            } else {
                this.#waitingToBeSent.pushNode(node);
            }
        });
    }


subscribe(command: PubSubSubscribeCommands, channels: string | Array<string>, listener: PubSubListener): Promise<void> {
        const channelsToSubscribe: Array<string> = [],
            listeners = command === PubSubSubscribeCommands.SUBSCRIBE ? this.#pubSubListeners.channels : this.#pubSubListeners.patterns;
        for (const channel of (Array.isArray(channels) ? channels : [channels])) {
            if (listeners.has(channel)) {
                listeners.get(channel)!.add(listener);
                continue;
            }

            listeners.set(channel, new Set([listener]));
            channelsToSubscribe.push(channel);
        }

        if (!channelsToSubscribe.length) {
            return Promise.resolve();
        }

        return this.#pushPubSubCommand(command, channelsToSubscribe);
    }


unsubscribe(command: PubSubUnsubscribeCommands, channels?: string | Array<string>, listener?: PubSubListener): Promise<void> {
        const listeners = command === PubSubUnsubscribeCommands.UNSUBSCRIBE ? this.#pubSubListeners.channels : this.#pubSubListeners.patterns;
        if (!channels) {
            const size = listeners.size;
            listeners.clear();
            return this.#pushPubSubCommand(command, size);
        }

        const channelsToUnsubscribe = [];
        for (const channel of (Array.isArray(channels) ? channels : [channels])) {
            const set = listeners.get(channel);
            if (!set) continue;

            let shouldUnsubscribe = !listener;
            if (listener) {
                set.delete(listener);
                shouldUnsubscribe = set.size === 0;
            }

            if (shouldUnsubscribe) {
                channelsToUnsubscribe.push(channel);
                listeners.delete(channel);
            }
        }

        if (!channelsToUnsubscribe.length) {
            return Promise.resolve();
        }

        return this.#pushPubSubCommand(command, channelsToUnsubscribe);
    }


#pushPubSubCommand(command: PubSubSubscribeCommands | PubSubUnsubscribeCommands, channels: number | Array<string>): Promise<void> {
        return new Promise((resolve, reject) => {
            const isSubscribe = command === PubSubSubscribeCommands.SUBSCRIBE || command === PubSubSubscribeCommands.PSUBSCRIBE,
                inProgressKey = isSubscribe ? 'subscribing' : 'unsubscribing',
                commandArgs: Array<string> = [command];

            let channelsCounter: number;
            if (typeof channels === 'number') { // unsubscribe only
                channelsCounter = channels;
            } else {
                commandArgs.push(...channels);
                channelsCounter = channels.length;
            }

            this.#pubSubState[inProgressKey] += channelsCounter;

            this.#waitingToBeSent.push({
                args: commandArgs,
                channelsCounter,
                resolve: () => {
                    this.#pubSubState[inProgressKey] -= channelsCounter;
                    this.#pubSubState.subscribed += channelsCounter * (isSubscribe ? 1 : -1);
                    resolve();
                },
                reject: () => {
                    this.#pubSubState[inProgressKey] -= channelsCounter;
                    reject();
                }
            });
        });
    }


resubscribe(): Promise<any> | undefined {
        if (!this.#pubSubState.subscribed && !this.#pubSubState.subscribing) {
            return;
        }

        this.#pubSubState.subscribed = this.#pubSubState.subscribing = 0;

        // TODO: acl error on one channel/pattern will reject the whole command
        return Promise.all([
            this.#pushPubSubCommand(PubSubSubscribeCommands.SUBSCRIBE, [...this.#pubSubListeners.channels.keys()]),
            this.#pushPubSubCommand(PubSubSubscribeCommands.PSUBSCRIBE, [...this.#pubSubListeners.patterns.keys()])
        ]);
    }


getCommandToSend(): Array<string | Buffer> | undefined {
        const toSend = this.#waitingToBeSent.shift();

        if (toSend) {
            this.#waitingForReply.push({
                resolve: toSend.resolve,
                reject: toSend.reject,
                channelsCounter: toSend.channelsCounter,
                bufferMode: toSend.bufferMode
            });
        }

        this.#chainInExecution = toSend?.chainId;

        return toSend?.args;
    }


parseResponse(data: Buffer): void {
        this.#parser.setReturnBuffers(!!this.#waitingForReply.head?.value.bufferMode);
        this.#parser.execute(data);
    }


#shiftWaitingForReply(): CommandWaitingForReply {
        if (!this.#waitingForReply.length) {
            throw new Error('Got an unexpected reply from Redis');
        }

        return this.#waitingForReply.shift()!;
    }


flushWaitingForReply(err: Error): void {
        RedisCommandsQueue.#flushQueue(this.#waitingForReply, err);

        if (!this.#chainInExecution) {
            return;
        }

        while (this.#waitingToBeSent.head?.value.chainId === this.#chainInExecution) {
            this.#waitingToBeSent.shift();
        }

        this.#chainInExecution = undefined;
    }


flushAll(err: Error): void {
        RedisCommandsQueue.#flushQueue(this.#waitingForReply, err);
        RedisCommandsQueue.#flushQueue(this.#waitingToBeSent, err);
    }


//react component
export default class Directions extends AnimationSubsection {

    protected subsection = 'directions' as const;

    protected values: Direction[] = directions;

    protected isChecked(direction: Direction) {
        return this.props.options.directions.includes(direction);
    }

    protected toggle(direction: Direction) {
        let options = this.props.options.directions.map(x => x);
        if (this.isChecked(direction)) {
            options.splice(options.indexOf(direction), 1);
        } else {
            options.push(direction);
        }
        this.props.changeValue('directions', options);
    }
}


protected isChecked(direction: Direction) {
        return this.props.options.directions.includes(direction);
    }


protected toggle(direction: Direction) {
        let options = this.props.options.directions.map(x => x);
        if (this.isChecked(direction)) {
            options.splice(options.indexOf(direction), 1);
        } else {
            options.push(direction);
        }
        this.props.changeValue('directions', options);
    }


() => {
  test
  .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => {
    api
    .get('/buildpacks/heroku%2Fruby')
    .reply(200, Fixture.buildpack({
      name: 'ruby',
      source: {
        type: 'github',
        owner: 'heroku',
        repo: 'heroku-buildpack-ruby',
      },
    }))
    .get('/buildpacks/heroku%2Fruby/revisions')
    .reply(200, [Fixture.revision()])
    .get('/buildpacks/heroku%2Fruby/readme')
    .reply(200, Fixture.readme())
  })
  .stdout()
  .command(['buildpacks:info', 'heroku/ruby'])
  .it('shows info about the buildpack', ctx => {
    expect(ctx.stdout).to.contain('=== heroku/ruby')
    expect(ctx.stdout).to.contain('languages')
    expect(ctx.stdout).to.match(/source:\s+https:\/\/github\.com\/heroku\/heroku-buildpack-ruby/)
  })

  test
  .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => {
    api
    .get('/buildpacks/hone%2Ftest')
    .reply(404, {})
  })
  .command(['buildpacks:info', 'hone/test'])
  .catch("Could not find the buildpack 'hone/test'")
  .it("handles if the buildpack doesn't exist")

  test
  .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => {
    api
    .get('/buildpacks/hone%2Ftest')
    .reply(500, 'some error')
  })
  .command(['buildpacks:info', 'hone/test'])
  .catch('Problems finding buildpack info: some error')
  .it('handles case if there are errors from the API')
}


(api: nock.Scope) => {
    api
    .get('/buildpacks/heroku%2Fruby')
    .reply(200, Fixture.buildpack({
      name: 'ruby',
      source: {
        type: 'github',
        owner: 'heroku',
        repo: 'heroku-buildpack-ruby',
      },
    }))
    .get('/buildpacks/heroku%2Fruby/revisions')
    .reply(200, [Fixture.revision()])
    .get('/buildpacks/heroku%2Fruby/readme')
    .reply(200, Fixture.readme())
  }


ctx => {
    expect(ctx.stdout).to.contain('=== heroku/ruby')
    expect(ctx.stdout).to.contain('languages')
    expect(ctx.stdout).to.match(/source:\s+https:\/\/github\.com\/heroku\/heroku-buildpack-ruby/)
  }


(api: nock.Scope) => {
    api
    .get('/buildpacks/hone%2Ftest')
    .reply(404, {})
  }


(api: nock.Scope) => {
    api
    .get('/buildpacks/hone%2Ftest')
    .reply(500, 'some error')
  }


/**
	 * Prepares data object for video events tracking
	 */
getEventData(videoData: VideoData): VideoEventData {
		const now: Date = new Date();
		const slot: AdSlot = slotService.get(videoData.position);

		if (!slot) {
			throw new Error(`Slot ${videoData.position} is not registered.`);
		}

		return {
			ad_error_code: videoData.ad_error_code,
			ad_product: videoData.ad_product,
			audio: videoData.audio ? 1 : 0,
			browser: `${utils.client.getOperatingSystem()} ${utils.client.getBrowser()}`,
			content_type: videoData.content_type || '',
			country: utils.geoService.getCountryCode() || '',
			creative_id: videoData.creative_id || '',
			ctp: videoData.ctp ? 1 : 0,
			document_visibility: utils.getDocumentVisibilityStatus(),
			event_name: videoData.event_name,
			line_item_id: videoData.line_item_id || '',
			player: videoData.player,
			position: slot.getSlotName().toLowerCase(),
			price: '',
			pv_number: context.get('options.pvNumber') || window.pvNumber || -1,
			skin: context.get('targeting.skin') || '',
			timestamp: now.getTime(),
			tz_offset: now.getTimezoneOffset(),
			user_block_autoplay: videoData.user_block_autoplay,
			video_id: videoData.video_id || '',
			wsi: slot.targeting.wsi || '',
		};
	}


() => import('@/views/Find/pages/Recommed/index.vue')


() => import('@/views/Find/pages/MusicList/index.vue')


() => import('@/views/Find/pages/AanchrRadio/index.vue')


() => import('@/views/Find/pages/Top/index.vue')


() => import('@/views/Find/pages/Singer/index.vue')


() => import('@/views/Find/pages/Latest/index.vue')


() => ({
  panelActions: {
    justifyContent: "flex-start"
  },
  failureMessage: {
    backgroundColor: "#EDEDED",
    overflowX: "auto",
    fontSize: "0.9em"
  }
})


({
  testCase,
  publicId,
  showFullFailure
}: TestCaseFailurePanelProps) => {
  const classes = useStyles({});
  const testCaseIdentifier = `${testCase.testSuiteIdx}-${testCase.idx}`;

  return (
    <ExpansionPanel
      defaultExpanded
      data-testid={`test-case-summary-${testCaseIdentifier}`}
    >
      <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
        <Typography variant="subtitle2" data-testid="test-case-title">
          {testCase.packageName}.{testCase.className} {testCase.name}
        </Typography>
      </ExpansionPanelSummary>
      <ExpansionPanelDetails className={classes.failureMessage}>
        {testCase.failure != null && (

            <pre data-testid={`test-case-failure-text-${testCaseIdentifier}`}>
              {showFullFailure
                ? testCase.failure.failureText


interface TestCaseFailurePanelProps {
  testCase: TestCase;
  publicId: string;
  showFullFailure?: boolean;
}


export function TelemetryPlugin(vue: typeof _Vue): void {
	const telemetry = new Telemetry();

	Object.defineProperty(vue, '$telemetry', {
		get() { return telemetry; },
	});
	Object.defineProperty(vue.prototype, '$telemetry', {
		get() { return telemetry; },
	});
}


(node: INodeCreateElement) => node.key


(t: any) => { // tslint:disable-line:no-any
			return (...args: any[]) => { // tslint:disable-line:no-any
				const r = Array.prototype.slice.call(args);
				r.unshift(t);
				this.telemetry.push(r);
				return this.telemetry;
			};
		}


(...args: any[]) => { // tslint:disable-line:no-any
				const r = Array.prototype.slice.call(args);
				r.unshift(t);
				this.telemetry.push(r);
				return this.telemetry;
			}