Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ node_modules/
dist/
tmp/
coverage/
img/

# Misc
npm-debug.log
Expand Down
15 changes: 13 additions & 2 deletions .markdown-doctest-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ var marbleTesting = require('./spec/helpers/marble-testing');

global.rxTestScheduler = new Rx.TestScheduler(marbleTesting.assertDeepEqual);

function it(callback) {
callback();
}

it.asDiagram = function asDiagram() {
return function (spec, callback) {
callback();
}
};

module.exports = {
require: {
'@reactivex/rxjs': Rx
Expand All @@ -17,10 +27,11 @@ module.exports = {
expectSubscriptions: marbleTesting.expectSubscriptions,
assertDeepEqual: marbleTesting.assertDeepEqual,
Observable: Rx.Observable,
someObservable: Rx.Observable.range(1, 10)
someObservable: Rx.Observable.range(1, 10),
it: it
},

babel: {
stage: 0
}
}
};
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ The build and test structure is fairly primitive at the moment. There are variou
- build_docs: generates API documentation from `dist/es6` to `dist/docs`
- build_cover: runs `istanbul` code coverage against test cases
- test: runs tests with `jasmine`, must have built prior to running.
- tests2png: generates PNG marble diagrams from test cases.

### Example

Expand All @@ -86,3 +87,26 @@ Run `npm run perf_micro` to run micro performance test benchmarking operator.

## Adding documentation
RxNext uses [ESDoc](https://esdoc.org/) to generate API documentation. Refer to ESDoc's documentation for syntax. Run `npm run build_docs` to generate.

## Generating PNG marble diagrams

The script `npm run tests2png` requires some native packages installed locally: `imagemagick`, `graphicsmagick`, and `ghostscript`.

For Mac OS X with [Homebrew](http://brew.sh/):

- `brew install imagemagick`
- `brew install graphicsmagick`
- `brew install ghostscript`

For Debian Linux:

- `sudo add-apt-repository ppa:dhor/myway`
- `apt-get install imagemagick`
- `apt-get install graphicsmagick`
- `apt-get install ghostscript`

For Windows and other Operating Systems, check the download instructions here:

- http://imagemagick.org
- http://www.graphicsmagick.org
- http://www.ghostscript.com/
27 changes: 27 additions & 0 deletions doc/writing-marble-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,30 @@ expectSubscriptions(y.subscriptions).toBe(ysubs);

In most tests it will be unnecessary to test subscription and unsubscription points, be either obvious or can be implied from the `expected` diagram. In those cases do not write subscription assertions. In test cases that have inner subscriptions or cold observables with multiple subscribers, these subscription assertions can be useful.

## Generating PNG marble diagrams from tests

Typically, each test case in Jasmine is written as `it('should do something', function () { /* ... */ })`. To mark a test case for PNG diagram generation, you must use the `asDiagram(label)` function, like this:

<!-- skip-example -->
```js
it.asDiagram(operatorLabel)('should do something', function () {
// ...
});
```

For instance, with `zip`, we would write

```js
it.asDiagram('zip')('should zip by concatenating', function () {
var e1 = hot('---a---b---|');
var e2 = hot('-----c---d---|');
var expected = '-----x---y---|';
var values = { x: 'ac', y: 'bd' };

var result = e1.zip(e2, function(x, y) { return String(x) + String(y); });

expectObservable(result).toBe(expected, values);
});
```

Then, when running `npm run tests2png`, this test case will be parsed and a PNG file `zip.png` (filename determined by `${operatorLabel}.png`) will be created in the `img/` folder.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"cover": "istanbul cover -x \"*-spec.js index.js *-helper.js spec/helpers/*\" ./node_modules/jasmine/bin/jasmine.js && npm run cover_remapping",
"cover_remapping": "remap-istanbul -i coverage/coverage.json -o coverage/coverage-remapped.json && remap-istanbul -i coverage/coverage.json -o coverage/coverage-remapped.lcov -t lcovonly && remap-istanbul -i coverage/coverage.json -o coverage/coverage-remapped -t html",
"test": "jasmine",
"tests2png": "mkdirp img && JASMINE_CONFIG_PATH=spec/support/tests2png.json jasmine",
"watch": "watch \"echo triggering build && npm run build_test && echo build completed\" src -d -u -w=15",
"perf": "protractor protractor.conf.js",
"perf_micro": "node ./perf/micro/index.js",
Expand Down Expand Up @@ -89,13 +90,15 @@
"fs-extra": "0.24.0",
"ghooks": "0.3.2",
"glob": "5.0.14",
"gm": "1.21.1",
"google-closure-compiler": "20151015.0.0",
"http-server": "0.8.0",
"istanbul": "0.3.22",
"jasmine": "2.3.2",
"jasmine-core": "2.3.4",
"lodash": "3.10.1",
"markdown-doctest": "^0.2.0",
"mkdirp": "0.5.1",
"platform": "1.3.0",
"promise": "7.0.3",
"protractor": "2.5.1",
Expand Down
4 changes: 4 additions & 0 deletions spec/helpers/test-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ global.it = function (description, cb, timeout) {
}
};

global.it.asDiagram = function () {
return global.it;
};

function stringify(x) {
return JSON.stringify(x, function (key, value) {
if (Array.isArray(value)) {
Expand Down
165 changes: 165 additions & 0 deletions spec/helpers/tests2png/diagram-test-runner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;

var Rx = require('../../../dist/cjs/Rx.KitchenSink');
var marbleHelpers = require('../marble-testing');
var painter = require('./painter');

global.rxTestScheduler = null;
global.cold = marbleHelpers.cold;
global.hot = marbleHelpers.hot;
global.expectObservable = marbleHelpers.expectObservable;
global.expectSubscriptions = marbleHelpers.expectSubscriptions;

function getInputStreams(rxTestScheduler) {
return Array.prototype.concat.call([],
rxTestScheduler.hotObservables
.map(function (hot) {
return {
messages: hot.messages,
subscription: {start: 0, end: '100%'},
};
})
.slice(),
rxTestScheduler.coldObservables
.map(function (cold) {
return {
messages: cold.messages,
cold: cold,
};
})
.slice()
);
}

function updateInputStreamsPostFlush(inputStreams, rxTestScheduler) {
return inputStreams.map(function (singleInputStream) {
if (singleInputStream.cold && singleInputStream.cold.subscriptions.length) {
singleInputStream.subscription = {
start: singleInputStream.cold.subscriptions[0].subscribedFrame,
end: singleInputStream.cold.subscriptions[0].unsubscribedFrame,
};
}
return singleInputStream;
});
}

function postProcessOutputMessage(msg) {
if (Array.isArray(msg.notification.value)
&& msg.notification.value.length
&& typeof msg.notification.value[0] === 'object') {
msg.notification.value = {
messages: msg.notification.value,
subscription: {start: msg.frame, end: '100%'},
}
}
return msg;
}

function makeFilename(operatorLabel) {
return /^(\w+)/.exec(operatorLabel)[1];
}

var glit = global.it;

global.it = function (description, specFn, timeout) { };

global.it.asDiagram = function asDiagram(operatorLabel) {
return function specFnWithPainter(description, specFn) {
if (specFn.length === 0) {
glit(description, function () {
var outputStream;
global.rxTestScheduler = new Rx.TestScheduler(function (actual) {
if (Array.isArray(actual) && typeof actual[0].frame === 'number') {
outputStream = {
messages: actual.map(postProcessOutputMessage),
subscription: {start: 0, end: '100%'}
};
}
return true;
});
specFn();
var inputStreams = getInputStreams(global.rxTestScheduler);
global.rxTestScheduler.flush();
inputStreams = updateInputStreamsPostFlush(inputStreams, rxTestScheduler);
var filename = makeFilename(operatorLabel);
painter(inputStreams, operatorLabel, outputStream, filename);
console.log('Painted img/' + filename + '.png');
});
} else {
throw new Error('Cannot generate PNG marble diagram for async test ' + description);
}
};
};

beforeEach(function () {
jasmine.addMatchers({
toDeepEqual: function (util, customEqualityTesters) {
return {
compare: function (actual, expected) {
return { pass: true };
}
};
}
});
});

afterEach(function () {
global.rxTestScheduler = null;
});

(function () {
var objectTypes = {
'boolean': false,
'function': true,
'object': true,
'number': false,
'string': false,
'undefined': false
};

Object.defineProperty(Error.prototype, 'toJSON', {
value: function () {
var alt = {};

Object.getOwnPropertyNames(this).forEach(function (key) {
if (key !== 'stack') {
alt[key] = this[key];
}
}, this);
return alt;
},
configurable: true
});

var _root = (objectTypes[typeof self] && self) || (objectTypes[typeof window] && window);

var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
var freeGlobal = objectTypes[typeof global] && global;

if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
_root = freeGlobal;
}

global.__root__ = _root;
})();

global.lowerCaseO = function lowerCaseO() {
var values = [].slice.apply(arguments);

var o = {
subscribe: function (observer) {
values.forEach(function (v) {
observer.next(v);
});
observer.complete();
}
};

o[Symbol.observable] = function () {
return this;
};

return o;
};

Loading