diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 49bac18..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,37 +0,0 @@ -module.exports = { - "ecmaFeatures": { - "modules": true, - "spread" : true, - "restParams" : true - }, - // "plugins": [ - // "unicorn" - // ], - "env": { - "es6": true, - "node": true, - "mocha": true - }, - "parserOptions": { - "ecmaVersion": 2017, - "sourceType": "module" - }, - "extends": "eslint:recommended", - "rules": { - "indent": [ - "error", - 2 - ], - // "unicorn/no-array-instanceof": "error", - "no-console": 0, - "semi": ["error", "never"], - "linebreak-style": [ - "error", - "unix" - ], - "quotes": [ - "error", - "single" - ] - } -} diff --git a/examples/traverse.js b/examples/traverse.js index 4a565e5..7034aa8 100644 --- a/examples/traverse.js +++ b/examples/traverse.js @@ -1,59 +1,79 @@ import RxClass from '../src/rx-class.js' -class Example extends RxClass { - constructor(opts={}){ +const log = o => console.dir(o, { depth: 5 }) + +const rxopts = Symbol.for('rx-class-opts') + +class Example extends RxClass { + constructor (opts = {}) { super(opts) this.test = { - tomrrow: {bar:'fight', foo:'fighters'}, - today: {bing:'bing', bong:{who:'first',what:'second'}} + tomrrow: { bar: 'fight', foo: 'fighters' }, + today: { bing: 'bing', bong: { who: 'first', what: 'second' } } } } } - -const example = new Example({ - _rx_ : { - handler: value => {console.log('-----------default subscription handler-------', value)}, +const opts = { + [rxopts]: { + handler: value => { console.log('-----------default subscription handler-------', value) }, skip: 1 } -}) +} -example.foundation = null +console.log('options\n', opts) -console.log('-----EXAMPLE: TRAVERSE OF EXISTING OBJECT OR WHEN SETTING A PLAIN OBJECT-----------') -console.log('instance before doing rx on properties\n') -console.log(example) -console.log('--------------------------') +const example = new Example(opts) -// example.on('changed', (prop) => console.log('emitted----a property changed-----',prop)) -// example.on('test', (val) => console.log('emitted value of test',val)) +// console.log('-----EXAMPLE: TRAVERSE OF EXISTING OBJECT OR WHEN SETTING A PLAIN OBJECT-----------') +// console.log('instance before doing rx on properties\n') +// // log(example) +// console.log('------------------------------------------') -console.log('traversing exitings object \'test\' and making all leaves reactive') -console.log(example.test) -example.rxAdd('test',{traverse:true}) +// example.on('changed', (prop) => console.log('emitted----a property changed-----', prop)) +// example.on('test', (val) => console.log('emitted value of test', val)) -console.log('adding new property which is made reactive by default \'test.today.bong.why\'') -example.set('test.today.bong.why','do not know') +// console.log('----- traversing existing object \'test\' and making all leaves reactive ------\n') +// example.rxAdd('test', { traverse: true }) +// // log(example) +// console.log('------------------------------------------') -console.log('------\'test\', raw object ------ \n', example.$get('test')) -console.log('------ \'test\', values object ------\n>', example.get('test')) -console.log(example) -console.log('now removing reactivty from \'test\'') -example.rxRemove('test',{confirm:true}) -console.log(example.$get('test')) -console.log('-------------------------') +// console.log('now get value for bong directly and via get', example.test.today.bong, example.get('test.today.bong')) +// console.log('Note: using example.get will traverse bong and get actual values') -console.log('now add a completely new object to root of \'foundation\' and make it reactive') -const obj = {giskard:'daneel', hari: {name:'seldon', planet:'Helcion'}} -example.set('foundation',obj) +// console.log('adding new property which is made reactive by default \'test.today.bong.why\'') +// example.set('test.today.bong.why', 'do not know') -console.log('------\'foundation\', raw object ------ \n', example.$get('foundation')) -console.log('------ \'foundation\', values object ------\n>', example.get('foundation')) +// console.log('------\'.bong\', raw object ------ \n', example.test.today.bong) +// console.log('------ \'.bong\', values object ------\n>', example.get('test.today.bong')) +// console.log('------, note: raw leaf access returns value not [Getter/Setter] ==>', example.test.today.bong.why) +// console.log('\nnow removing reactivty from \'test\'') +// example.rxRemove('test', { confirm: true }) +// console.log('------\'.test\', raw object ------ \n', example.test) +// console.log('--------------------------') -console.log('--------instance after doing rx----\n') -console.log(example) -console.log('--------------------------') +console.log('\n\nnow add a completely new object to root of \'foundation\' and make it reactive without traverse') +example.foundation = { planets: ['Trantor', 'Helcion'], persons: { giskard: { alias: 'daneel' }, hari: { name: 'seldon', planet: 'Helcion' } } } +console.dir(example.foundation) +example.rxAdd('foundation.persons.hari.name') +// console.log('foundation', example.isRx('foundation')) +console.log(example.isRx('foundation.persons.hari')) +// console.log('------\'foundation\', raw object ------ \n') +// // log(example) +// console.log('get raw nested property raw or with get returns the same') +// console.log(example.foundation.hari, example.get('foundation.hari')) -console.log('now change a reactive property directly like') -console.log('example.foundation.giskard = \'daneel a robot\'') -example.foundation.giskard = 'daneel a robot' -console.log('the new value extracted by the getter is',example.foundation.giskard) +// console.log('now change one of the nested properties and see if whole object foundation is reactive') + +// example.foundation.hari.planet = 'Trantor' +// example.set('foundation.hari.planet', 'Trantor') + +// console.log(example.foundation) +// log(example) +// console.log('--------instance after doing rx----\n') +// console.log(example) +// console.log('--------------------------') + +// console.log('now change a reactive property directly like') +// console.log('example.foundation.giskard = \'daneel a robot\'') +// example.foundation.giskard = 'daneel a robot' +// console.log('the new value extracted by the getter is', example.foundation.giskard) diff --git a/package.json b/package.json index d215c32..07f9eeb 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,9 @@ }, "homepage": "https://github.com/uCOMmandIt/uci-utils#readme", "dependencies": { + "@uci-utils/bind-funcs": "^0.3.0", "@uci-utils/logger": "^0.1.0", - "@uci-utils/obj-nested-prop": "^0.1.1", + "@uci-utils/obj-nested-prop": "^0.1.2", "@uci-utils/type": "^0.6.2", "deep-equal": "^2.0.5", "rxjs": "^7.0.0", diff --git a/src/rx-class.js b/src/rx-class.js index 16a4850..53898f0 100644 --- a/src/rx-class.js +++ b/src/rx-class.js @@ -6,129 +6,138 @@ import { BehaviorSubject, from } from 'rxjs' import { distinctUntilChanged, skip } from 'rxjs/operators' // uci modules import { check } from '@uci-utils/type' -import { get, set, del, walkPath, getKeys, pathToString } from '@uci-utils/obj-nested-prop' +import bind from '@uci-utils/bind-funcs' +import { get as $get, set as $set, del as $del, walkPath, getKeys, pathToString } from '@uci-utils/obj-nested-prop' import { logger } from '@uci-utils/logger' // supporting import equal from 'deep-equal' import traverse from 'traverse' const log = logger({ file: '/src/rx-class.js', package: '@uci-utils/rx-class', class: 'RxClass' }) - // to pass rx options use 'Symbol.for('rx-class-opts')' class RxClass extends EventEmitter { - // private fields - #rx - constructor (opts = {}) { super(opts) const rxopts = opts[Symbol.for('rx-class-opts')] || {} - this.#rx = { + Object.defineProperty(this, '$$__rxns__$$', { + enumerable: false, + value: rxopts.namespace || 'rx' + }) + this[this.$$__rxns__$$] = bind(methods, this) + this[this.$$__rxns__$$].props = {} // where reactive property info is stored including actual values + this[this.$$__rxns__$$].opts = { emitter: rxopts.emitter != null ? rxopts.emitter : true, event: rxopts.event === false ? false : rxopts.event || 'changed', handler: rxopts.handler, // default subscription handler - props: {}, // where reactive property info is store including actual values amendValue: rxopts.amendValue, // amend_path: rxopts.amend_path, // by default amend value with path hook: rxopts.hook, // function called when setting - namespace: rxopts.namespace || 'rx', // TODO namespace to added to all public rx methods + namespace: this.$$__rxns__$$, // TODO namespace to added to all public rx methods operators: new Map(), // TODO added to all rx pipes, in order skip: rxopts.skip == null ? 1 : rxopts.skip } - this.isRx.bind(this) - } + console.log(`${rxopts.getSetPrefix || ''}set`) + $set(this, [rxopts.getSetNamespace, `${rxopts.getSetPrefix || ''}set`], set.bind(this)) + $set(this, [rxopts.getSetNamespace, `${rxopts.getSetPrefix || ''}get`], get.bind(this)) + console.log(this) + } // end constructor + // reactive methods are bound at instantiation +} // end class - get (path) { - const value = this.#get(path) - if (!check.isPlainObject(value)) return value - const obj = {} // return a copy with actual values instead of getters - const self = this - traverse(this.#get(path)).map(function () { - if (this.isLeaf) { - self.#set(obj, this.path, this.node) - } - } - ) - return obj - } +// helpers - set (path, value, opts = {}) { - console.log('set:', path, value, opts, !this.isRx(path)) - if (!opts.noRx && !this.isRx(path)) { // travsere - opts = Object.assign( - (check.isPlainObject(value) && opts.traverse !== false) - ? { values: value, traverse: true } - : { value: value } - , opts) - console.log('adding rx from set', opts) - this.rxAdd(path, opts) - } else { - const curValue = this.#get(path) - if (!equal(curValue, value) && value !== undefined) { - console.log('setting already reactive value', path, value) - this.#set(path, value) - console.log('value that was set', this.#get(path)) - } - return this.#get(path) +export default RxClass +export { RxClass, $set as set, $get as get, $del as del, getKeys, walkPath, pathToString } + +function get (path) { + const value = _.get.call(this, path) + if (!check.isPlainObject(value)) return value + const obj = {} // return a copy with actual values instead of getters + const self = this + traverse(_.get.call(this, path)).map(function () { + if (this.isLeaf) { + _.set.call(self, obj, this.path, this.node) } } + ) + return obj +} - getRaw () { - return get(this, [this.#rx.namespace, ...arguments]) +function set (path, value, opts = {}) { + console.log('set:', path, value, opts, !this[this.$$__rxns__$$].isRx(path)) + if (!opts.noRx && !this[this.$$__rxns__$$].isRx(path)) { // travsere + opts = Object.assign( + (check.isPlainObject(value) && opts.traverse !== false) + ? { values: value, traverse: true } + : { value: value } + , opts) + console.log('adding rx from set', opts) + this[this.$$__rxns__$$].add(path, opts) + } else { + const curValue = _.get.call(this, path) + if (!equal(curValue, value) && value !== undefined) { + console.log('setting already reactive value', path, value) + _.set.call(this, path, value) + console.log('value that was set', _.get.call(this, path)) + } + return _.get.call(this, path) } +} - // if property exists it moves value to #rx.props and creates getter and setter and behavior subject - rxAdd (path, opts = {}) { +// public methods will be namespaced (default is rx.) +const methods = { + add: function add (path, opts = {}) { if (opts.traverse) { // will add rx to all leaves - const obj = opts.values ? opts.values : this.#get(path) + const obj = opts.values ? opts.values : _.get.call(this, path) const self = this // console.log('object to traverse', obj) traverse(obj).map(function () { - // console.log('traversing', this) + // console.log('traversing', this) if (this.isLeaf) { const lpath = this.path lpath.unshift(path) // console.log('#{!opts.values ? \'existing\':\'new\'} leaf on path \'#{lpath}\' with value: #{this.node}') - self.rxAdd(lpath, { value: this.node }) + self[this.$$__rxns__$$].add(lpath, { value: this.node }) } } ) return true } // end traverse - const value = this.#get(path) // current value + const value = _.get.call(this, path) // current value if (value === undefined) { - // console.log('no current property or value for', path, 'creating temporary null') - this.#set(path, null) + // console.log('no current property or value for', path, 'creating temporary null') + _.set.call(this, path, null) } - if (this.isRx(path) && !opts.force) return true + if (this[this.$$__rxns__$$].isRx(path) && !opts.force) return true console.log(path, 'making reactive') - const { parent, name } = this.#rxGetObj(path, '__parent__') + const { parent, name } = _.rxGetObj.call(this, path, '__parent__') log.debug({ class: 'RxClass', - method: '#rxGetObj', + method: 'rx.add', path: path, name: name, parent: parent, msg: 'got name and parent' }) - this.#set(['#rx.props', path], {}) - const rx = this.#get(['#rx.props', path]) + _.set.call(this, [this.$$__rxns__$$, 'props', path], {}) + const rx = _.get.call(this, [this.$$__rxns__$$, 'props', path]) // console.log('moving',opts.value != null ? opts.value : value,path) rx.value = opts.value != null ? opts.value : value rx.path = path - rx.amendValue = opts.amendValue || this.#rx.amendValue || ((val) => val) + rx.amendValue = opts.amendValue || this[this.$$__rxns__$$].opts.amendValue || ((val) => val) rx.obs = from(new BehaviorSubject(rx.amendValue(rx.value, rx.path)).pipe( - skip(opts.skip != null ? opts.skip : this.#rx.skip), + skip(opts.skip != null ? opts.skip : this[this.$$__rxns__$$].opts.skip), distinctUntilChanged() // allow custom comparator - // allow custom/additional operators here - // takeUntil(#get(this.#deleted,path)) + // allow custom/additional operators here + // takeUntil(#get(this.#deleted,path)) )) // console.log(path,'---------------\n',Object.getOwnPropertyNames(Object.getPrototypeOf(rx.obs)),rx.obs.subscribe) rx.subs = {} rx.hook = opts.hook - if (check.isFunction(this.#rx.handler)) this.rxSubscribe(rx, this.#rx.handler, '_default_') + if (check.isFunction(this[this.$$__rxns__$$].opts.handler)) this[this.$$__rxns__$$].subscribe(rx, this[this.$$__rxns__$$].opts.handler, '_default_') const subs = Object.entries(opts.subscribe || {}) subs.forEach(sub => { - if (check.isFunction(sub[1])) this.rxSubscribe(rx, sub[1], sub[0]) + if (check.isFunction(sub[1])) this[this.$$__rxns__$$].subscribe(rx, sub[1], sub[0]) }) const self = this Object.defineProperty(parent, name, { @@ -141,9 +150,9 @@ class RxClass extends EventEmitter { rx.value = value value = rx.amendValue(value, path) rx.obs.next(value) // react - if (self.#rx.emitter) { // emit + if (self[this.$$__rxns__$$].opts.emitter) { // emit if (opts.event) self.emit(opts.event, value, path) // custom event - if (self.#rx.event) self.emit(self.#rx.event, value, path) // global event + if (self[this.$$__rxns__$$].opts.event) self.emit(self[this.$$__rxns__$$].opts.event, value, path) // global event const spath = pathToString(path) if (spath) self.emit(spath, value, path) // also emit path if is stringable. self.emit(name, value, path) @@ -151,52 +160,47 @@ class RxClass extends EventEmitter { // any hook function that is already bound will use that context, otherwise this // hooks // TODO write with for loop and async await - if (check.isFunction(self.#rx.hook)) self.#rx.hook.call(self, value) // global hook + if (check.isFunction(self[this.$$__rxns__$$].opts.hook)) self[this.$$__rxns__$$].opts.hook.call(self, value) // global hook if (check.isFunction(rx.hook)) rx.hook.call(self, value) // property hook } }) - // console.log('rxadd done', path, opts, this.#get(['#rx.props', path])) + // console.log('rxadd done', path, opts, _.get.call(this,[this.$$__rxns__$$,'props', path])) return true - } + }, - // if name is only passed remove - // rxHook (func, path) { - // this.#rx.hooks[name] = func - // } - - rxAmendValue (path, func) { - if (arguments.length === 0) { this.#rx.amendValue = null; return } - if (typeof path === 'function') { this.#rx.amendValue = path; return } - const rx = this.#rxGetObj(path) + amendValue: function amendValue (path, func) { + if (arguments.length === 0) { this[this.$$__rxns__$$].opts.amendValue = null; return } + if (typeof path === 'function') { this[this.$$__rxns__$$].opts.amendValue = path; return } + const rx = _.rxGetObj.call(this, path) if (!check.isEmptyPlainObject(rx)) return false func = func || (val => val) rx.amendValue = func === 'path' ? (value, path) => { return { value: value, path: path } } : func return !!rx.amendValue - } + }, // if name is only passed remove - rxOperator (func, name) { - this.#rx.operators[name] = func - } + operator: function Operator (func, name) { + this[this.$$__rxns__$$].opts.operators[name] = func + }, // removes obser, subscriptions, and getter and setter and make back into plain value - rxRemove (path, opts = {}) { + remove: function remove (path, opts = {}) { if (!opts.confirm) return false // must confirm to remove - if (!this.isRx(path) && check.isPlainObject(this.#get(path))) { + if (!this[this.$$__rxns__$$].isRx(path) && check.isPlainObject(_.get.call(this, path))) { const self = this - traverse(this.#get(path)).map(function () { + traverse(_.get.call(this, path)).map(function () { if (this.isLeaf) { const lpath = this.path lpath.unshift(path) - if (self.isRx(lpath)) self.rxRemove(lpath, { confirm: true }) + if (self[this.$$__rxns__$$].isRx(lpath)) self[this.$$__rxns__$$].remove(lpath, { confirm: true }) } }) // all done removing the leaves so remove branch - this.#del(['#rx.props', path], true) + _.del.call(this, [this.$$__rxns__$$, 'props', path], true) return true } - const { parent, name, parentPath } = this.#rxGetObj(path, '__parent__') - const rxparent = this.#get(['#rx.props', parentPath]) + const { parent, name, parentPath } = _.rxGetObj.call(this, path, '__parent__') + const rxparent = _.get.call(this, [this.$$__rxns__$$, 'props', parentPath]) const rx = rxparent[name] if (!rx) return true // // not reactive nothing to remove Object.values(rx.subs).forEach(sub => sub.unsubscribe()) @@ -205,22 +209,21 @@ class RxClass extends EventEmitter { delete rxparent[name] // console.log('removed rx from', path) return true - } + }, - rxGetObs (path) { - return (this.#rxGetObj(path) || {}).obs - } + getObs: function getObs (path) { + return (_.rxGetObj.call(this, path) || {}).obs + }, - rxGetSubs (path, name) { - const rx = this.#rxGetObj(path) + getSubs: function getSubs (path, name) { + const rx = _.rxGetObj.call(this, path) if (rx) return (name ? rx.subs[name] : Object.keys(rx.subs)) - } - + }, // pass rx as object or path - rxSubscribe (path, handler, name) { - const rx = this.#rxGetObj(path) + subscribe: function subscribe (path, handler, name) { + const rx = _.rxGetObj.call(this, path) if (rx) { - // TODO if no name generate a name and return with handle + // TODO if no name generate a name and return with handle if (check.isFunction(handler)) { if (rx.subs[name]) rx.subs[name].unsubscribe() // replace if exits rx.subs[name] = rx.obs.subscribe(handler) @@ -229,10 +232,10 @@ class RxClass extends EventEmitter { } } return false - } + }, - rxRemoveSubs (path, name) { - const rx = this.#rxGetObj(path) + removeSubs: function removeSubs (path, name) { + const rx = _.rxGetObj.call(this, path) // if (name && name!=='_default' && (rxObj||{}).path) console.log('rx object for',rxObj.path) if (rx) { if (name) { @@ -248,122 +251,71 @@ class RxClass extends EventEmitter { } } return false - } - - isGetSet (path) { + }, + isGetSet: function isGetSet (path) { const keys = getKeys(path) const prop = keys.pop() - const parent = this.#get(keys) + const parent = _.get.call(this, keys) // console.log('isgetset', path, keys, prop, 'parent?', !!parent) if (parent && prop) { return !!(Object.getOwnPropertyDescriptor(parent, prop) || {}).get && !!(Object.getOwnPropertyDescriptor(parent, prop) || {}).set } return false - } - + }, // is already reactive, checks for setter/getter and the corresponding rx object - isRx (path) { - // console.log('in isRX', path) + isRx: function isRx (path) { + // console.log('in isRX', path) const keys = getKeys(path) const cnt = keys.length for (let index = 0; index < cnt; index++) { - // console.log('testing rx', index, cnt, keys, !!this.#get(keys), !!this.isGetSet(keys), !!this.#get(['#rx.props', keys])) - if (this.#get(keys) === undefined) return false - if (this.isGetSet(keys) && this.#get(['#rx.props', keys])) return true + // console.log('testing rx', index, cnt, keys, !!_.get.call(this,keys), !!this.isGetSet(keys), !!_.get.call(this,[this.$$__rxns__$$,'props', keys])) + if (_.get.call(this, keys) === undefined) return false + if (this[this.$$__rxns__$$].isGetSet(keys) && _.get.call(this, [this.$$__rxns__$$, 'props', keys])) return true keys.pop() } return false - // // console.log('in rx every', lpath) - // // if (this.#get(['#rx.props', ...lpath]) === undefined) { - // // return false - // // } - // // - // console.log(parent, name, parentPath) - // const rxparent = this.#get(['#rx.props', parentPath]) || {} - // const rx = rxparent[name] - // console.log(path, 'rxparent,rx', !!parent, !!name, !!rxparent, !!rx) - // if (!rx) return true - // else { - // console.log(Object.getOwnPropertyDescriptor(parent, name).get) - // isRx = !!Object.getOwnPropertyDescriptor(parent, name).get - // return false - // } - // }) - // return isRx } +} - // isRx(path) { - // // console.log(path) - // let isRx = false - // const lpath = [] - // getKeys(path).every(key => { - // lpath.push(key) - // console.log('in rx every', lpath, this.#get(lpath)) - // if (this.#get(lpath) === undefined) { - // return false - // } - // // const { parent, name, parentPath } = this.#rxGetObj(lpath, '__parent__') - // // console.log(parent, name, parentPath) - // const rxparent = this.#get(['#rx.props', parentPath]) || {} - // const rx = rxparent[name] - // console.log(path, 'rxparent,rx', !!parent, !!name, !!rxparent, !!rx) - // if (!rx) return true - // else { - // console.log(Object.getOwnPropertyDescriptor(parent, name).get) - // isRx = !!Object.getOwnPropertyDescriptor(parent, name).get - // return false - // } - // }) - // return isRx - // } +// private methods - // PRIVATE METHODS - - // pass '__parent__' when getting objects for creating reactive prop - // otherwise it gets the rx prop object and/or one of it's props - #rxGetObj (path, prop) { - log.debug({ class: 'RxClass', method: '#rxGetObj', path: path, prop: prop, msg: 'getting rx object' }) +const _ = { + rxGetObj: function rxGetObj (path, prop) { + log.debug({ class: 'RxClass', method: '_.rxGetObj', path: path, prop: prop, msg: 'getting rx object' }) let name, parent, parentPath, rx // if (!path) parent = this if (check.isString(path) || check.isArray(path)) { // it's a normal path const keys = getKeys(path) name = (keys || []).pop() - parent = this.#get(keys) + parent = _.get.call(this, keys) parentPath = keys - log.debug({ method: '#rxGetObj', path: path, prop: prop, key: name, parent: parent, msg: 'getting rx object' }) + log.debug({ method: '_.rxGetObj', path: path, prop: prop, key: name, parent: parent, msg: 'getting rx object' }) if (parent === null) parent = {} if (prop === '__parent__') return { name: name, parent: parent, parentPath: parentPath } - rx = this.#get(['#rx.props', path]) + rx = _.get.call(this, [this.$$__rxns__$$, 'props', path]) } else rx = check.isPlainObject(path) ? path : rx // if path was plain object assume it's already rx object return prop ? (rx || {})[prop] : rx - } - - #set () { + }, + set: function set () { const args = [...arguments] if (args.length < 2) return false if (args.length === 2) args.unshift(this) else if (!args[0]) return false // console.log('in #set',args[1],args[2]) - return set(...args) - } - - #get () { + return $set(...args) + }, + get: function get () { const args = [...arguments] if (args.length === 1) args.unshift(this) - return get(...args) - } - - #del () { + return $get(...args) + }, + del: function del () { const args = [...arguments] if (args.length < 2) return false if (args[args.length - 1] !== true) return false if (args.length === 2) args.unshift(this) - return del(...args) + return $del(...args) } -} // end class -// helpers - -export default RxClass -export { RxClass, set, get, del, getKeys, pathToString } +} diff --git a/test/rxclass.test.js b/test/rxclass.test.js index 3854e0c..fc578f6 100644 --- a/test/rxclass.test.js +++ b/test/rxclass.test.js @@ -1,4 +1,4 @@ -import assert from 'assert' +import assert, { doesNotMatch } from 'assert' import { RxClass } from '../src/rx-class.js' const rxopts = Symbol.for('rx-class-opts') @@ -8,6 +8,8 @@ const obj = { today: { bing: 'bing', bong: { who: 'first', what: 'second' } } } +const foundation = { planets: ['Trantor', 'Helcion'], persons: { giskard: { alias: 'daneel' }, hari: { name: 'seldon', planet: 'Helcion' } } } + class Test extends RxClass { constructor (opts = {}) { super(opts) @@ -17,121 +19,151 @@ class Test extends RxClass { const opts = { [rxopts]: { - skip: 1, - handler: () => {} + // handler: () => {} + handler: (value) => { console.log('default subscription handler', value) } } } const test = new Test(opts) +opts[rxopts].namespace = 'rx2' +opts[rxopts].getSetPrefix = '_' +opts[rxopts].getSetNamespace = '$' +const test2 = new Test(opts) describe('Reactive Class', function () { it('Can be extended', function () { assert.deepStrictEqual(test.obj, obj) }) - it('Can get non rx deep value', function () { - assert.strictEqual(test.obj.today.bing, 'bing') - assert.strictEqual(test.get('obj.today.bing'), 'bing') - assert(!test.isRx('obj.today.bing')) - }) + // it('Can get non rx deep value', function () { + // assert.strictEqual(test.obj.today.bing, 'bing') + // assert.strictEqual(test.get('obj.today.bing'), 'bing') + // assert(!test.isRx('obj.today.bing')) + // }) - it('rx added to value', function () { - test.rxAdd('obj.today.bing') - assert.strictEqual(test.obj.today.bing, 'bing') - assert.strictEqual(test.get('obj.today.bing'), 'bing') - assert(test.isRx('obj.today.bing')) + // it('can make a property reactive', function () { + // test.rxAdd('obj.today.bing') + // assert(test.isRx('obj.today.bing')) + // }) + + it('can make a property containing object reactive', function () { + // test.foundation = foundation + test.set('foundation.test', foundation, { traverse: false }) + test2.$._set('foundation.test', foundation, { traverse: false }) + // console.log(test.foundation) + // test.rx.set('foundation.test.persons.jimbo', { name: 'jim', plant: 'remulak' }) + // console.log(test.foundation.test) + assert(test.rx.isRx('foundation.test')) + assert(test2.rx2.isRx('foundation.test')) + // assert(test.rx.isRx('foundation.test.persons')) + // assert(test.rx.isRx('foundation.test.persons.giskard.alias')) + // assert(!test.rx.isRx('foundation.test.persons.bogus')) }) }) -describe('Emitted Events', function () { - function etest (event, value, path, done) { - test.once(event, (v, p) => { - // console.log('fired:', event, value, path) - assert.strictEqual(v, value) - assert.strictEqual(p, path) - clearTimeout(timeout) - done() - }) - const timeout = setTimeout(() => { - assert(false, `Event ${event} did not fire in 1000 ms.`) - done() - }, 1000) - test.set(path, value) - } +// describe('Emitted Events', function () { +// function etest (event, value, path, done) { +// test.once(event, (v, p) => { +// // console.log('fired:', event, value, path) +// assert.strictEqual(v, value) +// assert.strictEqual(p, path) +// clearTimeout(timeout) +// done() +// }) +// const timeout = setTimeout(() => { +// assert(false, `Event ${event} did not fire in 1000 ms.`) +// done() +// }, 1000) +// test.set(path, value) +// } - const path = 'obj.today.bing' - it('should emit global default event', function (done) { - const event = 'changed' - etest(event, event, path, done) - }) - it('should emit path if stringable', function (done) { - const event = path - etest(event, event, path, done) - }) +// const path = 'obj.today.bing' +// it('should emit global default event', function (done) { +// const event = 'changed' +// etest(event, event, path, done) +// }) +// it('should emit path if stringable', function (done) { +// const event = path +// etest(event, event, path, done) +// }) - it('should emit the key name of value being changed', function (done) { - const event = path.split('.').pop() - etest(event, event, path, done) - }) +// it('should emit the key name of value being changed', function (done) { +// const event = path.split('.').pop() +// etest(event, event, path, done) +// }) - it('should emit a custom set event for key/value change after force rxAdd', function (done) { - const event = 'testing' - test.rxAdd('obj.today.bing', { force: true, event: 'testing' }) - etest(event, event, path, done) - }) -}) // end emits +// it('should emit a custom set event for key/value change after force rxAdd', function (done) { +// const event = 'testing' +// test.rxAdd('obj.today.bing', { force: true, event: 'testing' }) +// etest(event, event, path, done) +// }) +// }) // end emits -describe('Subscriptions', function () { - const subsHandler = (value, timeout, name, done, v) => { - assert.strictEqual(v, value) - clearTimeout(timeout) - test.rxRemoveSubs(name) - done() - } - function subs (name, path, value, done, handler) { - const timeout = setTimeout(() => { - assert(false, 'subscription handler did not react in 1000 ms.') - done() - }, 1000) - const subscribe = handler ? { [handler]: subsHandler.bind(null, value, timeout, name, done) } : {} - test.rxAdd(path, { force: true, subscribe: subscribe }) - if (!handler) { - test.rxSubscribe('obj.today.bing', - subsHandler.bind(null, value, timeout, name, done) - , name) - } - test.set(path, value) - } +// describe('Subscriptions', function () { +// const subsHandler = (value, timeout, name, done, v) => { +// assert.strictEqual(v, value) +// clearTimeout(timeout) +// test.rxRemoveSubs(name) +// done() +// } +// function subs (name, path, value, done, handler) { +// const timeout = setTimeout(() => { +// assert(false, 'subscription handler did not react in 1000 ms.') +// done() +// }, 1000) +// const subscribe = handler ? { [handler]: subsHandler.bind(null, value, timeout, name, done) } : {} +// test.rxAdd(path, { force: true, subscribe: subscribe }) +// if (!handler) { +// test.rxSubscribe('obj.today.bing', +// subsHandler.bind(null, value, timeout, name, done) +// , name) +// } +// test.set(path, value) +// } - const path = 'obj.today.bing' - it('should react to default subscription', function (done) { - const name = '_default_' - subs(name, path, name, done, name) - }) - it('should react to property/key subscription', function (done) { - const name = 'keytest' - subs(name, path, name, done, name) - }) - it('should react to new subscription', function (done) { - const name = 'atest' - subs(name, path, name, done) - }) -}) // end subscriptions +// const path = 'obj.today.bing' +// it('should react to default subscription', function (done) { +// const name = '_default_' +// subs(name, path, name, done, name) +// }) +// it('should react to property/key subscription', function (done) { +// const name = 'keytest' +// subs(name, path, name, done, name) +// }) +// it('should react to new subscription', function (done) { +// const name = 'atest' +// subs(name, path, name, done) +// }) +// }) // end subscriptions -describe('TODO: Amend,Hooks,Operators', function () { - it('should run a default hook', function (done) { - done() - }) +// describe('TODO: Amend,Hooks,Operators', function () { +// it('should run a default hook', function (done) { +// done() +// }) - it('should run a custom hook', function (done) { - done() - }) +// it('should run a custom hook', function (done) { +// done() +// }) - it('should support custom rxjs operators', function (done) { - done() - }) +// it('should support custom rxjs operators', function (done) { +// done() +// }) - it('should amend a value that is set', function (done) { - done() - }) -}) // end hooks +// it('should amend a value that is set', function (done) { +// done() +// }) +// }) // end hooks + +// describe('TODO: Misc', function () { +// it('get should return values of object nested props', function (done) { +// done() +// }) + +// it('set should make all nested leaves reactive if traverse is requested', function (done) { +// done() +// }) + +// it('should remove rx for property', function (done) { +// done() +// }) +// }) // end hooks