├── test-app ├── tests │ ├── unit │ │ ├── .gitkeep │ │ ├── helpers │ │ │ ├── inc-test.js │ │ │ ├── dec-test.js │ │ │ ├── next-test.js │ │ │ ├── flatten-test.js │ │ │ ├── previous-test.js │ │ │ ├── toggle-test.js │ │ │ ├── repeat-test.js │ │ │ ├── without-test.js │ │ │ ├── range-test.js │ │ │ └── invoke-test.js │ │ └── utils │ │ │ ├── is-object-test.js │ │ │ ├── is-promise-test.js │ │ │ └── get-index-test.js │ ├── integration │ │ ├── .gitkeep │ │ └── helpers │ │ │ ├── noop-test.js │ │ │ ├── compute-test.js │ │ │ ├── toggle-action-test.js │ │ │ ├── repeat-test.js │ │ │ ├── pipe-action-test.js │ │ │ ├── values-test.js │ │ │ ├── call-test.js │ │ │ ├── inc-test.js │ │ │ ├── from-entries-test.js │ │ │ ├── keys-test.js │ │ │ ├── dec-test.js │ │ │ ├── pick-test.js │ │ │ ├── drop-test.js │ │ │ ├── optional-test.js │ │ │ ├── entries-test.js │ │ │ ├── flatten-test.js │ │ │ ├── group-by-test.js │ │ │ └── queue-test.js │ ├── helpers │ │ ├── destroy-app.js │ │ ├── start-app.js │ │ ├── module-for-acceptance.js │ │ └── index.ts │ ├── test-helper.js │ └── index.html ├── .watchmanconfig ├── types │ ├── global.d.ts │ └── ember-data │ │ └── types │ │ └── registries │ │ └── model.d.ts ├── public │ └── robots.txt ├── app │ ├── resolver.ts │ ├── styles │ │ └── app.css │ ├── router.js │ ├── config │ │ └── environment.d.ts │ ├── app.ts │ └── index.html ├── .stylelintrc.js ├── .stylelintignore ├── config │ ├── targets.js │ ├── optional-features.json │ ├── ember-cli-update.json │ └── environment.js ├── .eslintignore ├── .ember-cli ├── type-tests │ ├── reject-by-test.ts │ ├── repeat-test.ts │ ├── map-by-test.ts │ ├── compute-test.ts │ ├── call-test.ts │ └── optional-test.ts ├── .template-lintrc.js ├── tsconfig.json ├── .gitignore ├── testem.js ├── ember-cli-build.js └── .eslintrc.js ├── test-app-min-supported ├── app │ ├── styles │ │ └── app.css │ ├── resolver.js │ ├── router.js │ ├── app.js │ └── index.html ├── tests │ ├── unit │ │ ├── .gitkeep │ │ ├── helpers │ │ │ ├── inc-test.js │ │ │ ├── dec-test.js │ │ │ ├── next-test.js │ │ │ ├── flatten-test.js │ │ │ ├── previous-test.js │ │ │ ├── repeat-test.js │ │ │ ├── without-test.js │ │ │ ├── toggle-test.js │ │ │ ├── range-test.js │ │ │ └── invoke-test.js │ │ └── utils │ │ │ ├── is-object-test.js │ │ │ ├── is-promise-test.js │ │ │ └── get-index-test.js │ ├── helpers │ │ ├── .gitkeep │ │ ├── destroy-app.js │ │ ├── start-app.js │ │ └── module-for-acceptance.js │ ├── integration │ │ ├── .gitkeep │ │ └── helpers │ │ │ ├── noop-test.js │ │ │ ├── compute-test.js │ │ │ ├── toggle-action-test.js │ │ │ ├── repeat-test.js │ │ │ ├── pipe-action-test.js │ │ │ ├── values-test.js │ │ │ ├── call-test.js │ │ │ ├── inc-test.js │ │ │ ├── from-entries-test.js │ │ │ ├── keys-test.js │ │ │ ├── dec-test.js │ │ │ ├── optional-test.js │ │ │ ├── pick-test.js │ │ │ ├── drop-test.js │ │ │ ├── entries-test.js │ │ │ ├── flatten-test.js │ │ │ └── queue-test.js │ ├── test-helper.js │ └── index.html ├── .watchmanconfig ├── public │ └── robots.txt ├── .template-lintrc.js ├── config │ ├── optional-features.json │ ├── targets.js │ └── environment.js ├── .ember-cli ├── .eslintignore ├── .gitignore ├── testem.js ├── ember-cli-build.js └── .eslintrc.js ├── stderr.log ├── test-app-min-supported-classic ├── app │ ├── styles │ │ └── app.css │ ├── resolver.js │ ├── router.js │ ├── app.js │ └── index.html ├── tests │ ├── helpers │ │ ├── .gitkeep │ │ ├── destroy-app.js │ │ ├── start-app.js │ │ └── module-for-acceptance.js │ ├── unit │ │ ├── .gitkeep │ │ ├── helpers │ │ │ ├── inc-test.js │ │ │ ├── dec-test.js │ │ │ ├── next-test.js │ │ │ ├── flatten-test.js │ │ │ ├── previous-test.js │ │ │ ├── repeat-test.js │ │ │ ├── without-test.js │ │ │ ├── toggle-test.js │ │ │ ├── range-test.js │ │ │ └── invoke-test.js │ │ └── utils │ │ │ ├── is-object-test.js │ │ │ ├── is-promise-test.js │ │ │ └── get-index-test.js │ ├── integration │ │ ├── .gitkeep │ │ └── helpers │ │ │ ├── noop-test.js │ │ │ ├── compute-test.js │ │ │ ├── toggle-action-test.js │ │ │ ├── repeat-test.js │ │ │ ├── pipe-action-test.js │ │ │ ├── values-test.js │ │ │ ├── call-test.js │ │ │ ├── inc-test.js │ │ │ ├── from-entries-test.js │ │ │ ├── keys-test.js │ │ │ ├── dec-test.js │ │ │ ├── optional-test.js │ │ │ ├── pick-test.js │ │ │ ├── drop-test.js │ │ │ ├── entries-test.js │ │ │ ├── flatten-test.js │ │ │ └── queue-test.js │ ├── test-helper.js │ └── index.html ├── .watchmanconfig ├── public │ └── robots.txt ├── .template-lintrc.js ├── config │ ├── optional-features.json │ ├── targets.js │ └── environment.js ├── .ember-cli ├── .eslintignore ├── .gitignore ├── ember-cli-build.js ├── testem.js └── .eslintrc.js ├── ember-composable-helpers ├── .template-lintrc.cjs ├── addon-main.cjs ├── .prettierignore ├── src │ ├── helpers │ │ ├── noop.ts │ │ ├── append.ts │ │ ├── take.ts │ │ ├── drop.ts │ │ ├── keys.ts │ │ ├── from-entries.ts │ │ ├── values.ts │ │ ├── slice.ts │ │ ├── entries.ts │ │ ├── toggle-action.ts │ │ ├── union.ts │ │ ├── reverse.ts │ │ ├── map.ts │ │ ├── pipe-action.ts │ │ ├── compact.ts │ │ ├── filter.ts │ │ ├── map-by.ts │ │ ├── dec.ts │ │ ├── inc.ts │ │ ├── join.ts │ │ ├── pick.ts │ │ ├── reduce.ts │ │ ├── find-by.ts │ │ ├── object-at.ts │ │ ├── flatten.ts │ │ ├── compute.ts │ │ ├── repeat.ts │ │ ├── group-by.ts │ │ ├── includes.ts │ │ ├── without.ts │ │ ├── pipe.ts │ │ ├── range.ts │ │ ├── previous.ts │ │ ├── chunk.ts │ │ ├── invoke.ts │ │ ├── toggle.ts │ │ ├── has-next.ts │ │ ├── optional.ts │ │ ├── shuffle.ts │ │ ├── has-previous.ts │ │ ├── next.ts │ │ ├── reject-by.ts │ │ ├── queue.ts │ │ ├── intersect.ts │ │ ├── filter-by.ts │ │ └── call.ts │ ├── utils │ │ ├── is-object.ts │ │ ├── types.ts │ │ ├── comparison.ts │ │ ├── is-equal.ts │ │ ├── get-index.ts │ │ └── is-promise.ts │ └── -private │ │ ├── get-value-array-and-use-deep-equal-from-params.ts │ │ └── closure-action.ts ├── .prettierrc.cjs ├── .gitignore ├── babel.config.json └── unpublished-development-types │ └── index.d.ts ├── .prettierrc.cjs ├── pnpm-workspace.yaml ├── .github ├── renovate.json5 └── workflows │ ├── push-dist.yml │ └── publish.yml ├── .prettierignore ├── .gitignore ├── .editorconfig ├── config └── ember-cli-update.json ├── .npmrc ├── .release-plan.json ├── package.json └── RELEASE.md /test-app/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app-min-supported/app/styles/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stderr.log: -------------------------------------------------------------------------------- 1 | Warning: unknown package "test-app" 2 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/app/styles/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["dist"] 3 | } 4 | -------------------------------------------------------------------------------- /test-app/types/global.d.ts: -------------------------------------------------------------------------------- 1 | import '@glint/environment-ember-loose'; 2 | -------------------------------------------------------------------------------- /test-app-min-supported/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /test-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /test-app-min-supported/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test-app/app/resolver.ts: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test-app-min-supported/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /test-app/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: ['stylelint-config-standard'], 5 | }; 6 | -------------------------------------------------------------------------------- /ember-composable-helpers/.template-lintrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /test-app-min-supported/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended' 5 | }; 6 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended' 5 | }; 6 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | plugins: ['prettier-plugin-ember-template-tag'], 5 | singleQuote: true, 6 | }; 7 | -------------------------------------------------------------------------------- /test-app/app/styles/app.css: -------------------------------------------------------------------------------- 1 | /* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */ 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'ember-composable-helpers' 3 | - 'test-app' 4 | - 'test-app-min-supported' 5 | - 'test-app-min-supported-classic' 6 | -------------------------------------------------------------------------------- /test-app/.stylelintignore: -------------------------------------------------------------------------------- 1 | # unconventional files 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | 7 | # addons 8 | /.node_modules.ember-try/ 9 | -------------------------------------------------------------------------------- /ember-composable-helpers/addon-main.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { addonV1Shim } = require('@embroider/addon-shim'); 4 | module.exports = addonV1Shim(__dirname); 5 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | // Docs: 2 | // https://docs.renovatebot.com/configuration-options/ 3 | { 4 | "extends": [ 5 | "github>NullVoxPopuli/renovate:npm.json5" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test-app/tests/helpers/destroy-app.js: -------------------------------------------------------------------------------- 1 | import { run } from '@ember/runloop'; 2 | 3 | export default function destroyApp(application) { 4 | run(application, 'destroy'); 5 | } 6 | -------------------------------------------------------------------------------- /ember-composable-helpers/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | /declarations/ 7 | 8 | # misc 9 | /coverage/ 10 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/helpers/destroy-app.js: -------------------------------------------------------------------------------- 1 | import { run } from '@ember/runloop'; 2 | 3 | export default function destroyApp(application) { 4 | run(application, 'destroy'); 5 | } 6 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/noop.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function noop() { 4 | return () => {}; 5 | } 6 | 7 | export default helper(noop); 8 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/helpers/destroy-app.js: -------------------------------------------------------------------------------- 1 | import { run } from '@ember/runloop'; 2 | 3 | export default function destroyApp(application) { 4 | run(application, 'destroy'); 5 | } 6 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/utils/is-object.ts: -------------------------------------------------------------------------------- 1 | import { typeOf } from '@ember/utils'; 2 | 3 | export default function isObject(val: unknown) { 4 | return typeOf(val) === 'object' || typeOf(val) === 'instance'; 5 | } 6 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/append.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function append([...arrays]) { 4 | return [].concat(...arrays); 5 | } 6 | 7 | export default helper(append); 8 | -------------------------------------------------------------------------------- /test-app-min-supported/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /test-app/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 | module.exports = { 10 | browsers, 11 | }; -------------------------------------------------------------------------------- /test-app-min-supported-classic/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /test-app/types/ember-data/types/registries/model.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Catch-all for ember-data. 3 | */ 4 | export default interface ModelRegistry { 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | [key: string]: any; 7 | } 8 | -------------------------------------------------------------------------------- /test-app/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true, 6 | "no-implicit-route-model": true 7 | } 8 | -------------------------------------------------------------------------------- /test-app/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional j# unconventional js 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /declarations/ 6 | /dist/ 7 | 8 | # misc 9 | /coverage/ 10 | !.* 11 | .*/ 12 | 13 | # ember-try 14 | /.node_modules.ember-try/ 15 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Prettier is also run from each package, so the ignores here 2 | # protect against files that may not be within a package 3 | 4 | # misc 5 | !.* 6 | .lint-todo/ 7 | 8 | # ember-try 9 | /.node_modules.ember-try/ 10 | /pnpm-lock.ember-try.yaml 11 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/utils/types.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 2 | export type AnyFn = (...args: any[]) => any; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | export type AnyVoidFn = (...args: any[]) => void; 6 | -------------------------------------------------------------------------------- /test-app/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript 4 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available. 5 | */ 6 | "isTypeScriptProject": true 7 | } 8 | -------------------------------------------------------------------------------- /test-app/type-tests/reject-by-test.ts: -------------------------------------------------------------------------------- 1 | import { rejectBy } from '@nullvoxpopuli/ember-composable-helpers/helpers/reject-by'; 2 | import { expectTypeOf } from 'expect-type'; 3 | 4 | expectTypeOf(rejectBy(['address', [{ address: 1 }]])).toEqualTypeOf< 5 | { address: number }[] 6 | >(); 7 | -------------------------------------------------------------------------------- /test-app/type-tests/repeat-test.ts: -------------------------------------------------------------------------------- 1 | import { repeat } from '@nullvoxpopuli/ember-composable-helpers/helpers/repeat'; 2 | import { expectTypeOf } from 'expect-type' 3 | 4 | expectTypeOf(repeat([10, "hello"])).toEqualTypeOf(); 5 | expectTypeOf(repeat([10])).toEqualTypeOf(); 6 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/take.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import asArray from '../utils/as-array.ts'; 3 | 4 | export function take([takeAmount, array]: [number, T[]]) { 5 | return asArray(array).slice(0, takeAmount); 6 | } 7 | 8 | export default helper(take); 9 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/drop.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | import asArray from '../utils/as-array.ts'; 4 | 5 | export function drop([dropAmount, array]: [number, T[]]) { 6 | return asArray(array).slice(dropAmount); 7 | } 8 | 9 | export default helper(drop); 10 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/keys.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function keys([object]: [Record | null | undefined]) { 4 | if (!object) { 5 | return object; 6 | } 7 | return Object.keys(object); 8 | } 9 | 10 | export default helper(keys); 11 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/from-entries.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function fromEntries([entries]: [[unknown, unknown][]]) { 4 | if (!entries) { 5 | return entries; 6 | } 7 | return Object.fromEntries(entries); 8 | } 9 | 10 | export default helper(fromEntries); 11 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/values.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function values([object]: [Record | null | undefined]) { 4 | if (!object) { 5 | return object; 6 | } 7 | return Object.values(object); 8 | } 9 | 10 | export default helper(values); 11 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/slice.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import asArray from '../utils/as-array.ts'; 3 | 4 | export function slice([...args]) { 5 | let array = args.pop(); 6 | array = asArray(array); 7 | return array.slice(...args); 8 | } 9 | 10 | export default helper(slice); 11 | -------------------------------------------------------------------------------- /test-app-min-supported/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 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/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-composable-helpers/src/helpers/entries.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function entries([object]: [ 4 | Record | null | undefined, 5 | ]) { 6 | if (!object) { 7 | return object; 8 | } 9 | return Object.entries(object); 10 | } 11 | 12 | export default helper(entries); 13 | -------------------------------------------------------------------------------- /test-app-min-supported/.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 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/.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 | -------------------------------------------------------------------------------- /ember-composable-helpers/.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | plugins: ['prettier-plugin-ember-template-tag'], 5 | overrides: [ 6 | { 7 | files: '*.{js,gjs,ts,gts,mjs,mts,cjs,cts}', 8 | options: { 9 | singleQuote: true, 10 | templateSingleQuote: false, 11 | }, 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /test-app/type-tests/map-by-test.ts: -------------------------------------------------------------------------------- 1 | import { mapBy } from '@nullvoxpopuli/ember-composable-helpers/helpers/map-by'; 2 | import { expectTypeOf } from 'expect-type'; 3 | 4 | expectTypeOf(mapBy(['address', [{ address: 1 }]])).toEqualTypeOf(); 5 | 6 | // @ts-expect-error -- byPath isn't a key of array obj 7 | expectTypeOf(mapBy(['foo', [{ address: 1 }]])); 8 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/utils/comparison.ts: -------------------------------------------------------------------------------- 1 | export function lte(a: number, b: number) { 2 | return a <= b; 3 | } 4 | 5 | export function lt(a: number, b: number) { 6 | return a < b; 7 | } 8 | 9 | export function gte(a: number, b: number) { 10 | return a >= b; 11 | } 12 | 13 | export function gt(a: number, b: number) { 14 | return a > b; 15 | } 16 | -------------------------------------------------------------------------------- /test-app-min-supported/.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 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/.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 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/toggle-action.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { toggle } from './toggle.ts'; 3 | import ACTION from '../-private/closure-action.ts'; 4 | 5 | const closureToggle = toggle; 6 | if (ACTION) { 7 | closureToggle[ACTION] = true as never; // TODO: remove never 8 | } 9 | 10 | export default helper(closureToggle); 11 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/union.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import asArray from '../utils/as-array.ts'; 3 | 4 | export function union([...arrays]) { 5 | const items = [].concat(...arrays); 6 | 7 | return items.filter( 8 | (value, index, array) => asArray(array).indexOf(value) === index, 9 | ); 10 | } 11 | 12 | export default helper(union); 13 | -------------------------------------------------------------------------------- /test-app/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | // eslint-disable-next-line ember/no-classic-classes -- TODO: Modernize 5 | const Router = EmberRouter.extend({ 6 | location: config.locationType, 7 | rootURL: config.rootURL 8 | }); 9 | 10 | Router.map(function() { 11 | }); 12 | 13 | export default Router; 14 | -------------------------------------------------------------------------------- /test-app/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 | import * as QUnit from 'qunit'; 6 | import { setup } from 'qunit-dom'; 7 | 8 | setup(QUnit.assert); 9 | 10 | setApplication(Application.create(config.APP)); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/reverse.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { A as emberArray, isArray as isEmberArray } from '@ember/array'; 3 | 4 | export function reverse([array]: [T[]]) { 5 | if (!isEmberArray(array)) { 6 | return [array]; 7 | } 8 | 9 | return emberArray(array).slice(0).reverse(); 10 | } 11 | 12 | export default helper(reverse); 13 | -------------------------------------------------------------------------------- /test-app/app/config/environment.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type declarations for 3 | * import config from 'test-app/config/environment' 4 | */ 5 | declare const config: { 6 | environment: string; 7 | modulePrefix: string; 8 | podModulePrefix: string; 9 | locationType: 'history' | 'hash' | 'none'; 10 | rootURL: string; 11 | APP: Record; 12 | }; 13 | 14 | export default config; 15 | -------------------------------------------------------------------------------- /test-app-min-supported/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 | import * as QUnit from 'qunit'; 6 | import { setup } from 'qunit-dom'; 7 | 8 | setup(QUnit.assert); 9 | 10 | setApplication(Application.create(config.APP)); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/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 | import * as QUnit from 'qunit'; 6 | import { setup } from 'qunit-dom'; 7 | 8 | setup(QUnit.assert); 9 | 10 | setApplication(Application.create(config.APP)); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /test-app/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | 6 | overrides: [ 7 | { 8 | files: ['tests/integration/helpers/chunk-test.js'], 9 | rules: { 10 | 'no-obscure-array-access': 'off', 11 | }, 12 | }, 13 | { 14 | files: ['tests/**/*.js'], 15 | rules: { 16 | // TODO: Fix this! 17 | 'no-action': 'off', 18 | } 19 | } 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # misc 7 | .env* 8 | .pnp* 9 | .pnpm-debug.log 10 | .sass-cache 11 | .eslintcache 12 | coverage/ 13 | npm-debug.log* 14 | yarn-error.log 15 | 16 | # ember-try 17 | /.node_modules.ember-try/ 18 | /package.json.ember-try 19 | /package-lock.json.ember-try 20 | /yarn.lock.ember-try 21 | /pnpm-lock.ember-try.yaml 22 | 23 | -------------------------------------------------------------------------------- /.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 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/map.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isEmpty } from '@ember/utils'; 3 | import asArray from '../utils/as-array.ts'; 4 | 5 | export function map([callback, array]: [ 6 | (value: T, index: number, array: T[]) => T, 7 | T[], 8 | ]) { 9 | if (isEmpty(callback)) { 10 | return []; 11 | } 12 | 13 | return asArray(array).map(callback); 14 | } 15 | 16 | export default helper(map); 17 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/pipe-action.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { pipe } from './pipe.ts'; 3 | import ACTION from '../-private/closure-action.ts'; 4 | 5 | const closurePipe = pipe; 6 | if (ACTION) { 7 | closurePipe[ACTION] = true as never; // TODO: remove never 8 | } 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | export default helper(closurePipe as any); // TODO: remove any 12 | -------------------------------------------------------------------------------- /ember-composable-helpers/.gitignore: -------------------------------------------------------------------------------- 1 | # The authoritative copies of these live in the monorepo root (because they're 2 | # more useful on github that way), but the build copies them into here so they 3 | # will also appear in published NPM packages. 4 | /README.md 5 | /LICENSE.md 6 | 7 | # compiled output 8 | dist/ 9 | declarations/ 10 | 11 | # npm/pnpm/yarn pack output 12 | *.tgz 13 | 14 | # deps & caches 15 | node_modules/ 16 | .eslintcache 17 | .prettiercache 18 | -------------------------------------------------------------------------------- /test-app-min-supported/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 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/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 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/compact.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isPresent } from '@ember/utils'; 3 | import { isArray } from '@ember/array'; 4 | 5 | export function compact([value]: [T[]]): T[] { 6 | let array; 7 | if (Array.isArray(value) || isArray(value)) { 8 | array = value; 9 | } else { 10 | array = [value]; 11 | } 12 | 13 | return array.filter((item) => isPresent(item)); 14 | } 15 | 16 | export default helper(compact); 17 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/filter.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isEmpty } from '@ember/utils'; 3 | import asArray from '../utils/as-array.ts'; 4 | 5 | export function filter([callback, array]: [ 6 | (value: T, index: number, array: T[]) => value is T, 7 | T[], 8 | ]) { 9 | if (isEmpty(callback) || !array) { 10 | return []; 11 | } 12 | 13 | return asArray(array).filter(callback); 14 | } 15 | 16 | export default helper(filter); 17 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/utils/is-equal.ts: -------------------------------------------------------------------------------- 1 | import { isEqual as emberIsEqual } from '@ember/utils'; 2 | 3 | export default function isEqual( 4 | firstValue: unknown, 5 | secondValue: unknown, 6 | useDeepEqual = false, 7 | ) { 8 | if (useDeepEqual) { 9 | return JSON.stringify(firstValue) === JSON.stringify(secondValue); 10 | } else { 11 | return ( 12 | emberIsEqual(firstValue, secondValue) || 13 | emberIsEqual(secondValue, firstValue) 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test-app-min-supported/.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 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /connect.lock 16 | /coverage/ 17 | /libpeerconnection.log 18 | /npm-debug.log* 19 | /testem.log 20 | /yarn-error.log 21 | 22 | # ember-try 23 | /.node_modules.ember-try/ 24 | /bower.json.ember-try 25 | /package.json.ember-try 26 | -------------------------------------------------------------------------------- /config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "@embroider/addon-blueprint", 6 | "version": "4.1.0", 7 | "blueprints": [ 8 | { 9 | "name": "@embroider/addon-blueprint", 10 | "isBaseBlueprint": true, 11 | "options": [ 12 | "--pnpm", 13 | "--typescript", 14 | "--addon-location=ember-composable-helpers" 15 | ] 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/map-by.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { get } from '@ember/object'; 3 | import { isEmpty } from '@ember/utils'; 4 | import asArray from '../utils/as-array.ts'; 5 | 6 | export function mapBy([byPath, array]: [ 7 | K, 8 | T[], 9 | ]) { 10 | if (isEmpty(byPath)) { 11 | return []; 12 | } 13 | 14 | return asArray(array).map((item) => get(item, byPath)); 15 | } 16 | 17 | export default helper(mapBy); 18 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/.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 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /connect.lock 16 | /coverage/ 17 | /libpeerconnection.log 18 | /npm-debug.log* 19 | /testem.log 20 | /yarn-error.log 21 | 22 | # ember-try 23 | /.node_modules.ember-try/ 24 | /bower.json.ember-try 25 | /package.json.ember-try 26 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/dec.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isEmpty } from '@ember/utils'; 3 | 4 | export function dec([step, val]: [number | undefined, number?]) { 5 | if (isEmpty(val)) { 6 | val = step; 7 | step = undefined; 8 | } 9 | 10 | val = Number(val); 11 | 12 | if (isNaN(val)) { 13 | return; 14 | } 15 | 16 | if (step === undefined) { 17 | step = 1; 18 | } 19 | 20 | return val - step; 21 | } 22 | 23 | export default helper(dec); 24 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/inc.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isEmpty } from '@ember/utils'; 3 | 4 | export function inc([step, val]: [number | undefined, number?]) { 5 | if (isEmpty(val)) { 6 | val = step; 7 | step = undefined; 8 | } 9 | 10 | val = Number(val); 11 | 12 | if (isNaN(val)) { 13 | return; 14 | } 15 | 16 | if (step === undefined) { 17 | step = 1; 18 | } 19 | 20 | return val + step; 21 | } 22 | 23 | export default helper(inc); 24 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/join.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isArray as isEmberArray } from '@ember/array'; 3 | import asArray from '../utils/as-array.ts'; 4 | 5 | export function join([separator, rawArray]: [string | T[], T[]?]): string { 6 | let array = asArray(rawArray!); 7 | 8 | if (isEmberArray(separator)) { 9 | array = separator as T[]; 10 | separator = ','; 11 | } 12 | 13 | return array.join(separator); 14 | } 15 | 16 | export default helper(join); 17 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/utils/get-index.ts: -------------------------------------------------------------------------------- 1 | import isEqual from '../utils/is-equal.ts'; 2 | 3 | export default function getIndex( 4 | array: T[], 5 | currentValue: T, 6 | useDeepEqual: boolean, 7 | ) { 8 | let needle = currentValue; 9 | 10 | if (useDeepEqual) { 11 | needle = array.find((object) => { 12 | return isEqual(object, currentValue, useDeepEqual); 13 | }) as T; 14 | } 15 | 16 | const index = (array || []).indexOf(needle); 17 | 18 | return index >= 0 ? index : null; 19 | } 20 | -------------------------------------------------------------------------------- /test-app/app/app.ts: -------------------------------------------------------------------------------- 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 | // eslint-disable-next-line ember/no-classic-classes -- TODO: Modernize 7 | const App = Application.extend({ 8 | modulePrefix: config.modulePrefix, 9 | podModulePrefix: config.podModulePrefix, 10 | Resolver 11 | }); 12 | 13 | loadInitializers(App, config.modulePrefix); 14 | 15 | export default App; 16 | -------------------------------------------------------------------------------- /.github/workflows/push-dist.yml: -------------------------------------------------------------------------------- 1 | name: Push dist 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | push-dist: 10 | name: Push dist 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: wyvox/action-setup-pnpm@v3 15 | - uses: kategengler/put-built-npm-package-contents-on-branch@v2.0.0 16 | with: 17 | branch: dist 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | working-directory: ember-composable-helpers 20 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/pick.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { get } from '@ember/object'; 3 | import type { AnyVoidFn } from '../utils/types'; 4 | 5 | export function pick>([ 6 | path, 7 | action, 8 | ]: [string, AnyVoidFn]) { 9 | return function (obj: T) { 10 | const value = get(obj, path); 11 | 12 | if (!action) { 13 | return value; 14 | } 15 | 16 | action(value); 17 | }; 18 | } 19 | 20 | export default helper(pick); 21 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/utils/is-promise.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | import { typeOf } from '@ember/utils'; 3 | import isObject from './is-object.ts'; 4 | 5 | function isPromiseLike(obj: unknown = {}) { 6 | return ( 7 | typeOf((obj as Promise).then) === 'function' && 8 | typeOf((obj as Promise).catch) === 'function' 9 | ); 10 | } 11 | 12 | export default function isPromise(obj: unknown): obj is Promise { 13 | return isObject(obj) && isPromiseLike(obj); 14 | } 15 | -------------------------------------------------------------------------------- /test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember/tsconfig.json", 3 | "compilerOptions": { 4 | // The combination of `baseUrl` with `paths` allows Ember's classic package 5 | // layout, which is not resolvable with the Node resolution algorithm, to 6 | // work with TypeScript. 7 | "baseUrl": ".", 8 | "paths": { 9 | "test-app/tests/*": ["tests/*"], 10 | "test-app/*": ["app/*"], 11 | "*": ["types/*"] 12 | } 13 | }, 14 | "glint": { 15 | "environment": "ember-loose" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/reduce.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isEmpty } from '@ember/utils'; 3 | import asArray from '../utils/as-array.ts'; 4 | 5 | export function reduce([callback, initialValue, array]: [ 6 | (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, 7 | T, 8 | T[], 9 | ]) { 10 | if (isEmpty(callback)) { 11 | return []; 12 | } 13 | 14 | return asArray(array).reduce(callback, initialValue); 15 | } 16 | 17 | export default helper(reduce); 18 | -------------------------------------------------------------------------------- /test-app/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "5.8.0", 7 | "blueprints": [ 8 | { 9 | "name": "app", 10 | "outputRepo": "https://github.com/ember-cli/ember-new-output", 11 | "codemodsSource": "ember-app-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--no-welcome" 15 | ] 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/find-by.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isEmpty } from '@ember/utils'; 3 | import { get } from '@ember/object'; 4 | import asArray from '../utils/as-array.ts'; 5 | 6 | export function findBy([byPath, value, array]: [ 7 | K, 8 | T[K], 9 | T[], 10 | ]) { 11 | if (isEmpty(byPath)) { 12 | return undefined; 13 | } 14 | 15 | return asArray(array).find((item) => get(item, String(byPath)) === value); 16 | } 17 | 18 | export default helper(findBy); 19 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/-private/get-value-array-and-use-deep-equal-from-params.ts: -------------------------------------------------------------------------------- 1 | export default function getValueArrayAndUseDeepEqualFromParams( 2 | params: [T, boolean | T[], T[]?], 3 | ) { 4 | const currentValue = params[0]; 5 | 6 | let array; 7 | let useDeepEqual = false; 8 | if (params.length === 2) { 9 | array = params[1]; 10 | } else { 11 | useDeepEqual = params[1] as boolean; 12 | array = params[2] as T[]; 13 | } 14 | 15 | return { 16 | currentValue, 17 | array, 18 | useDeepEqual, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/object-at.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { A, isArray as isEmberArray } from '@ember/array'; 3 | 4 | export function objectAt(index: number | string, array: T[]) { 5 | if (!isEmberArray(array)) { 6 | return undefined; 7 | } 8 | 9 | index = parseInt(index as string, 10); 10 | 11 | return A(array).objectAt(index); 12 | } 13 | 14 | export default helper(function ([index, array]: [number, T[]]): 15 | | T 16 | | undefined { 17 | return objectAt(index, array); 18 | }); 19 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/flatten.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isArray as isEmberArray } from '@ember/array'; 3 | import asArray from '../utils/as-array.ts'; 4 | 5 | export function flatten(array: T[]): T[] { 6 | if (!isEmberArray(array)) { 7 | return array; 8 | } 9 | 10 | return asArray(array).reduce((flattened, el) => { 11 | return flattened.concat(flatten(el as T[])); 12 | }, []); 13 | } 14 | 15 | export default helper(function ([array]: [T[]]) { 16 | return flatten(array); 17 | }); 18 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/compute.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import type { AnyFn } from '../utils/types'; 3 | 4 | /** 5 | * @deprecated since ember 3.25 (with polyfill) and ember 4.5, this utility is not needed as functions can be directly invoked 6 | * 7 | * Calls a function 8 | */ 9 | export function compute([action, ...params]: [ 10 | action: Action, 11 | ...params: Parameters, 12 | ]): ReturnType { 13 | return action(...params); 14 | } 15 | 16 | export default helper(compute); 17 | -------------------------------------------------------------------------------- /test-app/tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import { run } from '@ember/runloop'; 2 | import Application from '../../app'; 3 | import config from '../../config/environment'; 4 | 5 | export default function startApp(attrs) { 6 | let attributes = Object.assign({}, config.APP); 7 | attributes = Object.assign(attributes, attrs); // use defaults, but you can override; 8 | 9 | return run(() => { 10 | let application = Application.create(attributes); 11 | application.setupForTesting(); 12 | application.injectTestHelpers(); 13 | return application; 14 | }); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /test-app/type-tests/compute-test.ts: -------------------------------------------------------------------------------- 1 | import { compute } from '@nullvoxpopuli/ember-composable-helpers/helpers/compute'; 2 | import { expectTypeOf } from 'expect-type' 3 | 4 | expectTypeOf(compute([(a: number, b: string) => true, 1, '2'])).toEqualTypeOf(); 5 | 6 | // @ts-expect-error -- missing all arguments 7 | compute([(a: number, b: string) => true]); 8 | 9 | // @ts-expect-error -- missing some arguments 10 | compute([(a: number, b: string) => true]); 11 | 12 | // @ts-expect-error -- incorrectly typed arguments 13 | compute([(a: number, b: string) => true, '1', 2]); 14 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import { run } from '@ember/runloop'; 2 | import Application from '../../app'; 3 | import config from '../../config/environment'; 4 | 5 | export default function startApp(attrs) { 6 | let attributes = Object.assign({}, config.APP); 7 | attributes = Object.assign(attributes, attrs); // use defaults, but you can override; 8 | 9 | return run(() => { 10 | let application = Application.create(attributes); 11 | application.setupForTesting(); 12 | application.injectTestHelpers(); 13 | return application; 14 | }); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import { run } from '@ember/runloop'; 2 | import Application from '../../app'; 3 | import config from '../../config/environment'; 4 | 5 | export default function startApp(attrs) { 6 | let attributes = Object.assign({}, config.APP); 7 | attributes = Object.assign(attributes, attrs); // use defaults, but you can override; 8 | 9 | return run(() => { 10 | let application = Application.create(attributes); 11 | application.setupForTesting(); 12 | application.injectTestHelpers(); 13 | return application; 14 | }); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/noop-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from "qunit"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { render, click } from "@ember/test-helpers"; 5 | 6 | module("Integration | Helper | {{noop}}", function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test("It successfully renders and does nothing when clicked", async function(assert) { 10 | assert.expect(0); 11 | await render(hbs` `); 12 | await click("button"); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/noop-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from "qunit"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { render, click } from "@ember/test-helpers"; 5 | 6 | module("Integration | Helper | {{noop}}", function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test("It successfully renders and does nothing when clicked", async function(assert) { 10 | assert.expect(0); 11 | await render(hbs` `); 12 | await click("button"); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/noop-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from "qunit"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { render, click } from "@ember/test-helpers"; 5 | 6 | module("Integration | Helper | {{noop}}", function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test("It successfully renders and does nothing when clicked", async function(assert) { 10 | assert.expect(0); 11 | await render(hbs` `); 12 | await click("button"); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/repeat.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { typeOf } from '@ember/utils'; 3 | 4 | export function repeat([ 5 | length, 6 | value, 7 | ]: Args): Args extends [number, infer T] ? T[] : undefined[]; 8 | export function repeat([length, value]: [number, T?] | [number]): Array< 9 | T | undefined 10 | > { 11 | if (typeOf(length) !== 'number') { 12 | return [value]; 13 | } 14 | return Array.apply(null, { length } as []).map(() => value); // eslint-disable-line 15 | } 16 | 17 | export default helper(repeat); 18 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const EmberApp = require("ember-cli/lib/broccoli/ember-app"); 4 | 5 | module.exports = function (defaults) { 6 | const app = new EmberApp(defaults, { 7 | // Add options here 8 | autoImport: { 9 | watchDependencies: ["ember-composable-helpers"], 10 | alias: { 11 | "ember-composable-helpers": "@nullvoxpopuli/ember-composable-helpers", 12 | }, 13 | webpack: { 14 | resolve: { 15 | alias: {}, 16 | }, 17 | }, 18 | }, 19 | name: "test-app", 20 | }); 21 | 22 | return app.toTree(); 23 | }; 24 | -------------------------------------------------------------------------------- /ember-composable-helpers/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "@babel/plugin-transform-typescript", 5 | { 6 | "allExtensions": true, 7 | "onlyRemoveTypeImports": true, 8 | "allowDeclareFields": true 9 | } 10 | ], 11 | "@embroider/addon-dev/template-colocation-plugin", 12 | [ 13 | "babel-plugin-ember-template-compilation", 14 | { 15 | "targetFormat": "hbs", 16 | "transforms": [] 17 | } 18 | ], 19 | [ 20 | "module:decorator-transforms", 21 | { "runtime": { "import": "decorator-transforms/runtime" } } 22 | ] 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /test-app/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /declarations/ 4 | 5 | # dependencies 6 | /node_modules/ 7 | 8 | # misc 9 | /.env* 10 | /.pnp* 11 | <<<<<<< HEAD 12 | /.sass-cache 13 | /connect.lock 14 | ||||||| parent of 837ac73 (v3.28.0...v5.8.0) 15 | /.sass-cache 16 | /.eslintcache 17 | /connect.lock 18 | ======= 19 | /.eslintcache 20 | >>>>>>> 837ac73 (v3.28.0...v5.8.0) 21 | /coverage/ 22 | /npm-debug.log* 23 | /testem.log 24 | /yarn-error.log 25 | 26 | # ember-try 27 | /.node_modules.ember-try/ 28 | /npm-shrinkwrap.json.ember-try 29 | /package.json.ember-try 30 | /package-lock.json.ember-try 31 | /yarn.lock.ember-try 32 | 33 | # broccoli-debug 34 | /DEBUG/ 35 | -------------------------------------------------------------------------------- /test-app/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-dev-shm-usage', 17 | '--disable-software-rasterizer', 18 | '--mute-audio', 19 | '--remote-debugging-port=0', 20 | '--window-size=1440,900' 21 | ].filter(Boolean) 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /test-app-min-supported/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-dev-shm-usage', 17 | '--disable-software-rasterizer', 18 | '--mute-audio', 19 | '--remote-debugging-port=0', 20 | '--window-size=1440,900' 21 | ].filter(Boolean) 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/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-dev-shm-usage', 17 | '--disable-software-rasterizer', 18 | '--mute-audio', 19 | '--remote-debugging-port=0', 20 | '--window-size=1440,900' 21 | ].filter(Boolean) 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /test-app/tests/helpers/module-for-acceptance.js: -------------------------------------------------------------------------------- 1 | import { Promise } from 'rsvp'; 2 | import { module } from 'qunit'; 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 Promise.resolve(afterEach).then(() => destroyApp(this.application)); 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/group-by.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { get } from '@ember/object'; 3 | import asArray from '../utils/as-array.ts'; 4 | 5 | export function groupBy([byPath, array]: [ 6 | K, 7 | T[], 8 | ]) { 9 | const groups: { [key: string]: T[] } = {}; 10 | 11 | asArray(array).forEach((item) => { 12 | const groupName = get(item, byPath) as string; 13 | let group = groups[groupName]; 14 | 15 | if (!Array.isArray(group)) { 16 | group = []; 17 | groups[groupName] = group; 18 | } 19 | 20 | group.push(item); 21 | }); 22 | 23 | return groups; 24 | } 25 | 26 | export default helper(groupBy); 27 | -------------------------------------------------------------------------------- /test-app/tests/unit/helpers/inc-test.js: -------------------------------------------------------------------------------- 1 | import { inc } from 'ember-composable-helpers/helpers/inc'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | inc', function() { 5 | test('it increments a value', function(assert) { 6 | let result = inc([1]); 7 | 8 | assert.strictEqual(result, 2, 'should equal 2'); 9 | }); 10 | 11 | test('it increments a value by step', function(assert) { 12 | let result = inc([1, 2]); 13 | 14 | assert.strictEqual(result, 3, 'should equal 3'); 15 | }); 16 | 17 | test('it increments a value by step float', function(assert) { 18 | let result = inc([1.5, 2]); 19 | 20 | assert.strictEqual(result, 3.5, 'should equal 3.5'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/helpers/module-for-acceptance.js: -------------------------------------------------------------------------------- 1 | import { Promise } from 'rsvp'; 2 | import { module } from 'qunit'; 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 Promise.resolve(afterEach).then(() => destroyApp(this.application)); 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /test-app/tests/unit/helpers/dec-test.js: -------------------------------------------------------------------------------- 1 | import { dec } from 'ember-composable-helpers/helpers/dec'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | dec', function() { 5 | test('it decrements a value', function(assert) { 6 | let result = dec([2]); 7 | 8 | assert.strictEqual(result, 1, 'should equal 1'); 9 | }); 10 | 11 | test('it decrements a value by step integer', function(assert) { 12 | let result = dec([2, 3]); 13 | 14 | assert.strictEqual(result, 1, 'should equal 1'); 15 | }); 16 | 17 | test('it decrements a value by step float', function(assert) { 18 | let result = dec([2.5, 3]); 19 | 20 | assert.strictEqual(result, 0.5, 'should equal 0.5'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/helpers/module-for-acceptance.js: -------------------------------------------------------------------------------- 1 | import { Promise } from 'rsvp'; 2 | import { module } from 'qunit'; 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 Promise.resolve(afterEach).then(() => destroyApp(this.application)); 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/helpers/inc-test.js: -------------------------------------------------------------------------------- 1 | import { inc } from 'ember-composable-helpers/helpers/inc'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | inc', function() { 5 | test('it increments a value', function(assert) { 6 | let result = inc([1]); 7 | 8 | assert.strictEqual(result, 2, 'should equal 2'); 9 | }); 10 | 11 | test('it increments a value by step', function(assert) { 12 | let result = inc([1, 2]); 13 | 14 | assert.strictEqual(result, 3, 'should equal 3'); 15 | }); 16 | 17 | test('it increments a value by step float', function(assert) { 18 | let result = inc([1.5, 2]); 19 | 20 | assert.strictEqual(result, 3.5, 'should equal 3.5'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test-app/tests/unit/helpers/next-test.js: -------------------------------------------------------------------------------- 1 | import { next } from 'ember-composable-helpers/helpers/next'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | next', function() { 5 | test('it returns the current value if it is the last element in the array', function(assert) { 6 | let result = next(4, [1, 2, 3, 4]); 7 | 8 | assert.strictEqual(result, 4, 'should return 4'); 9 | }); 10 | 11 | test('it returns `null` if the given value is not in the array', function(assert) { 12 | let result = next(5, [1, 2, 3, 4]); 13 | 14 | // eslint-disable-next-line qunit/no-assert-equal -- FIXME: This doesn't actually return null! 15 | assert.equal(result, null, 'should return `null`'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/helpers/inc-test.js: -------------------------------------------------------------------------------- 1 | import { inc } from 'ember-composable-helpers/helpers/inc'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | inc', function() { 5 | test('it increments a value', function(assert) { 6 | let result = inc([1]); 7 | 8 | assert.strictEqual(result, 2, 'should equal 2'); 9 | }); 10 | 11 | test('it increments a value by step', function(assert) { 12 | let result = inc([1, 2]); 13 | 14 | assert.strictEqual(result, 3, 'should equal 3'); 15 | }); 16 | 17 | test('it increments a value by step float', function(assert) { 18 | let result = inc([1.5, 2]); 19 | 20 | assert.strictEqual(result, 3.5, 'should equal 3.5'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/helpers/dec-test.js: -------------------------------------------------------------------------------- 1 | import { dec } from 'ember-composable-helpers/helpers/dec'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | dec', function() { 5 | test('it decrements a value', function(assert) { 6 | let result = dec([2]); 7 | 8 | assert.strictEqual(result, 1, 'should equal 1'); 9 | }); 10 | 11 | test('it decrements a value by step integer', function(assert) { 12 | let result = dec([2, 3]); 13 | 14 | assert.strictEqual(result, 1, 'should equal 1'); 15 | }); 16 | 17 | test('it decrements a value by step float', function(assert) { 18 | let result = dec([2.5, 3]); 19 | 20 | assert.strictEqual(result, 0.5, 'should equal 0.5'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test-app/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TestApp 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | 19 | {{content-for "body"}} 20 | 21 | 22 | 23 | 24 | {{content-for "body-footer"}} 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test-app/tests/unit/helpers/flatten-test.js: -------------------------------------------------------------------------------- 1 | import { flatten } from 'ember-composable-helpers/helpers/flatten'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | flatten', function() { 5 | test('it flattens a single level array', function(assert) { 6 | let result = flatten([42, 10]); 7 | 8 | assert.deepEqual(result, [42, 10]); 9 | }); 10 | 11 | test('it flattens a two-level array', function(assert) { 12 | let result = flatten([42, [10, 11]]); 13 | 14 | assert.deepEqual(result, [42, 10, 11]); 15 | }); 16 | 17 | test('it flattens a three-level array', function(assert) { 18 | let result = flatten([42, [10, [11]]]); 19 | 20 | assert.deepEqual(result, [42, 10, 11]); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/helpers/dec-test.js: -------------------------------------------------------------------------------- 1 | import { dec } from 'ember-composable-helpers/helpers/dec'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | dec', function() { 5 | test('it decrements a value', function(assert) { 6 | let result = dec([2]); 7 | 8 | assert.strictEqual(result, 1, 'should equal 1'); 9 | }); 10 | 11 | test('it decrements a value by step integer', function(assert) { 12 | let result = dec([2, 3]); 13 | 14 | assert.strictEqual(result, 1, 'should equal 1'); 15 | }); 16 | 17 | test('it decrements a value by step float', function(assert) { 18 | let result = dec([2.5, 3]); 19 | 20 | assert.strictEqual(result, 0.5, 'should equal 0.5'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/helpers/next-test.js: -------------------------------------------------------------------------------- 1 | import { next } from 'ember-composable-helpers/helpers/next'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | next', function() { 5 | test('it returns the current value if it is the last element in the array', function(assert) { 6 | let result = next(4, [1, 2, 3, 4]); 7 | 8 | assert.strictEqual(result, 4, 'should return 4'); 9 | }); 10 | 11 | test('it returns `null` if the given value is not in the array', function(assert) { 12 | let result = next(5, [1, 2, 3, 4]); 13 | 14 | // eslint-disable-next-line qunit/no-assert-equal -- FIXME: This doesn't actually return null! 15 | assert.equal(result, null, 'should return `null`'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/helpers/flatten-test.js: -------------------------------------------------------------------------------- 1 | import { flatten } from 'ember-composable-helpers/helpers/flatten'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | flatten', function() { 5 | test('it flattens a single level array', function(assert) { 6 | let result = flatten([42, 10]); 7 | 8 | assert.deepEqual(result, [42, 10]); 9 | }); 10 | 11 | test('it flattens a two-level array', function(assert) { 12 | let result = flatten([42, [10, 11]]); 13 | 14 | assert.deepEqual(result, [42, 10, 11]); 15 | }); 16 | 17 | test('it flattens a three-level array', function(assert) { 18 | let result = flatten([42, [10, [11]]]); 19 | 20 | assert.deepEqual(result, [42, 10, 11]); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/helpers/next-test.js: -------------------------------------------------------------------------------- 1 | import { next } from 'ember-composable-helpers/helpers/next'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | next', function() { 5 | test('it returns the current value if it is the last element in the array', function(assert) { 6 | let result = next(4, [1, 2, 3, 4]); 7 | 8 | assert.strictEqual(result, 4, 'should return 4'); 9 | }); 10 | 11 | test('it returns `null` if the given value is not in the array', function(assert) { 12 | let result = next(5, [1, 2, 3, 4]); 13 | 14 | // eslint-disable-next-line qunit/no-assert-equal -- FIXME: This doesn't actually return null! 15 | assert.equal(result, null, 'should return `null`'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test-app/tests/unit/helpers/previous-test.js: -------------------------------------------------------------------------------- 1 | import { previous } from 'ember-composable-helpers/helpers/previous'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | previous', function() { 5 | test('it returns the current value if it is the first element in the array', function(assert) { 6 | let result = previous(1, [1, 2, 3, 4]); 7 | 8 | assert.strictEqual(result, 1, 'should return 1'); 9 | }); 10 | 11 | test('it returns `null` if the given value is not in the array', function(assert) { 12 | let result = previous(5, [1, 2, 3, 4]); 13 | 14 | // eslint-disable-next-line qunit/no-assert-equal -- FIXME: This doesn't actually return null! 15 | assert.equal(result, null, 'should return `null`'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/helpers/flatten-test.js: -------------------------------------------------------------------------------- 1 | import { flatten } from 'ember-composable-helpers/helpers/flatten'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | flatten', function() { 5 | test('it flattens a single level array', function(assert) { 6 | let result = flatten([42, 10]); 7 | 8 | assert.deepEqual(result, [42, 10]); 9 | }); 10 | 11 | test('it flattens a two-level array', function(assert) { 12 | let result = flatten([42, [10, 11]]); 13 | 14 | assert.deepEqual(result, [42, 10, 11]); 15 | }); 16 | 17 | test('it flattens a three-level array', function(assert) { 18 | let result = flatten([42, [10, [11]]]); 19 | 20 | assert.deepEqual(result, [42, 10, 11]); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test-app-min-supported/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 | // Ember's browser support policy is changing, and IE11 support will end in 10 | // v4.0 onwards. 11 | // 12 | // See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy 13 | // 14 | // If you need IE11 support on a version of Ember that still offers support 15 | // for it, uncomment the code block below. 16 | // 17 | // const isCI = Boolean(process.env.CI); 18 | // const isProduction = process.env.EMBER_ENV === 'production'; 19 | // 20 | // if (isCI || isProduction) { 21 | // browsers.push('ie 11'); 22 | // } 23 | 24 | module.exports = { 25 | browsers, 26 | }; -------------------------------------------------------------------------------- /ember-composable-helpers/unpublished-development-types/index.d.ts: -------------------------------------------------------------------------------- 1 | // Add any types here that you need for local development only. 2 | // These will *not* be published as part of your addon, so be careful that your published code does not rely on them! 3 | 4 | import '@glint/environment-ember-loose'; 5 | import '@glint/environment-ember-template-imports'; 6 | 7 | // Uncomment if you need to support consuming projects in loose mode 8 | // 9 | // declare module '@glint/environment-ember-loose/registry' { 10 | // export default interface Registry { 11 | // // Add any registry entries from other addons here that your addon itself uses (in non-strict mode templates) 12 | // // See https://typed-ember.gitbook.io/glint/using-glint/ember/using-addons 13 | // } 14 | // } 15 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/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 | // Ember's browser support policy is changing, and IE11 support will end in 10 | // v4.0 onwards. 11 | // 12 | // See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy 13 | // 14 | // If you need IE11 support on a version of Ember that still offers support 15 | // for it, uncomment the code block below. 16 | // 17 | // const isCI = Boolean(process.env.CI); 18 | // const isProduction = process.env.EMBER_ENV === 'production'; 19 | // 20 | // if (isCI || isProduction) { 21 | // browsers.push('ie 11'); 22 | // } 23 | 24 | module.exports = { 25 | browsers, 26 | }; -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/helpers/previous-test.js: -------------------------------------------------------------------------------- 1 | import { previous } from 'ember-composable-helpers/helpers/previous'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | previous', function() { 5 | test('it returns the current value if it is the first element in the array', function(assert) { 6 | let result = previous(1, [1, 2, 3, 4]); 7 | 8 | assert.strictEqual(result, 1, 'should return 1'); 9 | }); 10 | 11 | test('it returns `null` if the given value is not in the array', function(assert) { 12 | let result = previous(5, [1, 2, 3, 4]); 13 | 14 | // eslint-disable-next-line qunit/no-assert-equal -- FIXME: This doesn't actually return null! 15 | assert.equal(result, null, 'should return `null`'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/compute-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{compute}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | hooks.beforeEach(function() { 10 | this.actions = {}; 11 | this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); 12 | }); 13 | 14 | test("It calls an action and returns it's value", async function(assert) { 15 | this.actions.square = (x) => x * x; 16 | await render(hbs`{{compute (action "square") 4}}`); 17 | 18 | assert.dom().hasText('16', '4 squared is 16'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/includes.ts: -------------------------------------------------------------------------------- 1 | import { isArray as isEmberArray } from '@ember/array'; 2 | import { helper } from '@ember/component/helper'; 3 | import asArray from '../utils/as-array.ts'; 4 | 5 | export function includes(needleOrNeedles: T | T[], haystack: T[]) { 6 | if (!isEmberArray(haystack)) { 7 | return false; 8 | } 9 | 10 | const needles = isEmberArray(needleOrNeedles) 11 | ? needleOrNeedles 12 | : [needleOrNeedles]; 13 | const haystackAsEmberArray = asArray(haystack); 14 | 15 | return asArray(needles as T[]).every((needle) => { 16 | return haystackAsEmberArray.includes(needle); 17 | }); 18 | } 19 | 20 | export default helper(function ([needle, haystack]: [T | T[], T[]]) { 21 | return includes(needle, haystack); 22 | }); 23 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/helpers/previous-test.js: -------------------------------------------------------------------------------- 1 | import { previous } from 'ember-composable-helpers/helpers/previous'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | previous', function() { 5 | test('it returns the current value if it is the first element in the array', function(assert) { 6 | let result = previous(1, [1, 2, 3, 4]); 7 | 8 | assert.strictEqual(result, 1, 'should return 1'); 9 | }); 10 | 11 | test('it returns `null` if the given value is not in the array', function(assert) { 12 | let result = previous(5, [1, 2, 3, 4]); 13 | 14 | // eslint-disable-next-line qunit/no-assert-equal -- FIXME: This doesn't actually return null! 15 | assert.equal(result, null, 'should return `null`'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/compute-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{compute}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | hooks.beforeEach(function() { 10 | this.actions = {}; 11 | this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); 12 | }); 13 | 14 | test("It calls an action and returns it's value", async function(assert) { 15 | this.actions.square = (x) => x * x; 16 | await render(hbs`{{compute (action "square") 4}}`); 17 | 18 | assert.dom().hasText('16', '4 squared is 16'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test-app/type-tests/call-test.ts: -------------------------------------------------------------------------------- 1 | import { call } from '@nullvoxpopuli/ember-composable-helpers/helpers/call'; 2 | import { expectTypeOf } from 'expect-type' 3 | 4 | expectTypeOf(call([() => true])).toEqualTypeOf(); 5 | 6 | // @ts-expect-error Does not allow function with arguments 7 | call([(a: number, b: string) => true]); 8 | 9 | // Allows optional arguments 10 | call([(a?: number) => true]); 11 | 12 | type ThisArg = { a: number }; 13 | 14 | // Allows a `this` argument 15 | const thisArg: ThisArg = { a: 1 }; 16 | call([() => true, thisArg]); 17 | 18 | // `thisArg` matches the type 19 | call([function (this: ThisArg) { return true; }, thisArg]); 20 | 21 | // @ts-expect-error -- thisArg is not the correct type 22 | call([function (this: ThisArg) { return true; }, { a: '1' }]); 23 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/compute-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{compute}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | hooks.beforeEach(function() { 10 | this.actions = {}; 11 | this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); 12 | }); 13 | 14 | test("It calls an action and returns it's value", async function(assert) { 15 | this.actions.square = (x) => x * x; 16 | await render(hbs`{{compute (action "square") 4}}`); 17 | 18 | assert.dom().hasText('16', '4 squared is 16'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/without.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { A as emberArray, isArray as isEmberArray } from '@ember/array'; 3 | 4 | function contains(needle: T, haystack: T[]) { 5 | return emberArray(haystack).includes(needle); 6 | } 7 | 8 | export function without(needle: T, haystack: T[]) { 9 | if (!isEmberArray(haystack)) { 10 | return false; 11 | } 12 | 13 | if (isEmberArray(needle) && needle.length) { 14 | return haystack.reduce((acc, val) => { 15 | return contains(val, needle as T[]) ? acc : acc.concat(val); 16 | }, []); 17 | } 18 | 19 | return emberArray(haystack).without(needle); 20 | } 21 | 22 | export default helper(function ([needle, haystack]: [T | T[], T[]]) { 23 | return without(needle, haystack); 24 | }); 25 | -------------------------------------------------------------------------------- /test-app-min-supported/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 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/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 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/toggle-action-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, click } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{toggle-action}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it can be used as a closure action', async function(assert) { 10 | this.set('isExpanded', false); 11 | 12 | await render(hbs` 13 |

{{if this.isExpanded "I am expanded" "I am not"}}

14 | 17 | `); 18 | 19 | await click('button'); 20 | 21 | assert.dom('p').hasText('I am expanded', 'should be expanded'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/toggle-action-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, click } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{toggle-action}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it can be used as a closure action', async function(assert) { 10 | this.set('isExpanded', false); 11 | 12 | await render(hbs` 13 |

{{if this.isExpanded "I am expanded" "I am not"}}

14 | 17 | `); 18 | 19 | await click('button'); 20 | 21 | assert.dom('p').hasText('I am expanded', 'should be expanded'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/toggle-action-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, click } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{toggle-action}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it can be used as a closure action', async function(assert) { 10 | this.set('isExpanded', false); 11 | 12 | await render(hbs` 13 |

{{if this.isExpanded "I am expanded" "I am not"}}

14 | 17 | `); 18 | 19 | await click('button'); 20 | 21 | assert.dom('p').hasText('I am expanded', 'should be expanded'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | #################### 2 | # super strict mode 3 | #################### 4 | auto-install-peers=true 5 | strict-peer-dependents=true 6 | resolve-peers-from-workspace-root=false 7 | 8 | ################ 9 | # Optimizations 10 | ################ 11 | # Less strict, but required for tooling to not barf on duplicate peer trees. 12 | # (many libraries declare the same peers, which resolve to the same 13 | # versions) 14 | dedupe-peer-dependents=true 15 | public-hoist-pattern[]=ember-source 16 | 17 | ################ 18 | # Compatibility 19 | ################ 20 | # highest is what everyone is used to, but 21 | # not ensuring folks are actually compatible with declared ranges. 22 | resolution-mode=highest 23 | 24 | ################ 25 | # Misc 26 | ################ 27 | verify-deps-before-run=install # always verify deps before running any scripts 28 | -------------------------------------------------------------------------------- /test-app/type-tests/optional-test.ts: -------------------------------------------------------------------------------- 1 | import { optional } from '@nullvoxpopuli/ember-composable-helpers/helpers/optional'; 2 | import { expectTypeOf } from 'expect-type' 3 | 4 | expectTypeOf(optional([() => true])).toEqualTypeOf<(() => boolean)>(); 5 | 6 | expectTypeOf(optional([])).toEqualTypeOf<(arg: Arg) => Arg>(); 7 | 8 | declare const maybeFn: ((a: number) => string) | undefined; 9 | 10 | expectTypeOf(optional([maybeFn])).toEqualTypeOf<((a: number) => string) | ((arg: Arg) => Arg)>(); 11 | 12 | function takesFn(fn: (a: number) => number) { 13 | return fn(1); 14 | } 15 | 16 | takesFn(optional([])); 17 | 18 | function invalidTakesFn(fn: (a: number) => string) { 19 | return fn(1); 20 | } 21 | 22 | // @ts-expect-error -- optional returns a function that returns the same type as the argument 23 | invalidTakesFn(optional([])); 24 | 25 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/-private/closure-action.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Note that this file *at all* is deprecated in ember-source v6, 3 | * and will not work in v7 as this import is being removed in v7. 4 | * Do not use closure-action with ember-composable-helpers. 5 | */ 6 | import Ember from 'ember'; 7 | 8 | const { __loader } = Ember; 9 | 10 | let ClosureActionModule = { ACTION: null }; 11 | 12 | if (__loader && 'ember-htmlbars/keywords/closure-action' in __loader.registry) { 13 | ClosureActionModule = __loader.require( 14 | 'ember-htmlbars/keywords/closure-action', 15 | ); 16 | } else if ( 17 | __loader && 18 | 'ember-routing-htmlbars/keywords/closure-action' in __loader.registry 19 | ) { 20 | ClosureActionModule = __loader.require( 21 | 'ember-routing-htmlbars/keywords/closure-action', 22 | ); 23 | } 24 | 25 | export default ClosureActionModule.ACTION; 26 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/pipe.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import isPromise from '../utils/is-promise.ts'; 3 | 4 | export function invokeFunction(acc: T, curr: (...args: unknown[]) => void) { 5 | if (isPromise(acc)) { 6 | return acc.then(curr); 7 | } 8 | 9 | return curr(acc); 10 | } 11 | 12 | export function pipe(actions: [...((...args1: unknown[]) => unknown)[]] = []) { 13 | return function (...args: [...unknown[]]) { 14 | // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents 15 | return actions.reduce>((acc, curr, idx) => { 16 | if (idx === 0) { 17 | return curr(...args); 18 | } 19 | 20 | return invokeFunction(acc, curr); 21 | }, undefined); 22 | }; 23 | } 24 | 25 | export default helper(pipe as (actions: unknown[]) => unknown); 26 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/range.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { typeOf } from '@ember/utils'; 3 | import { gte, lte, gt, lt } from '../utils/comparison.ts'; 4 | 5 | export function range([min, max, isInclusive]: [number, number, boolean?]) { 6 | isInclusive = typeOf(isInclusive) === 'boolean' ? isInclusive : false; 7 | const numbers: number[] = []; 8 | 9 | if (min < max) { 10 | const testFn = isInclusive ? lte : lt; 11 | for (let i = min; testFn(i, max); i++) { 12 | numbers.push(i); 13 | } 14 | } 15 | 16 | if (min > max) { 17 | const testFn = isInclusive ? gte : gt; 18 | for (let i = min; testFn(i, max); i--) { 19 | numbers.push(i); 20 | } 21 | } 22 | 23 | if (min === max && isInclusive) { 24 | numbers.push(max); 25 | } 26 | 27 | return numbers; 28 | } 29 | 30 | export default helper(range); 31 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/previous.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import getIndex from '../utils/get-index.ts'; 3 | import { isEmpty } from '@ember/utils'; 4 | import getValueArrayAndUseDeepEqualFromParams from '../-private/get-value-array-and-use-deep-equal-from-params.ts'; 5 | 6 | export function previous(currentValue: T, array: T[], useDeepEqual = false) { 7 | const currentIndex = getIndex(array, currentValue, useDeepEqual); 8 | 9 | if (null === currentIndex || isEmpty(currentIndex)) { 10 | return; 11 | } 12 | 13 | return currentIndex === 0 ? currentValue : array.at(currentIndex - 1); 14 | } 15 | 16 | export default helper(function (params: [T, boolean | T[], T[]?]) { 17 | const { currentValue, array, useDeepEqual } = 18 | getValueArrayAndUseDeepEqualFromParams(params); 19 | 20 | return previous(currentValue, array as T[], useDeepEqual); 21 | }); 22 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/repeat-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{repeat}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it repeats `n` times', async function(assert) { 10 | await render(hbs` 11 | {{~#each (repeat 3)~}} 12 | 1 13 | {{~/each~}} 14 | `); 15 | 16 | assert.dom().hasText('111', 'should repeat 3 times'); 17 | }); 18 | 19 | test('it repeats `n` times with a value', async function(assert) { 20 | this.set('person', { name: 'Adam' }); 21 | await render(hbs` 22 | {{~#each (repeat 3 this.person) as |person|~}} 23 | {{~person.name~}} 24 | {{~/each~}} 25 | `); 26 | 27 | assert.dom().hasText('AdamAdamAdam', 'should repeat 3 times with a value'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test-app/tests/unit/helpers/toggle-test.js: -------------------------------------------------------------------------------- 1 | import { toggle } from 'ember-composable-helpers/helpers/toggle'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | toggle', function() { 5 | test('it toggles the property', function(assert) { 6 | let jimBob = { isAlive: false }; 7 | let action = toggle(['isAlive', jimBob]); 8 | action(); 9 | 10 | assert.true(jimBob.isAlive, 'should be true'); 11 | }); 12 | 13 | test('it correctly toggles non-boolean falsey values', function(assert) { 14 | let jimBob = { isAlive: undefined }; 15 | let action = toggle(['isAlive', jimBob]); 16 | action(); 17 | 18 | assert.true(jimBob.isAlive, 'should be true'); 19 | }); 20 | 21 | test('it correctly toggles non-boolean truthy values', function(assert) { 22 | let jimBob = { isAlive: {} }; 23 | let action = toggle(['isAlive', jimBob]); 24 | action(); 25 | 26 | assert.false(jimBob.isAlive, 'should be false'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test-app-min-supported/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const EmberApp = require("ember-cli/lib/broccoli/ember-app"); 4 | 5 | module.exports = function (defaults) { 6 | const app = new EmberApp(defaults, { 7 | // Add options here 8 | autoImport: { 9 | watchDependencies: ["ember-composable-helpers"], 10 | }, 11 | name: "test-app", 12 | }); 13 | 14 | const { Webpack } = require("@embroider/webpack"); 15 | return require("@embroider/compat").compatBuild(app, Webpack, { 16 | staticAddonTestSupportTrees: true, 17 | staticAddonTrees: true, 18 | staticHelpers: true, 19 | staticModifiers: true, 20 | staticComponents: true, 21 | staticEmberSource: true, 22 | packagerOptions: { 23 | webpackConfig: { 24 | resolve: { 25 | alias: { 26 | "ember-composable-helpers": 27 | "@nullvoxpopuli/ember-composable-helpers", 28 | }, 29 | }, 30 | }, 31 | }, 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/repeat-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{repeat}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it repeats `n` times', async function(assert) { 10 | await render(hbs` 11 | {{~#each (repeat 3) as |empty|~}} 12 | 1 13 | {{~/each~}} 14 | `); 15 | 16 | assert.dom().hasText('111', 'should repeat 3 times'); 17 | }); 18 | 19 | test('it repeats `n` times with a value', async function(assert) { 20 | this.set('person', { name: 'Adam' }); 21 | await render(hbs` 22 | {{~#each (repeat 3 this.person) as |person|~}} 23 | {{~person.name~}} 24 | {{~/each~}} 25 | `); 26 | 27 | assert.dom().hasText('AdamAdamAdam', 'should repeat 3 times with a value'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/chunk.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isArray as isEmberArray } from '@ember/array'; 3 | const { max, ceil } = Math; 4 | import asArray from '../utils/as-array.ts'; 5 | 6 | export function chunk(num: number | string, array: T[]) { 7 | const integer = parseInt(num as string, 10); 8 | const size = max(integer, 0); 9 | 10 | let length = 0; 11 | if (isEmberArray(array)) { 12 | length = array.length; 13 | } 14 | 15 | array = asArray(array); 16 | 17 | if (!length || size < 1) { 18 | return []; 19 | } else { 20 | let index = 0; 21 | let resultIndex = -1; 22 | const result = new Array(ceil(length / size)); 23 | 24 | while (index < length) { 25 | result[++resultIndex] = array.slice(index, (index += size)); 26 | } 27 | 28 | return result; 29 | } 30 | } 31 | 32 | export default helper(function ([num, array]: [number | string, T[]]) { 33 | return chunk(num, array); 34 | }); 35 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/repeat-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{repeat}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it repeats `n` times', async function(assert) { 10 | await render(hbs` 11 | {{~#each (repeat 3) as |empty|~}} 12 | 1 13 | {{~/each~}} 14 | `); 15 | 16 | assert.dom().hasText('111', 'should repeat 3 times'); 17 | }); 18 | 19 | test('it repeats `n` times with a value', async function(assert) { 20 | this.set('person', { name: 'Adam' }); 21 | await render(hbs` 22 | {{~#each (repeat 3 this.person) as |person|~}} 23 | {{~person.name~}} 24 | {{~/each~}} 25 | `); 26 | 27 | assert.dom().hasText('AdamAdamAdam', 'should repeat 3 times with a value'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/invoke.ts: -------------------------------------------------------------------------------- 1 | import { isArray as isEmberArray } from '@ember/array'; 2 | import { helper } from '@ember/component/helper'; 3 | import RSVP from 'rsvp'; 4 | import type { AnyVoidFn } from '../utils/types'; 5 | 6 | const { all } = RSVP; 7 | 8 | /** 9 | * @deprecated since ember 3.25 (with polyfill) and ember 4.5, this utility is not needed as functions can be directly invoked 10 | * 11 | * Invokes a method on an object, or on each object of an array 12 | */ 13 | export function invoke>([ 14 | methodName, 15 | ...args 16 | ]: [K, T, ...unknown[]]) { 17 | const obj = args.pop() as T | T[]; 18 | 19 | if (isEmberArray(obj)) { 20 | return function () { 21 | const promises = obj.map((item) => item[methodName]?.(...args)); 22 | 23 | return all(promises); 24 | }; 25 | } 26 | 27 | return function () { 28 | return obj[methodName]?.(...args); 29 | }; 30 | } 31 | 32 | export default helper(invoke); 33 | -------------------------------------------------------------------------------- /test-app/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const EmberApp = require("ember-cli/lib/broccoli/ember-app"); 4 | 5 | module.exports = function (defaults) { 6 | const app = new EmberApp(defaults, { 7 | 'ember-cli-babel': { enableTypeScriptTransform: true }, 8 | // Add options here 9 | autoImport: { 10 | watchDependencies: ["ember-composable-helpers"], 11 | }, 12 | }); 13 | 14 | const { Webpack } = require("@embroider/webpack"); 15 | return require("@embroider/compat").compatBuild(app, Webpack, { 16 | staticAddonTestSupportTrees: true, 17 | staticAddonTrees: true, 18 | staticHelpers: true, 19 | staticModifiers: true, 20 | staticComponents: true, 21 | staticEmberSource: true, 22 | packagerOptions: { 23 | webpackConfig: { 24 | resolve: { 25 | alias: { 26 | "ember-composable-helpers": 27 | "@nullvoxpopuli/ember-composable-helpers", 28 | }, 29 | }, 30 | }, 31 | }, 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /test-app/tests/unit/helpers/repeat-test.js: -------------------------------------------------------------------------------- 1 | import { repeat } from 'ember-composable-helpers/helpers/repeat'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | repeat', function() { 5 | test('it repeats `n` times', function(assert) { 6 | let expectedResult = [undefined, undefined, undefined]; 7 | let result = repeat([3]); 8 | 9 | assert.deepEqual(result, expectedResult, 'should return an array of `undefined` values'); 10 | }); 11 | 12 | test('it repeats `n` times with a value', function(assert) { 13 | let expectedResult = ['foo', 'foo', 'foo']; 14 | let result = repeat([3, 'foo']); 15 | 16 | assert.deepEqual(result, expectedResult, 'should return an array of "foo" values'); 17 | }); 18 | 19 | test('it handles non-number length', function(assert) { 20 | let expectedResult = [undefined]; 21 | let result = repeat(['foo']); 22 | 23 | assert.deepEqual(result, expectedResult, 'should return array containing non-number length'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/toggle.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { get } from '@ember/object'; 3 | import { set } from '@ember/object'; 4 | import { isPresent } from '@ember/utils'; 5 | 6 | function nextIndex(length: number, currentIdx: number) { 7 | if (currentIdx === -1 || currentIdx + 1 === length) { 8 | return 0; 9 | } 10 | 11 | return currentIdx + 1; 12 | } 13 | 14 | export function toggle([prop, obj, ...values]: [ 15 | keyof T, 16 | T, 17 | ...T[keyof T][], 18 | ]) { 19 | return function () { 20 | const currentValue = get(obj as object, prop as string); 21 | 22 | if (isPresent(values)) { 23 | const currentIdx = values.indexOf(currentValue as T[keyof T]); 24 | const nextIdx = nextIndex(values.length, currentIdx); 25 | 26 | return set(obj as object, prop as string, values[nextIdx]!); 27 | } 28 | 29 | return set(obj as object, prop as string, !currentValue); 30 | }; 31 | } 32 | 33 | export default helper(toggle); 34 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/helpers/repeat-test.js: -------------------------------------------------------------------------------- 1 | import { repeat } from 'ember-composable-helpers/helpers/repeat'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | repeat', function() { 5 | test('it repeats `n` times', function(assert) { 6 | let expectedResult = [undefined, undefined, undefined]; 7 | let result = repeat([3]); 8 | 9 | assert.deepEqual(result, expectedResult, 'should return an array of `undefined` values'); 10 | }); 11 | 12 | test('it repeats `n` times with a value', function(assert) { 13 | let expectedResult = ['foo', 'foo', 'foo']; 14 | let result = repeat([3, 'foo']); 15 | 16 | assert.deepEqual(result, expectedResult, 'should return an array of "foo" values'); 17 | }); 18 | 19 | test('it handles non-number length', function(assert) { 20 | let expectedResult = [undefined]; 21 | let result = repeat(['foo']); 22 | 23 | assert.deepEqual(result, expectedResult, 'should return array containing non-number length'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/has-next.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isPresent } from '@ember/utils'; 3 | import { next } from './next.ts'; 4 | import isEqual from '../utils/is-equal.ts'; 5 | import getValueArrayAndUseDeepEqualFromParams from '../-private/get-value-array-and-use-deep-equal-from-params.ts'; 6 | import asArray from '../utils/as-array.ts'; 7 | 8 | export function hasNext( 9 | currentValue: T, 10 | maybeArray: T[], 11 | useDeepEqual = false, 12 | ) { 13 | const array = asArray(maybeArray); 14 | const nextValue = next(currentValue, array, useDeepEqual); 15 | const isNotSameValue = !isEqual(nextValue, currentValue, useDeepEqual); 16 | 17 | return isNotSameValue && isPresent(nextValue); 18 | } 19 | 20 | export default helper(function (params: [T, boolean | T[], T[]?]) { 21 | const { currentValue, array, useDeepEqual } = 22 | getValueArrayAndUseDeepEqualFromParams(params); 23 | 24 | return hasNext(currentValue, array as T[], useDeepEqual); 25 | }); 26 | -------------------------------------------------------------------------------- /test-app/tests/unit/utils/is-object-test.js: -------------------------------------------------------------------------------- 1 | import EmberObject from '@ember/object'; 2 | import ObjectProxy from '@ember/object/proxy'; 3 | import isObject from 'ember-composable-helpers/utils/is-object'; 4 | import { module, test } from 'qunit'; 5 | 6 | module('Unit | Utility | is object', function() { 7 | let testData = [ 8 | { 9 | label: 'POJOs', 10 | value: { foo: 'bar' }, 11 | expected: true 12 | }, 13 | { 14 | label: 'EmberObjects', 15 | value: EmberObject.create({ foo: 'bar' }), 16 | expected: true 17 | }, 18 | { 19 | label: 'ObjectProxies', 20 | value: ObjectProxy.create({ 21 | content: EmberObject.create({ foo: 'bar' }) 22 | }), 23 | expected: true 24 | } 25 | ]; 26 | 27 | testData.forEach(({ label, value, expected }) => { 28 | test(`it works with ${label}`, function(assert) { 29 | let result = isObject(value); 30 | assert.strictEqual(result, expected, `should be ${expected}`); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/optional.ts: -------------------------------------------------------------------------------- 1 | import type { AnyFn } from '../utils/types'; 2 | 3 | export function optional([action]: [fn: Fn]): Fn; 4 | export function optional([action]: [fn: Fn | undefined]): 5 | | Fn 6 | | ((arg: Arg) => Arg); 7 | export function optional([action]: [fn?: undefined]): (arg: Arg) => Arg; 8 | export function optional([action]: [fn?: Fn | undefined]): 9 | | Fn 10 | | ((arg: Arg) => Arg) { 11 | if (typeof action === 'function') { 12 | return action; 13 | } 14 | 15 | return (i: Arg) => i; 16 | } 17 | 18 | export default function optionalHelper(action: Fn): Fn; 19 | export default function optionalHelper( 20 | action: Fn | undefined, 21 | ): Fn | ((arg: Arg) => Arg); 22 | export default function optionalHelper( 23 | action?: undefined, 24 | ): (arg: Arg) => Arg; 25 | export default function optionalHelper(action?: AnyFn) { 26 | return optional([action]); 27 | } 28 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/helpers/repeat-test.js: -------------------------------------------------------------------------------- 1 | import { repeat } from 'ember-composable-helpers/helpers/repeat'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | repeat', function() { 5 | test('it repeats `n` times', function(assert) { 6 | let expectedResult = [undefined, undefined, undefined]; 7 | let result = repeat([3]); 8 | 9 | assert.deepEqual(result, expectedResult, 'should return an array of `undefined` values'); 10 | }); 11 | 12 | test('it repeats `n` times with a value', function(assert) { 13 | let expectedResult = ['foo', 'foo', 'foo']; 14 | let result = repeat([3, 'foo']); 15 | 16 | assert.deepEqual(result, expectedResult, 'should return an array of "foo" values'); 17 | }); 18 | 19 | test('it handles non-number length', function(assert) { 20 | let expectedResult = [undefined]; 21 | let result = repeat(['foo']); 22 | 23 | assert.deepEqual(result, expectedResult, 'should return array containing non-number length'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/pipe-action-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, click } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{pipe-action}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it can be used as a closure action', async function(assert) { 10 | let value = 0; 11 | 12 | this.add = (x, y) => x + y; 13 | this.square = (x) => x * x; 14 | this.squareRoot = (x) => value = Math.sqrt(x); 15 | 16 | await render(hbs` 17 | {{#let (pipe-action this.add this.square this.squareRoot) as |calculate|}} 18 | 21 | {{/let}} 22 | `); 23 | 24 | assert.strictEqual(value, 0, 'precond - should return 0'); 25 | 26 | await click('button'); 27 | 28 | assert.strictEqual(value, 6, 'should return 6'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/utils/is-object-test.js: -------------------------------------------------------------------------------- 1 | import EmberObject from '@ember/object'; 2 | import ObjectProxy from '@ember/object/proxy'; 3 | import isObject from 'ember-composable-helpers/utils/is-object'; 4 | import { module, test } from 'qunit'; 5 | 6 | module('Unit | Utility | is object', function() { 7 | let testData = [ 8 | { 9 | label: 'POJOs', 10 | value: { foo: 'bar' }, 11 | expected: true 12 | }, 13 | { 14 | label: 'EmberObjects', 15 | value: EmberObject.create({ foo: 'bar' }), 16 | expected: true 17 | }, 18 | { 19 | label: 'ObjectProxies', 20 | value: ObjectProxy.create({ 21 | content: EmberObject.create({ foo: 'bar' }) 22 | }), 23 | expected: true 24 | } 25 | ]; 26 | 27 | testData.forEach(({ label, value, expected }) => { 28 | test(`it works with ${label}`, function(assert) { 29 | let result = isObject(value); 30 | assert.strictEqual(result, expected, `should be ${expected}`); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/pipe-action-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, click } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{pipe-action}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it can be used as a closure action', async function(assert) { 10 | let value = 0; 11 | 12 | this.add = (x, y) => x + y; 13 | this.square = (x) => x * x; 14 | this.squareRoot = (x) => value = Math.sqrt(x); 15 | 16 | await render(hbs` 17 | {{#let (pipe-action this.add this.square this.squareRoot) as |calculate|}} 18 | 21 | {{/let}} 22 | `); 23 | 24 | assert.strictEqual(value, 0, 'precond - should return 0'); 25 | 26 | await click('button'); 27 | 28 | assert.strictEqual(value, 6, 'should return 6'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/shuffle.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isArray as isEmberArray } from '@ember/array'; 3 | import { typeOf } from '@ember/utils'; 4 | 5 | export function shuffle(array: T[], randomizer?: () => number) { 6 | array = array.slice(0); 7 | let count = array.length; 8 | let rand, temp; 9 | randomizer = (typeOf(randomizer) === 'function' && randomizer) || Math.random; 10 | 11 | while (count > 1) { 12 | rand = Math.floor(randomizer() * count--); 13 | 14 | temp = array[count]; 15 | array[count] = array[rand]!; 16 | array[rand] = temp!; 17 | } 18 | return array; 19 | } 20 | 21 | export default helper(function ([randomizer, array]: [ 22 | (() => number) | T[] | undefined, 23 | T[]?, 24 | ]) { 25 | if (array === undefined) { 26 | array = randomizer as T[]; 27 | randomizer = undefined; 28 | } 29 | 30 | if (!isEmberArray(array)) { 31 | return [array]; 32 | } 33 | 34 | return shuffle(array, randomizer as () => number); 35 | }); 36 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/pipe-action-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, click } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{pipe-action}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it can be used as a closure action', async function(assert) { 10 | let value = 0; 11 | 12 | this.add = (x, y) => x + y; 13 | this.square = (x) => x * x; 14 | this.squareRoot = (x) => value = Math.sqrt(x); 15 | 16 | await render(hbs` 17 | {{#let (pipe-action this.add this.square this.squareRoot) as |calculate|}} 18 | 21 | {{/let}} 22 | `); 23 | 24 | assert.strictEqual(value, 0, 'precond - should return 0'); 25 | 26 | await click('button'); 27 | 28 | assert.strictEqual(value, 6, 'should return 6'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/utils/is-object-test.js: -------------------------------------------------------------------------------- 1 | import EmberObject from '@ember/object'; 2 | import ObjectProxy from '@ember/object/proxy'; 3 | import isObject from 'ember-composable-helpers/utils/is-object'; 4 | import { module, test } from 'qunit'; 5 | 6 | module('Unit | Utility | is object', function() { 7 | let testData = [ 8 | { 9 | label: 'POJOs', 10 | value: { foo: 'bar' }, 11 | expected: true 12 | }, 13 | { 14 | label: 'EmberObjects', 15 | value: EmberObject.create({ foo: 'bar' }), 16 | expected: true 17 | }, 18 | { 19 | label: 'ObjectProxies', 20 | value: ObjectProxy.create({ 21 | content: EmberObject.create({ foo: 'bar' }) 22 | }), 23 | expected: true 24 | } 25 | ]; 26 | 27 | testData.forEach(({ label, value, expected }) => { 28 | test(`it works with ${label}`, function(assert) { 29 | let result = isObject(value); 30 | assert.strictEqual(result, expected, `should be ${expected}`); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test-app/tests/unit/helpers/without-test.js: -------------------------------------------------------------------------------- 1 | import { without } from 'ember-composable-helpers/helpers/without'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | without', function() { 5 | test('it returns the array with the given value ommitted', function(assert) { 6 | let expectedResult = [42, 56]; 7 | let result = without(78, [42, 56, 78]); 8 | 9 | assert.deepEqual(result, expectedResult, 'should return the remaining values'); 10 | }); 11 | 12 | test('it returns the array with the given values ommitted', function(assert) { 13 | let expectedResult = [78]; 14 | let result = without([42, 56], [42, 56, 78]); 15 | 16 | assert.deepEqual(result, expectedResult, 'should return the remaining values'); 17 | }); 18 | 19 | test('it returns the same array when no values are ommitted', function(assert) { 20 | let expectedResult = [42, 56, 78]; 21 | let result = without('foo', [42, 56, 78]); 22 | 23 | assert.deepEqual(result, expectedResult, 'should return the same array'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/has-previous.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isPresent } from '@ember/utils'; 3 | import { previous } from './previous.ts'; 4 | import isEqual from '../utils/is-equal.ts'; 5 | import getValueArrayAndUseDeepEqualFromParams from '../-private/get-value-array-and-use-deep-equal-from-params.ts'; 6 | import asArray from '../utils/as-array.ts'; 7 | 8 | export function hasPrevious( 9 | currentValue: T, 10 | maybeArray: T[], 11 | useDeepEqual = false, 12 | ) { 13 | const array = asArray(maybeArray); 14 | const previousValue = previous(currentValue, array, useDeepEqual); 15 | const isNotSameValue = !isEqual(previousValue, currentValue, useDeepEqual); 16 | 17 | return isNotSameValue && isPresent(previousValue); 18 | } 19 | 20 | export default helper(function (params: [T, boolean | T[], T[]?]) { 21 | const { currentValue, array, useDeepEqual } = 22 | getValueArrayAndUseDeepEqualFromParams(params); 23 | 24 | return hasPrevious(currentValue, array as T[], useDeepEqual); 25 | }); 26 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/helpers/without-test.js: -------------------------------------------------------------------------------- 1 | import { without } from 'ember-composable-helpers/helpers/without'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | without', function() { 5 | test('it returns the array with the given value ommitted', function(assert) { 6 | let expectedResult = [42, 56]; 7 | let result = without(78, [42, 56, 78]); 8 | 9 | assert.deepEqual(result, expectedResult, 'should return the remaining values'); 10 | }); 11 | 12 | test('it returns the array with the given values ommitted', function(assert) { 13 | let expectedResult = [78]; 14 | let result = without([42, 56], [42, 56, 78]); 15 | 16 | assert.deepEqual(result, expectedResult, 'should return the remaining values'); 17 | }); 18 | 19 | test('it returns the same array when no values are ommitted', function(assert) { 20 | let expectedResult = [42, 56, 78]; 21 | let result = without('foo', [42, 56, 78]); 22 | 23 | assert.deepEqual(result, expectedResult, 'should return the same array'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/values-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | values', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it returns object values', async function(assert) { 10 | this.set('inputValue', {a: 1, b: 2}); 11 | 12 | await render(hbs`{{#each (values this.inputValue) as |v|}}{{v}}{{/each}}`); 13 | 14 | assert.dom(this.element).hasText('12'); 15 | }); 16 | 17 | test('it handles undefined input', async function(assert) { 18 | await render(hbs` 19 | {{#each (values undefined) as |v|}}{{v}}{{/each}} 20 | `); 21 | 22 | assert.dom(this.element).hasText(''); 23 | }); 24 | 25 | test('it handles null input', async function(assert) { 26 | await render(hbs` 27 | {{#each (values null) as |v|}}{{v}}{{/each}} 28 | `); 29 | 30 | assert.dom(this.element).hasText(''); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/next.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import getIndex from '../utils/get-index.ts'; 3 | import { isEmpty } from '@ember/utils'; 4 | import getValueArrayAndUseDeepEqualFromParams from '../-private/get-value-array-and-use-deep-equal-from-params.ts'; 5 | import asArray from '../utils/as-array.ts'; 6 | 7 | export function next( 8 | currentValue: T, 9 | maybeArray: T[], 10 | useDeepEqual = false, 11 | ) { 12 | const array = asArray(maybeArray); 13 | const currentIndex = getIndex(array, currentValue, useDeepEqual); 14 | const lastIndex = array.length - 1; 15 | 16 | if (null === currentIndex || isEmpty(currentIndex)) { 17 | return; 18 | } 19 | 20 | return currentIndex === lastIndex ? currentValue : array.at(currentIndex + 1); 21 | } 22 | 23 | export default helper(function (params: [T, boolean | T[], T[]?]) { 24 | const { currentValue, array, useDeepEqual } = 25 | getValueArrayAndUseDeepEqualFromParams(params); 26 | 27 | return next(currentValue, array as T[], useDeepEqual); 28 | }); 29 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/helpers/without-test.js: -------------------------------------------------------------------------------- 1 | import { without } from 'ember-composable-helpers/helpers/without'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | without', function() { 5 | test('it returns the array with the given value ommitted', function(assert) { 6 | let expectedResult = [42, 56]; 7 | let result = without(78, [42, 56, 78]); 8 | 9 | assert.deepEqual(result, expectedResult, 'should return the remaining values'); 10 | }); 11 | 12 | test('it returns the array with the given values ommitted', function(assert) { 13 | let expectedResult = [78]; 14 | let result = without([42, 56], [42, 56, 78]); 15 | 16 | assert.deepEqual(result, expectedResult, 'should return the remaining values'); 17 | }); 18 | 19 | test('it returns the same array when no values are ommitted', function(assert) { 20 | let expectedResult = [42, 56, 78]; 21 | let result = without('foo', [42, 56, 78]); 22 | 23 | assert.deepEqual(result, expectedResult, 'should return the same array'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/values-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | values', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it returns object values', async function(assert) { 10 | this.set('inputValue', {a: 1, b: 2}); 11 | 12 | await render(hbs`{{#each (values this.inputValue) as |v|}}{{v}}{{/each}}`); 13 | 14 | assert.dom(this.element).hasText('12'); 15 | }); 16 | 17 | test('it handles undefined input', async function(assert) { 18 | await render(hbs` 19 | {{#each (values undefined) as |v|}}{{v}}{{/each}} 20 | `); 21 | 22 | assert.dom(this.element).hasText(''); 23 | }); 24 | 25 | test('it handles null input', async function(assert) { 26 | await render(hbs` 27 | {{#each (values null) as |v|}}{{v}}{{/each}} 28 | `); 29 | 30 | assert.dom(this.element).hasText(''); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/helpers/toggle-test.js: -------------------------------------------------------------------------------- 1 | import { get } from '@ember/object'; 2 | import { toggle } from 'ember-composable-helpers/helpers/toggle'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Unit | Helper | toggle', function() { 6 | test('it toggles the property', function(assert) { 7 | let jimBob = { isAlive: false }; 8 | let action = toggle(['isAlive', jimBob]); 9 | action(); 10 | 11 | assert.ok(get(jimBob, 'isAlive') === true, 'should be true'); 12 | }); 13 | 14 | test('it correctly toggles non-boolean falsey values', function(assert) { 15 | let jimBob = { isAlive: undefined }; 16 | let action = toggle(['isAlive', jimBob]); 17 | action(); 18 | 19 | assert.ok(get(jimBob, 'isAlive') === true, 'should be true'); 20 | }); 21 | 22 | test('it correctly toggles non-boolean truthy values', function(assert) { 23 | let jimBob = { isAlive: {} }; 24 | let action = toggle(['isAlive', jimBob]); 25 | action(); 26 | 27 | assert.ok(get(jimBob, 'isAlive') === false, 'should be false'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/values-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | values', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it returns object values', async function(assert) { 10 | this.set('inputValue', {a: 1, b: 2}); 11 | 12 | await render(hbs`{{#each (values this.inputValue) as |v|}}{{v}}{{/each}}`); 13 | 14 | assert.dom(this.element).hasText('12'); 15 | }); 16 | 17 | test('it handles undefined input', async function(assert) { 18 | await render(hbs` 19 | {{#each (values undefined) as |v|}}{{v}}{{/each}} 20 | `); 21 | 22 | assert.dom(this.element).hasText(''); 23 | }); 24 | 25 | test('it handles null input', async function(assert) { 26 | await render(hbs` 27 | {{#each (values null) as |v|}}{{v}}{{/each}} 28 | `); 29 | 30 | assert.dom(this.element).hasText(''); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/helpers/toggle-test.js: -------------------------------------------------------------------------------- 1 | import { get } from '@ember/object'; 2 | import { toggle } from 'ember-composable-helpers/helpers/toggle'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Unit | Helper | toggle', function() { 6 | test('it toggles the property', function(assert) { 7 | let jimBob = { isAlive: false }; 8 | let action = toggle(['isAlive', jimBob]); 9 | action(); 10 | 11 | assert.ok(get(jimBob, 'isAlive') === true, 'should be true'); 12 | }); 13 | 14 | test('it correctly toggles non-boolean falsey values', function(assert) { 15 | let jimBob = { isAlive: undefined }; 16 | let action = toggle(['isAlive', jimBob]); 17 | action(); 18 | 19 | assert.ok(get(jimBob, 'isAlive') === true, 'should be true'); 20 | }); 21 | 22 | test('it correctly toggles non-boolean truthy values', function(assert) { 23 | let jimBob = { isAlive: {} }; 24 | let action = toggle(['isAlive', jimBob]); 25 | action(); 26 | 27 | assert.ok(get(jimBob, 'isAlive') === false, 'should be false'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/call-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | call', function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it renders a function return value with curried arguments', async function (assert) { 10 | this.fn = function (value) { 11 | return value.toUpperCase(); 12 | }; 13 | 14 | await render(hbs`{{call (fn this.fn "foo")}}`); 15 | 16 | assert.dom(this.element).hasText('FOO'); 17 | }); 18 | 19 | test('it calls a function with this binding', async function (assert) { 20 | this.that = { 21 | value: 'foo', 22 | }; 23 | 24 | this.fn = function () { 25 | return this.value.toUpperCase(); 26 | }; 27 | 28 | await render(hbs`{{call this.fn this.that}}`); 29 | 30 | assert.dom(this.element).hasText('FOO'); 31 | }); 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/call-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | call', function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it renders a function return value with curried arguments', async function (assert) { 10 | this.fn = function (value) { 11 | return value.toUpperCase(); 12 | }; 13 | 14 | await render(hbs`{{call (fn this.fn "foo")}}`); 15 | 16 | assert.dom(this.element).hasText('FOO'); 17 | }); 18 | 19 | test('it calls a function with this binding', async function (assert) { 20 | this.that = { 21 | value: 'foo', 22 | }; 23 | 24 | this.fn = function () { 25 | return this.value.toUpperCase(); 26 | }; 27 | 28 | await render(hbs`{{call this.fn this.that}}`); 29 | 30 | assert.dom(this.element).hasText('FOO'); 31 | }); 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/inc-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from "ember-cli-htmlbars"; 2 | import { module, test } from "qunit"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { render } from "@ember/test-helpers"; 5 | 6 | module("Integration | Helper | {{inc}}", function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test("it increments a value", async function (assert) { 10 | await render(hbs`{{inc 1}}`); 11 | 12 | assert.dom().hasText("2", "should increment by 1"); 13 | }); 14 | 15 | test("it increments a value", async function (assert) { 16 | await render(hbs`{{inc 2 1}}`); 17 | 18 | assert.dom().hasText("3", "should increment by 2"); 19 | }); 20 | 21 | test("It can increment a string", async function (assert) { 22 | await render(hbs`{{inc "1"}}`); 23 | 24 | assert.dom().hasText("2", "should increment by 1"); 25 | }); 26 | 27 | test("It handles when undefined is passed", async function (assert) { 28 | await render(hbs`{{( inc )}}`); 29 | 30 | assert.dom().hasText("", "should not return a value"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/inc-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from "ember-cli-htmlbars"; 2 | import { module, test } from "qunit"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { render } from "@ember/test-helpers"; 5 | 6 | module("Integration | Helper | {{inc}}", function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test("it increments a value", async function (assert) { 10 | await render(hbs`{{inc 1}}`); 11 | 12 | assert.dom().hasText("2", "should increment by 1"); 13 | }); 14 | 15 | test("it increments a value with amount", async function (assert) { 16 | await render(hbs`{{inc 2 1}}`); 17 | 18 | assert.dom().hasText("3", "should increment by 2"); 19 | }); 20 | 21 | test("It can increment a string", async function (assert) { 22 | await render(hbs`{{inc "1"}}`); 23 | 24 | assert.dom().hasText("2", "should increment by 1"); 25 | }); 26 | 27 | test("It handles when undefined is passed", async function (assert) { 28 | await render(hbs`{{( inc )}}`); 29 | 30 | assert.dom().hasText("", "should not return a value"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/call-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | call', function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it renders a function return value with curried arguments', async function (assert) { 10 | this.fn = function (value) { 11 | return value.toUpperCase(); 12 | }; 13 | 14 | await render(hbs`{{call (fn this.fn "foo")}}`); 15 | 16 | assert.dom(this.element).hasText('FOO'); 17 | }); 18 | 19 | test('it calls a function with this binding', async function (assert) { 20 | this.that = { 21 | value: 'foo', 22 | }; 23 | 24 | this.fn = function () { 25 | return this.value.toUpperCase(); 26 | }; 27 | 28 | await render(hbs`{{call this.fn this.that}}`); 29 | 30 | assert.dom(this.element).hasText('FOO'); 31 | }); 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/inc-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from "ember-cli-htmlbars"; 2 | import { module, test } from "qunit"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { render } from "@ember/test-helpers"; 5 | 6 | module("Integration | Helper | {{inc}}", function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test("it increments a value", async function (assert) { 10 | await render(hbs`{{inc 1}}`); 11 | 12 | assert.dom().hasText("2", "should increment by 1"); 13 | }); 14 | 15 | test("it increments a value", async function (assert) { 16 | await render(hbs`{{inc 2 1}}`); 17 | 18 | assert.dom().hasText("3", "should increment by 2"); 19 | }); 20 | 21 | test("It can increment a string", async function (assert) { 22 | await render(hbs`{{inc "1"}}`); 23 | 24 | assert.dom().hasText("2", "should increment by 1"); 25 | }); 26 | 27 | test("It handles when undefined is passed", async function (assert) { 28 | await render(hbs`{{inc}}`); 29 | 30 | assert.dom().hasText("", "should not return a value"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/reject-by.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isArray as isEmberArray } from '@ember/array'; 3 | import { isPresent } from '@ember/utils'; 4 | import { get } from '@ember/object'; 5 | import isEqual from '../utils/is-equal.ts'; 6 | import asArray from '../utils/as-array.ts'; 7 | 8 | export function rejectBy([ 9 | byPath, 10 | value, 11 | array, 12 | ]: [K, T | ((value: T[K]) => boolean) | undefined, T[]] | [K, T[]]) { 13 | if (!isEmberArray(array) && isEmberArray(value)) { 14 | array = value as T[]; 15 | value = undefined; 16 | } 17 | array = asArray(array as T[]); 18 | 19 | let filterFn; 20 | 21 | if (isPresent(value)) { 22 | if (typeof value === 'function') { 23 | filterFn = (item: T) => !value(get(item, byPath)); 24 | } else { 25 | filterFn = (item: T) => !isEqual(get(item, byPath), value); 26 | } 27 | } else { 28 | filterFn = (item: T) => !get(item, byPath); 29 | } 30 | 31 | return array.filter(filterFn); 32 | } 33 | 34 | export default helper(rejectBy); 35 | -------------------------------------------------------------------------------- /.release-plan.json: -------------------------------------------------------------------------------- 1 | { 2 | "solution": { 3 | "@nullvoxpopuli/ember-composable-helpers": { 4 | "impact": "minor", 5 | "oldVersion": "5.2.11", 6 | "newVersion": "5.3.0", 7 | "tagName": "latest", 8 | "constraints": [ 9 | { 10 | "impact": "minor", 11 | "reason": "Appears in changelog section :rocket: Enhancement" 12 | } 13 | ], 14 | "pkgJSONPath": "./ember-composable-helpers/package.json" 15 | } 16 | }, 17 | "description": "## Release (2025-08-22)\n\n* @nullvoxpopuli/ember-composable-helpers 5.3.0 (minor)\n\n#### :rocket: Enhancement\n* `@nullvoxpopuli/ember-composable-helpers`\n * [#55](https://github.com/NullVoxPopuli/ember-composable-helpers/pull/55) [ note for ember-source < 4.5 ] Remove ember-functions-as-helper-polyfill from the library ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :house: Internal\n* [#56](https://github.com/NullVoxPopuli/ember-composable-helpers/pull/56) Update release-plan ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 1\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n" 18 | } 19 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/from-entries-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | from-entries', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it returns an object', async function(assert) { 10 | this.set('inputValue', [['a', 1], ['b', 2]]); 11 | 12 | await render(hbs`{{#each-in (from-entries this.inputValue) as |k v|}}{{k}}{{v}}{{/each-in}}`); 13 | 14 | assert.dom(this.element).hasText('a1b2'); 15 | }); 16 | 17 | test('it handles undefined input', async function(assert) { 18 | await render(hbs` 19 | {{#each-in (from-entries undefined) as |k v|}}{{k v}}{{/each-in}} 20 | `); 21 | 22 | assert.dom(this.element).hasText(''); 23 | }); 24 | 25 | test('it handles null input', async function(assert) { 26 | await render(hbs` 27 | {{#each-in (from-entries null) as |k v|}}{{k v}}{{/each-in}} 28 | `); 29 | 30 | assert.dom(this.element).hasText(''); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/queue.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import isPromise from '../utils/is-promise.ts'; 3 | 4 | function invokeMaybeNullable( 5 | curr: (...args1: unknown[]) => void | null, 6 | args: unknown[], 7 | ) { 8 | return curr == null ? undefined : curr(...args); 9 | } 10 | 11 | export function queue(positional: unknown[] = []) { 12 | const actions = positional as (() => void)[]; 13 | 14 | return function (...args: unknown[]) { 15 | const invokeWithArgs = function (acc: unknown, curr: () => void) { 16 | if (isPromise(acc)) { 17 | return acc.then(() => invokeMaybeNullable(curr, args)); 18 | } 19 | 20 | return invokeMaybeNullable(curr, args); 21 | }; 22 | 23 | // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents 24 | return actions.reduce>((acc, curr, idx) => { 25 | if (idx === 0) { 26 | return invokeMaybeNullable(curr, args); 27 | } 28 | 29 | return invokeWithArgs(acc, curr); 30 | }, undefined); 31 | }; 32 | } 33 | 34 | export default helper(queue); 35 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/from-entries-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | from-entries', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it returns an object', async function(assert) { 10 | this.set('inputValue', [['a', 1], ['b', 2]]); 11 | 12 | await render(hbs`{{#each-in (from-entries this.inputValue) as |k v|}}{{k}}{{v}}{{/each-in}}`); 13 | 14 | assert.dom(this.element).hasText('a1b2'); 15 | }); 16 | 17 | test('it handles undefined input', async function(assert) { 18 | await render(hbs` 19 | {{#each-in (from-entries undefined) as |k v|}}{{k v}}{{/each-in}} 20 | `); 21 | 22 | assert.dom(this.element).hasText(''); 23 | }); 24 | 25 | test('it handles null input', async function(assert) { 26 | await render(hbs` 27 | {{#each-in (from-entries null) as |k v|}}{{k v}}{{/each-in}} 28 | `); 29 | 30 | assert.dom(this.element).hasText(''); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/intersect.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isArray as isEmberArray } from '@ember/array'; 3 | import asArray from '../utils/as-array.ts'; 4 | 5 | export function intersect([...arrays]: [...T[]]) { 6 | const confirmedArrays = asArray(arrays).map((array) => { 7 | return isEmberArray(array) ? (array as T[]) : []; 8 | }); 9 | // copied from https://github.com/emberjs/ember.js/blob/315ec6472ff542ac714432036cc96fe4bd62bd1f/packages/%40ember/object/lib/computed/reduce_computed_macros.js#L1063-L1100 10 | const results = confirmedArrays.pop()!.filter((candidate) => { 11 | for (let i = 0; i < confirmedArrays.length; i++) { 12 | let found = false; 13 | const array = confirmedArrays[i] as T[]; 14 | for (let j = 0; j < array.length; j++) { 15 | if (array[j] === candidate) { 16 | found = true; 17 | break; 18 | } 19 | } 20 | 21 | if (found === false) { 22 | return false; 23 | } 24 | } 25 | 26 | return true; 27 | }); 28 | 29 | return results; 30 | } 31 | 32 | export default helper(intersect); 33 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/from-entries-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | from-entries', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it returns an object', async function(assert) { 10 | this.set('inputValue', [['a', 1], ['b', 2]]); 11 | 12 | await render(hbs`{{#each-in (from-entries this.inputValue) as |k v|}}{{k}}{{v}}{{/each-in}}`); 13 | 14 | assert.dom(this.element).hasText('a1b2'); 15 | }); 16 | 17 | test('it handles undefined input', async function(assert) { 18 | await render(hbs` 19 | {{#each-in (from-entries undefined) as |k v|}}{{k v}}{{/each-in}} 20 | `); 21 | 22 | assert.dom(this.element).hasText(''); 23 | }); 24 | 25 | test('it handles null input', async function(assert) { 26 | await render(hbs` 27 | {{#each-in (from-entries null) as |k v|}}{{k v}}{{/each-in}} 28 | `); 29 | 30 | assert.dom(this.element).hasText(''); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/filter-by.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { isArray as isEmberArray } from '@ember/array'; 3 | import { isEmpty } from '@ember/utils'; 4 | import { get } from '@ember/object'; 5 | import isEqual from '../utils/is-equal.ts'; 6 | import asArray from '../utils/as-array.ts'; 7 | 8 | export function filterBy([ 9 | byPath, 10 | value, 11 | array, 12 | ]: [K, T[K] | T[] | undefined, T[]]) { 13 | let isPresent = true; 14 | if (!isEmberArray(array) && isEmberArray(value)) { 15 | array = value as T[]; 16 | value = undefined; 17 | isPresent = false; 18 | } 19 | 20 | array = asArray(array); 21 | 22 | if (isEmpty(byPath) || isEmpty(array)) { 23 | return []; 24 | } 25 | 26 | let filterFn; 27 | 28 | if (isPresent) { 29 | if (typeof value === 'function') { 30 | filterFn = (item: T) => value(get(item, byPath)); 31 | } else { 32 | filterFn = (item: T) => isEqual(get(item, byPath), value); 33 | } 34 | } else { 35 | filterFn = (item: T) => !!get(item, byPath); 36 | } 37 | 38 | return array.filter(filterFn); 39 | } 40 | 41 | export default helper(filterBy); 42 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/keys-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | keys', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it returns the keys', async function(assert) { 10 | let object = { 11 | a: 1, 12 | b: 2 13 | } 14 | 15 | this.set('object', object); 16 | 17 | await render(hbs`{{#each (keys this.object) as |key|}}{{key}}{{/each}}`); 18 | 19 | assert.dom().hasText('ab'); 20 | }); 21 | 22 | test('it handles undefined input', async function(assert) { 23 | await render(hbs` 24 | {{#each (keys undefined) as |key|}}{{key}}{{/each}} 25 | `); 26 | 27 | assert.dom().hasText(''); 28 | }); 29 | 30 | test('it works with let helper', async function(assert) { 31 | let object = { 32 | a: 1, 33 | b: 2 34 | } 35 | 36 | this.set('object', object); 37 | 38 | await render(hbs` 39 | {{#let (keys this.object) as |objectKeys|}} 40 | {{objectKeys.length}} 41 | {{/let}} 42 | `); 43 | 44 | assert.dom().hasText('2'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test-app/tests/unit/utils/is-promise-test.js: -------------------------------------------------------------------------------- 1 | import { resolve, Promise } from 'rsvp'; 2 | import isPromise from 'ember-composable-helpers/utils/is-promise'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Unit | Utility | is promise', function() { 6 | let testData = [ 7 | { 8 | value: resolve('foo'), 9 | expected: true 10 | }, 11 | { 12 | value: new Promise((resolve) => resolve('blah')), 13 | expected: true 14 | }, 15 | { 16 | value: { then() {}, catch() {}, finally() {} }, 17 | expected: true 18 | }, 19 | { 20 | value: { then() {} }, 21 | expected: false 22 | }, 23 | { 24 | value: 'blah', 25 | expected: false 26 | }, 27 | { 28 | value: 42, 29 | expected: false 30 | }, 31 | { 32 | value: ['meow'], 33 | expected: false 34 | }, 35 | { 36 | value: null, 37 | expected: false 38 | } 39 | ]; 40 | 41 | testData.forEach(({ value, expected }) => { 42 | test('it checks if an object is an instance of an RSVP.Promise', function(assert) { 43 | let result = isPromise(value); 44 | 45 | assert.strictEqual(result, expected, `should be ${expected}`); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workspace-root", 3 | "private": true, 4 | "repository": { 5 | "type": "git", 6 | "url": "git@github.com:NullVoxPopuli/ember-composable-helpers.git" 7 | }, 8 | "license": "MIT", 9 | "author": "", 10 | "scripts": { 11 | "build": "pnpm --filter ember-composable-helpers build", 12 | "format": "pnpm -r --if-present format", 13 | "lint": "pnpm -r --if-present lint", 14 | "lint:fix": "pnpm -r --if-present lint:fix", 15 | "start": "concurrently 'pnpm:start:*' --restart-after 5000 --prefix-colors cyan,white,yellow", 16 | "start:addon": "pnpm --filter ember-composable-helpers start --no-watch.clearScreen", 17 | "start:test-app": "pnpm --filter test-app start", 18 | "test": "pnpm -r --workspace-concurrency=1 --if-present test" 19 | }, 20 | "devDependencies": { 21 | "@glint/core": "^1.5.2", 22 | "concurrently": "^9.1.2", 23 | "prettier": "^3.5.2", 24 | "prettier-plugin-ember-template-tag": "^2.0.4", 25 | "release-plan": "^0.16.0" 26 | }, 27 | "packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b", 28 | "engines": { 29 | "node": "20.12.2" 30 | } 31 | } -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/keys-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | keys', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it returns the keys', async function(assert) { 10 | let object = { 11 | a: 1, 12 | b: 2 13 | } 14 | 15 | this.set('object', object); 16 | 17 | await render(hbs`{{#each (keys this.object) as |key|}}{{key}}{{/each}}`); 18 | 19 | assert.dom().hasText('ab'); 20 | }); 21 | 22 | test('it handles undefined input', async function(assert) { 23 | await render(hbs` 24 | {{#each (keys undefined) as |key|}}{{key}}{{/each}} 25 | `); 26 | 27 | assert.dom().hasText(''); 28 | }); 29 | 30 | test('it works with let helper', async function(assert) { 31 | let object = { 32 | a: 1, 33 | b: 2 34 | } 35 | 36 | this.set('object', object); 37 | 38 | await render(hbs` 39 | {{#let (keys this.object) as |objectKeys|}} 40 | {{objectKeys.length}} 41 | {{/let}} 42 | `); 43 | 44 | assert.dom().hasText('2'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/utils/is-promise-test.js: -------------------------------------------------------------------------------- 1 | import { resolve, Promise } from 'rsvp'; 2 | import isPromise from 'ember-composable-helpers/utils/is-promise'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Unit | Utility | is promise', function() { 6 | let testData = [ 7 | { 8 | value: resolve('foo'), 9 | expected: true 10 | }, 11 | { 12 | value: new Promise((resolve) => resolve('blah')), 13 | expected: true 14 | }, 15 | { 16 | value: { then() {}, catch() {}, finally() {} }, 17 | expected: true 18 | }, 19 | { 20 | value: { then() {} }, 21 | expected: false 22 | }, 23 | { 24 | value: 'blah', 25 | expected: false 26 | }, 27 | { 28 | value: 42, 29 | expected: false 30 | }, 31 | { 32 | value: ['meow'], 33 | expected: false 34 | }, 35 | { 36 | value: null, 37 | expected: false 38 | } 39 | ]; 40 | 41 | testData.forEach(({ value, expected }) => { 42 | test('it checks if an object is an instance of an RSVP.Promise', function(assert) { 43 | let result = isPromise(value); 44 | 45 | assert.strictEqual(result, expected, `should be ${expected}`); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/utils/is-promise-test.js: -------------------------------------------------------------------------------- 1 | import { resolve, Promise } from 'rsvp'; 2 | import isPromise from 'ember-composable-helpers/utils/is-promise'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Unit | Utility | is promise', function() { 6 | let testData = [ 7 | { 8 | value: resolve('foo'), 9 | expected: true 10 | }, 11 | { 12 | value: new Promise((resolve) => resolve('blah')), 13 | expected: true 14 | }, 15 | { 16 | value: { then() {}, catch() {}, finally() {} }, 17 | expected: true 18 | }, 19 | { 20 | value: { then() {} }, 21 | expected: false 22 | }, 23 | { 24 | value: 'blah', 25 | expected: false 26 | }, 27 | { 28 | value: 42, 29 | expected: false 30 | }, 31 | { 32 | value: ['meow'], 33 | expected: false 34 | }, 35 | { 36 | value: null, 37 | expected: false 38 | } 39 | ]; 40 | 41 | testData.forEach(({ value, expected }) => { 42 | test('it checks if an object is an instance of an RSVP.Promise', function(assert) { 43 | let result = isPromise(value); 44 | 45 | assert.strictEqual(result, expected, `should be ${expected}`); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/keys-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | keys', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it returns the keys', async function(assert) { 10 | let object = { 11 | a: 1, 12 | b: 2 13 | } 14 | 15 | this.set('object', object); 16 | 17 | await render(hbs`{{#each (keys this.object) as |key|}}{{key}}{{/each}}`); 18 | 19 | assert.dom().hasText('ab'); 20 | }); 21 | 22 | test('it handles undefined input', async function(assert) { 23 | await render(hbs` 24 | {{#each (keys undefined) as |key|}}{{key}}{{/each}} 25 | `); 26 | 27 | assert.dom().hasText(''); 28 | }); 29 | 30 | test('it works with let helper', async function(assert) { 31 | let object = { 32 | a: 1, 33 | b: 2 34 | } 35 | 36 | this.set('object', object); 37 | 38 | await render(hbs` 39 | {{#let (keys this.object) as |objectKeys|}} 40 | {{objectKeys.length}} 41 | {{/let}} 42 | `); 43 | 44 | assert.dom().hasText('2'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/dec-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from "ember-cli-htmlbars"; 2 | import { module, test } from "qunit"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { render } from "@ember/test-helpers"; 5 | 6 | module("Integration | Helper | {{dec}}", function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test("it decrements a value", async function (assert) { 10 | await render(hbs`{{dec 3}}`); 11 | 12 | assert.dom().hasText("2", "should decrement by 1"); 13 | }); 14 | 15 | test("it decrements a value with amount", async function (assert) { 16 | await render(hbs`{{dec 2 5}}`); 17 | 18 | assert.dom().hasText("3", "should decrement by 2"); 19 | }); 20 | 21 | test("it decrements below 0", async function (assert) { 22 | await render(hbs`{{dec 2 0}}`); 23 | 24 | assert.dom().hasText("-2", "should be -2"); 25 | }); 26 | 27 | test("It can decrement a string", async function (assert) { 28 | await render(hbs`{{dec "2"}}`); 29 | 30 | assert.dom().hasText("1", "should decrement by 1"); 31 | }); 32 | 33 | test("It handles when undefined is passed", async function (assert) { 34 | await render(hbs`{{(dec)}}`); 35 | 36 | assert.dom().hasText("", "should not return a value"); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/dec-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from "ember-cli-htmlbars"; 2 | import { module, test } from "qunit"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { render } from "@ember/test-helpers"; 5 | 6 | module("Integration | Helper | {{dec}}", function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test("it decrements a value", async function (assert) { 10 | await render(hbs`{{dec 3}}`); 11 | 12 | assert.dom().hasText("2", "should decrement by 1"); 13 | }); 14 | 15 | test("it decrements a value", async function (assert) { 16 | await render(hbs`{{dec 2 5}}`); 17 | 18 | assert.dom().hasText("3", "should decrement by 2"); 19 | }); 20 | 21 | test("it decrements below 0", async function (assert) { 22 | await render(hbs`{{dec 2 0}}`); 23 | 24 | assert.dom().hasText("-2", "should be -2"); 25 | }); 26 | 27 | test("It can decrement a string", async function (assert) { 28 | await render(hbs`{{dec "2"}}`); 29 | 30 | assert.dom().hasText("1", "should decrement by 1"); 31 | }); 32 | 33 | test("It handles when undefined is passed", async function (assert) { 34 | await render(hbs`{{(dec)}}`); 35 | 36 | assert.dom().hasText("", "should not return a value"); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/dec-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from "ember-cli-htmlbars"; 2 | import { module, test } from "qunit"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { render } from "@ember/test-helpers"; 5 | 6 | module("Integration | Helper | {{dec}}", function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test("it decrements a value", async function (assert) { 10 | await render(hbs`{{dec 3}}`); 11 | 12 | assert.dom().hasText("2", "should decrement by 1"); 13 | }); 14 | 15 | test("it decrements a value", async function (assert) { 16 | await render(hbs`{{dec 2 5}}`); 17 | 18 | assert.dom().hasText("3", "should decrement by 2"); 19 | }); 20 | 21 | test("it decrements below 0", async function (assert) { 22 | await render(hbs`{{dec 2 0}}`); 23 | 24 | assert.dom().hasText("-2", "should be -2"); 25 | }); 26 | 27 | test("It can decrement a string", async function (assert) { 28 | await render(hbs`{{dec "2"}}`); 29 | 30 | assert.dom().hasText("1", "should decrement by 1"); 31 | }); 32 | 33 | test("It handles when undefined is passed", async function (assert) { 34 | await render(hbs`{{dec}}`); 35 | 36 | assert.dom().hasText("", "should not return a value"); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # For every push to the primary branch with .release-plan.json modified, 2 | # runs release-plan. 3 | 4 | name: Publish Stable 5 | 6 | on: 7 | workflow_dispatch: 8 | push: 9 | branches: 10 | - main 11 | - master 12 | paths: 13 | - '.release-plan.json' 14 | 15 | concurrency: 16 | group: publish-${{ github.head_ref || github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | publish: 21 | name: "NPM Publish" 22 | runs-on: ubuntu-latest 23 | permissions: 24 | contents: write 25 | pull-requests: write 26 | id-token: write 27 | attestations: write 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: pnpm/action-setup@v4 32 | - uses: actions/setup-node@v4 33 | with: 34 | node-version: 18 35 | # This creates an .npmrc that reads the NODE_AUTH_TOKEN environment variable 36 | registry-url: 'https://registry.npmjs.org' 37 | cache: pnpm 38 | - run: pnpm install --frozen-lockfile 39 | - name: Publish to NPM 40 | run: NPM_CONFIG_PROVENANCE=true pnpm release-plan publish 41 | env: 42 | GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} 43 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 44 | -------------------------------------------------------------------------------- /test-app/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TestApp 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 | 22 | {{content-for "body"}} 23 | {{content-for "test-body"}} 24 | 25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {{content-for "body-footer"}} 39 | {{content-for "test-body-footer"}} 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test-app/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'my-app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | EXTEND_PROTOTYPES: false, 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 14 | }, 15 | }, 16 | 17 | APP: { 18 | // Here you can pass flags/options to your application instance 19 | // when it is created 20 | }, 21 | }; 22 | 23 | if (environment === 'development') { 24 | // ENV.APP.LOG_RESOLVER = true; 25 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 26 | // ENV.APP.LOG_TRANSITIONS = true; 27 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 28 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 29 | } 30 | 31 | if (environment === 'test') { 32 | // Testem prefers this... 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | ENV.APP.autoboot = false; 41 | } 42 | 43 | if (environment === 'production') { 44 | // here you can enable a production-specific feature 45 | } 46 | 47 | return ENV; 48 | }; 49 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{content-for "body-footer"}} 38 | {{content-for "test-body-footer"}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{content-for "body-footer"}} 38 | {{content-for "test-body-footer"}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /test-app-min-supported/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | ecmaVersion: 2018, 6 | sourceType: 'module', 7 | ecmaFeatures: { 8 | legacyDecorators: true 9 | } 10 | }, 11 | plugins: [ 12 | 'ember' 13 | ], 14 | extends: [ 15 | 'eslint:recommended', 16 | 'plugin:ember/recommended' 17 | ], 18 | env: { 19 | browser: true 20 | }, 21 | rules: { 22 | 'ember/no-jquery': 'error' 23 | }, 24 | overrides: [ 25 | // node files 26 | { 27 | files: [ 28 | '.eslintrc.js', 29 | '.template-lintrc.js', 30 | 'ember-cli-build.js', 31 | 'index.js', 32 | 'testem.js', 33 | 'blueprints/*/index.js', 34 | 'config/**/*.js', 35 | 'lib/**/*', 36 | 'tests/**/config/**/*.js', 37 | 'test/**/*' 38 | ], 39 | excludedFiles: [ 40 | 'addon/**', 41 | 'addon-test-support/**', 42 | 'app/**', 43 | 'tests/**/app/**' 44 | ], 45 | parserOptions: { 46 | sourceType: 'script' 47 | }, 48 | env: { 49 | browser: false, 50 | node: true 51 | }, 52 | plugins: ['node'], 53 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 54 | // add your custom rules and overrides for node files here 55 | }) 56 | } 57 | ] 58 | }; 59 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/optional-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, click } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{optional}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | hooks.beforeEach(function() { 10 | this.actions = {}; 11 | this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); 12 | }); 13 | 14 | test('If the action does not exist, it passes a no-op function', async function(assert) { 15 | assert.expect(0); 16 | await render(hbs` `); 17 | await click('button'); 18 | }); 19 | 20 | test('If the action does exist, it passes the given action', async function(assert) { 21 | assert.expect(1); 22 | this.set('handler', () => assert.ok(true)); 23 | await render(hbs` `); 24 | await click('button'); 25 | }); 26 | 27 | test('Works in a pipe', async function(assert) { 28 | assert.expect(1); 29 | this.actions.check = (value) => assert.strictEqual(value, 42); 30 | await render(hbs` 31 | `); 32 | await click('button'); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | ecmaVersion: 2018, 6 | sourceType: 'module', 7 | ecmaFeatures: { 8 | legacyDecorators: true 9 | } 10 | }, 11 | plugins: [ 12 | 'ember' 13 | ], 14 | extends: [ 15 | 'eslint:recommended', 16 | 'plugin:ember/recommended' 17 | ], 18 | env: { 19 | browser: true 20 | }, 21 | rules: { 22 | 'ember/no-jquery': 'error' 23 | }, 24 | overrides: [ 25 | // node files 26 | { 27 | files: [ 28 | '.eslintrc.js', 29 | '.template-lintrc.js', 30 | 'ember-cli-build.js', 31 | 'index.js', 32 | 'testem.js', 33 | 'blueprints/*/index.js', 34 | 'config/**/*.js', 35 | 'lib/**/*', 36 | 'tests/**/config/**/*.js', 37 | 'test/**/*' 38 | ], 39 | excludedFiles: [ 40 | 'addon/**', 41 | 'addon-test-support/**', 42 | 'app/**', 43 | 'tests/**/app/**' 44 | ], 45 | parserOptions: { 46 | sourceType: 'script' 47 | }, 48 | env: { 49 | browser: false, 50 | node: true 51 | }, 52 | plugins: ['node'], 53 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 54 | // add your custom rules and overrides for node files here 55 | }) 56 | } 57 | ] 58 | }; 59 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/optional-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, click } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{optional}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | hooks.beforeEach(function() { 10 | this.actions = {}; 11 | this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); 12 | }); 13 | 14 | test('If the action does not exist, it passes a no-op function', async function(assert) { 15 | assert.expect(0); 16 | await render(hbs` `); 17 | await click('button'); 18 | }); 19 | 20 | test('If the action does exist, it passes the given action', async function(assert) { 21 | assert.expect(1); 22 | this.set('handler', () => assert.ok(true)); 23 | await render(hbs` `); 24 | await click('button'); 25 | }); 26 | 27 | test('Works in a pipe', async function(assert) { 28 | assert.expect(1); 29 | this.actions.check = (value) => assert.strictEqual(value, 42); 30 | await render(hbs` 31 | `); 32 | await click('button'); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test-app/tests/unit/utils/get-index-test.js: -------------------------------------------------------------------------------- 1 | import EmberObject from '@ember/object'; 2 | import getIndex from 'ember-composable-helpers/utils/get-index'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Unit | Utility | get index', function() { 6 | let testData = [ 7 | { 8 | label: 'POJOs', 9 | array: [{ foo: 'baz' }, { bar: 'foo' }, { baz: 'bar' }], 10 | item: { foo: 'baz' }, 11 | expected: 0, 12 | useDeepEqual: true 13 | }, 14 | { 15 | label: 'EmberObjects', 16 | array: [ 17 | EmberObject.create({ bar: 'foo' }), 18 | EmberObject.create({ foo: 'baz' }), 19 | EmberObject.create({ baz: 'bar' }) 20 | ], 21 | item: { foo: 'baz' }, 22 | expected: 1, 23 | useDeepEqual: true 24 | }, 25 | { 26 | label: 'Strings', 27 | array: ['a', 'b', 'c'], 28 | item: 'c', 29 | expected: 2, 30 | useDeepEqual: false 31 | } 32 | ]; 33 | 34 | testData.forEach(({ label, array, item, expected, useDeepEqual }) => { 35 | test(`it works with ${label}`, function(assert) { 36 | let result = getIndex(array, item, useDeepEqual); 37 | assert.strictEqual(result, expected, `should be ${expected}`); 38 | }); 39 | }); 40 | 41 | test('it returns null if the given value is not in the array', function(assert) { 42 | let result = getIndex([1, 2, 3], 4); 43 | assert.strictEqual(result, null, 'should be null'); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/utils/get-index-test.js: -------------------------------------------------------------------------------- 1 | import EmberObject from '@ember/object'; 2 | import getIndex from 'ember-composable-helpers/utils/get-index'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Unit | Utility | get index', function() { 6 | let testData = [ 7 | { 8 | label: 'POJOs', 9 | array: [{ foo: 'baz' }, { bar: 'foo' }, { baz: 'bar' }], 10 | item: { foo: 'baz' }, 11 | expected: 0, 12 | useDeepEqual: true 13 | }, 14 | { 15 | label: 'EmberObjects', 16 | array: [ 17 | EmberObject.create({ bar: 'foo' }), 18 | EmberObject.create({ foo: 'baz' }), 19 | EmberObject.create({ baz: 'bar' }) 20 | ], 21 | item: { foo: 'baz' }, 22 | expected: 1, 23 | useDeepEqual: true 24 | }, 25 | { 26 | label: 'Strings', 27 | array: ['a', 'b', 'c'], 28 | item: 'c', 29 | expected: 2, 30 | useDeepEqual: false 31 | } 32 | ]; 33 | 34 | testData.forEach(({ label, array, item, expected, useDeepEqual }) => { 35 | test(`it works with ${label}`, function(assert) { 36 | let result = getIndex(array, item, useDeepEqual); 37 | assert.strictEqual(result, expected, `should be ${expected}`); 38 | }); 39 | }); 40 | 41 | test('it returns null if the given value is not in the array', function(assert) { 42 | let result = getIndex([1, 2, 3], 4); 43 | assert.strictEqual(result, null, 'should be null'); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/utils/get-index-test.js: -------------------------------------------------------------------------------- 1 | import EmberObject from '@ember/object'; 2 | import getIndex from 'ember-composable-helpers/utils/get-index'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Unit | Utility | get index', function() { 6 | let testData = [ 7 | { 8 | label: 'POJOs', 9 | array: [{ foo: 'baz' }, { bar: 'foo' }, { baz: 'bar' }], 10 | item: { foo: 'baz' }, 11 | expected: 0, 12 | useDeepEqual: true 13 | }, 14 | { 15 | label: 'EmberObjects', 16 | array: [ 17 | EmberObject.create({ bar: 'foo' }), 18 | EmberObject.create({ foo: 'baz' }), 19 | EmberObject.create({ baz: 'bar' }) 20 | ], 21 | item: { foo: 'baz' }, 22 | expected: 1, 23 | useDeepEqual: true 24 | }, 25 | { 26 | label: 'Strings', 27 | array: ['a', 'b', 'c'], 28 | item: 'c', 29 | expected: 2, 30 | useDeepEqual: false 31 | } 32 | ]; 33 | 34 | testData.forEach(({ label, array, item, expected, useDeepEqual }) => { 35 | test(`it works with ${label}`, function(assert) { 36 | let result = getIndex(array, item, useDeepEqual); 37 | assert.strictEqual(result, expected, `should be ${expected}`); 38 | }); 39 | }); 40 | 41 | test('it returns null if the given value is not in the array', function(assert) { 42 | let result = getIndex([1, 2, 3], 4); 43 | assert.strictEqual(result, null, 'should be null'); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test-app-min-supported/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'test-app', 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. EMBER_NATIVE_DECORATOR_SUPPORT: 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 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'test-app', 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. EMBER_NATIVE_DECORATOR_SUPPORT: 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 | -------------------------------------------------------------------------------- /test-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: '@typescript-eslint/parser', 6 | parserOptions: { 7 | ecmaVersion: 'latest', 8 | }, 9 | plugins: ['ember', '@typescript-eslint'], 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:ember/recommended', 13 | ], 14 | env: { 15 | browser: true, 16 | }, 17 | rules: {}, 18 | overrides: [ 19 | // ts files 20 | { 21 | files: ['**/*.ts'], 22 | extends: [ 23 | 'plugin:@typescript-eslint/eslint-recommended', 24 | 'plugin:@typescript-eslint/recommended', 25 | ], 26 | rules: {}, 27 | }, 28 | // node files 29 | { 30 | files: [ 31 | './.eslintrc.js', 32 | './.prettierrc.js', 33 | './.stylelintrc.js', 34 | './.template-lintrc.js', 35 | './ember-cli-build.js', 36 | './testem.js', 37 | './blueprints/*/index.js', 38 | './config/**/*.js', 39 | './lib/*/index.js', 40 | './server/**/*.js', 41 | ], 42 | env: { 43 | browser: false, 44 | node: true, 45 | }, 46 | extends: ['plugin:n/recommended'], 47 | }, 48 | { 49 | // test files 50 | files: ['tests/**/*-test.{js,ts}'], 51 | extends: ['plugin:qunit/recommended'], 52 | }, 53 | { 54 | // type tests 55 | files: ['type-tests/**'], 56 | rules: { 57 | '@typescript-eslint/no-unused-vars': 'off', 58 | }, 59 | } 60 | ], 61 | }; 62 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/pick-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { click, render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | pick', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | // eslint-disable-next-line qunit/require-expect 10 | test("Works when used with {{on}} modifier and pipe", async function(assert) { 11 | assert.expect(1); 12 | 13 | this.set('click', function(value) { 14 | assert.strictEqual(value, 'pizza party', 'The action receives the correct value'); 15 | }) 16 | 17 | await render(hbs` 18 | 23 | `); 24 | 25 | await click('#test-input'); 26 | }); 27 | 28 | // eslint-disable-next-line qunit/require-expect 29 | test("Shorthand works when used with {{on}} modifier and optional action is provided", async function(assert) { 30 | assert.expect(1); 31 | 32 | this.set('click', function(value) { 33 | assert.strictEqual(value, 'pizza party', 'The action receives the correct value'); 34 | }) 35 | 36 | await render(hbs` 37 | 42 | `); 43 | 44 | await click('#test-input'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test-app/tests/unit/helpers/range-test.js: -------------------------------------------------------------------------------- 1 | import { range } from 'ember-composable-helpers/helpers/range'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | range', function() { 5 | test('it generates a range', function(assert) { 6 | let expectedResult1 = [1, 2, 3, 4]; 7 | let expectedResult2 = [10, 11, 12, 13, 14]; 8 | let expectedResult3 = [-1, 0, 1]; 9 | let result1 = range([1, 5]); 10 | let result2 = range([10, 15]); 11 | let result3 = range([-1, 2]); 12 | 13 | assert.deepEqual(result1, expectedResult1, 'should generate 1 to 4'); 14 | assert.deepEqual(result2, expectedResult2, 'should generate 10 to 14'); 15 | assert.deepEqual(result3, expectedResult3, 'should generate -1 to 1'); 16 | }); 17 | 18 | test('it generates a negative range', function(assert) { 19 | let expectedResult = [5, 4, 3, 2, 1]; 20 | let result = range([5, 0]); 21 | 22 | assert.deepEqual(result, expectedResult, 'should generate 5 to 1'); 23 | }); 24 | 25 | test('it generates an inclusive range', function(assert) { 26 | let expectedResult = [1, 2, 3, 4, 5]; 27 | let result = range([1, 5, true]); 28 | 29 | assert.deepEqual(result, expectedResult, 'should generate 1 to 5 inclusive'); 30 | }); 31 | 32 | test('it generates an inclusive negative range', function(assert) { 33 | let expectedResult = [5, 4, 3, 2, 1, 0]; 34 | let result = range([5, 0, true]); 35 | 36 | assert.deepEqual(result, expectedResult, 'should generate 5 to 0 inclusive'); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/pick-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from "ember-cli-htmlbars"; 2 | import { module, test } from "qunit"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { click, render } from "@ember/test-helpers"; 5 | 6 | module("Integration | Helper | pick", function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test("Works when used with {{on}} modifier and pipe", async function (assert) { 10 | assert.expect(1); 11 | 12 | this.set("click", function (value) { 13 | assert.strictEqual( 14 | value, 15 | "pizza party", 16 | "The action receives the correct value", 17 | ); 18 | }); 19 | 20 | await render(hbs` 21 | 26 | `); 27 | 28 | await click("#test-input"); 29 | }); 30 | 31 | test("Shorthand works when used with {{on}} modifier and optional action is provided", async function (assert) { 32 | assert.expect(1); 33 | 34 | this.set("click", function (value) { 35 | assert.strictEqual( 36 | value, 37 | "pizza party", 38 | "The action receives the correct value", 39 | ); 40 | }); 41 | 42 | await render(hbs` 43 | 48 | `); 49 | 50 | await click("#test-input"); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/pick-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from "ember-cli-htmlbars"; 2 | import { module, test } from "qunit"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { click, render } from "@ember/test-helpers"; 5 | 6 | module("Integration | Helper | pick", function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test("Works when used with {{on}} modifier and pipe", async function (assert) { 10 | assert.expect(1); 11 | 12 | this.set("click", function (value) { 13 | assert.strictEqual( 14 | value, 15 | "pizza party", 16 | "The action receives the correct value", 17 | ); 18 | }); 19 | 20 | await render(hbs` 21 | 26 | `); 27 | 28 | await click("#test-input"); 29 | }); 30 | 31 | test("Shorthand works when used with {{on}} modifier and optional action is provided", async function (assert) { 32 | assert.expect(1); 33 | 34 | this.set("click", function (value) { 35 | assert.strictEqual( 36 | value, 37 | "pizza party", 38 | "The action receives the correct value", 39 | ); 40 | }); 41 | 42 | await render(hbs` 43 | 48 | `); 49 | 50 | await click("#test-input"); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/helpers/range-test.js: -------------------------------------------------------------------------------- 1 | import { range } from 'ember-composable-helpers/helpers/range'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | range', function() { 5 | test('it generates a range', function(assert) { 6 | let expectedResult1 = [1, 2, 3, 4]; 7 | let expectedResult2 = [10, 11, 12, 13, 14]; 8 | let expectedResult3 = [-1, 0, 1]; 9 | let result1 = range([1, 5]); 10 | let result2 = range([10, 15]); 11 | let result3 = range([-1, 2]); 12 | 13 | assert.deepEqual(result1, expectedResult1, 'should generate 1 to 4'); 14 | assert.deepEqual(result2, expectedResult2, 'should generate 10 to 14'); 15 | assert.deepEqual(result3, expectedResult3, 'should generate -1 to 1'); 16 | }); 17 | 18 | test('it generates a negative range', function(assert) { 19 | let expectedResult = [5, 4, 3, 2, 1]; 20 | let result = range([5, 0]); 21 | 22 | assert.deepEqual(result, expectedResult, 'should generate 5 to 1'); 23 | }); 24 | 25 | test('it generates an inclusive range', function(assert) { 26 | let expectedResult = [1, 2, 3, 4, 5]; 27 | let result = range([1, 5, true]); 28 | 29 | assert.deepEqual(result, expectedResult, 'should generate 1 to 5 inclusive'); 30 | }); 31 | 32 | test('it generates an inclusive negative range', function(assert) { 33 | let expectedResult = [5, 4, 3, 2, 1, 0]; 34 | let result = range([5, 0, true]); 35 | 36 | assert.deepEqual(result, expectedResult, 'should generate 5 to 0 inclusive'); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/helpers/range-test.js: -------------------------------------------------------------------------------- 1 | import { range } from 'ember-composable-helpers/helpers/range'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | range', function() { 5 | test('it generates a range', function(assert) { 6 | let expectedResult1 = [1, 2, 3, 4]; 7 | let expectedResult2 = [10, 11, 12, 13, 14]; 8 | let expectedResult3 = [-1, 0, 1]; 9 | let result1 = range([1, 5]); 10 | let result2 = range([10, 15]); 11 | let result3 = range([-1, 2]); 12 | 13 | assert.deepEqual(result1, expectedResult1, 'should generate 1 to 4'); 14 | assert.deepEqual(result2, expectedResult2, 'should generate 10 to 14'); 15 | assert.deepEqual(result3, expectedResult3, 'should generate -1 to 1'); 16 | }); 17 | 18 | test('it generates a negative range', function(assert) { 19 | let expectedResult = [5, 4, 3, 2, 1]; 20 | let result = range([5, 0]); 21 | 22 | assert.deepEqual(result, expectedResult, 'should generate 5 to 1'); 23 | }); 24 | 25 | test('it generates an inclusive range', function(assert) { 26 | let expectedResult = [1, 2, 3, 4, 5]; 27 | let result = range([1, 5, true]); 28 | 29 | assert.deepEqual(result, expectedResult, 'should generate 1 to 5 inclusive'); 30 | }); 31 | 32 | test('it generates an inclusive negative range', function(assert) { 33 | let expectedResult = [5, 4, 3, 2, 1, 0]; 34 | let result = range([5, 0, true]); 35 | 36 | assert.deepEqual(result, expectedResult, 'should generate 5 to 0 inclusive'); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/drop-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { A as emberArray } from '@ember/array'; 3 | import { run } from '@ember/runloop'; 4 | import { module, test } from 'qunit'; 5 | import { setupRenderingTest } from 'ember-qunit'; 6 | import { render } from '@ember/test-helpers'; 7 | 8 | module('Integration | Helper | {{drop}}', function(hooks) { 9 | setupRenderingTest(hooks); 10 | 11 | test('It drops the first N entries of array', async function(assert) { 12 | this.set('array', emberArray([1, 2, 3, 4, 5])); 13 | 14 | await render(hbs` 15 | {{~#each (drop 2 this.array) as |n|~}} 16 | {{n}} 17 | {{~/each~}} 18 | `); 19 | 20 | assert.dom().hasText('345', 'first two values are dropped'); 21 | }); 22 | 23 | test('It watches for changes', async function(assert) { 24 | let array = emberArray([1, 2, 3, 4, 5]); 25 | this.set('array', array); 26 | 27 | await render(hbs` 28 | {{~#each (drop 2 this.array) as |n|~}} 29 | {{n}} 30 | {{~/each~}} 31 | `); 32 | 33 | run(() => array.unshiftObject(0)); 34 | 35 | assert.dom().hasText('2345', '0 and 1 are dropped'); 36 | }); 37 | 38 | test('It allows null array', async function(assert) { 39 | this.set('array', null); 40 | 41 | await render(hbs` 42 | this is all that will render 43 | {{#each (drop 2 this.array) as |n|}} 44 | {{n}} 45 | {{/each}} 46 | `); 47 | 48 | assert.dom().hasText('this is all that will render', 'no error is thrown'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test-app/tests/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | type SetupTestOptions, 6 | } from 'ember-qunit'; 7 | 8 | // This file exists to provide wrappers around ember-qunit's 9 | // test setup functions. This way, you can easily extend the setup that is 10 | // needed per test type. 11 | 12 | function setupApplicationTest(hooks: NestedHooks, options?: SetupTestOptions) { 13 | upstreamSetupApplicationTest(hooks, options); 14 | 15 | // Additional setup for application tests can be done here. 16 | // 17 | // For example, if you need an authenticated session for each 18 | // application test, you could do: 19 | // 20 | // hooks.beforeEach(async function () { 21 | // await authenticateSession(); // ember-simple-auth 22 | // }); 23 | // 24 | // This is also a good place to call test setup functions coming 25 | // from other addons: 26 | // 27 | // setupIntl(hooks); // ember-intl 28 | // setupMirage(hooks); // ember-cli-mirage 29 | } 30 | 31 | function setupRenderingTest(hooks: NestedHooks, options?: SetupTestOptions) { 32 | upstreamSetupRenderingTest(hooks, options); 33 | 34 | // Additional setup for rendering tests can be done here. 35 | } 36 | 37 | function setupTest(hooks: NestedHooks, options?: SetupTestOptions) { 38 | upstreamSetupTest(hooks, options); 39 | 40 | // Additional setup for unit tests can be done here. 41 | } 42 | 43 | export { setupApplicationTest, setupRenderingTest, setupTest }; 44 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/optional-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, click } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{optional}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | hooks.beforeEach(function() { 10 | this.actions = {}; 11 | this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); 12 | }); 13 | 14 | test('If the action does not exist, it passes a no-op function', async function(assert) { 15 | assert.expect(0); 16 | await render(hbs` `); 17 | await click('button'); 18 | }); 19 | 20 | // eslint-disable-next-line qunit/require-expect 21 | test('If the action does exist, it passes the given action', async function(assert) { 22 | assert.expect(1); 23 | this.set('handler', () => assert.ok(true)); 24 | await render(hbs` `); 25 | await click('button'); 26 | }); 27 | 28 | // eslint-disable-next-line qunit/require-expect 29 | test('Works in a pipe', async function(assert) { 30 | assert.expect(1); 31 | this.actions.check = (value) => assert.strictEqual(value, 42); 32 | await render(hbs` 33 | `); 34 | await click('button'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/drop-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { A as emberArray } from '@ember/array'; 3 | import { run } from '@ember/runloop'; 4 | import { module, test } from 'qunit'; 5 | import { setupRenderingTest } from 'ember-qunit'; 6 | import { render } from '@ember/test-helpers'; 7 | 8 | module('Integration | Helper | {{drop}}', function(hooks) { 9 | setupRenderingTest(hooks); 10 | 11 | test('It drops the first N entries of array', async function(assert) { 12 | this.set('array', emberArray([1, 2, 3, 4, 5])); 13 | 14 | await render(hbs` 15 | {{~#each (drop 2 this.array) as |n|~}} 16 | {{n}} 17 | {{~/each~}} 18 | `); 19 | 20 | assert.dom().hasText('345', 'first two values are dropped'); 21 | }); 22 | 23 | test('It watches for changes', async function(assert) { 24 | let array = emberArray([1, 2, 3, 4, 5]); 25 | this.set('array', array); 26 | 27 | await render(hbs` 28 | {{~#each (drop 2 this.array) as |n|~}} 29 | {{n}} 30 | {{~/each~}} 31 | `); 32 | 33 | run(() => array.unshiftObject(0)); 34 | 35 | assert.dom().hasText('2345', '0 and 1 are dropped'); 36 | }); 37 | 38 | test('It allows null array', async function(assert) { 39 | this.set('array', null); 40 | 41 | await render(hbs` 42 | this is all that will render 43 | {{#each (drop 2 this.array) as |n|}} 44 | {{n}} 45 | {{/each}} 46 | `); 47 | 48 | assert.dom().hasText('this is all that will render', 'no error is thrown'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/drop-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { A as emberArray } from '@ember/array'; 3 | import { run } from '@ember/runloop'; 4 | import { module, test } from 'qunit'; 5 | import { setupRenderingTest } from 'ember-qunit'; 6 | import { render } from '@ember/test-helpers'; 7 | 8 | module('Integration | Helper | {{drop}}', function(hooks) { 9 | setupRenderingTest(hooks); 10 | 11 | test('It drops the first N entries of array', async function(assert) { 12 | this.set('array', emberArray([1, 2, 3, 4, 5])); 13 | 14 | await render(hbs` 15 | {{~#each (drop 2 this.array) as |n|~}} 16 | {{n}} 17 | {{~/each~}} 18 | `); 19 | 20 | assert.dom().hasText('345', 'first two values are dropped'); 21 | }); 22 | 23 | test('It watches for changes', async function(assert) { 24 | let array = emberArray([1, 2, 3, 4, 5]); 25 | this.set('array', array); 26 | 27 | await render(hbs` 28 | {{~#each (drop 2 this.array) as |n|~}} 29 | {{n}} 30 | {{~/each~}} 31 | `); 32 | 33 | run(() => array.unshiftObject(0)); 34 | 35 | assert.dom().hasText('2345', '0 and 1 are dropped'); 36 | }); 37 | 38 | test('It allows null array', async function(assert) { 39 | this.set('array', null); 40 | 41 | await render(hbs` 42 | this is all that will render 43 | {{#each (drop 2 this.array) as |n|}} 44 | {{n}} 45 | {{/each}} 46 | `); 47 | 48 | assert.dom().hasText('this is all that will render', 'no error is thrown'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | Releases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/release-plan/). Once you label all your PRs correctly (see below) you will have an automatically generated PR that updates your CHANGELOG.md file and a `.release-plan.json` that is used to prepare the release once the PR is merged. 4 | 5 | ## Preparation 6 | 7 | Since the majority of the actual release process is automated, the remaining tasks before releasing are: 8 | 9 | - correctly labeling **all** pull requests that have been merged since the last release 10 | - updating pull request titles so they make sense to our users 11 | 12 | Some great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.1.0/), but the overall 13 | guiding principle here is that changelogs are for humans, not machines. 14 | 15 | When reviewing merged PR's the labels to be used are: 16 | 17 | - breaking - Used when the PR is considered a breaking change. 18 | - enhancement - Used when the PR adds a new feature or enhancement. 19 | - bug - Used when the PR fixes a bug included in a previous release. 20 | - documentation - Used when the PR adds or updates documentation. 21 | - internal - Internal changes or things that don't fit in any other category. 22 | 23 | **Note:** `release-plan` requires that **all** PRs are labeled. If a PR doesn't fit in a category it's fine to label it as `internal` 24 | 25 | ## Release 26 | 27 | Once the prep work is completed, the actual release is straight forward: you just need to merge the open [Plan Release](https://github.com/NullVoxPopuli/ember-composable-helpers/pulls?q=is%3Apr+is%3Aopen+%22Prepare+Release%22+in%3Atitle) PR 28 | -------------------------------------------------------------------------------- /test-app/tests/unit/helpers/invoke-test.js: -------------------------------------------------------------------------------- 1 | import { A } from '@ember/array'; 2 | import { resolve } from 'rsvp'; 3 | import { invoke } from 'ember-composable-helpers/helpers/invoke'; 4 | import { module, test } from 'qunit'; 5 | 6 | module('Unit | Helper | invoke', function() { 7 | test('it calls method inside objects', function(assert) { 8 | let object = { 9 | callMom() { 10 | return `calling mom in ${[...arguments]}`; 11 | } 12 | }; 13 | let action = invoke(['callMom', 1, 2, 3, object]); 14 | 15 | assert.strictEqual(action(), 'calling mom in 1,2,3', 'it calls functions'); 16 | }); 17 | 18 | test('it is promise aware', function(assert) { 19 | let done = assert.async(); 20 | let object = { 21 | func() { 22 | return resolve([1, 2, 3]); 23 | } 24 | }; 25 | 26 | let action = invoke(['func', object]); 27 | let result = action(); 28 | 29 | result.then((resolved) => { 30 | assert.deepEqual([1, 2, 3], resolved, 'it is promise aware'); 31 | done(); 32 | }); 33 | }); 34 | 35 | test('it wraps array of promises in another promise', function(assert) { 36 | let done = assert.async(); 37 | let array = A(); 38 | 39 | array.pushObject({ 40 | func() { 41 | return resolve(1); 42 | } 43 | }); 44 | array.pushObject({ 45 | func() { 46 | return resolve(2); 47 | } 48 | }); 49 | array.pushObject({ 50 | func() { 51 | return resolve(3); 52 | } 53 | }); 54 | 55 | let action = invoke(['func', array]); 56 | let result = action(); 57 | 58 | result.then((resolved) => { 59 | assert.deepEqual([1, 2, 3], resolved, 'it is promise aware'); 60 | done(); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/entries-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | entries', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it return object entries', async function(assert) { 10 | let object = { 11 | a: 1, 12 | b: 2 13 | } 14 | 15 | this.set('object', object); 16 | 17 | await render(hbs` 18 | {{#each (entries this.object) as |entry|}}{{get entry 0}}{{get entry 1}}{{/each}}`); 19 | assert.dom(this.element).hasText('a1b2'); 20 | }); 21 | 22 | test('it works with sort-by', async function(assert) { 23 | let object = { 24 | b: 2, 25 | a: 1, 26 | d: 4, 27 | c: 3 28 | } 29 | 30 | this.set('object', object); 31 | this.set('myOwnSortBy', function(a, b) { 32 | if (a[1] > b[1]) { 33 | return 1; 34 | } else if (a[1] < b[1]) { 35 | return -1; 36 | } 37 | return 0; 38 | }); 39 | await render(hbs` 40 | {{#each (sort-by this.myOwnSortBy (entries this.object)) as |entry|}}{{get entry 0}}{{/each}}`); 41 | assert.dom(this.element).hasText('abcd'); 42 | }); 43 | 44 | test('it handles undefined input', async function(assert) { 45 | await render(hbs` 46 | {{#each (entries undefined) as |key|}}{{key}}{{/each}} 47 | `); 48 | 49 | assert.dom(this.element).hasText(''); 50 | }); 51 | 52 | test('it handles null input', async function(assert) { 53 | await render(hbs` 54 | {{#each (entries null) as |key|}}{{key}}{{/each}} 55 | `); 56 | 57 | assert.dom(this.element).hasText(''); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /ember-composable-helpers/src/helpers/call.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated since ember 3.25 (with polyfill) and ember 4.5, this utility is not needed as functions can be directly invoked 3 | * 4 | * Calls a function passed within a template and returns its value. 5 | * In order to pass arguments to the function being called, you must 6 | * curry the function using the `fn` helper. 7 | * 8 | ```example 9 |
unknown>([ 17 | fn, 18 | thisArg, 19 | ]: [fn: Fn, thisArg: ThisArg]): ReturnType; 20 | export function call unknown>([fn]: [ 21 | fn: Fn, 22 | ]): ReturnType; 23 | export function call([fn, thisArg]: [fn?: undefined, thisArg?: unknown]): void; 24 | export function call([fn, thisArg]: [ 25 | fn?: (() => unknown) | undefined, 26 | thisArg?: unknown, 27 | ]): unknown { 28 | if (fn) { 29 | if (thisArg) { 30 | return fn.apply(thisArg); 31 | } else { 32 | return fn(); 33 | } 34 | } 35 | } 36 | 37 | export default function callHelper< 38 | ThisArg, 39 | Fn extends (this: ThisArg) => unknown, 40 | >(fn: Fn, thisArg: ThisArg): ReturnType; 41 | export default function callHelper unknown>( 42 | fn: Fn, 43 | ): ReturnType; 44 | export default function callHelper(fn?: undefined, thisArg?: unknown): void; 45 | export default function callHelper(fn?: () => unknown, thisArg?: unknown) { 46 | // @ts-expect-error -- This is fine, but TS doesn't like it with the overrides 47 | return call([fn, thisArg]); 48 | } 49 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/flatten-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{flatten}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it flattens an array', async function(assert) { 10 | await render(hbs` 11 | {{#each (flatten (repeat 3 (range 1 3 true))) as |number|}}{{number}}{{/each}} 12 | `); 13 | 14 | assert.dom().hasText('123123123', 'should handle a single level depth array'); 15 | }); 16 | 17 | test('it flattens an array of arrays', async function(assert) { 18 | this.set('array', [1, [2, 3], [4, [5, 6]]]); 19 | await render(hbs` 20 | {{#each (flatten this.array) as |number|}}{{number}}{{/each}} 21 | `); 22 | 23 | assert.dom().hasText('123456', 'should handle multi level depth array'); 24 | }); 25 | 26 | test('it handles empty array', async function(assert) { 27 | this.set('array', []); 28 | await render(hbs` 29 | {{#each (flatten this.array) as |number|}}{{number}}{{/each}} 30 | `); 31 | 32 | assert.dom().hasText('', 'should handle an empty array'); 33 | }); 34 | 35 | test('it handles null input', async function(assert) { 36 | await render(hbs` 37 | {{#each (flatten null) as |number|}}{{number}}{{/each}} 38 | `); 39 | 40 | assert.dom().hasText('', 'should handle null input'); 41 | }); 42 | 43 | test('it handles undefined input', async function(assert) { 44 | await render(hbs` 45 | {{#each (flatten undefined) as |number|}}{{number}}{{/each}} 46 | `); 47 | 48 | assert.dom().hasText('', 'should handle undefined input'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/unit/helpers/invoke-test.js: -------------------------------------------------------------------------------- 1 | import { A } from '@ember/array'; 2 | import { resolve } from 'rsvp'; 3 | import { invoke } from 'ember-composable-helpers/helpers/invoke'; 4 | import { module, test } from 'qunit'; 5 | 6 | module('Unit | Helper | invoke', function() { 7 | test('it calls method inside objects', function(assert) { 8 | let object = { 9 | callMom() { 10 | return `calling mom in ${[...arguments]}`; 11 | } 12 | }; 13 | let action = invoke(['callMom', 1, 2, 3, object]); 14 | 15 | assert.strictEqual(action(), 'calling mom in 1,2,3', 'it calls functions'); 16 | }); 17 | 18 | test('it is promise aware', function(assert) { 19 | let done = assert.async(); 20 | let object = { 21 | func() { 22 | return resolve([1, 2, 3]); 23 | } 24 | }; 25 | 26 | let action = invoke(['func', object]); 27 | let result = action(); 28 | 29 | result.then((resolved) => { 30 | assert.deepEqual([1, 2, 3], resolved, 'it is promise aware'); 31 | done(); 32 | }); 33 | }); 34 | 35 | test('it wraps array of promises in another promise', function(assert) { 36 | let done = assert.async(); 37 | let array = A(); 38 | 39 | array.pushObject({ 40 | func() { 41 | return resolve(1); 42 | } 43 | }); 44 | array.pushObject({ 45 | func() { 46 | return resolve(2); 47 | } 48 | }); 49 | array.pushObject({ 50 | func() { 51 | return resolve(3); 52 | } 53 | }); 54 | 55 | let action = invoke(['func', array]); 56 | let result = action(); 57 | 58 | result.then((resolved) => { 59 | assert.deepEqual([1, 2, 3], resolved, 'it is promise aware'); 60 | done(); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/unit/helpers/invoke-test.js: -------------------------------------------------------------------------------- 1 | import { A } from '@ember/array'; 2 | import { resolve } from 'rsvp'; 3 | import { invoke } from 'ember-composable-helpers/helpers/invoke'; 4 | import { module, test } from 'qunit'; 5 | 6 | module('Unit | Helper | invoke', function() { 7 | test('it calls method inside objects', function(assert) { 8 | let object = { 9 | callMom() { 10 | return `calling mom in ${[...arguments]}`; 11 | } 12 | }; 13 | let action = invoke(['callMom', 1, 2, 3, object]); 14 | 15 | assert.strictEqual(action(), 'calling mom in 1,2,3', 'it calls functions'); 16 | }); 17 | 18 | test('it is promise aware', function(assert) { 19 | let done = assert.async(); 20 | let object = { 21 | func() { 22 | return resolve([1, 2, 3]); 23 | } 24 | }; 25 | 26 | let action = invoke(['func', object]); 27 | let result = action(); 28 | 29 | result.then((resolved) => { 30 | assert.deepEqual([1, 2, 3], resolved, 'it is promise aware'); 31 | done(); 32 | }); 33 | }); 34 | 35 | test('it wraps array of promises in another promise', function(assert) { 36 | let done = assert.async(); 37 | let array = A(); 38 | 39 | array.pushObject({ 40 | func() { 41 | return resolve(1); 42 | } 43 | }); 44 | array.pushObject({ 45 | func() { 46 | return resolve(2); 47 | } 48 | }); 49 | array.pushObject({ 50 | func() { 51 | return resolve(3); 52 | } 53 | }); 54 | 55 | let action = invoke(['func', array]); 56 | let result = action(); 57 | 58 | result.then((resolved) => { 59 | assert.deepEqual([1, 2, 3], resolved, 'it is promise aware'); 60 | done(); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/entries-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | entries', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it return object entries', async function(assert) { 10 | let object = { 11 | a: 1, 12 | b: 2 13 | } 14 | 15 | this.set('object', object); 16 | 17 | await render(hbs` 18 | {{#each (entries this.object) as |entry|}}{{get entry 0}}{{get entry 1}}{{/each}}`); 19 | assert.dom(this.element).hasText('a1b2'); 20 | }); 21 | 22 | test('it works with sort-by', async function(assert) { 23 | let object = { 24 | b: 2, 25 | a: 1, 26 | d: 4, 27 | c: 3 28 | } 29 | 30 | this.set('object', object); 31 | this.set('myOwnSortBy', function(a, b) { 32 | if (a[1] > b[1]) { 33 | return 1; 34 | } else if (a[1] < b[1]) { 35 | return -1; 36 | } 37 | return 0; 38 | }); 39 | await render(hbs` 40 | {{#each (sort-by this.myOwnSortBy (entries this.object)) as |entry|}}{{get entry 0}}{{/each}}`); 41 | assert.dom(this.element).hasText('abcd'); 42 | }); 43 | 44 | test('it handles undefined input', async function(assert) { 45 | await render(hbs` 46 | {{#each (entries undefined) as |key|}}{{key}}{{/each}} 47 | `); 48 | 49 | assert.dom(this.element).hasText(''); 50 | }); 51 | 52 | test('it handles null input', async function(assert) { 53 | await render(hbs` 54 | {{#each (entries null) as |key|}}{{key}}{{/each}} 55 | `); 56 | 57 | assert.dom(this.element).hasText(''); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/entries-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | entries', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it return object entries', async function(assert) { 10 | let object = { 11 | a: 1, 12 | b: 2 13 | } 14 | 15 | this.set('object', object); 16 | 17 | await render(hbs` 18 | {{#each (entries this.object) as |entry|}}{{get entry 0}}{{get entry 1}}{{/each}}`); 19 | assert.dom(this.element).hasText('a1b2'); 20 | }); 21 | 22 | test('it works with sort-by', async function(assert) { 23 | let object = { 24 | b: 2, 25 | a: 1, 26 | d: 4, 27 | c: 3 28 | } 29 | 30 | this.set('object', object); 31 | this.set('myOwnSortBy', function(a, b) { 32 | if (a[1] > b[1]) { 33 | return 1; 34 | } else if (a[1] < b[1]) { 35 | return -1; 36 | } 37 | return 0; 38 | }); 39 | await render(hbs` 40 | {{#each (sort-by this.myOwnSortBy (entries this.object)) as |entry|}}{{get entry 0}}{{/each}}`); 41 | assert.dom(this.element).hasText('abcd'); 42 | }); 43 | 44 | test('it handles undefined input', async function(assert) { 45 | await render(hbs` 46 | {{#each (entries undefined) as |key|}}{{key}}{{/each}} 47 | `); 48 | 49 | assert.dom(this.element).hasText(''); 50 | }); 51 | 52 | test('it handles null input', async function(assert) { 53 | await render(hbs` 54 | {{#each (entries null) as |key|}}{{key}}{{/each}} 55 | `); 56 | 57 | assert.dom(this.element).hasText(''); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/flatten-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{flatten}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it flattens an array', async function(assert) { 10 | await render(hbs` 11 | {{#each (flatten (repeat 3 (range 1 3 true))) as |number|}}{{number}}{{/each}} 12 | `); 13 | 14 | assert.dom().hasText('123123123', 'should handle a single level depth array'); 15 | }); 16 | 17 | test('it flattens an array of arrays', async function(assert) { 18 | this.set('array', [1, [2, 3], [4, [5, 6]]]); 19 | await render(hbs` 20 | {{#each (flatten this.array) as |number|}}{{number}}{{/each}} 21 | `); 22 | 23 | assert.dom().hasText('123456', 'should handle multi level depth array'); 24 | }); 25 | 26 | test('it handles empty array', async function(assert) { 27 | this.set('array', []); 28 | await render(hbs` 29 | {{#each (flatten this.array) as |number|}}{{number}}{{/each}} 30 | `); 31 | 32 | assert.dom().hasText('', 'should handle an empty array'); 33 | }); 34 | 35 | test('it handles null input', async function(assert) { 36 | await render(hbs` 37 | {{#each (flatten null) as |number|}}{{number}}{{/each}} 38 | `); 39 | 40 | assert.dom().hasText('', 'should handle null input'); 41 | }); 42 | 43 | test('it handles undefined input', async function(assert) { 44 | await render(hbs` 45 | {{#each (flatten undefined) as |number|}}{{number}}{{/each}} 46 | `); 47 | 48 | assert.dom().hasText('', 'should handle undefined input'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/flatten-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render } from '@ember/test-helpers'; 5 | 6 | module('Integration | Helper | {{flatten}}', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it flattens an array', async function(assert) { 10 | await render(hbs` 11 | {{#each (flatten (repeat 3 (range 1 3 true))) as |number|}}{{number}}{{/each}} 12 | `); 13 | 14 | assert.dom().hasText('123123123', 'should handle a single level depth array'); 15 | }); 16 | 17 | test('it flattens an array of arrays', async function(assert) { 18 | this.set('array', [1, [2, 3], [4, [5, 6]]]); 19 | await render(hbs` 20 | {{#each (flatten this.array) as |number|}}{{number}}{{/each}} 21 | `); 22 | 23 | assert.dom().hasText('123456', 'should handle multi level depth array'); 24 | }); 25 | 26 | test('it handles empty array', async function(assert) { 27 | this.set('array', []); 28 | await render(hbs` 29 | {{#each (flatten this.array) as |number|}}{{number}}{{/each}} 30 | `); 31 | 32 | assert.dom().hasText('', 'should handle an empty array'); 33 | }); 34 | 35 | test('it handles null input', async function(assert) { 36 | await render(hbs` 37 | {{#each (flatten null) as |number|}}{{number}}{{/each}} 38 | `); 39 | 40 | assert.dom().hasText('', 'should handle null input'); 41 | }); 42 | 43 | test('it handles undefined input', async function(assert) { 44 | await render(hbs` 45 | {{#each (flatten undefined) as |number|}}{{number}}{{/each}} 46 | `); 47 | 48 | assert.dom().hasText('', 'should handle undefined input'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test-app-min-supported/tests/integration/helpers/queue-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { resolve } from 'rsvp'; 3 | import { module, test } from 'qunit'; 4 | import { setupRenderingTest } from 'ember-qunit'; 5 | import { render, click } from '@ember/test-helpers'; 6 | 7 | module('Integration | Helper | {{queue}}', function(hooks) { 8 | setupRenderingTest(hooks); 9 | 10 | hooks.beforeEach(function() { 11 | this.actions = {}; 12 | this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); 13 | }); 14 | 15 | test('it queues actions', async function(assert) { 16 | this.actions.doAThing = () => null; 17 | this.actions.process = (x) => this.set('value', x * x); 18 | this.actions.undoAThing = () => null; 19 | this.set('value', 2); 20 | await render(hbs` 21 |

{{this.value}}

22 | 25 | `); 26 | 27 | assert.dom('p').hasText('2', 'precond - should render 2'); 28 | await click('button'); 29 | assert.dom('p').hasText('4', 'should render 4'); 30 | }); 31 | 32 | test('it handles promises', async function(assert) { 33 | this.set('value', 3); 34 | this.actions.doAThingThatTakesTime = resolve; 35 | this.actions.process = (x) => this.set('value', x * x); 36 | await render(hbs` 37 |

{{this.value}}

38 | 41 | `); 42 | 43 | assert.dom('p').hasText('3', 'precond - should render 3'); 44 | await click('button'); 45 | assert.dom('p').hasText('9', 'should render 9'); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/group-by-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { A as emberArray } from '@ember/array'; 3 | import { run } from '@ember/runloop'; 4 | import { set } from '@ember/object'; 5 | import { module, test } from 'qunit'; 6 | import { setupRenderingTest } from 'ember-qunit'; 7 | import { render } from '@ember/test-helpers'; 8 | 9 | module('Integration | Helper | {{group-by}}', function(hooks) { 10 | setupRenderingTest(hooks); 11 | 12 | test('It groups by given property', async function(assert) { 13 | this.set('array', emberArray([ 14 | { category: 'a', name: 'a' }, 15 | { category: 'b', name: 'c' }, 16 | { category: 'a', name: 'b' }, 17 | { category: 'b', name: 'd' } 18 | ])); 19 | 20 | await render(hbs` 21 | {{~#each-in (group-by 'category' this.array) as |category entries|~}} 22 | {{~category~}} 23 | {{~#each entries as |entry|~}}{{~entry.name~}}{{~/each~}} 24 | {{~/each-in~}} 25 | `); 26 | 27 | assert.dom().hasText('aabbcd', 'aabbcd is the right order'); 28 | }); 29 | 30 | test('It watches for changes', async function(assert) { 31 | let array = emberArray([ 32 | { category: 'a', name: 'a' }, 33 | { category: 'b', name: 'c' }, 34 | { category: 'a', name: 'b' }, 35 | { category: 'b', name: 'd' } 36 | ]); 37 | 38 | this.set('array', array); 39 | 40 | await render(hbs` 41 | {{~#each-in (group-by 'category' this.array) as |category entries|~}} 42 | {{~category~}} 43 | {{~#each entries as |entry|~}}{{~entry.name~}}{{~/each~}} 44 | {{~/each-in~}} 45 | `); 46 | 47 | run(() => set(array.objectAt(3), 'category', 'c')); 48 | 49 | assert.dom().hasText('aabbccd', 'aabbccd is the right order'); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test-app-min-supported-classic/tests/integration/helpers/queue-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { resolve } from 'rsvp'; 3 | import { module, test } from 'qunit'; 4 | import { setupRenderingTest } from 'ember-qunit'; 5 | import { render, click } from '@ember/test-helpers'; 6 | 7 | module('Integration | Helper | {{queue}}', function(hooks) { 8 | setupRenderingTest(hooks); 9 | 10 | hooks.beforeEach(function() { 11 | this.actions = {}; 12 | this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); 13 | }); 14 | 15 | test('it queues actions', async function(assert) { 16 | this.actions.doAThing = () => null; 17 | this.actions.process = (x) => this.set('value', x * x); 18 | this.actions.undoAThing = () => null; 19 | this.set('value', 2); 20 | await render(hbs` 21 |

{{this.value}}

22 | 25 | `); 26 | 27 | assert.dom('p').hasText('2', 'precond - should render 2'); 28 | await click('button'); 29 | assert.dom('p').hasText('4', 'should render 4'); 30 | }); 31 | 32 | test('it handles promises', async function(assert) { 33 | this.set('value', 3); 34 | this.actions.doAThingThatTakesTime = resolve; 35 | this.actions.process = (x) => this.set('value', x * x); 36 | await render(hbs` 37 |

{{this.value}}

38 | 41 | `); 42 | 43 | assert.dom('p').hasText('3', 'precond - should render 3'); 44 | await click('button'); 45 | assert.dom('p').hasText('9', 'should render 9'); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/queue-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { resolve } from 'rsvp'; 3 | import { module, test } from 'qunit'; 4 | import { setupRenderingTest } from 'ember-qunit'; 5 | import { render, click } from '@ember/test-helpers'; 6 | 7 | module('Integration | Helper | {{queue}}', function(hooks) { 8 | setupRenderingTest(hooks); 9 | 10 | hooks.beforeEach(function() { 11 | this.actions = {}; 12 | this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); 13 | }); 14 | 15 | test('it queues actions', async function(assert) { 16 | this.actions.doAThing = () => null; 17 | this.actions.process = (x) => this.set('value', x * x); 18 | this.actions.undoAThing = () => null; 19 | this.set('value', 2); 20 | await render(hbs` 21 |

{{this.value}}

22 | 25 | `); 26 | 27 | assert.dom('p').hasText('2', 'precond - should render 2'); 28 | await click('button'); 29 | assert.dom('p').hasText('4', 'should render 4'); 30 | }); 31 | 32 | test('it handles promises', async function(assert) { 33 | this.set('value', 3); 34 | this.actions.doAThingThatTakesTime = resolve; 35 | this.actions.process = (x) => this.set('value', x * x); 36 | await render(hbs` 37 |

{{this.value}}

38 | 41 | `); 42 | 43 | assert.dom('p').hasText('3', 'precond - should render 3'); 44 | await click('button'); 45 | assert.dom('p').hasText('9', 'should render 9'); 46 | }); 47 | }); 48 | --------------------------------------------------------------------------------