Improved application initialization test. Improved error handling in hunt optimizer.

This commit is contained in:
Daan Vanden Bosch 2019-12-22 02:37:06 +01:00
parent 2083793e67
commit d168030550
13 changed files with 111 additions and 83 deletions

View File

@ -5,5 +5,6 @@ module.exports = {
setupFiles: ["./test/src/setup.js"], setupFiles: ["./test/src/setup.js"],
moduleNameMapper: { moduleNameMapper: {
"\\.(css|gif|jpg|png|svg|ttf)$": "<rootDir>/src/__mocks__/static_files.js", "\\.(css|gif|jpg|png|svg|ttf)$": "<rootDir>/src/__mocks__/static_files.js",
"monaco-editor": "<rootDir>/node_modules/monaco-editor/dev/vs/editor/editor.main.js",
}, },
}; };

View File

@ -7,6 +7,9 @@ const logger = Logger.get("core/observable/Disposer");
* Container for disposables. * Container for disposables.
*/ */
export class Disposer implements Disposable { export class Disposer implements Disposable {
private _disposed = false;
private readonly disposables: Disposable[];
/** /**
* The amount of disposables contained in this disposer. * The amount of disposables contained in this disposer.
*/ */
@ -18,9 +21,6 @@ export class Disposer implements Disposable {
return this._disposed; return this._disposed;
} }
private _disposed = false;
private readonly disposables: Disposable[];
constructor(...disposables: Disposable[]) { constructor(...disposables: Disposable[]) {
this.disposables = disposables; this.disposables = disposables;
} }

View File

@ -12,7 +12,7 @@ import { ItemTypeDto } from "../dto/ItemTypeDto";
import { GuiStore } from "./GuiStore"; import { GuiStore } from "./GuiStore";
import { HttpClient } from "../HttpClient"; import { HttpClient } from "../HttpClient";
export function load_item_type_stores( export function create_item_type_stores(
http_client: HttpClient, http_client: HttpClient,
gui_store: GuiStore, gui_store: GuiStore,
): ServerMap<ItemTypeStore> { ): ServerMap<ItemTypeStore> {

View File

@ -1,8 +1,6 @@
import { Server } from "../model"; import { Server } from "../model";
import { Property } from "../observable/property/Property"; import { Property } from "../observable/property/Property";
import { memoize } from "lodash"; import { memoize } from "lodash";
import { sequential } from "../sequential";
import { Disposable } from "../observable/Disposable";
import { GuiStore } from "./GuiStore"; import { GuiStore } from "./GuiStore";
/** /**
@ -30,9 +28,4 @@ export class ServerMap<T> {
get(server: Server): Promise<T> { get(server: Server): Promise<T> {
return this.get_value(server); return this.get_value(server);
} }
observe_current(f: (current: T) => void, options?: { call_now?: boolean }): Disposable {
const seq_f = sequential(async ({ value }: { value: Promise<T> }) => f(await value));
return this.current.observe(seq_f, options);
}
} }

View File

@ -15,6 +15,9 @@ import { SortDirection, Table } from "../../core/gui/Table";
import { list_property } from "../../core/observable"; import { list_property } from "../../core/observable";
import { ServerMap } from "../../core/stores/ServerMap"; import { ServerMap } from "../../core/stores/ServerMap";
import { HuntMethodStore } from "../stores/HuntMethodStore"; import { HuntMethodStore } from "../stores/HuntMethodStore";
import Logger from "js-logger";
const logger = Logger.get("hunt_optimizer/gui/MethodsForEpisodeView");
export class MethodsForEpisodeView extends ResizableWidget { export class MethodsForEpisodeView extends ResizableWidget {
readonly element = el.div({ class: "hunt_optimizer_MethodsForEpisodeView" }); readonly element = el.div({ class: "hunt_optimizer_MethodsForEpisodeView" });
@ -121,22 +124,28 @@ export class MethodsForEpisodeView extends ResizableWidget {
this.element.append(table.element); this.element.append(table.element);
this.disposable( this.disposable(
hunt_method_stores.observe_current( hunt_method_stores.current.observe(
hunt_method_store => { async ({ value }) => {
if (this.hunt_methods_observer) { try {
this.hunt_methods_observer.dispose(); const hunt_method_store = await value;
}
this.hunt_methods_observer = hunt_method_store.methods.observe( if (this.hunt_methods_observer) {
({ value }) => { this.hunt_methods_observer.dispose();
hunt_methods.val = value.filter( }
method => method.episode === this.episode,
); this.hunt_methods_observer = hunt_method_store.methods.observe(
}, ({ value }) => {
{ hunt_methods.val = value.filter(
call_now: true, method => method.episode === this.episode,
}, );
); },
{
call_now: true,
},
);
} catch (e) {
logger.error("Couldn't load hunt optimizer store.", e);
}
}, },
{ call_now: true }, { call_now: true },
), ),

View File

@ -10,6 +10,9 @@ import "./OptimizationResultView.css";
import { Duration } from "luxon"; import { Duration } from "luxon";
import { ServerMap } from "../../core/stores/ServerMap"; import { ServerMap } from "../../core/stores/ServerMap";
import { HuntOptimizerStore } from "../stores/HuntOptimizerStore"; import { HuntOptimizerStore } from "../stores/HuntOptimizerStore";
import Logger from "js-logger";
const logger = Logger.get("hunt_optimizer/gui/OptimizationResultView");
export class OptimizationResultView extends Widget { export class OptimizationResultView extends Widget {
readonly element = el.div( readonly element = el.div(
@ -24,18 +27,24 @@ export class OptimizationResultView extends Widget {
super(); super();
this.disposable( this.disposable(
hunt_optimizer_stores.observe_current( hunt_optimizer_stores.current.observe(
hunt_optimizer_store => { async ({ value }) => {
if (this.results_observer) { try {
this.results_observer.dispose(); const hunt_optimizer_store = await value;
}
this.results_observer = hunt_optimizer_store.result.observe( if (this.results_observer) {
({ value }) => this.update_table(value), this.results_observer.dispose();
{ }
call_now: true,
}, this.results_observer = hunt_optimizer_store.result.observe(
); ({ value }) => this.update_table(value),
{
call_now: true,
},
);
} catch (e) {
logger.error("Couldn't load hunt optimizer store.", e);
}
}, },
{ call_now: true }, { call_now: true },
), ),

View File

@ -11,6 +11,9 @@ import { ItemType } from "../../core/model/items";
import { Disposable } from "../../core/observable/Disposable"; import { Disposable } from "../../core/observable/Disposable";
import { ServerMap } from "../../core/stores/ServerMap"; import { ServerMap } from "../../core/stores/ServerMap";
import { HuntOptimizerStore } from "../stores/HuntOptimizerStore"; import { HuntOptimizerStore } from "../stores/HuntOptimizerStore";
import Logger from "js-logger";
const logger = Logger.get("hunt_optimizer/gui/WantedItemsView");
export class WantedItemsView extends Widget { export class WantedItemsView extends Widget {
readonly element = el.div({ class: "hunt_optimizer_WantedItemsView" }); readonly element = el.div({ class: "hunt_optimizer_WantedItemsView" });
@ -48,29 +51,34 @@ export class WantedItemsView extends Widget {
); );
this.disposables( this.disposables(
hunt_optimizer_stores.observe_current( hunt_optimizer_stores.current.observe(
hunt_optimizer_store => { async ({ value }) => {
this.store_disposer.dispose_all(); try {
const hunt_optimizer_store = await value;
this.store_disposer.dispose_all();
this.store_disposer.add_all( this.store_disposer.add_all(
bind_children_to( bind_children_to(
this.tbody_element, this.tbody_element,
hunt_optimizer_store.wanted_items, hunt_optimizer_store.wanted_items,
this.create_row, this.create_row,
), ),
combo_box.selected.observe(({ value: item_type }) => { combo_box.selected.observe(({ value: item_type }) => {
if (item_type) { if (item_type) {
hunt_optimizer_store.add_wanted_item(item_type); hunt_optimizer_store.add_wanted_item(item_type);
combo_box.selected.val = undefined; combo_box.selected.val = undefined;
} }
}), }),
); );
huntable_items.val = hunt_optimizer_store.huntable_item_types huntable_items.val = hunt_optimizer_store.huntable_item_types
.slice() .slice()
.sort((a, b) => a.name.localeCompare(b.name)); .sort((a, b) => a.name.localeCompare(b.name));
filtered_huntable_items.val = huntable_items.val; filtered_huntable_items.val = huntable_items.val;
} catch (e) {
logger.error("Couldn't load hunt optimizer store.", e);
}
}, },
{ call_now: true }, { call_now: true },
), ),

View File

@ -1,8 +1,8 @@
import { HuntOptimizerView } from "./gui/HuntOptimizerView"; import { HuntOptimizerView } from "./gui/HuntOptimizerView";
import { ServerMap } from "../core/stores/ServerMap"; import { ServerMap } from "../core/stores/ServerMap";
import { HuntMethodStore, load_hunt_method_stores } from "./stores/HuntMethodStore"; import { HuntMethodStore, create_hunt_method_stores } from "./stores/HuntMethodStore";
import { GuiStore } from "../core/stores/GuiStore"; import { GuiStore } from "../core/stores/GuiStore";
import { HuntOptimizerStore, load_hunt_optimizer_stores } from "./stores/HuntOptimizerStore"; import { HuntOptimizerStore, create_hunt_optimizer_stores } from "./stores/HuntOptimizerStore";
import { ItemTypeStore } from "../core/stores/ItemTypeStore"; import { ItemTypeStore } from "../core/stores/ItemTypeStore";
import { HuntMethodPersister } from "./persistence/HuntMethodPersister"; import { HuntMethodPersister } from "./persistence/HuntMethodPersister";
import { HuntOptimizerPersister } from "./persistence/HuntOptimizerPersister"; import { HuntOptimizerPersister } from "./persistence/HuntOptimizerPersister";
@ -15,12 +15,12 @@ export function initialize_hunt_optimizer(
item_type_stores: ServerMap<ItemTypeStore>, item_type_stores: ServerMap<ItemTypeStore>,
item_drop_stores: ServerMap<ItemDropStore>, item_drop_stores: ServerMap<ItemDropStore>,
): HuntOptimizerView { ): HuntOptimizerView {
const hunt_method_stores: ServerMap<HuntMethodStore> = load_hunt_method_stores( const hunt_method_stores: ServerMap<HuntMethodStore> = create_hunt_method_stores(
http_client, http_client,
gui_store, gui_store,
new HuntMethodPersister(), new HuntMethodPersister(),
); );
const hunt_optimizer_stores: ServerMap<HuntOptimizerStore> = load_hunt_optimizer_stores( const hunt_optimizer_stores: ServerMap<HuntOptimizerStore> = create_hunt_optimizer_stores(
gui_store, gui_store,
new HuntOptimizerPersister(item_type_stores), new HuntOptimizerPersister(item_type_stores),
item_type_stores, item_type_stores,

View File

@ -20,7 +20,7 @@ const DEFAULT_DURATION = Duration.fromObject({ minutes: 30 });
const DEFAULT_GOVERNMENT_TEST_DURATION = Duration.fromObject({ minutes: 45 }); const DEFAULT_GOVERNMENT_TEST_DURATION = Duration.fromObject({ minutes: 45 });
const DEFAULT_LARGE_ENEMY_COUNT_DURATION = Duration.fromObject({ minutes: 45 }); const DEFAULT_LARGE_ENEMY_COUNT_DURATION = Duration.fromObject({ minutes: 45 });
export function load_hunt_method_stores( export function create_hunt_method_stores(
http_client: HttpClient, http_client: HttpClient,
gui_store: GuiStore, gui_store: GuiStore,
hunt_method_persister: HuntMethodPersister, hunt_method_persister: HuntMethodPersister,

View File

@ -25,7 +25,7 @@ import { ServerMap } from "../../core/stores/ServerMap";
import { GuiStore } from "../../core/stores/GuiStore"; import { GuiStore } from "../../core/stores/GuiStore";
import { HuntOptimizerPersister } from "../persistence/HuntOptimizerPersister"; import { HuntOptimizerPersister } from "../persistence/HuntOptimizerPersister";
export function load_hunt_optimizer_stores( export function create_hunt_optimizer_stores(
gui_store: GuiStore, gui_store: GuiStore,
hunt_optimizer_persister: HuntOptimizerPersister, hunt_optimizer_persister: HuntOptimizerPersister,
item_type_stores: ServerMap<ItemTypeStore>, item_type_stores: ServerMap<ItemTypeStore>,

View File

@ -10,7 +10,7 @@ import { HttpClient } from "../../core/HttpClient";
const logger = Logger.get("stores/ItemDropStore"); const logger = Logger.get("stores/ItemDropStore");
export function load_item_drop_stores( export function create_item_drop_stores(
http_client: HttpClient, http_client: HttpClient,
gui_store: GuiStore, gui_store: GuiStore,
item_type_stores: ServerMap<ItemTypeStore>, item_type_stores: ServerMap<ItemTypeStore>,

View File

@ -8,24 +8,32 @@ import { initialize } from "./initialize";
import { StubHttpClient } from "./core/HttpClient"; import { StubHttpClient } from "./core/HttpClient";
import { DisposableThreeRenderer } from "./core/rendering/Renderer"; import { DisposableThreeRenderer } from "./core/rendering/Renderer";
test("Initialization and shutdown should succeed without throwing or logging errors.", () => { for (const path of [undefined, "/viewer", "/quest_editor", "/hunt_optimizer"]) {
const logged_errors: string[] = []; const with_path = path == undefined ? "without specific path" : `with path ${path}`;
Logger.setHandler((messages: any[], context: IContext) => { test(`Initialization and shutdown ${with_path} should succeed without throwing errors or logging with level WARN or above.`, () => {
if (context.level.value >= Logger.ERROR.value) { const logged_errors: string[] = [];
logged_errors.push(Array.prototype.join.call(messages, " "));
Logger.setHandler((messages: any[], context: IContext) => {
if (context.level.value >= Logger.WARN.value) {
logged_errors.push(Array.prototype.join.call(messages, " "));
}
});
if (path != undefined) {
window.location.hash = path;
} }
const app = initialize(new StubHttpClient(), () => new StubRenderer());
expect(app).toBeDefined();
expect(logged_errors).toEqual([]);
app.dispose();
expect(logged_errors).toEqual([]);
}); });
}
const app = initialize(new StubHttpClient(), () => new StubRenderer());
expect(app).toBeDefined();
expect(logged_errors).toEqual([]);
app.dispose();
expect(logged_errors).toEqual([]);
});
class StubRenderer implements DisposableThreeRenderer { class StubRenderer implements DisposableThreeRenderer {
domElement: HTMLCanvasElement = document.createElement("canvas"); domElement: HTMLCanvasElement = document.createElement("canvas");

View File

@ -1,8 +1,8 @@
import { HttpClient } from "./core/HttpClient"; import { HttpClient } from "./core/HttpClient";
import { Disposable } from "./core/observable/Disposable"; import { Disposable } from "./core/observable/Disposable";
import { GuiStore, GuiTool } from "./core/stores/GuiStore"; import { GuiStore, GuiTool } from "./core/stores/GuiStore";
import { load_item_type_stores } from "./core/stores/ItemTypeStore"; import { create_item_type_stores } from "./core/stores/ItemTypeStore";
import { load_item_drop_stores } from "./hunt_optimizer/stores/ItemDropStore"; import { create_item_drop_stores } from "./hunt_optimizer/stores/ItemDropStore";
import { ApplicationView } from "./application/gui/ApplicationView"; import { ApplicationView } from "./application/gui/ApplicationView";
import { throttle } from "lodash"; import { throttle } from "lodash";
import { DisposableThreeRenderer } from "./core/rendering/Renderer"; import { DisposableThreeRenderer } from "./core/rendering/Renderer";
@ -23,8 +23,8 @@ export function initialize(
// Initialize core stores shared by several submodules. // Initialize core stores shared by several submodules.
const gui_store = new GuiStore(); const gui_store = new GuiStore();
const item_type_stores = load_item_type_stores(http_client, gui_store); const item_type_stores = create_item_type_stores(http_client, gui_store);
const item_drop_stores = load_item_drop_stores(http_client, gui_store, item_type_stores); const item_drop_stores = create_item_drop_stores(http_client, gui_store, item_type_stores);
// Initialize application view. // Initialize application view.
const application_view = new ApplicationView(gui_store, [ const application_view = new ApplicationView(gui_store, [