New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@cycle/time

Package Overview
Dependencies
Maintainers
2
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cycle/time - npm Package Compare versions

Comparing version 0.1.0 to 0.1.1

dist/Index.d.ts

86

dist/assert-equal.js
"use strict";
var deepEqual = require("deep-equal");
function checkEqual(completeStore, assert, interval) {
var equal = deepEqual(completeStore['actual'], completeStore['expected']);
var pass = true;
completeStore['actual'].forEach(function (actual, index) {
var expected = completeStore['expected'][index];
if (!actual || !expected) {
pass = false;
return;
}
if (actual.type !== expected.type) {
pass = false;
}
if (actual.type === 'next') {
var rightTime = diagramFrame(actual.time, interval) === diagramFrame(expected.time, interval);
var rightValue = deepEqual(actual.value, expected.value);
if (!rightTime || !rightValue) {
pass = false;
}
}
if (actual.type === 'error') {
var rightTime = diagramFrame(actual.time, interval) === diagramFrame(expected.time, interval);
if (expected.type !== 'error') {
pass = false;
}
if (!rightTime) {
pass = false;
}
if (!pass) {
assert.unexpectedErrors.push(actual.error);
}
}
});
if (pass) {
assert.state = 'passed';
}
else {
assert.state = 'failed';
assert.error = new Error(strip("\n Expected\n\n " + diagramString(completeStore['expected'], interval) + "\n\n Got\n\n " + diagramString(completeStore['actual'], interval) + "\n\n " + displayUnexpectedErrors(assert.unexpectedErrors) + "\n "));
}
}
function makeAssertEqual(schedule, currentTime, interval, addAssert) {

@@ -10,3 +50,6 @@ return function assertEqual(actual, expected) {

error: null,
unexpectedErrors: []
unexpectedErrors: [],
finish: function () {
checkEqual(completeStore, assert, interval);
}
};

@@ -16,42 +59,4 @@ addAssert(assert);

calledComplete++;
completeStore[label] = diagram;
if (calledComplete === 2) {
var equal = deepEqual(completeStore['actual'], completeStore['expected']);
var pass_1 = true;
completeStore['actual'].forEach(function (actual, index) {
var expected = completeStore['expected'][index];
if (!actual || !expected) {
pass_1 = false;
return;
}
if (actual.type !== expected.type) {
pass_1 = false;
}
if (actual.type === 'next') {
var rightTime = diagramFrame(actual.time, interval) === diagramFrame(expected.time, interval);
var rightValue = deepEqual(actual.value, expected.value);
if (!rightTime || !rightValue) {
pass_1 = false;
}
}
if (actual.type === 'error') {
var rightTime = diagramFrame(actual.time, interval) === diagramFrame(expected.time, interval);
if (expected.type !== 'error') {
pass_1 = false;
}
if (!rightTime) {
pass_1 = false;
}
if (!pass_1) {
assert.unexpectedErrors.push(actual.error);
}
}
});
if (pass_1) {
assert.state = 'passed';
}
else {
assert.state = 'failed';
assert.error = new Error(strip("\n Expected\n\n " + diagramString(completeStore['expected'], interval) + "\n\n Got\n\n " + diagramString(completeStore['actual'], interval) + "\n\n " + displayUnexpectedErrors(assert.unexpectedErrors) + "\n "));
}
checkEqual(completeStore, assert, interval);
}

@@ -61,2 +66,3 @@ }

var entries = [];
completeStore[label] = entries;
return {

@@ -63,0 +69,0 @@ next: function (ev) {

"use strict";
var combineErrors = require("combine-errors");
require('setimmediate');

@@ -30,9 +31,11 @@ var scheduler_1 = require("./scheduler");

if (!eventToProcess) {
var failedAsserts = asserts.filter(function (assert) { return assert.state === 'failed'; });
var pendingAsserts = asserts.filter(function (assert) { return assert.state === 'pending'; });
if (pendingAsserts.length > 0) {
console.log('Pending asserts after run finished: ', pendingAsserts);
pendingAsserts.forEach(function (assert) { return assert.finish(); });
}
var failedAsserts = asserts.filter(function (assert) { return assert.state === 'failed'; });
if (failedAsserts.length > 0) {
done(failedAsserts[0].error);
var errors = failedAsserts.map(function (assert) { return assert.error; });
var error = combineErrors(errors);
done(error);
}

@@ -39,0 +42,0 @@ else {

{
"name": "@cycle/time",
"version": "0.1.0",
"version": "0.1.1",
"description": "A time driver designed to enable awesome testing and dev tooling",

@@ -10,11 +10,8 @@ "main": "dist/index.js",

"homepage": "https://github.com/cyclejs/time#readme",
"files": [
"dist/"
],
"publishConfig": {
"access": "public"
},
"scripts": {

@@ -29,3 +26,2 @@ "start": "budo example/index.ts:index.js --live -- -p tsify",

},
"repository": {

@@ -35,7 +31,5 @@ "type": "git",

},
"bugs": {
"url": "https://github.com/cyclejs/time/issues"
},
"dependencies": {

@@ -45,2 +39,3 @@ "@cycle/xstream-run": "^4.2.0",

"@types/node": "^7.0.0",
"combine-errors": "^3.0.3",
"deep-equal": "^1.0.1",

@@ -52,3 +47,2 @@ "raf": "^3.3.0",

},
"devDependencies": {

@@ -63,3 +57,3 @@ "@cycle/dom": "^14.1.0",

"mocha": "^3.1.2",
"snabbdom-selector": "^1.0.1",
"snabbdom-selector": "^1.1.1",
"ts-node": "^2.0.0",

@@ -66,0 +60,0 @@ "tsify": "^3.0.0",

@@ -5,14 +5,75 @@ # @cycle/time

`@cycle/time` is a library that deals with all things time related in Cycle.js. It's a driver for time, providing methods like `debounce`, `delay`, `throttle` and `periodic`. It also provides tools for elegantly testing streams.
`@cycle/time` is a library that deals with all things time related in Cycle.js. It's a driver for time, providing methods like `debounce`, `delay`, `throttle` and `periodic`. It also provides tools for elegantly testing Cycle applications and any functions that use streams.
Testing
Features
---
Cycle.js is great because everything all of your application's inputs and outputs are streams. In theory this should make Cycle applications simple to test, because all of the input and output is explicitly passed around.
`@cycle/time` is split into two parts, `timeDriver` and `mockTimeSource`.
Cycle is also great for building applications with complex asynchronous behaviour. This is possible because of great observable operators like `debounce` and `delay`.
**Development/production** - `timeDriver`
So what does testing with Cycle look like? The basic principle is to subscribe to a stream, and to make assertions about what it emits. Here's a contrived example:
* Super smooth side effect free implementations of `periodic`, `delay`, `debounce` and more
* Enables excellent dev tooling like hot code reloading and time travel
* Powered by `requestAnimationFrame`, so your apps will be faster and smoother
**Testing** - `mockTimeSource`
* Write tests using marble diagram syntax, including expected output
* Blazing fast! 100x faster than tests written with `xstream`'s `fromDiagram`
* No more intermittent failures and timing errors. Runs in virtual time so ordering is guaranteed.
Installation
---
```bash
$ npm install @cycle/time --save
```
Usage (Development / Production)
---
`@cycle/time` exports a `timeDriver`, a driver that provides time based streams and operators.
Firstly import the `timeDriver`.
<!-- share-code-between-examples -->
```js
import {timeDriver} from '@cycle/time';
```
Then it needs to be added to the drivers object.
```js
const drivers = {
DOM: makeDOMDriver('.app'),
Time: timeDriver
}
```
Here is a simple timer using `periodic`.
```js
function Timer (sources) {
const count$ = sources.Time.periodic(1000);
return {
DOM: count$.map(count => div(`${count} seconds elapsed`))
}
}
run(Timer, drivers);
```
The `timeDriver` also provides `delay`, `debounce` and `throttle` operators that can be used with `.compose`.
Usage (Testing)
---
One of the primary strengths of Cycle's design is that all of your application's inputs and outputs are streams, and all side effects are handled in drivers. In theory this should make Cycle applications simple to test, because all of the input and output is explicitly passed around.
Cycle is also great for building applications with complex asynchronous behaviour. This is possible because of useful observable operators like `debounce`, `delay` and `throttle`.
So what does testing with Cycle look like? The basic principle is to subscribe to a stream, and to make assertions about what it emits. Here's a contrived example (using mocha):
```js
import assert from 'assert';

@@ -22,16 +83,24 @@ import xs from 'xstream';

const input$ = fromDiagram('---1---2---3--|');
function double (i) {
return i * 2;
}
const actual$ = input$.map(i => i * 2);
describe('double', () => {
it('doubles a number', (done) => {
const input$ = fromDiagram('---1---2---3--|');
const expectedValues = [2, 4, 6];
const actual$ = input$.map(i => i * 2);
actual$.take(expectedValues.length).addListener({
next (value) {
assert.equal(value, expectedValues.shift());
},
const expectedValues = [2, 4, 6];
error: done,
complete: done
})
actual$.take(expectedValues.length).addListener({
next (value) {
assert.equal(value, expectedValues.shift());
},
error: done,
complete: done
})
});
});
```

@@ -41,3 +110,3 @@

There are a few problems here. The first is that `xstream`'s `fromDiagram` is very slow. By default, each character in a diagram string represents 20ms. That diagram is 15 characters long, and will take 300ms to complete. If you have 10 unit tests like that, suddenly your test suite takes 3 seconds.
There are a few problems here. The first is that `xstream`'s `fromDiagram` is very slow. By default, each character in a diagram string represents 20ms. The above diagram is 15 characters long, and will take 300ms to complete. If you have 10 unit tests like that, suddenly your test suite takes 3 seconds.

@@ -48,23 +117,29 @@ Additionally, and perhaps more significantly, since `setTimeout` provides no guarantees of accurate scheduling, writing tests with multiple `fromDiagram` inputs will occasionally fail due to events occurring in the wrong order.

So where does `@cycle/time` come in? `@cycle/time` is a library that will help you write the asynchronous tests you always dreamed of.
So where does `@cycle/time` come in?
Let's rewrite our contrived example:
```js
import {mockTimeSource} from '@cycle/time';
const Time = mockTimeSource();
function double (i) {
return i * 2;
}
const input$ = Time.diagram('---1---2---3--|');
const actual$ = input$.map(i => i * 2);
const expected$ = Time.diagram('---2---4---6--|');
describe('double', () => {
it('doubles a number', (done) => {
const Time = mockTimeSource();
Time.assertEqual(actual$, expected$);
const input$ = Time.diagram('---1---2---3--|');
const actual$ = input$.map(i => i * 2);
const expected$ = Time.diagram('---2---4---6--|');
Time.run(done);
Time.assertEqual(actual$, expected$);
Time.run(done);
});
});
```
A few things have changed here. First is that we're now creating our input streams from diagrams using `@cycle/time`. Instead of scheduling their events using `setTimeout`, which as discussed is slow and inconsistent, their events are scheduled on a central queue inside of `@cycle/time`.
A few things have changed here. First is that we're now creating our input streams from diagrams using `@cycle/time`. Instead of scheduling their events using `setTimeout`, which is slow and inconsistent, their events are scheduled on a central queue inside of `@cycle/time`.
This queue is processed when we call `time.run();`. Even though each character in the diagram still represents 20ms, we don't have to wait all that time. Instead, the application's time is managed by `@cycle/time`, so we can run on "virtual time". This means this test is much faster than the equivalent using `xstream` `fromDiagram`, over 10x faster.
This queue is processed when we call `Time.run();`. Even though each character in the diagram still represents 20ms, we don't have to wait all that time. Instead, the application's time is managed by `@cycle/time`, so we can run on "virtual time". This means this test is much faster than the equivalent using `xstream` `fromDiagram`, around 100x faster.

@@ -83,16 +158,20 @@ This approach is comparable to RxJS's schedulers and HistoricalScheduler approach, but works with `xstream` and potentially other libraries.

const input$ = fromDiagram('-1--------2---|');
describe('xstream delay', () => {
it('slows our test down by 200ms', (done) => {
const input$ = fromDiagram('-1--------2---|');
const actual$ = input$.compose(delay(200));
const actual$ = input$.compose(delay(200));
const expectedValues = [1, 2];
const expectedValues = [1, 2];
actual$.take(expectedValues.length).addListener({
next (value) {
assert.equal(value, expectedValues.shift());
},
actual$.take(expectedValues.length).addListener({
next (value) {
assert.equal(value, expectedValues.shift());
},
error: done,
complete: done
})
error: done,
complete: done
});
});
});
```

@@ -105,11 +184,15 @@

const Time = mockTimeSource();
describe('@cycle/time delay', () => {
it('is super quick because of virtual time', (done) => {
const Time = mockTimeSource();
const input$ = Time.diagram('-1--------2---|');
const actual$ = input$.compose(Time.delay(200));
const expected$ = Time.diagram('-----------1--------2---|');
const input$ = Time.diagram('-1--------2---|');
const actual$ = input$.compose(Time.delay(200));
const expected$ = Time.diagram('-----------1--------2---|');
Time.assertEqual(actual$, expected$);
Time.assertEqual(actual$, expected$);
Time.run(done);
Time.run(done);
});
});
```

@@ -119,6 +202,5 @@

As well as `delay`, `@cycle/time` has implementations of `debounce`, `throttle`, and `periodic`.
Usage (testing Cycle applications)
---
What about testing a Cycle component with multiple inputs?
Say we have a counter, defined like this:

@@ -164,59 +246,51 @@

const addClick = `---x--x-------x--x--|`;
const subtractClick = `---------x----------|`;
const expectedCount = `0--1--2--1----2--3--|`;
describe('Counter', () => {
it('increments and decrements in response to clicks', (done) => {
const addClick = `---x--x-------x--x--|`;
const subtractClick = `---------x----------|`;
const expectedCount = `0--1--2--1----2--3--|`;
const Time = mockTimeSource();
const DOM = mockDOMSource(xsAdapter, {
'.add': {
'click': Time.diagram(addClick)
},
const Time = mockTimeSource();
const DOM = mockDOMSource(xsAdapter, {
'.add': {
'click': Time.diagram(addClick)
},
'.subtract': {
'click': Time.diagram(subtractClick)
},
});
'.subtract': {
'click': Time.diagram(subtractClick)
},
});
const counter = Counter({DOM});
const counter = Counter({DOM});
const count$ = counter.DOM.map(vtree => select('.count', vtree)[0].text);
const expectedCount$ = Time.diagram(expectedCount);
const count$ = counter.DOM.map(vtree => select('.count', vtree)[0].text);
Time.assertEqual(
count$,
expectedCount$
)
const expectedCount$ = Time.diagram(expectedCount);
Time.run(done);
Time.assertEqual(count$, expectedCount$)
Time.run(done);
});
});
```
Development / Production
---
If you want to see more examples of tests using `@cycle/time`, check out the test directory.
You can use `@cycle/time` in your test suite without using it in dev or production. However, if you want to use any time based operators, you must use the ones provided by `@cycle/time`.
## FAQ
`@cycle/time` exports a `timeDriver`, a driver that provides time based streams and operators. All you need to do is add it your drivers object, and replace usages of time-based operators like `delay`, `debounce`, `throttle` and `periodic` with the `@cycle/time` implementation. Here is a counter using `Time.periodic`.
### Why would I want to use the time based operators provided by this library over the ones from `xstream`?
```js
import {timeDriver} from '@cycle/time';
import {makeDOMDriver, div} from '@cycle/dom';
import {run} from '@cycle/xstream-run';
xstream's time-based operators (`periodic`, `delay`, `debounce`, `throttle`, etc) are implemented using `setTimeout`.
const drivers = {
Time: timeDriver,
DOM: makeDOMDriver('.app')
}
`setTimeout` provides no guarantee that it will actually fire the event precisely at the given interval. The variance in `setTimeout` has a few consequences.
function Counter ({Time}) {
const count$ = Time.periodic(1000);
* It makes it impossible to consistently record streams into diagrams, which prevents asserting two streams are equal
* Events might occur in different orders each time the code is run
* Operators implemented using `setTimeout` cause a real delay in tests. A delay of 300ms is common for normal `fromDiagram` tests
return {
DOM: count$.map(count => div(`Count: ${count}`))
}
}
Instead, `@cycle/time` schedules events onto a central queue. In tests, they are then emitted as fast as possible, while guaranteeing the ordering.
run(Counter, drivers);
```
This allows incredibly fast tests for complex asynchronous behaviour. A `@cycle/time` test takes 3-5ms to run on my machine.
This will display a counter where the count goes up every second.
This approach also means we can express our expected output using a diagram, which is nice.

@@ -297,7 +371,7 @@ ## API

Returns a `TimeSource` object, with all of the methods from the `timeDriver` (`debounce`, `delay`, `periodic`, `throttle`), along with a collection of methods useful for writing unit tests.
Returns a `TimeSource` object, with all of the methods from the `timeDriver` (`debounce`, `delay`, `periodic`, `throttle`), along with `diagram`, `assertEqual` and `run`, which are useful for writing unit tests.
Instead of all delays and debounces running in real time in your tests, causing unecessary delays, they will be run in "virtual time".
Has some additional methods:
Has some additional methods that are useful for testing:

@@ -309,5 +383,17 @@ #### `run(doneCallback = raiseError)`

#### `diagram(diagramString)`
A constructor that takes a string representing a stream and returns a stream. Useful for testing.
#### `diagram(diagramString, values = {})`
A constructor that takes a string representing a stream and returns a stream.
The diagram syntax is inspired by xstream's [fromDiagram](Vhttps://github.com/staltz/xstream/blob/master/EXTRA_DOCS.md#-fromdiagramdiagram-options) and RxJS's [marble diagrams](Vhttps://github.com/ReactiveX/rxjs/blob/master/doc/writing-marble-tests.md).
* `-` the passage of time without any events, by default 20 virtual millseconds (can be changed by passing an argument to `mockTimeSource`)
* `1` numbers 0-9 are treated as literal numeric values
* `a` other literal values are strings
* `|` completion of the stream
* `#` an error
The stream returned by diagram will only emit events once `Time.run()` is called.
`diagram` can also take an optional values object that can be used to emit more complex values than simple literals.
```js

@@ -322,2 +408,14 @@ Time.diagram('---1---2---3---|').subscribe(i => console.log(i));

// 3
Time.diagram(
'---a---b---c---|',
{a: 'foo', b: 'bar', c: 'baz'}
).subscribe(i => console.log(i));
Time.run();
// Logs:
// foo
// bar
// baz
```

@@ -348,6 +446,2 @@

## Install
Yet to be released
## License

@@ -354,0 +448,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc