UNPKG

extjs-gpl

Version:

GPL licensed version of Sencha Ext JS

814 lines (691 loc) 29.5 kB
describe("Ext.data.reader.Xml", function() { var reader, responseText, readData, createReader, user, orders, ajaxResponse, doc = document, parseXml = function parseXml(str) { if (window.ActiveXObject) { var doc = new ActiveXObject('Microsoft.XMLDOM'); doc.loadXML(str); return doc; } else if (window.DOMParser) { return (new DOMParser()).parseFromString(str, 'text/xml'); } return ''; }, getXml = function(str){ str = '<root>' + str + '</root>'; return parseXml(str); }, DQ; beforeEach(function() { DQ = Ext.DomQuery; Ext.DomQuery = { isXml: function(el) { var docEl = (el ? el.ownerDocument || el : 0).documentElement; return docEl ? docEl.nodeName !== "HTML" : false; }, selectNode: function(path, root){ return Ext.DomQuery.select(path, root, null, true)[0]; }, select: function(path, root, type, single) { if (typeof root == 'string') { return []; } if (doc.querySelectorAll && !DQ.isXml(root)) { return single ? [ root.querySelector(path) ] : Ext.Array.toArray(root.querySelectorAll(path)); } else { return DQ.jsSelect.call(this, path, root, type); } } }; }); afterEach(function() { Ext.DomQuery = DQ; if (reader) { reader.destroy(); } reader = null; }); describe("raw data", function() { var Model, xml, rec; beforeEach(function() { Model = Ext.define('spec.Xml', { extend: 'Ext.data.Model', fields: ['name'] }); xml = getXml('<dog><name>Utley</name></dog><dog><name>Molly</name></dog>'); reader = new Ext.data.reader.Xml({ model: 'spec.Xml', record: 'dog' }); }); afterEach(function() { Ext.data.Model.schema.clear(true); Ext.undefine('spec.Xml'); rec = xml = Model = null; }); it("should not set raw data reference by default", function() { rec = reader.readRecords(xml).getRecords()[0]; expect(rec.raw).not.toBeDefined(); }); it("should set raw data reference for a TreeStore record", function () { // Simulate TreeStore node spec.Xml.prototype.isNode = true; rec = reader.readRecords(xml).getRecords()[0]; expect(rec.raw).toBe(xml.firstChild.firstChild); }); }); describe("copyFrom", function() { var Model = Ext.define(null, { extend: 'Ext.data.Model' }); it("should copy the model", function() { var reader = new Ext.data.reader.Xml({ model: Model }); var copy = new Ext.data.reader.Xml(); copy.copyFrom(reader); expect(copy.getModel()).toBe(Model); }); it("should copy the record", function() { var reader = new Ext.data.reader.Xml({ model: Model, record: 'foo' }); var copy = new Ext.data.reader.Xml(); copy.copyFrom(reader); expect(copy.getRecord()).toBe('foo'); var result = reader.read(getXml('<foo /><foo /><foo /><bar />')); expect(result.getCount()).toBe(3); }); it("should copy the totalProperty", function() { var reader = new Ext.data.reader.Xml({ model: Model, totalProperty: 'aTotal', record: 'foo' }); var copy = new Ext.data.reader.Xml(); copy.copyFrom(reader); expect(copy.getTotalProperty()).toBe('aTotal'); var result = reader.read(getXml('<aTotal>1000</aTotal>')); expect(result.getTotal()).toBe(1000); }); it("should copy the successProperty", function() { var reader = new Ext.data.reader.Xml({ model: Model, successProperty: 'aSuccess', record: 'foo' }); var copy = new Ext.data.reader.Xml(); copy.copyFrom(reader); expect(copy.getSuccessProperty()).toBe('aSuccess'); var result = reader.read(getXml('<aSuccess>false</aSuccess>')); expect(result.getSuccess()).toBe(false); }); it("should copy the messageProperty", function() { var reader = new Ext.data.reader.Xml({ model: Model, messageProperty: 'aMessage', record: 'foo' }); var copy = new Ext.data.reader.Xml(); copy.copyFrom(reader); expect(copy.getMessageProperty()).toBe('aMessage'); var result = reader.read(getXml('<aMessage>Some Message</aMessage>')); expect(result.getMessage()).toBe('Some Message'); }); it("should copy the rootProperty", function() { var reader = new Ext.data.reader.Xml({ model: Model, rootProperty: 'aRoot', record: 'foo' }); var copy = new Ext.data.reader.Xml(); copy.copyFrom(reader); expect(copy.getRootProperty()).toBe('aRoot'); var result = reader.read(getXml('<notRoot><foo /><foo /><foo /></notRoot><aRoot><foo /></aRoot>')); expect(result.getCount()).toBe(1); }); }); describe("extractors", function(){ /** * All the values read from the XML document should be strings or XML nodes. */ function createReader(cfg) { Ext.define('spec.FooXmlTest', { extend: 'Ext.data.Model', fields: ['field'] }); cfg = cfg || {}; reader = new Ext.data.reader.Xml(Ext.apply({ model: 'spec.FooXmlTest' }, cfg)); } afterEach(function () { Ext.data.Model.schema.clear(); Ext.undefine('spec.FooXmlTest'); }); it("should run function extractors in the reader scope", function(){ var actual; createReader({ successProperty: function(){ actual = this; return true; } }); reader.getSuccess({ success: true }); expect(actual).toBe(reader); }); describe("getTotal", function(){ it("should default to total", function(){ createReader(); expect(reader.getTotal(getXml('<total>10</total>'))).toBe('10'); }); it("should have no getTotal method if the totalProperty isn't specified", function(){ createReader({ totalProperty: '' }); expect(reader.getTotal).toBeUndefined(); }); it("should read the specified property name", function(){ createReader({ totalProperty: 'foo' }); expect(reader.getTotal(getXml('<foo>17</foo>'))).toBe('17'); }); it("should accept a function configuration", function(){ createReader({ totalProperty: function(root){ return this.getNodeValue(root.firstChild.childNodes[2]); } }); expect(reader.getTotal(getXml('<node1>1</node1><node2>2</node2><node3>3</node3>'))).toBe('3'); }); xit("should be able to use some xpath", function(){ createReader({ totalProperty: 'foo/bar' }); expect(reader.getTotal(getXml('<foo><bar>18</bar></foo>'))).toBe('18'); }); xit("should support attribute reading", function(){ createReader({ totalProperty: '@total' }); expect(reader.getTotal(parseXml('<node total="11" />').firstChild)).toBe('11'); }); }); describe("getSuccess", function(){ it("should default to success", function(){ createReader(); expect(reader.getSuccess(getXml('<success>true</success>'))).toBe('true'); }); it("should have no getSuccess method if the successProperty isn't specified", function(){ createReader({ successProperty: '' }); expect(reader.getSuccess).toBeUndefined(); }); it("should read the specified property name", function(){ createReader({ successProperty: 'foo' }); expect(reader.getSuccess(getXml('<foo>false</foo>'))).toBe('false'); }); it("should accept a function configuration", function(){ createReader({ successProperty: function(root){ return this.getNodeValue(root.firstChild.childNodes[0]); } }); expect(reader.getSuccess(getXml('<node1>true</node1><node2>false</node2><node3>false</node3>'))).toBe('true'); }); xit("should be able to use some xpath", function(){ createReader({ successProperty: 'a/node/path' }); expect(reader.getSuccess(getXml('<a><node><path>false</path></node></a>'))).toBe('false'); }); xit("should support attribute reading", function(){ createReader({ totalProperty: '@success' }); expect(reader.getTotal(parseXml('<node success="true" />').firstChild)).toBe('true'); }); }); describe("getMessage", function(){ it("should default to undefined", function(){ createReader(); expect(reader.getMessage).toBeUndefined(); }); it("should have no getMessage method if the messageProperty isn't specified", function(){ createReader({ messageProperty: '' }); expect(reader.getMessage).toBeUndefined(); }); it("should read the specified property name", function(){ createReader({ messageProperty: 'foo' }); expect(reader.getMessage(getXml('<foo>a msg</foo>'))).toBe('a msg'); }); it("should accept a function configuration", function(){ createReader({ messageProperty: function(root){ return this.getNodeValue(root.firstChild.childNodes[1]); } }); expect(reader.getMessage(getXml('<node1>msg1</node1><node2>msg2</node2><node3>msg3</node3>'))).toBe('msg2'); }); xit("should be able to use some xpath", function(){ createReader({ messageProperty: 'some/nodes' }); expect(reader.getMessage(getXml('<some><nodes>message here</nodes></some>'))).toBe('message here'); }); xit("should support attribute reading", function(){ createReader({ totalProperty: '@message' }); expect(reader.getTotal(parseXml('<node message="attribute msg" />').firstChild)).toBe('attribute msg'); }); }); describe("fields", function(){ var rawOptions = { recordCreator: Ext.identityFn }; function createReader(fields, readerCfg) { Ext.define('spec.XmlFieldTest', { extend: 'Ext.data.Model', fields: fields }); reader = new Ext.data.reader.Xml(Ext.apply({ model: 'spec.XmlFieldTest', record: 'root' }, readerCfg)); } afterEach(function(){ Ext.data.Model.schema.clear(); Ext.undefine('spec.XmlFieldTest'); }); it("should read the name if no mapping is specified", function(){ createReader(['field']); var result = reader.readRecords(getXml('<field>val</field>').firstChild, rawOptions).getRecords()[0]; expect(result.field).toBe('val'); }); it("should give precedence to the mapping", function(){ createReader([{ name: 'field', mapping: 'other' }]); var result = reader.readRecords(getXml('<field>val</field><other>real value</other>').firstChild, rawOptions).getRecords()[0]; expect(result.field).toBe('real value'); }); it("should handle dot notation mapping with nested undefined properties", function(){ createReader([{ name: 'field', mapping: 'some.nested.property' }]); var result = reader.readRecords(getXml('<foo>val</foo>').firstChild, rawOptions).getRecords()[0]; expect(result.field).toBeUndefined(); // default value }); it("should accept a function", function(){ createReader([{ name: 'field', mapping: function(root){ return reader.getNodeValue(root.childNodes[1]); } }]); var result = reader.readRecords(getXml('<node1>a</node1><node2>b</node2><node3>c</node3>'), rawOptions).getRecords()[0]; expect(result.field).toBe('b'); }); xit("should allow basic xpath", function(){ createReader([{ name: 'field', mapping: 'some/xpath/here' }]); var result = reader.readRecords(getXml('<some><xpath><here>a value</here></xpath></some>'), rawOptions).getRecords()[0]; expect(result.field).toBe('a value'); }); xit("should support attribute reading", function(){ createReader([{ name: 'field', mapping: '@other' }]); var result = reader.readRecords(parseXml('<node other="attr value" />').firstChild, rawOptions).getRecords()[0]; expect(result.field).toBe('attr value'); }); xit("should read fields from xml nodes that have a namespace prefix", function() { createReader(['field'], {namespace: 'n'}); var result = reader.readRecords(getXml('<n:field xmlns:n="nns">val</n:field>').firstChild, rawOptions).getRecords()[0]; expect(result.field).toBe('val'); }); xit("should read field data from a mapped xml node with namespace prefix", function(){ createReader([{ name: 'field', mapping: 'm|other' }]); var result = reader.readRecords(getXml( '<n:field xmlns:n="nns">val</n:field><m:other xmlns:m="mns">real value</m:other>' ).firstChild, rawOptions).getRecords()[0]; expect(result.field).toBe('real value'); }); }); }); xdescribe("reading data", function() { var readData, user; beforeEach(function() { Ext.define('spec.XmlReader', { extend: 'Ext.data.Model', fields: [ {name: 'id', mapping: 'idProp', type: 'int'}, {name: 'name', mapping: 'FullName', type: 'string'}, {name: 'email', mapping: '@email', type: 'string'} ] }); reader = new Ext.data.reader.Xml({ root: 'data', totalProperty: 'totalProp', messageProperty: 'messageProp', successProperty: 'successProp', model: 'spec.XmlReader', record: 'user' }); ajaxResponse = new MockAjax(); //FIXME: Has to be a better way? responseText = ['<results>', '<totalProp>2300</totalProp>', '<successProp>true</successProp>', '<messageProp>It worked</messageProp>', '<data>', '<user email=\"ed@sencha.com\">', '<idProp>123</idProp>', '<FullName>Ed Spencer</FullName>', '</user>', '</data>', '</results>'].join(''); ajaxResponse.complete({ status: 200, statusText: 'OK', responseText: responseText, responseHeaders: { "Content-type": "application/xml" } }); readData = reader.read(ajaxResponse); user = readData.getRecords()[0]; }); afterEach(function() { Ext.data.Model.schema.clear(); Ext.undefine('spec.XmlReader'); }); it("should extract the correct total", function() { expect(readData.getTotal()).toBe(2300); }); it("should extract success", function() { expect(readData.getSuccess()).toBe(true); }); it("should extract count", function() { expect(readData.getCount()).toBe(1); }); it("should extract the message", function() { expect(readData.getMessage()).toBe("It worked"); }); it("should extract the id", function() { expect(user.getId()).toBe(123); }); it("should respect field mappings", function() { expect(user.get('name')).toBe("Ed Spencer"); }); it("should respect field mappings containing @", function() { expect(user.get('email')).toBe("ed@sencha.com"); }); }); xdescribe("loading nested data", function() { //We have four models - User, Order, OrderItem and Product beforeEach(function() { ajaxResponse = new MockAjax(); //FIXME: Has to be a better way? responseText = ['<users>', '<user>', '<id>123</id>', '<name>Ed</name>', '<orders>', '<order>', '<id>50</id>', '<total>100</total>', '<order_items>', '<order_item>', '<id>20</id>', '<price>40</price>', '<quantity>2</quantity>', '<product>', '<id>1000</id>', '<name>MacBook Pro</name>', '</product>', '</order_item>', '<order_item>', '<id>21</id>', '<price>20</price>', '<quantity>1</quantity>', '<product>', '<id>1001</id>', '<name>iPhone</name>', '</product>', '</order_item>', '</order_items>', '</order>', '<order>', '<id>51</id>', '<total>10</total>', '<order_items>', '<order_item>', '<id>22</id>', '<price>10</price>', '<quantity>1</quantity>', '<product>', '<id>1002</id>', '<name>iPad</name>', '</product>', '</order_item>', '</order_items>', '</order>', '</orders>', '</user>', '</users>'].join(''); ajaxResponse.complete({ status: 200, statusText: 'OK', responseText: responseText }); Ext.define("spec.User", { extend: 'Ext.data.Model', fields: [ 'id', 'name' ], hasMany: {model: 'spec.Order', name: 'orders'}, proxy: { type: 'rest', reader: { type: 'xml', root: 'users' } } }); Ext.define("spec.Order", { extend: 'Ext.data.Model', fields: [ 'id', 'total' ], hasMany : {model: 'spec.OrderItem', name: 'orderItems', associationKey: 'order_items'}, belongsTo: 'spec.User', proxy: { type: 'memory', reader: { type: 'xml', root: 'orders', record: 'order' } } }); Ext.define("spec.OrderItem", { extend: 'Ext.data.Model', fields: [ 'id', 'price', 'quantity', 'order_id', 'product_id' ], belongsTo: ['spec.Order', {model: 'spec.Product', getterName: 'getProduct', associationKey: 'product'}], proxy: { type: 'memory', reader: { type: 'xml', root: 'order_items', record: 'order_item' } } }); Ext.define("spec.Product", { extend: 'Ext.data.Model', fields: [ 'id', 'name' ], hasMany: {model: 'spec.OrderItem', name: 'orderItems'}, proxy: { type: 'memory', reader: { type: 'xml', record: 'product' } } }); createReader = function (config) { return new Ext.data.reader.Xml(Ext.apply({}, config, { model : "spec.User", root : "users", record: "user" })); }; }); afterEach(function() { Ext.data.Model.schema.clear(); Ext.undefine('spec.User'); Ext.undefine('spec.Order'); Ext.undefine('spec.OrderItem'); Ext.undefine('spec.Product'); }); it("should set implicitIncludes to true by default", function() { reader = createReader(); expect(reader.getImplicitIncludes()).toBe(true); }); it("should not parse includes if implicitIncludes is set to false", function() { reader = createReader({implicitIncludes: false}); readData = reader.read(ajaxResponse); user = readData.records[0]; orders = user.orders(); expect(orders.getCount()).toEqual(0); }); describe("when reading nested data", function() { beforeEach(function() { reader = createReader(); readData = reader.read(ajaxResponse); user = readData.records[0]; orders = user.orders(); }); it("should populate first-order associations", function() { expect(orders.getCount()).toEqual(2); }); it("should populate second-order associations", function() { var order = orders.first(); expect(order.orderItems().getCount()).toEqual(2); }); it("should populate belongsTo associations", function() { var order = orders.first(), item = order.orderItems().first(), product = item.getProduct(); expect(product.get('name')).toEqual('MacBook Pro'); }); }); }); describe("reading xhr", function() { var xml = '<users><success>true</success><user><name>Ben</name><location>Boston</location></user><user><name>Mike</name><location>Redwood City</location></user><user><name>Nick</name><location>Kansas City</location></user></users>', goodResponse = { responseText: 'something', responseXML: parseXml(xml) }, badResponse = { responseText: 'something', responseXML: null }; beforeEach(function() { Ext.define('spec.User', { extend: 'Ext.data.Model', fields: ['name', 'location'] }); reader = new Ext.data.reader.Xml({ record: 'user', model: 'spec.User', listeners: { exception: function (reader, response, errorMsg, eOpts) { } } }); spyOn(reader, 'readRecords').andCallThrough(); spyOn(reader, 'getResponseData').andCallThrough(); }); afterEach(function() { Ext.data.Model.schema.clear(); Ext.undefine('spec.User'); }); function doRead(response) { return reader.read(response); } describe("if there is a responseXML property", function() { describe("if there is valid XML", function() { it("should call readRecords", function() { doRead(goodResponse); expect(reader.readRecords).toHaveBeenCalled(); }); it("should be successful", function() { expect(doRead(goodResponse).getSuccess()).toBe(true); }); it("should return the expected number of records", function() { expect(doRead(goodResponse).getCount()).toBe(3); }); it("should not return a non-empty dataset", function() { expect(doRead(goodResponse).getRecords().length).toBeGreaterThan(0); }); }); describe("if there is invalid XML", function() { beforeEach(function() { spyOn(Ext, 'log'); spyOn(Ext.Logger, 'log'); }); it("should not call readRecords", function() { doRead(badResponse); expect(reader.readRecords).not.toHaveBeenCalled(); }); it("should not be successful", function() { expect(doRead(badResponse).getSuccess()).toBe(false); }); it("should not return any records", function() { expect(doRead(badResponse).getTotal()).toBe(0); }); it("should return any empty dataset", function() { expect(doRead(badResponse).getRecords().length).toBe(0); }); }); }); describe("if there is no responseText property", function() { beforeEach(function() { doRead("something"); }); it("should not call readRecords", function() { expect(reader.getResponseData).not.toHaveBeenCalled(); }); }); }); });