@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
596 lines (535 loc) • 19.6 kB
JavaScript
import './setup';
import {DOM} from 'aurelia-pal';
import {bindingMode} from '../src/binding-mode';
import {
createElement,
checkDelay,
createObserverLocator,
getBinding
} from './shared';
import {createScopeForTest} from '../src/scope';
describe('SelectValueObserver', () => {
var observerLocator;
beforeAll(() => {
observerLocator = createObserverLocator();
});
function getElementValue(element) {
var options = element.options, option, i, ii, count = 0, value = [];
for(i = 0, ii = options.length; i < ii; i++) {
option = options.item(i);
if (!option.selected) {
continue;
}
value[count] = option.hasOwnProperty('model') ? option.model : option.value;
count++;
}
if (!element.multiple) {
if (count === 0) {
value = null;
} else {
value = value[0];
}
}
return value;
}
describe('single-select strings', () => {
var obj, el, binding;
beforeAll(() => {
obj = { selectedItem: 'B' };
el = createElement(
`<select>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>`);
document.body.appendChild(el);
binding = getBinding(observerLocator, obj, 'selectedItem', el, 'value', bindingMode.twoWay).binding;
});
it('binds', () => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'bind').and.callThrough();
binding.bind(createScopeForTest(obj));
expect(targetObserver.bind).toHaveBeenCalled();
expect(el.value).toBe(obj.selectedItem);
});
it('responds to model change', done => {
obj.selectedItem = 'C';
setTimeout(() => {
expect(el.value).toBe(obj.selectedItem);
done();
}, 0);
});
it('responds to element change', done => {
el.value = 'A';
el.dispatchEvent(DOM.createCustomEvent('change'));
setTimeout(() => {
expect(el.value).toBe(obj.selectedItem);
done();
}, 0);
});
it('responds to options change', done => {
var option = document.createElement('option');
option.value = option.text = 'D';
obj.selectedItem = 'D';
el.appendChild(option);
setTimeout(() => {
expect(el.value).toBe(obj.selectedItem);
el.innerHTML =
`<option value="X">X</option>
<option value="Y">Y</option>
<option value="Z">Z</option>`;
setTimeout(() => {
expect(el.value).toBe('X');
expect(el.value).toBe(obj.selectedItem);
done();
});
}, 0);
});
it('notifies', () => {
let targetObserver = binding.targetObserver;
let spy = jasmine.createSpy('callback');
let oldValue = binding.targetObserver.getValue();
let newValue = [];
targetObserver.subscribe(spy);
targetObserver.setValue(newValue);
expect(spy).toHaveBeenCalledWith(newValue, oldValue);
});
it('unbinds', () => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'unbind').and.callThrough();
binding.unbind();
expect(targetObserver.unbind).toHaveBeenCalled();
});
afterAll(() => {
document.body.removeChild(el);
});
});
describe('multi-select strings', () => {
var obj, el, binding, elementValueProperty;
beforeAll(() => {
var info;
obj = { selectedItems: ['B', 'C'] };
el = createElement(
`<select multiple>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>`);
document.body.appendChild(el);
info = getBinding(observerLocator, obj, 'selectedItems', el, 'value', bindingMode.twoWay);
binding = info.binding;
elementValueProperty = info.targetObserver;
});
it('binds', () => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'bind').and.callThrough();
binding.bind(createScopeForTest(obj));
expect(targetObserver.bind).toHaveBeenCalled();
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
});
it('responds to model change', done => {
obj.selectedItems = ['A'];
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
});
it('responds to model mutate', done => {
obj.selectedItems.pop();
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
obj.selectedItems.push('B');
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
}, 0);
});
it('responds to element change', done => {
el.options.item(1).selected = true;
el.options.item(2).selected = true;
el.dispatchEvent(DOM.createCustomEvent('change'));
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
});
it('responds to options change', done => {
var option = document.createElement('option');
option.value = option.text = 'D';
obj.selectedItems = ['D'];
el.appendChild(option);
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
});
it('unbinds', () => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'unbind').and.callThrough();
binding.unbind();
expect(targetObserver.unbind).toHaveBeenCalled();
});
afterAll(() => {
document.body.removeChild(el);
});
});
describe('single-select objects', () => {
var obj, el, binding, elementValueProperty;
beforeAll(() => {
var option, info;
el = createElement('<select></select>');
option = document.createElement('option');
option.text = 'A';
option.model = { foo: 'A' };
el.appendChild(option);
option = document.createElement('option');
option.text = 'B';
option.model = { foo: 'B' };
el.appendChild(option);
option = document.createElement('option');
option.text = 'C';
option.model = { foo: 'C' };
el.appendChild(option);
document.body.appendChild(el);
obj = { selectedItem: el.options.item(2).model };
info = getBinding(observerLocator, obj, 'selectedItem', el, 'value', bindingMode.twoWay);
binding = info.binding;
elementValueProperty = info.targetObserver;
});
it('binds', () => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'bind').and.callThrough();
binding.bind(createScopeForTest(obj));
expect(targetObserver.bind).toHaveBeenCalled();
expect(elementValueProperty.getValue()).toBe(obj.selectedItem);
expect(getElementValue(el)).toEqual(obj.selectedItem);
});
it('responds to model change', done => {
obj.selectedItem = el.options.item(0).model;
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItem);
expect(getElementValue(el)).toEqual(obj.selectedItem);
done();
}, 0);
});
it('responds to element change', done => {
el.options.item(0).selected = false;
el.options.item(1).selected = true;
el.dispatchEvent(DOM.createCustomEvent('change'));
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItem);
expect(getElementValue(el)).toEqual(obj.selectedItem);
done();
}, 0);
});
it('responds to options change', done => {
var option = document.createElement('option');
option.text = 'D';
option.model = { foo: 'D' };
obj.selectedItem = option.model;
el.appendChild(option);
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItem);
expect(getElementValue(el)).toEqual(obj.selectedItem);
done();
}, 0);
});
it('unbinds', () => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'unbind').and.callThrough();
binding.unbind();
expect(targetObserver.unbind).toHaveBeenCalled();
});
afterAll(() => {
document.body.removeChild(el);
});
});
describe('single-select objects with matcher', () => {
var obj, el, binding, elementValueProperty;
beforeAll(() => {
var option, info;
el = createElement('<select></select>');
option = document.createElement('option');
option.text = 'A';
option.model = { foo: 'A' };
el.appendChild(option);
option = document.createElement('option');
option.text = 'B';
option.model = { foo: 'B' };
el.appendChild(option);
option = document.createElement('option');
option.text = 'C';
option.model = { foo: 'C' };
el.appendChild(option);
document.body.appendChild(el);
obj = { selectedItem: { foo: 'B' } };
info = getBinding(observerLocator, obj, 'selectedItem', el, 'value', bindingMode.twoWay);
binding = info.binding;
elementValueProperty = info.targetObserver;
});
it('binds', done => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'bind').and.callThrough();
binding.bind(createScopeForTest(obj));
expect(targetObserver.bind).toHaveBeenCalled();
el.matcher = (a, b) => a.foo === b.foo;
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItem);
expect(getElementValue(el)).toEqual(obj.selectedItem);
done();
});
});
it('responds to model change', done => {
obj.selectedItem = { foo: 'A' };
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItem);
expect(getElementValue(el)).toEqual(obj.selectedItem);
done();
}, 0);
});
it('responds to element change', done => {
el.options.item(0).selected = false;
el.options.item(1).selected = true;
el.dispatchEvent(DOM.createCustomEvent('change'));
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItem);
expect(getElementValue(el)).toEqual(obj.selectedItem);
done();
}, 0);
});
it('responds to options change', done => {
var option = document.createElement('option');
option.text = 'D';
option.model = { foo: 'D' };
obj.selectedItem = { foo: 'D' };
el.appendChild(option);
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItem);
expect(getElementValue(el)).toEqual(obj.selectedItem);
done();
}, 0);
});
it('unbinds', () => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'unbind').and.callThrough();
binding.unbind();
expect(targetObserver.unbind).toHaveBeenCalled();
});
afterAll(() => {
document.body.removeChild(el);
});
});
describe('multi-select objects', () => {
var obj, el, binding, elementValueProperty;
beforeAll(() => {
var option, info;
el = createElement('<select multiple></select>');
option = document.createElement('option');
option.text = 'A';
option.model = { foo: 'A' };
el.appendChild(option);
option = document.createElement('option');
option.text = 'B';
option.model = { foo: 'B' };
el.appendChild(option);
option = document.createElement('option');
option.text = 'C';
option.model = { foo: 'C' };
el.appendChild(option);
document.body.appendChild(el);
obj = { selectedItems: [el.options.item(1).model, el.options.item(2).model] };
info = getBinding(observerLocator, obj, 'selectedItems', el, 'value', bindingMode.twoWay);
binding = info.binding;
elementValueProperty = info.targetObserver;
});
it('binds', () => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'bind').and.callThrough();
binding.bind(createScopeForTest(obj));
expect(targetObserver.bind).toHaveBeenCalled();
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
});
it('responds to model change', done => {
obj.selectedItems = [el.options.item(0).model];
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
});
it('responds to model mutate', done => {
while(obj.selectedItems.length) {
obj.selectedItems.pop();
}
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
obj.selectedItems.push(el.options.item(1).model);
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
}, 0);
});
it('responds to element change', done => {
el.options.item(0).selected = true;
el.options.item(1).selected = false;
el.options.item(2).selected = true;
el.dispatchEvent(DOM.createCustomEvent('change'));
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
});
it('responds to options change', done => {
var option = document.createElement('option');
option.text = 'D';
option.model = { foo: 'D' };
obj.selectedItems = [option.model];
el.appendChild(option);
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
});
it('unbinds', () => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'unbind').and.callThrough();
binding.unbind();
expect(targetObserver.unbind).toHaveBeenCalled();
});
afterAll(() => {
document.body.removeChild(el);
});
});
describe('multi-select objects with matcher', () => {
var obj, el, binding, elementValueProperty;
beforeAll(() => {
var option, info;
el = createElement('<select multiple></select>');
option = document.createElement('option');
option.text = 'A';
option.model = { foo: 'A' };
el.appendChild(option);
option = document.createElement('option');
option.text = 'B';
option.model = { foo: 'B' };
el.appendChild(option);
option = document.createElement('option');
option.text = 'C';
option.model = { foo: 'C' };
el.appendChild(option);
document.body.appendChild(el);
obj = { selectedItems: [{ foo: 'B'}, { foo: 'C' }] };
info = getBinding(observerLocator, obj, 'selectedItems', el, 'value', bindingMode.twoWay);
binding = info.binding;
elementValueProperty = info.targetObserver;
});
it('binds', done => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'bind').and.callThrough();
binding.bind(createScopeForTest(obj));
expect(targetObserver.bind).toHaveBeenCalled();
el.matcher = (a, b) => a.foo === b.foo;
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
});
});
it('responds to model change', done => {
obj.selectedItems = [{ foo: 'A' }];
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
});
it('responds to model mutate', done => {
while(obj.selectedItems.length) {
obj.selectedItems.pop();
}
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
obj.selectedItems.push({ foo: 'B' });
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
}, 0);
});
it('responds to element change', done => {
el.options.item(0).selected = true;
el.options.item(1).selected = false;
el.options.item(2).selected = true;
el.dispatchEvent(DOM.createCustomEvent('change'));
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
});
it('responds to options change', done => {
var option = document.createElement('option');
option.text = 'D';
option.model = { foo: 'D' };
obj.selectedItems = [{ foo: 'D' }];
el.appendChild(option);
setTimeout(() => {
expect(elementValueProperty.getValue()).toBe(obj.selectedItems);
expect(getElementValue(el)).toEqual(obj.selectedItems.slice(0));
done();
}, 0);
});
it('unbinds', () => {
var targetObserver = observerLocator.getObserver(el, 'value');
spyOn(targetObserver, 'unbind').and.callThrough();
binding.unbind();
expect(targetObserver.unbind).toHaveBeenCalled();
});
afterAll(() => {
document.body.removeChild(el);
});
});
describe('option value bound after select value bound', () => {
var obj, el, binding, binding2;
beforeAll(() => {
obj = { selectedItem: 'B', optionB: 'B' };
el = createElement(
`<select>
<option value="A">Option A</option>
<option >Option B</option>
<option value="C">Option C</option>
</select>`);
document.body.appendChild(el);
binding = getBinding(observerLocator, obj, 'selectedItem', el, 'value', bindingMode.twoWay).binding;
binding2 = getBinding(observerLocator, obj, 'optionB', el.options.item(1), 'value', bindingMode.oneWay).binding;
});
it('binds', done => {
var targetObserver = observerLocator.getObserver(el, 'value');
// select-value bind.
binding.bind(createScopeForTest(obj));
expect(el.options.item(1).selected).toBe(false);
// now bind the option value.
binding2.bind(createScopeForTest(obj));
setTimeout(() => {
expect(el.options.item(1).selected).toBe(true);
done();
}, 100);
});
afterAll(() => {
document.body.removeChild(el);
});
});
});