Initial Sample.

This commit is contained in:
2024-06-03 20:23:50 +05:30
parent ef2b65f673
commit 5269ec3c66
2575 changed files with 282312 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
import assert from 'assert';
describe('concat', () => {
it('concatenates the supplied Observable arguments', async () => {
let list = [];
await Observable
.from([1, 2, 3, 4])
.concat(Observable.of(5, 6, 7))
.forEach(x => list.push(x));
assert.deepEqual(list, [1, 2, 3, 4, 5, 6, 7]);
});
it('can be used multiple times to produce the same results', async () => {
const list1 = [];
const list2 = [];
const concatenated = Observable.from([1, 2, 3, 4])
.concat(Observable.of(5, 6, 7));
await concatenated
.forEach(x => list1.push(x));
await concatenated
.forEach(x => list2.push(x));
assert.deepEqual(list1, [1, 2, 3, 4, 5, 6, 7]);
assert.deepEqual(list2, [1, 2, 3, 4, 5, 6, 7]);
});
});

View File

@@ -0,0 +1,36 @@
import assert from 'assert';
import { testMethodProperty } from './properties.js';
describe('constructor', () => {
it('throws if called as a function', () => {
assert.throws(() => Observable(() => {}));
assert.throws(() => Observable.call({}, () => {}));
});
it('throws if the argument is not callable', () => {
assert.throws(() => new Observable({}));
assert.throws(() => new Observable());
assert.throws(() => new Observable(1));
assert.throws(() => new Observable('string'));
});
it('accepts a function argument', () => {
let result = new Observable(() => {});
assert.ok(result instanceof Observable);
});
it('is the value of Observable.prototype.constructor', () => {
testMethodProperty(Observable.prototype, 'constructor', {
configurable: true,
writable: true,
length: 1,
});
});
it('does not call the subscriber function', () => {
let called = 0;
new Observable(() => { called++ });
assert.equal(called, 0);
});
});

View File

@@ -0,0 +1,43 @@
import assert from 'assert';
import { parse } from './parse.js';
import { combineLatest } from '../../src/extras.js';
describe('extras/combineLatest', () => {
it('should emit arrays containing the most recent values', async () => {
let output = [];
await combineLatest(
parse('a-b-c-d'),
parse('-A-B-C-D')
).forEach(
value => output.push(value.join(''))
);
assert.deepEqual(output, [
'aA',
'bA',
'bB',
'cB',
'cC',
'dC',
'dD',
]);
});
it('should emit values in the correct order', async () => {
let output = [];
await combineLatest(
parse('-a-b-c-d'),
parse('A-B-C-D')
).forEach(
value => output.push(value.join(''))
);
assert.deepEqual(output, [
'aA',
'aB',
'bB',
'bC',
'cC',
'cD',
'dD',
]);
});
});

View File

@@ -0,0 +1,16 @@
import assert from 'assert';
import { parse } from './parse.js';
import { merge } from '../../src/extras.js';
describe('extras/merge', () => {
it('should emit all data from each input in parallel', async () => {
let output = '';
await merge(
parse('a-b-c-d'),
parse('-A-B-C-D')
).forEach(
value => output += value
);
assert.equal(output, 'aAbBcCdD');
});
});

View File

@@ -0,0 +1,11 @@
export function parse(string) {
return new Observable(async observer => {
await null;
for (let char of string) {
if (observer.closed) return;
else if (char !== '-') observer.next(char);
await null;
}
observer.complete();
});
}

View File

@@ -0,0 +1,21 @@
import assert from 'assert';
import { parse } from './parse.js';
import { zip } from '../../src/extras.js';
describe('extras/zip', () => {
it('should emit pairs of corresponding index values', async () => {
let output = [];
await zip(
parse('a-b-c-d'),
parse('-A-B-C-D')
).forEach(
value => output.push(value.join(''))
);
assert.deepEqual(output, [
'aA',
'bB',
'cC',
'dD',
]);
});
});

View File

@@ -0,0 +1,14 @@
import assert from 'assert';
describe('filter', () => {
it('filters the results using the supplied callback', async () => {
let list = [];
await Observable
.from([1, 2, 3, 4])
.filter(x => x > 2)
.forEach(x => list.push(x));
assert.deepEqual(list, [3, 4]);
});
});

View File

@@ -0,0 +1,23 @@
import assert from 'assert';
describe('flatMap', () => {
it('maps and flattens the results using the supplied callback', async () => {
let list = [];
await Observable.of('a', 'b', 'c').flatMap(x =>
Observable.of(1, 2, 3).map(y => [x, y])
).forEach(x => list.push(x));
assert.deepEqual(list, [
['a', 1],
['a', 2],
['a', 3],
['b', 1],
['b', 2],
['b', 3],
['c', 1],
['c', 2],
['c', 3],
]);
});
});

View File

@@ -0,0 +1,70 @@
import assert from 'assert';
describe('forEach', () => {
it('rejects if the argument is not a function', async () => {
let promise = Observable.of(1, 2, 3).forEach();
try {
await promise;
assert.ok(false);
} catch (err) {
assert.equal(err.name, 'TypeError');
}
});
it('rejects if the callback throws', async () => {
let error = {};
try {
await Observable.of(1, 2, 3).forEach(x => { throw error });
assert.ok(false);
} catch (err) {
assert.equal(err, error);
}
});
it('does not execute callback after callback throws', async () => {
let calls = [];
try {
await Observable.of(1, 2, 3).forEach(x => {
calls.push(x);
throw {};
});
assert.ok(false);
} catch (err) {
assert.deepEqual(calls, [1]);
}
});
it('rejects if the producer calls error', async () => {
let error = {};
try {
let observer;
let promise = new Observable(x => { observer = x }).forEach(() => {});
observer.error(error);
await promise;
assert.ok(false);
} catch (err) {
assert.equal(err, error);
}
});
it('resolves with undefined if the producer calls complete', async () => {
let observer;
let promise = new Observable(x => { observer = x }).forEach(() => {});
observer.complete();
assert.equal(await promise, undefined);
});
it('provides a cancellation function as the second argument', async () => {
let observer;
let results = [];
await Observable.of(1, 2, 3).forEach((value, cancel) => {
results.push(value);
if (value > 1) {
return cancel();
}
});
assert.deepEqual(results, [1, 2]);
});
});

View File

@@ -0,0 +1,95 @@
import assert from 'assert';
import { testMethodProperty } from './properties.js';
describe('from', () => {
const iterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
},
};
it('is a method on Observable', () => {
testMethodProperty(Observable, 'from', {
configurable: true,
writable: true,
length: 1,
});
});
it('throws if the argument is null', () => {
assert.throws(() => Observable.from(null));
});
it('throws if the argument is undefined', () => {
assert.throws(() => Observable.from(undefined));
});
it('throws if the argument is not observable or iterable', () => {
assert.throws(() => Observable.from({}));
});
describe('observables', () => {
it('returns the input if the constructor matches "this"', () => {
let ctor = function() {};
let observable = new Observable(() => {});
observable.constructor = ctor;
assert.equal(Observable.from.call(ctor, observable), observable);
});
it('wraps the input if it is not an instance of Observable', () => {
let obj = {
'constructor': Observable,
[Symbol.observable]() { return this },
};
assert.ok(Observable.from(obj) !== obj);
});
it('throws if @@observable property is not a method', () => {
assert.throws(() => Observable.from({
[Symbol.observable]: 1
}));
});
it('returns an observable wrapping @@observable result', () => {
let inner = {
subscribe(x) {
observer = x;
return () => { cleanupCalled = true };
},
};
let observer;
let cleanupCalled = true;
let observable = Observable.from({
[Symbol.observable]() { return inner },
});
observable.subscribe();
assert.equal(typeof observer.next, 'function');
observer.complete();
assert.equal(cleanupCalled, true);
});
});
describe('iterables', () => {
it('throws if @@iterator is not a method', () => {
assert.throws(() => Observable.from({ [Symbol.iterator]: 1 }));
});
it('returns an observable wrapping iterables', async () => {
let calls = [];
let subscription = Observable.from(iterable).subscribe({
next(v) { calls.push(['next', v]) },
complete() { calls.push(['complete']) },
});
assert.deepEqual(calls, []);
await null;
assert.deepEqual(calls, [
['next', 1],
['next', 2],
['next', 3],
['complete'],
]);
});
});
});

View File

@@ -0,0 +1,13 @@
import assert from 'assert';
describe('map', () => {
it('maps the results using the supplied callback', async () => {
let list = [];
await Observable.from([1, 2, 3])
.map(x => x * 2)
.forEach(x => list.push(x));
assert.deepEqual(list, [2, 4, 6]);
});
});

View File

@@ -0,0 +1,35 @@
import assert from 'assert';
import { testMethodProperty } from './properties.js';
describe('observer.closed', () => {
it('is a getter on SubscriptionObserver.prototype', () => {
let observer;
new Observable(x => { observer = x }).subscribe();
testMethodProperty(Object.getPrototypeOf(observer), 'closed', {
get: true,
configurable: true,
writable: true,
length: 1
});
});
it('returns false when the subscription is open', () => {
new Observable(observer => {
assert.equal(observer.closed, false);
}).subscribe();
});
it('returns true when the subscription is completed', () => {
let observer;
new Observable(x => { observer = x; }).subscribe();
observer.complete();
assert.equal(observer.closed, true);
});
it('returns true when the subscription is errored', () => {
let observer;
new Observable(x => { observer = x; }).subscribe(null, () => {});
observer.error();
assert.equal(observer.closed, true);
});
});

View File

@@ -0,0 +1,143 @@
import assert from 'assert';
import { testMethodProperty } from './properties.js';
describe('observer.complete', () => {
function getObserver(inner) {
let observer;
new Observable(x => { observer = x }).subscribe(inner);
return observer;
}
it('is a method of SubscriptionObserver', () => {
let observer = getObserver();
testMethodProperty(Object.getPrototypeOf(observer), 'complete', {
configurable: true,
writable: true,
length: 0,
});
});
it('does not forward arguments', () => {
let args;
let observer = getObserver({ complete(...a) { args = a } });
observer.complete(1);
assert.deepEqual(args, []);
});
it('does not return a value', () => {
let observer = getObserver({ complete() { return 1 } });
assert.equal(observer.complete(), undefined);
});
it('does not forward when the subscription is complete', () => {
let count = 0;
let observer = getObserver({ complete() { count++ } });
observer.complete();
observer.complete();
assert.equal(count, 1);
});
it('does not forward when the subscription is cancelled', () => {
let count = 0;
let observer;
let subscription = new Observable(x => { observer = x }).subscribe({
complete() { count++ },
});
subscription.unsubscribe();
observer.complete();
assert.equal(count, 0);
});
it('queues if the subscription is not initialized', async () => {
let completed = false;
new Observable(x => { x.complete() }).subscribe({
complete() { completed = true },
});
assert.equal(completed, false);
await null;
assert.equal(completed, true);
});
it('queues if the observer is running', async () => {
let observer;
let completed = false
new Observable(x => { observer = x }).subscribe({
next() { observer.complete() },
complete() { completed = true },
});
observer.next();
assert.equal(completed, false);
await null;
assert.equal(completed, true);
});
it('closes the subscription before invoking inner observer', () => {
let closed;
let observer = getObserver({
complete() { closed = observer.closed },
});
observer.complete();
assert.equal(closed, true);
});
it('reports error if "complete" is not a method', () => {
let observer = getObserver({ complete: 1 });
observer.complete();
assert.ok(hostError instanceof Error);
});
it('does not report error if "complete" is undefined', () => {
let observer = getObserver({ complete: undefined });
observer.complete();
assert.ok(!hostError);
});
it('does not report error if "complete" is null', () => {
let observer = getObserver({ complete: null });
observer.complete();
assert.ok(!hostError);
});
it('reports error if "complete" throws', () => {
let error = {};
let observer = getObserver({ complete() { throw error } });
observer.complete();
assert.equal(hostError, error);
});
it('calls the cleanup method after "complete"', () => {
let calls = [];
let observer;
new Observable(x => {
observer = x;
return () => { calls.push('cleanup') };
}).subscribe({
complete() { calls.push('complete') },
});
observer.complete();
assert.deepEqual(calls, ['complete', 'cleanup']);
});
it('calls the cleanup method if there is no "complete"', () => {
let calls = [];
let observer;
new Observable(x => {
observer = x;
return () => { calls.push('cleanup') };
}).subscribe({});
observer.complete();
assert.deepEqual(calls, ['cleanup']);
});
it('reports error if the cleanup function throws', () => {
let error = {};
let observer;
new Observable(x => {
observer = x;
return () => { throw error };
}).subscribe();
observer.complete();
assert.equal(hostError, error);
});
});

View File

@@ -0,0 +1,145 @@
import assert from 'assert';
import { testMethodProperty } from './properties.js';
describe('observer.error', () => {
function getObserver(inner) {
let observer;
new Observable(x => { observer = x }).subscribe(inner);
return observer;
}
it('is a method of SubscriptionObserver', () => {
let observer = getObserver();
testMethodProperty(Object.getPrototypeOf(observer), 'error', {
configurable: true,
writable: true,
length: 1,
});
});
it('forwards the argument', () => {
let args;
let observer = getObserver({ error(...a) { args = a } });
observer.error(1);
assert.deepEqual(args, [1]);
});
it('does not return a value', () => {
let observer = getObserver({ error() { return 1 } });
assert.equal(observer.error(), undefined);
});
it('does not throw when the subscription is complete', () => {
let observer = getObserver({ error() {} });
observer.complete();
observer.error('error');
});
it('does not throw when the subscription is cancelled', () => {
let observer;
let subscription = new Observable(x => { observer = x }).subscribe({
error() {},
});
subscription.unsubscribe();
observer.error(1);
assert.ok(!hostError);
});
it('queues if the subscription is not initialized', async () => {
let error;
new Observable(x => { x.error({}) }).subscribe({
error(err) { error = err },
});
assert.equal(error, undefined);
await null;
assert.ok(error);
});
it('queues if the observer is running', async () => {
let observer;
let error;
new Observable(x => { observer = x }).subscribe({
next() { observer.error({}) },
error(e) { error = e },
});
observer.next();
assert.ok(!error);
await null;
assert.ok(error);
});
it('closes the subscription before invoking inner observer', () => {
let closed;
let observer = getObserver({
error() { closed = observer.closed },
});
observer.error(1);
assert.equal(closed, true);
});
it('reports an error if "error" is not a method', () => {
let observer = getObserver({ error: 1 });
observer.error(1);
assert.ok(hostError);
});
it('reports an error if "error" is undefined', () => {
let error = {};
let observer = getObserver({ error: undefined });
observer.error(error);
assert.equal(hostError, error);
});
it('reports an error if "error" is null', () => {
let error = {};
let observer = getObserver({ error: null });
observer.error(error);
assert.equal(hostError, error);
});
it('reports error if "error" throws', () => {
let error = {};
let observer = getObserver({ error() { throw error } });
observer.error(1);
assert.equal(hostError, error);
});
it('calls the cleanup method after "error"', () => {
let calls = [];
let observer;
new Observable(x => {
observer = x;
return () => { calls.push('cleanup') };
}).subscribe({
error() { calls.push('error') },
});
observer.error();
assert.deepEqual(calls, ['error', 'cleanup']);
});
it('calls the cleanup method if there is no "error"', () => {
let calls = [];
let observer;
new Observable(x => {
observer = x;
return () => { calls.push('cleanup') };
}).subscribe({});
try {
observer.error();
} catch (err) {}
assert.deepEqual(calls, ['cleanup']);
});
it('reports error if the cleanup function throws', () => {
let error = {};
let observer;
new Observable(x => {
observer = x;
return () => { throw error };
}).subscribe();
observer.error(1);
assert.equal(hostError, error);
});
});

View File

@@ -0,0 +1,137 @@
import assert from 'assert';
import { testMethodProperty } from './properties.js';
describe('observer.next', () => {
function getObserver(inner) {
let observer;
new Observable(x => { observer = x }).subscribe(inner);
return observer;
}
it('is a method of SubscriptionObserver', () => {
let observer = getObserver();
testMethodProperty(Object.getPrototypeOf(observer), 'next', {
configurable: true,
writable: true,
length: 1,
});
});
it('forwards the first argument', () => {
let args;
let observer = getObserver({ next(...a) { args = a } });
observer.next(1, 2);
assert.deepEqual(args, [1]);
});
it('does not return a value', () => {
let observer = getObserver({ next() { return 1 } });
assert.equal(observer.next(), undefined);
});
it('does not forward when the subscription is complete', () => {
let count = 0;
let observer = getObserver({ next() { count++ } });
observer.complete();
observer.next();
assert.equal(count, 0);
});
it('does not forward when the subscription is cancelled', () => {
let count = 0;
let observer;
let subscription = new Observable(x => { observer = x }).subscribe({
next() { count++ },
});
subscription.unsubscribe();
observer.next();
assert.equal(count, 0);
});
it('remains closed if the subscription is cancelled from "next"', () => {
let observer;
let subscription = new Observable(x => { observer = x }).subscribe({
next() { subscription.unsubscribe() },
});
observer.next();
assert.equal(observer.closed, true);
});
it('queues if the subscription is not initialized', async () => {
let values = [];
let observer;
new Observable(x => { observer = x, x.next(1) }).subscribe({
next(val) {
values.push(val);
if (val === 1) {
observer.next(3);
}
},
});
observer.next(2);
assert.deepEqual(values, []);
await null;
assert.deepEqual(values, [1, 2]);
await null;
assert.deepEqual(values, [1, 2, 3]);
});
it('drops queue if subscription is closed', async () => {
let values = [];
let subscription = new Observable(x => { x.next(1) }).subscribe({
next(val) { values.push(val) },
});
assert.deepEqual(values, []);
subscription.unsubscribe();
await null;
assert.deepEqual(values, []);
});
it('queues if the observer is running', async () => {
let observer;
let values = [];
new Observable(x => { observer = x }).subscribe({
next(val) {
values.push(val);
if (val === 1) observer.next(2);
},
});
observer.next(1);
assert.deepEqual(values, [1]);
await null;
assert.deepEqual(values, [1, 2]);
});
it('reports error if "next" is not a method', () => {
let observer = getObserver({ next: 1 });
observer.next();
assert.ok(hostError);
});
it('does not report error if "next" is undefined', () => {
let observer = getObserver({ next: undefined });
observer.next();
assert.ok(!hostError);
});
it('does not report error if "next" is null', () => {
let observer = getObserver({ next: null });
observer.next();
assert.ok(!hostError);
});
it('reports error if "next" throws', () => {
let error = {};
let observer = getObserver({ next() { throw error } });
observer.next();
assert.equal(hostError, error);
});
it('does not close the subscription on error', () => {
let observer = getObserver({ next() { throw {} } });
observer.next();
assert.equal(observer.closed, false);
});
});

View File

@@ -0,0 +1,32 @@
import assert from 'assert';
import { testMethodProperty } from './properties.js';
describe('of', () => {
it('is a method on Observable', () => {
testMethodProperty(Observable, 'of', {
configurable: true,
writable: true,
length: 0,
});
});
it('uses the this value if it is a function', () => {
let usesThis = false;
Observable.of.call(function() { usesThis = true; });
assert.ok(usesThis);
});
it('uses Observable if the this value is not a function', () => {
let result = Observable.of.call({}, 1, 2, 3, 4);
assert.ok(result instanceof Observable);
});
it('delivers arguments to next in a job', async () => {
let values = [];
let turns = 0;
Observable.of(1, 2, 3, 4).subscribe(v => values.push(v));
assert.equal(values.length, 0);
await null;
assert.deepEqual(values, [1, 2, 3, 4]);
});
});

View File

@@ -0,0 +1,31 @@
import assert from 'assert';
export function testMethodProperty(object, key, options) {
let desc = Object.getOwnPropertyDescriptor(object, key);
let { enumerable = false, configurable = false, writable = false, length } = options;
assert.ok(desc, `Property ${ key.toString() } exists`);
if (options.get || options.set) {
if (options.get) {
assert.equal(typeof desc.get, 'function', 'Getter is a function');
assert.equal(desc.get.length, 0, 'Getter length is 0');
} else {
assert.equal(desc.get, undefined, 'Getter is undefined');
}
if (options.set) {
assert.equal(typeof desc.set, 'function', 'Setter is a function');
assert.equal(desc.set.length, 1, 'Setter length is 1');
} else {
assert.equal(desc.set, undefined, 'Setter is undefined');
}
} else {
assert.equal(typeof desc.value, 'function', 'Value is a function');
assert.equal(desc.value.length, length, `Function length is ${ length }`);
assert.equal(desc.writable, writable, `Writable property is correct ${ writable }`);
}
assert.equal(desc.enumerable, enumerable, `Enumerable property is ${ enumerable }`);
assert.equal(desc.configurable, configurable, `Configurable property is ${ configurable }`);
}

View File

@@ -0,0 +1,38 @@
import assert from 'assert';
describe('reduce', () => {
it('reduces without a seed', async () => {
await Observable.from([1, 2, 3, 4, 5, 6]).reduce((a, b) => {
return a + b;
}).forEach(x => {
assert.equal(x, 21);
});
});
it('errors if empty and no seed', async () => {
try {
await Observable.from([]).reduce((a, b) => {
return a + b;
}).forEach(() => null);
assert.ok(false);
} catch (err) {
assert.ok(true);
}
});
it('reduces with a seed', async () => {
Observable.from([1, 2, 3, 4, 5, 6]).reduce((a, b) => {
return a + b;
}, 100).forEach(x => {
assert.equal(x, 121);
});
});
it('reduces an empty list with a seed', async () => {
await Observable.from([]).reduce((a, b) => {
return a + b;
}, 100).forEach(x => {
assert.equal(x, 100);
});
});
});

View File

@@ -0,0 +1,9 @@
import { Observable } from '../src/Observable.js';
beforeEach(() => {
global.Observable = Observable;
global.hostError = null;
let $extensions = Object.getOwnPropertySymbols(Observable)[1];
let { hostReportError } = Observable[$extensions];
hostReportError.log = (e => global.hostError = e);
});

View File

@@ -0,0 +1,28 @@
import assert from 'assert';
describe('species', () => {
it('uses Observable when constructor is undefined', () => {
let instance = new Observable(() => {});
instance.constructor = undefined;
assert.ok(instance.map(x => x) instanceof Observable);
});
it('uses Observable if species is null', () => {
let instance = new Observable(() => {});
instance.constructor = { [Symbol.species]: null };
assert.ok(instance.map(x => x) instanceof Observable);
});
it('uses Observable if species is undefined', () => {
let instance = new Observable(() => {});
instance.constructor = { [Symbol.species]: undefined };
assert.ok(instance.map(x => x) instanceof Observable);
});
it('uses value of Symbol.species', () => {
function ctor() {}
let instance = new Observable(() => {});
instance.constructor = { [Symbol.species]: ctor };
assert.ok(instance.map(x => x) instanceof ctor);
});
});

View File

@@ -0,0 +1,137 @@
import assert from 'assert';
import { testMethodProperty } from './properties.js';
describe('subscribe', () => {
it('is a method of Observable.prototype', () => {
testMethodProperty(Observable.prototype, 'subscribe', {
configurable: true,
writable: true,
length: 1,
});
});
it('accepts an observer argument', () => {
let observer;
let nextValue;
new Observable(x => observer = x).subscribe({
next(v) { nextValue = v },
});
observer.next(1);
assert.equal(nextValue, 1);
});
it('accepts a next function argument', () => {
let observer;
let nextValue;
new Observable(x => observer = x).subscribe(
v => nextValue = v
);
observer.next(1);
assert.equal(nextValue, 1);
});
it('accepts an error function argument', () => {
let observer;
let errorValue;
let error = {};
new Observable(x => observer = x).subscribe(
null,
e => errorValue = e
);
observer.error(error);
assert.equal(errorValue, error);
});
it('accepts a complete function argument', () => {
let observer;
let completed = false;
new Observable(x => observer = x).subscribe(
null,
null,
() => completed = true
);
observer.complete();
assert.equal(completed, true);
});
it('uses function overload if first argument is null', () => {
let observer;
let completed = false;
new Observable(x => observer = x).subscribe(
null,
null,
() => completed = true
);
observer.complete();
assert.equal(completed, true);
});
it('uses function overload if first argument is undefined', () => {
let observer;
let completed = false;
new Observable(x => observer = x).subscribe(
undefined,
null,
() => completed = true
);
observer.complete();
assert.equal(completed, true);
});
it('uses function overload if first argument is a primative', () => {
let observer;
let completed = false;
new Observable(x => observer = x).subscribe(
'abc',
null,
() => completed = true
);
observer.complete();
assert.equal(completed, true);
});
it('enqueues a job to send error if subscriber throws', async () => {
let error = {};
let errorValue = undefined;
new Observable(() => { throw error }).subscribe({
error(e) { errorValue = e },
});
assert.equal(errorValue, undefined);
await null;
assert.equal(errorValue, error);
});
it('does not send error if unsubscribed', async () => {
let error = {};
let errorValue = undefined;
let subscription = new Observable(() => { throw error }).subscribe({
error(e) { errorValue = e },
});
subscription.unsubscribe();
assert.equal(errorValue, undefined);
await null;
assert.equal(errorValue, undefined);
});
it('accepts a cleanup function from the subscriber function', () => {
let cleanupCalled = false;
let subscription = new Observable(() => {
return () => cleanupCalled = true;
}).subscribe();
subscription.unsubscribe();
assert.equal(cleanupCalled, true);
});
it('accepts a subscription object from the subscriber function', () => {
let cleanupCalled = false;
let subscription = new Observable(() => {
return {
unsubscribe() { cleanupCalled = true },
};
}).subscribe();
subscription.unsubscribe();
assert.equal(cleanupCalled, true);
});
});

View File

@@ -0,0 +1,41 @@
import assert from 'assert';
import { testMethodProperty } from './properties.js';
describe('subscription', () => {
function getSubscription(subscriber = () => {}) {
return new Observable(subscriber).subscribe();
}
describe('unsubscribe', () => {
it('is a method on Subscription.prototype', () => {
let subscription = getSubscription();
testMethodProperty(Object.getPrototypeOf(subscription), 'unsubscribe', {
configurable: true,
writable: true,
length: 0,
});
});
it('reports an error if the cleanup function throws', () => {
let error = {};
let subscription = getSubscription(() => {
return () => { throw error };
});
subscription.unsubscribe();
assert.equal(hostError, error);
});
});
describe('closed', () => {
it('is a getter on Subscription.prototype', () => {
let subscription = getSubscription();
testMethodProperty(Object.getPrototypeOf(subscription), 'closed', {
configurable: true,
writable: true,
get: true,
});
});
});
});