diff --git a/src/bindings/stores/createStore.ts b/src/bindings/stores/createStore.ts index 6c367ce..7df6f10 100644 --- a/src/bindings/stores/createStore.ts +++ b/src/bindings/stores/createStore.ts @@ -1,6 +1,6 @@ import { ArrayStore } from "./arrayStore"; import { DefaultArrayStore } from "./defaultArrayStore"; -import { DefaultStore } from "./defaultStore"; +import { DefaultWritableStore } from "./defaultWritableStore"; import { WritableStore } from "./writableStore"; @@ -14,7 +14,7 @@ export function store(): WritableStore; export function store(initialValue: T): WritableStore; export function store(initialValue?: T): WritableStore { - return new DefaultStore(initialValue); + return new DefaultWritableStore(initialValue); } diff --git a/src/bindings/stores/defaultArrayStore.ts b/src/bindings/stores/defaultArrayStore.ts index 5ea6289..944328e 100644 --- a/src/bindings/stores/defaultArrayStore.ts +++ b/src/bindings/stores/defaultArrayStore.ts @@ -8,9 +8,9 @@ import { DefaultStore } from "./defaultStore"; */ export class DefaultArrayStore extends DefaultStore implements ArrayStore { - 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)) { diff --git a/src/bindings/stores/defaultStore.ts b/src/bindings/stores/defaultStore.ts index 78b6d84..b171ce8 100644 --- a/src/bindings/stores/defaultStore.ts +++ b/src/bindings/stores/defaultStore.ts @@ -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 implements WritableStore +export abstract class DefaultStore implements Store { - private _listeners?: Event; + protected _listeners?: Event; constructor(protected _value: T) { @@ -19,16 +18,6 @@ export class DefaultStore implements WritableStore 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) diff --git a/src/bindings/stores/defaultStoreDecorator.ts b/src/bindings/stores/defaultStoreDecorator.ts index 30501d7..20aece6 100644 --- a/src/bindings/stores/defaultStoreDecorator.ts +++ b/src/bindings/stores/defaultStoreDecorator.ts @@ -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 extends DefaultStore +export class DefaultStoreDecorator extends DefaultWritableStore { constructor( private _store: WritableStore, @@ -20,4 +20,4 @@ export class DefaultStoreDecorator extends DefaultStore { this._store.set(value); } -} \ No newline at end of file +} diff --git a/src/bindings/stores/defaultWritableStore.ts b/src/bindings/stores/defaultWritableStore.ts new file mode 100644 index 0000000..92961fa --- /dev/null +++ b/src/bindings/stores/defaultWritableStore.ts @@ -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 extends DefaultStore implements WritableStore +{ + 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); + } + } +} diff --git a/tests/bindings/compute.tests.ts b/tests/bindings/compute.tests.ts index 516cd0e..0e86cfa 100644 --- a/tests/bindings/compute.tests.ts +++ b/tests/bindings/compute.tests.ts @@ -1,32 +1,32 @@ /// -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"); @@ -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); @@ -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); @@ -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)); -}); \ No newline at end of file +}); diff --git a/tests/bindings/decorate.tests.ts b/tests/bindings/decorate.tests.ts index 672aecf..772b275 100644 --- a/tests/bindings/decorate.tests.ts +++ b/tests/bindings/decorate.tests.ts @@ -1,13 +1,13 @@ /// -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) => @@ -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) => @@ -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) => @@ -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); -}); \ No newline at end of file +}); diff --git a/tests/bindings/defaultStore.tests.ts b/tests/bindings/defaultStore.tests.ts index 9d596cc..a7baf91 100644 --- a/tests/bindings/defaultStore.tests.ts +++ b/tests/bindings/defaultStore.tests.ts @@ -1,27 +1,27 @@ /// -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"); }); @@ -29,12 +29,12 @@ 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" ]); }); @@ -44,12 +44,12 @@ 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" ]); }); @@ -57,23 +57,26 @@ test("subscription receives new value", t => 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"); -}); \ No newline at end of file + source.set("Pineapple"); + + t.true(first); + t.true(second); +}); diff --git a/tests/bindings/isStore.tests.ts b/tests/bindings/isStore.tests.ts index 7b0d0ba..35fd618 100644 --- a/tests/bindings/isStore.tests.ts +++ b/tests/bindings/isStore.tests.ts @@ -1,29 +1,37 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /// -import { DefaultArrayStore } from "@src/bindings/stores/defaultArrayStore"; -import { DefaultStore } from "@src/bindings/stores/defaultStore"; +import { compute } from "@src/bindings/stores/compute"; +import { arrayStore, store } from "@src/bindings/stores/createStore"; import { isStore, isWritableStore } from "@src/bindings/stores/isStore"; import test from "ava"; test("Store string is true", t => { - const store = new DefaultStore("Bob"); - t.true(isStore(store)); + const source = store("Bob"); + t.true(isStore(source)); }); test("Store number is true", t => { - const store = new DefaultStore(54); - t.true(isStore(store)); + const source = store(54); + t.true(isStore(source)); }); test("Array store is true", t => { - const store = new DefaultArrayStore([ "a", "b" ]); - t.true(isStore(store)); + const source = arrayStore([ "a", "b" ]); + t.true(isStore(source)); +}); + + +test("Compute store is true", t => +{ + const child = store(54); + const source = compute(child, v => v * 15); + t.true(isStore(source)); }); @@ -132,4 +140,4 @@ test("Writable contract without subscribe() is false", t => set: () => t.fail("Calling set is not allowed"), }; t.false(isWritableStore(store)); -}); \ No newline at end of file +}); diff --git a/tests/bindings/storify.tests.ts b/tests/bindings/storify.tests.ts index 069ceec..63d7341 100644 --- a/tests/bindings/storify.tests.ts +++ b/tests/bindings/storify.tests.ts @@ -1,5 +1,5 @@ /// -import { DefaultStore } from "@src/bindings/stores/defaultStore"; +import { store } from "@src/bindings/stores/createStore"; import { isStore } from "@src/bindings/stores/isStore"; import { storify } from "@src/bindings/stores/storify"; import test from "ava"; @@ -17,8 +17,8 @@ test("Storify returns store for value", t => test("Storify returns itself for store", t => { - const original = new DefaultStore("Bob"); + const original = store("Bob"); const storified = storify(original); t.is(storified, original); -}); \ No newline at end of file +}); diff --git a/tests/windows/widgetBinder.tests.ts b/tests/windows/widgetBinder.tests.ts index 2fb6543..1b19a39 100644 --- a/tests/windows/widgetBinder.tests.ts +++ b/tests/windows/widgetBinder.tests.ts @@ -1,6 +1,6 @@ /// -import { DefaultStore } from "@src/bindings/stores/defaultStore"; +import { store } from "@src/bindings/stores/createStore"; import { ElementVisibility } from "@src/elements/elementParams"; import { mutable } from "@src/utilities/mutable"; import { noop } from "@src/utilities/noop"; @@ -44,7 +44,7 @@ test("read() adds store to binder", t => t.deepEqual(binder["_bindings"], []); - const storeNumber = new DefaultStore(25); + const storeNumber = store(25); binder.add(label, "y", storeNumber); t.is(binder["_bindings"].length, 1); }); @@ -61,7 +61,7 @@ test("read() sets store in window frame", t => const output = new FrameBuilder({ redraw: noop }, {}, [], undefined); output.add(label); - const storeNumber = new DefaultStore(25); + const storeNumber = store(25); output.binder.add(label, "x", storeNumber); const frame = mutable(output.context); @@ -89,7 +89,7 @@ test("read() sets store through converter", t => const output = new FrameBuilder({ redraw: noop }, {}, [], undefined); output.add(label); - const storeNumber = new DefaultStore("visible"); + const storeNumber = store("visible"); output.binder.add(label, "isVisible", storeNumber, v => (v === "visible")); const frame = mutable(output.context); @@ -106,4 +106,4 @@ test("read() sets store through converter", t => storeNumber.set("none"); t.false(label.isVisible); -}); \ No newline at end of file +});