├── vendor
└── .gitkeep
├── tests
├── unit
│ ├── .gitkeep
│ └── utils
│ │ ├── composable-test.js
│ │ └── computable-test.js
├── integration
│ └── .gitkeep
├── dummy
│ ├── app
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ └── .gitkeep
│ │ ├── routes
│ │ │ └── .gitkeep
│ │ ├── styles
│ │ │ └── app.css
│ │ ├── components
│ │ │ └── .gitkeep
│ │ ├── controllers
│ │ │ └── .gitkeep
│ │ ├── templates
│ │ │ ├── components
│ │ │ │ └── .gitkeep
│ │ │ └── application.hbs
│ │ ├── resolver.js
│ │ ├── router.js
│ │ ├── app.js
│ │ └── index.html
│ ├── public
│ │ ├── robots.txt
│ │ └── crossdomain.xml
│ └── config
│ │ ├── targets.js
│ │ └── environment.js
├── .eslintrc.js
├── helpers
│ ├── destroy-app.js
│ ├── resolver.js
│ ├── start-app.js
│ └── module-for-acceptance.js
├── test-helper.js
├── .jshintrc
└── index.html
├── .watchmanconfig
├── index.js
├── addon
├── index.js
└── utils
│ ├── composable.js
│ └── computable.js
├── config
├── environment.js
└── ember-try.js
├── .eslintrc.js
├── .npmignore
├── .ember-cli
├── .gitignore
├── .editorconfig
├── testem.js
├── ember-cli-build.js
├── app
└── initializers
│ └── computable.js
├── .travis.yml
├── package.json
├── README.md
└── LICENSE.txt
/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/tests/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | embertest: true
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember-resolver';
2 |
3 | export default Resolver;
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = {
5 | name: 'ember-computable'
6 | };
7 |
--------------------------------------------------------------------------------
/addon/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './utils/computable';
2 | export { default as Composable } from './utils/composable';
3 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = function(/* environment, appConfig */) {
5 | return { };
6 | };
7 |
--------------------------------------------------------------------------------
/tests/helpers/destroy-app.js:
--------------------------------------------------------------------------------
1 | import { run } from '@ember/runloop';
2 |
3 | export default function destroyApp(application) {
4 | run(application, 'destroy');
5 | }
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 | {{!-- The following component displays Ember's default welcome message. --}}
2 | {{welcome-page}}
3 | {{!-- Feel free to remove this! --}}
4 |
5 | {{outlet}}
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import resolver from './helpers/resolver';
2 | import {
3 | setResolver
4 | } from 'ember-qunit';
5 | import { start } from 'ember-cli-qunit';
6 |
7 | setResolver(resolver);
8 | start();
9 |
--------------------------------------------------------------------------------
/tests/dummy/config/targets.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | browsers: [
4 | 'ie 9',
5 | 'last 1 Chrome versions',
6 | 'last 1 Firefox versions',
7 | 'last 1 Safari versions'
8 | ]
9 | };
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parserOptions: {
4 | ecmaVersion: 2017,
5 | sourceType: 'module'
6 | },
7 | extends: 'eslint:recommended',
8 | env: {
9 | browser: true
10 | },
11 | rules: {
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /bower_components
2 | /config/ember-try.js
3 | /dist
4 | /tests
5 | /tmp
6 | **/.gitkeep
7 | .bowerrc
8 | .editorconfig
9 | .ember-cli
10 | .gitignore
11 | .eslintrc.js
12 | .watchmanconfig
13 | .travis.yml
14 | bower.json
15 | ember-cli-build.js
16 | testem.js
17 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/tests/helpers/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from '../../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/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 |
--------------------------------------------------------------------------------
/.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 | /node_modules
9 | /bower_components
10 |
11 | # misc
12 | /.sass-cache
13 | /connect.lock
14 | /coverage/*
15 | /libpeerconnection.log
16 | npm-debug.log*
17 | yarn-error.log
18 | testem.log
19 |
20 | # ember-try
21 | .node_modules.ember-try/
22 | bower.json.ember-try
23 | package.json.ember-try
24 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | test_page: 'tests/index.html?hidepassed',
4 | disable_watching: true,
5 | launch_in_ci: [
6 | 'Chrome'
7 | ],
8 | launch_in_dev: [
9 | 'Chrome'
10 | ],
11 | browser_args: {
12 | Chrome: {
13 | mode: 'ci',
14 | args: [
15 | '--disable-gpu',
16 | '--headless',
17 | '--remote-debugging-port=9222',
18 | '--window-size=1440,900'
19 | ]
20 | },
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/tests/helpers/start-app.js:
--------------------------------------------------------------------------------
1 | import Application from '../../app';
2 | import config from '../../config/environment';
3 | import { merge } from '@ember/polyfills';
4 | import { run } from '@ember/runloop';
5 |
6 | export default function startApp(attrs) {
7 | let attributes = merge({}, config.APP);
8 | attributes = merge(attributes, attrs); // use defaults, but you can override;
9 |
10 | return run(() => {
11 | let application = Application.create(attributes);
12 | application.setupForTesting();
13 | application.injectTestHelpers();
14 | return application;
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
5 |
6 | module.exports = function(defaults) {
7 | let app = new EmberAddon(defaults, {
8 | // Add options here
9 | });
10 |
11 | /*
12 | This build file specifies the options for the dummy test app of this
13 | addon, located in `/tests/dummy`
14 | This build file does *not* influence how the addon or the app using it
15 | behave. You most likely want to be modifying `./index.js` or app's build file
16 | */
17 |
18 | return app.toTree();
19 | };
20 |
--------------------------------------------------------------------------------
/tests/dummy/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/app/initializers/computable.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Computable from 'ember-computable/utils/computable';
3 | const { computed } = Ember;
4 |
5 | export function initialize() {
6 | return;
7 | }
8 |
9 | // We don't want to rely on the initializers running in order to
10 | // add these methods to the Ember.computed namespace. Doing it
11 | // this way so these methods are available in unit tests.
12 | (function() {
13 | Object.keys(Computable).forEach(function(key){
14 | if (!computed[key]) {
15 | computed[key] = Computable[key];
16 | }
17 | });
18 | })();
19 |
20 | export default {
21 | name: 'computable',
22 | initialize: initialize
23 | };
24 |
--------------------------------------------------------------------------------
/tests/helpers/module-for-acceptance.js:
--------------------------------------------------------------------------------
1 | import { module } from 'qunit';
2 | import { resolve } from 'rsvp';
3 | import startApp from '../helpers/start-app';
4 | import destroyApp from '../helpers/destroy-app';
5 |
6 | export default function(name, options = {}) {
7 | module(name, {
8 | beforeEach() {
9 | this.application = startApp();
10 |
11 | if (options.beforeEach) {
12 | return options.beforeEach.apply(this, arguments);
13 | }
14 | },
15 |
16 | afterEach() {
17 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments);
18 | return resolve(afterEach).then(() => destroyApp(this.application));
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/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/.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/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 |
--------------------------------------------------------------------------------
/.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 | - "4"
7 |
8 | sudo: false
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 | # we recommend new addons test the current and previous LTS
23 | # as well as latest stable release (bonus points to beta/canary)
24 | - EMBER_TRY_SCENARIO=ember-lts-2.8
25 | - EMBER_TRY_SCENARIO=ember-lts-2.12
26 | - EMBER_TRY_SCENARIO=ember-release
27 | - EMBER_TRY_SCENARIO=ember-beta
28 | - EMBER_TRY_SCENARIO=ember-canary
29 | - EMBER_TRY_SCENARIO=ember-default
30 |
31 | matrix:
32 | fast_finish: true
33 | allow_failures:
34 | - env: EMBER_TRY_SCENARIO=ember-canary
35 |
36 | before_install:
37 | - npm config set spin false
38 | - npm install -g npm@4
39 | - npm --version
40 |
41 | script:
42 | # Usually, it's ok to finish the test scenario without reverting
43 | # to the addon's original dependency state, skipping "cleanup".
44 | - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup
45 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = function(environment) {
5 | let ENV = {
6 | modulePrefix: 'dummy',
7 | environment,
8 | rootURL: '/',
9 | locationType: 'auto',
10 | EmberENV: {
11 | FEATURES: {
12 | // Here you can enable experimental features on an ember canary build
13 | // e.g. 'with-controller': true
14 | },
15 | EXTEND_PROTOTYPES: {
16 | // Prevent Ember Data from overriding Date.parse.
17 | Date: false
18 | }
19 | },
20 |
21 | APP: {
22 | // Here you can pass flags/options to your application instance
23 | // when it is created
24 | }
25 | };
26 |
27 | if (environment === 'development') {
28 | // ENV.APP.LOG_RESOLVER = true;
29 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
30 | // ENV.APP.LOG_TRANSITIONS = true;
31 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
32 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
33 | }
34 |
35 | if (environment === 'test') {
36 | // Testem prefers this...
37 | ENV.locationType = 'none';
38 |
39 | // keep test console output quieter
40 | ENV.APP.LOG_ACTIVE_GENERATION = false;
41 | ENV.APP.LOG_VIEW_LOOKUPS = false;
42 |
43 | ENV.APP.rootElement = '#ember-testing';
44 | }
45 |
46 | if (environment === 'production') {
47 | // here you can enable a production-specific feature
48 | }
49 |
50 | return ENV;
51 | };
52 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-computable",
3 | "version": "0.0.5",
4 | "description": "Computed Properties extension for Ember. Adds more functional programming capabilities to computed properties.",
5 | "keywords": [
6 | "ember-addon",
7 | "computed",
8 | "properties",
9 | "reactive",
10 | "compose",
11 | "functional"
12 | ],
13 | "license": "Apache-2.0",
14 | "author": "Terrill Dent ",
15 | "directories": {
16 | "doc": "doc",
17 | "test": "tests"
18 | },
19 | "repository": "",
20 | "scripts": {
21 | "build": "ember build",
22 | "start": "ember server",
23 | "test": "ember try:each"
24 | },
25 | "dependencies": {
26 | "ember-cli-babel": "^6.6.0"
27 | },
28 | "devDependencies": {
29 | "broccoli-asset-rev": "^2.4.5",
30 | "ember-ajax": "^3.0.0",
31 | "ember-cli": "~2.16.1",
32 | "ember-cli-dependency-checker": "^2.0.0",
33 | "ember-cli-eslint": "^4.0.0",
34 | "ember-cli-htmlbars": "^2.0.1",
35 | "ember-cli-htmlbars-inline-precompile": "^1.0.0",
36 | "ember-cli-inject-live-reload": "^1.4.1",
37 | "ember-cli-qunit": "^4.0.0",
38 | "ember-cli-shims": "^1.1.0",
39 | "ember-cli-sri": "^2.1.0",
40 | "ember-cli-uglify": "^2.0.0",
41 | "ember-disable-prototype-extensions": "^1.1.2",
42 | "ember-export-application-global": "^2.0.0",
43 | "ember-load-initializers": "^1.0.0",
44 | "ember-resolver": "^4.0.0",
45 | "ember-source": "~2.16.0",
46 | "ember-welcome-page": "^3.0.0",
47 | "loader.js": "^4.2.3"
48 | },
49 | "engines": {
50 | "node": "^4.5 || 6.* || >= 7.*"
51 | },
52 | "ember-addon": {
53 | "configPath": "tests/dummy/config"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | scenarios: [
4 | {
5 | name: 'ember-lts-2.8',
6 | bower: {
7 | dependencies: {
8 | 'ember': 'components/ember#lts-2-8'
9 | },
10 | resolutions: {
11 | 'ember': 'lts-2-8'
12 | }
13 | },
14 | npm: {
15 | devDependencies: {
16 | 'ember-source': null
17 | }
18 | }
19 | },
20 | {
21 | name: 'ember-lts-2.12',
22 | npm: {
23 | devDependencies: {
24 | 'ember-source': '~2.12.0'
25 | }
26 | }
27 | },
28 | {
29 | name: 'ember-release',
30 | bower: {
31 | dependencies: {
32 | 'ember': 'components/ember#release'
33 | },
34 | resolutions: {
35 | 'ember': 'release'
36 | }
37 | },
38 | npm: {
39 | devDependencies: {
40 | 'ember-source': null
41 | }
42 | }
43 | },
44 | {
45 | name: 'ember-beta',
46 | bower: {
47 | dependencies: {
48 | 'ember': 'components/ember#beta'
49 | },
50 | resolutions: {
51 | 'ember': 'beta'
52 | }
53 | },
54 | npm: {
55 | devDependencies: {
56 | 'ember-source': null
57 | }
58 | }
59 | },
60 | {
61 | name: 'ember-canary',
62 | bower: {
63 | dependencies: {
64 | 'ember': 'components/ember#canary'
65 | },
66 | resolutions: {
67 | 'ember': 'canary'
68 | }
69 | },
70 | npm: {
71 | devDependencies: {
72 | 'ember-source': null
73 | }
74 | }
75 | },
76 | {
77 | name: 'ember-default',
78 | npm: {
79 | devDependencies: {}
80 | }
81 | }
82 | ]
83 | };
84 |
--------------------------------------------------------------------------------
/addon/utils/composable.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 |
4 | argsToArray: function() {
5 | return Array.prototype.slice.call(arguments);
6 | },
7 |
8 | compact: function(arg) {
9 | return arg.filter(this.identity);
10 | },
11 |
12 | identity: function(arg) {
13 | return arg;
14 | },
15 |
16 | not: function(arg) {
17 | return !arg;
18 | },
19 |
20 | // Curried and Partially Evaluated Fns
21 |
22 | compose: function() {
23 | var composedFns = Array.prototype.slice.call(arguments);
24 |
25 | return function(){
26 | var args = Array.prototype.slice.call(arguments),
27 | i = composedFns.length - 1,
28 | intermediate;
29 |
30 | intermediate = composedFns[i].apply(this, args);
31 | while (i--) {
32 | intermediate = composedFns[i].call(this, intermediate);
33 | }
34 | return intermediate;
35 | };
36 | },
37 |
38 | default: function(defaultValue) {
39 | return function(runtimeValue) {
40 | return runtimeValue || defaultValue;
41 | };
42 | },
43 |
44 | filter: function(fn) {
45 | return function(collection) {
46 | return collection.filter(fn);
47 | };
48 | },
49 |
50 | filterBy: function(key, value) {
51 | return function(collection) {
52 | return collection.filterBy(key, value);
53 | };
54 | },
55 |
56 | join: function(separator) {
57 | return function(arg) {
58 | return arg.join(separator);
59 | };
60 | },
61 |
62 | lookupKey: function(map, defaultValue) {
63 | return function(key) {
64 | return map[key] || defaultValue;
65 | };
66 | },
67 |
68 | mapBy: function(key) {
69 | return function(collection) {
70 | return collection.mapBy(key);
71 | };
72 | },
73 |
74 | parseInt: function(radix) {
75 | return function(arg) {
76 | return parseInt(arg, radix);
77 | };
78 | },
79 |
80 | replace: function(regex, replacement) {
81 | return function(arg) {
82 | return arg && arg.replace(regex, replacement);
83 | };
84 | }
85 |
86 | };
87 |
--------------------------------------------------------------------------------
/tests/unit/utils/composable-test.js:
--------------------------------------------------------------------------------
1 | import Composable from 'ember-computable/utils/composable';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Utils | Composable');
5 |
6 | test('#argsToArray', function(assert) {
7 | var a = 'one',
8 | b = 'two',
9 | c = 'three',
10 | result = Composable.argsToArray(a, b, c);
11 | assert.equal(result.length, 3);
12 | assert.equal(result[0], a);
13 | assert.equal(result[1], b);
14 | assert.equal(result[2], c);
15 | });
16 |
17 | test('#compact', function(assert) {
18 | var result = Composable.compact(['one', 2, null, false, true, '0', undefined]);
19 | assert.equal(result.length, 4);
20 | assert.equal(result[0], 'one');
21 | assert.equal(result[1], 2);
22 | assert.equal(result[2], true);
23 | assert.equal(result[3], '0');
24 | });
25 |
26 | test('#compose', function(assert) {
27 | var divideBy = function(divisor) {
28 | return function(number) {
29 | return number / divisor;
30 | };
31 | };
32 |
33 | var divideBy200 = Composable.compose(divideBy(20), divideBy(10));
34 | var divideBy6000 = Composable.compose(divideBy(30), divideBy(20), divideBy(10));
35 |
36 | assert.equal(divideBy200(400), 2, 'Divides 400 by 200 = 2');
37 | assert.equal(divideBy200(800), 4, 'Divides 800 by 200 = 4');
38 |
39 | assert.equal(divideBy6000(6000), 1, 'Divides 6000 by 6000 = 1');
40 | assert.equal(divideBy6000(6), 0.001, 'Divides 6 by 6000 = .0001');
41 | });
42 |
43 | test('#default', function(assert) {
44 | var otherwiseRed = Composable.default('red');
45 |
46 | assert.equal(otherwiseRed('blue'), 'blue', 'When a truthy value is provided, the truthy value is returned');
47 | assert.equal(otherwiseRed(true), true, 'When a truthy value is provided, the truthy value is returned');
48 |
49 | var obj = { foo: 'bar' };
50 | assert.equal(otherwiseRed(obj), obj, 'When an object is provided, the same object is returned');
51 |
52 | assert.equal(otherwiseRed(false), 'red', 'When a falsy value is provided, "red" (the default) is returned');
53 | assert.equal(otherwiseRed(0), 'red', 'When a falsy value is provided, "red" (the default) is returned');
54 | assert.equal(otherwiseRed(undefined), 'red', 'When a falsy value is provided, "red" (the default) is returned');
55 | assert.equal(otherwiseRed(null), 'red', 'When a falsy value is provided, "red" (the default) is returned');
56 | });
57 |
58 | test('#lookupKey', function(assert) {
59 | var stateMapping = {
60 | COMPLETE: '/success.png',
61 | FAILURE: '/error.png',
62 | PENDING: '/waiting.png',
63 | INSTANT: '/instant.png',
64 | };
65 |
66 | var stateToImage = Composable.lookupKey(stateMapping, stateMapping.COMPLETE);
67 | assert.equal(stateToImage('FAILURE'), stateMapping.FAILURE, 'Lookup a valid key');
68 | assert.equal(stateToImage('FAILURES'), stateMapping.COMPLETE, 'Lookup a non-existant key returns the default value');
69 | assert.equal(stateToImage(null), stateMapping.COMPLETE, 'Lookup an invalid key returns the default value');
70 | });
71 |
--------------------------------------------------------------------------------
/addon/utils/computable.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Composable from './composable';
3 |
4 | // Opt-in to native array extensions
5 | Ember.NativeArray.apply(Array.prototype);
6 |
7 | /**
8 | * Helper functions similar to Ember.computed helpers
9 | */
10 | var Computable = {
11 |
12 | /**
13 | * If the value stored at the dependentKey evaluates to true
14 | * then `value` is returned, otherwise `elseValue` is returned
15 | */
16 | ifElse(dependentKey, value, elseValue) {
17 | return Ember.computed(dependentKey, function(){
18 | return this.get(dependentKey) ? value : elseValue;
19 | });
20 | },
21 |
22 | /**
23 | * If the value stored at the dependentKey evaluates to true
24 | * then the value stored at `key` is returned, otherwise the value at `elseKey`
25 | */
26 | ifElseKeys(dependentKey, key, elseKey) {
27 | return Ember.computed(dependentKey, key, elseKey, function(){
28 | return this.get(this.get(dependentKey) ? key : elseKey);
29 | });
30 | },
31 |
32 | /**
33 | * Returns true if the provided value is in the enumerable collection
34 | *
35 | * Note: This is an O(n) operation, it should not be used with large collections
36 | *
37 | * Ex:
38 | *
39 | * instrument: Ember.computed.includes('supported_instrument_types', 'DEBIT_CARD'),
40 | *
41 | */
42 | includes(collectionKey, value) {
43 | return Ember.computed(collectionKey, function(){
44 | var collection = this.get(collectionKey);
45 | return collection &&
46 | ((collection.includes && collection.includes(value)) || (!collection.includes && collection.contains && collection.contains(value)));
47 | });
48 | },
49 |
50 | /**
51 | * Iterates the collection and uses the values at selector
52 | * as keys to populate a hashtable containing the items.
53 | *
54 | * posts: [{guid: 'foo', body:'one'}, {guid: 'bar', body: 'two'}]
55 | * idLookup: Ember.computed.indexBy('posts.[]', 'guid')
56 | *
57 | * // idLookup has the value:
58 | * {
59 | * foo: {guid: 'foo', body:'one' },
60 | * bar: {guid: 'bar', body: 'two'}
61 | * }
62 | *
63 | */
64 | indexBy(collectionKey, selector) {
65 | return Ember.computed(collectionKey, function(){
66 | var collection = this.get(collectionKey),
67 | result = {};
68 | if (collection && typeof collection.forEach === 'function'){
69 | collection.forEach(function(item){
70 | let key = item.get(selector);
71 | if (key) {
72 | result[key] = item;
73 | }
74 | });
75 | }
76 | return result;
77 | });
78 | },
79 |
80 | /**
81 | * Returns the first item in the target colelction with a property
82 | * at key matching the provided value
83 | *
84 | * Note: This is an O(n) operation, it should not be used with large collections
85 | *
86 | * Ex:
87 | *
88 | * instrument: Ember.computed.findBy('instruments', 'type', 'DEBIT_CARD'),
89 | *
90 | */
91 | findBy(collectionKey, key, value) {
92 | return Ember.computed(collectionKey, function(){
93 | var collection = this.get(collectionKey);
94 | return collection && Ember.A(collection).findBy(key, value);
95 | });
96 | },
97 |
98 | /**
99 | * Tests that the value at `dependentKey` is not equal to the provided value.
100 | *
101 | * city: 'Montreal',
102 | * isMontreal: Ember.computed.equal('city', 'Montreal')
103 | * isNotMontreal: Ember.computed.notEqual('city', 'Montreal')
104 | *
105 | * // isMontreal is true
106 | * // isNotMontreal is false
107 | */
108 | notEqual(dependentKey, value) {
109 | Ember.assert(value, "notEqual requires a value for comparison");
110 | return Ember.computed(dependentKey, function() {
111 | return this.get(dependentKey) !== value;
112 | });
113 | },
114 |
115 | /**
116 | * Compose multiple arguments and functions together.
117 | *
118 | * Takes an unlimited number of args and fns. (key, [dependentKey, ] [fn, ] fn)
119 | * The first m string arguments are dependent keys.
120 | * The remaining arguments are functions that get evaluated right to left
121 | * which is often called `compose`.
122 | *
123 | * The values of the dependent keys are passed to the rightmost function.
124 | * The result from each function is passed to the next function, and the final
125 | * result is the value the computed property takes on.
126 | *
127 | * - The order of the keys provided is preserved.
128 | * - Inside the provided functions `this` is the parent object.
129 | *
130 | * Ex:
131 | *
132 | * formattedTotal: Ember.computed.fn( 'amount', 'fee', 'tax',
133 | * function(total) {
134 | * return `$${total}`;
135 | * },
136 | * function(amount, fee, tax){
137 | * return amount + fee + tax;
138 | * })
139 | */
140 | compose() {
141 | var args = Array.prototype.slice.call(arguments),
142 | dependentKeys = args.filter(function(arg){ return typeof arg === 'string'; }),
143 | computedArgs = dependentKeys.slice(),
144 | composedFn = Composable.compose.apply(this, args.slice(dependentKeys.length));
145 |
146 | computedArgs.push(function(){
147 | return composedFn.apply(this, dependentKeys.map( (key) => { return this.get(key); }));
148 | });
149 | return Ember.computed.apply(Ember, computedArgs);
150 | }
151 | };
152 |
153 | // Alias the includes functino to `contains`
154 | Computable.contains = Computable.includes;
155 |
156 | // Alias the compose function to `fn`
157 | Computable.fn = Computable.compose;
158 |
159 | export default Computable;
160 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ember-computable
2 |
3 | This library has two parts:
4 | * `Computable` - a set of Computable Property extensions applied to Ember.computed.
5 | * `Composable` - a set of curried and partially applied functions that can be used with `Ember.computed.fn()`.
6 |
7 | ---
8 | ### Quick Example
9 |
10 |
11 | Allows you to start with this:
12 |
13 | ```
14 | amount: Ember.computed('isSender', 'paymentAmount',
15 | function(){
16 | var isSender = this.get('isSender'),
17 | amount = this.get('paymentAmount'),
18 | direction = isSender ? '-' : '';
19 | return `${direction}${amount}`;
20 | }
21 | ),
22 | ```
23 |
24 | Rewritten as:
25 |
26 | ```
27 | amount: Ember.computed.fn('isSender', 'paymentAmount',
28 | function(isSender, paymentAmount){
29 | var direction = isSender ? '-' : '';
30 | return `${direction}${paymentAmount}`;
31 | }
32 | ),
33 | ```
34 |
35 | ---
36 | ## Computable - Computed Property Extensions
37 |
38 | #### .compose(dependentKey, [dependentKey, ] [fn, ] fn1)
39 |
40 | Aliased to `.fn()`
41 |
42 | Composes multiple arguments and functions together.
43 |
44 | The leading string arguments are dependent keys. The values of the dependent keys are evaluated at runtime and passed to the rightmost function (`fn1`).
45 |
46 | The remaining arguments are functions that get evaluated right to left, a process called 'composition'. The result from each function is passed to the next function, and the final
47 | result is the value the computed property takes on.
48 |
49 | - The order of the keys provided is preserved.
50 | - Inside the provided functions `this` is the parent object of the computed property.
51 |
52 | ```
53 | formattedTotal: Ember.computed.fn( 'amount', 'fee', 'tax',
54 | function(total) {
55 | return `$${total}`;
56 | },
57 | function(amount, fee, tax){
58 | return amount + fee + tax;
59 | })
60 | ```
61 |
62 | #### .ifElse(dependentKey, value, elseValue)
63 |
64 | If the value stored at the `dependentKey` is truthy then `value` is returned, otherwise `elseValue` is returned.
65 |
66 | #### .ifElseKeys(dependentKey, key, elseKey)
67 |
68 | If the value stored at the `dependentKey` is truthy then the value stored at `key` is returned, otherwise the value at `elseKey`.
69 |
70 | #### .includes(collectionKey, value)
71 |
72 | Returns true if `value` is in the enumerable collection.
73 |
74 | #### .indexBy(collectionKey, selector)
75 |
76 | Iterates a collection of objects and uses the attributes at `selector` of each as keys to populate a new hashtable containing the original items.
77 |
78 | #### .findBy(collectionKey, key, value)
79 |
80 | Returns the first item in the target collection with a property at key matching the provided value
81 |
82 | #### .fn(dependentKey, [dependentKey, ] [fn, ] fn1)
83 |
84 | `.fn()` is an alias for `.compose()`
85 |
86 | #### .notEqual(dependentKey, value)
87 |
88 | Tests that the value at `dependentKey` is not equal to the provided value.
89 |
90 | ---
91 |
92 | ## Composable - Helper functions
93 |
94 |
95 | #### .argsToArray
96 |
97 | Converts all the function `arguments` to a proper Array and returns.
98 |
99 | #### .identity
100 |
101 | Returns the value of the first argument passed to the function.
102 |
103 | #### .not
104 |
105 | Returns the inverse truthy/falsy value of the provided argument.
106 |
107 | #### .compact
108 |
109 | Iterates an array and returns a new array that does not contain null or undefined elements.
110 |
111 | #### .default(defaultValue)
112 |
113 | - @param `defaultValue` - a default value that will be returned by the returned function if called with a falsy value.
114 | - @returns `fn(value) -> value || defaultValue`
115 |
116 | ```
117 | stateImageUrl: Ember.computed.fn('state', _c.default('not_found.png'), _c.lookupKey(STATE_IMAGE_MAP))
118 | ```
119 |
120 | #### .filter(filterFn)
121 |
122 | Returns a partially evaluated function that will filter an array using the provided filter function.
123 |
124 | - @param `filterFn(array) -> boolean`
125 | - @returns `fn(array) -> array`
126 |
127 | #### .filterBy(key, value)
128 |
129 | Returns a partial fn that will filter an array by the `key` and `value` provided.
130 |
131 | - @param `key` - accessor
132 | - @param `value` - desired value
133 | - @returns `fn(array) -> array`
134 |
135 | #### .join(separator)
136 |
137 | - @param `separator` - the separator to be applied
138 | - @returns `fn(array) -> string`
139 |
140 | #### .lookupKey(map, defaultValue)
141 |
142 | - @param `map` - A map `{}` that maps keys to values.
143 | - @param `defaultValue` - if the key is not found in the map, this value is returned.
144 | - @returns `fn(key) -> map[key]` - return the value at `map[key]`.
145 |
146 | #### .mapBy(key)
147 |
148 | - @param `separator` - the separator to be applied
149 | - @returns `fn(array) -> array`
150 |
151 | #### .parseInt(radix)
152 |
153 | - @param `radix` - the radix (int) value to use
154 | - @returns `fn(stringValue) -> int` - will call parseInt(radix) on the `stringValue`
155 |
156 | #### .replace(regex, replacement)
157 |
158 | Returns a partial fn that runs the provided regular expression replacement on a string.
159 |
160 | - @param `RegExp` - the regex to use
161 | - @param `replacement` - the replacement string
162 | - @returns `fn(stringValue) -> string`
163 |
164 | ---
165 | ## Examples
166 |
167 | Calling a utility method with the same value as you're dependent on:
168 |
169 | ```
170 | paymentComplete: Ember.computed('payment.state', function() {
171 | return Utils.isSuccessfulPayment(this.get('payment.state'));
172 | }
173 | ```
174 |
175 | Reduces to directly providing the function.
176 |
177 | ```
178 | paymentComplete: Ember.computed.fn('payment.state', Utils.isSuccessfulPayment)
179 | ```
180 |
181 | #### Multiple Composed Functions
182 |
183 | This example converts a function that calls two functions in sequence:
184 |
185 | ```
186 | date: Ember.computed('transaction.initiated_at', function() {
187 | let date = parseInt(this.get('transaction.initiated_at'), 10);
188 | return moment(date);
189 | })
190 | ```
191 |
192 | Into two composed functions that are evaluated in reverse order:
193 |
194 | ```
195 | date: Ember.computed.compose('transaction.initiated_at', moment, comp.parseInt(10))
196 | ```
197 |
198 | #### Multiple dependencies
199 |
200 | Previously multiple dependencies required a lot of boilerplate getter code.
201 |
202 | ```
203 | singleLineAddress: Ember.computed(
204 | 'model.address_line_1',
205 | 'model.address_line_2',
206 | 'model.locality',
207 | 'model.administrative_district_level_1',
208 | 'model.postal_code',
209 | function() {
210 | [
211 | this.get('model.address_line_1'),
212 | this.get('model.address_line_2'),
213 | this.get('model.locality'),,
214 | this.get('model.administrative_district_level_1'),
215 | this.get('model.postal_code'),
216 | ].map(function(part) {
217 | return part && part.trim().replace(/(,$)/g, '');
218 | }).filter(function(part) {
219 | return part;
220 | }).join(', ');
221 | }
222 | ),
223 | ```
224 |
225 | Now the dependencies are passed directly to the composed function. We can combine many small partial functions together into a series of composed functions:
226 |
227 | ```
228 | singleLineAddress: Ember.computed.compose(
229 | 'model.address_line_1',
230 | 'model.address_line_2',
231 | 'model.locality',
232 | 'model.administrative_district_level_1',
233 | 'model.postal_code',
234 | comp.join(', '),
235 | comp.compact,
236 | comp.map(comp.replace(/^\s+|,?\s+$/g, ''))),
237 | ```
238 |
239 | ## Publishing New Versions
240 |
241 | 1) Update package.json and increment the version number.
242 | 2) Commit all your changes.
243 | 3) Tag the new version. If it is version `v0.0.4` then the command is:
244 |
245 | ```
246 | git tag -a v0.0.4 -m "contains/include fix"
247 | ```
248 |
249 | 4) Push the tag to the server.
250 |
251 | ```
252 | git push origin v0.0.4
253 | ```
254 |
255 |
256 | ## Installation
257 |
258 | * `git clone` this repository
259 | * `npm install`
260 | * `bower install`
261 |
262 | ## Running
263 |
264 | * `ember server`
265 | * Visit your app at http://localhost:4200.
266 |
267 | ## Running Tests
268 |
269 | * `npm test` (Runs `ember try:testall` to test your addon against multiple Ember versions)
270 | * `ember test`
271 | * `ember test --server`
272 |
273 | ## Building
274 |
275 | * `ember build`
276 |
277 | For more information on using ember-cli, visit [http://ember-cli.com/](http://ember-cli.com/).
278 |
279 | _____________________________________________________
280 |
281 | Copyright 2017 Square, Inc.
282 |
283 | Licensed under the Apache License, Version 2.0 (the "License");
284 | you may not use this file except in compliance with the License.
285 | You may obtain a copy of the License at
286 |
287 | http://www.apache.org/licenses/LICENSE-2.0
288 |
289 | Unless required by applicable law or agreed to in writing, software
290 | distributed under the License is distributed on an "AS IS" BASIS,
291 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
292 | See the License for the specific language governing permissions and
293 | limitations under the License.
294 |
--------------------------------------------------------------------------------
/tests/unit/utils/computable-test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Computable from 'ember-computable/utils/computable';
3 | import Composable from 'ember-computable/utils/composable';
4 | import { module, test } from 'qunit';
5 |
6 | module('Unit | Utils | Computable.ifElse');
7 |
8 | test('Should return first item when condition property evaluates to true', function(assert) {
9 | var component = Ember.Component.extend({
10 | renderer: {},
11 | isBusiness: true,
12 | message: Computable.ifElse('isBusiness', 'Hello, Business Customer', 'Hola, Personal Customer')
13 | }).create();
14 | assert.equal(component.get('message'), 'Hello, Business Customer');
15 | });
16 |
17 | test('Should return second item when condition property evaluates to true', function(assert) {
18 | var component = Ember.Component.extend({
19 | renderer: {},
20 | isBusiness: false,
21 | message: Computable.ifElse('isBusiness', 'Hello, Business Customer', 'Hola, Personal Customer')
22 | }).create();
23 | assert.equal(component.get('message'), 'Hola, Personal Customer');
24 | });
25 |
26 |
27 | module('Unit | Utils | Computable.ifElseKeys');
28 |
29 | test('Should return first item when condition property evaluates to true', function(assert) {
30 | var component = Ember.Component.extend({
31 | renderer: {},
32 | isBusiness: true,
33 | businessFee: 12,
34 | personalFee: 5,
35 | fee: Computable.ifElseKeys('isBusiness', 'businessFee', 'personalFee')
36 | }).create();
37 | assert.equal(component.get('fee'), 12);
38 | });
39 |
40 | test('Should return second item when condition property evaluates to false', function(assert) {
41 | var component = Ember.Component.extend({
42 | renderer: {},
43 | isBusiness: false,
44 | businessFee: 12,
45 | personalFee: 5,
46 | fee: Computable.ifElseKeys('isBusiness', 'businessFee', 'personalFee')
47 | }).create();
48 | assert.equal(component.get('fee'), 5);
49 | });
50 |
51 | test('Should handle undefined keys', function(assert) {
52 | var component = Ember.Component.extend({
53 | renderer: {},
54 | isBusiness: false,
55 | businessFee: 12,
56 | fee: Computable.ifElseKeys('isBusiness', 'businessFee', 'personalFee')
57 | }).create();
58 | assert.equal(component.get('fee'), undefined);
59 | });
60 |
61 |
62 | module('Unit | Utils | Computable.findBy');
63 |
64 | test('Should find the requested item', function(assert) {
65 | var component = Ember.Component.extend({
66 | renderer: {},
67 | participants: [
68 | {
69 | name: 'Alice',
70 | id: 1492
71 | },
72 | {
73 | name: 'Bob',
74 | id: 1493
75 | },
76 | {
77 | name: 'Eve',
78 | id: 1494
79 | },
80 | {
81 | name: 'Dave',
82 | id: 1495
83 | }
84 | ],
85 | eve: Computable.findBy('participants', 'id', 1494)
86 | }).create();
87 | assert.equal(component.get('eve.name'), 'Eve');
88 | });
89 |
90 | test('Should handle duplicate items, returning first match', function(assert) {
91 | var component = Ember.Component.extend({
92 | renderer: {},
93 | participants: [
94 | {
95 | name: 'Alice',
96 | id: 1492
97 | },
98 | {
99 | name: 'Bob',
100 | id: 1493
101 | },
102 | {
103 | name: 'Eve',
104 | id: 1494
105 | },
106 | {
107 | name: 'Dave',
108 | id: 1495
109 | },
110 | {
111 | name: 'Eve',
112 | id: 1496
113 | }
114 | ],
115 | eve: Computable.findBy('participants', 'name', 'Eve')
116 | }).create();
117 | assert.equal(component.get('eve.id'), 1494);
118 | });
119 |
120 | test('Should handle an empty collection', function(assert) {
121 | var component = Ember.Component.extend({
122 | renderer: {},
123 | participants: [],
124 | eve: Computable.findBy('participants', 'id', 1494)
125 | }).create();
126 | assert.equal(component.get('eve'), undefined);
127 | });
128 |
129 | test('Should handle a non-existant collection', function(assert) {
130 | var component = Ember.Component.extend({
131 | renderer: {},
132 | eve: Computable.findBy('participants', 'id', 1494)
133 | }).create();
134 | assert.equal(component.get('eve'), undefined);
135 | });
136 |
137 |
138 | module('Unit | Utils | Computable.indexBy');
139 |
140 | test('Indexes by single level selector', function(assert) {
141 | var alice = Ember.Object.create({ name: 'Alice', id: 1492 });
142 | var bob = Ember.Object.create({ name: 'Bob', id: 1493 });
143 | var eve = Ember.Object.create({ name: 'Eve', id: 1494 });
144 | var dave = Ember.Object.create({ name: 'Bob', id: 1495 });
145 |
146 | var component = Ember.Component.extend({
147 | renderer: {},
148 | participants: Ember.A([
149 | alice,
150 | bob,
151 | eve,
152 | dave
153 | ]),
154 | idLookup: Computable.indexBy('participants', 'id')
155 | }).create();
156 | var idLookup = component.get('idLookup');
157 |
158 | assert.equal(idLookup['1492'], alice);
159 | assert.equal(idLookup['1493'], bob);
160 | });
161 |
162 | test('Gracefully handles empty target array', function(assert) {
163 | var component = Ember.Component.extend({
164 | renderer: {},
165 | participants: Ember.A([]),
166 | idLookup: Computable.indexBy('participants', 'id')
167 | }).create();
168 | var idLookup = component.get('idLookup');
169 | assert.notEqual(idLookup, undefined);
170 | assert.equal(idLookup['1492'], undefined);
171 | });
172 |
173 |
174 | module('Unit | Utils | Computable.includes');
175 |
176 | test('Indexes by single level selector', function(assert) {
177 | var component = Ember.Component.extend({
178 | renderer: {},
179 | participants: Ember.A([
180 | 'one',
181 | 'two',
182 | 'three'
183 | ]),
184 | hasThree: Computable.includes('participants', 'three'),
185 | hasFour: Computable.includes('participants', 'four')
186 | }).create();
187 |
188 | assert.equal(component.get('hasThree'), true);
189 | assert.equal(component.get('hasFour'), false);
190 | });
191 |
192 | test('Gracefully handles empty target array', function(assert) {
193 | var component = Ember.Component.extend({
194 | renderer: {},
195 | participants: Ember.A([]),
196 | hasFour: Computable.includes('participants', 'four')
197 | }).create();
198 | assert.equal(component.get('hasFour'), false);
199 | });
200 |
201 | module('Unit | Utils | Computable.notEqual');
202 | test('returns true when not equal', function(assert) {
203 | let component = Ember.Component.extend({
204 | renderer: {},
205 | value: 'A VALUE',
206 | notEqual: Computable.notEqual('value', 'NOT A VALUE')
207 | }).create();
208 | assert.equal(component.get('notEqual'), true);
209 | });
210 | test('returns false when equal', function(assert) {
211 | let component = Ember.Component.extend({
212 | renderer: {},
213 | value: 'A VALUE',
214 | notEqual: Computable.notEqual('value', 'A VALUE')
215 | }).create();
216 | assert.equal(component.get('notEqual'), false);
217 | });
218 | test('handles undefined values', function(assert) {
219 | let component = Ember.Component.extend({
220 | renderer: {},
221 | value: undefined,
222 | notEqual: Computable.notEqual('value', 'A VALUE')
223 | }).create();
224 | assert.equal(component.get('notEqual'), true);
225 | });
226 |
227 |
228 | module('Unit | Utils | Computable.compose');
229 |
230 | test('Should resolve the dependent keys', function(assert) {
231 | var component = Ember.Component.extend({
232 | renderer: {},
233 | numerator: 6,
234 | denominator: 2,
235 | result: Computable.compose('numerator', 'denominator', function(numerator, denominator){
236 | return numerator/denominator;
237 | })
238 | }).create();
239 | assert.equal(component.get('result'), 3);
240 | });
241 |
242 | test('Should resolve many dependent keys', function(assert) {
243 | var component = Ember.Component.extend({
244 | renderer: {},
245 | a: 'the',
246 | b: 'quick',
247 | c: 'brown',
248 | d: 'fox',
249 | e: 'jumps',
250 | sentence: Computable.compose('a', 'b', 'c', 'd', 'e', Composable.join(' '), Composable.argsToArray),
251 | sentenceFn: Computable.fn('a', 'b', 'c', 'd', 'e', Composable.join(' '), Composable.argsToArray)
252 | }).create();
253 | assert.equal(component.get('sentence'), 'the quick brown fox jumps');
254 | assert.equal(component.get('sentenceFn'), 'the quick brown fox jumps');
255 | });
256 |
257 | test('Should have correct `this` scope inside function', function(assert) {
258 | var component = Ember.Component.extend({
259 | renderer: {},
260 | a: 'the',
261 | b: 'quick',
262 | c: 'brown',
263 | sentence: Computable.compose('a', 'b', function(){
264 | return this.get('c');
265 | })
266 | }).create();
267 | assert.equal(component.get('sentence'), 'brown');
268 | });
269 |
270 | test('Should handle multiple composed functions', function(assert) {
271 | var component = Ember.Component.extend({
272 | renderer: {},
273 | participants: [
274 | {
275 | name: 'Alice',
276 | id: 1492
277 | },
278 | {
279 | name: 'Bob',
280 | id: 1493
281 | },
282 | {
283 | name: 'Eve',
284 | id: 1494
285 | },
286 | {
287 | name: 'Dave',
288 | id: 1495
289 | },
290 | {
291 | name: 'Eve',
292 | id: 1496
293 | }
294 | ],
295 | beforeSignupChange: Computable.compose('participants',
296 | Composable.mapBy('name'),
297 | Composable.filter((participant) => {
298 | return participant.id <= 1494;
299 | }))
300 | }).create();
301 | var result = component.get('beforeSignupChange');
302 | assert.equal(result.length, 3, 'Has three items');
303 | assert.equal(result[0], 'Alice');
304 | assert.equal(result[1], 'Bob');
305 | assert.equal(result[2], 'Eve');
306 | });
307 |
308 | test('Should handle multiple composed functions 2', function(assert) {
309 | var component = Ember.Component.extend({
310 | renderer: {},
311 | initiated_at: '533995200000',
312 | initiated_at_friendly: Computable.compose('initiated_at',
313 | function(dateInt) {
314 | return new Date(dateInt).toString();
315 | },
316 | Composable.parseInt(10))
317 | }).create();
318 | assert.equal(component.get('initiated_at_friendly'), 'Wed Dec 03 1986 07:00:00 GMT-0500 (EST)');
319 | });
320 |
321 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, March 2017
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2017 Square
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------