├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .template-lintrc.js ├── .travis.yml ├── .watchmanconfig ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep └── index.js ├── app └── .gitkeep ├── config ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.js ├── tests ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── resolver.js │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ └── components │ │ │ └── .gitkeep │ ├── config │ │ ├── environment.js │ │ ├── optional-features.json │ │ └── targets.js │ └── public │ │ └── robots.txt ├── helpers │ └── .gitkeep ├── index.html ├── integration │ └── diff-attrs-test.js └── test-helper.js ├── vendor └── .gitkeep └── yarn.lock /.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 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | 17 | # ember-try 18 | /.node_modules.ember-try/ 19 | /bower.json.ember-try 20 | /package.json.ember-try 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 2017, 5 | sourceType: 'module' 6 | }, 7 | plugins: [ 8 | 'ember' 9 | ], 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:ember/recommended' 13 | ], 14 | env: { 15 | browser: true 16 | }, 17 | rules: { 18 | }, 19 | overrides: [ 20 | // node files 21 | { 22 | files: [ 23 | '.eslintrc.js', 24 | '.template-lintrc.js', 25 | 'ember-cli-build.js', 26 | 'index.js', 27 | 'testem.js', 28 | 'blueprints/*/index.js', 29 | 'config/**/*.js', 30 | 'tests/dummy/config/**/*.js' 31 | ], 32 | excludedFiles: [ 33 | 'addon/**', 34 | 'addon-test-support/**', 35 | 'app/**', 36 | 'tests/dummy/app/**' 37 | ], 38 | parserOptions: { 39 | sourceType: 'script', 40 | ecmaVersion: 2015 41 | }, 42 | env: { 43 | browser: false, 44 | node: true 45 | }, 46 | plugins: ['node'], 47 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 48 | // add your custom rules and overrides for node files here 49 | }) 50 | } 51 | ] 52 | }; 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/ 15 | /libpeerconnection.log 16 | /npm-debug.log* 17 | /testem.log 18 | /yarn-error.log 19 | 20 | # ember-try 21 | /.node_modules.ember-try/ 22 | /bower.json.ember-try 23 | /package.json.ember-try 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.eslintignore 13 | /.eslintrc.js 14 | /.gitignore 15 | /.template-lintrc.js 16 | /.travis.yml 17 | /.watchmanconfig 18 | /bower.json 19 | /config/ember-try.js 20 | /ember-cli-build.js 21 | /testem.js 22 | /tests/ 23 | /yarn.lock 24 | .gitkeep 25 | 26 | # ember-try 27 | /.node_modules.ember-try/ 28 | /bower.json.ember-try 29 | /package.json.ember-try 30 | -------------------------------------------------------------------------------- /.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended' 5 | }; 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | # we recommend testing addons with the same minimum supported node version as Ember CLI 5 | # so that your addon works for all apps 6 | - "10" 7 | 8 | sudo: true 9 | dist: trusty 10 | 11 | addons: 12 | chrome: stable 13 | 14 | cache: 15 | directories: 16 | - $HOME/.npm 17 | 18 | env: 19 | global: 20 | # See https://git.io/vdao3 for details. 21 | - JOBS=1 22 | 23 | jobs: 24 | fail_fast: true 25 | allow_failures: 26 | - env: EMBER_TRY_SCENARIO=ember-canary 27 | 28 | include: 29 | # runs linting and tests with current locked deps 30 | 31 | - stage: "Tests" 32 | name: "Tests" 33 | script: 34 | - npm run lint:hbs 35 | - npm run lint:js 36 | - npm test 37 | 38 | # we recommend new addons test the current and previous LTS 39 | # as well as latest stable release (bonus points to beta/canary) 40 | - stage: "Additional Tests" 41 | env: EMBER_TRY_SCENARIO=ember-lts-2.16 42 | - env: EMBER_TRY_SCENARIO=ember-lts-2.18 43 | - env: EMBER_TRY_SCENARIO=ember-release 44 | - env: EMBER_TRY_SCENARIO=ember-beta 45 | - env: EMBER_TRY_SCENARIO=ember-canary 46 | - env: EMBER_TRY_SCENARIO=ember-default-with-jquery 47 | 48 | before_install: 49 | - npm config set spin false 50 | - npm install -g npm@4 51 | - npm --version 52 | 53 | script: 54 | - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO 55 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ember-diff-attrs Changelog 2 | 3 | - [#14](https://github.com/workmanw/ember-diff-attrs/pull/14) Removed `ember-weakmap` (@scalvert, @workmanw) 4 | - [#13](https://github.com/workmanw/ember-diff-attrs/pull/13) Update ember and ember-cli (@Duder-onomy) 5 | 6 | ### 0.2.1 (January 11th, 2018) 7 | 8 | - [#9](https://github.com/workmanw/ember-diff-attrs/pull/9) Upgraded addon packages/structure to match Ember 2.18. Resolves [#8](https://github.com/workmanw/ember-diff-attrs/pull/8) (@workmanw) 9 | 10 | ### 0.2.0 (November 11th, 2017) 11 | 12 | - [#6](https://github.com/workmanw/ember-diff-attrs/pull/6) Upgrade ember-weakmap to 3.0 (@ryanto) 13 | 14 | ### 0.1.2 (February 19th, 2017) 15 | 16 | - [#2](https://github.com/workmanw/ember-diff-attrs/pull/2) Added ember-weakmap as a dependency (@workmanw) 17 | 18 | ### 0.1.1 (February 8th, 2017) 19 | 20 | - Added repository information to the `package.json` so it could be located by NPM and Ember Observer (@workmanw) 21 | 22 | ### 0.1.0 (December 25th, 2016) 23 | 24 | - Initial implementation (@workmanw) 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 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 | # ember-diff-attrs 2 | 3 | This addon was spun out of a discussion on [emberjs/rfcs#191](https://github.com/emberjs/rfcs/pull/191) [Deprecate component lifecycle hook arguments]. 4 | 5 | ember-diff-attrs provides a dry way to track attribute changes using a component's `didReceiveAttrs` lifecycle hook. 6 | 7 | PRs, RFCs and comments are welcome! 8 | 9 | ## ember-did-change-attrs 10 | 11 | @GavinJoyce and I (mostly Gavin) created an alternative version of this addon that offers a slightly cleaner API using a mixin instead of a decorator. 12 | 13 | See: [ember-did-change-attrs](https://github.com/workmanw/ember-did-change-attrs) 14 | 15 | ## Usage 16 | 17 | ### Shorthand usage 18 | ```javascript 19 | import diffAttrs from 'ember-diff-attrs'; 20 | 21 | export default Ember.Component.extend({ 22 | didReceiveAttrs: diffAttrs('email', 'isAdmin', function(changedAttrs, ...args) { 23 | this._super(...args); 24 | 25 | if(changedAttrs && changedAttrs.email) { 26 | let oldEmail = changedAttrs.email[0], 27 | newEmail = changedAttrs.email[1]; 28 | // Do stuff 29 | } 30 | }) 31 | }); 32 | ``` 33 | 34 | Some quick notes: 35 | * The function hook provided to `diffAttrs` will **always** be called, even when a tracked attr is not changed. 36 | * `changedAttrs` will be `null` on the first call. 37 | 38 | 39 | ### Extended usage 40 | 41 | ```javascript 42 | import diffAttrs from 'ember-diff-attrs'; 43 | 44 | export default Ember.Component.extend({ 45 | didReceiveAttrs: diffAttrs({ 46 | keys: ['user', 'isAdmin'], 47 | isEqual(key, a, b) { 48 | if (key === 'user') { 49 | return (a && b) ? a.id === b.id : a === b; 50 | } 51 | return a === b; 52 | }, 53 | hook(changedAttrs, ...args) { 54 | this._super(...args); 55 | 56 | if(changedAttrs && changedAttrs.user) { 57 | let oldUser = changedAttrs.user[0], 58 | newUser = changedAttrs.user[1]; 59 | // Do stuff 60 | } 61 | } 62 | }) 63 | }); 64 | ``` 65 | 66 | 67 | ## Design thoughts / rationales. 68 | 69 | * `changedAttrs` null on `init` -- It seems likely that some users will want an alternate behavior for `init` vs `update`. There is no loss of functionality by having `changedAttrs` null on `init` and it's easy to explain, _nothing has actually changed yet_. 70 | * `changedAttrs` structure -- I followed the precedence started by ember-data (`model.changedAttributes()`). 71 | 72 | ## Outstanding Questions 73 | 74 | ### Changed attrs format 75 | 76 | I followed ember-data's precedence for representing old and new values (`model.changedAttributes()`). This format has always felt odd to me. I'm more than happy to discuss changing this. 77 | 78 | ### didUpdateAttrs 79 | 80 | Since this addon is implemented as a macro, it cannot easily utilize a component's `init` call to setup. Because of this, we are unable to determine what has changed the first time `didUpdateAttrs` is called. 81 | 82 | ### Running tests 83 | 84 | * `ember test` – Runs the test suite on the current Ember version 85 | * `ember test --server` – Runs the test suite in "watch mode" 86 | * `ember try:each` – Runs the test suite against multiple Ember versions 87 | 88 | ### Running the dummy application 89 | 90 | * `ember serve` 91 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200). 92 | 93 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). 94 | 95 | License 96 | ------------------------------------------------------------------------------ 97 | 98 | This project is licensed under the [MIT License](LICENSE.md). 99 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workmanw/ember-diff-attrs/c5b14d0b1f94dcf5378e0c539cd1635ecf46a49a/addon/.gitkeep -------------------------------------------------------------------------------- /addon/index.js: -------------------------------------------------------------------------------- 1 | import { expandProperties } from '@ember/object/computed'; 2 | 3 | function isEqual(key, a, b) { 4 | return a === b; 5 | } 6 | 7 | export default function(keys, hook) { 8 | let oldValuesMap = new WeakMap(); 9 | let isEqualFunc = isEqual; 10 | 11 | if (typeof keys === 'object') { 12 | let options = keys; 13 | keys = options.keys; 14 | 15 | if (options.isEqual) { 16 | isEqualFunc = options.isEqual; 17 | } 18 | if (options.hook) { 19 | hook = options.hook; 20 | } 21 | } else if (arguments.length > 1) { 22 | keys = [].slice.call(arguments); 23 | hook = keys.pop(); 24 | } else { 25 | throw new Error('Invalid `diffAttrs` argument. Expected either one or more strings and a function, or an options hash.'); 26 | } 27 | 28 | return function() { 29 | let changedAttrs = {}; 30 | let oldValues; 31 | let isFirstCall = false; 32 | 33 | if (!oldValuesMap.has(this)) { 34 | isFirstCall = true; 35 | oldValuesMap.set(this, {}); 36 | } 37 | 38 | oldValues = oldValuesMap.get(this); 39 | 40 | const expandedKeys = []; 41 | keys.forEach(key => expandProperties(key, expandedKey => expandedKeys.push(expandedKey))); 42 | 43 | expandedKeys.forEach(key => { 44 | let value = this.get(key); 45 | if (!isEqualFunc(key, oldValues[key], value)) { 46 | changedAttrs[key] = [oldValues[key], value]; 47 | oldValues[key] = value; 48 | } 49 | }); 50 | 51 | hook.apply(this, [(isFirstCall ? null : changedAttrs), ...arguments]); 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workmanw/ember-diff-attrs/c5b14d0b1f94dcf5378e0c539cd1635ecf46a49a/app/.gitkeep -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | 5 | module.exports = function() { 6 | return Promise.all([ 7 | getChannelURL('release'), 8 | getChannelURL('beta'), 9 | getChannelURL('canary') 10 | ]).then((urls) => { 11 | return { 12 | scenarios: [ 13 | { 14 | name: 'ember-lts-2.16', 15 | env: { 16 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true }) 17 | }, 18 | npm: { 19 | devDependencies: { 20 | '@ember/jquery': '^0.5.1', 21 | 'ember-source': '~2.16.0' 22 | } 23 | } 24 | }, 25 | { 26 | name: 'ember-lts-2.18', 27 | env: { 28 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true }) 29 | }, 30 | npm: { 31 | devDependencies: { 32 | '@ember/jquery': '^0.5.1', 33 | 'ember-source': '~2.18.0' 34 | } 35 | } 36 | }, 37 | { 38 | name: 'ember-release', 39 | npm: { 40 | devDependencies: { 41 | 'ember-source': urls[0] 42 | } 43 | } 44 | }, 45 | { 46 | name: 'ember-beta', 47 | npm: { 48 | devDependencies: { 49 | 'ember-source': urls[1] 50 | } 51 | } 52 | }, 53 | { 54 | name: 'ember-canary', 55 | npm: { 56 | devDependencies: { 57 | 'ember-source': urls[2] 58 | } 59 | } 60 | }, 61 | { 62 | name: 'ember-default', 63 | npm: { 64 | devDependencies: {} 65 | } 66 | }, 67 | { 68 | name: 'ember-default-with-jquery', 69 | env: { 70 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 71 | 'jquery-integration': true 72 | }) 73 | }, 74 | npm: { 75 | devDependencies: { 76 | '@ember/jquery': '^0.5.1' 77 | } 78 | } 79 | } 80 | ] 81 | }; 82 | }); 83 | }; 84 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function(defaults) { 6 | let app = new EmberAddon(defaults, { 7 | // Add options here 8 | }); 9 | 10 | /* 11 | This build file specifies 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 | 'use strict'; 2 | 3 | module.exports = { 4 | name: require('./package').name 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-diff-attrs", 3 | "version": "0.2.2", 4 | "description": "An ember-addon that provides a dry way to track attribute changes using a component's didReceiveAttrs lifecycle hook.", 5 | "keywords": [ 6 | "ember-addon" 7 | ], 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/workmanw/ember-diff-attrs.git" 11 | }, 12 | "license": "MIT", 13 | "author": "Wesley Workman", 14 | "directories": { 15 | "doc": "doc", 16 | "test": "tests" 17 | }, 18 | "scripts": { 19 | "build": "ember build", 20 | "lint:hbs": "ember-template-lint .", 21 | "lint:js": "eslint .", 22 | "start": "ember serve", 23 | "test": "ember test", 24 | "test:all": "ember try:each" 25 | }, 26 | "dependencies": { 27 | "ember-cli-babel": "^7.0.0" 28 | }, 29 | "devDependencies": { 30 | "@ember/optional-features": "^0.6.3", 31 | "broccoli-asset-rev": "^2.7.0", 32 | "ember-ajax": "^3.1.0", 33 | "ember-cli": "~3.5.1", 34 | "ember-cli-dependency-checker": "^3.0.0", 35 | "ember-cli-eslint": "^4.2.3", 36 | "ember-cli-htmlbars": "^3.0.0", 37 | "ember-cli-htmlbars-inline-precompile": "^1.0.3", 38 | "ember-cli-inject-live-reload": "^1.8.2", 39 | "ember-cli-sri": "^2.1.1", 40 | "ember-cli-template-lint": "^1.0.0-beta.1", 41 | "ember-cli-uglify": "^2.1.0", 42 | "ember-disable-prototype-extensions": "^1.1.3", 43 | "ember-export-application-global": "^2.0.0", 44 | "ember-load-initializers": "^1.1.0", 45 | "ember-maybe-import-regenerator": "^0.1.6", 46 | "ember-qunit": "^3.4.1", 47 | "ember-resolver": "^5.0.1", 48 | "ember-source": "~3.5.1", 49 | "ember-source-channel-url": "^1.1.0", 50 | "ember-try": "^1.0.0", 51 | "eslint-plugin-ember": "^5.2.0", 52 | "eslint-plugin-node": "^7.0.1", 53 | "loader.js": "^4.7.0", 54 | "qunit-dom": "^0.8.0" 55 | }, 56 | "engines": { 57 | "node": ">= 10.*" 58 | }, 59 | "ember-addon": { 60 | "configPath": "tests/dummy/config" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test_page: 'tests/index.html?hidepassed', 3 | disable_watching: true, 4 | launch_in_ci: [ 5 | 'Chrome' 6 | ], 7 | launch_in_dev: [ 8 | 'Chrome' 9 | ], 10 | browser_args: { 11 | Chrome: { 12 | ci: [ 13 | // --no-sandbox is needed when running Chrome inside a container 14 | process.env.CI ? '--no-sandbox' : null, 15 | '--headless', 16 | '--disable-gpu', 17 | '--disable-dev-shm-usage', 18 | '--disable-software-rasterizer', 19 | '--mute-audio', 20 | '--remote-debugging-port=0', 21 | '--window-size=1440,900' 22 | ].filter(Boolean) 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const App = Application.extend({ 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | Resolver 10 | }); 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workmanw/ember-diff-attrs/c5b14d0b1f94dcf5378e0c539cd1635ecf46a49a/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workmanw/ember-diff-attrs/c5b14d0b1f94dcf5378e0c539cd1635ecf46a49a/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workmanw/ember-diff-attrs/c5b14d0b1f94dcf5378e0c539cd1635ecf46a49a/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/workmanw/ember-diff-attrs/c5b14d0b1f94dcf5378e0c539cd1635ecf46a49a/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | const Router = EmberRouter.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL 7 | }); 8 | 9 | Router.map(function() { 10 | }); 11 | 12 | export default Router; 13 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workmanw/ember-diff-attrs/c5b14d0b1f94dcf5378e0c539cd1635ecf46a49a/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workmanw/ember-diff-attrs/c5b14d0b1f94dcf5378e0c539cd1635ecf46a49a/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember

2 | 3 | {{outlet}} -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workmanw/ember-diff-attrs/c5b14d0b1f94dcf5378e0c539cd1635ecf46a49a/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'dummy', 6 | environment, 7 | rootURL: '/', 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 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false 17 | } 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | } 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /tests/dummy/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "jquery-integration": false 3 | } 4 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | const isCI = !!process.env.CI; 10 | const isProduction = process.env.EMBER_ENV === 'production'; 11 | 12 | if (isCI || isProduction) { 13 | browsers.push('ie 11'); 14 | } 15 | 16 | module.exports = { 17 | browsers 18 | }; 19 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workmanw/ember-diff-attrs/c5b14d0b1f94dcf5378e0c539cd1635ecf46a49a/tests/helpers/.gitkeep -------------------------------------------------------------------------------- /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 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/integration/diff-attrs-test.js: -------------------------------------------------------------------------------- 1 | import EmComponent from '@ember/component' 2 | import { test, moduleForComponent } from 'ember-qunit'; 3 | import hbs from 'htmlbars-inline-precompile'; 4 | import diffAttrs from 'ember-diff-attrs'; 5 | 6 | 7 | function registerComponent(testSuite, hash, klass = EmComponent) { 8 | testSuite.register('component:x-changer', klass.extend(hash)); 9 | } 10 | 11 | moduleForComponent('x-changer', 'Integration | diffAttrs', { 12 | integration: true 13 | }); 14 | 15 | test('Basic usage', function(assert) { 16 | let changedAttrs; 17 | registerComponent(this, { 18 | didReceiveAttrs: diffAttrs('email', 'isAdmin', function(changedAttrsArg) { 19 | changedAttrs = changedAttrsArg; 20 | }) 21 | }); 22 | 23 | this.set('name', 'Tomster'); 24 | this.set('email', 'ember@hamster.org'); 25 | this.set('isAdmin', false); 26 | 27 | this.render(hbs`{{x-changer email=email isAdmin=isAdmin name=name}}`); 28 | 29 | assert.notOk(changedAttrs, '`changedAttrs` is null init.'); 30 | 31 | this.set('email', 'emberjs@hamster.org'); 32 | assert.equal(changedAttrs.email[0], 'ember@hamster.org'); 33 | assert.equal(changedAttrs.email[1], 'emberjs@hamster.org'); 34 | assert.notOk(changedAttrs.isAdmin); 35 | 36 | this.set('name', 'TheTomster'); 37 | assert.equal(Object.keys(changedAttrs).length, 0, '`changedAttrs` is because `name` is not tracked'); 38 | }); 39 | 40 | test('Calling `_super`', function(assert) { 41 | let superInvokeCount = 0, changedAttrs; 42 | let SuperComponent = EmComponent.extend({ 43 | didReceiveAttrs() { 44 | superInvokeCount++; 45 | } 46 | }); 47 | registerComponent(this, { 48 | didReceiveAttrs: diffAttrs('email', function(changedAttrsArg, ...args) { 49 | this._super(...args); 50 | changedAttrs = changedAttrsArg; 51 | }) 52 | }, SuperComponent); 53 | 54 | this.set('name', 'Tomster'); 55 | this.set('email', 'ember@hamster.org'); 56 | 57 | this.render(hbs`{{x-changer email=email name=name}}`); 58 | 59 | assert.equal(superInvokeCount, 1, 'Super invoked on init.'); 60 | assert.notOk(changedAttrs, '`changedAttrs` is null init.'); 61 | 62 | this.set('email', 'emberjs@hamster.org'); 63 | assert.equal(superInvokeCount, 2, 'Super invoked on change.'); 64 | assert.ok(changedAttrs.email, 'Email was changed.'); 65 | 66 | this.set('name', 'TheTomster'); 67 | assert.equal(superInvokeCount, 3, 'Super invoked when there is no difference.'); 68 | assert.equal(Object.keys(changedAttrs).length, 0, '`changedAttrs` is because `name` is not tracked'); 69 | }); 70 | 71 | test('Options', function(assert) { 72 | let changedAttrs; 73 | registerComponent(this, { 74 | didReceiveAttrs: diffAttrs({ 75 | keys: ['email', 'isAdmin'], 76 | hook(changedAttrsArg) { 77 | changedAttrs = changedAttrsArg; 78 | } 79 | }) 80 | }); 81 | 82 | this.set('name', 'Tomster'); 83 | this.set('email', 'ember@hamster.org'); 84 | this.set('isAdmin', false); 85 | 86 | this.render(hbs`{{x-changer email=email isAdmin=isAdmin name=name}}`); 87 | 88 | assert.notOk(changedAttrs, '`changedAttrs` is null init.'); 89 | 90 | this.set('email', 'emberjs@hamster.org'); 91 | assert.equal(changedAttrs.email[0], 'ember@hamster.org'); 92 | assert.equal(changedAttrs.email[1], 'emberjs@hamster.org'); 93 | assert.notOk(changedAttrs.isAdmin); 94 | 95 | this.set('name', 'TheTomster'); 96 | assert.equal(Object.keys(changedAttrs).length, 0, '`changedAttrs` is because `name` is not tracked'); 97 | }); 98 | 99 | test('Options - Compare', function(assert) { 100 | let changedAttrs = {}; 101 | registerComponent(this, { 102 | didReceiveAttrs: diffAttrs({ 103 | keys: ['user', 'isAdmin'], 104 | isEqual(key, a, b) { 105 | if (key === 'user') { 106 | return (a && b) ? a.id === b.id : a === b; 107 | } 108 | return a === b; 109 | }, 110 | hook(changedAttrsArg) { 111 | changedAttrs = changedAttrsArg; 112 | } 113 | }) 114 | }); 115 | 116 | this.set('user', { name: 'Tomster', id: '123' }); 117 | this.set('isAdmin', false); 118 | 119 | this.render(hbs`{{x-changer user=user isAdmin=isAdmin}}`); 120 | 121 | assert.notOk(changedAttrs, '`changedAttrs` is null init.'); 122 | 123 | this.set('user', { name: 'TheTomster', id: '123' }); 124 | assert.equal(Object.keys(changedAttrs).length, 0, '`user` not included in `changedAttrs` because user entities are equal'); 125 | 126 | this.set('user', { name: 'Zoey', id: '456' }); 127 | assert.ok(changedAttrs.user, '`user` included in `changedAttrs` because `user.id` is different.'); 128 | 129 | this.set('isAdmin', true); 130 | assert.ok(changedAttrs.isAdmin, '`isAdmin` fell back to the default comparer'); 131 | }); 132 | 133 | [true, false].forEach(shorthandUsage => { 134 | const usage = shorthandUsage ? 'shorthand' : 'extended'; 135 | 136 | test(`With ${usage} usage, brace expansion does not affect tracked properties outside of the braces`, function(assert) { 137 | let changedAttrs = {}; 138 | if (shorthandUsage) { 139 | registerComponent(this, { 140 | didReceiveAttrs: diffAttrs('isAdmin', 'user.{forename,email}', function(changedAttrsArg) { 141 | changedAttrs = changedAttrsArg; 142 | }) 143 | }); 144 | } else { 145 | registerComponent(this, { 146 | didReceiveAttrs: diffAttrs({ 147 | keys: ['isAdmin', 'user.{forename,email}'], 148 | hook(changedAttrsArg) { 149 | changedAttrs = changedAttrsArg; 150 | } 151 | }) 152 | }); 153 | } 154 | 155 | this.setProperties({ 156 | user: { 157 | forename: 'Bob', 158 | surname: 'Smith', 159 | email: 'bob@smith' 160 | }, 161 | isAdmin: false 162 | }); 163 | 164 | this.render(hbs`{{x-changer user=user isAdmin=isAdmin}}`); 165 | assert.notOk(changedAttrs, '`changedAttrs` is null initially'); 166 | 167 | this.set('isAdmin', true); 168 | assert.equal(Object.keys(changedAttrs).join(), 'isAdmin', '`isAdmin` should be the only changed property'); 169 | }); 170 | 171 | test(`With ${usage} usage, brace expansion works on multiple properties across multiple sets of braces`, function(assert) { 172 | let changedAttrs = {}; 173 | if (shorthandUsage) { 174 | registerComponent(this, { 175 | didReceiveAttrs: diffAttrs('user.{forename,age}', 'admin.{surname,email,age}', function(changedAttrsArg) { 176 | changedAttrs = changedAttrsArg; 177 | }) 178 | }); 179 | } else { 180 | registerComponent(this, { 181 | didReceiveAttrs: diffAttrs({ 182 | keys: ['user.{forename,age}', 'admin.{surname,email,age}'], 183 | hook(changedAttrsArg) { 184 | changedAttrs = changedAttrsArg; 185 | } 186 | }) 187 | }); 188 | } 189 | 190 | this.setProperties({ 191 | user: { 192 | forename: 'Bob', 193 | surname: 'Smith', 194 | email: 'bob@smith', 195 | age: 22 196 | }, 197 | admin: { 198 | forename: 'Fred', 199 | surname: 'Jones', 200 | email: 'fred@jones', 201 | age: 42 202 | } 203 | }); 204 | 205 | this.render(hbs`{{x-changer user=user admin=admin}}`); 206 | assert.notOk(changedAttrs, '`changedAttrs` is null initially'); 207 | 208 | this.setProperties({ 209 | user: { 210 | forename: 'Bob', 211 | surname: 'Smythe', 212 | email: 'bob@smythe', 213 | age: 22 214 | }, 215 | admin: { 216 | forename: 'Freddy', 217 | surname: 'Jones', 218 | email: 'fred@jones', 219 | age: 42 220 | } 221 | }); 222 | assert.equal(Object.keys(changedAttrs).join(), '', 'No tracked properties should have changed'); 223 | 224 | this.setProperties({ 225 | user: { 226 | forename: 'Robert', 227 | surname: 'Smythe', 228 | email: 'robert@smythe', 229 | age: 23 230 | }, 231 | admin: { 232 | forename: 'Freddy', 233 | surname: 'Johnston', 234 | email: 'freddy@johnston', 235 | age: 42 236 | } 237 | }); 238 | assert.equal(Object.keys(changedAttrs).join(), 'user.forename,user.age,admin.surname,admin.email', 'Brace expansion should have worked'); 239 | }); 240 | }); 241 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from '../app'; 2 | import config from '../config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | 6 | setApplication(Application.create(config.APP)); 7 | 8 | start(); 9 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workmanw/ember-diff-attrs/c5b14d0b1f94dcf5378e0c539cd1635ecf46a49a/vendor/.gitkeep --------------------------------------------------------------------------------