├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jscsrc ├── .jshintrc ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep ├── adapters │ └── socket.js ├── helpers │ └── function-test-helper.js ├── initializers │ └── socket-request-response-logger.js ├── serializer-response.js ├── serializers │ └── socket.js └── services │ └── store.js ├── app ├── .gitkeep ├── adapters │ ├── application.js │ └── socket-adapter.js ├── helpers │ └── function-test-helper.js ├── initializers │ └── socket-request-response-logger.js ├── serializers │ ├── application.js │ └── socket-serializer.js └── services │ └── store.js ├── bower.json ├── config ├── ember-try.js ├── environment.js └── release.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.json ├── tests ├── .jshintrc ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ └── components │ │ │ └── .gitkeep │ ├── config │ │ └── environment.js │ └── public │ │ ├── crossdomain.xml │ │ └── robots.txt ├── fixtures.js ├── helpers │ ├── destroy-app.js │ ├── module-for-acceptance.js │ ├── resolver.js │ ├── start-app.js │ └── store.js ├── index.html ├── integration │ ├── .gitkeep │ └── socket-adapter-test.js ├── test-helper.js └── unit │ ├── .gitkeep │ └── helpers │ └── function-test-helper-test.js └── vendor ├── .gitkeep └── ember-data └── .bower.json /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | 19 | /.tags 20 | /.tags1 21 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "ember-suave", 3 | 4 | "requireCommentsToIncludeAccess": false, 5 | "requireCamelCaseOrUpperCaseIdentifiers": false, 6 | "disallowMultiLineTernary": true, 7 | "disallowNestedTernaries": true, 8 | 9 | "disallowMultipleVarDecl": false, 10 | "disallowVar": false, 11 | "requireObjectDestructuring": false, 12 | "requireArrayDestructuring": false, 13 | "requireTemplateStringsForConcatenation": false, 14 | "requireEnhancedObjectLiterals": false, 15 | "requireLineBreakAfterVariableAssignment": false, 16 | "requireDotNotation": false, 17 | "disallowTrailingComma": false, 18 | "validateQuoteMarks": false 19 | } 20 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | tests/ 3 | tmp/ 4 | dist/ 5 | 6 | .bowerrc 7 | .editorconfig 8 | .ember-cli 9 | .travis.yml 10 | .npmignore 11 | **/.gitkeep 12 | bower.json 13 | Brocfile.js 14 | testem.json 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | env: 13 | - EMBER_TRY_SCENARIO=default 14 | - EMBER_TRY_SCENARIO=ember-release 15 | - EMBER_TRY_SCENARIO=ember-beta 16 | - EMBER_TRY_SCENARIO=ember-canary 17 | 18 | matrix: 19 | fast_finish: true 20 | allow_failures: 21 | - env: EMBER_TRY_SCENARIO=ember-release 22 | - env: EMBER_TRY_SCENARIO=ember-beta 23 | - env: EMBER_TRY_SCENARIO=ember-canary 24 | 25 | before_install: 26 | - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH 27 | - "npm config set spin false" 28 | - "npm install -g npm@^2" 29 | 30 | install: 31 | - npm install -g bower 32 | - npm install 33 | - bower install 34 | 35 | script: 36 | - ember try $EMBER_TRY_SCENARIO test 37 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Socket-adapter 2 | 3 | This README outlines the details of collaborating on this Ember addon. 4 | 5 | ## Installation 6 | 7 | * `git clone` this repository 8 | * `npm install` 9 | * `bower install` 10 | 11 | ## Running 12 | 13 | * `ember server` 14 | * Visit your app at http://localhost:4200. 15 | 16 | ## Running Tests 17 | 18 | * `npm test` (Runs `ember try:testall` to test your addon against multiple Ember versions) 19 | * `ember test` 20 | * `ember test --server` 21 | 22 | ## Building 23 | 24 | * `ember build` 25 | 26 | For more information on using ember-cli, visit [http://www.ember-cli.com/](http://www.ember-cli.com/). 27 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/addon/.gitkeep -------------------------------------------------------------------------------- /addon/adapters/socket.js: -------------------------------------------------------------------------------- 1 | /* global io */ 2 | /*jshint camelcase: false */ 3 | import DS from 'ember-data'; 4 | import Ember from 'ember'; 5 | import { requestResponseLogger } from './../initializers/socket-request-response-logger'; 6 | 7 | const { 8 | get, 9 | set, 10 | computed, 11 | Logger: { debug } 12 | } = Ember; 13 | 14 | function printRequestStack(requestHash) { 15 | const e = new Error(); 16 | if (!e.stack) { 17 | return; // phantomjs for 18 | } 19 | const stack = e.stack.replace(/^[^\(]+?[\n$]/gm, '') 20 | .replace(/^\s+at\s+/gm, '') 21 | .replace(/^Object.\s*\(/gm, '{anonymous}()@') 22 | .split('\n'); 23 | stack.shift(); 24 | stack.unshift('\n'); 25 | debug(requestHash, stack.join('\n')); 26 | } 27 | 28 | export default DS.RESTAdapter.extend({ 29 | socketAddress: 'http://api.collectrium.websocket:5000', 30 | socketHandshakeQuery: '', 31 | bulkOperationsSupport: { 32 | createRecord: false, 33 | updateRecord: false, 34 | deleteRecord: false 35 | }, 36 | updateAsPatch: true, 37 | logRequests: true, 38 | collectRequestResponseLog: true, 39 | socketConnections: computed(function() { 40 | return Ember.Object.create(); 41 | }), 42 | requestsPool: computed(function() { 43 | return []; 44 | }), 45 | 46 | /** 47 | * generate unique request id 48 | * @returns {string} 49 | */ 50 | generateRequestId() { 51 | const S4 = function() { 52 | return Math.floor(Math.random() * 0x10000).toString(16); // 65536 53 | }; 54 | 55 | return ( 56 | S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4() 57 | ); 58 | }, 59 | /** 60 | * 61 | * @param type 62 | * @param options 63 | * @returns {Ember.get|*|Object} 64 | */ 65 | getConnection(type, options) { 66 | /*jshint -W004 */ 67 | var address = get(this, 'socketAddress') + '/'; 68 | var requestsPool = get(this, 'requestsPool'); 69 | type = type.modelName; 70 | var connections = get(this, 'socketConnections'); 71 | var socketNS = type && get(connections, type); 72 | var onConnectFailed = this.onConnectFailed; 73 | var onError = this.onError; 74 | var adapter = this; 75 | const store = adapter.store; 76 | const collectRequestResponseLog = get(this, 'collectRequestResponseLog'); 77 | 78 | if (arguments.length === 1) { 79 | options = {}; 80 | } 81 | if (!type) { 82 | options = arguments[0]; 83 | } 84 | if (socketNS) { 85 | // TODO: not sure that manually socket reconnecting is required 86 | if (socketNS.hasOwnProperty('socket') && !socketNS.socket.connected && !socketNS.socket.connecting) { 87 | socketNS.socket.connect(); 88 | } 89 | } else { 90 | if (type) { 91 | address = `${address}${this.pathForType(type)}/`; 92 | } 93 | socketNS = io.connect(address, options); 94 | if (type) { 95 | socketNS.on('message', function(response) { 96 | if (response.hasOwnProperty('errors')) { 97 | if (response.request_id && requestsPool[response.request_id]) { 98 | var rejecter = requestsPool[response.request_id].reject; 99 | delete response.request_id; 100 | delete requestsPool[response.request_id]; 101 | Ember.run(null, rejecter, response); 102 | } 103 | } else { 104 | if (response.request_id && requestsPool[response.request_id]) { 105 | var resolver = requestsPool[response.request_id].resolve; 106 | if (collectRequestResponseLog) { 107 | requestResponseLogger.logResponse(response); 108 | } 109 | delete response.request_id; 110 | delete requestsPool[response.request_id]; 111 | Ember.run(null, resolver, response); 112 | } else { 113 | /** 114 | * Handling PUSH notifications 115 | * Operations can be only multiple 116 | */ 117 | store.trigger('notification', response); 118 | // if response contains only ids array it means that we receive DELETE 119 | if (response.ids) { 120 | // remove all records from store without sending DELETE requests 121 | response.ids.forEach(function(id) { 122 | const record = store.getById(type, id); 123 | if (record) { 124 | store.unloadRecord(record); 125 | } 126 | }); 127 | } 128 | // we receive CREATE or UPDATE, ember-data will manage data itself 129 | else { 130 | if (response.hasOwnProperty('payload')) { 131 | store.pushPayload(type, response.payload); 132 | } 133 | } 134 | } 135 | } 136 | }); 137 | set(connections, type, socketNS); 138 | } else { 139 | socketNS.on('connect_failed', function(response) { 140 | if (onConnectFailed) { 141 | onConnectFailed.call(adapter, response); 142 | } 143 | }); 144 | socketNS.on('error', function(response) { 145 | if (onError) { 146 | onError.call(adapter, response); 147 | } 148 | }); 149 | } 150 | } 151 | return socketNS; 152 | }, 153 | 154 | /** 155 | * 156 | * @param type 157 | * @param requestType 158 | * @param hash 159 | * @returns {Ember.RSVP.Promise} 160 | */ 161 | send: function(type, requestType, hash) { 162 | const connection = this.getConnection(type); 163 | const requestsPool = get(this, 'requestsPool'); 164 | const requestId = this.generateRequestId(); 165 | const modelName = type.modelName; 166 | const deffered = Ember.RSVP.defer('DS: SocketAdapter#emit ' + requestType + ' to ' + modelName); 167 | const logRequests = get(this, 'logRequests'); 168 | const collectRequestResponseLog = get(this, 'collectRequestResponseLog'); 169 | if (!(hash instanceof Object)) { 170 | hash = {}; 171 | } 172 | deffered.requestType = requestType; 173 | hash.request_id = requestId; 174 | if (collectRequestResponseLog) { 175 | requestResponseLogger.logRequest({ 176 | modelName, 177 | operation: requestType, 178 | hash 179 | }); 180 | } 181 | requestsPool[requestId] = deffered; 182 | if (logRequests) { 183 | printRequestStack(hash); 184 | } 185 | connection.emit(requestType, hash); 186 | return deffered.promise; 187 | }, 188 | 189 | /** 190 | * Fetching all resources of a given type from server, can't be called with params. 191 | * Returns resources without full relations. 192 | * @param store 193 | * @param type 194 | * @returns {Ember.RSVP.Promise} 195 | */ 196 | findAll: function(store, type) { 197 | return this.send(type, 'READ_LIST'); 198 | }, 199 | 200 | /** 201 | * 202 | * @param store 203 | * @param type 204 | * @param query 205 | * @returns {Ember.RSVP.Promise} 206 | */ 207 | query: function(store, type, query) { 208 | return this.send(type, 'READ_LIST', query); 209 | }, 210 | 211 | /** 212 | * 213 | * @param store 214 | * @param type 215 | * @param id 216 | * @returns {Ember.RSVP.Promise} 217 | */ 218 | findRecord: function(store, type, id) { 219 | const model = store.modelFor(type.modelName), data = { 220 | id: id 221 | }; 222 | if (model._findByIdParams) { 223 | if (model._findByIdParams.include) { 224 | data['include'] = model._findByIdParams.include; 225 | } 226 | if (model._findByIdParams.fields) { 227 | data['fields'] = model._findByIdParams.fields; 228 | } 229 | } 230 | return this.send(type, 'READ', data); 231 | }, 232 | 233 | /** 234 | * 235 | * @param store 236 | * @param type 237 | * @param record 238 | * @returns {Ember.RSVP.Promise} 239 | */ 240 | createRecord(store, type, snapshot) { 241 | const serializer = store.serializerFor(type.modelName); 242 | const data = {}; 243 | serializer.serializeIntoHash(data, type, snapshot, { includeId: true }); 244 | 245 | return this.send(type, 'CREATE', data); 246 | }, 247 | 248 | /** 249 | * 250 | * @param store 251 | * @param type 252 | * @param records 253 | * @returns {Ember.RSVP.Promise} 254 | */ 255 | createRecords: function(store, type, snapshots) { 256 | const serializer = store.serializerFor(type.modelName); 257 | const data = {}; 258 | serializer.serializeIntoHash(data, type, snapshots, { includeId: true }); 259 | 260 | return this.send(type, 'CREATE_LIST', data); 261 | }, 262 | 263 | /** 264 | * 265 | * @param store 266 | * @param type 267 | * @param record 268 | * @returns {*|ajax|v.support.ajax|jQuery.ajax|Promise|E.ajax} 269 | */ 270 | updateRecord: function(store, type, snapshot) { 271 | const serializer = store.serializerFor(type.modelName); 272 | const data = {}; 273 | const updateAsPatch = get(this, 'updateAsPatch'); 274 | serializer.serializeIntoHash(data, type, snapshot, { 275 | includeId: true, 276 | updateAsPatch 277 | }); 278 | 279 | return this.send(type, 'UPDATE', data); 280 | }, 281 | 282 | /** 283 | * 284 | * @param store 285 | * @param type 286 | * @param records 287 | * @returns {Ember.RSVP.Promise} 288 | */ 289 | updateRecords: function(store, type, snapshots) { 290 | const serializer = store.serializerFor(type.modelName); 291 | const updateAsPatch = get(this, 'updateAsPatch'); 292 | const data = {}; 293 | serializer.serializeIntoHash(data, type, snapshots, { 294 | includeId: true, 295 | updateAsPatch 296 | }); 297 | 298 | return this.send(type, 'UPDATE_LIST', data); 299 | }, 300 | 301 | /** 302 | * 303 | * @param store 304 | * @param type 305 | * @param record 306 | * @returns {Ember.RSVP.Promise} 307 | */ 308 | deleteRecord: function(store, type, record) { 309 | var id = get(record, 'id'); 310 | 311 | return this.send(type, 'DELETE', { id }); 312 | }, 313 | 314 | /** 315 | * 316 | * @param store 317 | * @param type 318 | * @param records 319 | * @returns {Ember.RSVP.Promise} 320 | */ 321 | deleteRecords: function(store, type, records) { 322 | const data = { 323 | ids: [] 324 | }; 325 | 326 | records.forEach(function(record) { 327 | data.ids.push(get(record, 'id')); 328 | }); 329 | 330 | return this.send(type, 'DELETE_LIST', data); 331 | }, 332 | 333 | openSocket: Ember.on('init', function() { 334 | var config = { 335 | resource: 'handshake' 336 | }; 337 | var socketHandshakeQuery = get(this, 'socketHandshakeQuery'); 338 | if (socketHandshakeQuery) { 339 | config.query = socketHandshakeQuery; 340 | } else if (this.version) { 341 | config.query = 'version=' + this.version; 342 | } 343 | this.getConnection(config); 344 | }) 345 | }); 346 | -------------------------------------------------------------------------------- /addon/helpers/function-test-helper.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export function functionTestHelper(params/*, hash*/) { 4 | return params; 5 | } 6 | 7 | export default Ember.Helper.helper(functionTestHelper); 8 | -------------------------------------------------------------------------------- /addon/initializers/socket-request-response-logger.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | const { 3 | ArrayProxy, 4 | A: createArray 5 | } = Ember; 6 | 7 | export const requestResponseLogger = ArrayProxy.create({ 8 | _requests: createArray(), 9 | content: createArray(), 10 | logRequest(request) { 11 | this._requests.addObject(request); 12 | }, 13 | logResponse(response) { 14 | const request = this._requests.findBy('hash.request_id', response.request_id); 15 | delete request.hash.request_id; 16 | delete response.request_id; 17 | this._requests.removeObject(request); 18 | this.addObject({ 19 | request, 20 | response 21 | }); 22 | } 23 | }); 24 | 25 | export default { 26 | name: 'socket-request-response-logger', 27 | initialize(registry, application) { 28 | application.register('socket-request-response-logger:-main', requestResponseLogger, { instantiate: false, singleton: true }); 29 | application.inject('socket-request-response-logger:-main', 'application', 'application:main'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /addon/serializer-response.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import Ember from 'ember'; 3 | 4 | const { 5 | Model 6 | } = DS; 7 | 8 | const { 9 | get, 10 | } = Ember; 11 | 12 | /** 13 | Convert the payload from `serializer.extract` to a JSON-API Document. 14 | @method _normalizeSerializerPayload 15 | @private 16 | @param {subclass of DS.Model} modelClass 17 | @param {Object} payload 18 | @return {Object} JSON-API Document 19 | */ 20 | export function _normalizeSerializerPayload(modelClass, payload) { 21 | let data = null; 22 | 23 | if (payload) { 24 | if (Ember.typeOf(payload) === 'array') { 25 | data = payload.map((payload) => _normalizeSerializerPayloadItem(modelClass, payload)); 26 | } else { 27 | data = _normalizeSerializerPayloadItem(modelClass, payload); 28 | } 29 | } 30 | 31 | return { data }; 32 | } 33 | 34 | /** 35 | Convert the payload representing a single record from `serializer.extract` to 36 | a JSON-API Resource Object. 37 | @method _normalizeSerializerPayloadItem 38 | @private 39 | @param {subclass of DS.Model} modelClass 40 | @param {Object} payload 41 | @return {Object} JSON-API Resource Object 42 | */ 43 | export function _normalizeSerializerPayloadItem(modelClass, itemPayload) { 44 | var item = {}; 45 | 46 | item.id = '' + itemPayload.id; 47 | item.type = modelClass.modelName; 48 | item.attributes = {}; 49 | item.relationships = {}; 50 | 51 | modelClass.eachAttribute(function(name) { 52 | if (itemPayload.hasOwnProperty(name)) { 53 | item.attributes[name] = itemPayload[name]; 54 | } 55 | }); 56 | 57 | modelClass.eachRelationship(function(key, relationshipMeta) { 58 | var relationship, value; 59 | 60 | if (itemPayload.hasOwnProperty(key)) { 61 | relationship = {}; 62 | value = itemPayload[key]; 63 | let normalizeRelationshipData = function(value, relationshipMeta) { 64 | if (Ember.isNone(value)) { 65 | return null; 66 | } 67 | // Temporary support for https://github.com/emberjs/data/issues/3271 68 | if (value instanceof Model) { 69 | value = { id: value.id, type: value.constructor.modelName }; 70 | } 71 | if (Ember.typeOf(value) === 'object') { 72 | Ember.assert('Ember Data expected a number or string to represent the record(s) in the `' + key + '` relationship instead it found an object. If this is a polymorphic relationship please specify a `type` key. If this is an embedded relationship please include the `DS.EmbeddedRecordsMixin` and specify the `' + key + '` property in your serializer\'s attrs object.', value.type); 73 | if (value.id) { 74 | value.id = `${value.id}`; 75 | } 76 | return value; 77 | } 78 | 79 | Ember.assert("A " + relationshipMeta.parentType + " record was pushed into the store with the value of " + key + " being " + Ember.inspect(value) + ", but " + key + " is a belongsTo relationship so the value must not be an array. You should probably check your data payload or serializer.", !Ember.isArray(value)); 80 | return { id: `${value}`, type: relationshipMeta.type }; 81 | }; 82 | 83 | if (relationshipMeta.kind === 'belongsTo') { 84 | relationship.data = normalizeRelationshipData(value, relationshipMeta); 85 | // handle the belongsTo polymorphic case, where { post:1, postType: 'video' } 86 | if (relationshipMeta.options && relationshipMeta.options.polymorphic && itemPayload[key + 'Type']) { 87 | relationship.data.type = itemPayload[key + 'Type']; 88 | } 89 | } else if (relationshipMeta.kind === 'hasMany') { 90 | // || [] because the hasMany could be === null 91 | Ember.assert("A " + relationshipMeta.parentType + " record was pushed into the store with the value of " + key + " being '" + Ember.inspect(value) + "', but " + key + " is a hasMany relationship so the value must be an array. You should probably check your data payload or serializer.", Ember.isArray(value) || value === null); 92 | 93 | var relationshipData = Ember.A(value || []); 94 | relationship.data = relationshipData.map((item) => normalizeRelationshipData(item, relationshipMeta)); 95 | } 96 | } 97 | 98 | if (itemPayload.links && itemPayload.links.hasOwnProperty(key)) { 99 | relationship = relationship || {}; 100 | value = itemPayload.links[key]; 101 | 102 | relationship.links = { 103 | related: value 104 | }; 105 | } 106 | 107 | if (relationship) { 108 | relationship.meta = get(itemPayload, `meta.${key}`); 109 | item.relationships[key] = relationship; 110 | } 111 | }); 112 | 113 | return item; 114 | } 115 | -------------------------------------------------------------------------------- /addon/serializers/socket.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import Ember from 'ember'; 3 | 4 | const { 5 | get, 6 | keys, 7 | copy, 8 | String: { underscore }, 9 | merge, 10 | compare, 11 | isArray 12 | } = Ember; 13 | 14 | const { 15 | RESTSerializer 16 | } = DS; 17 | 18 | export default RESTSerializer.extend({ 19 | extractFindQuery(store, type, payload) { 20 | return this.extractArray(store, type, payload.payload); 21 | }, 22 | buildDiff(hash, snapshot) { 23 | hash = copy(hash); 24 | const isNew = get(snapshot, 'isNew'); 25 | if (isNew) { 26 | return hash; 27 | } 28 | const diff = { 29 | id: hash.id 30 | }; 31 | const relationships = snapshot._internalModel._relationships; 32 | const initializedRelationshipsKeys = keys(relationships.initializedRelationships); 33 | const relationshipsData = {}; 34 | initializedRelationshipsKeys.forEach((key) => { 35 | const relationship = relationships.get(key); 36 | if (relationship.hasData && relationship.hasLoaded && relationship.canonicalState) { 37 | if (isArray(relationship.canonicalState)) { 38 | relationshipsData[key] = relationship.canonicalState.map((internalModel) => { 39 | return get(internalModel.getRecord(), 'id'); 40 | }); 41 | } else { 42 | relationshipsData[key] = get(relationship.canonicalState.getRecord(), 'id'); 43 | } 44 | } else { 45 | relationshipsData[key] = null; 46 | } 47 | }); 48 | const attributesData = get(snapshot, 'data'); 49 | const possibleHash = merge(attributesData, relationshipsData); 50 | keys(possibleHash).forEach((key) => { 51 | if (this.isDiffer(hash[key], possibleHash[key]) && hash[key] !== undefined) { 52 | // TODO: handle dates and all transforms processed data correctly 53 | diff[key] = hash[key]; 54 | } 55 | }); 56 | 57 | return diff; 58 | }, 59 | isDiffer(a, b) { 60 | return compare(a, b); 61 | }, 62 | payloadKeyFromModelName(modelName) { 63 | return underscore(modelName); 64 | }, 65 | serializeIntoHash(hash, typeClass, snapshot, options) { 66 | const isBulkOperation = snapshot instanceof Array; 67 | const normalizedRootKey = this.payloadKeyFromModelName(typeClass.modelName); 68 | if (isBulkOperation) { 69 | const bulkPayload = []; 70 | snapshot.forEach((snapshotData) => { 71 | bulkPayload.push(this.serialize(snapshotData, options)); 72 | }); 73 | hash[normalizedRootKey] = bulkPayload; 74 | } else { 75 | hash[normalizedRootKey] = this.serialize(snapshot, options); 76 | } 77 | }, 78 | extractFindAll(store, type, payload) { 79 | return this.extractArray(store, type, payload.payload); 80 | }, 81 | extractFindMany(store, type, payload) { 82 | return this.extractArray(store, type, payload.payload); 83 | }, 84 | extractCreateRecords(store, type, payload) { 85 | return this.extractArray(store, type, payload); 86 | }, 87 | extractUpdateRecords(store, type, payload) { 88 | return this.extractArray(store, type, payload); 89 | }, 90 | extractDeleteRecords(store, type, payload) { 91 | return this.extractArray(store, type, payload); 92 | }, 93 | serialize(snapshot, options = {}) { 94 | const { updateAsPatch } = options; 95 | let hash = this._super(snapshot, options); 96 | if (updateAsPatch) { 97 | hash = this.buildDiff(hash, snapshot); 98 | } 99 | return hash; 100 | } 101 | }); 102 | -------------------------------------------------------------------------------- /addon/services/store.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import Ember from 'ember'; 3 | 4 | import { 5 | _normalizeSerializerPayload 6 | } from './../serializer-response'; 7 | 8 | const { 9 | get, 10 | String: { pluralize }, 11 | assert, 12 | Evented 13 | } = Ember; 14 | 15 | // copied from ember-data store core 16 | function isThenable(object) { 17 | return object && typeof object.then === 'function'; 18 | } 19 | // copied from ember-data store core 20 | function serializerFor(container, type, defaultSerializer) { 21 | return container.lookup('serializer:' + type) || 22 | container.lookup('serializer:application') || 23 | container.lookup('serializer:' + defaultSerializer) || 24 | container.lookup('serializer:-default'); 25 | } 26 | // copied from ember-data store core 27 | function serializerForAdapter(adapter, type) { 28 | let serializer = adapter.serializer; 29 | const defaultSerializer = adapter.defaultSerializer; 30 | const container = adapter.container; 31 | 32 | if (container && serializer === undefined) { 33 | serializer = serializerFor(container, type.modelName, defaultSerializer); 34 | } 35 | 36 | if (serializer === null || serializer === undefined) { 37 | serializer = { 38 | extract: function(store, type, payload) { 39 | return payload; 40 | } 41 | }; 42 | } 43 | 44 | return serializer; 45 | } 46 | // copied from ember-data store core 47 | function _commit(adapter, store, operation, snapshot) { 48 | const internalModel = snapshot._internalModel; 49 | const modelName = snapshot.modelName; 50 | const typeClass = store.modelFor(modelName); 51 | const promise = adapter[operation](store, typeClass, snapshot); 52 | const serializer = serializerForAdapter(store, adapter, modelName); 53 | const label = 'DS: Extract and notify about ' + operation + ' completion of ' + modelName; 54 | 55 | assert("Your adapter's '" + operation + "' method must return a value, but it returned `undefined", promise !== undefined); 56 | 57 | return promise.then((adapterPayload) => { 58 | let payload; 59 | 60 | store._adapterRun(() => { 61 | if (adapterPayload) { 62 | payload = serializer.extract(store, typeClass, adapterPayload, snapshot.id, operation); 63 | } else { 64 | payload = adapterPayload; 65 | } 66 | store.didSaveRecord(internalModel, _normalizeSerializerPayload(typeClass, payload)); 67 | }); 68 | return internalModel; 69 | }, (reason) => { 70 | if (reason instanceof DS.InvalidError) { 71 | store.recordWasInvalid(internalModel, reason.errors); 72 | } else { 73 | store.recordWasError(internalModel, reason); 74 | } 75 | throw reason; 76 | }, label); 77 | } 78 | 79 | function _bulkCommit(adapter, store, operation, modelName, snapshots) { 80 | const typeClass = store.modelFor(modelName); 81 | const promise = adapter[operation](store, typeClass, snapshots); 82 | const serializer = serializerForAdapter(store, adapter, modelName); 83 | const label = 'DS: Extract and notify about ' + operation + ' completion of ' + snapshots.length + ' of type ' + modelName; 84 | const internalModels = snapshots.map((snapshot) => snapshot._internalModel); 85 | assert('Your adapter\'s ' + operation + ' method must return a promise, but it returned ' + promise, isThenable(promise)); 86 | 87 | return promise.then((adapterPayload) => { 88 | let payload; 89 | 90 | store._adapterRun(function() { 91 | if (adapterPayload) { 92 | payload = serializer.extract(store, typeClass, adapterPayload, null, operation); 93 | } else { 94 | payload = adapterPayload; 95 | } 96 | internalModels.forEach((internalModel, index) => { 97 | store.didSaveRecord(internalModel, _normalizeSerializerPayload(typeClass, payload && payload[index])); 98 | }); 99 | }); 100 | return internalModels; 101 | }, function(reason) { 102 | internalModels.forEach((internalModel) => { 103 | if (reason instanceof DS.InvalidError) { 104 | store.recordWasInvalid(internalModel, reason.errors); 105 | } else { 106 | store.recordWasError(internalModel, reason); 107 | } 108 | }); 109 | throw reason; 110 | }, label); 111 | } 112 | 113 | export default DS.Store.extend(Evented, { 114 | flushPendingSave: function() { 115 | const pending = this._pendingSave.slice(); 116 | this._pendingSave = []; 117 | const bulkRecords = []; 118 | const bulkDataTypeMap = []; 119 | const bulkDataResolvers = []; 120 | const bulkDataAdapters = []; 121 | const bulkDataOperationMap = [ 122 | 'createRecord', 123 | 'deleteRecord', 124 | 'updateRecord' 125 | ]; 126 | 127 | pending.forEach((pendingItem) => { 128 | const snapshot = pendingItem.snapshot; 129 | const internalModel = snapshot._internalModel; 130 | const resolver = pendingItem.resolver; 131 | const type = internalModel.type.modelName; 132 | const adapter = this.adapterFor(type); 133 | let bulkSupport; 134 | let operation; 135 | let typeIndex; 136 | let operationIndex; 137 | 138 | if (internalModel.isNew()) { 139 | operation = 'createRecord'; 140 | } else if (internalModel.isDeleted()) { 141 | operation = 'deleteRecord'; 142 | } else { 143 | operation = 'updateRecord'; 144 | } 145 | bulkSupport = get(adapter, 'bulkOperationsSupport')[operation]; 146 | 147 | if (bulkSupport) { 148 | operationIndex = bulkDataOperationMap.indexOf(operation); 149 | typeIndex = bulkDataTypeMap.indexOf(type); 150 | if (typeIndex < 0) { 151 | bulkDataTypeMap.push(type); 152 | typeIndex = bulkDataTypeMap.length - 1; 153 | bulkRecords[typeIndex] = []; 154 | bulkDataResolvers[typeIndex] = []; 155 | bulkDataAdapters[typeIndex] = adapter; 156 | } 157 | if (!(bulkRecords[typeIndex][operationIndex] instanceof Array)) { 158 | bulkRecords[typeIndex][operationIndex] = []; 159 | } 160 | if (!(bulkDataResolvers[typeIndex][operationIndex] instanceof Array)) { 161 | bulkDataResolvers[typeIndex][operationIndex] = []; 162 | } 163 | bulkDataResolvers[typeIndex][operationIndex].push(resolver); 164 | bulkRecords[typeIndex][operationIndex].push(snapshot); 165 | } else { 166 | resolver.resolve(_commit(adapter, this, operation, snapshot)); 167 | } 168 | }); 169 | 170 | /*jshint -W083 */ 171 | if (bulkRecords.length) { 172 | for (let i = 0; i < bulkRecords.length; i++) { 173 | for (let j = 0; j < bulkDataOperationMap.length; j++) { 174 | if (bulkRecords[i][j] && bulkRecords[i][j].length) { 175 | if (bulkRecords[i][j].length === 1) { 176 | bulkDataResolvers[i][j][0].resolve(_commit(bulkDataAdapters[i], this, bulkDataOperationMap[j], bulkRecords[i][j][0])); 177 | } else { 178 | _bulkCommit(bulkDataAdapters[i], this, pluralize(bulkDataOperationMap[j]), bulkDataTypeMap[i], bulkRecords[i][j]) 179 | .then( 180 | (snapshots) => { 181 | snapshots.forEach((snapshot, index) => { 182 | bulkDataResolvers[i][j][index].resolve(snapshot); 183 | }); 184 | }, 185 | (response) => { 186 | bulkDataResolvers[i][j].forEach((promise) => promise.reject(response)); 187 | } 188 | ); 189 | } 190 | } 191 | } 192 | } 193 | } 194 | } 195 | }); 196 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/app/.gitkeep -------------------------------------------------------------------------------- /app/adapters/application.js: -------------------------------------------------------------------------------- 1 | import SocketAdapter from './socket-adapter'; 2 | 3 | export default SocketAdapter; 4 | -------------------------------------------------------------------------------- /app/adapters/socket-adapter.js: -------------------------------------------------------------------------------- 1 | import SocketAdapter from 'socket-adapter/adapters/socket'; 2 | 3 | export default SocketAdapter.extend(); 4 | -------------------------------------------------------------------------------- /app/helpers/function-test-helper.js: -------------------------------------------------------------------------------- 1 | export { default, functionTestHelper } from 'socket-adapter/helpers/function-test-helper'; 2 | -------------------------------------------------------------------------------- /app/initializers/socket-request-response-logger.js: -------------------------------------------------------------------------------- 1 | import SocketLogger from 'socket-adapter/initializers/socket-request-response-logger'; 2 | 3 | export default SocketLogger; 4 | -------------------------------------------------------------------------------- /app/serializers/application.js: -------------------------------------------------------------------------------- 1 | import SocketSerializer from './socket-serializer'; 2 | 3 | export default SocketSerializer; 4 | -------------------------------------------------------------------------------- /app/serializers/socket-serializer.js: -------------------------------------------------------------------------------- 1 | import SocketSerializer from 'socket-adapter/serializers/socket'; 2 | 3 | export default SocketSerializer; 4 | -------------------------------------------------------------------------------- /app/services/store.js: -------------------------------------------------------------------------------- 1 | import SocketStore from 'socket-adapter/services/store'; 2 | 3 | export default SocketStore; 4 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket-adapter", 3 | "dependencies": { 4 | "ember": "2.2.2", 5 | "ember-cli-shims": "0.0.6", 6 | "ember-cli-test-loader": "0.2.1", 7 | "ember-data": "1.13.16", 8 | "ember-load-initializers": "0.1.7", 9 | "ember-qunit": "0.4.16", 10 | "ember-qunit-notifications": "0.1.0", 11 | "ember-resolver": "~0.1.20", 12 | "jquery": "2.1.4", 13 | "loader.js": "ember-cli/loader.js#3.4.0", 14 | "qunit": "~1.20.0" 15 | }, 16 | "resolutions": { 17 | "ember": "2.2.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | module.exports = { 3 | scenarios: [ 4 | { 5 | name: 'default', 6 | dependencies: { } 7 | }, 8 | { 9 | name: 'ember-release', 10 | dependencies: { 11 | 'ember': 'components/ember#release' 12 | }, 13 | resolutions: { 14 | 'ember': 'release' 15 | } 16 | }, 17 | { 18 | name: 'ember-beta', 19 | dependencies: { 20 | 'ember': 'components/ember#beta' 21 | }, 22 | resolutions: { 23 | 'ember': 'beta' 24 | } 25 | }, 26 | { 27 | name: 'ember-canary', 28 | dependencies: { 29 | 'ember': 'components/ember#canary' 30 | }, 31 | resolutions: { 32 | 'ember': 'canary' 33 | } 34 | } 35 | ] 36 | }; 37 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | module.exports = function(/* environment, appConfig */) { 5 | return { }; 6 | }; 7 | -------------------------------------------------------------------------------- /config/release.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | // var RSVP = require('rsvp'); 3 | 4 | // For details on each option run `ember help release` 5 | module.exports = { 6 | // local: true, 7 | // remote: 'some_remote', 8 | // annotation: "Release %@", 9 | // message: "Bumped version to %@", 10 | // manifest: [ 'package.json', 'bower.json', 'someconfig.json' ], 11 | // strategy: 'date', 12 | // format: 'YYYY-MM-DD', 13 | // timezone: 'America/Los_Angeles', 14 | // 15 | // beforeCommit: function(project, versions) { 16 | // return new RSVP.Promise(function(resolve, reject) { 17 | // // Do custom things here... 18 | // }); 19 | // } 20 | }; 21 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | /* global require, module */ 3 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function(defaults) { 6 | var app = new EmberAddon(defaults, { 7 | // Add options here 8 | }); 9 | 10 | /* 11 | This build file specifes the options for the dummy test app of this 12 | addon, located in `/tests/dummy` 13 | This build file does *not* influence how the addon or the app using it 14 | behave. You most likely want to be modifying `./index.js` or app's build file 15 | */ 16 | 17 | return app.toTree(); 18 | }; 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'socket-adapter' 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket-adapter", 3 | "version": "0.1.54", 4 | "description": "The default blueprint for ember-cli addons.", 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "build": "ember build", 11 | "start": "ember server", 12 | "test": "ember try:testall" 13 | }, 14 | "repository": "", 15 | "engines": { 16 | "node": ">= 0.10.0" 17 | }, 18 | "author": "", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "broccoli-asset-rev": "2.2.0", 22 | "ember-cli": "1.13.13", 23 | "ember-cli-app-version": "1.0.0", 24 | "ember-cli-content-security-policy": "0.4.0", 25 | "ember-cli-dependency-checker": "1.1.0", 26 | "ember-cli-htmlbars": "1.0.1", 27 | "ember-cli-htmlbars-inline-precompile": "0.3.1", 28 | "ember-cli-ic-ajax": "0.2.4", 29 | "ember-cli-inject-live-reload": "1.3.0", 30 | "ember-cli-qunit": "1.0.4", 31 | "ember-cli-release": "0.2.9", 32 | "ember-cli-sri": "^1.2.0", 33 | "ember-cli-uglify": "1.2.0", 34 | "ember-data": "1.13.15", 35 | "ember-disable-prototype-extensions": "1.0.0", 36 | "ember-disable-proxy-controllers": "1.0.1", 37 | "ember-export-application-global": "1.0.4", 38 | "ember-suave": "1.2.3", 39 | "ember-try": "0.0.8" 40 | }, 41 | "keywords": [ 42 | "ember-addon" 43 | ], 44 | "dependencies": { 45 | "ember-cli-babel": "5.1.5" 46 | }, 47 | "ember-addon": { 48 | "configPath": "tests/dummy/config" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html?hidepassed", 4 | "disable_watching": true, 5 | "launch_in_ci": [ 6 | "PhantomJS" 7 | ], 8 | "launch_in_dev": [ 9 | "PhantomJS", 10 | "Chrome" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "DS", 21 | "andThen", 22 | "currentURL", 23 | "currentPath", 24 | "currentRouteName" 25 | ], 26 | "node": false, 27 | "browser": false, 28 | "boss": true, 29 | "curly": true, 30 | "debug": false, 31 | "devel": false, 32 | "eqeqeq": true, 33 | "evil": true, 34 | "forin": false, 35 | "immed": false, 36 | "laxbreak": false, 37 | "newcap": true, 38 | "noarg": true, 39 | "noempty": false, 40 | "nonew": false, 41 | "nomen": false, 42 | "onevar": false, 43 | "plusplus": false, 44 | "regexp": false, 45 | "undef": true, 46 | "sub": true, 47 | "strict": false, 48 | "white": false, 49 | "eqnull": true, 50 | "esnext": true, 51 | "unused": true 52 | } 53 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from 'ember/resolver'; 3 | import loadInitializers from 'ember/load-initializers'; 4 | import config from './config/environment'; 5 | 6 | let App; 7 | 8 | Ember.MODEL_FACTORY_INJECTIONS = true; 9 | 10 | App = Ember.Application.extend({ 11 | modulePrefix: config.modulePrefix, 12 | podModulePrefix: config.podModulePrefix, 13 | Resolver 14 | }); 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | 12 | 13 | 14 | 15 | {{content-for 'head-footer'}} 16 | 17 | 18 | {{content-for 'body'}} 19 | 20 | 21 | 22 | 23 | {{content-for 'body-footer'}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | const Router = Ember.Router.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | Router.map(function() { 9 | }); 10 | 11 | export default Router; 12 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember

2 | 3 | {{outlet}} 4 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'dummy', 6 | environment: environment, 7 | baseURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | } 20 | }; 21 | 22 | if (environment === 'development') { 23 | // ENV.APP.LOG_RESOLVER = true; 24 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 25 | // ENV.APP.LOG_TRANSITIONS = true; 26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 27 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 28 | } 29 | 30 | if (environment === 'test') { 31 | // Testem prefers this... 32 | ENV.baseURL = '/'; 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | } 41 | 42 | if (environment === 'production') { 43 | 44 | } 45 | 46 | return ENV; 47 | }; 48 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/fixtures.js: -------------------------------------------------------------------------------- 1 | const fixtures = []; 2 | 3 | export function getFixture(name) { 4 | var fix; 5 | 6 | fixtures.forEach(function(fixture) { 7 | if (fixture.name === name) { 8 | fix = fixture.response; 9 | } 10 | }); 11 | fix = JSON.stringify(fix); 12 | return JSON.parse(fix); 13 | } 14 | 15 | export function addFixture(name, request, response) { 16 | var obj = {}; 17 | 18 | if (request.type) { 19 | obj.type = request.type; 20 | } 21 | if (request.requestType) { 22 | obj.requestType = request.requestType; 23 | } 24 | if (request.hash) { 25 | obj.hash = request.hash; 26 | } 27 | 28 | fixtures.push({ 29 | name: name, request: obj, response: response 30 | }); 31 | } 32 | 33 | // Find Post by ID without options 34 | addFixture('Find Post by ID = 1', { 35 | type: 'post', requestType: 'READ', hash: { id: "1", include: ['comments'], fields: ['name'] } 36 | }, { 37 | post: [{ id: 1, name: 'Socket.io is awesome', comments: [1, 2], author: 1 }] 38 | }); 39 | 40 | addFixture('Find Post by ID = 2', { 41 | type: 'post', requestType: 'READ', hash: { id: "2", include: ['comments'], fields: ['name'] } 42 | }, { 43 | post: [{ id: 2, name: 'Ember.js is awesome', comments: [] }] 44 | }); 45 | 46 | addFixture('Find Post by ID = 3', { 47 | type: 'post', requestType: 'READ', hash: { id: "3", include: ['comments'], fields: ['name'] } 48 | }, { 49 | post: [{ id: 3, name: 'Socket.io is awesome', comments: [] }] 50 | }); 51 | 52 | // Find All Posts without options 53 | addFixture('Find Posts without options', { 54 | type: 'post', requestType: 'READ_LIST', hash: {} 55 | }, { 56 | meta: {}, payload: { 57 | post: [{ id: 1, name: 'Socket.io is awesome', comments: [1, 2], author: 1 }, { 58 | id: 2, 59 | name: 'Ember.js is awesome', 60 | comments: [], 61 | author: null 62 | }] 63 | } 64 | }); 65 | 66 | addFixture('Find Related Comments', { 67 | type: 'comment', requestType: 'READ_LIST', hash: { 68 | query: { 69 | id__in: ["1", "2"] 70 | } 71 | } 72 | }, { 73 | meta: {}, payload: { 74 | comment: [{ id: 1, name: 'Greet.' }, { id: 2, name: 'Nice.' }] 75 | } 76 | }); 77 | 78 | addFixture('Find Posts with options {limit: 1}', { 79 | type: 'post', requestType: 'READ_LIST', hash: { limit: 1 } 80 | }, { 81 | meta: { total: 1 }, payload: { 82 | post: [{ id: 1, name: 'Socket.io is awesome', comments: [1, 2], author: 1 }] 83 | } 84 | }); 85 | 86 | addFixture('Find Posts with options {author: 1, limit: 1},', { 87 | type: 'post', requestType: 'READ_LIST', hash: { 88 | author: 1, limit: 1 89 | } 90 | }, { 91 | meta: { total: 1 }, payload: { 92 | post: [{ id: 1, name: 'Socket.io is awesome', comments: [1, 2], author: 1 }] 93 | } 94 | }); 95 | 96 | addFixture('Find Posts with options {limit: 2}', { 97 | type: 'post', requestType: 'READ_LIST', hash: { limit: 2 } 98 | }, { 99 | meta: { total: 2 }, payload: { 100 | post: [{ id: 1, name: 'Socket.io is awesome', comments: [1, 2], author: 1 }, { 101 | id: 2, 102 | name: 'Ember.js is awesome', 103 | comments: [], 104 | author: null 105 | }] 106 | } 107 | }); 108 | 109 | // Create Post 110 | addFixture('Create Post', { 111 | type: 'post', requestType: 'CREATE', hash: { 112 | post: { 113 | name: 'Socket.io is awesome', comments: [], author: '1' 114 | } 115 | } 116 | }, { 117 | post: [{ id: 1, name: 'Socket.io is awesome' }] 118 | }); 119 | 120 | // Create Posts 121 | addFixture('Create Posts', { 122 | type: 'post', requestType: 'CREATE_LIST', hash: { 123 | post: [{ name: 'Socket.io is awesome', comments: [], author: '1' }, { 124 | name: 'Ember.js is awesome', 125 | comments: [], 126 | author: '1' 127 | }] 128 | } 129 | }, { 130 | post: [{ id: 1, name: 'Socket.io is awesome', author: 1 }, { id: 2, name: 'Ember.js is awesome' }] 131 | }); 132 | 133 | // Update Post 134 | addFixture('Update Post without updating comments', { 135 | type: 'post', requestType: 'UPDATE', hash: { 136 | post: { 137 | id: '1', name: 'Javascript is awesome' 138 | } 139 | } 140 | }, { 141 | post: [{ id: 1, name: 'Javascript is awesome' }] 142 | }); 143 | 144 | // Update Posts 145 | addFixture('Update Posts', { 146 | type: 'post', requestType: 'UPDATE_LIST', hash: { 147 | post: [{ id: '1', name: 'Javascript is awesome' }, { id: '2', name: 'Javascript is awesome' }] 148 | } 149 | }, { 150 | post: [{ id: 1, name: 'Javascript is awesome', comments: ["1", "2"], author: "1" }, { 151 | id: 2, 152 | name: 'Javascript is awesome', 153 | comments: [], 154 | author: undefined 155 | }] 156 | }); 157 | 158 | // Delete Post 159 | addFixture('Delete Post', { 160 | type: 'post', requestType: 'DELETE', hash: { id: '2' } 161 | }, {}); 162 | 163 | // Delete Posts 164 | addFixture('Delete Posts', { 165 | type: 'post', requestType: 'DELETE_LIST', hash: { 166 | ids: ['1', '2'] 167 | } 168 | }, {}); 169 | 170 | // Read Posts with releations 171 | addFixture('Read Posts with releations', { 172 | type: 'post', requestType: 'READ_LIST', hash: { 173 | include: ['comments', 'author'] 174 | } 175 | }, { 176 | meta: { total: 2 }, payload: { 177 | post: [{ id: 1, name: 'Javascript is awesome', comments: ["1", "2"], author: "1" }, { 178 | id: 2, 179 | name: 'Socket.io is awesome', 180 | comments: [] 181 | }], comments: [{ id: 1, name: 'Greet.' }, { id: 2, name: 'Nice.' }], author: [{ id: 1, name: 'Test' }] 182 | } 183 | }); 184 | 185 | // Find Comment by ID without options 186 | addFixture('Find Comments by IDS = [1,2]', { 187 | type: 'comment', requestType: 'READ_LIST', hash: { 188 | ids: ["1", "2"] 189 | } 190 | }, { 191 | payload: { 192 | comment: [{ id: 1, name: 'Greet.' }, { id: 2, name: 'Nice.' }] 193 | } 194 | }); 195 | 196 | addFixture('Find Comment by ID = 1', { 197 | type: 'comment', requestType: 'READ', hash: { id: "1" } 198 | }, { 199 | comment: [{ id: 1, name: 'Greet.' }] 200 | }); 201 | 202 | addFixture('Find Comment by ID = 2', { 203 | type: 'comment', requestType: 'READ', hash: { id: "2" } 204 | }, { 205 | comment: [{ id: 2, name: 'Nice.' }] 206 | }); 207 | 208 | addFixture('Find Author by ID = 1', { 209 | type: 'author', requestType: 'READ', hash: { id: "1" } 210 | }, { 211 | author: [{ id: 1, name: 'Test' }] 212 | }); 213 | 214 | addFixture('Error message', { 215 | type: 'post', requestType: 'READ_LIST', hash: { id: 1, error: "error" } 216 | }, { 217 | error: 'Server error' 218 | }); 219 | 220 | addFixture('Update post without comments', { 221 | type: 'post', requestType: "UPDATE", hash: { 222 | post: { 223 | id: "1", name: "Javascript is awesome", comments: [], author: "1" 224 | } 225 | } 226 | }, { 227 | post: [{ id: 1, name: 'Javascript is awesome' }] 228 | }); 229 | 230 | addFixture('Update post without author', { 231 | type: "post", requestType: "UPDATE", hash: { 232 | post: { 233 | id: "2", name: "Javascript is awesome", comments: [] 234 | } 235 | } 236 | }, { 237 | post: [{ id: 2, name: 'Javascript is awesome' }] 238 | }); 239 | 240 | export default fixtures; 241 | -------------------------------------------------------------------------------- /tests/helpers/destroy-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default function destroyApp(application) { 4 | Ember.run(application, 'destroy'); 5 | } 6 | -------------------------------------------------------------------------------- /tests/helpers/module-for-acceptance.js: -------------------------------------------------------------------------------- 1 | import { module } from 'qunit'; 2 | import startApp from '../helpers/start-app'; 3 | import destroyApp from '../helpers/destroy-app'; 4 | 5 | export default function(name, options = {}) { 6 | module(name, { 7 | beforeEach() { 8 | this.application = startApp(); 9 | 10 | if (options.beforeEach) { 11 | options.beforeEach.apply(this, arguments); 12 | } 13 | }, 14 | 15 | afterEach() { 16 | destroyApp(this.application); 17 | 18 | if (options.afterEach) { 19 | options.afterEach.apply(this, arguments); 20 | } 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | const resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import config from '../../config/environment'; 4 | 5 | export default function startApp(attrs) { 6 | let application; 7 | 8 | let attributes = Ember.merge({}, config.APP); 9 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 10 | 11 | Ember.run(() => { 12 | application = Application.create(attributes); 13 | application.setupForTesting(); 14 | application.injectTestHelpers(); 15 | }); 16 | 17 | return application; 18 | } 19 | -------------------------------------------------------------------------------- /tests/helpers/store.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Adapter from 'dummy/adapters/socket-adapter'; 3 | import Serializer from 'dummy/serializers/socket-serializer'; 4 | import Store from 'dummy/services/store'; 5 | 6 | const { 7 | String: { decamelize } 8 | } = Ember; 9 | 10 | export default function(options) { 11 | var env = {}; 12 | options = options || {}; 13 | 14 | var registry = env.registry = new Ember.Registry(); 15 | var container = env.container = new Ember.Container(); 16 | container.registry = registry; 17 | 18 | var adapter = options.adapter || '-socket'; 19 | delete options.adapter; 20 | 21 | for (var prop in options) { 22 | registry.register('model:' + prop, options[prop]); 23 | } 24 | 25 | registry.register('store:main', Store.extend({ 26 | adapter 27 | })); 28 | 29 | registry.register('adapter:-socket', Adapter.extend({ 30 | bulkOperationsSupport: { 31 | createRecord: true, 32 | updateRecord: true, 33 | deleteRecord: true 34 | }, 35 | socketAddress: 'http://fake-endpoint.com/', 36 | pathForType(modelName) { 37 | return decamelize(modelName); 38 | } 39 | })); 40 | registry.register('serializer:-default', Serializer); 41 | 42 | registry.injection('serializer', 'store', 'store:main'); 43 | 44 | env.serializer = container.lookup('serializer:-default'); 45 | env.restSerializer = container.lookup('serializer:-rest'); 46 | env.store = container.lookup('store:main'); 47 | env.adapter = env.store.get('defaultAdapter'); 48 | return env; 49 | } 50 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | {{content-for 'test-head'}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for 'head-footer'}} 18 | {{content-for 'test-head-footer'}} 19 | 20 | 21 | {{content-for 'body'}} 22 | {{content-for 'test-body'}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {{content-for 'body-footer'}} 32 | {{content-for 'test-body-footer'}} 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/tests/integration/.gitkeep -------------------------------------------------------------------------------- /tests/integration/socket-adapter-test.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import Ember from 'ember'; 3 | import setupStore from 'dummy/tests/helpers/store'; 4 | import fixtures from 'dummy/tests/fixtures'; 5 | import { getFixture, addFixture } from 'dummy/tests/fixtures'; 6 | import {module, test} from 'qunit'; 7 | 8 | let store; 9 | let adapter; 10 | let socketRequest; 11 | let env; 12 | 13 | const { 14 | get, 15 | set, 16 | run, 17 | copy, 18 | RSVP: { all } 19 | } = Ember; 20 | 21 | window.io = {}; 22 | window.io.connect = function(address) { 23 | // TOOD: create typeFromAddress and addressFromType functions 24 | var type = address.split('/').reverse()[1]; 25 | return Ember.Object.extend(Ember.Evented, { 26 | 27 | /** 28 | * Tests will emit events only for resource namespaces, so requestType and type are always set, 29 | * hash can be an empty object 30 | * 31 | * @param requestType 32 | * @param hash 33 | */ 34 | emit: function(requestType, hash) { 35 | let fix; 36 | const hashCopy = copy(hash); 37 | const requestId = hash.request_id; 38 | delete hashCopy.request_id; 39 | socketRequest = {}; 40 | socketRequest.type = type; 41 | socketRequest.requestType = requestType; 42 | socketRequest.hash = hashCopy; 43 | fixtures.forEach((fixture) => { 44 | if (JSON.stringify(fixture.request) === JSON.stringify(socketRequest)) { 45 | // return fixture deep copy, to save fixture data across all tests 46 | fix = JSON.stringify(fixture.response); 47 | fix = JSON.parse(fix); 48 | } 49 | }); 50 | if (fix) { 51 | fix.request_id = requestId; 52 | this.trigger('message', fix); 53 | } else { 54 | console.error('fixture not found', socketRequest); 55 | } 56 | } 57 | }).create(); 58 | }; 59 | 60 | module('Acceptance | Socket Adapter', { 61 | beforeEach() { 62 | const Post = DS.Model.extend({ 63 | name: DS.attr('string'), 64 | comments: DS.hasMany('comment', { async: true }), 65 | author: DS.belongsTo('author', { async: true }) 66 | }); 67 | 68 | Post.reopenClass({ 69 | _findByIdParams: { 70 | include: ['comments'], fields: ['name'] 71 | } 72 | }); 73 | 74 | const Comment = DS.Model.extend({ 75 | name: DS.attr('string') 76 | }); 77 | 78 | const Author = DS.Model.extend({ 79 | name: DS.attr('string') 80 | }); 81 | 82 | env = setupStore({ 83 | post: Post, comment: Comment, author: Author 84 | }); 85 | 86 | store = env.store; 87 | adapter = env.adapter; 88 | env.registry.register('transform:string', DS.StringTransform); 89 | }, afterEach() { 90 | run(store, 'destroy'); 91 | } 92 | }); 93 | 94 | test('Find Post by ID without options', function(assert) { 95 | assert.expect(2); 96 | run(() => { 97 | store.find('post', 1).then((post) => { 98 | assert.deepEqual(socketRequest, { 99 | type: 'post', 100 | requestType: 'READ', 101 | hash: { id: '1', include: ['comments'], fields: ['name'] } 102 | }, `Defult includes and fields should be sent in READ request`); 103 | assert.ok(get(post, 'isLoaded'), 'post should be loaded in store correctly'); 104 | }); 105 | }); 106 | }); 107 | 108 | test('Find All Posts without options', function(assert) { 109 | assert.expect(2); 110 | run(() => { 111 | store.find('post').then(((posts) => { 112 | assert.deepEqual(socketRequest, { 113 | type: 'post', 114 | requestType: 'READ_LIST', 115 | hash: {} 116 | }, 'READ_LIST request should be sent with empty hash'); 117 | assert.ok(get(posts, 'isLoaded'), 'posts should be loaded in store correctly'); 118 | })); 119 | }); 120 | }); 121 | 122 | test('Find Posts with meta', function(assert) { 123 | assert.expect(2); 124 | run(() => { 125 | all([store.query('post', { limit: 1 }), store.findQuery('post', { limit: 2 })]).then((response) => { 126 | assert.equal(get(response[0], 'meta.total'), 1, 'meta.total in first query should be equal 1'); 127 | assert.equal(get(response[1], 'meta.total'), 2, 'meta.total in first query should be equal 2'); 128 | }); 129 | }); 130 | }); 131 | 132 | test('Create Post', function(assert) { 133 | assert.expect(3); 134 | run(() => { 135 | store.find('author', 1).then((author) => { 136 | assert.ok(author, 'Should find author #1'); 137 | const post = store.createRecord('post', { 138 | author: author, name: 'Socket.io is awesome' 139 | }); 140 | post.save().then((post) => { 141 | assert.deepEqual(socketRequest, { 142 | type: 'post', requestType: 'CREATE', hash: { 143 | post: { 144 | author: '1', name: 'Socket.io is awesome', comments: [] 145 | } 146 | } 147 | }, 'CREATE request should be sent with all new data'); 148 | assert.ok(get(post, 'isLoaded'), 'post should be loaded in store correctly'); 149 | }); 150 | }); 151 | }); 152 | }); 153 | 154 | test('Create Post response is well serialized', function(assert) { 155 | run(() => { 156 | store.find('author', 1).then((author) => { 157 | const post = store.createRecord('post', { 158 | author: author, name: 'Socket.io is awesome' 159 | }); 160 | post.save().then((post) => { 161 | assert.equal(get(post, 'id'), '1', 'response payload should be extracted in store correctly'); 162 | }); 163 | }); 164 | }); 165 | }); 166 | 167 | test('Create Posts', function(assert) { 168 | assert.expect(3); 169 | run(() => { 170 | store.find('author', 1).then(((author) => { 171 | assert.ok(!!author, 'Should find author #1'); 172 | const posts = [store.createRecord('post', { 173 | author: author, name: 'Socket.io is awesome' 174 | }), store.createRecord('post', { 175 | author: author, name: 'Ember.js is awesome' 176 | })]; 177 | all(posts.map((post) => post.save())).then((posts) => { 178 | assert.deepEqual(socketRequest, { 179 | type: 'post', requestType: 'CREATE_LIST', hash: { 180 | post: [{ name: 'Socket.io is awesome', comments: [], author: '1' }, { 181 | name: 'Ember.js is awesome', 182 | comments: [], 183 | author: '1' 184 | }] 185 | } 186 | }, 'CREATE_LIST request should be sent with all new data for both 2 posts'); 187 | assert.ok(posts.filter((post) => get(post, 'isLoaded')).length === 2, 'posts should be loaded in store correctly'); 188 | }); 189 | })); 190 | }); 191 | }); 192 | 193 | test('Create Posts response is well serialized in right sequence', function(assert) { 194 | run(() => { 195 | store.find('author', 1).then(((author) => { 196 | const posts = [store.createRecord('post', { 197 | author: author, name: 'Socket.io is awesome' 198 | }), store.createRecord('post', { 199 | author: author, name: 'Ember.js is awesome' 200 | })]; 201 | all(posts.map((post) => post.save())).then((posts) => { 202 | const [fPost, sPost] = posts; 203 | assert.equal(get(fPost, 'name'), 'Socket.io is awesome', 'First returned post should have correct name'); 204 | assert.equal(get(sPost, 'name'), 'Ember.js is awesome', 'Second returned post should have correct name'); 205 | }); 206 | })); 207 | }); 208 | }); 209 | 210 | test('Update Post', function(assert) { 211 | run(() => { 212 | store.pushPayload('post', getFixture('Find Post by ID = 1')); 213 | }); 214 | 215 | run(() => { 216 | const post = store.getById('post', 1); 217 | set(post, 'name', 'Javascript is awesome'); 218 | post.save().then(() => { 219 | assert.deepEqual(socketRequest, { 220 | type: 'post', requestType: 'UPDATE', hash: { 221 | post: { 222 | id: '1', name: 'Javascript is awesome' 223 | } 224 | } 225 | }, `UPDATE request should be sent with only updated data`); 226 | }); 227 | }); 228 | }); 229 | 230 | test('Update Posts', function(assert) { 231 | run(() => { 232 | store.pushPayload('post', getFixture('Find Posts without options').payload); 233 | }); 234 | 235 | run(() => { 236 | const posts = store.all('post'); 237 | posts.forEach((post) => { 238 | set(post, 'name', 'Javascript is awesome'); 239 | }); 240 | posts.save().then(() => { 241 | assert.deepEqual(socketRequest, { 242 | type: 'post', requestType: 'UPDATE_LIST', hash: { 243 | post: [{ id: '1', name: 'Javascript is awesome' }, { id: '2', name: 'Javascript is awesome' }] 244 | } 245 | }, 'UPDATE_LIST request should be sent with only updated data'); 246 | }); 247 | }); 248 | }); 249 | 250 | test('Delete Post', function(assert) { 251 | run(() => { 252 | store.pushPayload('post', getFixture('Find Posts without options').payload); 253 | }); 254 | run(() => { 255 | const posts = store.all('post'); 256 | const post = get(posts, 'lastObject'); 257 | 258 | post.destroyRecord().then((response) => { 259 | assert.deepEqual(socketRequest, { 260 | type: 'post', requestType: 'DELETE', hash: { id: '2' } 261 | }, 'DELETE reqeust should be sent only with deletable id'); 262 | assert.equal(response.get('id'), 2, 'post id should be equal 2'); 263 | }); 264 | }); 265 | }); 266 | 267 | test('Delete Posts', function(assert) { 268 | assert.expect(2); 269 | run(() => { 270 | store.pushPayload('post', getFixture('Find Posts without options').payload); 271 | }); 272 | run(() => { 273 | const posts = store.all('post'); 274 | 275 | posts.findBy('id', '1').deleteRecord(); 276 | posts.findBy('id', '2').deleteRecord(); 277 | 278 | posts.save().then((posts) => { 279 | assert.deepEqual(socketRequest, { 280 | type: 'post', requestType: 'DELETE_LIST', hash: { 281 | ids: ['1', '2'] 282 | } 283 | }, 'DELETE_LIST reqeust should be send with deletable ids'); 284 | assert.equal(posts.isEvery('isDeleted', true), true, 'every post should be deleted'); 285 | }); 286 | }); 287 | }); 288 | 289 | test('Read Posts with releations', function(assert) { 290 | assert.expect(3); 291 | 292 | run(() => { 293 | store.find('post', { include: ['comments', 'author'] }).then((posts) => { 294 | assert.equal(posts.get('length'), 2, 'posts length should be equal 2'); 295 | assert.equal(get(posts, 'firstObject.comments').findBy('id', '1').get('name'), 'Greet.', 'first comment to first post should be equal "Greet."'); 296 | assert.equal(get(posts, 'firstObject.author.name'), 'Test', 'author name sholud be equal "Test"'); 297 | }); 298 | }); 299 | }); 300 | 301 | test('Create Posts from Server\'s PUSH', function(assert) { 302 | run(() => { 303 | const socketNS = adapter.getConnection(store.modelFor('post')); 304 | const serverPUSH = { 305 | payload: { 306 | post: [{ id: 1, name: 'Socket.io is awesome' }, { id: 2, name: 'Ember.js is awesome' }] 307 | } 308 | }; 309 | socketNS.trigger('message', serverPUSH); 310 | }); 311 | 312 | run(() => { 313 | const posts = store.all('post'); 314 | assert.equal(get(posts, 'length'), 2, 'All posts should be loaded from store correctly'); 315 | }); 316 | }); 317 | 318 | test('Delete Posts from Server\'s PUSH', function(assert) { 319 | assert.expect(2); 320 | let posts; 321 | run(() => { 322 | store.pushPayload('post', getFixture('Find Posts without options').payload); 323 | }); 324 | 325 | run(() => { 326 | posts = store.all('post'); 327 | assert.equal(get(posts, 'length'), 2, 'posts should be loaded in store correctly'); 328 | const socketNS = adapter.getConnection(store.modelFor('post')); 329 | const serverPUSH = { 330 | ids: [1, 2] 331 | }; 332 | socketNS.trigger('message', serverPUSH); 333 | }); 334 | 335 | run(() => { 336 | posts = store.all('post'); 337 | assert.equal(get(posts, 'length'), 0, 'Posts with id 1 and 2 should be removed from store'); 338 | }); 339 | }); 340 | 341 | test('Request model key should be underscored', function(assert) { 342 | const UserPreferences = DS.Model.extend(); 343 | env.registry.register('model:user-preferences', UserPreferences); 344 | 345 | run(() => { 346 | addFixture('Create User Preferences', { 347 | type: 'user-preferences', requestType: 'CREATE', hash: { 348 | user_preferences: {} 349 | } 350 | }, { 351 | user_preferences: [{ id: 1 }] 352 | }); 353 | store.createRecord('user-preferences').save().then(() => { 354 | assert.ok(socketRequest.hash.user_preferences, 'User Preferences key have to be underscored in request'); 355 | }); 356 | }); 357 | }); 358 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/helpers/function-test-helper-test.js: -------------------------------------------------------------------------------- 1 | import { functionTestHelper } from '../../../helpers/function-test-helper'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | function test helper'); 5 | 6 | // Replace this with your real tests. 7 | test('it works', function(assert) { 8 | let result = functionTestHelper(42); 9 | assert.ok(result); 10 | }); 11 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collectrium/ember-data-socketio-adapter/c282744cdb5bada2c31fbb8ebc13cf28d8c9c247/vendor/.gitkeep -------------------------------------------------------------------------------- /vendor/ember-data/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-data", 3 | "main": "ember-data.js", 4 | "dependencies": { 5 | "ember": ">= 1.8.1 < 2.0.0" 6 | }, 7 | "ignore": [ 8 | "**/*_configuration.js", 9 | "ember-data-setup.js", 10 | "docs", 11 | "**/*.html" 12 | ], 13 | "homepage": "https://github.com/andrewfan/ember-data-1.0.0-beta.16-with-relationship-getter", 14 | "_release": "75e19b63a5", 15 | "_resolution": { 16 | "type": "branch", 17 | "branch": "master", 18 | "commit": "75e19b63a5685ea2ea902bbba90300915cb41213" 19 | }, 20 | "_source": "git://github.com/andrewfan/ember-data-1.0.0-beta.16-with-relationship-getter.git", 21 | "_target": "*", 22 | "_originalSource": "andrewfan/ember-data-1.0.0-beta.16-with-relationship-getter" 23 | } --------------------------------------------------------------------------------