Repository: mostjs/core
Pull request #206 (2017-12-22 01:55:16)

make switchLatest 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/multicast.js b/packages/core/src/combinator/multicast.js
index 0faee9c..269fe08 100644
--- a/packages/core/src/combinator/multicast.js
+++ b/packages/core/src/combinator/multicast.js
@@ -1,9 +1,12 @@
 import { append, findIndex, remove } from '@most/prelude'
 import { disposeNone, disposeOnce } from '@most/disposable'
 import { tryEnd, tryEvent } from '../source/tryEvent'
+import { isCanonicalEmpty } from '../source/empty'
 
 export const multicast = stream =>
-  stream instanceof Multicast ? stream : new Multicast(stream)
+  stream instanceof Multicast || isCanonicalEmpty(stream)
+    ? stream
+    : new Multicast(stream)
 
 class Multicast {
   constructor (source) {
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/multicast-test.js b/packages/core/test/combinator/multicast-test.js
index 85c83e9..bbf0c07 100644
--- a/packages/core/test/combinator/multicast-test.js
+++ b/packages/core/test/combinator/multicast-test.js
@@ -6,6 +6,7 @@ import { map, tap } from '../../src/combinator/transform'
 
 import FakeDisposeStream from '../helper/FakeDisposeStream'
 import { now } from '../../src/source/now'
+import { empty, isCanonicalEmpty } from '../../src/source/empty'
 import { runEffects } from '../../src/runEffects'
 import { sinkSpy } from '../helper/sinkSpy'
 import { spy } from 'sinon'
@@ -16,6 +17,11 @@ describe('multicast', () => {
     eq(s, multicast(s))
   })
 
+  it('given canonical empty stream, should return canonical empty', () => {
+    const s = multicast(empty())
+    assert(isCanonicalEmpty(s))
+  })
+
   it('should call mapper function once when there are > 1 observer', () => {
     const observer1Spy = spy()
     const observer2Spy = spy()
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))
     })
   })