diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..22be1f9
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,2 @@
+/coverage
+/dist
diff --git a/.eslintrc.js b/.eslintrc.js
index 25ad4d1..263f533 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -27,6 +27,7 @@ module.exports = {
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/explicit-function-return-type": "off",
- "@typescript-eslint/no-empty-function": "off"
+ "@typescript-eslint/no-empty-function": "off",
+ "@typescript-eslint/no-non-null-assertion": "error"
}
};
diff --git a/src/behavior.ts b/src/behavior.ts
index c566388..3b9bbc5 100644
--- a/src/behavior.ts
+++ b/src/behavior.ts
@@ -1,16 +1,27 @@
-import { cons, DoubleLinkedList, Node, fromArray, nil } from "./datastructures";
-import { combine, isPlaceholder } from "./index";
-import { State, Reactive, Time, BListener, Parent, SListener } from "./common";
-import { Future, BehaviorFuture } from "./future";
+import {
+ Cons,
+ cons,
+ DoubleLinkedList,
+ fromArray,
+ nil,
+ Node
+} from "./datastructures";
+import {
+ __UNSAFE_GET_LAST_BEHAVIOR_VALUE,
+ combine,
+ isPlaceholder
+} from "./index";
+import { BListener, Parent, Reactive, SListener, State, Time } from "./common";
import * as F from "./future";
+import { BehaviorFuture, Future } from "./future";
import {
- Stream,
- FlatFuturesOrdered,
+ FlatFutures,
FlatFuturesLatest,
- FlatFutures
+ FlatFuturesOrdered,
+ Stream
} from "./stream";
-import { tick, getTime } from "./clock";
-import { sample, Now } from "./now";
+import { getTime, tick } from "./clock";
+import { Now, sample } from "./now";
export type MapBehaviorTuple = { [K in keyof A]: Behavior };
@@ -22,7 +33,7 @@ export type MapBehaviorTuple = { [K in keyof A]: Behavior };
export abstract class Behavior extends Reactive
implements Parent {
// Behaviors cache their last value in `last`.
- last: A;
+ last?: A;
children: DoubleLinkedList = new DoubleLinkedList();
pulledAt: number | undefined;
changedAt: number | undefined;
@@ -70,7 +81,7 @@ export abstract class Behavior extends Reactive
const time = t === undefined ? tick() : t;
this.pull(time);
}
- return this.last;
+ return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this);
}
abstract update(t: number): A;
pushB(t: number): void {
@@ -88,7 +99,11 @@ export abstract class Behavior extends Reactive
for (const parent of this.parents) {
if (isBehavior(parent)) {
parent.pull(t);
- shouldRefresh = shouldRefresh || this.changedAt < parent.changedAt;
+ shouldRefresh =
+ shouldRefresh ||
+ (this.changedAt !== undefined &&
+ parent.changedAt !== undefined &&
+ this.changedAt < parent.changedAt);
}
}
if (shouldRefresh) {
@@ -139,7 +154,7 @@ function refresh(b: Behavior, t: number) {
export function isBehavior(b: unknown): b is Behavior {
return (
- (typeof b === "object" && "at" in b && !isPlaceholder(b)) ||
+ (typeof b === "object" && b !== null && "at" in b && !isPlaceholder(b)) ||
(isPlaceholder(b) && (b.source === undefined || isBehavior(b.source)))
);
}
@@ -184,14 +199,16 @@ class ProducerBehaviorFromFunction extends ProducerBehavior {
) {
super();
}
- deactivateFn: () => void;
+ deactivateFn?: () => void;
activateProducer(): void {
this.state = State.Push;
this.deactivateFn = this.activateFn(this.newValue.bind(this));
}
deactivateProducer(): void {
this.state = State.Inactive;
- this.deactivateFn();
+ if (this.deactivateFn !== undefined) {
+ this.deactivateFn();
+ }
}
}
@@ -243,7 +260,7 @@ export class MapBehavior extends Behavior {
this.parents = cons(parent);
}
update(_t: number): B {
- return this.f(this.parent.last);
+ return this.f(__UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.parent));
}
}
@@ -253,7 +270,9 @@ class ApBehavior extends Behavior {
this.parents = cons B) | A>>(fn, cons(val));
}
update(_t: number): B {
- return this.fn.last(this.val.last);
+ return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.fn)(
+ __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.val)
+ );
}
}
@@ -295,19 +314,22 @@ export class LiftBehavior extends Behavior {
class FlatMapBehavior extends Behavior {
// The last behavior returned by the chain function
- private innerB: Behavior;
+ private innerB?: Behavior | undefined;
private innerNode: Node = new Node(this);
constructor(private outer: Behavior, private fn: (a: A) => Behavior) {
super();
this.parents = cons(this.outer);
}
update(t: number): B {
- const outerChanged = this.outer.changedAt > this.changedAt;
+ const outerChanged =
+ this.outer.changedAt !== undefined &&
+ this.changedAt !== undefined &&
+ this.outer.changedAt > this.changedAt;
if (outerChanged || this.changedAt === undefined) {
if (this.innerB !== undefined) {
this.innerB.removeListener(this.innerNode);
}
- this.innerB = this.fn(this.outer.last);
+ this.innerB = this.fn(__UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.outer));
this.innerB.addListener(this.innerNode, t);
if (this.state !== this.innerB.state) {
this.changeStateDown(this.innerB.state);
@@ -317,7 +339,11 @@ class FlatMapBehavior extends Behavior {
this.innerB.pull(t);
}
}
- return this.innerB.last;
+ if (this.innerB === undefined) {
+ // panic!
+ throw new Error("FlatMapBehavior#innerB should be defined");
+ }
+ return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.innerB);
}
}
@@ -359,7 +385,7 @@ class SnapshotBehavior extends Behavior> implements SListener {
}
}
pushS(t: number, _val: A): void {
- this.last.resolve(this.parent.at(t), t);
+ __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this).resolve(this.parent.at(t), t);
this.parents = cons(this.parent);
this.changeStateDown(this.state);
this.parent.addListener(this.node, t);
@@ -368,7 +394,7 @@ class SnapshotBehavior extends Behavior> implements SListener {
if (this.future.state === State.Done) {
return Future.of(this.parent.at(t));
} else {
- return this.last;
+ return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this);
}
}
}
@@ -440,7 +466,7 @@ class SwitcherBehavior extends ActiveBehavior
next.addListener(this.nNode, t);
}
update(_t: Time): A {
- return this.b.last;
+ return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.b);
}
pushS(t: number, value: Behavior): void {
this.doSwitch(t, value);
@@ -662,7 +688,7 @@ export type SampleAt = (b: Behavior) => B;
class MomentBehavior extends Behavior {
private sampleBound: SampleAt;
- private currentSampleTime: Time;
+ private currentSampleTime?: Time;
constructor(private f: (at: SampleAt) => A) {
super();
this.sampleBound = (b) => this.sample(b);
@@ -703,17 +729,18 @@ class MomentBehavior extends Behavior {
parent.removeListener(node);
}
}
- this.parents = undefined;
- const value = this.f(this.sampleBound);
- return value;
+ this.parents = nil;
+ return this.f(this.sampleBound);
}
sample(b: Behavior): B {
const node = new Node(this);
this.listenerNodes = cons({ node, parent: b }, this.listenerNodes);
- b.addListener(node, this.currentSampleTime);
- b.at(this.currentSampleTime);
+ if (this.currentSampleTime !== undefined) {
+ b.addListener(node, this.currentSampleTime);
+ b.at(this.currentSampleTime);
+ }
this.parents = cons(b, this.parents);
- return b.last;
+ return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(b);
}
}
@@ -727,7 +754,7 @@ class FormatBehavior extends Behavior {
private behaviors: Array>
) {
super();
- let parents = undefined;
+ let parents: Cons> = nil;
for (const b of behaviors) {
if (isBehavior(b)) {
parents = cons(b, parents);
@@ -739,7 +766,7 @@ class FormatBehavior extends Behavior {
let resultString = this.strings[0];
for (let i = 0; i < this.behaviors.length; ++i) {
const b = this.behaviors[i];
- const value = isBehavior(b) ? b.last : b;
+ const value = isBehavior(b) ? __UNSAFE_GET_LAST_BEHAVIOR_VALUE(b) : b;
resultString += value.toString() + this.strings[i + 1];
}
return resultString;
diff --git a/src/common.ts b/src/common.ts
index 072f087..c28a509 100644
--- a/src/common.ts
+++ b/src/common.ts
@@ -1,15 +1,28 @@
-import { Cons, cons, DoubleLinkedList, Node } from "./datastructures";
+import { Cons, cons, DoubleLinkedList, nil, Node } from "./datastructures";
import { Behavior } from "./behavior";
import { tick } from "./clock";
export type Time = number;
function isBehavior(b: unknown): b is Behavior {
- return typeof b === "object" && "at" in b;
+ return typeof b === "object" && b !== null && "at" in b;
}
export type PullHandler = (pull: (t?: number) => void) => () => void;
+/**
+ * @internal
+ * Do not use!
+ * @throws {Error}
+ */
+export const __UNSAFE_GET_LAST_BEHAVIOR_VALUE = (b: Behavior): A => {
+ if (b.last === undefined) {
+ // panic!
+ throw new Error("Behavior#last value should be defined");
+ }
+ return b.last;
+};
+
/**
* The various states that a reactive can be in. The order matters here: Done <
* Push < Pull < Inactive. The idea is that a reactive can calculate its current
@@ -56,7 +69,7 @@ export class PushOnlyObserver implements BListener, SListener {
}
}
pushB(_t: number): void {
- this.callback((this.source as Behavior).last);
+ this.callback(__UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.source as Behavior));
}
pushS(_t: number, value: A): void {
this.callback(value);
@@ -73,13 +86,10 @@ export type NodeParentPair = {
};
export abstract class Reactive implements Child {
- state: State;
- parents: Cons>;
+ state: State = State.Inactive;
+ parents: Cons> = nil;
listenerNodes: Cons | undefined;
children: DoubleLinkedList = new DoubleLinkedList();
- constructor() {
- this.state = State.Inactive;
- }
addListener(node: Node, t: number): State {
const firstChild = this.children.head === undefined;
this.children.prepend(node);
@@ -135,18 +145,22 @@ export abstract class Reactive implements Child {
}
export class CbObserver implements BListener, SListener {
- private endPulling: () => void;
+ private endPulling?: () => void;
node: Node> = new Node(this);
constructor(
private callback: (a: A) => void,
readonly handlePulling: PullHandler,
- private time: Time,
+ private time: Time | undefined,
readonly source: ParentBehavior
) {
source.addListener(this.node, tick());
if (source.state === State.Pull) {
this.endPulling = handlePulling(this.pull.bind(this));
- } else if (isBehavior(source) && source.state === State.Push) {
+ } else if (
+ isBehavior(source) &&
+ source.state === State.Push &&
+ source.last !== undefined
+ ) {
callback(source.last);
}
this.time = undefined;
@@ -156,11 +170,13 @@ export class CbObserver implements BListener, SListener {
time !== undefined ? time : this.time !== undefined ? this.time : tick();
if (isBehavior(this.source) && this.source.state === State.Pull) {
this.source.pull(t);
- this.callback(this.source.last);
+ if (this.source.last !== undefined) {
+ this.callback(this.source.last);
+ }
}
}
pushB(_t: number): void {
- this.callback((this.source as Behavior).last);
+ this.callback(__UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.source as Behavior));
}
pushS(_t: number, value: A): void {
this.callback(value);
diff --git a/src/datastructures.ts b/src/datastructures.ts
index 638cc47..068178e 100644
--- a/src/datastructures.ts
+++ b/src/datastructures.ts
@@ -1,25 +1,40 @@
-export class Cons {
- constructor(
- public readonly value: A,
- public readonly tail: Cons,
- public readonly isNil: boolean
- ) {}
- *[Symbol.iterator](): IterableIterator {
- let head: Cons = this;
- while (head.isNil === false) {
- const v = head.value;
- head = head.tail;
- yield v;
- }
- }
+export interface ConsNil {
+ readonly isNil: true;
+ [Symbol.iterator](): Generator;
}
-export const nil: Cons = new Cons(undefined, undefined, true);
+export interface ConsValue {
+ readonly isNil: false;
+ readonly value: A;
+ readonly tail: Cons;
+ [Symbol.iterator](): Generator;
+}
+
+export type Cons = ConsNil | ConsValue;
-export function cons(value: A, tail: Cons = nil): Cons {
- return new Cons(value, tail, false);
+function* generator(this: Cons) {
+ let head: Cons = this;
+ while (!head.isNil) {
+ const v = head.value;
+ head = head.tail;
+ yield v;
+ }
}
+export const nil: Cons = {
+ isNil: true,
+ [Symbol.iterator]: generator
+};
+
+export const cons = (value: A, tail: Cons = nil): Cons => {
+ return {
+ isNil: false,
+ value,
+ tail,
+ [Symbol.iterator]: generator
+ };
+};
+
export function fromArray(values: A[]): Cons {
let list = cons(values[0]);
for (let i = 1; i < values.length; ++i) {
diff --git a/src/future.ts b/src/future.ts
index ed5cfd1..51e93f7 100644
--- a/src/future.ts
+++ b/src/future.ts
@@ -1,13 +1,23 @@
-import { State, SListener, Parent, BListener, Time } from "./common";
+import { State, SListener, Parent, BListener, Time, __UNSAFE_GET_LAST_BEHAVIOR_VALUE } from "./common";
import { Reactive } from "./common";
import { cons, fromArray, Node } from "./datastructures";
-import { Behavior, FunctionBehavior } from "./behavior";
+import {
+ Behavior,
+ FunctionBehavior
+} from "./behavior";
import { tick } from "./clock";
import { Stream } from "./stream";
import { sample, Now } from "./now";
export type MapFutureTuple = { [K in keyof A]: Future };
+const __UNSAFE_GET_LAST_FUTURE_VALUE = (f: Future): A => {
+ if (f.value === undefined) {
+ // panic!
+ throw new Error("Future#value should be defined");
+ }
+ return f.value;
+};
/**
* A future is a thing that occurs at some point in time with a value.
* It can be understood as a pair consisting of the time the future
@@ -17,7 +27,7 @@ export type MapFutureTuple = { [K in keyof A]: Future };
export abstract class Future extends Reactive>
implements Parent> {
// The value of the future. Often `undefined` until occurrence.
- value: A;
+ value?: A;
constructor() {
super();
}
@@ -37,7 +47,7 @@ export abstract class Future extends Reactive>
}
addListener(node: Node>, t: number): State {
if (this.state === State.Done) {
- node.value.pushS(t, this.value);
+ node.value.pushS(t, __UNSAFE_GET_LAST_FUTURE_VALUE(this));
return State.Done;
} else {
return super.addListener(node, t);
@@ -62,7 +72,7 @@ export abstract class Future extends Reactive>
of(b: B): Future {
return new OfFuture(b);
}
- ap: (f: Future<(a: A) => B>) => Future;
+ // ap: (f: Future<(a: A) => B>) => Future;
lift(
f: (...args: A) => R,
...args: MapFutureTuple
@@ -88,7 +98,7 @@ export abstract class Future extends Reactive>
}
export function isFuture(a: unknown): a is Future {
- return typeof a === "object" && "resolve" in a;
+ return typeof a === "object" && a !== null && "resolve" in a;
}
export class CombineFuture extends Future {
@@ -241,7 +251,7 @@ export class BehaviorFuture extends SinkFuture implements BListener {
}
pushB(t: number): void {
this.b.removeListener(this.node);
- this.resolve(this.b.last, t);
+ this.resolve(__UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.b), t);
}
}
diff --git a/src/placeholder.ts b/src/placeholder.ts
index ecebcae..48e3bb6 100644
--- a/src/placeholder.ts
+++ b/src/placeholder.ts
@@ -1,5 +1,10 @@
-import { Reactive, State, SListener, BListener, Time } from "./common";
-import { Behavior, isBehavior, MapBehavior, pushToChildren } from "./behavior";
+import { Reactive, State, SListener, BListener, Time, __UNSAFE_GET_LAST_BEHAVIOR_VALUE } from "./common";
+import {
+ Behavior,
+ isBehavior,
+ MapBehavior,
+ pushToChildren
+} from "./behavior";
import { Node, cons } from "./datastructures";
import { Stream, MapToStream } from "./stream";
import { tick } from "./clock";
@@ -14,7 +19,7 @@ class SamplePlaceholderError {
}
export class Placeholder extends Behavior {
- source: Reactive | BListener>;
+ source?: Reactive | BListener>;
private node: Node = new Node(this);
replaceWith(parent: Reactive | BListener>, t?: Time): void {
this.source = parent;
@@ -45,7 +50,7 @@ export class Placeholder extends Behavior {
}
}
update(_t: number): A {
- return (this.source as Behavior).last;
+ return __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.source as Behavior);
}
activate(t: number): void {
if (this.source !== undefined) {
@@ -73,7 +78,7 @@ export class Placeholder extends Behavior {
}
export function isPlaceholder(p: unknown): p is Placeholder {
- return typeof p === "object" && "replaceWith" in p;
+ return typeof p === "object" && p !== null && "replaceWith" in p;
}
class MapPlaceholder extends MapBehavior {
@@ -84,7 +89,7 @@ class MapPlaceholder extends MapBehavior {
}
class MapToPlaceholder extends MapToStream {
- changedAt: Time;
+ changedAt?: Time;
constructor(parent: Stream, public last: B) {
super(parent, last);
}
diff --git a/src/stream.ts b/src/stream.ts
index 5bcaeaf..4ee792f 100644
--- a/src/stream.ts
+++ b/src/stream.ts
@@ -1,4 +1,12 @@
-import { Reactive, State, Time, SListener, Parent, BListener } from "./common";
+import {
+ Reactive,
+ State,
+ Time,
+ SListener,
+ Parent,
+ BListener,
+ __UNSAFE_GET_LAST_BEHAVIOR_VALUE
+} from "./common";
import { cons, Node, DoubleLinkedList } from "./datastructures";
import {
Behavior,
@@ -19,11 +27,7 @@ import { Future } from ".";
*/
export abstract class Stream extends Reactive>
implements Parent> {
- constructor() {
- super();
- }
children: DoubleLinkedList> = new DoubleLinkedList();
- state: State;
combine(stream: Stream): Stream {
return new CombineStream(stream, this);
}
@@ -201,23 +205,25 @@ export function scan(
class ShiftBehaviorStream extends Stream implements BListener {
private bNode: Node = new Node(this);
private sNode: Node = new Node(this);
- private currentSource: Stream;
+ private currentSource?: Stream;
constructor(private b: Behavior>) {
super();
}
activate(t: number): void {
this.b.addListener(this.bNode, t);
if (this.b.state !== State.Inactive) {
- this.currentSource = this.b.last;
+ this.currentSource = __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.b);
this.currentSource.addListener(this.sNode, t);
}
}
deactivate(): void {
this.b.removeListener(this.bNode);
- this.currentSource.removeListener(this.sNode);
+ if (this.currentSource !== undefined) {
+ this.currentSource.removeListener(this.sNode);
+ }
}
pushB(t: number): void {
- const newStream = this.b.last;
+ const newStream = __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.b);
if (this.currentSource !== undefined) {
this.currentSource.removeListener(this.sNode);
}
@@ -254,7 +260,7 @@ export function shiftFrom(s: Stream>): Behavior> {
}
class ChangesStream extends Stream implements BListener {
- last: A;
+ last?: A;
initialized: boolean;
constructor(
readonly parent: Behavior,
@@ -269,17 +275,20 @@ class ChangesStream extends Stream implements BListener {
// The parent may be an unreplaced placeholder and in that case
// we can't read its current value.
if (this.parent.state === State.Push) {
- this.last = this.parent.last;
+ this.last = __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.parent);
this.initialized = true;
}
}
pushB(t: number): void {
if (!this.initialized) {
this.initialized = true;
- this.last = this.parent.last;
- } else if (!this.comparator(this.last, this.parent.last)) {
- this.pushSToChildren(t, this.parent.last);
- this.last = this.parent.last;
+ this.last = __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.parent);
+ } else {
+ const parentLast = __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.parent);
+ if (this.last !== undefined && !this.comparator(this.last, parentLast)) {
+ this.pushSToChildren(t, parentLast);
+ this.last = parentLast;
+ }
}
}
pushS(_t: number, _a: A): void {}
@@ -325,7 +334,7 @@ class ProducerStreamFromFunction extends ProducerStream {
constructor(private activateFn: ProducerStreamFunction) {
super();
}
- deactivateFn: () => void;
+ deactivateFn?: () => void;
publish(a: A, t: number = tick()): void {
this.pushS(t, a);
}
@@ -335,7 +344,9 @@ class ProducerStreamFromFunction extends ProducerStream {
}
deactivate(): void {
this.state = State.Inactive;
- this.deactivateFn();
+ if (this.deactivateFn !== undefined) {
+ this.deactivateFn();
+ }
}
}
@@ -448,7 +459,7 @@ export function selfie(stream: Stream>): Stream {
}
export function isStream(s: unknown): s is Stream {
- return typeof s === "object" && "scanFrom" in s;
+ return typeof s === "object" && s !== null && "scanFrom" in s;
}
class PerformCbStream extends ActiveStream implements SListener {
@@ -509,11 +520,12 @@ export class FlatFuturesOrdered extends Stream {
});
}
pushFromBuffer(): void {
- while (this.buffer[0] !== undefined) {
+ let a = this.buffer.shift();
+ while (a !== undefined) {
const t = tick();
- const { value } = this.buffer.shift();
- this.pushSToChildren(t, value);
+ this.pushSToChildren(t, a.value);
this.next++;
+ a = this.buffer.shift();
}
}
}
diff --git a/src/testing.ts b/src/testing.ts
index da9f477..027665a 100644
--- a/src/testing.ts
+++ b/src/testing.ts
@@ -44,31 +44,42 @@ import {
InstantRun
} from "./now";
import { time, DelayStream } from "./time";
+import { ConsValue } from "./datastructures";
// Future
-export type Occurrence = {
- time: Time;
- value: A;
-};
-
declare module "./future" {
interface Future {
model(): SemanticFuture;
}
}
-export const neverOccurringFuture = {
- time: "infinity" as "infinity",
- value: undefined as undefined
+export interface Occurrence {
+ tag: "Occurrence";
+ time: Time;
+ value: A;
+}
+
+export const occurrence = (time: Time, value: A): Occurrence => ({
+ tag: "Occurrence",
+ time,
+ value
+});
+
+export interface NeverOccurringFuture {
+ readonly tag: "NeverOccurringFuture";
+}
+
+export const neverOccurringFuture: NeverOccurringFuture = {
+ tag: "NeverOccurringFuture"
};
-export type SemanticFuture = Occurrence | typeof neverOccurringFuture;
+export type SemanticFuture = Occurrence | NeverOccurringFuture;
export function doesOccur(
future: SemanticFuture
): future is Occurrence {
- return future.time !== "infinity";
+ return future.tag === "Occurrence";
}
CombineFuture.prototype.model = function() {
@@ -80,30 +91,31 @@ CombineFuture.prototype.model = function() {
MapFuture.prototype.model = function() {
const p = this.parent.model();
return doesOccur(p)
- ? { time: p.time, value: this.f(p.value) }
+ ? occurrence(p.time, this.f(p.value))
: neverOccurringFuture;
};
MapToFuture.prototype.model = function() {
const p = this.parent.model();
- return doesOccur(p)
- ? { time: p.time, value: this.value }
- : neverOccurringFuture;
+ return doesOccur(p) ? occurrence(p.time, this.value) : neverOccurringFuture;
};
OfFuture.prototype.model = function() {
- return { time: -Infinity, value: this.value };
+ return occurrence(-Infinity, this.value);
};
NeverFuture.prototype.model = function() {
return neverOccurringFuture;
};
+const allOccurred = (fs: SemanticFuture[]): fs is Occurrence[] =>
+ fs.every((f) => f.tag === "Occurrence");
+
LiftFuture.prototype.model = function() {
const sems = (this.futures as Future[]).map((f) => f.model());
const time = Math.max(...sems.map((s) => (doesOccur(s) ? s.time : Infinity)));
- return time !== Infinity
- ? { time, value: this.f(...sems.map((s) => s.value)) }
+ return time !== Infinity && allOccurred(sems)
+ ? occurrence(time, this.f(...sems.map((s) => s.value)))
: neverOccurringFuture;
};
@@ -112,7 +124,7 @@ FlatMapFuture.prototype.model = function() {
if (doesOccur(a)) {
const b = this.f(a.value).model();
if (doesOccur(b)) {
- return { time: Math.max(a.time, b.time), value: b.value };
+ return occurrence(Math.max(a.time, b.time), b.value);
}
}
return neverOccurringFuture;
@@ -143,7 +155,7 @@ class TestFuture extends Future {
}
export function testFuture(time: number, value: A): Future {
- return new TestFuture({ time, value });
+ return new TestFuture(occurrence(time, value));
}
export function assertFutureEqual(
@@ -167,12 +179,12 @@ declare module "./stream" {
MapStream.prototype.model = function(this: MapStream) {
const s = this.parent.model();
- return s.map(({ time, value }) => ({ time, value: this.f(value) }));
+ return s.map(({ time, value }) => occurrence(time, this.f(value)));
};
MapToStream.prototype.model = function(this: MapToStream) {
- const s = (this.parents.value as Stream).model();
- return s.map(({ time }) => ({ time, value: this.b }));
+ const s = (this.parents as ConsValue>).value.model();
+ return s.map(({ time }) => occurrence(time, this.b));
};
FilterStream.prototype.model = function(this: FilterStream) {
@@ -189,7 +201,7 @@ ScanStream.prototype.model = function(this: ScanStream) {
.filter((o) => this.t < o.time)
.map(({ time, value }) => {
acc = this.f(value, acc);
- return { time, value: acc };
+ return occurrence(time, acc);
});
};
@@ -212,45 +224,48 @@ CombineStream.prototype.model = function(this: CombineStream) {
SnapshotStream.prototype.model = function(this: SnapshotStream) {
return this.trigger
.model()
- .map(({ time }) => ({ time, value: testAt(time, this.target) }));
+ .map(({ time }) => occurrence(time, testAt(time, this.target)));
};
DelayStream.prototype.model = function(this: DelayStream) {
- const s = (this.parents.value as Stream).model();
- return s.map(({ time, value }) => ({ time: time + this.ms, value }));
+ const s = (this.parents as ConsValue>).value.model();
+ return s.map(({ time, value }) => occurrence(time + this.ms, value));
};
-const flatFuture = (o: Occurrence>) => {
- const { time, value } = o.value.model();
- return time === "infinity" ? [] : [{ time: Math.max(o.time, time), value }];
+const flatFuture = (o: Occurrence>): Occurrence[] => {
+ const f = o.value.model();
+ if (f.tag === "NeverOccurringFuture") {
+ return [];
+ } else {
+ return [occurrence(Math.max(o.time, f.time), f.value)];
+ }
};
FlatFutures.prototype.model = function(this: FlatFutures) {
- return (this.parents.value as Stream>)
+ return (this.parents as ConsValue>>).value
.model()
.flatMap(flatFuture)
.sort((o, p) => o.time - p.time); // FIXME: Should use stable sort here
};
FlatFuturesOrdered.prototype.model = function(this: FlatFuturesOrdered) {
- return (this.parents.value as Stream>)
+ return (this.parents as ConsValue>>).value
.model()
.flatMap(flatFuture)
- .reduce((acc, o) => {
- const last = acc.length === 0 ? -Infinity : acc[acc.length - 1].time;
- return acc.concat([{ time: Math.max(last, o.time), value: o.value }]);
+ .reduce[]>((acc, o) => {
+ const last: Time =
+ acc.length === 0 ? -Infinity : acc[acc.length - 1].time;
+ return acc.concat([occurrence(Math.max(last, o.time), o.value)]);
}, []);
};
FlatFuturesLatest.prototype.model = function(this: FlatFuturesLatest) {
- return (this.parents.value as Stream>)
+ return (this.parents as ConsValue>>).value
.model()
.flatMap(flatFuture)
.reduceRight[]>((acc, o) => {
const last = acc.length === 0 ? Infinity : acc[0].time;
- return last < o.time
- ? acc
- : [{ time: o.time, value: o.value }].concat(acc);
+ return last < o.time ? acc : [occurrence(o.time, o.value)].concat(acc);
}, []);
};
@@ -276,15 +291,14 @@ class TestStream extends Stream {
}
export function testStreamFromArray(array: ([Time, A])[]): Stream {
- const semanticStream = array.map(([t, value]) => ({ value, time: t }));
+ const semanticStream = array.map(([t, value]) => occurrence(t, value));
return new TestStream(semanticStream);
}
export function testStreamFromObject(object: Record): Stream {
- const semanticStream = Object.keys(object).map((key) => ({
- time: parseFloat(key),
- value: object[key]
- }));
+ const semanticStream = Object.keys(object).map((key) =>
+ occurrence(parseFloat(key), object[key])
+ );
return new TestStream(semanticStream);
}
diff --git a/src/time.ts b/src/time.ts
index ba32c18..e668888 100644
--- a/src/time.ts
+++ b/src/time.ts
@@ -1,7 +1,10 @@
-import { Time, State } from "./common";
+import { Time, State, __UNSAFE_GET_LAST_BEHAVIOR_VALUE } from "./common";
import { cons } from "./datastructures";
import { Stream } from "./stream";
-import { Behavior, fromFunction } from "./behavior";
+import {
+ Behavior,
+ fromFunction
+} from "./behavior";
import { sample, Now, perform } from "./now";
/*
@@ -51,7 +54,9 @@ class DebounceStream extends Stream {
}
private timer: NodeJS.Timeout | undefined = undefined;
pushS(t: number, a: A): void {
- clearTimeout(this.timer);
+ if (this.timer !== undefined) {
+ clearTimeout(this.timer);
+ }
this.timer = setTimeout(() => {
this.pushSToChildren(t, a);
}, this.ms);
@@ -81,19 +86,20 @@ export const measureTime = sample(measureTimeFrom);
class IntegrateBehavior extends Behavior {
private lastPullTime: Time;
+ last = 0;
+ state = State.Pull;
constructor(private parent: Behavior, t: number) {
super();
this.lastPullTime = time.at(t);
- this.state = State.Pull;
- this.last = 0;
this.pulledAt = t;
this.changedAt = t;
this.parents = cons(parent, cons(time));
}
update(_t: Time): number {
- const currentPullTime = time.last;
+ const currentPullTime = __UNSAFE_GET_LAST_BEHAVIOR_VALUE(time);
const deltaMs = currentPullTime - this.lastPullTime;
- const value = this.last + deltaMs * this.parent.last;
+ const value =
+ this.last + deltaMs * __UNSAFE_GET_LAST_BEHAVIOR_VALUE(this.parent);
this.lastPullTime = currentPullTime;
return value;
}
diff --git a/test/behavior.ts b/test/behavior.ts
index 5bb2974..ee4c956 100644
--- a/test/behavior.ts
+++ b/test/behavior.ts
@@ -18,13 +18,22 @@ import {
Stream,
time,
runNow,
- Time
+ Time,
+ SinkBehavior
} from "../src";
import * as H from "../src";
import { subscribeSpy } from "./helpers";
import { placeholder } from "../src/placeholder";
+import { MonadDictionary } from "@funkia/jabz/dist/monad";
+
+declare module "@funkia/jabz" {
+ export function go(
+ gen: () => Generator,
+ monad?: MonadDictionary
+ ): any; // sorry
+}
function double(n: number): number {
return n * 2;
@@ -95,7 +104,7 @@ describe("behavior", () => {
});
it("can push and pull", () => {
let variable = 0;
- let push: (t: Time) => void;
+ let push: undefined | ((t: Time) => void);
const setVar = (n: number) => {
variable = n;
if (push !== undefined) {
@@ -257,20 +266,22 @@ describe("behavior", () => {
const numE = H.fromFunction(() => n);
const applied = H.ap(fnB, numE);
const cb = spy();
- let pull: () => void;
+ let pull: (() => void) | undefined;
applied.observe(cb, (pull_) => {
pull = pull_;
return () => {};
});
- pull();
- push(add(2), fnB);
- pull();
- n = 4;
- pull();
- push(double, fnB);
- pull();
- n = 8;
- pull();
+ if (pull !== undefined) {
+ pull();
+ push(add(2), fnB);
+ pull();
+ n = 4;
+ pull();
+ push(double, fnB);
+ pull();
+ n = 8;
+ pull();
+ }
assert.deepEqual(cb.args, [[6], [3], [6], [8], [16]]);
});
});
@@ -389,13 +400,12 @@ describe("behavior", () => {
const inner1 = sinkBehavior(1);
const inner2 = sinkBehavior(3);
const b = outer.flatMap((n) => {
- if (n === 0) {
- return Behavior.of(0);
- } else if (n === 1) {
+ if (n === 1) {
return inner1;
} else if (n === 2) {
return inner2;
}
+ return Behavior.of(0);
});
b.observe(() => {}, () => () => {});
assert.strictEqual(at(b), 0);
@@ -444,8 +454,12 @@ describe("behavior", () => {
});
it("works with go-notation", () => {
const a = H.sinkBehavior(1);
- const b = go(function*(): IterableIterator {
- const val: number = yield a;
+ const b: SinkBehavior = go(function*(): Generator<
+ SinkBehavior,
+ number,
+ number
+ > {
+ const val = yield a;
return val * 2;
});
const cb = spy();
@@ -624,7 +638,7 @@ describe("Behavior and Future", () => {
});
describe("snapshotAt", () => {
it("snapshots behavior at future occurring in future", () => {
- let result: number;
+ let result: number | undefined = undefined;
const bSink = sinkBehavior(1);
const futureSink = H.sinkFuture();
const mySnapshot = at(H.snapshotAt(bSink, futureSink));
@@ -636,7 +650,7 @@ describe("Behavior and Future", () => {
assert.strictEqual(result, 3);
});
it("uses current value when future occurred in the past", () => {
- let result: number;
+ let result: number | undefined = undefined;
const bSink = sinkBehavior(1);
const occurredFuture = H.Future.of({});
bSink.push(2);
@@ -935,7 +949,7 @@ describe("Behavior and Stream", () => {
const outer = sinkBehavior>(pushingB);
const flattened = H.flat(outer);
const pushSpy = spy();
- let pull: () => void;
+ let pull: undefined | (() => void);
const handlePulling = (p: () => void): (() => void) => {
pull = p;
return () => undefined;
@@ -943,11 +957,13 @@ describe("Behavior and Stream", () => {
flattened.observe(pushSpy, handlePulling);
outer.push(pullingB);
variable = 1;
- pull();
- variable = 2;
- pull();
- variable = 3;
- pull();
+ if (pull !== undefined) {
+ pull();
+ variable = 2;
+ pull();
+ variable = 3;
+ pull();
+ }
assert.deepEqual(pushSpy.args, [[0], [1], [2], [3]]);
});
});
diff --git a/test/future.ts b/test/future.ts
index 2b1331c..ce9da82 100644
--- a/test/future.ts
+++ b/test/future.ts
@@ -30,7 +30,7 @@ describe("Future", () => {
});
describe("sink", () => {
it("notifies subscriber", () => {
- let result: number;
+ let result: number | undefined;
const s = sinkFuture();
s.subscribe((x: number) => {
result = x;
@@ -40,7 +40,7 @@ describe("Future", () => {
assert.strictEqual(result, 2);
});
it("notifies subscriber several layers down", () => {
- let result: number;
+ let result: number | undefined;
const s = sinkFuture();
const s2 = s.map((n) => n + 2).mapTo(9);
s2.subscribe((x: number) => {
@@ -61,7 +61,7 @@ describe("Future", () => {
});
describe("Semigroup", () => {
it("returns the first future if it occurs first", () => {
- let result: number;
+ let result: number | undefined;
const future1 = sinkFuture();
const future2 = sinkFuture();
const combined = future1.combine(future2);
@@ -71,7 +71,7 @@ describe("Future", () => {
assert.strictEqual(result, 1);
});
it("returns the seconds future if it occurs first", () => {
- let result: number;
+ let result: number | undefined;
const future1 = sinkFuture();
const future2 = sinkFuture();
const combined = future1.combine(future2);
@@ -81,8 +81,8 @@ describe("Future", () => {
assert.strictEqual(result, 2);
});
it("returns when only one occurs", () => {
- let result1: number;
- let result2: number;
+ let result1: number | undefined;
+ let result2: number | undefined;
const future1 = sinkFuture();
const future2 = sinkFuture();
const combined = future1.combine(future2);
@@ -106,7 +106,7 @@ describe("Future", () => {
});
describe("Functor", () => {
it("maps over value", () => {
- let result: number;
+ let result: number | undefined;
const s = sinkFuture();
const mapped = s.map((x) => x * x);
mapped.subscribe((x: number) => {
@@ -117,7 +117,7 @@ describe("Future", () => {
assert.strictEqual(result, 16);
});
it("maps to constant", () => {
- let result: string;
+ let result: string | undefined;
const s = sinkFuture();
const mapped = s.mapTo("horse");
mapped.subscribe((x: string) => {
@@ -130,7 +130,7 @@ describe("Future", () => {
});
describe("Apply", () => {
it("lifts a function of one argument", () => {
- let result: string;
+ let result: string | undefined;
const fut = sinkFuture();
const lifted = H.lift((s: string) => s + "!", fut);
lifted.subscribe((s: string) => (result = s));
@@ -139,7 +139,7 @@ describe("Future", () => {
assert.strictEqual(result, "Hello!");
});
it("lifts a function of three arguments", () => {
- let result: string;
+ let result: string | undefined;
const fut1 = sinkFuture();
const fut2 = sinkFuture();
const fut3 = sinkFuture();
@@ -163,7 +163,7 @@ describe("Future", () => {
});
describe("Applicative", () => {
it("of gives future that has occurred", () => {
- let result: number;
+ let result: number | undefined;
const o = Future.of(12);
o.subscribe((x) => (result = x));
assert.strictEqual(result, 12);
@@ -203,19 +203,21 @@ describe("Future", () => {
});
});
it("can convert Promise to Future", async () => {
- let result: number;
- let resolve: (n: number) => void;
+ let result: number | undefined;
+ let resolve: ((n: number) => void) | undefined;
const promise = new Promise((res) => (resolve = res));
const future = fromPromise(promise);
future.subscribe((res: number) => (result = res));
assert.strictEqual(result, undefined);
- resolve(12);
+ if (resolve !== undefined) {
+ resolve(12);
+ }
await promise;
assert.strictEqual(result, 12);
});
describe("nextOccurence", () => {
it("resolves on next occurence", () => {
- let result: string;
+ let result: string | undefined;
const s = new SinkStream();
const next = nextOccurrenceFrom(s);
s.push("a");
@@ -230,8 +232,8 @@ describe("Future", () => {
it("resolves with result when done callback invoked", () => {
const fut = sinkFuture();
const cb = spy();
- let value: number;
- let done: (result: unknown) => void;
+ let value: number | undefined;
+ let done: ((result: unknown) => void) | undefined;
const fut2 = mapCbFuture((v, d) => {
value = v;
done = d;
@@ -240,7 +242,9 @@ describe("Future", () => {
fut.resolve(3);
assert.equal(value, 3);
assert.equal(cb.callCount, 0);
- done(value + 1);
+ if (done !== undefined && value !== undefined) {
+ done(value + 1);
+ }
assert.equal(cb.callCount, 1);
assert.deepEqual(cb.args, [[4]]);
});
diff --git a/test/now.ts b/test/now.ts
index 0b76c97..5f685bb 100644
--- a/test/now.ts
+++ b/test/now.ts
@@ -157,7 +157,9 @@ describe("Now", () => {
function loop(n: number): Now> {
return go(function*() {
const nextNumber: Future = yield performIO(getNextNr(1));
- const future = yield plan(nextNumber.map(loop));
+ const future: Future> = yield plan(
+ nextNumber.map(loop)
+ );
return switchTo(Behavior.of(n), future);
});
}
@@ -235,22 +237,24 @@ describe("Now", () => {
describe("loopNow", () => {
it("should loop the reactives", () => {
const result: unknown[] = [];
- let s: SinkStream;
+ let s: SinkStream | undefined;
const now = loopNow(({ stream }) => {
stream.subscribe((a) => result.push(a));
s = sinkStream();
return Now.of({ stream: s });
});
runNow(now);
- s.push("a");
- s.push("b");
- s.push("c");
+ if (s !== undefined) {
+ s.push("a");
+ s.push("b");
+ s.push("c");
+ }
assert.deepEqual(result, ["a", "b", "c"]);
});
it("should return the reactives", () => {
const result: unknown[] = [];
- let s: SinkStream;
+ let s: SinkStream | undefined;
const now = loopNow(({ stream }) => {
stream.subscribe((a) => a);
s = sinkStream();
@@ -258,9 +262,11 @@ describe("Now", () => {
});
const { stream } = runNow(now);
stream.subscribe((a) => result.push(a));
- s.push("a");
- s.push("b");
- s.push("c");
+ if (s !== undefined) {
+ s.push("a");
+ s.push("b");
+ s.push("c");
+ }
assert.deepEqual(result, ["a", "b", "c"]);
});
});
diff --git a/test/placeholder.ts b/test/placeholder.ts
index c1edd23..cb31972 100644
--- a/test/placeholder.ts
+++ b/test/placeholder.ts
@@ -24,7 +24,7 @@ import { createTestProducerBehavior } from "./helpers";
describe("placeholder", () => {
describe("behavior", () => {
it("subscribers are notified when placeholder is replaced", () => {
- let result: number;
+ let result: number | undefined;
const p = placeholder();
const mapped = p.map((s) => s.length);
mapped.subscribe((n: number) => (result = n));
@@ -32,7 +32,7 @@ describe("placeholder", () => {
assert.strictEqual(result, 5);
});
it("subscribers are notified when placeholder is replaced 2", () => {
- let result: string;
+ let result: string | undefined;
const p = placeholder();
p.subscribe((s) => (result = s));
p.replaceWith(Behavior.of("Hello"));
@@ -86,7 +86,7 @@ describe("placeholder", () => {
const mapResult: unknown[] = [];
const pm = p.map((n) => (mapResult.push(n), n));
const result: Array = [];
- let pull: (t?: number) => void;
+ let pull: ((t?: number) => void) | undefined;
observe(
(a) => {
result.push(a);
@@ -100,11 +100,13 @@ describe("placeholder", () => {
pm
);
p.replaceWith(b);
- pull();
- variable = 1;
- pull();
- variable = 2;
- pull();
+ if (pull !== undefined) {
+ pull();
+ variable = 1;
+ pull();
+ variable = 2;
+ pull();
+ }
assert.deepEqual(result, [0, 1, 2], "result");
assert.deepEqual(mapResult, [0, 1, 2], "mapResult");
});
@@ -192,7 +194,7 @@ describe("placeholder", () => {
const change = sum.map((_) => 1);
const sum2 = H.at(H.switcherFrom(H.at(H.integrateFrom(change)), H.empty));
const results: unknown[] = [];
- let pull: () => void;
+ let pull: (() => void) | undefined;
observe(
(n: number) => results.push(n),
(p) => {
@@ -202,11 +204,13 @@ describe("placeholder", () => {
sum
);
sum.replaceWith(sum2);
- pull();
- setTime(4000);
- pull();
- setTime(7000);
- pull();
+ if (pull !== undefined) {
+ pull();
+ setTime(4000);
+ pull();
+ setTime(7000);
+ pull();
+ }
assert.deepEqual(results, [0, 2000, 5000]);
restore();
});
@@ -373,14 +377,14 @@ describe("placeholder", () => {
assert.isTrue(H.isFuture(placeholder()));
});
it("subscribers are notified when replaced with occurred future", () => {
- let result: string;
+ let result: string | undefined;
const p = placeholder();
p.subscribe((n: string) => (result = n));
p.replaceWith(H.Future.of("Hello"));
assert.strictEqual(result, "Hello");
});
it("subscribers are notified when placeholder has been replaced", () => {
- let result: string;
+ let result: string | undefined;
const p = placeholder();
p.replaceWith(H.Future.of("Hello"));
p.subscribe((n: string) => (result = n));
diff --git a/test/stream.ts b/test/stream.ts
index 196c24f..d0fe06f 100644
--- a/test/stream.ts
+++ b/test/stream.ts
@@ -89,7 +89,7 @@ describe("stream", () => {
});
it("pushes to listener", () => {
const callback = spy();
- let push: (t: number, n: number) => void;
+ let push: ((t: number, n: number) => void) | undefined;
class MyProducer extends H.ProducerStream {
activate(): void {
push = this.pushS.bind(this);
@@ -100,8 +100,10 @@ describe("stream", () => {
}
const producer = new MyProducer();
producer.subscribe(callback);
- push(1, 1);
- push(2, 2);
+ if (push !== undefined) {
+ push(1, 1);
+ push(2, 2);
+ }
assert.deepEqual(callback.args, [[1], [2]]);
});
});
@@ -120,14 +122,16 @@ describe("stream", () => {
});
it("pushes to listener", () => {
const callback = spy();
- let push: (n: number) => void;
+ let push: ((n: number) => void) | undefined;
const producer = H.producerStream((p) => {
push = p;
return () => (push = undefined);
});
producer.subscribe(callback);
- push(1);
- push(2);
+ if (push !== undefined) {
+ push(1);
+ push(2);
+ }
assert.deepEqual(callback.args, [[1], [2]]);
});
});
diff --git a/test/testing.ts b/test/testing.ts
index e72f7aa..8ef2023 100644
--- a/test/testing.ts
+++ b/test/testing.ts
@@ -10,11 +10,42 @@ import {
assertStreamEqual,
testBehavior,
testNow,
- assertBehaviorEqual
+ assertBehaviorEqual,
+ neverOccurringFuture,
+ occurrence
} from "../src/testing";
import { createRef, mutateRef } from "./helpers";
import { fgo } from "@funkia/jabz";
import { withEffects } from "@funkia/io";
+import { MonadDictionary } from "@funkia/jabz/dist/monad";
+import { F1, F2, F3, F4, F5 } from "@funkia/jabz/dist/utils";
+
+declare module "@funkia/jabz" {
+ export function fgo(
+ gen: (a: A) => Generator,
+ monad?: MonadDictionary
+ ): F1;
+ export function fgo(
+ gen: (a: A, b: B) => Generator,
+ monad?: MonadDictionary
+ ): F2;
+ export function fgo(
+ gen: (a: A, b: B, c: C) => Generator,
+ monad?: MonadDictionary
+ ): F3;
+ export function fgo(
+ gen: (a: A, b: B, c: C, d: D) => Generator,
+ monad?: MonadDictionary
+ ): F4;
+ export function fgo(
+ gen: (a: A, b: B, c: C, d: D, e: E) => Generator,
+ monad?: MonadDictionary
+ ): F5;
+ export function fgo(
+ gen: (...a: any[]) => Generator,
+ monad?: MonadDictionary
+ ): any;
+}
describe("testing", () => {
describe("future", () => {
@@ -59,7 +90,7 @@ describe("testing", () => {
});
describe("never", () => {
it("never occurs", () => {
- assert.strictEqual(H.never.model().time, "infinity");
+ assert.strictEqual(H.never.model(), neverOccurringFuture);
});
});
describe("lift", () => {
@@ -110,10 +141,10 @@ describe("testing", () => {
it("creates test stream with increasing times from array", () => {
const s = testStreamFromArray([[0, 0], [1, 1], [2, 2], [3, 3]]);
assert.deepEqual(s.model(), [
- { value: 0, time: 0 },
- { value: 1, time: 1 },
- { value: 2, time: 2 },
- { value: 3, time: 3 }
+ occurrence(0, 0),
+ occurrence(1, 1),
+ occurrence(2, 2),
+ occurrence(3, 3)
]);
});
it("creates test stream from object", () => {
@@ -123,9 +154,9 @@ describe("testing", () => {
5.5: "three"
});
assert.deepEqual(s.model(), [
- { value: "one", time: 2 },
- { value: "two", time: 4 },
- { value: "three", time: 5.5 }
+ occurrence(2, "one"),
+ occurrence(4, "two"),
+ occurrence(5.5, "three")
]);
});
});
@@ -156,11 +187,11 @@ describe("testing", () => {
const s2 = testStreamFromObject({ 1: "#2", 2: "#4", 3: "#5" });
const combined = s2.combine(s1);
assert.deepEqual(combined.model(), [
- { time: 0, value: "#1" },
- { time: 1, value: "#2" },
- { time: 2, value: "#3" },
- { time: 2, value: "#4" },
- { time: 3, value: "#5" }
+ occurrence(0, "#1"),
+ occurrence(1, "#2"),
+ occurrence(2, "#3"),
+ occurrence(2, "#4"),
+ occurrence(3, "#5")
]);
});
});
@@ -344,7 +375,7 @@ describe("testing", () => {
const ref1 = createRef(1);
const comp = H.performIO(mutateRef(2, ref1));
const result = testNow(comp, [testFuture(0, "foo")]);
- assert(result.model().value, "foo");
+ assert.deepStrictEqual(result.model(), occurrence(0, "foo"));
});
});
describe("sample", () => {
@@ -370,6 +401,9 @@ describe("testing", () => {
describe("performStream", () => {
it("can be tested", () => {
const requests: number[] = [];
+ interface Foo {
+ readonly click: Stream;
+ }
const model = fgo(function*({ click }) {
const request = click.mapTo(
withEffects((n: number) => {
diff --git a/test/time.ts b/test/time.ts
index 44a41e2..1bf04f6 100644
--- a/test/time.ts
+++ b/test/time.ts
@@ -77,7 +77,7 @@ describe("behavior", () => {
const [setTime, restore] = mockNow();
setTime(3);
const time = H.runNow(H.measureTime);
- let pull: (t?: number) => void;
+ let pull: ((t?: number) => void) | undefined;
const results: number[] = [];
H.observe(
(n: number) => {
@@ -89,11 +89,13 @@ describe("behavior", () => {
},
time
);
- pull();
- setTime(4);
- pull();
- setTime(7);
- pull();
+ if (pull !== undefined) {
+ pull();
+ setTime(4);
+ pull();
+ setTime(7);
+ pull();
+ }
assert.deepEqual(results, [0, 1, 4]);
restore();
});
diff --git a/tsconfig.json b/tsconfig.json
index 5b71b67..99c6943 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,7 +7,9 @@
"importHelpers": true,
"sourceMap": true,
"skipLibCheck": true,
- "lib": ["dom", "es5", "es2015", "es2019"]
+ "lib": ["dom", "es5", "es2015", "es2019"],
+ "strictNullChecks": true,
+ "strictPropertyInitialization": true
},
"include": ["src/**/*", "test/**/*"],
"exclude": ["node_modules"]