├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep ├── component-managers │ └── sparkles.ts ├── index.ts └── tracked.ts ├── app ├── .gitkeep └── component-managers │ └── sparkles.js ├── blueprints ├── install-sparkles │ └── index.js ├── module-unification.js ├── sparkles-component-addon │ ├── files │ │ └── __root__ │ │ │ ├── __path__ │ │ │ └── __name__.js │ │ │ └── __templatepath__ │ │ │ └── __templatename__.js │ └── index.js ├── sparkles-component-test │ ├── index.js │ ├── mocha-0.12-files │ │ └── __root__ │ │ │ └── __testType__ │ │ │ └── __path__ │ │ │ └── __test__.__ext__ │ ├── mocha-files │ │ └── __root__ │ │ │ └── __testType__ │ │ │ └── __path__ │ │ │ └── __test__.__ext__ │ ├── qunit-files │ │ └── __root__ │ │ │ └── __testType__ │ │ │ └── __path__ │ │ │ └── __test__.__ext__ │ └── qunit-rfc-232-files │ │ └── __root__ │ │ └── __testType__ │ │ └── __path__ │ │ └── __test__.__ext__ ├── sparkles-component │ ├── files │ │ └── __root__ │ │ │ ├── __path__ │ │ │ └── __name__.__ext__ │ │ │ └── __templatepath__ │ │ │ └── __templatename__.hbs │ ├── index.js │ └── module-unification-files │ │ └── __root__ │ │ └── __path__ │ │ ├── component.__ext__ │ │ └── template.hbs └── test-framework-detector.js ├── config ├── ember-try.js └── environment.js ├── ember-addon-main.js ├── ember-cli-build.js ├── node-tests └── blueprints │ └── sparkle-test.js ├── package.json ├── testem.js ├── tests ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ └── conference-speakers.js │ │ ├── config │ │ │ └── environment.d.ts │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── resolver.js │ │ ├── router.js │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ ├── components │ │ │ └── conference-speakers.hbs │ │ │ └── conference-speakers.hbs │ ├── config │ │ ├── environment.js │ │ ├── optional-features.json │ │ └── targets.js │ └── public │ │ └── robots.txt ├── helpers │ └── .gitkeep ├── index.html ├── integration │ ├── .gitkeep │ └── components │ │ ├── glimmer-component-guide-test.js │ │ ├── sparkles-component-test.js │ │ ├── tracked-js-test.js │ │ └── tracked-ts-test.ts ├── test-helper.js └── unit │ └── .gitkeep ├── tsconfig.json ├── types ├── @ember-decorators │ └── utils │ │ └── decorator.d.ts ├── @ember │ └── component.d.ts ├── dummy │ └── index.d.ts ├── ember-compatibility-helpers.d.ts └── ember │ └── index.d.ts ├── vendor └── .gitkeep └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | 12 | # misc 13 | /coverage/ 14 | 15 | # ember-try 16 | /.node_modules.ember-try/ 17 | /bower.json.ember-try 18 | /package.json.ember-try 19 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | ecmaVersion: 2017, 6 | sourceType: 'module' 7 | }, 8 | plugins: [ 9 | 'ember' 10 | ], 11 | extends: [ 12 | 'eslint:recommended', 13 | 'plugin:ember/recommended' 14 | ], 15 | env: { 16 | browser: true 17 | }, 18 | rules: { 19 | }, 20 | overrides: [ 21 | // node files 22 | { 23 | files: [ 24 | 'ember-cli-build.js', 25 | 'ember-addon-main.js', 26 | 'testem.js', 27 | 'blueprints/*/index.js', 28 | 'config/**/*.js', 29 | 'tests/dummy/config/**/*.js' 30 | ], 31 | parserOptions: { 32 | sourceType: 'script', 33 | ecmaVersion: 2015 34 | }, 35 | env: { 36 | browser: false, 37 | node: true 38 | }, 39 | plugins: ['node'], 40 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 41 | // add your custom rules and overrides for node files here 42 | }) 43 | }, 44 | { 45 | files: ['node-tests/*/**.js'], 46 | parserOptions: { 47 | sourceType: 'script', 48 | ecmaVersion: 2015 49 | }, 50 | env: { 51 | mocha: true, 52 | node: true 53 | }, 54 | plugins: ['node'], 55 | rules: Object.assign( 56 | {}, 57 | require('eslint-plugin-node').configs.recommended.rules, 58 | { 59 | // add your custom rules and overrides for node files here 60 | } 61 | ) 62 | } 63 | ] 64 | }; 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/ 15 | /libpeerconnection.log 16 | /npm-debug.log* 17 | /testem.log 18 | /yarn-error.log 19 | 20 | # ember-try 21 | /.node_modules.ember-try/ 22 | /bower.json.ember-try 23 | /package.json.ember-try 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.eslintignore 13 | /.eslintrc.js 14 | /.gitignore 15 | /.watchmanconfig 16 | /.travis.yml 17 | /bower.json 18 | /config/ember-try.js 19 | /ember-cli-build.js 20 | /testem.js 21 | /tests/ 22 | /yarn.lock 23 | .gitkeep 24 | 25 | # ember-try 26 | /.node_modules.ember-try/ 27 | /bower.json.ember-try 28 | /package.json.ember-try 29 | /node-tests 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | # we recommend testing addons with the same minimum supported node version as Ember CLI 4 | # so that your addon works for all apps 5 | - '8' 6 | 7 | sudo: false 8 | dist: trusty 9 | 10 | addons: 11 | chrome: stable 12 | 13 | cache: 14 | yarn: true 15 | 16 | branches: 17 | only: 18 | - master 19 | # npm version tags 20 | - /^v\d+\.\d+\.\d+/ 21 | 22 | jobs: 23 | fail_fast: true 24 | allow_failures: 25 | - env: EMBER_TRY_SCENARIO=ember-canary 26 | env: 27 | global: 28 | # See https://git.io/vdao3 for details. 29 | - JOBS=1 30 | include: 31 | # runs linting and tests with current locked deps 32 | - stage: 'Tests' 33 | env: EMBER_TRY_SCENARIO=ember-lts-3.4 34 | env: EMBER_TRY_SCENARIO=ember-lts-3.4-legacy-decorators 35 | - env: EMBER_TRY_SCENARIO=ember-release 36 | - env: EMBER_TRY_SCENARIO=ember-release-legacy-decorators 37 | - env: EMBER_TRY_SCENARIO=ember-beta 38 | - env: EMBER_TRY_SCENARIO=ember-beta-legacy-decorators 39 | - env: EMBER_TRY_SCENARIO=ember-canary 40 | - env: EMBER_TRY_SCENARIO=ember-canary-legacy-decorators 41 | - env: EMBER_TRY_SCENARIO=ember-default 42 | - env: EMBER_TRY_SCENARIO=ember-default-legacy-decorators 43 | - name: 'Blueprints' 44 | script: yarn nodetest 45 | 46 | before_install: 47 | - curl -o- -L https://yarnpkg.com/install.sh | bash 48 | - export PATH=$HOME/.yarn/bin:$PATH 49 | 50 | install: 51 | - yarn install --no-lockfile --non-interactive 52 | 53 | script: 54 | - yarn lint:js 55 | # Usually, it's ok to finish the test scenario without reverting 56 | # to the addon's original dependency state, skipping "cleanup". 57 | - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup 58 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sparkles-component 2 | ============================================================================== 3 | 4 | ![R. Sparkles](https://media.giphy.com/media/NwkYPLmQSLmhy/giphy.gif) 5 | 6 | This addon was created to experiment with `@glimmer/component` style APIs in Ember apps via 7 | existing public APIs (with Ember 3.4+). 8 | 9 | **deprecated** Use [@glimmer/component](https://guides.emberjs.com/release/components/introducing-components/) instead. 10 | 11 | License 12 | ------------------------------------------------------------------------------ 13 | 14 | This project is licensed under the [MIT License](LICENSE.md). 15 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwjblue/sparkles-component/75e277095b2d95edc1682215c974cb1ce8b73627/addon/.gitkeep -------------------------------------------------------------------------------- /addon/component-managers/sparkles.ts: -------------------------------------------------------------------------------- 1 | import { set } from '@ember/object'; 2 | import { getOwner, setOwner } from '@ember/application'; 3 | import ApplicationInstance from '@ember/application/instance'; 4 | import { capabilities } from '@ember/component'; 5 | import SparklesComponent from '../'; 6 | 7 | export interface ComponentManagerArgs { 8 | named: object; 9 | positional: any[]; 10 | } 11 | type CreateComponentResult = SparklesComponent & { ___createComponentResult: true }; 12 | 13 | export default class SparklesComponentManager { 14 | static create(attrs: any) { 15 | let owner = getOwner(attrs); 16 | return new this(owner); 17 | } 18 | capabilities: any; 19 | constructor(owner: ApplicationInstance) { 20 | setOwner(this, owner); 21 | this.capabilities = capabilities('3.4', { 22 | destructor: true, 23 | asyncLifecycleCallbacks: true, 24 | }); 25 | } 26 | 27 | createComponent(Klass: typeof SparklesComponent, args: ComponentManagerArgs): CreateComponentResult { 28 | let instance = new Klass(args.named); 29 | setOwner(instance, getOwner(this)); 30 | return instance as CreateComponentResult; 31 | } 32 | 33 | updateComponent(component: CreateComponentResult, args: ComponentManagerArgs) { 34 | set(component, 'args', args.named); 35 | } 36 | 37 | destroyComponent(component: CreateComponentResult) { 38 | component.destroy(); 39 | } 40 | 41 | getContext(component: CreateComponentResult) { 42 | return component; 43 | } 44 | 45 | didCreateComponent(component: CreateComponentResult) { 46 | component.didInsertElement(); 47 | } 48 | 49 | didUpdateComponent(component: CreateComponentResult) { 50 | component.didUpdate(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /addon/index.ts: -------------------------------------------------------------------------------- 1 | import ApplicationInstance from '@ember/application/instance'; 2 | import { setComponentManager } from '@ember/component'; 3 | import { gte } from 'ember-compatibility-helpers'; 4 | import SparklesComponentManager from './component-managers/sparkles'; 5 | 6 | class SparklesComponent { 7 | constructor(public args: T) {} 8 | 9 | didInsertElement() {} 10 | didUpdate() {} 11 | // TODO: should we have this? 12 | // didRender() {} 13 | destroy() {} 14 | } 15 | 16 | if (gte('3.8.0-beta.1')) { 17 | setComponentManager((owner: ApplicationInstance) => { 18 | return new SparklesComponentManager(owner) 19 | }, SparklesComponent); 20 | } else { 21 | setComponentManager('sparkles', SparklesComponent); 22 | } 23 | 24 | export default SparklesComponent; 25 | 26 | export { tracked } from './tracked'; 27 | 28 | -------------------------------------------------------------------------------- /addon/tracked.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@ember/debug'; 2 | import { DEBUG } from '@glimmer/env'; 3 | import { notifyPropertyChange } from '@ember/object'; 4 | import { addObserver } from '@ember/object/observers'; 5 | import { decoratorWithParams } from '@ember-decorators/utils/decorator'; 6 | 7 | function setupObservers(instance: O, dependentKeys: (keyof O)[], notifyMethod: (() => void)) { 8 | for (let i = 0; i < dependentKeys.length; i++) { 9 | let dependentKey = dependentKeys[i]; 10 | addObserver(instance, dependentKey, instance, notifyMethod); 11 | } 12 | } 13 | 14 | function descriptorForTrackedComputedProperty(key: string | symbol, desc: PropertyDescriptor, dependencies?: string[]) { 15 | // TODO: really should use WeakSet here, but that isn't available on IE11 16 | const OBSERVERS_SETUP = new WeakMap(); 17 | 18 | assert( 19 | `You cannot use property paths with the tracked decorator, but for ${String(key)} you specified \`${(dependencies || []).join('`, `')}\`.`, 20 | (function() { 21 | if (dependencies === undefined) return true; // @tracked() 22 | for (let i = 0; i < dependencies.length; i++) { 23 | if (dependencies[i] !== undefined && dependencies[i].indexOf('.') > -1) { 24 | return false; 25 | } 26 | } 27 | 28 | return true; 29 | })() 30 | ); 31 | const getterProvided = desc.get; 32 | const setterProvided = desc.set; 33 | if (!getterProvided) { 34 | throw new Error(`@tracked - property descriptor for ${String(key)} must include a get() function`); 35 | } 36 | 37 | // will be bound to the instance when invoked 38 | function notify(this: object) { 39 | if (typeof key === 'string') { 40 | notifyPropertyChange(this, key); 41 | } else if (DEBUG) { 42 | throw new Error(`@tracked - unsupported property type ${String(key)}`); 43 | } 44 | } 45 | 46 | desc.get = function() { 47 | if (!OBSERVERS_SETUP.has(this) && Array.isArray(dependencies)) { 48 | setupObservers(this, dependencies, notify); 49 | } 50 | OBSERVERS_SETUP.set(this, true); 51 | 52 | return getterProvided.call(this); 53 | }; 54 | 55 | if (setterProvided) { 56 | desc.set = function(value) { 57 | if (typeof key === 'string') { 58 | notifyPropertyChange(this, key); 59 | setterProvided.call(this, value); 60 | } else if (DEBUG) { 61 | throw new Error(`@tracked - unsupported property type ${String(key)}`); 62 | } 63 | }; 64 | } 65 | 66 | return desc; 67 | } 68 | 69 | function installTrackedProperty(key: string | symbol, descriptor?: PropertyDescriptor, initializer?: () => any): PropertyDescriptor { 70 | let values = new WeakMap(); 71 | 72 | let get; 73 | if (typeof initializer === 'function') { 74 | get = function(this: object) { 75 | if (values.has(this)) { 76 | return values.get(this); 77 | } else { 78 | let value = initializer.call(this); 79 | values.set(this, value); 80 | return value; 81 | } 82 | }; 83 | } else { 84 | get = function(this: object) { 85 | return values.get(this); 86 | } 87 | } 88 | 89 | return { 90 | configurable: descriptor ? descriptor.configurable : true, 91 | enumerable: descriptor ? descriptor.enumerable : true, 92 | get, 93 | set(value) { 94 | if (typeof key === 'string') { 95 | values.set(this, value); 96 | notifyPropertyChange(this, key); 97 | } else if (DEBUG) { 98 | throw new Error(`@tracked - unsupported property type ${String(key)}`); 99 | } 100 | } 101 | }; 102 | } 103 | 104 | function _tracked( 105 | key: string | symbol, 106 | descriptor?: PropertyDescriptor, 107 | initializer?: () => any, 108 | dependencies?: string[] 109 | ): PropertyDescriptor { 110 | if (!descriptor || typeof descriptor.get !== 'function' && typeof descriptor.set !== 'function') { 111 | return installTrackedProperty(key, descriptor, initializer); 112 | } else { 113 | return descriptorForTrackedComputedProperty(key, descriptor, dependencies); 114 | } 115 | } 116 | 117 | // TODO: replace return w/ PropertyDescriptor once TS gets their decorator act together 118 | type TSDecorator = (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => void; 119 | type TrackedDecorator = TSDecorator & ((...args: string[]) => TSDecorator); 120 | 121 | export const tracked: TrackedDecorator = decoratorWithParams((desc, params = []) => { 122 | assert(`@tracked - Can only be used on class fields.`, desc.kind === 'field' || desc.kind === 'method'); 123 | const descriptor = _tracked(desc.key, desc.descriptor, desc.initializer, params); 124 | 125 | return { 126 | ...desc, 127 | descriptor, 128 | kind: 'method', 129 | initializer: undefined 130 | }; 131 | }); -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwjblue/sparkles-component/75e277095b2d95edc1682215c974cb1ce8b73627/app/.gitkeep -------------------------------------------------------------------------------- /app/component-managers/sparkles.js: -------------------------------------------------------------------------------- 1 | export { default } from 'sparkles-component/component-managers/sparkles'; 2 | -------------------------------------------------------------------------------- /blueprints/install-sparkles/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | description: 'Setup the sparkles-component library', 4 | normalizeEntityName() {} 5 | }; 6 | -------------------------------------------------------------------------------- /blueprints/module-unification.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | isModuleUnificationProject(project) { 6 | return ( 7 | project && project.isModuleUnification && project.isModuleUnification() 8 | ); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /blueprints/sparkles-component-addon/files/__root__/__path__/__name__.js: -------------------------------------------------------------------------------- 1 | export { default } from '<%= modulePath %>'; 2 | -------------------------------------------------------------------------------- /blueprints/sparkles-component-addon/files/__root__/__templatepath__/__templatename__.js: -------------------------------------------------------------------------------- 1 | export { default } from '<%= templatePath %>'; 2 | -------------------------------------------------------------------------------- /blueprints/sparkles-component-addon/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const stringUtil = require('ember-cli-string-utils'); 5 | const getPathOption = require('ember-cli-get-component-path-option'); 6 | const normalizeEntityName = require('ember-cli-normalize-entity-name'); 7 | 8 | module.exports = { 9 | description: 'Generates a component.', 10 | 11 | fileMapTokens() { 12 | return { 13 | // path of the folder, containing the component JS/TS re-export module 14 | __path__({ pod, podPath, locals, dasherizedModuleName }) { 15 | if (pod) { 16 | return path.join(podPath, locals.path, dasherizedModuleName); 17 | } 18 | return 'components'; 19 | }, 20 | // name of the component JS/TS re-export module 21 | __name__({ pod, dasherizedModuleName }) { 22 | if (pod) { 23 | return 'component'; 24 | } 25 | return dasherizedModuleName; 26 | }, 27 | // name of the component JS/TS re-export and HBS re-export 28 | __root__({ inRepoAddon }) { 29 | if (inRepoAddon) { 30 | return path.join('lib', inRepoAddon, 'app'); 31 | } 32 | return 'app'; 33 | }, 34 | // path of the containing folder, within __root__ where the HBS 35 | // re-export module is to be placed 36 | __templatepath__({ pod, podPath, locals, dasherizedModuleName }) { 37 | if (pod) { 38 | return path.join(podPath, locals.path, dasherizedModuleName); 39 | } 40 | return 'templates/components'; 41 | }, 42 | // name of HBS re-export module 43 | __templatename__({ pod, dasherizedModuleName }) { 44 | if (pod) { 45 | return 'template'; 46 | } 47 | return dasherizedModuleName; 48 | } 49 | }; 50 | }, 51 | 52 | normalizeEntityName(entityName) { 53 | return normalizeEntityName(entityName); 54 | }, 55 | 56 | locals(options) { 57 | const { inRepoAddon, inDummy, project, entity, pod } = options; 58 | const addonRawName = inRepoAddon ? inRepoAddon : project.name(); 59 | const addonName = stringUtil.dasherize(addonRawName); 60 | const fileName = stringUtil.dasherize(entity.name); 61 | let templatePath = ''; 62 | let importPathName = [addonName, 'components', fileName].join('/'); 63 | 64 | // if we're in an addon, build import statement 65 | if (project.isEmberCLIAddon() || (inRepoAddon && !inDummy)) { 66 | if (pod) { 67 | templatePath = './template'; 68 | } else { 69 | templatePath = [ 70 | addonName, 71 | 'templates/components', 72 | stringUtil.dasherize(entity.name) 73 | ].join('/'); 74 | } 75 | } 76 | 77 | if (pod) { 78 | importPathName = [addonName, 'components', fileName, 'component'].join( 79 | '/' 80 | ); 81 | } 82 | 83 | return { 84 | modulePath: importPathName, 85 | templatePath, 86 | path: getPathOption(options) 87 | }; 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /blueprints/sparkles-component-test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const stringUtil = require('ember-cli-string-utils'); 5 | const isPackageMissing = require('ember-cli-is-package-missing'); 6 | const getPathOption = require('ember-cli-get-component-path-option'); 7 | 8 | const useTestFrameworkDetector = require('../test-framework-detector'); 9 | const isModuleUnificationProject = require('../module-unification') 10 | .isModuleUnificationProject; 11 | 12 | module.exports = useTestFrameworkDetector({ 13 | description: 'Generates a component integration or unit test.', 14 | 15 | availableOptions: [ 16 | { 17 | name: 'test-type', 18 | type: ['integration', 'unit'], 19 | default: 'integration', 20 | aliases: [ 21 | { i: 'integration' }, 22 | { u: 'unit' }, 23 | { integration: 'integration' }, 24 | { unit: 'unit' } 25 | ] 26 | }, 27 | { 28 | name: 'lang', 29 | type: String 30 | } 31 | ], 32 | 33 | fileMapTokens() { 34 | if (isModuleUnificationProject(this.project)) { 35 | return { 36 | __ext__(options) { 37 | return options.locals.lang; 38 | }, 39 | __test__() { 40 | return 'component-test'; 41 | }, 42 | __root__(options) { 43 | if (options.inRepoAddon) { 44 | return path.join('packages', options.inRepoAddon, 'src'); 45 | } 46 | return 'src'; 47 | }, 48 | __testType__(options) { 49 | if (options.locals.testType === 'unit') { 50 | throw "The --unit flag isn't supported within a module unification app"; 51 | } 52 | 53 | return ''; 54 | }, 55 | __path__(options) { 56 | if (options.pod) { 57 | throw "Pods aren't supported within a module unification app"; 58 | } 59 | return path.join('ui', 'components', options.dasherizedModuleName); 60 | } 61 | }; 62 | } else { 63 | return { 64 | __ext__(options) { 65 | return options.locals.lang; 66 | }, 67 | __root__() { 68 | return 'tests'; 69 | }, 70 | __testType__(options) { 71 | return options.locals.testType || 'integration'; 72 | }, 73 | __path__(options) { 74 | if (options.pod) { 75 | return path.join( 76 | options.podPath, 77 | options.locals.path, 78 | options.dasherizedModuleName 79 | ); 80 | } 81 | return 'components'; 82 | } 83 | }; 84 | } 85 | }, 86 | getDefaultLang(options) { 87 | // if the ember-cli-typescript addon is detected, use ts as default 88 | if ('ember-cli-typescript' in options.project.addonPackages) return 'ts'; 89 | else return 'js'; // otherwise use js as default 90 | }, 91 | locals(options) { 92 | let dasherizedModuleName = stringUtil.dasherize(options.entity.name); 93 | let componentPathName = dasherizedModuleName; 94 | let testType = options.testType || 'integration'; 95 | 96 | let friendlyTestDescription = [ 97 | testType === 'unit' ? 'Unit' : 'Integration', 98 | 'Component', 99 | dasherizedModuleName 100 | ].join(' | '); 101 | 102 | if (options.pod && options.path !== 'components' && options.path !== '') { 103 | componentPathName = [options.path, dasherizedModuleName] 104 | .filter(Boolean) 105 | .join('/'); 106 | } else if (isModuleUnificationProject(this.project)) { 107 | if (options.inRepoAddon) { 108 | componentPathName = `${options.inRepoAddon}::${dasherizedModuleName}`; 109 | } else if (this.project.isEmberCLIAddon()) { 110 | componentPathName = `${this.project.pkg.name}::${dasherizedModuleName}`; 111 | } 112 | } 113 | const { lang = this.getDefaultLang(options) } = options; 114 | 115 | return { 116 | path: getPathOption(options), 117 | testType, 118 | lang, 119 | componentPathName, 120 | friendlyTestDescription: friendlyTestDescription 121 | }; 122 | }, 123 | 124 | afterInstall(options) { 125 | if ( 126 | !options.dryRun && 127 | options.testType === 'integration' && 128 | isPackageMissing(this, 'ember-cli-htmlbars-inline-precompile') 129 | ) { 130 | return this.addPackagesToProject([ 131 | { name: 'ember-cli-htmlbars-inline-precompile', target: '^0.3.1' } 132 | ]); 133 | } 134 | } 135 | }); 136 | -------------------------------------------------------------------------------- /blueprints/sparkles-component-test/mocha-0.12-files/__root__/__testType__/__path__/__test__.__ext__: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | import { setupComponentTest } from 'ember-mocha';<% if (testType === 'integration') { %> 4 | import hbs from 'htmlbars-inline-precompile';<% } %> 5 | 6 | describe('<%= friendlyTestDescription %>', function() { 7 | setupComponentTest('<%= componentPathName %>', { 8 | <% if (testType === 'integration' ) { %>integration: true<% } else if(testType === 'unit') { %>// Specify the other units that are required for this test 9 | // needs: ['component:foo', 'helper:bar'], 10 | unit: true<% } %> 11 | }); 12 | 13 | it('renders', function() { 14 | <% if (testType === 'integration' ) { %>// Set any properties with this.set('myProperty', 'value'); 15 | // Handle any actions with this.on('myAction', function(val) { ... }); 16 | // Template block usage: 17 | // this.render(hbs` 18 | // {{#<%= dasherizedModuleName %>}} 19 | // template content 20 | // {{/<%= dasherizedModuleName %>}} 21 | // `); 22 | 23 | this.render(hbs`{{<%= dasherizedModuleName %>}}`); 24 | expect(this.$()).to.have.length(1);<% } else if(testType === 'unit') { %>// creates the component instance 25 | let component = this.subject(); 26 | // renders the component on the page 27 | this.render(); 28 | expect(component).to.be.ok; 29 | expect(this.$()).to.have.length(1);<% } %> 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /blueprints/sparkles-component-test/mocha-files/__root__/__testType__/__path__/__test__.__ext__: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describeComponent, it } from 'ember-mocha';<% if (testType === 'integration') { %> 3 | import hbs from 'htmlbars-inline-precompile';<% } %> 4 | 5 | describeComponent('<%= componentPathName %>', '<%= friendlyTestDescription %>', 6 | { 7 | <% if (testType === 'integration' ) { %>integration: true<% } else if(testType === 'unit') { %>// Specify the other units that are required for this test 8 | // needs: ['component:foo', 'helper:bar'], 9 | unit: true<% } %> 10 | }, 11 | function() { 12 | it('renders', function() { 13 | <% if (testType === 'integration' ) { %>// Set any properties with this.set('myProperty', 'value'); 14 | // Handle any actions with this.on('myAction', function(val) { ... }); 15 | // Template block usage: 16 | // this.render(hbs` 17 | // {{#<%= dasherizedModuleName %>}} 18 | // template content 19 | // {{/<%= dasherizedModuleName %>}} 20 | // `); 21 | 22 | this.render(hbs`{{<%= dasherizedModuleName %>}}`); 23 | expect(this.$()).to.have.length(1);<% } else if(testType === 'unit') { %>// creates the component instance 24 | let component = this.subject(); 25 | // renders the component on the page 26 | this.render(); 27 | expect(component).to.be.ok; 28 | expect(this.$()).to.have.length(1);<% } %> 29 | }); 30 | } 31 | ); 32 | -------------------------------------------------------------------------------- /blueprints/sparkles-component-test/qunit-files/__root__/__testType__/__path__/__test__.__ext__: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit';<% if (testType === 'integration') { %> 2 | import hbs from 'htmlbars-inline-precompile';<% } %> 3 | 4 | moduleForComponent('<%= componentPathName %>', '<%= friendlyTestDescription %>', { 5 | <% if (testType === 'integration' ) { %>integration: true<% } else if(testType === 'unit') { %>// Specify the other units that are required for this test 6 | // needs: ['component:foo', 'helper:bar'], 7 | unit: true<% } %> 8 | }); 9 | 10 | test('it renders', function(assert) { 11 | <% if (testType === 'integration' ) { %>// Set any properties with this.set('myProperty', 'value'); 12 | // Handle any actions with this.on('myAction', function(val) { ... }); 13 | 14 | this.render(hbs`{{<%= componentPathName %>}}`); 15 | 16 | assert.equal(this.$().text().trim(), ''); 17 | 18 | // Template block usage: 19 | this.render(hbs` 20 | {{#<%= componentPathName %>}} 21 | template block text 22 | {{/<%= componentPathName %>}} 23 | `); 24 | 25 | assert.equal(this.$().text().trim(), 'template block text');<% } else if(testType === 'unit') { %> 26 | // Creates the component instance 27 | /*let component =*/ this.subject(); 28 | // Renders the component to the page 29 | this.render(); 30 | assert.equal(this.$().text().trim(), '');<% } %> 31 | }); 32 | -------------------------------------------------------------------------------- /blueprints/sparkles-component-test/qunit-rfc-232-files/__root__/__testType__/__path__/__test__.__ext__: -------------------------------------------------------------------------------- 1 | <% if (testType === 'integration') { %>import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import hbs from 'htmlbars-inline-precompile'; 5 | 6 | module('<%= friendlyTestDescription %>', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it renders', async function(assert) { 10 | // Set any properties with this.set('myProperty', 'value'); 11 | // Handle any actions with this.set('myAction', function(val) { ... }); 12 | 13 | await render(hbs`{{<%= componentPathName %>}}`); 14 | 15 | assert.equal(('' + this.element.textContent).trim(), ''); 16 | 17 | // Template block usage: 18 | await render(hbs` 19 | {{#<%= componentPathName %>}} 20 | template block text 21 | {{/<%= componentPathName %>}} 22 | `); 23 | 24 | assert.equal(('' + this.element.textContent).trim(), 'template block text'); 25 | }); 26 | });<% } else if (testType === 'unit') { %>import { module, test } from 'qunit'; 27 | import { setupTest } from 'ember-qunit'; 28 | 29 | module('<%= friendlyTestDescription %>', function(hooks) { 30 | setupTest(hooks); 31 | 32 | test('it exists', function(assert) { 33 | let component = this.owner.factoryFor('component:<%= componentPathName %>').create(); 34 | assert.ok(component); 35 | }); 36 | });<% } %> 37 | -------------------------------------------------------------------------------- /blueprints/sparkles-component/files/__root__/__path__/__name__.__ext__: -------------------------------------------------------------------------------- 1 | import Component from 'sparkles-component'; 2 | 3 | export default class <%= classifiedModuleName %> extends Component { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /blueprints/sparkles-component/files/__root__/__templatepath__/__templatename__.hbs: -------------------------------------------------------------------------------- 1 | {{yield}} 2 | -------------------------------------------------------------------------------- /blueprints/sparkles-component/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const stringUtil = require('ember-cli-string-utils'); 5 | const getPathOption = require('ember-cli-get-component-path-option'); 6 | const normalizeEntityName = require('ember-cli-normalize-entity-name'); 7 | const isModuleUnificationProject = require('../module-unification') 8 | .isModuleUnificationProject; 9 | 10 | module.exports = { 11 | description: 'Generates a sparkles-component', 12 | 13 | availableOptions: [ 14 | { 15 | name: 'path', 16 | type: String, 17 | default: 'components', 18 | aliases: [{ 'no-path': '' }] 19 | }, 20 | { 21 | name: 'lang', 22 | type: String 23 | } 24 | ], 25 | 26 | filesPath() { 27 | let filesDirectory = 'files'; 28 | 29 | if (isModuleUnificationProject(this.project)) { 30 | filesDirectory = 'module-unification-files'; 31 | } 32 | 33 | return path.join(this.path, filesDirectory); 34 | }, 35 | 36 | fileMapTokens() { 37 | if (isModuleUnificationProject(this.project)) { 38 | return { 39 | // component extension (ts or js) 40 | __ext__(options) { 41 | return options.locals.lang; 42 | }, 43 | // component root folder (i.e., app, addon, etc...) 44 | __root__({ inRepoAddon, inDummy }) { 45 | if (inRepoAddon) { 46 | return path.join('packages', inRepoAddon, 'src'); 47 | } 48 | if (inDummy) { 49 | return path.join('tests', 'dummy', 'src'); 50 | } 51 | return 'src'; 52 | }, 53 | // component path within __root__ 54 | __path__({ dasherizedModuleName }) { 55 | return path.join('ui', 'components', dasherizedModuleName); 56 | } 57 | }; 58 | } else { 59 | return { 60 | // component extension (ts or js) 61 | __ext__(options) { 62 | return options.locals.lang; 63 | }, 64 | // component path within app or addon folder 65 | __path__({ pod, podPath, locals, dasherizedModuleName }) { 66 | if (pod) { 67 | return path.join(podPath, locals.path, dasherizedModuleName); 68 | } else { 69 | return 'components'; 70 | } 71 | }, 72 | // path of the folder for component's template 73 | // NOTE: in an addon, this will be the private template 74 | // in the /addon/templates/components folder 75 | __templatepath__({ pod, podPath, locals, dasherizedModuleName }) { 76 | if (pod) { 77 | return path.join(podPath, locals.path, dasherizedModuleName); 78 | } 79 | return 'templates/components'; 80 | }, 81 | // name of the template file 82 | __templatename__({ pod, dasherizedModuleName }) { 83 | if (pod) { 84 | return 'template'; 85 | } 86 | return dasherizedModuleName; 87 | } 88 | }; 89 | } 90 | }, 91 | 92 | normalizeEntityName(entityName) { 93 | return normalizeEntityName(entityName); 94 | }, 95 | 96 | getDefaultLang(options) { 97 | // if the ember-cli-typescript addon is detected, use ts as default 98 | if ('ember-cli-typescript' in options.project.addonPackages) return 'ts'; 99 | else return 'js'; // otherwise use js as default 100 | }, 101 | locals(options) { 102 | const { lang = this.getDefaultLang(options) } = options; 103 | const classifiedModuleName = stringUtil.classify(options.entity.name); 104 | return { 105 | classifiedModuleName, 106 | lang, 107 | path: getPathOption(options) 108 | }; 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /blueprints/sparkles-component/module-unification-files/__root__/__path__/component.__ext__: -------------------------------------------------------------------------------- 1 | import Component from 'sparkles-component'; 2 | 3 | export default class <%= classifiedModuleName %> extends Component { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /blueprints/sparkles-component/module-unification-files/__root__/__path__/template.hbs: -------------------------------------------------------------------------------- 1 | {{yield}} 2 | -------------------------------------------------------------------------------- /blueprints/test-framework-detector.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const VersionChecker = require('ember-cli-version-checker'); 7 | 8 | module.exports = function(blueprint) { 9 | blueprint.supportsAddon = function() { 10 | return false; 11 | }; 12 | 13 | blueprint.filesPath = function() { 14 | let type; 15 | 16 | let dependencies = this.project.dependencies(); 17 | if ('ember-qunit' in dependencies) { 18 | type = 'qunit-rfc-232'; 19 | } else if ('ember-cli-qunit' in dependencies) { 20 | let checker = new VersionChecker(this.project); 21 | if ( 22 | fs.existsSync(this.path + '/qunit-rfc-232-files') && 23 | checker.for('ember-cli-qunit', 'npm').gte('4.2.0') 24 | ) { 25 | type = 'qunit-rfc-232'; 26 | } else { 27 | type = 'qunit'; 28 | } 29 | } else if ('ember-mocha' in dependencies) { 30 | type = 'mocha-0.12'; 31 | } else if ('ember-cli-mocha' in dependencies) { 32 | let checker = new VersionChecker(this.project); 33 | if ( 34 | fs.existsSync(this.path + '/mocha-0.12-files') && 35 | checker.for('ember-cli-mocha', 'npm').gte('0.12.0') 36 | ) { 37 | type = 'mocha-0.12'; 38 | } else { 39 | type = 'mocha'; 40 | } 41 | } else { 42 | this.ui.writeLine("Couldn't determine test style - using QUnit"); 43 | type = 'qunit'; 44 | } 45 | 46 | return path.join(this.path, type + '-files'); 47 | }; 48 | 49 | return blueprint; 50 | }; 51 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const flatMap = require('lodash.flatmap'); 5 | const merge = require('lodash.merge'); 6 | 7 | const withDecoratorVariants = scenarios => 8 | flatMap(scenarios, scenario => [ 9 | scenario, 10 | merge({}, scenario, { 11 | name: `${scenario.name}-legacy-decorators`, 12 | npm: { 13 | dependencies: { 14 | 'ember-cli-typescript': null 15 | }, 16 | devDependencies: { 17 | '@ember-decorators/babel-transforms': '^2.1.2', 18 | 'ember-cli-typescript': '^1.5.0' 19 | } 20 | } 21 | }) 22 | ]); 23 | 24 | module.exports = function() { 25 | return Promise.all([ 26 | getChannelURL('release'), 27 | getChannelURL('beta'), 28 | getChannelURL('canary') 29 | ]).then(urls => { 30 | return { 31 | useYarn: true, 32 | scenarios: withDecoratorVariants([ 33 | { 34 | name: 'ember-lts-2.12', 35 | npm: { 36 | devDependencies: { 37 | 'ember-source': '~2.12.0' 38 | } 39 | } 40 | }, 41 | { 42 | name: 'ember-lts-2.16', 43 | npm: { 44 | devDependencies: { 45 | 'ember-source': '~2.16.0' 46 | } 47 | } 48 | }, 49 | { 50 | name: 'ember-lts-2.18', 51 | npm: { 52 | devDependencies: { 53 | 'ember-source': '~2.18.0' 54 | } 55 | } 56 | }, 57 | { 58 | name: 'ember-lts-3.4', 59 | npm: { 60 | devDependencies: { 61 | 'ember-source': '~3.4.0' 62 | } 63 | } 64 | }, 65 | { 66 | name: 'ember-release', 67 | npm: { 68 | devDependencies: { 69 | 'ember-source': urls[0] 70 | } 71 | } 72 | }, 73 | { 74 | name: 'ember-beta', 75 | npm: { 76 | devDependencies: { 77 | 'ember-source': urls[1] 78 | } 79 | } 80 | }, 81 | { 82 | name: 'ember-canary', 83 | npm: { 84 | devDependencies: { 85 | 'ember-source': urls[2] 86 | } 87 | } 88 | }, 89 | { 90 | name: 'ember-default', 91 | npm: { 92 | devDependencies: {} 93 | } 94 | } 95 | ]) 96 | }; 97 | }); 98 | }; 99 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /ember-addon-main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: 'sparkles-component' 5 | }; 6 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function(defaults) { 6 | let app = new EmberAddon(defaults, { 7 | // Add options here 8 | }); 9 | 10 | /* 11 | This build file specifies the options for the dummy test app of this 12 | addon, located in `/tests/dummy` 13 | This build file does *not* influence how the addon or the app using it 14 | behave. You most likely want to be modifying `./index.js` or app's build file 15 | */ 16 | 17 | return app.toTree(); 18 | }; 19 | -------------------------------------------------------------------------------- /node-tests/blueprints/sparkle-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprintHelpers = require('ember-cli-blueprint-test-helpers/helpers'); 4 | const setupTestHooks = blueprintHelpers.setupTestHooks; 5 | const emberNew = blueprintHelpers.emberNew; 6 | const { emberGenerateDestroy } = blueprintHelpers; 7 | 8 | const expect = require('ember-cli-blueprint-test-helpers/chai').expect; 9 | 10 | describe('Classic App Layout: ember generate and destroy a sparkle component', function() { 11 | setupTestHooks(this); 12 | 13 | it('ember g sparkle x-foo', function() { 14 | // pass any additional command line options in the arguments array 15 | return emberNew().then(() => 16 | emberGenerateDestroy(['sparkles-component', 'x-foo'], file => { 17 | expect(file('app/components/x-foo.js')).to.eq( 18 | `import Component from 'sparkles-component'; 19 | 20 | export default class XFoo extends Component { 21 | 22 | } 23 | ` 24 | ); 25 | expect(file('tests/integration/components/x-foo-test.js')).to.eq( 26 | `import { module, test } from 'qunit'; 27 | import { setupRenderingTest } from 'ember-qunit'; 28 | import { render } from '@ember/test-helpers'; 29 | import hbs from 'htmlbars-inline-precompile'; 30 | 31 | module('Integration | Component | x-foo', function(hooks) { 32 | setupRenderingTest(hooks); 33 | 34 | test('it renders', async function(assert) { 35 | // Set any properties with this.set('myProperty', 'value'); 36 | // Handle any actions with this.set('myAction', function(val) { ... }); 37 | 38 | await render(hbs\`{{x-foo}}\`); 39 | 40 | assert.equal(('' + this.element.textContent).trim(), ''); 41 | 42 | // Template block usage: 43 | await render(hbs\` 44 | {{#x-foo}} 45 | template block text 46 | {{/x-foo}} 47 | \`); 48 | 49 | assert.equal(('' + this.element.textContent).trim(), 'template block text'); 50 | }); 51 | }); 52 | ` 53 | ); 54 | expect(file('app/templates/components/x-foo.hbs')).to.eq( 55 | `{{yield}} 56 | ` 57 | ); 58 | }) 59 | ); 60 | }); 61 | it('ember g sparkle x-foo --lang ts', function() { 62 | // pass any additional command line options in the arguments array 63 | return emberNew().then(() => 64 | emberGenerateDestroy( 65 | ['sparkles-component', 'x-foo', '--lang', 'ts'], 66 | file => { 67 | expect(file('app/components/x-foo.ts')).to.eq( 68 | `import Component from 'sparkles-component'; 69 | 70 | export default class XFoo extends Component { 71 | 72 | } 73 | ` 74 | ); 75 | expect(file('tests/integration/components/x-foo-test.ts')).to.eq( 76 | `import { module, test } from 'qunit'; 77 | import { setupRenderingTest } from 'ember-qunit'; 78 | import { render } from '@ember/test-helpers'; 79 | import hbs from 'htmlbars-inline-precompile'; 80 | 81 | module('Integration | Component | x-foo', function(hooks) { 82 | setupRenderingTest(hooks); 83 | 84 | test('it renders', async function(assert) { 85 | // Set any properties with this.set('myProperty', 'value'); 86 | // Handle any actions with this.set('myAction', function(val) { ... }); 87 | 88 | await render(hbs\`{{x-foo}}\`); 89 | 90 | assert.equal(('' + this.element.textContent).trim(), ''); 91 | 92 | // Template block usage: 93 | await render(hbs\` 94 | {{#x-foo}} 95 | template block text 96 | {{/x-foo}} 97 | \`); 98 | 99 | assert.equal(('' + this.element.textContent).trim(), 'template block text'); 100 | }); 101 | }); 102 | ` 103 | ); 104 | expect(file('app/templates/components/x-foo.hbs')).to.eq( 105 | `{{yield}} 106 | ` 107 | ); 108 | } 109 | ) 110 | ); 111 | }); 112 | it('ember g sparkle x-foo --lang js', function() { 113 | // pass any additional command line options in the arguments array 114 | return emberNew().then(() => 115 | emberGenerateDestroy( 116 | ['sparkles-component', 'x-foo', '--lang', 'js'], 117 | file => { 118 | expect(file('app/components/x-foo.js')).to.eq( 119 | `import Component from 'sparkles-component'; 120 | 121 | export default class XFoo extends Component { 122 | 123 | } 124 | ` 125 | ); 126 | expect(file('tests/integration/components/x-foo-test.js')).to.eq( 127 | `import { module, test } from 'qunit'; 128 | import { setupRenderingTest } from 'ember-qunit'; 129 | import { render } from '@ember/test-helpers'; 130 | import hbs from 'htmlbars-inline-precompile'; 131 | 132 | module('Integration | Component | x-foo', function(hooks) { 133 | setupRenderingTest(hooks); 134 | 135 | test('it renders', async function(assert) { 136 | // Set any properties with this.set('myProperty', 'value'); 137 | // Handle any actions with this.set('myAction', function(val) { ... }); 138 | 139 | await render(hbs\`{{x-foo}}\`); 140 | 141 | assert.equal(('' + this.element.textContent).trim(), ''); 142 | 143 | // Template block usage: 144 | await render(hbs\` 145 | {{#x-foo}} 146 | template block text 147 | {{/x-foo}} 148 | \`); 149 | 150 | assert.equal(('' + this.element.textContent).trim(), 'template block text'); 151 | }); 152 | }); 153 | ` 154 | ); 155 | expect(file('app/templates/components/x-foo.hbs')).to.eq( 156 | `{{yield}} 157 | ` 158 | ); 159 | } 160 | ) 161 | ); 162 | }); 163 | }); 164 | 165 | describe('Classic Addon Layout: ember generate and destroy a sparkle component', function() { 166 | setupTestHooks(this); 167 | 168 | it('ember g sparkle x-boz', function() { 169 | // pass any additional command line options in the arguments array 170 | return emberNew({ target: 'addon' }).then(() => 171 | emberGenerateDestroy(['sparkles-component', 'x-boz'], file => { 172 | expect(file('addon/components/x-boz.js')).to.eq( 173 | `import Component from 'sparkles-component'; 174 | 175 | export default class XBoz extends Component { 176 | 177 | } 178 | ` 179 | ); 180 | expect(file('addon/templates/components/x-boz.hbs')).to.eq( 181 | `{{yield}} 182 | ` 183 | ); 184 | expect(file('app/components/x-boz.js')).to.eq( 185 | `export { default } from 'my-addon/components/x-boz'; 186 | ` 187 | ); 188 | expect(file('app/templates/components/x-boz.js')).to.eq( 189 | `export { default } from 'my-addon/templates/components/x-boz'; 190 | ` 191 | ); 192 | }) 193 | ); 194 | }); 195 | it('ember g sparkle x-baz --lang js', function() { 196 | // pass any additional command line options in the arguments array 197 | return emberNew({ target: 'addon' }).then(() => 198 | emberGenerateDestroy( 199 | ['sparkles-component', 'x-baz', '--lang', 'js'], 200 | file => { 201 | expect(file('addon/components/x-baz.js')).to.eq( 202 | `import Component from 'sparkles-component'; 203 | 204 | export default class XBaz extends Component { 205 | 206 | } 207 | ` 208 | ); 209 | expect(file('addon/templates/components/x-baz.hbs')).to.eq( 210 | `{{yield}} 211 | ` 212 | ); 213 | expect(file('app/components/x-baz.js')).to.eq( 214 | `export { default } from 'my-addon/components/x-baz'; 215 | ` 216 | ); 217 | expect(file('app/templates/components/x-baz.js')).to.eq( 218 | `export { default } from 'my-addon/templates/components/x-baz'; 219 | ` 220 | ); 221 | } 222 | ) 223 | ); 224 | }); 225 | it('ember g sparkle x-biz --lang ts', function() { 226 | // pass any additional command line options in the arguments array 227 | return emberNew({ target: 'addon' }).then(() => 228 | emberGenerateDestroy( 229 | ['sparkles-component', 'x-biz', '--lang', 'ts'], 230 | file => { 231 | expect(file('addon/components/x-biz.ts')).to.eq( 232 | `import Component from 'sparkles-component'; 233 | 234 | export default class XBiz extends Component { 235 | 236 | } 237 | ` 238 | ); 239 | expect(file('addon/templates/components/x-biz.hbs')).to.eq( 240 | `{{yield}} 241 | ` 242 | ); 243 | expect(file('app/components/x-biz.js')).to.eq( 244 | `export { default } from 'my-addon/components/x-biz'; 245 | ` 246 | ); 247 | expect(file('app/templates/components/x-biz.js')).to.eq( 248 | `export { default } from 'my-addon/templates/components/x-biz'; 249 | ` 250 | ); 251 | } 252 | ) 253 | ); 254 | }); 255 | }); 256 | 257 | describe('MU App Layout: ember generate and destroy a sparkle component', function() { 258 | setupTestHooks(this); 259 | 260 | it('ember g sparkle x-foo', function() { 261 | // pass any additional command line options in the arguments array 262 | return emberNew({ isModuleUnification: true }).then(() => 263 | emberGenerateDestroy( 264 | ['sparkles-component', 'x-foo'], 265 | file => { 266 | expect(file('src/ui/components/x-foo/component.js')).to.eq( 267 | `import Component from 'sparkles-component'; 268 | 269 | export default class XFoo extends Component { 270 | 271 | } 272 | ` 273 | ); 274 | expect(file('src/ui/components/x-foo/template.hbs')).to.eq( 275 | `{{yield}} 276 | ` 277 | ); 278 | }, 279 | { isModuleUnification: true } 280 | ) 281 | ); 282 | }); 283 | it('ember g sparkle x-foo --lang js', function() { 284 | // pass any additional command line options in the arguments array 285 | return emberNew({ isModuleUnification: true }).then(() => 286 | emberGenerateDestroy( 287 | ['sparkles-component', 'x-foo', '--lang', 'js'], 288 | file => { 289 | expect(file('src/ui/components/x-foo/component.js')).to.eq( 290 | `import Component from 'sparkles-component'; 291 | 292 | export default class XFoo extends Component { 293 | 294 | } 295 | ` 296 | ); 297 | expect(file('src/ui/components/x-foo/template.hbs')).to.eq( 298 | `{{yield}} 299 | ` 300 | ); 301 | }, 302 | { isModuleUnification: true } 303 | ) 304 | ); 305 | }); 306 | it('ember g sparkle x-foo --lang ts', function() { 307 | // pass any additional command line options in the arguments array 308 | return emberNew({ isModuleUnification: true }).then(() => 309 | emberGenerateDestroy( 310 | ['sparkles-component', 'x-foo', '--lang', 'ts'], 311 | file => { 312 | expect(file('src/ui/components/x-foo/component.ts')).to.eq( 313 | `import Component from 'sparkles-component'; 314 | 315 | export default class XFoo extends Component { 316 | 317 | } 318 | ` 319 | ); 320 | expect(file('src/ui/components/x-foo/template.hbs')).to.eq( 321 | `{{yield}} 322 | ` 323 | ); 324 | }, 325 | { isModuleUnification: true } 326 | ) 327 | ); 328 | }); 329 | }); 330 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sparkles-component", 3 | "version": "1.3.0", 4 | "description": "Experiment with `@glimmer/component` style APIs in Ember apps via existing public APIs", 5 | "keywords": [ 6 | "ember-addon" 7 | ], 8 | "license": "MIT", 9 | "author": "Robert Jackson ", 10 | "directories": { 11 | "doc": "doc", 12 | "test": "tests" 13 | }, 14 | "repository": "https://github.com/rwjblue/sparkles-component", 15 | "scripts": { 16 | "build": "ember build", 17 | "lint:js": "eslint .", 18 | "start": "ember serve", 19 | "test": "ember test", 20 | "test:all": "ember try:each", 21 | "prepublishOnly": "ember ts:precompile", 22 | "postpublish": "ember ts:clean", 23 | "nodetest": "mocha node-tests --recursive" 24 | }, 25 | "dependencies": { 26 | "@ember-decorators/utils": "^5.1.3", 27 | "@glimmer/env": "^0.1.7", 28 | "ember-cli-babel": "^7.2.0", 29 | "ember-cli-get-component-path-option": "^1.0.0", 30 | "ember-cli-is-package-missing": "^1.0.0", 31 | "ember-cli-normalize-entity-name": "^1.0.0", 32 | "ember-cli-path-utils": "^1.0.0", 33 | "ember-cli-string-utils": "^1.1.0", 34 | "ember-cli-typescript": "^2.0.0-rc.1", 35 | "ember-compatibility-helpers": "^1.1.2" 36 | }, 37 | "devDependencies": { 38 | "@ember-decorators/babel-transforms": "^5.1.3", 39 | "@ember/optional-features": "^0.6.1", 40 | "@types/ember": "~3.0.26", 41 | "@types/ember-qunit": "~3.4.3", 42 | "@types/ember-test-helpers": "~1.0.4", 43 | "@types/ember-testing-helpers": "^0.0.3", 44 | "@types/ember__test-helpers": "~0.7.6", 45 | "@types/mocha": "^5.2.5", 46 | "@types/qunit": "^2.5.3", 47 | "@types/rsvp": "^4.0.2", 48 | "babel-eslint": "^8.2.6", 49 | "broccoli-asset-rev": "^2.7.0", 50 | "ember-ajax": "^3.0.0", 51 | "ember-cli": "~3.4.0-beta.1", 52 | "ember-cli-blueprint-test-helpers": "^0.19.1", 53 | "ember-cli-dependency-checker": "^2.0.0", 54 | "ember-cli-eslint": "^4.2.1", 55 | "ember-cli-htmlbars": "^2.0.3", 56 | "ember-cli-htmlbars-inline-precompile": "^1.0.3", 57 | "ember-cli-inject-live-reload": "^1.4.1", 58 | "ember-cli-qunit": "^4.3.2", 59 | "ember-cli-sri": "^2.1.0", 60 | "ember-cli-uglify": "^2.1.0", 61 | "ember-disable-prototype-extensions": "^1.1.2", 62 | "ember-export-application-global": "^2.0.0", 63 | "ember-load-initializers": "^1.1.0", 64 | "ember-maybe-import-regenerator": "^0.1.6", 65 | "ember-resolver": "^5.0.0", 66 | "ember-source": "~3.4.0", 67 | "ember-source-channel-url": "^1.0.1", 68 | "ember-try": "^1.0.0-beta.3", 69 | "eslint-plugin-ember": "^5.0.0", 70 | "eslint-plugin-node": "^6.0.1", 71 | "loader.js": "^4.2.3", 72 | "lodash.flatmap": "^4.5.0", 73 | "lodash.merge": "^4.6.1", 74 | "mocha": "^5.0.0", 75 | "qunit-dom": "^0.7.1", 76 | "typescript": "^2.9.2" 77 | }, 78 | "engines": { 79 | "node": "8.* || 10.* || >= 12.*" 80 | }, 81 | "ember-addon": { 82 | "configPath": "tests/dummy/config", 83 | "defaultBlueprint": "install-sparkles", 84 | "main": "ember-addon-main.js" 85 | }, 86 | "main": "addon/index.js" 87 | } 88 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test_page: 'tests/index.html?hidepassed', 3 | disable_watching: true, 4 | launch_in_ci: [ 5 | 'Chrome' 6 | ], 7 | launch_in_dev: [ 8 | 'Chrome' 9 | ], 10 | browser_args: { 11 | Chrome: { 12 | ci: [ 13 | // --no-sandbox is needed when running Chrome inside a container 14 | process.env.CI ? '--no-sandbox' : null, 15 | '--headless', 16 | '--disable-gpu', 17 | '--disable-dev-shm-usage', 18 | '--disable-software-rasterizer', 19 | '--mute-audio', 20 | '--remote-debugging-port=0', 21 | '--window-size=1440,900' 22 | ].filter(Boolean) 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const App = Application.extend({ 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | Resolver 10 | }); 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/components/conference-speakers.js: -------------------------------------------------------------------------------- 1 | import Component, { tracked } from "sparkles-component"; 2 | 3 | export default class ConferenceSpeakers extends Component { 4 | @tracked current = 0; 5 | speakers = ['Tom', 'Yehuda', 'Ed']; 6 | 7 | @tracked('current') 8 | get currentlySpeaking() { 9 | return this.speakers[this.current]; 10 | } 11 | 12 | @tracked('current') 13 | get moreSpeakers() { 14 | return (this.speakers.length - 1) > this.current; 15 | } 16 | 17 | next() { 18 | this.current = this.current + 1; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/dummy/app/config/environment.d.ts: -------------------------------------------------------------------------------- 1 | export default config; 2 | 3 | /** 4 | * Type declarations for 5 | * import config from './config/environment' 6 | * 7 | * For now these need to be managed by the developer 8 | * since different ember addons can materialize new entries. 9 | */ 10 | declare const config: { 11 | environment: any; 12 | modulePrefix: string; 13 | podModulePrefix: string; 14 | locationType: string; 15 | rootURL: string; 16 | }; 17 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwjblue/sparkles-component/75e277095b2d95edc1682215c974cb1ce8b73627/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwjblue/sparkles-component/75e277095b2d95edc1682215c974cb1ce8b73627/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwjblue/sparkles-component/75e277095b2d95edc1682215c974cb1ce8b73627/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | const Router = EmberRouter.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL 7 | }); 8 | 9 | Router.map(function() { 10 | this.route('conference-speakers'); 11 | }); 12 | 13 | export default Router; 14 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwjblue/sparkles-component/75e277095b2d95edc1682215c974cb1ce8b73627/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember

2 | 3 | {{outlet}} -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/conference-speakers.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Speaking: {{this.currentlySpeaking}}

3 |
    4 | {{#each this.speakers key="@index" as |speaker|}} 5 |
  • {{speaker}}
  • 6 | {{/each}} 7 |
8 | 9 | {{#if this.moreSpeakers}} 10 | 11 | {{else}} 12 |

All finished!

13 | {{/if}} 14 |
15 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/conference-speakers.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'dummy', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false 17 | } 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | } 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /tests/dummy/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "jquery-integration": false 3 | } 4 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | const isCI = !!process.env.CI; 10 | const isProduction = process.env.EMBER_ENV === 'production'; 11 | 12 | if (isCI || isProduction) { 13 | browsers.push('ie 11'); 14 | } 15 | 16 | module.exports = { 17 | browsers 18 | }; 19 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwjblue/sparkles-component/75e277095b2d95edc1682215c974cb1ce8b73627/tests/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwjblue/sparkles-component/75e277095b2d95edc1682215c974cb1ce8b73627/tests/integration/.gitkeep -------------------------------------------------------------------------------- /tests/integration/components/glimmer-component-guide-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render, click } from '@ember/test-helpers'; 4 | import hbs from 'htmlbars-inline-precompile'; 5 | 6 | module('Integration | Component | glimmer.js guide example', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | hooks.beforeEach(function(assert) { 10 | assert.validateSpeakers = (speakerNames, currentSpeaker) => { 11 | let items = this.element.querySelectorAll('li'); 12 | assert.equal(items.length, speakerNames.length, 'correct number of entires found'); 13 | 14 | for (let i = 0; i < speakerNames.length; i++) { 15 | assert.dom(items[i]).hasText(speakerNames[i]); 16 | } 17 | 18 | assert.dom('p').hasText(`Speaking: ${currentSpeaker}`); 19 | }; 20 | }); 21 | 22 | 23 | test('renders', async function(assert) { 24 | await render(hbs``); 25 | 26 | assert.validateSpeakers(['Tom', 'Yehuda', 'Ed'], 'Tom'); 27 | }); 28 | 29 | test('cycles through speakers', async function(assert) { 30 | await render(hbs``); 31 | 32 | assert.validateSpeakers(['Tom', 'Yehuda', 'Ed'], 'Tom'); 33 | 34 | await click('button'); 35 | 36 | assert.validateSpeakers(['Tom', 'Yehuda', 'Ed'], 'Yehuda'); 37 | 38 | await click('button'); 39 | 40 | assert.validateSpeakers(['Tom', 'Yehuda', 'Ed'], 'Ed'); 41 | assert.dom('button').doesNotExist(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/integration/components/sparkles-component-test.js: -------------------------------------------------------------------------------- 1 | import SparklesComponent, { tracked } from 'sparkles-component'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, clearRender, click } from '@ember/test-helpers'; 5 | import hbs from 'htmlbars-inline-precompile'; 6 | import { getOwner } from '@ember/application'; 7 | 8 | module('Integration | Component | sparkles-component', function(hooks) { 9 | let InstrumentedComponent; 10 | 11 | setupRenderingTest(hooks); 12 | 13 | hooks.beforeEach(function(assert) { 14 | InstrumentedComponent = class extends SparklesComponent { 15 | constructor() { 16 | super(...arguments); 17 | assert.step('constructor'); 18 | } 19 | 20 | didInsertElement() { 21 | assert.step('didInsertElement'); 22 | } 23 | 24 | didUpdate() { 25 | assert.step('didUpdate'); 26 | } 27 | 28 | destroy() { 29 | assert.step('destroy'); 30 | } 31 | } 32 | }); 33 | 34 | test('it can render with curlies (no args)', async function(assert) { 35 | this.owner.register('component:under-test', InstrumentedComponent); 36 | 37 | await render(hbs`{{under-test}}`); 38 | 39 | assert.verifySteps(['constructor', 'didInsertElement'], 'initial render steps'); 40 | 41 | await clearRender(); 42 | 43 | assert.verifySteps(['destroy'], 'post destroy steps'); 44 | }); 45 | 46 | test('it can render and update with curlies (args)', async function(assert) { 47 | this.owner.register('component:under-test', InstrumentedComponent); 48 | this.owner.register('template:components/under-test', hbs`

{{@text}}

`); 49 | 50 | this.set('text', 'hello!'); 51 | await render(hbs`{{under-test text=this.text}}`); 52 | 53 | assert.dom('p').hasText('hello!'); 54 | assert.verifySteps(['constructor', 'didInsertElement'], 'initial render steps'); 55 | 56 | this.set('text', 'hello world!'); 57 | 58 | assert.dom('p').hasText('hello world!'); 59 | assert.verifySteps(['didUpdate'], 'rerender steps'); 60 | 61 | this.set('text', 'hello!'); 62 | 63 | assert.dom('p').hasText('hello!'); 64 | assert.verifySteps(['didUpdate'], 'rerender steps'); 65 | 66 | await clearRender(); 67 | 68 | assert.verifySteps(['destroy'], 'post destroy steps'); 69 | }); 70 | 71 | test('it can render with angles (no args)', async function(assert) { 72 | this.owner.register('component:under-test', InstrumentedComponent); 73 | 74 | await render(hbs``); 75 | 76 | assert.verifySteps(['constructor', 'didInsertElement'], 'initial render steps'); 77 | 78 | await clearRender(); 79 | 80 | assert.verifySteps(['destroy'], 'post destroy steps'); 81 | }); 82 | 83 | test('it can render and update with angles (args)', async function(assert) { 84 | this.owner.register('component:under-test', InstrumentedComponent); 85 | this.owner.register('template:components/under-test', hbs`

{{@text}}

`); 86 | 87 | this.set('text', 'hello!'); 88 | await render(hbs``); 89 | 90 | assert.dom('p').hasText('hello!'); 91 | assert.verifySteps(['constructor', 'didInsertElement'], 'initial render steps'); 92 | 93 | this.set('text', 'hello world!'); 94 | 95 | assert.dom('p').hasText('hello world!'); 96 | assert.verifySteps(['didUpdate'], 'rerender steps'); 97 | 98 | this.set('text', 'hello!'); 99 | 100 | assert.dom('p').hasText('hello!'); 101 | assert.verifySteps(['didUpdate'], 'rerender steps'); 102 | 103 | await clearRender(); 104 | 105 | assert.verifySteps(['destroy'], 'post destroy steps'); 106 | }); 107 | 108 | test('it can use args in component', async function(assert) { 109 | this.owner.register('component:under-test', class extends SparklesComponent { 110 | get text() { 111 | return this.args.text.toUpperCase(); 112 | } 113 | }); 114 | this.owner.register('template:components/under-test', hbs`

{{this.text}}

`); 115 | 116 | this.set('text', 'hello!'); 117 | await render(hbs``); 118 | assert.dom('p').hasText('HELLO!'); 119 | }); 120 | 121 | test('it can use tracked to recompute when args change', async function(assert) { 122 | this.owner.register('component:under-test', class extends SparklesComponent { 123 | @tracked('args') 124 | get text() { 125 | return this.args.text.toUpperCase(); 126 | } 127 | }); 128 | this.owner.register('template:components/under-test', hbs`

{{this.text}}

`); 129 | 130 | this.set('text', 'hello!'); 131 | await render(hbs``); 132 | assert.dom('p').hasText('HELLO!'); 133 | 134 | this.set('text', 'hello world!'); 135 | assert.dom('p').hasText('HELLO WORLD!'); 136 | 137 | this.set('text', 'hello!'); 138 | assert.dom('p').hasText('HELLO!'); 139 | }); 140 | 141 | test('it can use tracked to recompute for changes', async function(assert) { 142 | this.owner.register('component:under-test', class extends SparklesComponent { 143 | constructor() { 144 | super(...arguments); 145 | 146 | this._count = 0; 147 | } 148 | 149 | @tracked 150 | get count() { 151 | return this._count; 152 | } 153 | 154 | set count(value) { 155 | this._count = value; 156 | } 157 | 158 | increment() { 159 | this.count++; 160 | } 161 | }); 162 | this.owner.register( 163 | 'template:components/under-test', 164 | hbs`

Count: {{this.count}}

` 165 | ); 166 | 167 | await render(hbs``); 168 | assert.dom('p').hasText('Count: 0'); 169 | 170 | await click('button[data-test=increment]'); 171 | assert.dom('p').hasText('Count: 1'); 172 | 173 | await click('button[data-test=increment]'); 174 | assert.dom('p').hasText('Count: 2'); 175 | }); 176 | 177 | test('does not update for non-tracked property changes', async function(assert) { 178 | this.owner.register('component:under-test', class extends SparklesComponent { 179 | constructor() { 180 | super(...arguments); 181 | 182 | this._count = 0; 183 | } 184 | 185 | get count() { 186 | return this._count; 187 | } 188 | 189 | set count(value) { 190 | this._count = value; 191 | } 192 | 193 | increment() { 194 | this.count++; 195 | } 196 | }); 197 | this.owner.register( 198 | 'template:components/under-test', 199 | hbs`

Count: {{this.count}}

` 200 | ); 201 | 202 | await render(hbs``); 203 | assert.dom('p').hasText('Count: 0'); 204 | 205 | await click('button[data-test=increment]'); 206 | assert.dom('p').hasText('Count: 0'); 207 | 208 | await click('button[data-test=increment]'); 209 | assert.dom('p').hasText('Count: 0'); 210 | }); 211 | 212 | test('it has an owner', async function(assert) { 213 | this.owner.register('component:under-test', class extends SparklesComponent { 214 | get environment() { 215 | return getOwner(this).resolveRegistration("config:environment").environment; 216 | } 217 | }); 218 | this.owner.register( 219 | 'template:components/under-test', 220 | hbs`

Environment: {{this.environment}}

` 221 | ); 222 | await render(hbs``); 223 | assert.dom('p').hasText('Environment: test'); 224 | }) 225 | }); 226 | -------------------------------------------------------------------------------- /tests/integration/components/tracked-js-test.js: -------------------------------------------------------------------------------- 1 | import SparklesComponent, { tracked } from 'sparkles-component'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, click } from '@ember/test-helpers'; 5 | import hbs from 'htmlbars-inline-precompile'; 6 | import 'qunit-dom'; 7 | 8 | module('tracked: js', function(hooks) { 9 | setupRenderingTest(hooks); 10 | 11 | test('class field with initializer', async function(assert) { 12 | this.owner.register('template:components/under-test', hbs` 13 |
14 |

{{this.current}}

15 | 16 |
17 | `); 18 | class UnderTest extends SparklesComponent { 19 | @tracked current = 0; 20 | 21 | increment() { 22 | this.current++; 23 | } 24 | } 25 | this.owner.register('component:under-test', UnderTest); 26 | 27 | await render(hbs``); 28 | assert.dom('p').hasText('0'); 29 | 30 | await click('button'); 31 | assert.dom('p').hasText('1'); 32 | 33 | await click('button'); 34 | assert.dom('p').hasText('2'); 35 | 36 | await click('button'); 37 | assert.dom('p').hasText('3'); 38 | }); 39 | 40 | test('class field without initializer', async function(assert) { 41 | this.owner.register('template:components/under-test', hbs` 42 |
43 |

{{this.current}}

44 | 45 |
46 | `); 47 | class UnderTest extends SparklesComponent { 48 | @tracked current; 49 | 50 | increment() { 51 | // silly, but whatever 52 | if (this.current === undefined) { 53 | this.current = 0; 54 | } 55 | this.current++; 56 | } 57 | } 58 | this.owner.register('component:under-test', UnderTest); 59 | 60 | await render(hbs``); 61 | assert.dom('p').hasText(''); 62 | 63 | await click('button'); 64 | assert.dom('p').hasText('1'); 65 | 66 | await click('button'); 67 | assert.dom('p').hasText('2'); 68 | 69 | await click('button'); 70 | assert.dom('p').hasText('3'); 71 | }); 72 | 73 | test('computed property without dependent keys', async function(assert) { 74 | this.owner.register('template:components/under-test', hbs` 75 |
76 |

{{this.current}}

77 | 78 |
79 | `); 80 | class UnderTest extends SparklesComponent { 81 | constructor() { 82 | super(...arguments); 83 | this._value = 0; 84 | } 85 | 86 | @tracked 87 | get current() { 88 | return this._value; 89 | } 90 | 91 | set current(value) { 92 | this._value = value; 93 | } 94 | 95 | increment() { 96 | this.current++; 97 | } 98 | } 99 | this.owner.register('component:under-test', UnderTest); 100 | 101 | await render(hbs``); 102 | assert.dom('p').hasText('0'); 103 | 104 | await click('button'); 105 | assert.dom('p').hasText('1'); 106 | 107 | await click('button'); 108 | assert.dom('p').hasText('2'); 109 | 110 | await click('button'); 111 | assert.dom('p').hasText('3'); 112 | }); 113 | 114 | test('computed property with dependent keys', async function(assert) { 115 | this.owner.register('template:components/under-test', hbs` 116 |
117 |

{{this.display}}

118 | 119 |
120 | `); 121 | class UnderTest extends SparklesComponent { 122 | @tracked current = 0; 123 | 124 | @tracked('current') 125 | get display() { 126 | return `Current: ${this.current}`; 127 | } 128 | 129 | increment() { 130 | this.current++; 131 | } 132 | } 133 | this.owner.register('component:under-test', UnderTest); 134 | 135 | await render(hbs``); 136 | assert.dom('p').hasText('Current: 0'); 137 | 138 | await click('button'); 139 | assert.dom('p').hasText('Current: 1'); 140 | 141 | await click('button'); 142 | assert.dom('p').hasText('Current: 2'); 143 | 144 | await click('button'); 145 | assert.dom('p').hasText('Current: 3'); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /tests/integration/components/tracked-ts-test.ts: -------------------------------------------------------------------------------- 1 | import SparklesComponent, { tracked } from 'sparkles-component'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, click } from '@ember/test-helpers'; 5 | import hbs from 'htmlbars-inline-precompile'; 6 | import 'qunit-dom'; 7 | 8 | module('tracked: ts', function(hooks) { 9 | setupRenderingTest(hooks); 10 | 11 | test('class field with initializer', async function(assert) { 12 | this.owner.register('template:components/under-test', hbs` 13 |
14 |

{{this.current}}

15 | 16 |
17 | `); 18 | class UnderTest extends SparklesComponent { 19 | @tracked current = 0; 20 | 21 | increment() { 22 | this.current++; 23 | } 24 | } 25 | this.owner.register('component:under-test', UnderTest); 26 | 27 | await render(hbs``); 28 | assert.dom('p').hasText('0'); 29 | 30 | await click('button'); 31 | assert.dom('p').hasText('1'); 32 | 33 | await click('button'); 34 | assert.dom('p').hasText('2'); 35 | 36 | await click('button'); 37 | assert.dom('p').hasText('3'); 38 | }); 39 | 40 | test('class field without initializer', async function(assert) { 41 | this.owner.register('template:components/under-test', hbs` 42 |
43 |

{{this.current}}

44 | 45 |
46 | `); 47 | class UnderTest extends SparklesComponent { 48 | @tracked current: number | undefined; 49 | 50 | increment() { 51 | // silly, but whatever 52 | if (this.current === undefined) { 53 | this.current = 0; 54 | } 55 | this.current++; 56 | } 57 | } 58 | this.owner.register('component:under-test', UnderTest); 59 | 60 | await render(hbs``); 61 | assert.dom('p').hasText(''); 62 | 63 | await click('button'); 64 | assert.dom('p').hasText('1'); 65 | 66 | await click('button'); 67 | assert.dom('p').hasText('2'); 68 | 69 | await click('button'); 70 | assert.dom('p').hasText('3'); 71 | }); 72 | 73 | test('computed property without dependent keys', async function(assert) { 74 | this.owner.register('template:components/under-test', hbs` 75 |
76 |

{{this.current}}

77 | 78 |
79 | `); 80 | class UnderTest extends SparklesComponent { 81 | _value: number; 82 | 83 | constructor(arg: {}) { 84 | super(arg); 85 | this._value = 0; 86 | } 87 | 88 | @tracked 89 | get current() { 90 | return this._value; 91 | } 92 | 93 | set current(value) { 94 | this._value = value; 95 | } 96 | 97 | increment() { 98 | this.current++; 99 | } 100 | } 101 | this.owner.register('component:under-test', UnderTest); 102 | 103 | await render(hbs``); 104 | assert.dom('p').hasText('0'); 105 | 106 | await click('button'); 107 | assert.dom('p').hasText('1'); 108 | 109 | await click('button'); 110 | assert.dom('p').hasText('2'); 111 | 112 | await click('button'); 113 | assert.dom('p').hasText('3'); 114 | }); 115 | 116 | test('computed property with dependent keys', async function(assert) { 117 | this.owner.register('template:components/under-test', hbs` 118 |
119 |

{{this.display}}

120 | 121 |
122 | `); 123 | class UnderTest extends SparklesComponent { 124 | @tracked current = 0; 125 | 126 | @tracked('current') 127 | get display() { 128 | return `Current: ${this.current}`; 129 | } 130 | 131 | increment() { 132 | this.current++; 133 | } 134 | } 135 | this.owner.register('component:under-test', UnderTest); 136 | 137 | await render(hbs``); 138 | assert.dom('p').hasText('Current: 0'); 139 | 140 | await click('button'); 141 | assert.dom('p').hasText('Current: 1'); 142 | 143 | await click('button'); 144 | assert.dom('p').hasText('Current: 2'); 145 | 146 | await click('button'); 147 | assert.dom('p').hasText('Current: 3'); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /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 'qunit-dom'; 6 | 7 | setApplication(Application.create(config.APP)); 8 | 9 | start(); 10 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwjblue/sparkles-component/75e277095b2d95edc1682215c974cb1ce8b73627/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "allowJs": true, 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "noImplicitAny": true, 8 | "noImplicitThis": true, 9 | "alwaysStrict": true, 10 | "strictNullChecks": true, 11 | "strictPropertyInitialization": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noEmitOnError": false, 17 | "noEmit": true, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "skipLibCheck": true, 21 | "experimentalDecorators": true, 22 | "baseUrl": ".", 23 | "module": "es6", 24 | "paths": { 25 | "dummy/tests/*": [ 26 | "tests/*" 27 | ], 28 | "dummy/*": [ 29 | "tests/dummy/app/*", 30 | "app/*" 31 | ], 32 | "sparkles-component": [ 33 | "addon" 34 | ], 35 | "sparkles-component/*": [ 36 | "addon/*" 37 | ], 38 | "sparkles-component/test-support": [ 39 | "addon-test-support" 40 | ], 41 | "sparkles-component/test-support/*": [ 42 | "addon-test-support/*" 43 | ], 44 | "*": [ 45 | "types/*" 46 | ] 47 | } 48 | }, 49 | "include": [ 50 | "app/**/*", 51 | "addon/**/*", 52 | "tests/**/*", 53 | "types/**/*", 54 | "test-support/**/*", 55 | "addon-test-support/**/*" 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /types/@ember-decorators/utils/decorator.d.ts: -------------------------------------------------------------------------------- 1 | type MemberDescriptor = { 2 | key: string; 3 | kind: 'class' | 'method' | 'field' | 'initializer'; 4 | elements?: MemberDescriptor[]; 5 | key: string; 6 | placement?: 'prototype' | 'static' | 'own'; 7 | extras?: MemberDescriptor[]; 8 | initializer?: () => any; 9 | descriptor?: PropertyDescriptor; 10 | }; 11 | 12 | export function decoratorWithParams( 13 | fn: (desc: MemberDescriptor, params?: any[]) => MemberDescriptor 14 | ): any; 15 | -------------------------------------------------------------------------------- /types/@ember/component.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@ember/component' { 2 | export function setComponentManager(managerId: string, baseClass: T): T; 3 | export function setComponentManager( 4 | managerFactory: (owner: Owner) => {}, 5 | baseClass: T 6 | ): T; 7 | export function capabilities( 8 | version: string, 9 | opts?: { 10 | destructor?: boolean; 11 | asyncLifecycleCallbacks?: boolean; 12 | } 13 | ): any; 14 | } 15 | -------------------------------------------------------------------------------- /types/dummy/index.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwjblue/sparkles-component/75e277095b2d95edc1682215c974cb1ce8b73627/types/dummy/index.d.ts -------------------------------------------------------------------------------- /types/ember-compatibility-helpers.d.ts: -------------------------------------------------------------------------------- 1 | export function gte(version: string): boolean; 2 | -------------------------------------------------------------------------------- /types/ember/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'ember' { 2 | namespace Ember { 3 | // Remove once https://github.com/DefinitelyTyped/DefinitelyTyped/pull/27661 lands 4 | interface _RegistryProxyMixin { 5 | register(fullName: string, factory: any, options?: { singleton?: boolean, instantiate?: boolean }): any; 6 | } 7 | 8 | function notifyPropertyChange(target: object, key: string): void; 9 | function addObserver(target: T, dependentKey: string, instance: any, notifyMethod: () => void): T; 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwjblue/sparkles-component/75e277095b2d95edc1682215c974cb1ce8b73627/vendor/.gitkeep --------------------------------------------------------------------------------