Repository: mostjs/core
Pull request #207 (2017-12-22 02:13:13)

make awaitPromises return canonical empty
See #152


Patch:
diff --git a/packages/core/src/combinator/mergeConcurrently.js b/packages/core/src/combinator/mergeConcurrently.js
index 8d2fafc..6ff450e 100644
--- a/packages/core/src/combinator/mergeConcurrently.js
+++ b/packages/core/src/combinator/mergeConcurrently.js
@@ -1,9 +1,8 @@
-/** @license MIT License (c) copyright 2010-2016 original author or authors */
-/** @author Brian Cavalier */
-/** @author John Hann */
+/** @license MIT License (c) copyright 2010 original author or authors */
 
 import { disposeOnce, tryDispose } from '@most/disposable'
 import LinkedList from '../LinkedList'
+import { empty, isCanonicalEmpty } from '../source/empty'
 import { id as identity } from '@most/prelude'
 import { schedulerRelativeTo } from '@most/scheduler'
 
@@ -11,7 +10,8 @@ export const mergeConcurrently = (concurrency, stream) =>
   mergeMapConcurrently(identity, concurrency, stream)
 
 export const mergeMapConcurrently = (f, concurrency, stream) =>
-  new MergeConcurrently(f, concurrency, stream)
+  isCanonicalEmpty(stream) ? empty()
+    : new MergeConcurrently(f, concurrency, stream)
 
 class MergeConcurrently {
   constructor (f, concurrency, source) {
diff --git a/packages/core/src/combinator/promises.js b/packages/core/src/combinator/promises.js
index 2f2b402..b3f8ffb 100644
--- a/packages/core/src/combinator/promises.js
+++ b/packages/core/src/combinator/promises.js
@@ -4,6 +4,7 @@
 
 import fatal from '../fatalError'
 import { now } from '../source/now'
+import { empty, isCanonicalEmpty } from '../source/empty'
 import { compose } from '@most/prelude'
 import { currentTime } from '@most/scheduler'
 
@@ -11,7 +12,8 @@ import { currentTime } from '@most/scheduler'
  * Turn a Stream<Promise<T>> into Stream<T> by awaiting each promise.
  * Event order is preserved. The stream will fail if any promise rejects.
  */
-export const awaitPromises = stream => new Await(stream)
+export const awaitPromises = stream =>
+  isCanonicalEmpty(stream) ? empty() : new Await(stream)
 
 /**
  * Create a stream containing only the promise's fulfillment
diff --git a/packages/core/src/combinator/switch.js b/packages/core/src/combinator/switch.js
index c6cbbad..aea3787 100644
--- a/packages/core/src/combinator/switch.js
+++ b/packages/core/src/combinator/switch.js
@@ -4,6 +4,7 @@
 
 import { disposeBoth, tryDispose } from '@most/disposable'
 import { schedulerRelativeTo, currentTime } from '@most/scheduler'
+import { empty, isCanonicalEmpty } from '../source/empty'
 
 /**
  * Given a stream of streams, return a new stream that adopts the behavior
@@ -11,7 +12,10 @@ import { schedulerRelativeTo, currentTime } from '@most/scheduler'
  * @param {Stream} stream of streams on which to switch
  * @returns {Stream} switching stream
  */
-export const switchLatest = stream => new Switch(stream)
+export const switchLatest = stream =>
+  isCanonicalEmpty(stream)
+    ? empty()
+    : new Switch(stream)
 
 class Switch {
   constructor (source) {


Test patch:
diff --git a/packages/core/test/combinator/mergeConcurrently-test.js b/packages/core/test/combinator/mergeConcurrently-test.js
index ca53b8f..3b477d7 100644
--- a/packages/core/test/combinator/mergeConcurrently-test.js
+++ b/packages/core/test/combinator/mergeConcurrently-test.js
@@ -1,11 +1,13 @@
 import { describe, it } from 'mocha'
-import { fail, is, eq } from '@briancavalier/assert'
+import { assert, fail, is, eq } from '@briancavalier/assert'
+import { id } from '@most/prelude'
 
 import { mergeMapConcurrently, mergeConcurrently } from '../../src/combinator/mergeConcurrently'
 import { periodic } from '../../src/source/periodic'
 import { take } from '../../src/combinator/slice'
 import { constant } from '../../src/combinator/transform'
 import { now } from '../../src/source/now'
+import { empty, isCanonicalEmpty } from '../../src/source/empty'
 import { collectEventsFor, makeEventsFromArray } from '../helper/testEnv'
 
 const sentinel = { value: 'sentinel' }
@@ -13,6 +15,11 @@ const sentinel = { value: 'sentinel' }
 const periodicConstant = (ms, x) => constant(x, periodic(ms))
 
 describe('mergeConcurrently', () => {
+  it('given canonical empty stream, should return canonical empty', () => {
+    const s = mergeConcurrently(Math.floor(Math.random() * 10), empty())
+    assert(isCanonicalEmpty(s))
+  })
+
   it('should be identity for 1 stream', () => {
     const s = mergeConcurrently(1, now(periodicConstant(1, sentinel)))
     const n = 3
@@ -73,6 +80,11 @@ describe('mergeConcurrently', () => {
 })
 
 describe('mergeMapConcurrently', () => {
+  it('given canonical empty stream, should return canonical empty', () => {
+    const s = mergeMapConcurrently(id, Math.floor(Math.random() * 10), empty())
+    assert(isCanonicalEmpty(s))
+  })
+
   it('when mapping function throws, it should catch and propagate error', () => {
     const error = new Error()
     const s = mergeMapConcurrently(x => { throw error }, 1, now(0))
diff --git a/packages/core/test/combinator/promises-test.js b/packages/core/test/combinator/promises-test.js
index 0a9e7e3..9aa4de8 100644
--- a/packages/core/test/combinator/promises-test.js
+++ b/packages/core/test/combinator/promises-test.js
@@ -1,8 +1,9 @@
 import { describe, it } from 'mocha'
-import { is, eq } from '@briancavalier/assert'
+import { assert, is, eq } from '@briancavalier/assert'
 
 import { awaitPromises, fromPromise } from '../../src/combinator/promises'
 import { recoverWith } from '../../src/combinator/errors'
+import { empty, isCanonicalEmpty } from '../../src/source/empty'
 
 import { atTime, makeEventsFromArray, collectEventsFor } from '../helper/testEnv'
 
@@ -21,6 +22,11 @@ const rejected = e => {
 
 describe('promises', () => {
   describe('awaitPromises', function () {
+    it('given canonical empty stream, should return canonical empty', () => {
+      const s = awaitPromises(empty())
+      assert(isCanonicalEmpty(s))
+    })
+
     it('should await promises', function () {
       const s = awaitPromises(atTime(0, Promise.resolve(sentinel)))
 
diff --git a/packages/core/test/switch-test.js b/packages/core/test/switch-test.js
index 264e654..1338fd4 100644
--- a/packages/core/test/switch-test.js
+++ b/packages/core/test/switch-test.js
@@ -1,18 +1,23 @@
 import { describe, it } from 'mocha'
-import { eq, fail } from '@briancavalier/assert'
+import { assert, eq, fail } from '@briancavalier/assert'
 
 import { switchLatest } from '../src/combinator/switch'
 import { take } from '../src/combinator/slice'
 import { constant, map, tap } from '../src/combinator/transform'
 import { periodic } from '../src/source/periodic'
-import { empty } from '../src/source/empty'
+import { empty, isCanonicalEmpty } from '../src/source/empty'
 import { now } from '../src/source/now'
 import { ticks, collectEventsFor, makeEvents, makeEventsFromArray } from './helper/testEnv'
 import { runEffects } from '../src/runEffects'
 
 describe('switch', () => {
+  it('given c anonical empty string, should return canonical empty', () => {
+    const s = switchLatest(empty())
+    assert(isCanonicalEmpty(s))
+  })
+
   describe('when input is empty', () => {
-    it('should return empty', function () {
+    it('should return empty', () => {
       return runEffects(tap(fail, switchLatest(empty())), ticks(1))
     })
   })