Prevented all tests to log to console, except for one DebugController test.

This commit is contained in:
Daan Vanden Bosch 2020-09-27 00:31:22 +02:00
parent 94d15b86ec
commit 864bf40da3
79 changed files with 458 additions and 498 deletions

View File

@ -9,7 +9,7 @@ import {
} from "../src/core/data_formats/parsing/quest/npc_types";
import { ItemTypeDto } from "../src/core/dto/ItemTypeDto";
import { BoxDropDto, EnemyDropDto } from "../src/hunt_optimizer/dto/drops";
import { LogManager } from "../src/core/Logger";
import { LogManager } from "../src/core/logging";
const logger = LogManager.get("assets_generation/update_drops_ephinea");

View File

@ -12,7 +12,7 @@ import { Endianness } from "../src/core/data_formats/block/Endianness";
import { ItemTypeDto } from "../src/core/dto/ItemTypeDto";
import { QuestDto } from "../src/hunt_optimizer/dto/QuestDto";
import { BoxDropDto, EnemyDropDto } from "../src/hunt_optimizer/dto/drops";
import { LogManager } from "../src/core/Logger";
import { LogManager } from "../src/core/logging";
import { Severity } from "../src/core/Severity";
import { unwrap } from "../src/core/Result";
import { get_npc_type } from "../src/core/data_formats/parsing/quest/QuestNpc";

View File

@ -4,7 +4,7 @@ import { BufferCursor } from "../src/core/data_formats/block/cursor/BufferCursor
import { parse_rlc } from "../src/core/data_formats/parsing/rlc";
import * as yaml from "yaml";
import { Endianness } from "../src/core/data_formats/block/Endianness";
import { LogManager } from "../src/core/Logger";
import { LogManager } from "../src/core/logging";
import { Severity } from "../src/core/Severity";
import { unwrap } from "../src/core/Result";

View File

@ -1,7 +1,6 @@
import { initialize_application } from "./index";
import { LogHandler, LogManager } from "../core/Logger";
import { FileSystemHttpClient } from "../../test/src/core/FileSystemHttpClient";
import { timeout } from "../../test/src/utils";
import { pw_test, timeout } from "../../test/src/utils";
import { Random } from "../core/Random";
import { Severity } from "../core/Severity";
import { StubClock } from "../../test/src/core/StubClock";
@ -10,35 +9,25 @@ import { STUB_RENDERER } from "../../test/src/core/rendering/StubRenderer";
for (const path of [undefined, "/viewer", "/quest_editor", "/hunt_optimizer"]) {
const with_path = path == undefined ? "without specific path" : `with path ${path}`;
test(`Initialization and shutdown ${with_path} should succeed without throwing or logging errors.`, async () => {
const logged_errors: string[] = [];
const handler: LogHandler = ({ severity, message }) => {
if (severity >= Severity.Error) {
logged_errors.push(message);
}
};
return LogManager.with_default_handler(handler, async () => {
test(
`Initialization and shutdown ${with_path} should succeed without throwing or logging errors.`,
pw_test({ max_log_severity: Severity.Warning }, async disposer => {
if (path != undefined) {
window.location.hash = path;
}
const app = initialize_application(
new FileSystemHttpClient(),
new Random(() => 0.27),
new StubClock(new Date("2020-01-01T15:40:20Z")),
() => STUB_RENDERER,
const app = disposer.add(
initialize_application(
new FileSystemHttpClient(),
new Random(() => 0.27),
new StubClock(new Date("2020-01-01T15:40:20Z")),
() => STUB_RENDERER,
),
);
expect(app).toBeDefined();
expect(logged_errors).toEqual([]);
await timeout(1000);
app.dispose();
expect(logged_errors).toEqual([]);
});
});
}),
);
}

View File

@ -1,4 +1,5 @@
import { Disposable } from "./observable/Disposable";
import { is_promise_like } from "./util";
enum State {
Pending,
@ -250,7 +251,3 @@ export class DisposablePromise<T> implements Promise<T>, Disposable {
}
}
}
function is_promise_like<T>(value?: T | PromiseLike<T>): value is PromiseLike<T> {
return value != undefined && typeof (value as any).then === "function";
}

View File

@ -1,4 +1,4 @@
import { Logger } from "./Logger";
import { Logging } from "./logging";
import { Severity } from "./Severity";
export type Result<T> = Success<T> | Failure;
@ -60,7 +60,7 @@ export function unwrap<T>(result: Result<T>): T {
export class ResultBuilder<T> {
private readonly problems: Problem[] = [];
constructor(private readonly logger: Logger) {}
constructor(private readonly logger: Logging) {}
/**
* Add a problem to the problems array and log it with {@link logger}.

View File

@ -2,7 +2,7 @@ import { InstructionSegment } from "../instructions";
import { ControlFlowGraph } from "./ControlFlowGraph";
import { OP_BB_MAP_DESIGNATE, OP_MAP_DESIGNATE, OP_MAP_DESIGNATE_EX } from "../opcodes";
import { get_register_value } from "./get_register_value";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
const logger = LogManager.get("core/data_formats/asm/data_flow_analysis/map_designations");

View File

@ -25,7 +25,7 @@ import {
} from "../opcodes";
import { BasicBlock, ControlFlowGraph } from "./ControlFlowGraph";
import { ValueSet } from "./ValueSet";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
const logger = LogManager.get("core/data_formats/asm/data_flow_analysis/register_value");

View File

@ -14,7 +14,7 @@ import {
import { BasicBlock, ControlFlowGraph } from "./ControlFlowGraph";
import { ValueSet } from "./ValueSet";
import { get_register_value } from "./get_register_value";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
const logger = LogManager.get("core/data_formats/asm/data_flow_analysis/stack_value");

View File

@ -2,7 +2,7 @@ import { Cursor } from "../../block/cursor/Cursor";
import { ResizableBlockCursor } from "../../block/cursor/ResizableBlockCursor";
import { WritableCursor } from "../../block/cursor/WritableCursor";
import { ResizableBlock } from "../../block/ResizableBlock";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
import { browser_supports_webassembly } from "../../../util";
import { get_prs_wasm_module } from "./prs_wasm";
import { Result, ResultBuilder, success } from "../../../Result";

View File

@ -1,5 +1,5 @@
import { Cursor } from "../block/cursor/Cursor";
import { LogManager } from "../../Logger";
import { LogManager } from "../../logging";
import { Result, ResultBuilder } from "../../Result";
import { Severity } from "../../Severity";

View File

@ -1,6 +1,6 @@
import { Cursor } from "../block/cursor/Cursor";
import { Result, ResultBuilder } from "../../Result";
import { LogManager } from "../../Logger";
import { LogManager } from "../../logging";
import { Severity } from "../../Severity";
const logger = LogManager.get("core/data_formats/parsing/iff");

View File

@ -1,6 +1,6 @@
import { Cursor } from "../../block/cursor/Cursor";
import { Vec2, Vec3 } from "../../vector";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
const logger = LogManager.get("core/data_formats/parsing/ninja/njcm");

View File

@ -1,6 +1,6 @@
import { Cursor } from "../../block/cursor/Cursor";
import { parse_iff, parse_iff_headers } from "../iff";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
import { Result, ResultBuilder } from "../../../Result";
import { Severity } from "../../../Severity";

View File

@ -1,6 +1,6 @@
import { Cursor } from "../../block/cursor/Cursor";
import { Vec2, Vec3 } from "../../vector";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
const logger = LogManager.get("core/data_formats/parsing/ninja/xj");

View File

@ -1,7 +1,7 @@
import { prs_decompress } from "../compression/prs/decompress";
import { Cursor } from "../block/cursor/Cursor";
import { prc_decrypt } from "../encryption/prc";
import { LogManager } from "../../Logger";
import { LogManager } from "../../logging";
import { Result, ResultBuilder } from "../../Result";
import { Severity } from "../../Severity";

View File

@ -6,41 +6,40 @@ import { BufferCursor } from "../../block/cursor/BufferCursor";
import { parse_bin, write_bin } from "./bin";
import { BinFormat } from "./BinFormat";
import { unwrap } from "../../../Result";
import { pw_test } from "../../../../../test/src/utils";
/**
* Parse a file, convert the resulting structure to BIN again and check whether the end result is equal to the original.
*/
function test_quest(path: string): void {
const orig_buffer = readFileSync(path);
const orig_bin = unwrap(prs_decompress(new BufferCursor(orig_buffer, Endianness.Little)));
const test_buffer = write_bin(parse_bin(orig_bin).bin, BinFormat.BB);
const test_bin = new ArrayBufferCursor(test_buffer, Endianness.Little);
function test_quest(path: string): () => void {
return pw_test({}, () => {
const orig_buffer = readFileSync(path);
const orig_bin = unwrap(prs_decompress(new BufferCursor(orig_buffer, Endianness.Little)));
const test_buffer = write_bin(parse_bin(orig_bin).bin, BinFormat.BB);
const test_bin = new ArrayBufferCursor(test_buffer, Endianness.Little);
orig_bin.seek_start(0);
expect(test_bin.size).toBe(orig_bin.size);
orig_bin.seek_start(0);
expect(test_bin.size).toBe(orig_bin.size);
let matching_bytes = 0;
let matching_bytes = 0;
while (orig_bin.bytes_left) {
const test_byte = test_bin.u8();
const orig_byte = orig_bin.u8();
while (orig_bin.bytes_left) {
const test_byte = test_bin.u8();
const orig_byte = orig_bin.u8();
if (test_byte !== orig_byte) {
throw new Error(
`Byte ${matching_bytes} didn't match, expected ${orig_byte}, got ${test_byte}.`,
);
if (test_byte !== orig_byte) {
throw new Error(
`Byte ${matching_bytes} didn't match, expected ${orig_byte}, got ${test_byte}.`,
);
}
matching_bytes++;
}
matching_bytes++;
}
expect(matching_bytes).toBe(orig_bin.size);
expect(matching_bytes).toBe(orig_bin.size);
});
}
test("parse_bin and write_bin with quest118_e.bin", () => {
test_quest("test/resources/quest118_e.bin");
});
test("parse_bin and write_bin with quest118_e.bin", test_quest("test/resources/quest118_e.bin"));
test("parse_bin and write_bin with quest27_e.bin", () => {
test_quest("test/resources/quest27_e.bin");
});
test("parse_bin and write_bin with quest27_e.bin", test_quest("test/resources/quest27_e.bin"));

View File

@ -1,6 +1,6 @@
import { Endianness } from "../../block/Endianness";
import { Cursor } from "../../block/cursor/Cursor";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
import { ArrayBufferCursor } from "../../block/cursor/ArrayBufferCursor";
import { assert } from "../../../util";
import { BinFormat } from "./BinFormat";

View File

@ -5,66 +5,73 @@ import { ResizableBlockCursor } from "../../block/cursor/ResizableBlockCursor";
import { parse_dat, write_dat } from "./dat";
import { readFileSync } from "fs";
import { unwrap } from "../../../Result";
import { pw_test } from "../../../../../test/src/utils";
/**
* Parse a file, convert the resulting structure to DAT again and check whether the end result is equal to the original.
*/
test("parse_dat and write_dat", () => {
const orig_buffer = readFileSync("test/resources/quest118_e.dat");
const orig_dat = unwrap(prs_decompress(new BufferCursor(orig_buffer, Endianness.Little)));
const test_dat = new ResizableBlockCursor(write_dat(parse_dat(orig_dat)));
orig_dat.seek_start(0);
test(
"parse_dat and write_dat",
pw_test({}, () => {
const orig_buffer = readFileSync("test/resources/quest118_e.dat");
const orig_dat = unwrap(prs_decompress(new BufferCursor(orig_buffer, Endianness.Little)));
const test_dat = new ResizableBlockCursor(write_dat(parse_dat(orig_dat)));
orig_dat.seek_start(0);
expect(test_dat.size).toBe(orig_dat.size);
expect(test_dat.size).toBe(orig_dat.size);
let match = true;
let match = true;
while (orig_dat.bytes_left) {
if (test_dat.u8() !== orig_dat.u8()) {
match = false;
break;
while (orig_dat.bytes_left) {
if (test_dat.u8() !== orig_dat.u8()) {
match = false;
break;
}
}
}
expect(match).toBe(true);
});
expect(match).toBe(true);
}),
);
/**
* Parse a file, modify the resulting structure, convert it to DAT again and check whether the end result is equal to the original except for the bytes that should be changed.
*/
test("parse, modify and write DAT", () => {
const orig_buffer = readFileSync("./test/resources/quest118_e.dat");
const orig_dat = unwrap(prs_decompress(new BufferCursor(orig_buffer, Endianness.Little)));
const test_parsed = parse_dat(orig_dat);
orig_dat.seek_start(0);
test(
"parse, modify and write DAT",
pw_test({}, () => {
const orig_buffer = readFileSync("./test/resources/quest118_e.dat");
const orig_dat = unwrap(prs_decompress(new BufferCursor(orig_buffer, Endianness.Little)));
const test_parsed = parse_dat(orig_dat);
orig_dat.seek_start(0);
const test_obj_array = new Float32Array(test_parsed.objs[9].data);
test_obj_array[4] = 13;
test_obj_array[5] = 17;
test_obj_array[6] = 19;
const test_obj_array = new Float32Array(test_parsed.objs[9].data);
test_obj_array[4] = 13;
test_obj_array[5] = 17;
test_obj_array[6] = 19;
const test_dat = new ResizableBlockCursor(write_dat(test_parsed));
const test_dat = new ResizableBlockCursor(write_dat(test_parsed));
expect(test_dat.size).toBe(orig_dat.size);
expect(test_dat.size).toBe(orig_dat.size);
while (orig_dat.bytes_left) {
if (orig_dat.position === 16 + 9 * 68 + 16) {
orig_dat.seek(12);
while (orig_dat.bytes_left) {
if (orig_dat.position === 16 + 9 * 68 + 16) {
orig_dat.seek(12);
expect(test_dat.f32()).toBe(13);
expect(test_dat.f32()).toBe(17);
expect(test_dat.f32()).toBe(19);
} else {
const test_byte = test_dat.u8();
const orig_byte = orig_dat.u8();
expect(test_dat.f32()).toBe(13);
expect(test_dat.f32()).toBe(17);
expect(test_dat.f32()).toBe(19);
} else {
const test_byte = test_dat.u8();
const orig_byte = orig_dat.u8();
if (test_byte !== orig_byte) {
throw new Error(
`Byte ${
test_dat.position - 1
} didn't match, expected ${orig_byte}, got ${test_byte}.`,
);
if (test_byte !== orig_byte) {
throw new Error(
`Byte ${
test_dat.position - 1
} didn't match, expected ${orig_byte}, got ${test_byte}.`,
);
}
}
}
}
});
}),
);

View File

@ -5,7 +5,7 @@ import { ResizableBlockCursor } from "../../block/cursor/ResizableBlockCursor";
import { ResizableBlock } from "../../block/ResizableBlock";
import { WritableCursor } from "../../block/cursor/WritableCursor";
import { assert } from "../../../util";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
import { ArrayBufferCursor } from "../../block/cursor/ArrayBufferCursor";
const logger = LogManager.get("core/data_formats/parsing/quest/dat");

View File

@ -1,6 +1,6 @@
import { readFileSync } from "fs";
import { Endianness } from "../../block/Endianness";
import { walk_qst_files } from "../../../../../test/src/utils";
import { pw_test, walk_qst_files } from "../../../../../test/src/utils";
import { ArrayBufferCursor } from "../../block/cursor/ArrayBufferCursor";
import { BufferCursor } from "../../block/cursor/BufferCursor";
import { parse_qst_to_quest, write_quest_qst } from "./index";
@ -14,37 +14,41 @@ import {
import { get_object_position, get_object_section_id, get_object_type } from "./QuestObject";
import { get_npc_position, get_npc_section_id, get_npc_type } from "./QuestNpc";
import { unwrap } from "../../../Result";
import { Severity } from "../../../Severity";
test("parse Towards the Future", () => {
const buffer = readFileSync("test/resources/quest118_e.qst");
const cursor = new BufferCursor(buffer, Endianness.Little);
const { quest } = unwrap(parse_qst_to_quest(cursor));
test(
"parse Towards the Future",
pw_test({ max_log_severity: Severity.Warning }, () => {
const buffer = readFileSync("test/resources/quest118_e.qst");
const cursor = new BufferCursor(buffer, Endianness.Little);
const { quest } = unwrap(parse_qst_to_quest(cursor));
expect(quest.name).toBe("Towards the Future");
expect(quest.short_description).toBe("Challenge the\nnew simulator.");
expect(quest.long_description).toBe(
"Client: Principal\nQuest: Wishes to have\nhunters challenge the\nnew simulator\nReward: ??? Meseta",
);
expect(quest.episode).toBe(1);
expect(quest.objects.length).toBe(277);
expect(get_object_type(quest.objects[0])).toBe(ObjectType.MenuActivation);
expect(get_object_type(quest.objects[4])).toBe(ObjectType.PlayerSet);
expect(quest.npcs.length).toBe(216);
expect(quest.map_designations).toEqual(
new Map([
[0, 0],
[2, 0],
[11, 0],
[5, 4],
[12, 0],
[7, 4],
[13, 0],
[8, 4],
[10, 4],
[14, 0],
]),
);
});
expect(quest.name).toBe("Towards the Future");
expect(quest.short_description).toBe("Challenge the\nnew simulator.");
expect(quest.long_description).toBe(
"Client: Principal\nQuest: Wishes to have\nhunters challenge the\nnew simulator\nReward: ??? Meseta",
);
expect(quest.episode).toBe(1);
expect(quest.objects.length).toBe(277);
expect(get_object_type(quest.objects[0])).toBe(ObjectType.MenuActivation);
expect(get_object_type(quest.objects[4])).toBe(ObjectType.PlayerSet);
expect(quest.npcs.length).toBe(216);
expect(quest.map_designations).toEqual(
new Map([
[0, 0],
[2, 0],
[11, 0],
[5, 4],
[12, 0],
[7, 4],
[13, 0],
[8, 4],
[10, 4],
[14, 0],
]),
);
}),
);
/**
* Round-trip tests.
@ -73,76 +77,79 @@ round_trip_test(
);
function round_trip_test(path: string, file_name: string, contents: Buffer): void {
test(`parse_quest and write_quest_qst ${path}`, () => {
const { quest: orig_quest, version, online } = unwrap(
parse_qst_to_quest(new BufferCursor(contents, Endianness.Little)),
);
const test_qst = write_quest_qst(orig_quest, file_name, version, online);
const { quest: test_quest } = unwrap(
parse_qst_to_quest(new ArrayBufferCursor(test_qst, Endianness.Little)),
);
test(
`parse_quest and write_quest_qst ${path}`,
pw_test({ max_log_severity: Severity.Warning }, () => {
const { quest: orig_quest, version, online } = unwrap(
parse_qst_to_quest(new BufferCursor(contents, Endianness.Little)),
);
const test_qst = write_quest_qst(orig_quest, file_name, version, online);
const { quest: test_quest } = unwrap(
parse_qst_to_quest(new ArrayBufferCursor(test_qst, Endianness.Little)),
);
expect(test_quest.name).toBe(orig_quest.name);
expect(test_quest.short_description).toBe(orig_quest.short_description);
expect(test_quest.long_description).toBe(orig_quest.long_description);
expect(test_quest.episode).toBe(orig_quest.episode);
expect(test_quest.objects.length).toBe(orig_quest.objects.length);
expect(test_quest.name).toBe(orig_quest.name);
expect(test_quest.short_description).toBe(orig_quest.short_description);
expect(test_quest.long_description).toBe(orig_quest.long_description);
expect(test_quest.episode).toBe(orig_quest.episode);
expect(test_quest.objects.length).toBe(orig_quest.objects.length);
for (let i = 0; i < orig_quest.objects.length; i++) {
const orig_obj = orig_quest.objects[i];
const test_obj = test_quest.objects[i];
expect(test_obj.area_id).toBe(orig_obj.area_id);
expect(get_object_section_id(test_obj)).toBe(get_object_section_id(orig_obj));
expect(get_object_position(test_obj)).toEqual(get_object_position(orig_obj));
expect(get_object_type(test_obj)).toBe(get_object_type(orig_obj));
}
expect(test_quest.npcs.length).toBe(orig_quest.npcs.length);
for (let i = 0; i < orig_quest.npcs.length; i++) {
const orig_npc = orig_quest.npcs[i];
const test_npc = test_quest.npcs[i];
expect(test_npc.area_id).toBe(orig_npc.area_id);
expect(get_npc_section_id(test_npc)).toBe(get_npc_section_id(orig_npc));
expect(get_npc_position(test_npc)).toEqual(get_npc_position(orig_npc));
expect(get_npc_type(test_npc)).toBe(get_npc_type(orig_npc));
}
expect(test_quest.map_designations).toEqual(orig_quest.map_designations);
expect(test_quest.object_code.length).toBe(orig_quest.object_code.length);
for (let i = 0; i < orig_quest.object_code.length; i++) {
const orig_segment = orig_quest.object_code[i];
const test_segment = test_quest.object_code[i];
expect(test_segment.type).toBe(orig_segment.type);
expect(test_segment.labels).toEqual(orig_segment.labels);
switch (orig_segment.type) {
case SegmentType.Instructions:
expect((test_segment as InstructionSegment).instructions.length).toBe(
orig_segment.instructions.length,
);
for (let j = 0; j < orig_segment.instructions.length; j++) {
const orig_inst = orig_segment.instructions[j];
const test_inst = (test_segment as InstructionSegment).instructions[j];
expect(test_inst.opcode.code).toBe(orig_inst.opcode.code);
expect(test_inst.args).toEqual(orig_inst.args);
}
break;
case SegmentType.Data:
expect((test_segment as DataSegment).data).toEqual(orig_segment.data);
break;
case SegmentType.String:
expect((test_segment as StringSegment).value).toBe(orig_segment.value);
break;
for (let i = 0; i < orig_quest.objects.length; i++) {
const orig_obj = orig_quest.objects[i];
const test_obj = test_quest.objects[i];
expect(test_obj.area_id).toBe(orig_obj.area_id);
expect(get_object_section_id(test_obj)).toBe(get_object_section_id(orig_obj));
expect(get_object_position(test_obj)).toEqual(get_object_position(orig_obj));
expect(get_object_type(test_obj)).toBe(get_object_type(orig_obj));
}
expect(test_quest.object_code[i]).toEqual(orig_quest.object_code[i]);
}
});
expect(test_quest.npcs.length).toBe(orig_quest.npcs.length);
for (let i = 0; i < orig_quest.npcs.length; i++) {
const orig_npc = orig_quest.npcs[i];
const test_npc = test_quest.npcs[i];
expect(test_npc.area_id).toBe(orig_npc.area_id);
expect(get_npc_section_id(test_npc)).toBe(get_npc_section_id(orig_npc));
expect(get_npc_position(test_npc)).toEqual(get_npc_position(orig_npc));
expect(get_npc_type(test_npc)).toBe(get_npc_type(orig_npc));
}
expect(test_quest.map_designations).toEqual(orig_quest.map_designations);
expect(test_quest.object_code.length).toBe(orig_quest.object_code.length);
for (let i = 0; i < orig_quest.object_code.length; i++) {
const orig_segment = orig_quest.object_code[i];
const test_segment = test_quest.object_code[i];
expect(test_segment.type).toBe(orig_segment.type);
expect(test_segment.labels).toEqual(orig_segment.labels);
switch (orig_segment.type) {
case SegmentType.Instructions:
expect((test_segment as InstructionSegment).instructions.length).toBe(
orig_segment.instructions.length,
);
for (let j = 0; j < orig_segment.instructions.length; j++) {
const orig_inst = orig_segment.instructions[j];
const test_inst = (test_segment as InstructionSegment).instructions[j];
expect(test_inst.opcode.code).toBe(orig_inst.opcode.code);
expect(test_inst.args).toEqual(orig_inst.args);
}
break;
case SegmentType.Data:
expect((test_segment as DataSegment).data).toEqual(orig_segment.data);
break;
case SegmentType.String:
expect((test_segment as StringSegment).value).toBe(orig_segment.value);
break;
}
expect(test_quest.object_code[i]).toEqual(orig_quest.object_code[i]);
}
}),
);
}

View File

@ -11,7 +11,7 @@ import { DatEntity, parse_dat, write_dat } from "./dat";
import { Quest, QuestEntity } from "./Quest";
import { Episode } from "./Episode";
import { parse_qst, QstContainedFile, write_qst } from "./qst";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
import { parse_object_code, write_object_code } from "./object_code";
import { get_map_designations } from "../../asm/data_flow_analysis/get_map_designations";
import { basename } from "../../../util";

View File

@ -17,7 +17,7 @@ import { get_register_value } from "../../asm/data_flow_analysis/get_register_va
import { get_stack_value } from "../../asm/data_flow_analysis/get_stack_value";
import { ArrayBufferCursor } from "../../block/cursor/ArrayBufferCursor";
import { Endianness } from "../../block/Endianness";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
import { ResizableBlockCursor } from "../../block/cursor/ResizableBlockCursor";
import { ResizableBlock } from "../../block/ResizableBlock";
import { BinFormat } from "./BinFormat";

View File

@ -5,7 +5,7 @@ import { ResizableBlockCursor } from "../../block/cursor/ResizableBlockCursor";
import { WritableCursor } from "../../block/cursor/WritableCursor";
import { ResizableBlock } from "../../block/ResizableBlock";
import { assert, basename, defined } from "../../../util";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
import { Version } from "./Version";
import { Result, ResultBuilder } from "../../../Result";
import { Severity } from "../../../Severity";

View File

@ -1,7 +1,7 @@
import { Endianness } from "../block/Endianness";
import { Cursor } from "../block/cursor/Cursor";
import { parse_prc } from "./prc";
import { LogManager } from "../../Logger";
import { LogManager } from "../../logging";
import { Result, ResultBuilder } from "../../Result";
import { Severity } from "../../Severity";

View File

@ -4,7 +4,7 @@ import { ListProperty } from "../observable/property/list/ListProperty";
import { Disposer } from "../observable/Disposer";
import "./Table.css";
import { Disposable } from "../observable/Disposable";
import { LogManager } from "../Logger";
import { LogManager } from "../logging";
const logger = LogManager.get("core/gui/Table");

View File

@ -3,7 +3,7 @@ import { Disposer } from "../observable/Disposer";
import { WritableProperty } from "../observable/property/WritableProperty";
import { WidgetProperty } from "../observable/property/WidgetProperty";
import { Property } from "../observable/property/Property";
import { LogManager } from "../Logger";
import { LogManager } from "../logging";
const logger = LogManager.get("core/gui/Widget");

View File

@ -9,7 +9,7 @@ import {
ListProperty,
} from "../observable/property/list/ListProperty";
import { Disposer } from "../observable/Disposer";
import { LogManager } from "../Logger";
import { LogManager } from "../logging";
const logger = LogManager.get("core/gui/dom");

View File

@ -1,11 +1,11 @@
import { Severity, severity_from_string } from "./Severity";
import { basename } from "./util";
import { basename, try_finally } from "./util";
export type LogEntry = {
readonly time: Date;
readonly message: string;
readonly severity: Severity;
readonly logger: Logger;
readonly logger: Logging;
readonly cause?: any;
};
@ -35,6 +35,7 @@ function default_log_handler({ time, message, severity, logger, cause }: LogEntr
break;
default:
method = console.log;
break;
}
if (cause == undefined) {
@ -57,7 +58,7 @@ function time_part_to_string(value: number, n: number): string {
return value.toString().padStart(n, "0");
}
export class Logger {
export class Logging {
private _severity?: Severity;
get severity(): Severity {
@ -108,42 +109,42 @@ export class Logger {
}
export class LogManager {
private static readonly loggers = new Map<string, Logger>();
private static readonly loggers = new Map<string, Logging>();
static default_severity: Severity = severity_from_string(process.env["LOG_LEVEL"] ?? "Info");
static default_handler: LogHandler = default_log_handler;
static get(name: string): Logger {
static get(name: string): Logging {
name = basename(name);
let logger = this.loggers.get(name);
if (!logger) {
logger = new Logger(name);
logger = new Logging(name);
this.loggers.set(name, logger);
}
return logger;
}
static with_default_handler<T>(handler: LogHandler, f: () => T): T {
static with_defaults<T>(
defaults: { severity?: Severity; handler?: LogHandler },
f: () => T,
): T {
const orig_severity = this.default_severity;
const orig_handler = this.default_handler;
let is_promise = false;
try {
this.default_handler = handler;
const r = f();
if (r instanceof Promise) {
is_promise = true;
return (r.finally(() => (this.default_handler = orig_handler)) as unknown) as T;
} else {
return r;
}
} finally {
if (!is_promise) {
this.default_handler = orig_handler;
}
if (defaults.severity != undefined) {
this.default_severity = defaults.severity;
}
if (defaults.handler != undefined) {
this.default_handler = defaults.handler;
}
return try_finally(f, () => {
this.default_severity = orig_severity;
this.default_handler = orig_handler;
});
}
}

View File

@ -1,5 +1,5 @@
import { Disposable } from "./Disposable";
import { LogManager } from "../Logger";
import { LogManager } from "../logging";
import { array_remove } from "../util";
const logger = LogManager.get("core/observable/Disposer");

View File

@ -1,7 +1,7 @@
import { Disposable } from "./Disposable";
import { Emitter } from "./Emitter";
import { ChangeEvent } from "./Observable";
import { LogManager } from "../Logger";
import { LogManager } from "../logging";
const logger = LogManager.get("core/observable/SimpleEmitter");

View File

@ -1,6 +1,6 @@
import { Disposable } from "../Disposable";
import { Property } from "./Property";
import { LogManager } from "../../Logger";
import { LogManager } from "../../logging";
import { ChangeEvent } from "../Observable";
const logger = LogManager.get("core/observable/property/AbstractMinimalProperty");

View File

@ -1,26 +1,26 @@
import { FlatMappedProperty } from "./FlatMappedProperty";
import { SimpleProperty } from "./SimpleProperty";
import { with_disposable } from "../../../../test/src/core/observables/disposable_helpers";
import { pw_test } from "../../../../test/src/utils";
// This is a regression test, it's important that this exact sequence of statements stays the same.
test(`It should emit a change when its direct property dependency changes.`, () => {
// p is the direct property dependency.
const p = new SimpleProperty(new SimpleProperty(7));
const fp = new FlatMappedProperty([p], () => p.val);
let v: number | undefined;
test(
`It should emit a change when its direct property dependency changes.`,
pw_test({}, disposer => {
// p is the direct property dependency.
const p = new SimpleProperty(new SimpleProperty(7));
const fp = new FlatMappedProperty([p], () => p.val);
let v: number | undefined = undefined;
with_disposable(
fp.observe(({ value }) => (v = value)),
() => {
expect(v).toBeUndefined();
disposer.add(fp.observe(({ value }) => (v = value)));
p.val.val = 99;
expect(v).toBeUndefined();
expect(v).toBe(99);
p.val.val = 99;
p.val = new SimpleProperty(7);
expect(v).toBe(99);
expect(v).toBe(7);
},
);
});
p.val = new SimpleProperty(7);
expect(v).toBe(7);
}),
);

View File

@ -3,7 +3,7 @@ import { AbstractProperty } from "../AbstractProperty";
import { Disposable } from "../../Disposable";
import { Observable } from "../../Observable";
import { Property } from "../Property";
import { LogManager } from "../../../Logger";
import { LogManager } from "../../../logging";
const logger = LogManager.get("core/observable/property/list/AbstractListProperty");

View File

@ -1,5 +1,5 @@
import { Server } from "../model";
import { LogManager } from "../Logger";
import { LogManager } from "../logging";
const logger = LogManager.get("core/persistence/Persister");

View File

@ -4,6 +4,10 @@ import { Disposer } from "../observable/Disposer";
export abstract class Store implements Disposable {
private readonly disposer = new Disposer();
protected get disposed(): boolean {
return this.disposer.disposed;
}
dispose(): void {
this.disposer.dispose();
}

View File

@ -3,7 +3,7 @@ import { WritableListProperty } from "../observable/property/list/WritableListPr
import { Action } from "./Action";
import { list_property, map, property } from "../observable";
import { undo_manager } from "./UndoManager";
import { LogManager } from "../Logger";
import { LogManager } from "../logging";
const logger = LogManager.get("core/undo/UndoStack");

View File

@ -138,6 +138,40 @@ export function browser_supports_webassembly(): boolean {
return typeof window === "object" && typeof window.WebAssembly === "object";
}
export function is_promise(value: unknown): value is Promise<any> {
return value && typeof value === "object" && "then" in value && "finally" in value;
/**
* @returns true iff the given value implements PromiseLike.
*/
export function is_promise_like<T>(value?: T | PromiseLike<T>): value is PromiseLike<T> {
return value != undefined && typeof (value as any).then === "function";
}
/**
* @returns true iff the given value implements Promise.
*/
export function is_promise<T>(value?: T | Promise<T>): value is Promise<T> {
return (
value != undefined &&
typeof (value as any).then === "function" &&
typeof (value as any).catch === "function" &&
typeof (value as any).finally === "function"
);
}
export function try_finally<T>(f: () => T, after: () => void): T {
let return_promise = false;
try {
const r = f();
if (is_promise(r)) {
return_promise = true;
return (r.finally(() => after()) as unknown) as T;
} else {
return r;
}
} finally {
if (!return_promise) {
after();
}
}
}

View File

@ -13,7 +13,7 @@ import { SortDirection, Table } from "../../core/gui/Table";
import { list_property } from "../../core/observable";
import { ServerMap } from "../../core/stores/ServerMap";
import { HuntMethodStore } from "../stores/HuntMethodStore";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { div } from "../../core/gui/dom";
import { ResizableView } from "../../core/gui/ResizableView";

View File

@ -9,7 +9,7 @@ import "./OptimizationResultView.css";
import { Duration } from "luxon";
import { ServerMap } from "../../core/stores/ServerMap";
import { HuntOptimizerStore } from "../stores/HuntOptimizerStore";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { View } from "../../core/gui/View";
const logger = LogManager.get("hunt_optimizer/gui/OptimizationResultView");

View File

@ -10,7 +10,7 @@ import { ItemType } from "../../core/model/items";
import { Disposable } from "../../core/observable/Disposable";
import { ServerMap } from "../../core/stores/ServerMap";
import { HuntOptimizerStore } from "../stores/HuntOptimizerStore";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { View } from "../../core/gui/View";
const logger = LogManager.get("hunt_optimizer/gui/WantedItemsView");

View File

@ -11,7 +11,7 @@ import { GuiStore } from "../../core/stores/GuiStore";
import { HttpClient } from "../../core/HttpClient";
import { Store } from "../../core/stores/Store";
import { DisposableServerMap } from "../../core/stores/DisposableServerMap";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
const logger = LogManager.get("hunt_optimizer/stores/HuntMethodStore");

View File

@ -8,7 +8,7 @@ import { ItemTypeStore } from "../../core/stores/ItemTypeStore";
import { HttpClient } from "../../core/HttpClient";
import { DisposableServerMap } from "../../core/stores/DisposableServerMap";
import { Store } from "../../core/stores/Store";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
const logger = LogManager.get("stores/ItemDropStore");

View File

@ -14,7 +14,7 @@ import { QuestObjectModel } from "./model/QuestObjectModel";
import { AreaStore } from "./stores/AreaStore";
import { InstructionPointer } from "./scripting/vm/InstructionPointer";
import { clone_segment } from "../core/data_formats/asm/instructions";
import { Logger } from "../core/Logger";
import { Logging } from "../core/logging";
import { LogStore } from "./stores/LogStore";
import { Severity } from "../core/Severity";
@ -56,7 +56,7 @@ export type GameState = Readonly<GameStateInternal>;
* delegates to {@link Debugger}.
*/
export class QuestRunner {
private logger: Logger;
private logger: Logging;
private animation_frame?: number;
private startup = true;
private readonly _state: WritableProperty<QuestRunnerState> = property(

View File

@ -1,15 +1,15 @@
import { with_disposer } from "../../../test/src/core/observables/disposable_helpers";
import { GuiStore } from "../../core/stores/GuiStore";
import { create_area_store } from "../../../test/src/quest_editor/stores/store_creation";
import { QuestEditorStore } from "../stores/QuestEditorStore";
import { DebugController } from "./DebugController";
import { LogStore } from "../stores/LogStore";
import { load_default_quest_model, next_animation_frame } from "../../../test/src/utils";
import { load_default_quest_model, next_animation_frame, pw_test } from "../../../test/src/utils";
import { disassemble } from "../scripting/disassembly";
import { assemble } from "../scripting/assembly";
test("Some widgets should only be enabled when a quest is loaded.", async () =>
with_disposer(async disposer => {
test(
"Some widgets should only be enabled when a quest is loaded.",
pw_test({}, async disposer => {
const gui_store = disposer.add(new GuiStore());
const area_store = create_area_store(disposer);
const log_store = disposer.add(new LogStore());
@ -25,10 +25,12 @@ test("Some widgets should only be enabled when a quest is loaded.", async () =>
expect(ctrl.can_debug.val).toBe(true);
expect(ctrl.can_step.val).toBe(false);
}));
}),
);
test("Debugging controls should be enabled and disabled at the right times.", async () =>
with_disposer(async disposer => {
test(
"Debugging controls should be enabled and disabled at the right times.",
pw_test({}, async disposer => {
const gui_store = disposer.add(new GuiStore());
const area_store = create_area_store(disposer);
const log_store = disposer.add(new LogStore());
@ -74,4 +76,5 @@ test("Debugging controls should be enabled and disabled at the right times.", as
expect(ctrl.can_step.val).toBe(true);
expect(ctrl.can_stop.val).toBe(true);
}));
}),
);

View File

@ -3,7 +3,7 @@ import { Property } from "../../core/observable/property/Property";
import { ListProperty } from "../../core/observable/property/list/ListProperty";
import { QuestEditorStore } from "../stores/QuestEditorStore";
import { GuiStore, GuiTool } from "../../core/stores/GuiStore";
import { LogEntry } from "../../core/Logger";
import { LogEntry } from "../../core/logging";
import { LogStore } from "../stores/LogStore";
import { Severity } from "../../core/Severity";
import { map } from "../../core/observable";

View File

@ -3,14 +3,14 @@ import {
create_area_store,
create_quest_editor_store,
} from "../../../test/src/quest_editor/stores/store_creation";
import { with_disposer } from "../../../test/src/core/observables/disposable_helpers";
import { Vector3 } from "three";
import { euler } from "../model/euler";
import { deg_to_rad } from "../../core/math";
import { load_default_quest_model } from "../../../test/src/utils";
import { load_default_quest_model, pw_test } from "../../../test/src/utils";
test("When input values change, this should be reflected in the selected entity.", () =>
with_disposer(disposer => {
test(
"When input values change, this should be reflected in the selected entity.",
pw_test({}, disposer => {
const area_store = create_area_store(disposer);
const store = create_quest_editor_store(disposer, area_store);
const ctrl = new EntityInfoController(store);
@ -45,4 +45,5 @@ test("When input values change, this should be reflected in the selected entity.
expect(entity.rotation.val.x).toBeCloseTo(deg_to_rad(180), 5);
expect(entity.rotation.val.y).toBeCloseTo(deg_to_rad(45), 5);
expect(entity.rotation.val.z).toBeCloseTo(deg_to_rad(223.83), 5);
}));
}),
);

View File

@ -5,12 +5,13 @@ import {
} from "../../../test/src/quest_editor/stores/store_creation";
import { QuestEditorToolBarController } from "./QuestEditorToolBarController";
import { Episode } from "../../core/data_formats/parsing/quest/Episode";
import { with_disposer } from "../../../test/src/core/observables/disposable_helpers";
import { QuestLoader } from "../loading/QuestLoader";
import { FileSystemHttpClient } from "../../../test/src/core/FileSystemHttpClient";
import { pw_test } from "../../../test/src/utils";
test("Some widgets should only be enabled when a quest is loaded.", async () =>
with_disposer(async disposer => {
test(
"Some widgets should only be enabled when a quest is loaded.",
pw_test({}, async disposer => {
const quest_loader = disposer.add(new QuestLoader(new FileSystemHttpClient()));
const gui_store = disposer.add(new GuiStore());
const area_store = create_area_store(disposer);
@ -31,4 +32,5 @@ test("Some widgets should only be enabled when a quest is loaded.", async () =>
expect(ctrl.can_save.val).toBe(true);
expect(ctrl.can_select_area.val).toBe(true);
}));
}),
);

View File

@ -16,7 +16,7 @@ import {
import { ArrayBufferCursor } from "../../core/data_formats/block/cursor/ArrayBufferCursor";
import { Endianness } from "../../core/data_formats/block/Endianness";
import { convert_quest_from_model, convert_quest_to_model } from "../stores/model_conversion";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { basename } from "../../core/util";
import { Version } from "../../core/data_formats/parsing/quest/Version";
import { WritableProperty } from "../../core/observable/property/WritableProperty";

View File

@ -3,11 +3,11 @@ import {
create_quest_editor_store,
} from "../../../test/src/quest_editor/stores/store_creation";
import { QuestInfoController } from "./QuestInfoController";
import { with_disposer } from "../../../test/src/core/observables/disposable_helpers";
import { load_default_quest_model } from "../../../test/src/utils";
import { load_default_quest_model, pw_test } from "../../../test/src/utils";
test("When a property's input value changes, this should be reflected in the current quest object and the undo stack.", async () =>
with_disposer(async disposer => {
test(
"When a property's input value changes, this should be reflected in the current quest object and the undo stack.",
pw_test({}, async disposer => {
const area_store = create_area_store(disposer);
const store = create_quest_editor_store(disposer, area_store);
const ctrl = disposer.add(new QuestInfoController(store));
@ -39,4 +39,5 @@ test("When a property's input value changes, this should be reflected in the cur
expect(store.current_quest.val!.long_description.val).toBe(
"Created with phantasmal.world.",
);
}));
}),
);

View File

@ -2,7 +2,7 @@ import { bind_children_to, div, Icon } from "../../core/gui/dom";
import { ToolBar } from "../../core/gui/ToolBar";
import "./DebugView.css";
import { Select } from "../../core/gui/Select";
import { LogEntry, time_to_string } from "../../core/Logger";
import { LogEntry, time_to_string } from "../../core/logging";
import { ResizableView } from "../../core/gui/ResizableView";
import { Severities, Severity } from "../../core/Severity";
import { Button } from "../../core/gui/Button";

View File

@ -4,12 +4,12 @@ import {
create_area_store,
create_quest_editor_store,
} from "../../../test/src/quest_editor/stores/store_creation";
import { with_disposer } from "../../../test/src/core/observables/disposable_helpers";
import { undo_manager } from "../../core/undo/UndoManager";
import { load_default_quest_model } from "../../../test/src/utils";
import { load_default_quest_model, pw_test } from "../../../test/src/utils";
test("Renders correctly without an entity selected.", () => {
with_disposer(disposer => {
test(
"Renders correctly without an entity selected.",
pw_test({}, disposer => {
const area_store = create_area_store(disposer);
const store = create_quest_editor_store(disposer, area_store);
const view = disposer.add(
@ -21,11 +21,12 @@ test("Renders correctly without an entity selected.", () => {
store.set_current_quest(load_default_quest_model(area_store));
expect(view.element).toMatchSnapshot('should render a "No entity selected." view');
});
});
}),
);
test("Renders correctly with an entity selected.", () => {
with_disposer(disposer => {
test(
"Renders correctly with an entity selected.",
pw_test({}, disposer => {
const area_store = create_area_store(disposer);
const store = create_quest_editor_store(disposer, area_store);
const view = disposer.add(
@ -37,11 +38,12 @@ test("Renders correctly with an entity selected.", () => {
store.set_selected_entity(quest.npcs.get(0));
expect(view.element).toMatchSnapshot("should render a table of editable properties");
});
});
}),
);
test("When the view's element is focused the quest editor store's undo stack should become the current stack.", () => {
with_disposer(disposer => {
test(
"When the view's element is focused the quest editor store's undo stack should become the current stack.",
pw_test({}, disposer => {
const store = create_quest_editor_store(disposer);
const view = disposer.add(
new EntityInfoView(disposer.add(new EntityInfoController(store))),
@ -55,5 +57,5 @@ test("When the view's element is focused the quest editor store's undo stack sho
view.element.focus();
expect(undo_manager.current.val).toBe(store.undo);
});
});
}),
);

View File

@ -12,7 +12,7 @@ import {
ListProperty,
} from "../../core/observable/property/list/ListProperty";
import { WritableProperty } from "../../core/observable/property/WritableProperty";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { View } from "../../core/gui/View";
const logger = LogManager.get("quest_editor/gui/EventSubGraphView");

View File

@ -4,19 +4,21 @@ import {
create_area_store,
create_quest_editor_store,
} from "../../../test/src/quest_editor/stores/store_creation";
import { with_disposer } from "../../../test/src/core/observables/disposable_helpers";
import { load_default_quest_model } from "../../../test/src/utils";
import { load_default_quest_model, pw_test } from "../../../test/src/utils";
test("Renders correctly without a current quest.", () =>
with_disposer(disposer => {
test(
"Renders correctly without a current quest.",
pw_test({}, disposer => {
const store = create_quest_editor_store(disposer);
const view = disposer.add(new NpcCountsView(disposer.add(new NpcCountsController(store))));
expect(view.element).toMatchSnapshot('Should render a "No quest loaded." view.');
}));
}),
);
test("Renders correctly with a current quest.", () =>
with_disposer(disposer => {
test(
"Renders correctly with a current quest.",
pw_test({}, disposer => {
const area_store = create_area_store(disposer);
const store = create_quest_editor_store(disposer, area_store);
const view = disposer.add(new NpcCountsView(disposer.add(new NpcCountsController(store))));
@ -24,4 +26,5 @@ test("Renders correctly with a current quest.", () =>
store.set_current_quest(load_default_quest_model(area_store));
expect(view.element).toMatchSnapshot("Should render a table with NPC names and counts.");
}));
}),
);

View File

@ -3,13 +3,14 @@ import { QuestEditorToolBarView } from "./QuestEditorToolBarView";
import { GuiStore } from "../../core/stores/GuiStore";
import { create_area_store } from "../../../test/src/quest_editor/stores/store_creation";
import { QuestEditorStore } from "../stores/QuestEditorStore";
import { with_disposer } from "../../../test/src/core/observables/disposable_helpers";
import { LogStore } from "../stores/LogStore";
import { QuestLoader } from "../loading/QuestLoader";
import { StubHttpClient } from "../../core/HttpClient";
import { pw_test } from "../../../test/src/utils";
test("Renders correctly.", () =>
with_disposer(disposer => {
test(
"Renders correctly.",
pw_test({}, disposer => {
const quest_loader = disposer.add(new QuestLoader(new StubHttpClient()));
const gui_store = disposer.add(new GuiStore());
const area_store = create_area_store(disposer);
@ -31,4 +32,5 @@ test("Renders correctly.", () =>
);
expect(tool_bar.element).toMatchSnapshot();
}));
}),
);

View File

@ -16,7 +16,7 @@ import { DebugView } from "./DebugView";
import { QuestRunnerRendererView } from "./QuestRunnerRendererView";
import { QuestEditorStore } from "../stores/QuestEditorStore";
import { QuestEditorUiPersister } from "../persistence/QuestEditorUiPersister";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { ErrorWidget } from "../../core/gui/ErrorWidget";
import { div } from "../../core/gui/dom";
import { ResizableView } from "../../core/gui/ResizableView";

View File

@ -5,11 +5,11 @@ import {
create_area_store,
create_quest_editor_store,
} from "../../../test/src/quest_editor/stores/store_creation";
import { with_disposer } from "../../../test/src/core/observables/disposable_helpers";
import { load_default_quest_model } from "../../../test/src/utils";
import { load_default_quest_model, pw_test } from "../../../test/src/utils";
test("Renders correctly without a current quest.", () =>
with_disposer(disposer => {
test(
"Renders correctly without a current quest.",
pw_test({}, disposer => {
const view = disposer.add(
new QuestInfoView(
disposer.add(new QuestInfoController(create_quest_editor_store(disposer))),
@ -17,10 +17,12 @@ test("Renders correctly without a current quest.", () =>
);
expect(view.element).toMatchSnapshot('should render a "No quest loaded." view');
}));
}),
);
test("Renders correctly with a current quest.", () =>
with_disposer(async disposer => {
test(
"Renders correctly with a current quest.",
pw_test({}, async disposer => {
const area_store = create_area_store(disposer);
const store = create_quest_editor_store(disposer);
const view = disposer.add(new QuestInfoView(disposer.add(new QuestInfoController(store))));
@ -28,10 +30,12 @@ test("Renders correctly with a current quest.", () =>
await store.set_current_quest(load_default_quest_model(area_store));
expect(view.element).toMatchSnapshot("should render property inputs");
}));
}),
);
test("When the view's element is focused the quest editor store's undo stack should become the current stack.", () =>
with_disposer(disposer => {
test(
"When the view's element is focused the quest editor store's undo stack should become the current stack.",
pw_test({}, disposer => {
const store = create_quest_editor_store(disposer);
const view = disposer.add(new QuestInfoView(disposer.add(new QuestInfoController(store))));
@ -43,4 +47,5 @@ test("When the view's element is focused the quest editor store's undo stack sho
view.element.focus();
expect(undo_manager.current.val).toBe(store.undo);
}));
}),
);

View File

@ -1,114 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Renders correctly with a current quest.: should render property inputs 1`] = `
<div
class="quest_editor_QuestInfoView"
tabindex="-1"
>
<table>
<tr>
<th>
Episode:
</th>
<td>
I
</td>
</tr>
<tr>
<th>
ID:
</th>
<td>
<span
class="core_NumberInput core_Input"
>
<input
class="core_NumberInput_inner core_Input_inner"
min="0"
step="1"
type="number"
/>
</span>
</td>
</tr>
<tr>
<th>
Name:
</th>
<td>
<span
class="core_TextInput core_Input"
>
<input
class="core_TextInput_inner core_Input_inner"
maxlength="32"
type="text"
/>
</span>
</td>
</tr>
<tr>
<th
colspan="2"
>
Short description:
</th>
</tr>
<tr>
<td
colspan="2"
>
<div
class="core_TextArea"
>
<textarea
class="core_TextArea_inner"
cols="25"
maxlength="128"
rows="5"
style="font-family: \\"Courier New\\", monospace;"
/>
</div>
</td>
</tr>
<tr>
<th
colspan="2"
>
Long description:
</th>
</tr>
<tr>
<td
colspan="2"
>
<div
class="core_TextArea"
>
<textarea
class="core_TextArea_inner"
cols="25"
maxlength="288"
rows="10"
style="font-family: \\"Courier New\\", monospace;"
/>
</div>
</td>
</tr>
</table>
<div
class="quest_editor_UnavailableView"
hidden=""
>
<label
class="core_Label disabled"
>
No quest loaded.
</label>
</div>
</div>
`;
exports[`Renders correctly without a current quest.: should render a "No quest loaded." view 1`] = `
<div
class="quest_editor_QuestInfoView"

View File

@ -15,7 +15,7 @@ import {
} from "../../core/data_formats/parsing/quest/Quest";
import { HttpClient } from "../../core/HttpClient";
import { Disposable } from "../../core/observable/Disposable";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { DisposablePromise } from "../../core/DisposablePromise";
import { Disposer } from "../../core/observable/Disposer";

View File

@ -14,7 +14,7 @@ import { entity_type_to_string } from "../../core/data_formats/parsing/quest/Que
import { QuestEventDagModel } from "./QuestEventDagModel";
import { assert, defined, require_array } from "../../core/util";
import { AreaStore } from "../stores/AreaStore";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { QuestEventModel } from "./QuestEventModel";
const logger = LogManager.get("quest_editor/model/QuestModel");

View File

@ -17,7 +17,7 @@ import { Episode } from "../../core/data_formats/parsing/quest/Episode";
import { AreaVariantModel } from "../model/AreaVariantModel";
import { EntityAssetLoader } from "../loading/EntityAssetLoader";
import { AreaAssetLoader } from "../loading/AreaAssetLoader";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { Property } from "../../core/observable/property/Property";
import { WaveModel } from "../model/WaveModel";
import { map } from "../../core/observable";

View File

@ -33,7 +33,7 @@ import {
Param,
StackInteraction,
} from "../../core/data_formats/asm/opcodes";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
const logger = LogManager.get("quest_editor/scripting/assembly");

View File

@ -8,7 +8,7 @@ import {
Param,
StackInteraction,
} from "../../core/data_formats/asm/opcodes";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { number_to_hex_string } from "../../core/util";
const logger = LogManager.get("quest_editor/scripting/disassembly");

View File

@ -104,7 +104,7 @@ import { Endianness } from "../../../core/data_formats/block/Endianness";
import { Random } from "./Random";
import { InstructionPointer } from "./InstructionPointer";
import { StepMode, Thread } from "./Thread";
import { LogManager } from "../../../core/Logger";
import { LogManager } from "../../../core/logging";
import { ArrayBufferBlock } from "../../../core/data_formats/block/ArrayBufferBlock";
export const REGISTER_COUNT = 256;

View File

@ -1,6 +1,6 @@
import { AsmToken } from "../../../core/data_formats/asm/instructions";
import { InstructionPointer } from "./InstructionPointer";
import { LogManager } from "../../../core/Logger";
import { LogManager } from "../../../core/logging";
const logger = LogManager.get("quest_editor/scripting/vm/io");

View File

@ -1,7 +1,7 @@
import { ListProperty } from "../../core/observable/property/list/ListProperty";
import { Property } from "../../core/observable/property/Property";
import { list_property, property } from "../../core/observable";
import { LogEntry, Logger, LogHandler, LogManager } from "../../core/Logger";
import { LogEntry, Logging, LogHandler, LogManager } from "../../core/logging";
import { Severity } from "../../core/Severity";
import { Store } from "../../core/stores/Store";
@ -23,7 +23,7 @@ export class LogStore extends Store {
this.severity.map(severity => message => message.severity >= severity),
);
get_logger(name: string): Logger {
get_logger(name: string): Logging {
const logger = LogManager.get(name);
logger.handler = this.handler;
return logger;
@ -46,6 +46,10 @@ export class LogStore extends Store {
if (this.adding_log_entries != undefined) return;
this.adding_log_entries = requestAnimationFrame(() => {
if (this.disposed) {
return;
}
const DROP_THRESHOLD = 500;
const DROP_THRESHOLD_HALF = DROP_THRESHOLD / 2;
const BATCH_SIZE = 200;

View File

@ -13,7 +13,7 @@ import { QuestRunner } from "../QuestRunner";
import { AreaStore } from "./AreaStore";
import { disposable_listener } from "../../core/gui/dom";
import { Store } from "../../core/stores/Store";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { WaveModel } from "../model/WaveModel";
import { LogStore } from "./LogStore";

View File

@ -17,7 +17,7 @@ import { QuestEventDagModel } from "../model/QuestEventDagModel";
import { Quest, QuestEvent } from "../../core/data_formats/parsing/quest/Quest";
import { clone_segment } from "../../core/data_formats/asm/instructions";
import { AreaStore } from "./AreaStore";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { WaveModel } from "../model/WaveModel";
import {
get_npc_section_id,

View File

@ -6,7 +6,7 @@ import { ArrayBufferCursor } from "../../../core/data_formats/block/cursor/Array
import { Endianness } from "../../../core/data_formats/block/Endianness";
import { parse_nj, parse_xj } from "../../../core/data_formats/parsing/ninja";
import { parse_njm } from "../../../core/data_formats/parsing/ninja/motion";
import { LogManager } from "../../../core/Logger";
import { LogManager } from "../../../core/logging";
import { failure, problem, Result, success } from "../../../core/Result";
import { Severity } from "../../../core/Severity";
import { property } from "../../../core/observable";

View File

@ -4,7 +4,7 @@ import { read_file } from "../../../core/files";
import { XvrTexture } from "../../../core/data_formats/parsing/ninja/texture";
import { ArrayBufferCursor } from "../../../core/data_formats/block/cursor/ArrayBufferCursor";
import { Endianness } from "../../../core/data_formats/block/Endianness";
import { LogManager } from "../../../core/Logger";
import { LogManager } from "../../../core/logging";
import { WritableListProperty } from "../../../core/observable/property/list/WritableListProperty";
import { list_property, property } from "../../../core/observable";
import { ListProperty } from "../../../core/observable/property/list/ListProperty";

View File

@ -1,4 +1,3 @@
import { with_disposer } from "../../../../test/src/core/observables/disposable_helpers";
import { ModelController } from "../../controllers/model/ModelController";
import { CharacterClassAssetLoader } from "../../loading/CharacterClassAssetLoader";
import { FileSystemHttpClient } from "../../../../test/src/core/FileSystemHttpClient";
@ -12,9 +11,11 @@ import { ModelToolBarController } from "../../controllers/model/ModelToolBarCont
import { CharacterClassOptionsView } from "./CharacterClassOptionsView";
import { CharacterClassOptionsController } from "../../controllers/model/CharacterClassOptionsController";
import { GuiStore } from "../../../core/stores/GuiStore";
import { pw_test } from "../../../../test/src/utils";
test("Renders correctly.", () =>
with_disposer(disposer => {
test(
"Renders correctly.",
pw_test({}, disposer => {
const store = disposer.add(
new ModelStore(
disposer.add(new GuiStore()),
@ -30,4 +31,5 @@ test("Renders correctly.", () =>
);
expect(view.element).toMatchSnapshot();
}));
}),
);

View File

@ -1,13 +1,15 @@
import { TextureView } from "./TextureView";
import { with_disposer } from "../../../../test/src/core/observables/disposable_helpers";
import { TextureController } from "../../controllers/texture/TextureController";
import { TextureRenderer } from "../../rendering/TextureRenderer";
import { STUB_RENDERER } from "../../../../test/src/core/rendering/StubRenderer";
import { pw_test } from "../../../../test/src/utils";
test("Renders correctly without textures.", () =>
with_disposer(disposer => {
test(
"Renders correctly without textures.",
pw_test({}, disposer => {
const ctrl = disposer.add(new TextureController());
const view = disposer.add(new TextureView(ctrl, new TextureRenderer(ctrl, STUB_RENDERER)));
expect(view.element).toMatchSnapshot("Should render a toolbar and a renderer widget.");
}));
}),
);

View File

@ -22,7 +22,7 @@ import {
import { DisposableThreeRenderer, Renderer } from "../../core/rendering/Renderer";
import { Disposer } from "../../core/observable/Disposer";
import { ChangeEvent } from "../../core/observable/Observable";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { ModelStore } from "../stores/ModelStore";
import { CharacterClassModel } from "../model/CharacterClassModel";

View File

@ -1,5 +1,5 @@
import { Disposer } from "../../core/observable/Disposer";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { TextureController } from "../controllers/texture/TextureController";
import { XvrTexture } from "../../core/data_formats/parsing/ninja/texture";
import { xvr_texture_to_three_texture } from "../../core/rendering/conversion/ninja_textures";

View File

@ -25,7 +25,7 @@ import { ListProperty } from "../../core/observable/property/list/ListProperty";
import { CharacterClassAssetLoader } from "../loading/CharacterClassAssetLoader";
import { Random } from "../../core/Random";
import { SectionId, SectionIds } from "../../core/model";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
import { NjObject } from "../../core/data_formats/parsing/ninja";
import { GuiStore, GuiTool } from "../../core/stores/GuiStore";
import { string_to_enum } from "../../core/enums";

View File

@ -6,7 +6,7 @@ import { Severity } from "../../core/Severity";
import { ArrayBufferCursor } from "../../core/data_formats/block/cursor/ArrayBufferCursor";
import { Endianness } from "../../core/data_formats/block/Endianness";
import { prs_decompress } from "../../core/data_formats/compression/prs/decompress";
import { LogManager } from "../../core/Logger";
import { LogManager } from "../../core/logging";
const logger = LogManager.get("viewer/util/texture_parsing");

View File

@ -1,29 +0,0 @@
import { Disposable } from "../../../../src/core/observable/Disposable";
import { Disposer } from "../../../../src/core/observable/Disposer";
import { is_promise } from "../../../../src/core/util";
export function with_disposable<D extends Disposable, T>(
disposable: D,
f: (disposable: D) => T,
): T {
let return_promise = false;
try {
const value = f(disposable);
if (is_promise(value)) {
return_promise = true;
return (value.finally(() => disposable.dispose()) as unknown) as T;
} else {
return value;
}
} finally {
if (!return_promise) {
disposable.dispose();
}
}
}
export function with_disposer<T>(f: (disposer: Disposer) => T): T {
return with_disposable(new Disposer(), f);
}

View File

@ -9,6 +9,39 @@ import { QuestModel } from "../../src/quest_editor/model/QuestModel";
import { AreaStore } from "../../src/quest_editor/stores/AreaStore";
import { convert_quest_to_model } from "../../src/quest_editor/stores/model_conversion";
import { unwrap } from "../../src/core/Result";
import { LogManager } from "../../src/core/logging";
import { Severity } from "../../src/core/Severity";
import { Disposer } from "../../src/core/observable/Disposer";
import { try_finally } from "../../src/core/util";
export function pw_test(
{ max_log_severity = Severity.Info }: { max_log_severity?: Severity },
f: (disposer: Disposer) => void,
): () => void {
return () => {
const disposer = new Disposer();
const log: string[] = [];
const orig_severity = LogManager.default_severity;
const orig_handler = LogManager.default_handler;
LogManager.default_severity = max_log_severity + 1;
LogManager.default_handler = entry => log.push(entry.message);
try_finally(
() => f(disposer),
() => {
disposer.dispose();
// Reset LogManager after disposing the disposer, because disposing it could trigger
// logging.
LogManager.default_severity = orig_severity;
LogManager.default_handler = orig_handler;
expect(log).toEqual([]);
},
);
};
}
export async function timeout(millis: number): Promise<void> {
return new Promise(resolve => {