diff --git a/incremental-dom/all-tests.ts b/incremental-dom/all-tests.ts new file mode 100644 index 000000000..099b41e3b --- /dev/null +++ b/incremental-dom/all-tests.ts @@ -0,0 +1,9 @@ +/// + +declare var describe:any +,it:any +,expect:any +,beforeEach:any +,afterEach:any; + +/// diff --git a/incremental-dom/incremental-dom.d.ts b/incremental-dom/incremental-dom.d.ts new file mode 100644 index 000000000..a9f3dc27f --- /dev/null +++ b/incremental-dom/incremental-dom.d.ts @@ -0,0 +1,81 @@ +// Type definitions for Incremetal DOM +// Project: https://github.com/google/incremental-dom +// Definitions by: Basarat Ali Syed +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +declare module "incremental-dom" { + /** + * Patches the document starting at el with the provided function. This function + * may be called during an existing patch operation. + * @param {!Element} el the element to patch + * @param {!function} fn A function containing elementOpen/elementClose/etc. + * calls that describe the DOM. + */ + export var patch: (el: Node, fn: Function) => void; + + /** + * Declares a virtual Element at the current location in the document. This + * corresponds to an opening tag and a elementClose tag is required. + * @param {string} tag The element's tag. + * @param {?string} key The key used to identify this element. This can be an + * empty string, but performance may be better if a unique value is used + * when iterating over an array of items. + * @param {?Array<*>} statics An array of attribute name/value pairs of the + * static attributes for the Element. These will only be set once when the + * Element is created. + * @param {...*} var_args Attribute name/value pairs of the dynamic attributes + * for the Element. + */ + export var elementOpen: (tag: string, key?: string, statics?: any[], ...var_args: any[]) => void; + /** + * Declares a virtual Element at the current location in the document. This + * corresponds to an opening tag and a elementClose tag is required. This is + * like elementOpen, but the attributes are defined using the attr function + * rather than being passed as arguments. Must be folllowed by 0 or more calls + * to attr, then a call to elementOpenEnd. + * @param {string} tag The element's tag. + * @param {?string} key The key used to identify this element. This can be an + * empty string, but performance may be better if a unique value is used + * when iterating over an array of items. + * @param {?Array<*>} statics An array of attribute name/value pairs of the + * static attributes for the Element. These will only be set once when the + * Element is created. + */ + export var elementOpenStart: (tag: string, key?: any, ...statics: any[]) => void; + /*** + * Defines a virtual attribute at this point of the DOM. This is only valid + * when called between elementOpenStart and elementOpenEnd. + * + * @param {string} name + * @param {*} value + */ + export var attr: (name: string, value: any) => void; + /** + * Closes an open tag started with elementOpenStart. + */ + export var elementOpenEnd: () => void; + /** + * Closes an open virtual Element. + */ + export var elementClose: () => void; + /** + * Declares a virtual Element at the current location in the document that has + * no children. + * @param {string} tag The element's tag. + * @param {?string} key The key used to identify this element. This can be an + * empty string, but performance may be better if a unique value is used + * when iterating over an array of items. + * @param {?Array<*>} statics An array of attribute name/value pairs of the + * static attributes for the Element. These will only be set once when the + * Element is created. + * @param {...*} var_args Attribute name/value pairs of the dynamic attributes + * for the Element. + */ + export var elementVoid: (tag: string, key?: string, statics?: any, ...var_args: any[]) => void; + /** + * Declares a virtual Text at this point in the document. + * + * @param {string} value The text of the Text. + */ + export var text: (value: string) => void; +} diff --git a/incremental-dom/tests/functional/attributes-tests.ts b/incremental-dom/tests/functional/attributes-tests.ts new file mode 100644 index 000000000..80df028c5 --- /dev/null +++ b/incremental-dom/tests/functional/attributes-tests.ts @@ -0,0 +1,171 @@ +/// + +import id = require('incremental-dom'); +var patch = id.patch; +var elementVoid = id.elementVoid; + +describe('attribute updates', () => { + var container:any; + + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + document.body.removeChild(container); + }); + + describe('for conditional attributes', () => { + function render(obj:any) { + elementVoid('div', '', [], + 'data-expanded', obj.key); + } + + it('should be present when they have a value', () => { + patch(container, () => render({ + key: 'hello' + })); + var el = container.childNodes[0]; + + expect(el.getAttribute('data-expanded')).to.equal('hello'); + }); + + it('should be present when falsy', () => { + patch(container, () => render({ + key: false + })); + var el = container.childNodes[0]; + + expect(el.getAttribute('data-expanded')).to.equal('false'); + }); + + it('should be not present when undefined', () => { + patch(container, () => render({ + key: undefined + })); + var el = container.childNodes[0]; + + expect(el.getAttribute('data-expanded')).to.equal(null); + }); + + it('should update the DOM when they change', () => { + patch(container, () => render({ + key: 'foo' + })); + patch(container, () => render({ + key: 'bar' + })); + var el = container.childNodes[0]; + + expect(el.getAttribute('data-expanded')).to.equal('bar'); + }); + }); + + describe('for function attributes', () => { + it('should not be set as attributes', () => { + var fn = () =>{}; + patch(container, () => { + elementVoid('div', '', null, + 'fn', fn); + }); + var el = container.childNodes[0]; + + expect(el.hasAttribute('fn')).to.be.false; + }); + + it('should be set on the node', () => { + var fn = () =>{}; + patch(container, () => { + elementVoid('div', '', null, + 'fn', fn); + }); + var el = container.childNodes[0]; + + expect(el.fn).to.equal(fn); + }); + }); + + describe('for object attributes', () => { + it('should not be set as attributes', () => { + var obj = {}; + patch(container, () => { + elementVoid('div', '', null, + 'obj', obj); + }); + var el = container.childNodes[0]; + + expect(el.hasAttribute('obj')).to.be.false; + }); + + it('should be set on the node', () => { + var obj = {}; + patch(container, () => { + elementVoid('div', '', null, + 'obj', obj); + }); + var el = container.childNodes[0]; + + expect(el.obj).to.equal(obj); + }); + }); + + describe('for style', () => { + function render(style:any) { + elementVoid('div', '', [], + 'style', style); + } + + it('should render with the correct style properties for objects', () => { + patch(container, () => render({ + color: 'white', + backgroundColor: 'red' + })); + var el = container.childNodes[0]; + + expect(el.style.color).to.equal('white'); + expect(el.style.backgroundColor).to.equal('red'); + }); + + it('should update the correct style properties', () => { + patch(container, () => render({ + color: 'white' + })); + patch(container, () => render({ + color: 'red' + })); + var el = container.childNodes[0]; + + expect(el.style.color).to.equal('red'); + }); + + it('should remove properties not present in the new object', () => { + patch(container, () => render({ + color: 'white' + })); + patch(container, () => render({ + backgroundColor: 'red' + })); + var el = container.childNodes[0]; + + expect(el.style.color).to.equal(''); + expect(el.style.backgroundColor).to.equal('red'); + }); + + it('should render with the correct style properties for strings', () => { + patch(container, () => render('color: white; background-color: red;')); + var el = container.childNodes[0]; + + expect(el.style.color).to.equal('white'); + expect(el.style.backgroundColor).to.equal('red'); + }); + + it('should render with the correct style properties for String instances', () => { + patch(container, () => render(new String('color: white; background-color: red;'))); + var el = container.childNodes[0]; + + expect(el.style.color).to.equal('white'); + expect(el.style.backgroundColor).to.equal('red'); + }); + }); +});