extjs-gpl
Version:
GPL licensed version of Sencha Ext JS
814 lines (691 loc) • 29.5 kB
JavaScript
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();
});
});
});
});