updates ts-eslint and fixes errors in src/observable

This commit is contained in:
Isaiah Becker-Mayer 2022-07-09 12:43:24 -04:00
parent deab8bdaf0
commit 0203ece3bd
18 changed files with 159 additions and 126 deletions

View File

@ -20,8 +20,10 @@ module.exports = {
rules: { rules: {
"@typescript-eslint/no-floating-promises": 2, "@typescript-eslint/no-floating-promises": 2,
"@typescript-eslint/no-misused-promises": 2, "@typescript-eslint/no-misused-promises": 2,
'no-unused-vars': 'off', "no-unused-vars": "off",
'@typescript-eslint/no-unused-vars': ['warn'], "@typescript-eslint/no-unused-vars": ["warn"],
'no-undef': 'off', "no-undef": "off",
"semi": ["error", "always"],
"@typescript-eslint/explicit-function-return-type": ["error"]
} }
}; };

View File

@ -34,7 +34,7 @@ export abstract class BaseObservable<T> {
if (this._handlers.size === 1) { if (this._handlers.size === 1) {
this.onSubscribeFirst(); this.onSubscribeFirst();
} }
return () => { return (): undefined => {
return this.unsubscribe(handler); return this.unsubscribe(handler);
}; };
} }
@ -63,17 +63,18 @@ export abstract class BaseObservable<T> {
// Add iterator over handlers here // Add iterator over handlers here
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function tests() { export function tests() {
class Collection extends BaseObservable<{}> { class Collection extends BaseObservable<{}> {
firstSubscribeCalls: number = 0; firstSubscribeCalls: number = 0;
firstUnsubscribeCalls: number = 0; firstUnsubscribeCalls: number = 0;
onSubscribeFirst() { this.firstSubscribeCalls += 1; } onSubscribeFirst(): void { this.firstSubscribeCalls += 1; }
onUnsubscribeLast() { this.firstUnsubscribeCalls += 1; } onUnsubscribeLast(): void { this.firstUnsubscribeCalls += 1; }
} }
return { return {
test_unsubscribe(assert) { test_unsubscribe(assert): void {
const c = new Collection(); const c = new Collection();
const unsubscribe = c.subscribe({}); const unsubscribe = c.subscribe({});
unsubscribe(); unsubscribe();

View File

@ -20,7 +20,7 @@ import type {SubscriptionHandle} from "./BaseObservable";
// like an EventEmitter, but doesn't have an event type // like an EventEmitter, but doesn't have an event type
export abstract class BaseObservableValue<T> extends BaseObservable<(value: T) => void> { export abstract class BaseObservableValue<T> extends BaseObservable<(value: T) => void> {
emit(argument: T) { emit(argument: T): void {
for (const h of this._handlers) { for (const h of this._handlers) {
h(argument); h(argument);
} }
@ -68,7 +68,7 @@ class WaitForHandle<T> implements IWaitHandle<T> {
return this._promise; return this._promise;
} }
dispose() { dispose(): void {
if (this._subscription) { if (this._subscription) {
this._subscription(); this._subscription();
this._subscription = null; this._subscription = null;
@ -82,7 +82,7 @@ class WaitForHandle<T> implements IWaitHandle<T> {
class ResolvedWaitForHandle<T> implements IWaitHandle<T> { class ResolvedWaitForHandle<T> implements IWaitHandle<T> {
constructor(public promise: Promise<T>) {} constructor(public promise: Promise<T>) {}
dispose() {} dispose(): void {}
} }
export class ObservableValue<T> extends BaseObservableValue<T> { export class ObservableValue<T> extends BaseObservableValue<T> {
@ -113,7 +113,7 @@ export class RetainedObservableValue<T> extends ObservableValue<T> {
this._freeCallback = freeCallback; this._freeCallback = freeCallback;
} }
onUnsubscribeLast() { onUnsubscribeLast(): void {
super.onUnsubscribeLast(); super.onUnsubscribeLast();
this._freeCallback(); this._freeCallback();
} }
@ -130,7 +130,7 @@ export class FlatMapObservableValue<P, C> extends BaseObservableValue<C | undefi
super(); super();
} }
onUnsubscribeLast() { onUnsubscribeLast(): void {
super.onUnsubscribeLast(); super.onUnsubscribeLast();
this.sourceSubscription = this.sourceSubscription!(); this.sourceSubscription = this.sourceSubscription!();
if (this.targetSubscription) { if (this.targetSubscription) {
@ -138,7 +138,7 @@ export class FlatMapObservableValue<P, C> extends BaseObservableValue<C | undefi
} }
} }
onSubscribeFirst() { onSubscribeFirst(): void {
super.onSubscribeFirst(); super.onSubscribeFirst();
this.sourceSubscription = this.source.subscribe(() => { this.sourceSubscription = this.source.subscribe(() => {
this.updateTargetSubscription(); this.updateTargetSubscription();
@ -147,7 +147,7 @@ export class FlatMapObservableValue<P, C> extends BaseObservableValue<C | undefi
this.updateTargetSubscription(); this.updateTargetSubscription();
} }
private updateTargetSubscription() { private updateTargetSubscription(): void {
const sourceValue = this.source.get(); const sourceValue = this.source.get();
if (sourceValue) { if (sourceValue) {
const target = this.mapper(sourceValue); const target = this.mapper(sourceValue);
@ -174,9 +174,10 @@ export class FlatMapObservableValue<P, C> extends BaseObservableValue<C | undefi
} }
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function tests() { export function tests() {
return { return {
"set emits an update": assert => { "set emits an update": (assert): void => {
const a = new ObservableValue<number>(0); const a = new ObservableValue<number>(0);
let fired = false; let fired = false;
const subscription = a.subscribe(v => { const subscription = a.subscribe(v => {
@ -187,7 +188,7 @@ export function tests() {
assert(fired); assert(fired);
subscription(); subscription();
}, },
"set doesn't emit if value hasn't changed": assert => { "set doesn't emit if value hasn't changed": (assert): void => {
const a = new ObservableValue(5); const a = new ObservableValue(5);
let fired = false; let fired = false;
const subscription = a.subscribe(() => { const subscription = a.subscribe(() => {
@ -198,7 +199,7 @@ export function tests() {
assert(!fired); assert(!fired);
subscription(); subscription();
}, },
"waitFor promise resolves on matching update": async assert => { "waitFor promise resolves on matching update": async (assert): Promise<void> => {
const a = new ObservableValue(5); const a = new ObservableValue(5);
const handle = a.waitFor(v => v === 6); const handle = a.waitFor(v => v === 6);
await Promise.resolve().then(() => { await Promise.resolve().then(() => {
@ -207,7 +208,7 @@ export function tests() {
await handle.promise; await handle.promise;
assert.strictEqual(a.get(), 6); assert.strictEqual(a.get(), 6);
}, },
"waitFor promise rejects when disposed": async assert => { "waitFor promise rejects when disposed": async (assert): Promise<void> => {
const a = new ObservableValue<number>(0); const a = new ObservableValue<number>(0);
const handle = a.waitFor(() => false); const handle = a.waitFor(() => false);
await Promise.resolve().then(() => { await Promise.resolve().then(() => {
@ -215,7 +216,7 @@ export function tests() {
}); });
await assert.rejects(handle.promise, AbortError); await assert.rejects(handle.promise, AbortError);
}, },
"flatMap.get": assert => { "flatMap.get": (assert): void => {
const a = new ObservableValue<undefined | {count: ObservableValue<number>}>(undefined); const a = new ObservableValue<undefined | {count: ObservableValue<number>}>(undefined);
const countProxy = a.flatMap(a => a!.count); const countProxy = a.flatMap(a => a!.count);
assert.strictEqual(countProxy.get(), undefined); assert.strictEqual(countProxy.get(), undefined);
@ -223,7 +224,7 @@ export function tests() {
a.set({count}); a.set({count});
assert.strictEqual(countProxy.get(), 0); assert.strictEqual(countProxy.get(), 0);
}, },
"flatMap update from source": assert => { "flatMap update from source": (assert): void => {
const a = new ObservableValue<undefined | {count: ObservableValue<number>}>(undefined); const a = new ObservableValue<undefined | {count: ObservableValue<number>}>(undefined);
const updates: (number | undefined)[] = []; const updates: (number | undefined)[] = [];
a.flatMap(a => a!.count).subscribe(count => { a.flatMap(a => a!.count).subscribe(count => {
@ -233,7 +234,7 @@ export function tests() {
a.set({count}); a.set({count});
assert.deepEqual(updates, [0]); assert.deepEqual(updates, [0]);
}, },
"flatMap update from target": assert => { "flatMap update from target": (assert): void => {
const a = new ObservableValue<undefined | {count: ObservableValue<number>}>(undefined); const a = new ObservableValue<undefined | {count: ObservableValue<number>}>(undefined);
const updates: (number | undefined)[] = []; const updates: (number | undefined)[] = [];
a.flatMap(a => a!.count).subscribe(count => { a.flatMap(a => a!.count).subscribe(count => {

View File

@ -135,10 +135,12 @@ class ResetEvent<F> {
import {ObservableArray} from "./ObservableArray"; import {ObservableArray} from "./ObservableArray";
import {ListObserver} from "../../mocks/ListObserver.js"; import {ListObserver} from "../../mocks/ListObserver.js";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function tests() { export function tests() {
return { return {
"events are emitted in order": async assert => { "events are emitted in order": async (assert): Promise<void> => {
const double = n => n * n; const double = (n: number): number => n * n;
const source = new ObservableArray<number>(); const source = new ObservableArray<number>();
const mapper = new AsyncMappedList(source, async n => { const mapper = new AsyncMappedList(source, async n => {
await new Promise(r => setTimeout(r, n)); await new Promise(r => setTimeout(r, n));

View File

@ -37,14 +37,15 @@ export class BaseMappedList<F,T,R = T> extends BaseObservableList<T> {
this._removeCallback = removeCallback; this._removeCallback = removeCallback;
} }
findAndUpdate(predicate: (value: T) => boolean, updater: (value: T) => any | false) { findAndUpdate(predicate: (value: T) => boolean, updater: (value: T) => any | false): boolean {
return findAndUpdateInArray(predicate, this._mappedValues!, this, updater); return findAndUpdateInArray(predicate, this._mappedValues!, this, updater);
} }
get length() { get length(): number {
return this._mappedValues!.length; return this._mappedValues!.length;
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
[Symbol.iterator]() { [Symbol.iterator]() {
return this._mappedValues!.values(); return this._mappedValues!.values();
} }

View File

@ -26,17 +26,17 @@ export interface IListObserver<T> {
export function defaultObserverWith<T>(overrides: { [key in keyof IListObserver<T>]?: IListObserver<T>[key] }): IListObserver<T> { export function defaultObserverWith<T>(overrides: { [key in keyof IListObserver<T>]?: IListObserver<T>[key] }): IListObserver<T> {
const defaults = { const defaults = {
onReset(){}, onReset(): void {},
onAdd(){}, onAdd(): void {},
onUpdate(){}, onUpdate(): void {},
onRemove(){}, onRemove(): void {},
onMove(){}, onMove(): void {},
}; };
return Object.assign(defaults, overrides); return Object.assign(defaults, overrides);
} }
export abstract class BaseObservableList<T> extends BaseObservable<IListObserver<T>> implements Iterable<T> { export abstract class BaseObservableList<T> extends BaseObservable<IListObserver<T>> implements Iterable<T> {
emitReset() { emitReset(): void {
for(let h of this._handlers) { for(let h of this._handlers) {
h.onReset(this); h.onReset(this);
} }

View File

@ -86,10 +86,12 @@ export class ConcatList<T> extends BaseObservableList<T> implements IListObserve
return len; return len;
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
[Symbol.iterator]() { [Symbol.iterator]() {
let sourceListIdx = 0; let sourceListIdx = 0;
let it = this._sourceLists[0][Symbol.iterator](); let it = this._sourceLists[0][Symbol.iterator]();
return { return {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
next: () => { next: () => {
let result = it.next(); let result = it.next();
while (result.done) { while (result.done) {
@ -108,16 +110,19 @@ export class ConcatList<T> extends BaseObservableList<T> implements IListObserve
import {ObservableArray} from "./ObservableArray"; import {ObservableArray} from "./ObservableArray";
import {defaultObserverWith} from "./BaseObservableList"; import {defaultObserverWith} from "./BaseObservableList";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function tests() { export async function tests() {
return { return {
test_length(assert) { test_length(assert): void {
const all = new ConcatList( const all = new ConcatList(
new ObservableArray([1, 2, 3]), new ObservableArray([1, 2, 3]),
new ObservableArray([11, 12, 13]) new ObservableArray([11, 12, 13])
); );
assert.equal(all.length, 6); assert.equal(all.length, 6);
}, },
test_iterator(assert) { test_iterator(assert): void {
const all = new ConcatList( const all = new ConcatList(
new ObservableArray([1, 2, 3]), new ObservableArray([1, 2, 3]),
new ObservableArray([11, 12, 13]) new ObservableArray([11, 12, 13])
@ -131,7 +136,7 @@ export async function tests() {
assert.equal(it.next().value, 13); assert.equal(it.next().value, 13);
assert(it.next().done); assert(it.next().done);
}, },
test_add(assert) { test_add(assert): void {
const list1 = new ObservableArray([1, 2, 3]); const list1 = new ObservableArray([1, 2, 3]);
const list2 = new ObservableArray([11, 12, 13]); const list2 = new ObservableArray([11, 12, 13]);
const all = new ConcatList(list1, list2); const all = new ConcatList(list1, list2);
@ -146,7 +151,7 @@ export async function tests() {
list2.insert(1, 11.5); list2.insert(1, 11.5);
assert(fired); assert(fired);
}, },
test_update(assert) { test_update(assert): void {
const list1 = new ObservableArray([1, 2, 3]); const list1 = new ObservableArray([1, 2, 3]);
const list2 = new ObservableArray([11, 12, 13]); const list2 = new ObservableArray([11, 12, 13]);
const all = new ConcatList(list1, list2); const all = new ConcatList(list1, list2);

View File

@ -19,7 +19,7 @@ import {IListObserver} from "./BaseObservableList";
import {BaseMappedList, runAdd, runUpdate, runRemove, runMove, runReset} from "./BaseMappedList"; import {BaseMappedList, runAdd, runUpdate, runRemove, runMove, runReset} from "./BaseMappedList";
export class MappedList<F,T> extends BaseMappedList<F,T> implements IListObserver<F> { export class MappedList<F,T> extends BaseMappedList<F,T> implements IListObserver<F> {
onSubscribeFirst() { onSubscribeFirst(): void {
this._sourceUnsubscribe = this._sourceList.subscribe(this); this._sourceUnsubscribe = this._sourceList.subscribe(this);
this._mappedValues = []; this._mappedValues = [];
for (const item of this._sourceList) { for (const item of this._sourceList) {
@ -61,18 +61,21 @@ import {ObservableArray} from "./ObservableArray";
import {BaseObservableList} from "./BaseObservableList"; import {BaseObservableList} from "./BaseObservableList";
import {defaultObserverWith} from "./BaseObservableList"; import {defaultObserverWith} from "./BaseObservableList";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function tests() { export async function tests() {
class MockList extends BaseObservableList<number> { class MockList extends BaseObservableList<number> {
get length() { get length(): 0 {
return 0; return 0;
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
[Symbol.iterator]() { [Symbol.iterator]() {
return [].values(); return [].values();
} }
} }
return { return {
test_add(assert) { test_add(assert): void {
const source = new MockList(); const source = new MockList();
const mapped = new MappedList(source, n => {return {n: n*n};}); const mapped = new MappedList(source, n => {return {n: n*n};});
let fired = false; let fired = false;
@ -87,7 +90,7 @@ export async function tests() {
assert(fired); assert(fired);
unsubscribe(); unsubscribe();
}, },
test_update(assert) { test_update(assert): void {
const source = new MockList(); const source = new MockList();
const mapped = new MappedList<number, { n: number, m?: number }>( const mapped = new MappedList<number, { n: number, m?: number }>(
source, source,
@ -109,7 +112,7 @@ export async function tests() {
assert(fired); assert(fired);
unsubscribe(); unsubscribe();
}, },
"test findAndUpdate not found": assert => { "test findAndUpdate not found": (assert): void => {
const source = new ObservableArray([1, 3, 4]); const source = new ObservableArray([1, 3, 4]);
const mapped = new MappedList( const mapped = new MappedList(
source, source,
@ -123,7 +126,7 @@ export async function tests() {
() => assert.fail() () => assert.fail()
), false); ), false);
}, },
"test findAndUpdate found but updater bails out of update": assert => { "test findAndUpdate found but updater bails out of update": (assert): void => {
const source = new ObservableArray([1, 3, 4]); const source = new ObservableArray([1, 3, 4]);
const mapped = new MappedList( const mapped = new MappedList(
source, source,
@ -143,7 +146,7 @@ export async function tests() {
), true); ), true);
assert.equal(fired, true); assert.equal(fired, true);
}, },
"test findAndUpdate emits update": assert => { "test findAndUpdate emits update": (assert): void => {
const source = new ObservableArray([1, 3, 4]); const source = new ObservableArray([1, 3, 4]);
const mapped = new MappedList( const mapped = new MappedList(
source, source,
@ -161,6 +164,6 @@ export async function tests() {
assert.equal(mapped.findAndUpdate(n => n === 9, () => "param"), true); assert.equal(mapped.findAndUpdate(n => n === 9, () => "param"), true);
assert.equal(fired, true); assert.equal(fired, true);
}, },
}; };
} }

View File

@ -75,6 +75,7 @@ export class ObservableArray<T> extends BaseObservableList<T> {
return this._items.length; return this._items.length;
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
[Symbol.iterator]() { [Symbol.iterator]() {
return this._items.values(); return this._items.values();
} }

View File

@ -112,6 +112,7 @@ export class SortedArray<T> extends BaseObservableList<T> {
return this._items.length; return this._items.length;
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
[Symbol.iterator]() { [Symbol.iterator]() {
return new Iterator(this); return new Iterator(this);
} }
@ -127,6 +128,7 @@ class Iterator<T> {
this._current = null; this._current = null;
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
next() { next() {
if (this._sortedArray) { if (this._sortedArray) {
if (this._current) { if (this._current) {
@ -147,9 +149,10 @@ class Iterator<T> {
} }
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function tests() { export function tests() {
return { return {
"setManyUnsorted": assert => { "setManyUnsorted": (assert): void => {
const sa = new SortedArray<string>((a, b) => a.localeCompare(b)); const sa = new SortedArray<string>((a, b) => a.localeCompare(b));
sa.setManyUnsorted(["b", "a", "c"]); sa.setManyUnsorted(["b", "a", "c"]);
assert.equal(sa.length, 3); assert.equal(sa.length, 3);
@ -157,7 +160,7 @@ export function tests() {
assert.equal(sa.get(1), "b"); assert.equal(sa.get(1), "b");
assert.equal(sa.get(2), "c"); assert.equal(sa.get(2), "c");
}, },
"_getNext": assert => { "_getNext": (assert): void => {
const sa = new SortedArray<string>((a, b) => a.localeCompare(b)); const sa = new SortedArray<string>((a, b) => a.localeCompare(b));
sa.setManyUnsorted(["b", "a", "f"]); sa.setManyUnsorted(["b", "a", "f"]);
assert.equal(sa._getNext("a"), "b"); assert.equal(sa._getNext("a"), "b");
@ -166,7 +169,7 @@ export function tests() {
assert.equal(sa._getNext("c"), "f"); assert.equal(sa._getNext("c"), "f");
assert.equal(sa._getNext("f"), undefined); assert.equal(sa._getNext("f"), undefined);
}, },
"iterator with removals": assert => { "iterator with removals": (assert): void => {
const queue = new SortedArray<{idx: number}>((a, b) => a.idx - b.idx); const queue = new SortedArray<{idx: number}>((a, b) => a.idx - b.idx);
queue.setManyUnsorted([{idx: 5}, {idx: 3}, {idx: 1}, {idx: 4}, {idx: 2}]); queue.setManyUnsorted([{idx: 5}, {idx: 3}, {idx: 1}, {idx: 4}, {idx: 2}]);
const it = queue[Symbol.iterator](); const it = queue[Symbol.iterator]();

View File

@ -17,7 +17,12 @@ limitations under the License.
import {BaseObservableList} from "./BaseObservableList"; import {BaseObservableList} from "./BaseObservableList";
/* inline update of item in collection backed by array, without replacing the preexising item */ /* inline update of item in collection backed by array, without replacing the preexising item */
export function findAndUpdateInArray<T>(predicate: (value: T) => boolean, array: T[], observable: BaseObservableList<T>, updater: (value: T) => any | false) { export function findAndUpdateInArray<T>(
predicate: (value: T) => boolean,
array: T[],
observable: BaseObservableList<T>,
updater: (value: T) => any | false
): boolean {
const index = array.findIndex(predicate); const index = array.findIndex(predicate);
if (index !== -1) { if (index !== -1) {
const value = array[index]; const value = array[index];

View File

@ -36,42 +36,42 @@ export class ApplyMap<K, V> extends BaseObservableMap<K, V> {
this._config = config<K, V>(); this._config = config<K, V>();
} }
hasApply() { hasApply(): boolean {
return !!this._apply; return !!this._apply;
} }
setApply(apply?: Apply<K, V>) { setApply(apply?: Apply<K, V>): void {
this._apply = apply; this._apply = apply;
if (this._apply) { if (this._apply) {
this.applyOnce(this._apply); this.applyOnce(this._apply);
} }
} }
applyOnce(apply: Apply<K, V>) { applyOnce(apply: Apply<K, V>): void {
for (const [key, value] of this._source) { for (const [key, value] of this._source) {
apply(key, value); apply(key, value);
} }
} }
onAdd(key: K, value: V) { onAdd(key: K, value: V): void {
if (this._apply) { if (this._apply) {
this._apply(key, value); this._apply(key, value);
} }
this.emitAdd(key, value); this.emitAdd(key, value);
} }
onRemove(key: K, value: V) { onRemove(key: K, value: V): void {
this.emitRemove(key, value); this.emitRemove(key, value);
} }
onUpdate(key: K, value: V, params: any) { onUpdate(key: K, value: V, params: any): void {
if (this._apply) { if (this._apply) {
this._apply(key, value, params); this._apply(key, value, params);
} }
this.emitUpdate(key, value, params); this.emitUpdate(key, value, params);
} }
onSubscribeFirst() { onSubscribeFirst(): void {
this._subscription = this._source.subscribe(this); this._subscription = this._source.subscribe(this);
if (this._apply) { if (this._apply) {
this.applyOnce(this._apply); this.applyOnce(this._apply);
@ -79,27 +79,28 @@ export class ApplyMap<K, V> extends BaseObservableMap<K, V> {
super.onSubscribeFirst(); super.onSubscribeFirst();
} }
onUnsubscribeLast() { onUnsubscribeLast(): void {
super.onUnsubscribeLast(); super.onUnsubscribeLast();
if (this._subscription) this._subscription = this._subscription(); if (this._subscription) this._subscription = this._subscription();
} }
onReset() { onReset(): void {
if (this._apply) { if (this._apply) {
this.applyOnce(this._apply); this.applyOnce(this._apply);
} }
this.emitReset(); this.emitReset();
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
[Symbol.iterator]() { [Symbol.iterator]() {
return this._source[Symbol.iterator](); return this._source[Symbol.iterator]();
} }
get size() { get size(): number {
return this._source.size; return this._source.size;
} }
get(key: K) { get(key: K): V | undefined {
return this._source.get(key); return this._source.get(key);
} }

View File

@ -36,26 +36,26 @@ export type BaseObservableMapConfig<K, V> = {
} }
export abstract class BaseObservableMap<K, V> extends BaseObservable<IMapObserver<K, V>> { export abstract class BaseObservableMap<K, V> extends BaseObservable<IMapObserver<K, V>> {
emitReset() { emitReset(): void {
for(let h of this._handlers) { for(let h of this._handlers) {
h.onReset(); h.onReset();
} }
} }
// we need batch events, mostly on index based collection though? // we need batch events, mostly on index based collection though?
// maybe we should get started without? // maybe we should get started without?
emitAdd(key: K, value: V) { emitAdd(key: K, value: V): void {
for(let h of this._handlers) { for(let h of this._handlers) {
h.onAdd(key, value); h.onAdd(key, value);
} }
} }
emitUpdate(key: K, value: V, params: any) { emitUpdate(key: K, value: V, params: any): void {
for(let h of this._handlers) { for(let h of this._handlers) {
h.onUpdate(key, value, params); h.onUpdate(key, value, params);
} }
} }
emitRemove(key: K, value: V) { emitRemove(key: K, value: V): void {
for(let h of this._handlers) { for(let h of this._handlers) {
h.onRemove(key, value); h.onRemove(key, value);
} }

View File

@ -35,7 +35,7 @@ export class FilteredMap<K, V> extends BaseObservableMap<K, V> {
this._config = config<K, V>(); this._config = config<K, V>();
} }
setFilter(filter: Filter<K, V>) { setFilter(filter: Filter<K, V>): void {
this._filter = filter; this._filter = filter;
if (this._subscription) { if (this._subscription) {
this._reapplyFilter(); this._reapplyFilter();
@ -45,7 +45,7 @@ export class FilteredMap<K, V> extends BaseObservableMap<K, V> {
/** /**
* reapply the filter * reapply the filter
*/ */
_reapplyFilter(silent = false) { _reapplyFilter(silent = false): void {
if (this._filter) { if (this._filter) {
const oldIncluded = this._included; const oldIncluded = this._included;
this._included = this._included || new Map(); this._included = this._included || new Map();
@ -71,7 +71,7 @@ export class FilteredMap<K, V> extends BaseObservableMap<K, V> {
} }
} }
onAdd(key: K, value: V) { onAdd(key: K, value: V): void {
if (this._filter) { if (this._filter) {
if (this._included) { if (this._included) {
const included = this._filter(value, key); const included = this._filter(value, key);
@ -86,7 +86,7 @@ export class FilteredMap<K, V> extends BaseObservableMap<K, V> {
this.emitAdd(key, value); this.emitAdd(key, value);
} }
onRemove(key: K, value: V) { onRemove(key: K, value: V): void {
const wasIncluded = !this._filter || this._included?.get(key); const wasIncluded = !this._filter || this._included?.get(key);
if (this._included) { if (this._included) {
this._included.delete(key); this._included.delete(key);
@ -98,7 +98,7 @@ export class FilteredMap<K, V> extends BaseObservableMap<K, V> {
} }
} }
onUpdate(key: K, value: V, params: any) { onUpdate(key: K, value: V, params: any): void {
// if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it // if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it
if (!this._included) { if (!this._included) {
return; return;
@ -113,7 +113,7 @@ export class FilteredMap<K, V> extends BaseObservableMap<K, V> {
} }
} }
_emitForUpdate(wasIncluded: boolean | undefined, isIncluded: boolean, key: K, value: V, params: any = null) { _emitForUpdate(wasIncluded: boolean | undefined, isIncluded: boolean, key: K, value: V, params: any = null): void {
if (wasIncluded && !isIncluded) { if (wasIncluded && !isIncluded) {
this.emitRemove(key, value); this.emitRemove(key, value);
} else if (!wasIncluded && isIncluded) { } else if (!wasIncluded && isIncluded) {
@ -123,13 +123,13 @@ export class FilteredMap<K, V> extends BaseObservableMap<K, V> {
} }
} }
onSubscribeFirst() { onSubscribeFirst(): void {
this._subscription = this._source.subscribe(this); this._subscription = this._source.subscribe(this);
this._reapplyFilter(true); this._reapplyFilter(true);
super.onSubscribeFirst(); super.onSubscribeFirst();
} }
onUnsubscribeLast() { onUnsubscribeLast(): void {
super.onUnsubscribeLast(); super.onUnsubscribeLast();
this._included = undefined; this._included = undefined;
if (this._subscription) { if (this._subscription) {
@ -137,16 +137,17 @@ export class FilteredMap<K, V> extends BaseObservableMap<K, V> {
} }
} }
onReset() { onReset(): void {
this._reapplyFilter(); this._reapplyFilter();
this.emitReset(); this.emitReset();
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
[Symbol.iterator]() { [Symbol.iterator]() {
return new FilterIterator<K, V>(this._source, this._included); return new FilterIterator<K, V>(this._source, this._included);
} }
get size() { get size(): number {
let count = 0; let count = 0;
this._included?.forEach(included => { this._included?.forEach(included => {
if (included) { if (included) {
@ -156,7 +157,7 @@ export class FilteredMap<K, V> extends BaseObservableMap<K, V> {
return count; return count;
} }
get(key) { get(key): V | undefined{
const value = this._source.get(key); const value = this._source.get(key);
if (value && this._filter(value, key)) { if (value && this._filter(value, key)) {
return value; return value;
@ -188,6 +189,7 @@ class FilterIterator<K, V> {
this._sourceIterator = map[Symbol.iterator](); this._sourceIterator = map[Symbol.iterator]();
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
next() { next() {
// eslint-disable-next-line no-constant-condition // eslint-disable-next-line no-constant-condition
while (true) { while (true) {
@ -204,9 +206,11 @@ class FilterIterator<K, V> {
} }
import {ObservableMap} from ".."; import {ObservableMap} from "..";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function tests() { export function tests() {
return { return {
"filter preloaded list": assert => { "filter preloaded list": (assert): void => {
const source = new ObservableMap(); const source = new ObservableMap();
source.add("one", 1); source.add("one", 1);
source.add("two", 2); source.add("two", 2);
@ -233,17 +237,17 @@ export function tests() {
assert.deepEqual(it.next().value, ["three", 3]); assert.deepEqual(it.next().value, ["three", 3]);
assert.equal(it.next().done, true); assert.equal(it.next().done, true);
}, },
// "filter added values": assert => { // "filter added values": (assert): void => {
// }, // },
// "filter removed values": assert => { // "filter removed values": (assert): void => {
// }, // },
// "filter changed values": assert => { // "filter changed values": (assert): void => {
// }, // },
"emits must trigger once": assert => { "emits must trigger once": (assert): void => {
const source = new ObservableMap(); const source = new ObservableMap();
let count_add = 0, count_update = 0, count_remove = 0; let count_add = 0, count_update = 0, count_remove = 0;
source.add("num1", 1); source.add("num1", 1);

View File

@ -33,7 +33,7 @@ export class JoinedMap<K, V> extends BaseObservableMap<K, V> {
this._config = config<K, V>(); this._config = config<K, V>();
} }
onAdd(source: BaseObservableMap<K, V>, key: K, value: V) { onAdd(source: BaseObservableMap<K, V>, key: K, value: V): void {
if (!this._isKeyAtSourceOccluded(source, key)) { if (!this._isKeyAtSourceOccluded(source, key)) {
const occludingValue = this._getValueFromOccludedSources(source, key); const occludingValue = this._getValueFromOccludedSources(source, key);
if (occludingValue !== undefined) { if (occludingValue !== undefined) {
@ -45,7 +45,7 @@ export class JoinedMap<K, V> extends BaseObservableMap<K, V> {
} }
} }
onRemove(source: BaseObservableMap<K, V>, key: K, value: V) { onRemove(source: BaseObservableMap<K, V>, key: K, value: V): void {
if (!this._isKeyAtSourceOccluded(source, key)) { if (!this._isKeyAtSourceOccluded(source, key)) {
this.emitRemove(key, value); this.emitRemove(key, value);
const occludedValue = this._getValueFromOccludedSources(source, key); const occludedValue = this._getValueFromOccludedSources(source, key);
@ -57,7 +57,7 @@ export class JoinedMap<K, V> extends BaseObservableMap<K, V> {
} }
} }
onUpdate(source: BaseObservableMap<K, V>, key: K, value: V, params: any) { onUpdate(source: BaseObservableMap<K, V>, key: K, value: V, params: any): void {
// if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it // if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it
if (!this._subscriptions) { if (!this._subscriptions) {
return; return;
@ -67,16 +67,16 @@ export class JoinedMap<K, V> extends BaseObservableMap<K, V> {
} }
} }
onReset() { onReset(): void {
this.emitReset(); this.emitReset();
} }
onSubscribeFirst() { onSubscribeFirst(): void {
this._subscriptions = this._sources.map(source => new SourceSubscriptionHandler(source, this).subscribe()); this._subscriptions = this._sources.map(source => new SourceSubscriptionHandler(source, this).subscribe());
super.onSubscribeFirst(); super.onSubscribeFirst();
} }
_isKeyAtSourceOccluded(source: BaseObservableMap<K, V>, key: K) { _isKeyAtSourceOccluded(source: BaseObservableMap<K, V>, key: K): boolean {
// sources that come first in the sources array can // sources that come first in the sources array can
// hide the keys in later sources, to prevent events // hide the keys in later sources, to prevent events
// being emitted for the same key and different values, // being emitted for the same key and different values,
@ -91,7 +91,7 @@ export class JoinedMap<K, V> extends BaseObservableMap<K, V> {
} }
// get the value that the given source and key occlude, if any // get the value that the given source and key occlude, if any
_getValueFromOccludedSources(source: BaseObservableMap<K, V>, key: K) { _getValueFromOccludedSources(source: BaseObservableMap<K, V>, key: K): V | undefined{
// sources that come first in the sources array can // sources that come first in the sources array can
// hide the keys in later sources, to prevent events // hide the keys in later sources, to prevent events
// being emitted for the same key and different values, // being emitted for the same key and different values,
@ -107,7 +107,7 @@ export class JoinedMap<K, V> extends BaseObservableMap<K, V> {
return undefined; return undefined;
} }
onUnsubscribeLast() { onUnsubscribeLast(): void {
super.onUnsubscribeLast(); super.onUnsubscribeLast();
if (this._subscriptions) { if (this._subscriptions) {
for (const s of this._subscriptions) { for (const s of this._subscriptions) {
@ -116,15 +116,16 @@ export class JoinedMap<K, V> extends BaseObservableMap<K, V> {
} }
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
[Symbol.iterator]() { [Symbol.iterator]() {
return new JoinedIterator<K, V>(this._sources); return new JoinedIterator<K, V>(this._sources);
} }
get size() { get size(): number {
return this._sources.reduce((sum, s) => sum + s.size, 0); return this._sources.reduce((sum, s) => sum + s.size, 0);
} }
get(key: K): V | undefined{ get(key: K): V | undefined {
for (const s of this._sources) { for (const s of this._sources) {
const value = s.get(key); const value = s.get(key);
if (value) { if (value) {
@ -199,28 +200,28 @@ class SourceSubscriptionHandler<K, V> {
this._subscription = undefined; this._subscription = undefined;
} }
subscribe() { subscribe(): this {
this._subscription = this._source.subscribe(this); this._subscription = this._source.subscribe(this);
return this; return this;
} }
dispose() { dispose(): void {
if (this._subscription) this._subscription = this._subscription(); if (this._subscription) this._subscription = this._subscription();
} }
onAdd(key: K, value: V) { onAdd(key: K, value: V): void {
this._joinedMap.onAdd(this._source, key, value); this._joinedMap.onAdd(this._source, key, value);
} }
onRemove(key: K, value: V) { onRemove(key: K, value: V): void {
this._joinedMap.onRemove(this._source, key, value); this._joinedMap.onRemove(this._source, key, value);
} }
onUpdate(key: K, value: V, params: any) { onUpdate(key: K, value: V, params: any): void {
this._joinedMap.onUpdate(this._source, key, value, params); this._joinedMap.onUpdate(this._source, key, value, params);
} }
onReset() { onReset(): void {
this._joinedMap.onReset(); this._joinedMap.onReset();
} }
} }
@ -228,9 +229,9 @@ class SourceSubscriptionHandler<K, V> {
import {ObservableMap} from ".."; import {ObservableMap} from "..";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function tests() { export function tests() {
function observeMap(map: JoinedMap<any, any>): { type: string; key: any; value: any; params?: any; }[] {
function observeMap(map: JoinedMap<any, any>) {
const events: { type: string, key: any, value: any, params?: any }[] = []; const events: { type: string, key: any, value: any, params?: any }[] = [];
map.subscribe({ map.subscribe({
onAdd(key, value) { events.push({ type: "add", key, value }); }, onAdd(key, value) { events.push({ type: "add", key, value }); },
@ -244,7 +245,7 @@ export function tests() {
} }
return { return {
"joined iterator": assert => { "joined iterator": (assert): void => {
const firstKV: [string, number] = ["a", 1]; const firstKV: [string, number] = ["a", 1];
const secondKV: [string, number] = ["b", 2]; const secondKV: [string, number] = ["b", 2];
const thirdKV: [string, number] = ["c", 3]; const thirdKV: [string, number] = ["c", 3];
@ -254,7 +255,7 @@ export function tests() {
assert.equal(it.next().value, thirdKV); assert.equal(it.next().value, thirdKV);
assert.equal(it.next().done, true); assert.equal(it.next().done, true);
}, },
"prevent key collision during iteration": assert => { "prevent key collision during iteration": (assert): void => {
const first = new ObservableMap(); const first = new ObservableMap();
const second = new ObservableMap(); const second = new ObservableMap();
const join = new JoinedMap([first, second]); const join = new JoinedMap([first, second]);
@ -266,7 +267,7 @@ export function tests() {
assert.deepEqual(it.next().value, ["b", 3]); assert.deepEqual(it.next().value, ["b", 3]);
assert.equal(it.next().done, true); assert.equal(it.next().done, true);
}, },
"adding occluded key doesn't emit add": assert => { "adding occluded key doesn't emit add": (assert): void => {
const first = new ObservableMap(); const first = new ObservableMap();
const second = new ObservableMap(); const second = new ObservableMap();
const join = new JoinedMap([first, second]); const join = new JoinedMap([first, second]);
@ -278,7 +279,7 @@ export function tests() {
assert.equal(events[0].key, "a"); assert.equal(events[0].key, "a");
assert.equal(events[0].value, 1); assert.equal(events[0].value, 1);
}, },
"updating occluded key doesn't emit update": assert => { "updating occluded key doesn't emit update": (assert): void => {
const first = new ObservableMap(); const first = new ObservableMap();
const second = new ObservableMap(); const second = new ObservableMap();
const join = new JoinedMap([first, second]); const join = new JoinedMap([first, second]);
@ -288,7 +289,7 @@ export function tests() {
second.update("a", 3); second.update("a", 3);
assert.equal(events.length, 0); assert.equal(events.length, 0);
}, },
"removal of occluding key emits add after remove": assert => { "removal of occluding key emits add after remove": (assert): void => {
const first = new ObservableMap(); const first = new ObservableMap();
const second = new ObservableMap(); const second = new ObservableMap();
const join = new JoinedMap([first, second]); const join = new JoinedMap([first, second]);
@ -304,7 +305,7 @@ export function tests() {
assert.equal(events[1].key, "a"); assert.equal(events[1].key, "a");
assert.equal(events[1].value, 2); assert.equal(events[1].value, 2);
}, },
"adding occluding key emits remove first": assert => { "adding occluding key emits remove first": (assert): void => {
const first = new ObservableMap(); const first = new ObservableMap();
const second = new ObservableMap(); const second = new ObservableMap();
const join = new JoinedMap([first, second]); const join = new JoinedMap([first, second]);

View File

@ -42,47 +42,48 @@ export class LogMap<K, V> extends BaseObservableMap<K, V> {
return this._log.log(labelOrValues, logLevel); return this._log.log(labelOrValues, logLevel);
} }
onAdd(key: K, value: V) { onAdd(key: K, value: V): void {
this.log("add " + JSON.stringify({key, value})); this.log("add " + JSON.stringify({key, value}));
this.emitAdd(key, value); this.emitAdd(key, value);
} }
onRemove(key: K, value: V) { onRemove(key: K, value: V): void {
this.log("remove " + JSON.stringify({key, value})); this.log("remove " + JSON.stringify({key, value}));
this.emitRemove(key, value); this.emitRemove(key, value);
} }
onUpdate(key: K, value: V, params: any) { onUpdate(key: K, value: V, params: any): void {
this.log("update" + JSON.stringify({key, value, params})); this.log("update" + JSON.stringify({key, value, params}));
this.emitUpdate(key, value, params); this.emitUpdate(key, value, params);
} }
onSubscribeFirst() { onSubscribeFirst(): void {
this.log("subscribeFirst"); this.log("subscribeFirst");
this._subscription = this._source.subscribe(this); this._subscription = this._source.subscribe(this);
super.onSubscribeFirst(); super.onSubscribeFirst();
} }
onUnsubscribeLast() { onUnsubscribeLast(): void {
super.onUnsubscribeLast(); super.onUnsubscribeLast();
if (this._subscription) this._subscription = this._subscription(); if (this._subscription) this._subscription = this._subscription();
this.log("unsubscribeLast"); this.log("unsubscribeLast");
} }
onReset() { onReset(): void {
this.log("reset"); this.log("reset");
this.emitReset(); this.emitReset();
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
[Symbol.iterator]() { [Symbol.iterator]() {
return this._source[Symbol.iterator](); return this._source[Symbol.iterator]();
} }
get size() { get size(): number {
return this._source.size; return this._source.size;
} }
get(key: K) { get(key: K): V | undefined{
return this._source.get(key); return this._source.get(key);
} }

View File

@ -46,28 +46,28 @@ export class MappedMap<K, V> extends BaseObservableMap<K, V> {
this._config = config<K, V>(); this._config = config<K, V>();
} }
_emitSpontaneousUpdate(key: K, params: any) { _emitSpontaneousUpdate(key: K, params: any): void {
const value = this._mappedValues.get(key); const value = this._mappedValues.get(key);
if (value) { if (value) {
this.emitUpdate(key, value, params); this.emitUpdate(key, value, params);
} }
} }
onAdd(key: K, value: V) { onAdd(key: K, value: V): void {
const emitSpontaneousUpdate = this._emitSpontaneousUpdate.bind(this, key); const emitSpontaneousUpdate = this._emitSpontaneousUpdate.bind(this, key);
const mappedValue = this._mapper(value, emitSpontaneousUpdate); const mappedValue = this._mapper(value, emitSpontaneousUpdate);
this._mappedValues.set(key, mappedValue); this._mappedValues.set(key, mappedValue);
this.emitAdd(key, mappedValue); this.emitAdd(key, mappedValue);
} }
onRemove(key: K/*, _value*/) { onRemove(key: K/*, _value*/): void {
const mappedValue = this._mappedValues.get(key); const mappedValue = this._mappedValues.get(key);
if (this._mappedValues.delete(key)) { if (this._mappedValues.delete(key)) {
if (mappedValue) this.emitRemove(key, mappedValue); if (mappedValue) this.emitRemove(key, mappedValue);
} }
} }
onUpdate(key: K, value: V, params: any) { onUpdate(key: K, value: V, params: any): void {
// if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it // if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it
if (!this._mappedValues) { if (!this._mappedValues) {
return; return;
@ -80,7 +80,7 @@ export class MappedMap<K, V> extends BaseObservableMap<K, V> {
} }
} }
onSubscribeFirst() { onSubscribeFirst(): void {
this._subscription = this._source.subscribe(this); this._subscription = this._source.subscribe(this);
for (let [key, value] of this._source) { for (let [key, value] of this._source) {
const emitSpontaneousUpdate = this._emitSpontaneousUpdate.bind(this, key); const emitSpontaneousUpdate = this._emitSpontaneousUpdate.bind(this, key);
@ -90,22 +90,23 @@ export class MappedMap<K, V> extends BaseObservableMap<K, V> {
super.onSubscribeFirst(); super.onSubscribeFirst();
} }
onUnsubscribeLast() { onUnsubscribeLast(): void {
super.onUnsubscribeLast(); super.onUnsubscribeLast();
if (this._subscription) this._subscription = this._subscription(); if (this._subscription) this._subscription = this._subscription();
this._mappedValues.clear(); this._mappedValues.clear();
} }
onReset() { onReset(): void {
this._mappedValues.clear(); this._mappedValues.clear();
this.emitReset(); this.emitReset();
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
[Symbol.iterator]() { [Symbol.iterator]() {
return this._mappedValues.entries(); return this._mappedValues.entries();
} }
get size() { get size(): number {
return this._mappedValues.size; return this._mappedValues.size;
} }

View File

@ -117,9 +117,10 @@ export class ObservableMap<K, V> extends BaseObservableMap<K, V> {
} }
}; };
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function tests() { export function tests() {
return { return {
test_initial_values(assert) { test_initial_values(assert): void {
const map = new ObservableMap([ const map = new ObservableMap([
["a", 5], ["a", 5],
["b", 10] ["b", 10]
@ -129,7 +130,7 @@ export function tests() {
assert.equal(map.get("b"), 10); assert.equal(map.get("b"), 10);
}, },
test_add(assert) { test_add(assert): void {
let fired = 0; let fired = 0;
const map = new ObservableMap<number, {value: number}>(); const map = new ObservableMap<number, {value: number}>();
map.subscribe({ map.subscribe({
@ -147,7 +148,7 @@ export function tests() {
assert.equal(fired, 1); assert.equal(fired, 1);
}, },
test_update(assert) { test_update(assert): void {
let fired = 0; let fired = 0;
const map = new ObservableMap<number, {number: number}>(); const map = new ObservableMap<number, {number: number}>();
const value = {number: 5}; const value = {number: 5};
@ -168,7 +169,7 @@ export function tests() {
assert.equal(fired, 1); assert.equal(fired, 1);
}, },
test_update_unknown(assert) { test_update_unknown(assert): void {
let fired = 0; let fired = 0;
const map = new ObservableMap<number, {number: number}>(); const map = new ObservableMap<number, {number: number}>();
map.subscribe({ map.subscribe({
@ -182,7 +183,7 @@ export function tests() {
assert.equal(result, false); assert.equal(result, false);
}, },
test_set(assert) { test_set(assert): void {
let add_fired = 0, update_fired = 0; let add_fired = 0, update_fired = 0;
const map = new ObservableMap<number, {value: number}>(); const map = new ObservableMap<number, {value: number}>();
map.subscribe({ map.subscribe({
@ -209,7 +210,7 @@ export function tests() {
assert.equal(update_fired, 1); assert.equal(update_fired, 1);
}, },
test_remove(assert) { test_remove(assert): void {
let fired = 0; let fired = 0;
const map = new ObservableMap<number, {value: number}>(); const map = new ObservableMap<number, {value: number}>();
const value = {value: 5}; const value = {value: 5};
@ -229,7 +230,7 @@ export function tests() {
assert.equal(fired, 1); assert.equal(fired, 1);
}, },
test_iterate(assert) { test_iterate(assert): void {
const results: any[] = []; const results: any[] = [];
const map = new ObservableMap<number, {number: number}>(); const map = new ObservableMap<number, {number: number}>();
map.add(1, {number: 5}); map.add(1, {number: 5});
@ -243,7 +244,7 @@ export function tests() {
assert.equal(results.find(([key]) => key === 2)[1].number, 6); assert.equal(results.find(([key]) => key === 2)[1].number, 6);
assert.equal(results.find(([key]) => key === 3)[1].number, 7); assert.equal(results.find(([key]) => key === 3)[1].number, 7);
}, },
test_size(assert) { test_size(assert): void {
const map = new ObservableMap<number, {number: number}>(); const map = new ObservableMap<number, {number: number}>();
map.add(1, {number: 5}); map.add(1, {number: 5});
map.add(2, {number: 6}); map.add(2, {number: 6});