@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
222 lines (184 loc) • 7.42 kB
JavaScript
import './setup';
import {TestObservationAdapter} from './adapter';
import {DirtyCheckProperty} from '../src/dirty-checking';
import {SetterObserver} from '../src/property-observation';
import {
ValueAttributeObserver,
XLinkAttributeObserver,
DataAttributeObserver,
StyleObserver
} from '../src/element-observation';
import {SelectValueObserver} from '../src/select-value-observer';
import {CheckedObserver} from '../src/checked-observer';
import {createElement, createObserverLocator} from './shared';
import {FEATURE} from 'aurelia-pal';
import {Logger} from 'aurelia-logging';
describe('ObserverLocator', () => {
var locator;
beforeAll(() => {
locator = createObserverLocator();
locator.addAdapter(new TestObservationAdapter(() => locator));
});
it('uses SetterObserver for defined, primitive properties on pojos', () => {
var obj = { foo: 'bar' },
observer = locator.getObserver(obj, 'foo');
expect(observer instanceof SetterObserver).toBe(true);
});
it('uses SetterObserver for ad-hoc properties on Elements', () => {
var obj = createElement('<h1></h1>'),
observer;
obj.foo = 'bar';
observer = locator.getObserver(obj, 'foo');
expect(obj instanceof Element).toBe(true);
expect(observer instanceof SetterObserver).toBe(true);
});
it('warns when its not able to subscribe to a SetterObserver', () => {
spyOn(Logger.prototype, 'warn');
var obj = {};
Object.defineProperties(obj, {
'foo': {configurable: true},
'bar': {configurable: false}
});
locator.getObserver(obj, 'foo').convertProperty();
expect(Logger.prototype.warn).not.toHaveBeenCalled();
locator.getObserver(obj, 'bar').convertProperty();
expect(Logger.prototype.warn).toHaveBeenCalledWith('Cannot observe property \'bar\' of object', obj);
});
it('respects the enumerability in the SetterObserver', () => {
var obj = {};
Object.defineProperties(obj, {
'foo': {
configurable: true,
enumerable: true
},
'bar': {
configurable: true,
enumerable: false
}
});
['foo', 'bar', 'baz'].forEach(prop => {
locator.getObserver(obj, prop).convertProperty();
});
expect(Object.keys(obj)).toEqual(['foo', 'baz']);
});
it('uses ValueAttributeObserver for element value attributes', () => {
var obj = createElement('<input />'),
observer = locator.getObserver(obj, 'value');
expect(observer instanceof ValueAttributeObserver).toBe(true);
obj = createElement('<textarea></textarea'),
observer = locator.getObserver(obj, 'value');
expect(observer instanceof ValueAttributeObserver).toBe(true);
});
it('uses DataAttributeObserver for data-* attributes', () => {
var obj = createElement('<input data-foo="bar" />'),
observer = locator.getObserver(obj, 'data-foo');
expect(observer instanceof DataAttributeObserver).toBe(true);
});
it('uses DataAttributeObserver for aria-* attributes', () => {
var obj = createElement('<input aria-hidden="bar" />'),
observer = locator.getObserver(obj, 'aria-hidden');
expect(observer instanceof DataAttributeObserver).toBe(true);
});
it('uses CheckedObserver for input checked attributes', () => {
var obj = createElement('<input />'),
observer = locator.getObserver(obj, 'checked');
expect(observer instanceof CheckedObserver).toBe(true);
});
it('uses SelectValueObserver for select value attributes', () => {
var obj = createElement('<select></select>'),
observer = locator.getObserver(obj, 'value');
expect(observer instanceof SelectValueObserver).toBe(true);
});
it('uses StyleObserver for style attributes', () => {
var obj = createElement('<select></select>'),
observer = locator.getObserver(obj, 'style');
expect(observer instanceof StyleObserver).toBe(true);
});
it('uses StyleObserver for css attributes', () => {
var obj = createElement('<select></select>'),
observer = locator.getObserver(obj, 'css');
expect(observer instanceof StyleObserver).toBe(true);
});
it('uses DirtyCheckProperty for defined, complex properties on pojos', () => {
var obj = {}, foo, observer;
Object.defineProperty(obj, 'foo', {
get: function() { return foo; },
set: function(newValue) { foo = newValue; },
enumerable: true,
configurable: true
});
observer = locator.getObserver(obj, 'foo');
expect(observer instanceof DirtyCheckProperty).toBe(true);
});
it('uses custom getObserver when provided on the descriptor\'s getter', () => {
var obj = {},
foo,
customObserver = {},
observer,
descriptor = {
get: function() { return foo; },
set: function(newValue) { foo = newValue; },
enumerable: true,
configurable: true
};
descriptor.get.getObserver = function(obj){
customObserver.target = obj;
return customObserver;
};
Object.defineProperty(obj, 'foo', descriptor);
observer = locator.getObserver(obj, 'foo');
expect(observer).toBe(customObserver);
expect(observer.target).toBe(obj);
});
it('uses custom getObserver when provided on the descriptor\'s setter without getter', () => {
var obj = {},
foo,
customObserver = {},
observer,
descriptor = {
set: function(newValue) { foo = newValue; },
enumerable: true,
configurable: true
};
descriptor.set.getObserver = function(obj){
customObserver.target = obj;
return customObserver;
};
Object.defineProperty(obj, 'foo', descriptor);
observer = locator.getObserver(obj, 'foo');
expect(observer).toBe(customObserver);
expect(observer.target).toBe(obj);
});
it('warns when its not able to bind to an object', () => {
spyOn(Logger.prototype, 'warn');
var obj1 = {};
locator.createObserversLookup(obj1);
expect(Logger.prototype.warn).not.toHaveBeenCalled();
expect('__observers__' in obj1).toBeTruthy();
var obj2 = Object.seal({});
locator.createObserversLookup(obj2);
expect(Logger.prototype.warn).toHaveBeenCalledWith('Cannot add observers to object', obj2);
expect('__observers__' in obj2).toBeFalsy();
});
it('uses Adapter for for defined, complex properties on pojos that adapter canObserve', () => {
var obj = { handleWithAdapter: true }, foo, observer, adapter, descriptor;
adapter = locator.adapters[0];
spyOn(adapter, 'getObserver').and.callThrough();
Object.defineProperty(obj, 'foo', {
get: function() { return foo; },
set: function(newValue) { foo = newValue; },
enumerable: true,
configurable: true
});
descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
observer = locator.getObserver(obj, 'foo');
expect(observer.___from_adapter).toBe(true);
expect(adapter.getObserver).toHaveBeenCalledWith(obj, 'foo', descriptor);
});
it('getAccessor returns ValueAttributeObserver for input.value', () => {
expect(locator.getAccessor(document.createElement('input'), 'value') instanceof ValueAttributeObserver).toBe(true);
});
it('getAccessor returns SetterObserver for input.model', () => {
expect(locator.getAccessor(document.createElement('input'), 'model') instanceof SetterObserver).toBe(true);
});
});