UNPKG

extjs-gpl

Version:

GPL licensed version of Sencha Ext JS

1,157 lines (1,002 loc) 120 kB
describe("Ext.data.schema.ManyToOne", function() { var schema, Post, Thread, threadRole, postRole, threadCalled = false, postCalled = false; function definePost(refCfg) { Post = Ext.define('spec.Post', { extend: 'Ext.data.Model', fields: ['id', 'content', { name: 'threadId', reference: Ext.apply({ type: 'Thread' }, refCfg) }], constructor: function() { postCalled = true; this.callParent(arguments); } }); threadRole = Post.associations.thread; postRole = Thread.associations.posts; } function complete(data, status) { Ext.Ajax.mockComplete({ status: status || 200, responseText: Ext.JSON.encode(data) }); } beforeEach(function() { threadCalled = postCalled = false; MockAjaxManager.addMethods(); schema = Ext.data.Model.schema; schema.setNamespace('spec'); Thread = Ext.define('spec.Thread', { extend: 'Ext.data.Model', fields: ['id', 'title'], constructor: function() { threadCalled = true; this.callParent(arguments); } }); }); afterEach(function() { MockAjaxManager.removeMethods(); Ext.undefine('spec.Post'); Ext.undefine('spec.Thread'); schema.clear(true); Post = postRole = Thread = threadRole = schema = null; threadCalled = postCalled = false; }); describe("Model.associations", function() { it("should have an association role on each model", function() { definePost(); expect(Post.associations.thread).toBeDefined(); expect(Thread.associations.posts).toBeDefined(); }); it("should have a reference back to the association for each role", function() { definePost(); expect(Post.associations.thread.association).toBe(Thread.associations.posts.association); expect(Thread.associations.posts.association.isManyToOne).toBe(true); }); }); describe("association default config", function() { var assoc; beforeEach(function() { definePost(); assoc = threadRole.association; }); it("should have a schema set", function() { expect(assoc.schema).toBe(schema); }); it("should have the reference field set", function() { expect(assoc.field).toBe(Post.getField('threadId')); }); it("should have the left part be set to the key holder", function() { expect(assoc.left).toBe(postRole); }); it("should set definedBy to the key holder", function() { expect(assoc.definedBy).toBe(Post); }); it("should have the right part be set to the non key holder", function() { expect(assoc.right).toBe(threadRole); }); it("should have the owner as null", function() { expect(assoc.owner).toBeNull(); }); it("should set the assoc name to {PluralKeyHolder}By{SingluarOther}", function() { expect(assoc.name).toBe('ThreadPosts'); }); }); describe("left", function() { beforeEach(function() { definePost(); }); it("should set the role to be plural lowercase & the type to be the entity name", function() { expect(postRole.role).toBe('posts'); expect(postRole.type).toBe('Post'); }); it("should set the inverse role to the right", function() { expect(postRole.inverse).toBe(threadRole); }); it("should set the entity", function() { expect(postRole.cls).toBe(Post); }); }); describe("right", function() { beforeEach(function() { definePost(); }); it("should set the role to be singular lowercase & the type to be the entity name", function() { expect(threadRole.role).toBe('thread'); expect(threadRole.type).toBe('Thread'); }); it("should set the inverse role to the left", function() { expect(threadRole.inverse).toBe(postRole); }); it("should set the entity", function() { expect(threadRole.cls).toBe(Thread); }); }); describe("configuring", function() { it("should set an association name", function() { definePost({ association: 'CustomName' }); expect(postRole.association.name).toBe('CustomName'); }); it("should set the owner based on the child param", function() { definePost({ child: true }); expect(postRole.association.owner).toBe(postRole); expect(postRole.owner).toBe(true); expect(threadRole.owner).toBe(false); }); it("should set the owner based on the parent param", function() { definePost({ parent: true }); expect(postRole.association.owner).toBe(threadRole); expect(threadRole.owner).toBe(true); expect(postRole.owner).toBe(false); }); it("should be able to set a custom role", function() { definePost({ role: 'foo' }); threadRole = Post.associations.foo; expect(threadRole.association.name).toBe('ThreadFooPosts'); expect(threadRole.role).toBe('foo'); }); describe("inverse", function() { it("should set with a string", function() { definePost({ inverse: 'foo' }); postRole = Thread.associations.foo; expect(postRole.association.name).toBe('ThreadFoo'); expect(postRole.role).toBe('foo'); }); it("should set with an object", function() { definePost({ inverse: { role: 'foo' } }); postRole = Thread.associations.foo; expect(postRole.association.name).toBe('ThreadFoo'); expect(postRole.role).toBe('foo'); }); }); }); describe("model decoration", function() { it("should generate a getter on the key holder", function() { definePost(); expect(typeof Post.prototype.getThread).toBe('function'); }); it("should generate a setter on the key holder", function() { definePost(); expect(typeof Post.prototype.setThread).toBe('function'); }); it("should define a getter on the inverse", function() { definePost(); expect(typeof Thread.prototype.posts).toBe('function'); }); it("should allow a custom getter name on the key holder", function() { definePost({ inverse: { getterName: 'getFoo' } }); expect(typeof Thread.prototype.getFoo).toBe('function'); }); it("should allow a custom setter name on the key holder", function() { definePost({ setterName: 'setFoo' }); expect(typeof Post.prototype.setFoo).toBe('function'); }); it("should allow a custom getter name on the inverse", function() { definePost({ getterName: 'ghosts' }); expect(typeof Post.prototype.ghosts).toBe('function'); }); it("should decorate the model based on the role", function() { var OtherPost = Ext.define('spec.OtherPost', { extend: 'Ext.data.Model', fields: ['id', 'name', { name: 'threadAId', reference: { type: 'Thread', role: 'ThreadA' } }, { name: 'threadBId', reference: { type: 'Thread', role: 'ThreadB' } }] }); expect(typeof OtherPost.prototype.getThreadA).toBe('function'); expect(typeof OtherPost.prototype.getThreadB).toBe('function'); Ext.undefine('spec.OtherPost'); }); }); describe("subclassing", function() { // Post describe("the left", function() { var SubPost; beforeEach(function() { definePost(); SubPost = Ext.define('spec.SubPost', { extend: 'spec.Post' }); }); afterEach(function() { Ext.undefine('spec.SubPost'); SubPost = null; }); it("should still have the original association", function() { var inverse = Post.associations.thread.inverse; expect(inverse.role).toBe('posts'); expect(inverse.cls).toBe(Post); }); it("should inherit the association from the parent and modify the relevant classes", function() { var inverse = SubPost.associations.thread.inverse; expect(inverse.role).toBe('subPosts'); expect(inverse.cls).toBe(SubPost); }); }); // Thread describe("the right", function() { var SubThread; beforeEach(function() { definePost(); SubThread = Ext.define('spec.SubThread', { extend: 'spec.Thread' }); }); it("should not have any associations", function() { expect(SubThread.associations).toEqual({}); }); }); }); describe("nested loading", function() { it("should infer the key when using remoteFilter: false", function() { definePost({ inverse: { storeConfig: { remoteFilter: false } } }); var thread = Thread.load(1); complete({ id: 1, posts: [{ id: 101 }, { id: 102 }] }); var posts = thread.posts(); expect(posts.getAt(0).get('threadId')).toBe(1); expect(posts.getAt(0).dirty).toBe(false); expect(posts.getAt(1).get('threadId')).toBe(1); expect(posts.getAt(1).dirty).toBe(false); expect(posts.getRemoteFilter()).toBe(false); }); it("should delete the many from the data collection", function() { definePost(); var thread = Thread.load(1); complete({ id: 1, posts: [{ id: 101 }, { id: 102 }] }); expect(thread.get('posts')).toBeUndefined(); expect(thread.posts().getCount()).toBe(2); }); it("should delete the one from the data collection", function() { definePost(); var post = Post.load(101); complete({ id: 101, thread: { id: 1 } }); expect(post.get('thread')).toBeUndefined(); expect(post.getThread().getId()).toBe(1); }); it("should not pollute the reader when reading nested data of the same type", function() { function getData() { return { records: [{ id: 1, parentId: null, children: [{ id: 101, parentId: 1 }, { id: 102, parentId: 1 }] }] }; } Ext.define('spec.Node', { extend: 'Ext.data.Model', fields: [{ name: 'parentId', reference: { type: 'Node', inverse: 'children' } }], proxy: { type: 'ajax', reader: { type: 'json', rootProperty: 'records' } } }); var store = new Ext.data.Store({ // Always want immediate load asynchronousLoad: false, model: 'Node' }); store.load(); complete(getData()); expect(store.first().children().getCount()).toBe(2); store.load(); complete(getData()); expect(store.first().children().getCount()).toBe(2); store.destroy(); Ext.undefine('spec.Node'); }); describe("complete", function() { it("should mark the store complete if there are records", function() { definePost(); var store = new Ext.data.Store({ model: Thread }); store.loadRawData([{ id: 1, posts: [{ id: 101, content: 'Foo' }, { id: 102, content: 'Bar' }] }]); expect(store.first().posts().complete).toBe(true); }); it("should mark the store complete if a root exists and there are no records", function() { definePost(); var store = new Ext.data.Store({ model: Thread }); store.loadRawData([{ id: 1, posts: [] }]); expect(store.first().posts().complete).toBe(true); }); it("should not mark the store complete if a root doesn't exist", function() { definePost(); var store = new Ext.data.Store({ model: Thread }); store.loadRawData([{ id: 1 }]); expect(store.first().posts().complete).toBe(false); }); }); describe("key inference", function() { describe("without session", function() { beforeEach(function() { definePost(); }); it("should infer the key from the parent", function() { var thread = Thread.load(1); complete({ id: 1, posts: [{ id: 101 }, { id: 102 }] }); var posts = thread.posts(); expect(posts.getCount()).toBe(2); expect(posts.getAt(0).getId()).toBe(101); expect(posts.getAt(0).get('threadId')).toBe(1); expect(posts.getAt(0).dirty).toBe(false); expect(posts.getAt(1).getId()).toBe(102); expect(posts.getAt(1).get('threadId')).toBe(1); expect(posts.getAt(1).dirty).toBe(false); }); it("should infer the key when loading the store, not nested", function() { var thread = Thread.load(1); complete({ id: 1 }); var posts = thread.posts(); posts.load(); complete([{ id: 101 }, { id: 102 }]); expect(posts.getCount()).toBe(2); expect(posts.getAt(0).getId()).toBe(101); expect(posts.getAt(0).get('threadId')).toBe(1); expect(posts.getAt(0).dirty).toBe(false); expect(posts.getAt(1).getId()).toBe(102); expect(posts.getAt(1).get('threadId')).toBe(1); expect(posts.getAt(1).dirty).toBe(false); }); }); describe("with session", function() { var session; beforeEach(function() { definePost(); session = new Ext.data.Session(); }); afterEach(function() { session.destroy(); session = null; }); it("should favour an existing reference", function() { var post = session.createRecord('Post', { id: 101, threadId: 3 }); var thread = Thread.load(1, null, session); complete({ id: 1, posts: [{ id: 101 }, { id: 102 }] }); var posts = thread.posts(); expect(posts.getCount()).toBe(1); expect(posts.getAt(0).getId()).toBe(102); expect(posts.getAt(0).get('threadId')).toBe(1); expect(posts.getAt(0).dirty).toBe(false); expect(posts.indexOf(post)).toBe(-1); }); it("should infer the key from the parent if not specified", function() { var thread = Thread.load(1, null, session); complete({ id: 1, posts: [{ id: 101 }, { id: 102 }] }); var posts = thread.posts(); expect(posts.getCount()).toBe(2); expect(posts.getAt(0).getId()).toBe(101); expect(posts.getAt(0).get('threadId')).toBe(1); expect(posts.getAt(0).dirty).toBe(false); expect(posts.getAt(1).getId()).toBe(102); expect(posts.getAt(1).get('threadId')).toBe(1); expect(posts.getAt(1).dirty).toBe(false); }); it("should infer the key when loading the store, not nested", function() { var thread = Thread.load(1, null, session); complete({ id: 1 }); var posts = thread.posts(); posts.load(); complete([{ id: 101 }, { id: 102 }]); expect(posts.getCount()).toBe(2); expect(posts.getAt(0).getId()).toBe(101); expect(posts.getAt(0).get('threadId')).toBe(1); expect(posts.getAt(0).dirty).toBe(false); expect(posts.getAt(1).getId()).toBe(102); expect(posts.getAt(1).get('threadId')).toBe(1); expect(posts.getAt(1).dirty).toBe(false); }); it("should not infer the key from the parent if a key is specified", function() { var thread = Thread.load(1, null, session); complete({ id: 1, posts: [{ id: 101, threadId: 100 }, { id: 102 }] }); var posts = thread.posts(); expect(posts.getCount()).toBe(1); expect(posts.getAt(0).getId()).toBe(102); expect(posts.getAt(0).get('threadId')).toBe(1); expect(posts.getAt(0).dirty).toBe(false); var rec = session.peekRecord('Post', 101); expect(posts.indexOf(rec)).toBe(-1); }); it("should allow inference for phantom records", function() { var thread = session.createRecord('Thread', {}), post = session.createRecord('Post', { threadId: thread.getId() }); expect(thread.posts().getCount()).toBe(1); expect(thread.posts().getAt(0)).toBe(post); }); }); }); }); describe("id change of the one", function() { function createSuite(withSession) { describe(withSession ? "with session" : "without session", function() { var session, thread; beforeEach(function() { definePost(); if (withSession) { session = new Ext.data.Session(); } thread = new Thread(null, session); }); afterEach(function() { if (withSession) { session.destroy(); } session = thread = null; }); it("should not cause an exception if the store is not created", function() { expect(function() { thread.setId(100); }).not.toThrow(); }); it("should update the store filter", function() { var posts = thread.posts(), filter = posts.getFilters().getAt(0); expect(filter.getValue()).toBe(thread.id); posts.add({ id: 1 }); thread.setId(100); expect(filter.getValue()).toBe(100); }); it("should update the foreign key", function() { var records = thread.posts().add([{id: 1}, {id: 2}, {id: 3}]); expect(records[0].get('threadId')).toBe(thread.id); expect(records[1].get('threadId')).toBe(thread.id); expect(records[2].get('threadId')).toBe(thread.id); thread.setId(100); expect(records[0].get('threadId')).toBe(100); expect(records[1].get('threadId')).toBe(100); expect(records[2].get('threadId')).toBe(100); }); }); } createSuite(false); createSuite(true); }); describe("getters/setters", function() { function createSuite(withSession) { describe(withSession ? "with session" : "without session", function() { var spy, session, post, thread; beforeEach(function() { spy = jasmine.createSpy(); if (withSession) { session = new Ext.data.Session(); } }); afterEach(function() { if (withSession) { session.destroy(); } session = post = thread = null; }); describe("the one", function() { beforeEach(function() { definePost(); }); describe("getter", function() { beforeEach(function() { post = new Post({ id: 4 }, session); }); describe("without an instance", function() { describe("with no foreign key value", function() { it("should return null", function() { expect(post.getThread()).toBeNull(); }); it("should not make any request", function() { spy = spyOn(Thread.getProxy(), 'read'); post.getThread(); expect(spy).not.toHaveBeenCalled(); }); describe("callbacks", function() { it("should call the callbacks before the function returns", function() { post.getThread(spy); expect(spy).toHaveBeenCalled(); spy.reset(); post.getThread({ success: spy }); expect(spy).toHaveBeenCalled(); spy.reset(); post.getThread({ callback: spy }); expect(spy).toHaveBeenCalled(); }); it("should accept a function as the callback and default the scope to the model", function() { post.getThread(spy); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1]).toBeNull(); expect(call.args[2]).toBe(true); expect(call.object).toBe(post); }); it("should accept a function with a scope", function() { var o = {}; post.getThread(spy, o); expect(spy.mostRecentCall.object).toBe(o); }); it("should accept an options object with success and default the scope to the model", function() { post.getThread({ success: spy }); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1]).toBeNull(); expect(call.object).toBe(post); }); it("should accept an options object with success and a scope", function() { var o = {}, call; post.getThread({ scope: o, success: spy }); call = spy.mostRecentCall; expect(call.object).toBe(o); }); it("should accept an options object with callback and default the scope to the model", function() { post.getThread({ callback: spy }); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1]).toBeNull(); expect(call.args[2]).toBe(true); expect(call.object).toBe(post); }); it("should accept an options object with callback and a scope", function() { var o = {}, call; post.getThread({ scope: o, callback: spy }); call = spy.mostRecentCall; expect(call.object).toBe(o); }); }); }); describe("with a foreign key value", function() { beforeEach(function() { post.set('threadId', 17); }); if (withSession) { it("should create an instance in the session", function() { expect(post.getThread()).toBe(session.getRecord('Thread', 17, false)); }); it("should use an existing record instance", function() { thread = session.getRecord('Thread', 17, false); expect(post.getThread()).toBe(thread); }); it("should not load an existing instance", function() { thread = session.getRecord('Thread', { id: 17 }, false); post.getThread(); expect(thread.isLoading()).toBe(false); }); } it("should return an instance with the matching id", function() { expect(post.getThread().getId()).toBe(17); }); it("should be in a loading state", function() { expect(post.getThread().isLoading()).toBe(true); }); it("should trigger a load for the record", function() { spy = spyOn(Thread.getProxy(), 'read'); post.getThread(); expect(spy.mostRecentCall.args[0].getId()).toBe(17); }); describe("calling while during a load", function() { it("should return the same record", function() { var rec = post.getThread(); expect(post.getThread()).toBe(rec); }); it("should not trigger a second load", function() { post.getThread(); spy = spyOn(Thread.getProxy(), 'read'); post.getThread(); expect(spy).not.toHaveBeenCalled(); }); it("should not trigger any callback until load completes", function() { post.getThread(); post.getThread({ success: spy, callback: spy }); expect(spy).not.toHaveBeenCalled(); }); it("should trigger the callbacks once loaded", function() { post.getThread(); post.getThread({ success: spy, callback: spy }); complete({}); expect(spy.callCount).toBe(2); }); }); describe("callbacks", function() { it("should not trigger any callbacks until the load completes", function() { post.getThread(spy); post.getThread({ success: spy }); post.getThread({ failure: spy }); post.getThread({ callback: spy }); expect(spy).not.toHaveBeenCalled(); }); describe("when successful", function() { it("should accept a function as the callback and default the scope to the model", function() { thread = post.getThread(spy); complete({}); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1].isOperation).toBe(true); expect(call.args[2]).toBe(true); expect(call.object).toBe(post); }); it("should accept a function with a scope", function() { var o = {}; post.getThread(spy, o); complete({}); expect(spy.mostRecentCall.object).toBe(o); }); it("should accept an options object with success and default the scope to the model", function() { thread = post.getThread({ success: spy }); complete({}); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1].isOperation).toBe(true); expect(call.object).toBe(post); }); it("should accept an options object with success and a scope", function() { var o = {}, call; post.getThread({ scope: o, success: spy }); complete({}); call = spy.mostRecentCall; expect(call.object).toBe(o); }); it("should accept an options object with callback and default the scope to the model", function() { thread = post.getThread({ callback: spy }); complete({}); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1].isOperation).toBe(true); expect(call.args[2]).toBe(true); expect(call.object).toBe(post); }); it("should accept an options object with callback and a scope", function() { var o = {}, call; post.getThread({ scope: o, callback: spy }); complete({}); call = spy.mostRecentCall; expect(call.object).toBe(o); }); }); describe("when failed", function() { it("should accept a function as the callback and default the scope to the model", function() { thread = post.getThread(spy); complete(null, 500); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1].isOperation).toBe(true); expect(call.args[2]).toBe(false); expect(call.object).toBe(post); }); it("should accept a function with a scope", function() { var o = {}; post.getThread(spy, o); complete(null, 500); expect(spy.mostRecentCall.object).toBe(o); }); it("should accept an options object with failure and default the scope to the model", function() { thread = post.getThread({ failure: spy }); complete(null, 500); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1].isOperation).toBe(true); expect(call.object).toBe(post); }); it("should accept an options object with failure and a scope", function() { var o = {}, call; post.getThread({ scope: o, failure: spy }); complete(null, 500); call = spy.mostRecentCall; expect(call.object).toBe(o); }); it("should accept an options object with callback and default the scope to the model", function() { thread = post.getThread({ callback: spy }); complete(null, 500); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1].isOperation).toBe(true); expect(call.args[2]).toBe(false); expect(call.object).toBe(post); }); it("should accept an options object with callback and a scope", function() { var o = {}, call; post.getThread({ scope: o, callback: spy }); complete(null, 500); call = spy.mostRecentCall; expect(call.object).toBe(o); }); }); }); }); }); describe("with an already loaded instance", function() { beforeEach(function() { thread = new Thread({ id: 2 }, session); post.setThread(thread); }); it("should return the same instance", function() { expect(post.getThread()).toBe(thread); }); it("should not attempt to load", function() { spy = spyOn(Thread.getProxy(), 'read'); post.getThread(); expect(spy).not.toHaveBeenCalled(); }); it("should attempt to reload if called with options.reload", function() { spy = spyOn(Thread.getProxy(), 'read').andReturn(); post.getThread({ reload: true }); expect(spy).toHaveBeenCalled(); }); it("should reload the same record when called with reload", function() { var result = post.getThread({ reload: true }); expect(result).toBe(thread); }); describe("callbacks", function() { it("should call the callbacks before the function returns", function() { post.getThread(spy); expect(spy).toHaveBeenCalled(); spy.reset(); post.getThread({ success: spy }); expect(spy).toHaveBeenCalled(); spy.reset(); post.getThread({ callback: spy }); expect(spy).toHaveBeenCalled(); }); it("should accept a function as the callback and default the scope to the model", function() { post.getThread(spy); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1]).toBeNull(); expect(call.args[2]).toBe(true); expect(call.object).toBe(post); }); it("should accept a function with a scope", function() { var o = {}; post.getThread(spy, o); expect(spy.mostRecentCall.object).toBe(o); }); it("should accept an options object with success and default the scope to the model", function() { post.getThread({ success: spy }); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1]).toBeNull(); expect(call.object).toBe(post); }); it("should accept an options object with success and a scope", function() { var o = {}, call; post.getThread({ scope: o, success: spy }); call = spy.mostRecentCall; expect(call.object).toBe(o); }); it("should accept an options object with callback and default the scope to the model", function() { post.getThread({ callback: spy }); var call = spy.mostRecentCall; expect(call.args[0]).toBe(thread); expect(call.args[1]).toBeNull(); expect(call.args[2]).toBe(true); expect(call.object).toBe(post); }); it("should accept an options object with callback and a scope", function() { var o = {}, call; post.getThread({ scope: o, callback: spy }); call = spy.mostRecentCall; expect(call.object).toBe(o); }); }); }); }); describe("setter", function() { beforeEach(function() { post = new Post({ id: 7 }, session); }); describe("insta