{"bugs":{"url":"https://github.com/kwonoj/rx-sandbox/issues"},"dist":{"shasum":"d0e16d0ce9f007cc3253335df5b202101673446d","tarball":"https://registry.npmjs.org/rx-sandbox/-/rx-sandbox-2.0.0-beta.2.tgz","fileCount":76,"integrity":"sha512-GgjbHC2NXiUMICDA6pWqs4N4gL9fAmTNK0ZPtNaLoXFmz4HVTgHHva4xwt9A0dWpMrPl+XipXR46oDpsxJjhdQ==","signatures":[{"sig":"MEYCIQCoS4PdDze9z4xh+QK32UzLRPUfx5mu3giAiMctvLCIZwIhAKgpZt0dcll92/teq0h/ON48XALSfbgTvJDmwFat5T+L","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":151745,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfkybyCRA9TVsSAnZWagAANCgP/1fnF2VVEnz1kDuLbfdO\n43mY8NOHE05dJJVawmeLs9qc8PdEv2n59DPQSKg5/D20zYvuyo/qef/jfC2U\nU/XnVbhn+20tv1znahrDQLsdx+udnxWwbgB45IKapau9nfgVBvm28sNvFsrh\nmtd39DwhIA7mf8takkmg0jCrfI185nsgQUk0x3CcS9n1e1HRRuCrFODKLaeT\n6zaM9KZRgS9uYAxKsPtmNfNjdbVQhthYImF68PKQmCAjg7LqZh5gyZM3i8+D\ncCa/l7Vr8FdPD/BQdf/sEh1C7gcUMrVPPDDDdGeUwLXeHK6bZvMMnHs3x8Jj\nv5YejEmACP+0jEf+uoa49T9Qz6WypIxlQ6bHirr4VgbEecZwYXUjP5Rudnpn\nutSlvnbfOAoO7DAGpepD3KMUPrfqM1/nAsyxHzzNu4gm9vII5ywhU6bxyiHF\nLou7go7Jm27TGfAGI+tY9PNDMgPklrf7w6J7iZrvrrv7/zsgoTAkzCQNTzl9\n4dh8RPyU7tjgDKFvCWuLVcDp45FpQTi5n4nC22KNsFe38VhZ7zQeOlbC8TpH\nZ2OpqJLIRu14VQ4UktNboZ9EWiLYaHjO033108Q6IsqmywwlIvaOsW7h4+mM\n28rmTE9dyvgIj8NR5896dJBHfbNK1f+zGuvT8Tv3lpXZaz67ksLA+VUvEvPt\n2YX2\r\n=4DpL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"./dist/index.js","name":"rx-sandbox","husky":{"hooks":{"pre-push":"npm-run-all build test","commit-msg":"commitlint -e","pre-commit":"lint-staged"}},"types":"./dist/index.d.ts","author":{"name":"OJ Kwon","email":"kwon.ohjoong@gmail.com"},"readme":"[![Build Status](https://circleci.com/gh/kwonoj/rx-sandbox/tree/master.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/kwonoj/rx-sandbox/tree/master)\n[![codecov](https://codecov.io/gh/kwonoj/rx-sandbox/branch/master/graph/badge.svg)](https://codecov.io/gh/kwonoj/rx-sandbox)\n[![npm](https://img.shields.io/npm/v/rx-sandbox.svg)](https://www.npmjs.com/package/rx-sandbox)\n\n# RxSandbox\n\n`RxSandbox` is test suite for RxJS, based on marble diagram DSL for easier assertion around Observables.\nFor RxJS 5 support, check pre-1.x versions. 1.x supports latest RxJS 6.x.\n\n## What's difference with `TestScheduler` in RxJS?\n\n`RxJS` 5's test cases are written via its own [`TestScheduler`](https://github.com/ReactiveX/rxjs/blob/9267b30ebc982e1845843f85866906496b3aaa8f/src/testing/TestScheduler.ts) implementation. While it still can be used for testing any other Observable based codes its ergonomics are not user code friendly, reason why core repo tracks [issue](https://github.com/ReactiveX/rxjs/issues/1775) to provide separate package for general usage. RxSandbox aims to resolve those ergonomics with few design goals\n\n- Provides feature parity to `TestScheduler`\n- Support extended marble diagram DSL\n- Near-zero configuration, works out of box\n- No dependencies to specific test framework\n\n# Install\n\nThis has a peer dependencies of `rxjs@6.*.*`, which will have to be installed as well\n\n```sh\nnpm install rx-sandbox\n```\n\n# Usage\n\n## Observable marble diagram token description\n\nIn `RxSandbox`, `Observable` is represented via `marble` diagram. Marble syntax is a string represents events happening over `virtual` time so called as `frame`.\n\n- `-` : Single `frame` of time passage, by default `1`.\n- `|` : Successful completion of an observable signaling `complete()`.\n- `#` : An error terminating the observable signaling `error()`.\n- ` `(whitespace) : Noop, whitespace does nothing but allows align marbles for readability.\n- `a` : Any other character than predefined token represents a value being emitted by `next()`\n- `()` : When multiple events need to single in the same frame synchronously, parenthesis are used to group those events. You can group nexted values, a completion or an error in this manner. The position of the initial `(`determines the time at which its values are emitted.\n- `^` : (hot observables only) Shows the point at which the tested observables will be subscribed to the hot observable. This is the \"zero frame\" for that observable, every frame before the `^` will be negative.\n- `!` : (for subscription testing) Shows the point  at which the tested observables will be unsubscribed.\n- `...n...` : (`n` is number) Expanding timeframe. For cases of testing long time period in observable, can shorten marble diagram instead of repeating `-`.\n\nThe first character of marble string always represents `0` frame.\n\n### Few examples\n\n```js\nconst never = `------`; // Observable.never() regardless of number of `-`\nconst empty = `|`;      // Observable.empty();\nconst error = `#`;      // Observable.throw(`#`);\nconst obs1 = `----a----`;\n//`           01234    `, emits `a` on frame 4\nconst obs2 = `----a---|`;\n//`           012345678`, emits `a` on frame 4, completes on 8\nconst obs2 = `-a-^-b--|`;\n//`              012345`, emits `b` on frame 2, completes on 5 - hot observable only\nconst obs3 = `--(abc)-|`;\n//`           012222234, emits `a`,`b`,`c` on frame 2, completes on 4\nconst obs4 = `----(a|)`;\n//`           01234444, emits `a` and complets on frame 4\nconst obs5 = ` - --a- -|`;\n//`            0 1234 56, emits `a` on frame 3, completes on frame 6\nconst obs6 = `--...4...--|`\n//`           01......5678, completes on 8\n\n```\n\n\n\n## Subscription marble diagram token description\n\nThe subscription marble syntax is slightly different to conventional marble syntax. It represents the **subscription** and an **unsubscription** points happening over time. There should be no other type of event represented in such diagram.\n\n- `-` : Single `frame` of time passage, by default `1`.\n- `^` : Shows the point in time at which a subscription happen.\n- `!` : Shows the point in time at which a subscription is unsubscribed.\n- (whitespace) : Noop, whitespace does nothing but allows align marbles for readability.\n- `...n...` : (`n` is number) Expanding timeframe. For cases of testing long time period in observable, can shorten marble diagram instead of repeating `-`.\n\nThere should be **at most one** `^` point in a subscription marble diagram, and **at most one** `!` point. Other than that, the `-` character is the only one allowed in a subscription marble diagram.\n\n### Few examples\n\n```js\nconst sub1 = `-----`; // no subscription\nconst sub2 = `--^--`;\n//`           012`, subscription happend on frame 2, not unsubscribed\nconst sub3 = `--^--!-`;\n//`           012345, subscription happend on frame 2, unsubscribed on frame 5\n```\n\n\n\n## Anatomy of test interface\n\nYou can import `rxSandbox`, and create instance using `create()`.\n\n```js\nimport { expect } from 'chai';\nimport { rxSandbox } from 'rx-sandbox';\n\nit('testcase', () => {\n  const { hot, cold, flush, getMessages, e, s } = rxSandbox.create();\n  const e1 = hot('  --^--a--b--|');\n  const e2 = cold('   ---x--y--|', {x: 1, y: 2});\n\n  const expected = e('       ---q--r--|');\n  const sub =      s('       ^        !');\n\n  const messages = getMessages(e1.merge(e2));\n\n  flush();\n\n  //assertion\n  expect(messages).to.deep.equal(expected);\n  expect(e1.subscriptions).to.deep.equal(sub);\n});\n```\n\n### Creating sandbox\n\n```typescript\nrxSandbox.create(autoFlush?: boolean, frameTimeFactor?: number, maxFrameValue?: number): RxSandboxInstance\n```\n\n`frameTimeFactor` allows to override default frame passage `1` to given value.\n`maxFrameValue` allows to override maximum frame number testscheduler will accept. (`1000` by default). Maxframevalue is relavant to frameTimeFactor. (i.e if `frameTimeFactor = 2` and `maxFrameValue = 4`, `--` will represent max frame)\n\nRefer below for `autoFlush` option.\n\n### Using RxSandboxInstance\n\n`RxSandboxInstance` exposes below interfaces.\n\n#### Creating hot, cold observable\n\n```typescript\nhot<T = string>(marble: string, value?: { [key: string]: T } | null, error?: any): HotObservable<T>;\nhot<T = string>(messages: Array<TestMessage<T>>): HotObservable<T>;\n\ncold<T = string>(marble: string, value?: { [key: string]: T } | null, error?: any): ColdObservable<T>;\ncold<T = string>(messages: Array<TestMessage<T>>): ColdObservable<T>;\n```\n\nBoth interfaces accepts marble diagram string, and optionally accepts custom values for marble values or errors. Otherwise, you can create `Array<TestMessage<T>>` directly instead of marble diagram.\n\n#### Creating expected value, subscriptions\n\nTo compare observable's result, we can use marble diagram as well wrapped by utility function to generate values to be asserted.\n\n```typescript\ne<T = string>(marble: string, value?: { [key: string]: T } | null, error?: any): Array<TestMessage<T>>;\n\n//const expected = e(`----a---b--|`);\n```\n\nIt accepts same parameter to hot / cold observable creation but instead of returning observable, returns array of metadata for marble diagram.\n\nSubscription metadata also need to be generated via wrapped function.\n\n```typescript\ns(marble: string): SubscriptionLog;\n\n//const subs = s('--^---!');\n```\n\n#### Getting values from observable\n\nOnce we have hot, cold observables we can get metadata from those observables as well to assert with expected metadata values.\n\n```typescript\ngetMessages<T = string>(observable: Observable<any>, unsubscriptionMarbls: string = null): Array<TestMessage<T>>>;\n\nconst e1 = hot('--a--b--|');\nconst messages = getMessages(e1.mapTo('x'));\n\n//at this moment, messages are empty!\nassert(messages.length === 0);\n```\n\nIt is important to note at the moment of getting metadata array, it is not filled with actual value but just empty array. Scheduler should be flushed to fill in values.\n\n```typescript\nconst e1 = hot('    --a--b--|');\nconst expected = e('--x--x--|')\nconst subs = s(`    ^       !`);\nconst messages = getMessages(e1.mapTo('x'));\n\n//at this moment, messages are empty!\nexpect(messages).to.be.empty;\n\nflush();\n\n//now values are available\nexpect(messages).to.deep.equal(expected);\n//subscriptions are also available too\nexpect(e1.subscriptions).to.deep.equal(subs);\n```\n\nOr if you need to control timeframe instead of flush out whole at once, you can use `advanceTo` as well.\n\n```typescript\nconst e1 = hot('    --a--b--|');\nconst subs = s(`    ^       !`);\nconst messages = getMessages(e1.mapTo('x'));\n\n//at this moment, messages are empty!\nexpect(messages).to.be.empty;\n\nadvanceTo(3);\nconst expected = e('--x------'); // we're flushing to frame 3 only, so rest of marbles are not constructed\n\n//now values are available\nexpect(messages).to.deep.equal(expected);\n//subscriptions are also available too\nexpect(e1.subscriptions).to.deep.equal(subs);\n```\n\n#### Flushing scheduler automatically\n\nBy default sandbox instance requires to `flush()` explicitly to execute observables. For cases each test case doesn't require to schedule multiple observables but only need to test single, we can create sandbox instance to flush automatically. Since it flushes scheduler as soon as `getMessages` being called, subsequent `getMessages` call will raise errors.\n\n```typescript\nconst { hot, e } = rxSandbox.create(true);\n\nconst e1 = hot('    --a--b--|');\nconst expected = e('--x--x--|')\nconst messages = getMessages(e1.mapTo('x'));\n\n//without flushing, observable immeditealy executes and values are available.\nexpect(messages).to.deep.equal(expected);\n\n//subsequent attempt will throw\nexpect(() => getMessages(e1.mapTo('y'))).to.throw();\n\n```\n\n#### Custom frame time factor\n\nEach timeframe `-` is predefined to `1`, can be overridden.\n\n```typescript\nconst { e } = rxSandbox.create(false, 10);\n\nconst expected = e('--x--x--|');\n\n// now each frame takes 10\nexpect(expected[0].frame).to.equal(20);\n```\n\n#### Custom assertion for marble diagram\n\nMessages generated by `rxSandbox` is plain object array, so any kind of assertion can be used. In addition to those, `rxSandbox` provides own custom assertion method `marbleAssert` for easier marble diagram diff.\n\n```typescript\nmarbleAssert<T = string>(source: Array<SubscriptionLog | TestMessage<T>>): { to: { equal(expected: Array<SubscriptionLog | TestMessage<T>>): void } }\n```\n\nIt accepts array of test messages generated by `getMessages` and `e`, or subscription log by `Hot/ColdObservable.subscriptions` or `s` (in case of utility method `s` it returns single subscription, so need to be constructed as array).\n\n```typescript\nimport { rxSandbox } from 'rx-sandbox';\nconst { marbleAssert } = rxSandbox;\n\nconst {hot, e, s, getMessages, flush} = rxSandbox.create();\n\nconst source = hot('---a--b--|');\nconst expected = e('---x--x---|');\nconst sub = s('^-----!');\n\nconst messages = getMessages(source.mapTo('x'));\nflush();\n\nmarbleAssert(messages).to.equal(expected);\nmarbleAssert(source.subscriptions).to.equal([sub]);\n```\n\nWhen assertion fails, it'll display visual / object diff with raw object values for easier debugging.\n\n**Assert Observable marble diagram**\n\n<img src=\"https://user-images.githubusercontent.com/1210596/29504109-186ac83a-85f2-11e7-8fd1-b65cef4a7803.png\" width=\"600\">\n\n**Assert subscription log marble diagram**\n\n<img src=\"https://user-images.githubusercontent.com/1210596/29504117-291c5ebe-85f2-11e7-939f-986f2d400b4f.png\" width=\"600\">\n\n# Building / Testing\n\nFew npm scripts are supported for build / test code.\n\n- `build`: Transpiles code to ES5 commonjs to `dist`.\n- `build:clean`: Clean up existing build\n- `test`: Run unit test. Does not require `build` before execute test.\n- `lint`: Run lint over all codebases\n- `lint:staged`: Run lint only for staged changes. This'll be executed automatically with precommit hook.\n- `commit`: Commit wizard to write commit message\n","engines":{"npm":">=3.0.0","node":">=4.2.4"},"gitHead":"47c25b144aa65091cef6d50826c72cab4b880d6c","license":"MIT","scripts":{"lint":"tslint -c tslint.json -p tsconfig.json \"src/**/*.ts\" \"spec/**/*.ts\"","test":"jest --config jest.json --coverage","build":"tsc -b --verbose src/tsconfig.json spec/tsconfig.json","changelog":"conventional-changelog -p angular -i CHANGELOG.md -s","build:clean":"shx rm -rf ./dist ./.tmp","lint:staged":"lint-staged","prepublishOnly":"npm-run-all build:clean build"},"_npmUser":{"name":"kwonoj","email":"kwon.ohjoong@gmail.com"},"homepage":"https://github.com/kwonoj/rx-sandbox#readme","keywords":["Rx","RxJS","ReactiveX","ReactiveExtensions","Streams","Observables","Observable","Stream","ES6","ES2015"],"repository":{"url":"git+https://github.com/kwonoj/rx-sandbox.git","type":"git"},"_npmVersion":"7.0.2","description":"Marble diagram DSL based test suite for RxJS 6","directories":{},"lint-staged":{"*.{ts,js}":["prettier --write --single-quote --print-width 120 --jsx-bracket-same-line true","tslint --fix","git add"]},"maintainers":[{"name":"kwonoj","email":"kwon.ohjoong@gmail.com"}],"_nodeVersion":"15.0.0","dependencies":{"expect":"^26.6.1","jest-matcher-utils":"^26.6.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"shx":"^0.3.2","chai":"^4.2.0","jest":"^26.6.1","rxjs":"^7.0.0-beta.0","husky":"^3.1.0","tslint":"^6.1.3","ts-jest":"^26.4.2","prettier":"^2.1.2","typescript":"^4.0.3","@types/chai":"^4.2.14","@types/jest":"^26.0.15","@types/node":"^14.14.2","lint-staged":"^10.4.2","npm-run-all":"^4.1.5","@commitlint/cli":"^11.0.0","jest-spin-reporter":"^2.0.0","cz-conventional-changelog":"^3.3.0","@commitlint/config-angular":"^11.0.0","conventional-changelog-cli":"^2.1.0","tslint-no-unused-expression-chai":"^0.1.4"},"peerDependencies":{"rxjs":"^7.0.0-beta.8"},"_npmOperationalInternal":{"tmp":"tmp/rx-sandbox_2.0.0-beta.2_1603479281451_0.7846585404231632","host":"s3://npm-registry-packages"},"_id":"rx-sandbox@2.0.0-beta.2","version":"2.0.0-beta.2"}