Skip to content

Commit

Permalink
Split default store into seperate readable base store and writable st…
Browse files Browse the repository at this point in the history
…ore classes
  • Loading branch information
Basssiiie committed Oct 21, 2023
1 parent 56cc824 commit 429f081
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 87 deletions.
4 changes: 2 additions & 2 deletions src/bindings/stores/createStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ArrayStore } from "./arrayStore";
import { DefaultArrayStore } from "./defaultArrayStore";
import { DefaultStore } from "./defaultStore";
import { DefaultWritableStore } from "./defaultWritableStore";
import { WritableStore } from "./writableStore";


Expand All @@ -14,7 +14,7 @@ export function store<U>(): WritableStore<U | undefined>;
export function store<T>(initialValue: T): WritableStore<T>;
export function store<T>(initialValue?: T): WritableStore<T | undefined>
{
return new DefaultStore<T | undefined>(initialValue);
return new DefaultWritableStore<T | undefined>(initialValue);
}


Expand Down
6 changes: 3 additions & 3 deletions src/bindings/stores/defaultArrayStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { DefaultStore } from "./defaultStore";
*/
export class DefaultArrayStore<T> extends DefaultStore<T[]> implements ArrayStore<T>
{
override set(array: T[]): void;
override set(index: number, item: T): void;
override set(indexOrArray: number | T[], optionalItem?: T): void
set(array: T[]): void;
set(index: number, item: T): void;
set(indexOrArray: number | T[], optionalItem?: T): void
{
if (isArray(indexOrArray))
{
Expand Down
19 changes: 4 additions & 15 deletions src/bindings/stores/defaultStore.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Event, invoke } from "@src/utilities/event";
import { WritableStore } from "./writableStore";
import * as Log from "@src/utilities/logger";
import { Store } from "./store";


/**
* The default implementation of a store.
* The default implementation of a read-only store.
*/
export class DefaultStore<T> implements WritableStore<T>
export abstract class DefaultStore<T> implements Store<T>
{
private _listeners?: Event<T>;
protected _listeners?: Event<T>;

constructor(protected _value: T)
{
Expand All @@ -19,16 +18,6 @@ export class DefaultStore<T> implements WritableStore<T>
return this._value;
}

set(value: T): void
{
if (this._value !== value)
{
Log.debug("(Update store from", this._value, "to", value, "-> update", this._listeners?.length, "listeners)");
this._value = value;
this._updateListeners(value);
}
}

subscribe(callback: (value: T) => void): () => void
{
if (!this._listeners)
Expand Down
8 changes: 4 additions & 4 deletions src/bindings/stores/defaultStoreDecorator.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { DefaultStore } from "./defaultStore";
import { WritableStore } from "./writableStore";
import { DefaultWritableStore } from "./defaultWritableStore";
import { subscribe } from "./subscribe";
import { WritableStore } from "./writableStore";


/**
* Default implementation of a store that decorates another store.
*/
export class DefaultStoreDecorator<T> extends DefaultStore<T>
export class DefaultStoreDecorator<T> extends DefaultWritableStore<T>
{
constructor(
private _store: WritableStore<T>,
Expand All @@ -20,4 +20,4 @@ export class DefaultStoreDecorator<T> extends DefaultStore<T>
{
this._store.set(value);
}
}
}
20 changes: 20 additions & 0 deletions src/bindings/stores/defaultWritableStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as Log from "@src/utilities/logger";
import { DefaultStore } from "./defaultStore";
import { WritableStore } from "./writableStore";


/**
* The default implementation of a store.
*/
export class DefaultWritableStore<T> extends DefaultStore<T> implements WritableStore<T>
{
set(value: T): void
{
if (this._value !== value)
{
Log.debug("(Update store from", this._value, "to", value, "-> update", this._listeners?.length, "listeners)");
this._value = value;
this._updateListeners(value);
}
}
}
42 changes: 21 additions & 21 deletions tests/bindings/compute.tests.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
/// <reference path="../../lib/openrct2.d.ts" />
import { DefaultStore } from "@src/bindings/stores/defaultStore";
import { isStore } from "@src/bindings/stores/isStore";
import { compute } from "@src/bindings/stores/compute";
import { store } from "@src/bindings/stores/createStore";
import { isStore } from "@src/bindings/stores/isStore";
import test from "ava";


test("Compute to property", t =>
{
const store = new DefaultStore({ value: 5 });
const source = store({ value: 5 });

const dependant = compute(store, s => s.value);
const dependant = compute(source, s => s.value);
t.is(dependant.get(), 5);

store.set({ value: 88 });
source.set({ value: 88 });
t.is(dependant.get(), 88);
});


test("Compute to nested property", t =>
{
const store = new DefaultStore({
const source = store({
value: { text: "hello" }
});

const dependant = compute(store, s => s.value.text);
const dependant = compute(source, s => s.value.text);
t.is(dependant.get(), "hello");

store.set({
source.set({
value: { text: "bye" }
});
t.is(dependant.get(), "bye");
Expand All @@ -35,8 +35,8 @@ test("Compute to nested property", t =>

test("Compute from two stores", t =>
{
const a = new DefaultStore({ value: 5 });
const b = new DefaultStore(45);
const a = store({ value: 5 });
const b = store(45);

const dependant = compute(a, b, (a, b) => a.value * b);
t.is(dependant.get(), 225);
Expand All @@ -51,11 +51,11 @@ test("Compute from two stores", t =>

test("Compute from five stores", t =>
{
const a = new DefaultStore({ value: 5 });
const b = new DefaultStore(45);
const c = new DefaultStore<[string, number]>(["aaa", 75]);
const d = new DefaultStore("something");
const e = new DefaultStore(66);
const a = store({ value: 5 });
const b = store(45);
const c = store<[string, number]>(["aaa", 75]);
const d = store("something");
const e = store(66);

const dependant = compute(a, b, c, d, e, (a, b, c ,d, e) => a.value + b + c[1] + d.length + e);
t.is(dependant.get(), 200);
Expand All @@ -79,21 +79,21 @@ test("Compute from five stores", t =>

test("Compute is one-way", t =>
{
const store = new DefaultStore({ value: "hey" });
const source = store({ value: "hey" });

const dependant = compute(store, s => s.value);
const dependant = compute(source, s => s.value);
t.is(dependant.get(), "hey");

dependant.set("bye");
t.is(dependant.get(), "bye");
t.deepEqual(store.get(), { value: "hey" });
t.deepEqual(source.get(), { value: "hey" });
});


test("Computed store is valid store", t =>
{
const store = new DefaultStore({ value: 5 });
const source = store({ value: 5 });

const dependant = compute(store, s => s.value);
const dependant = compute(source, s => s.value);
t.true(isStore(dependant));
});
});
10 changes: 5 additions & 5 deletions tests/bindings/decorate.tests.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/// <reference path="../../lib/openrct2.d.ts" />
import { DefaultStore } from "@src/bindings/stores/defaultStore";
import { decorate } from "@src/bindings/stores/decorate";
import { DefaultWritableStore } from "@src/bindings/stores/defaultWritableStore";
import test from "ava";


test("Decorate calls in order", t =>
{
const hits: string[] = [];
const store = new DefaultStore(12);
const store = new DefaultWritableStore(12);
store.subscribe(v => hits.push(`store ${v}`));

const decorator = decorate(store, (v, c) =>
Expand Down Expand Up @@ -49,7 +49,7 @@ test("Decorate calls in order", t =>
test("Decorate can silence inner calls", t =>
{
const hits: string[] = [];
const store = new DefaultStore(12);
const store = new DefaultWritableStore(12);
store.subscribe(v => hits.push(`store ${v}`));

const decorator = decorate(store, (v) =>
Expand Down Expand Up @@ -90,7 +90,7 @@ test("Decorate can silence inner calls", t =>
test("Decorate can silence only odd calls", t =>
{
const hits: string[] = [];
const store = new DefaultStore(12);
const store = new DefaultWritableStore(12);
store.subscribe(v => hits.push(`store ${v}`));

const decorator = decorate(store, (v, c) =>
Expand Down Expand Up @@ -129,4 +129,4 @@ test("Decorate can silence only odd calls", t =>
t.deepEqual(hits, ["store 7", "before 7", "after 7"]);
t.is(decorator.get(), 10);
t.is(store.get(), 7);
});
});
43 changes: 23 additions & 20 deletions tests/bindings/defaultStore.tests.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
/// <reference path="../../lib/openrct2.d.ts" />
import { DefaultStore } from "@src/bindings/stores/defaultStore";
import { store } from "@src/bindings/stores/createStore";
import test from "ava";


test("get() returns string from constructor", t =>
{
const store = new DefaultStore("Bob");
t.is(store.get(), "Bob");
const source = store("Bob");
t.is(source.get(), "Bob");
});


test("get() returns number from constructor", t =>
{
const store = new DefaultStore(10.54);
t.is(store.get(), 10.54);
const source = store(10.54);
t.is(source.get(), 10.54);
});


test("set() changes get() value", t =>
{
const store = new DefaultStore("Cheese");
store.set("Pineapple");
t.is(store.get(), "Pineapple");
const source = store("Cheese");
source.set("Pineapple");
t.is(source.get(), "Pineapple");
});


test("set() triggers subscription", t =>
{
const events: string[] = [];

const store = new DefaultStore("Cheese");
store.subscribe(() =>
const source = store("Cheese");
source.subscribe(() =>
{
events.push("hit");
});
store.set("Pineapple");
source.set("Pineapple");

t.deepEqual(events, [ "hit" ]);
});
Expand All @@ -44,36 +44,39 @@ test("subscription receives new value", t =>
{
const events: string[] = [];

const store = new DefaultStore("Cheese");
store.subscribe(value =>
const source = store("Cheese");
source.subscribe(value =>
{
events.push(value);
});
store.set("Pineapple");
source.set("Pineapple");

t.deepEqual(events, [ "Pineapple" ]);
});


test("set() triggers multiple subscriptions", t =>
{
t.plan(4);
t.plan(6);
let first = false, second = false;

const store = new DefaultStore("Cheese");
store.subscribe(v =>
const source = store("Cheese");
source.subscribe(v =>
{
// First
t.is(v, "Pineapple");
t.false(first);
first = true;
});
store.subscribe(v =>
source.subscribe(v =>
{
// Second
t.is(v, "Pineapple");
t.false(second);
second = true;
});
store.set("Pineapple");
});
source.set("Pineapple");

t.true(first);
t.true(second);
});
Loading

0 comments on commit 429f081

Please sign in to comment.