diff --git a/.travis.yml b/.travis.yml index c8cec19..3c2e1e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,8 +49,13 @@ jobs: # we recommend new addons test the current and previous LTS # as well as latest stable release (bonus points to beta/canary) - stage: "Additional Tests" - env: EMBER_TRY_SCENARIO=ember-lts-2.18 + env: EMBER_TRY_SCENARIO=ember-lts-2.12 + - env: EMBER_TRY_SCENARIO=ember-lts-2.16 + - env: EMBER_TRY_SCENARIO=ember-lts-2.18 - env: EMBER_TRY_SCENARIO=ember-lts-3.4 + - env: EMBER_TRY_SCENARIO=ember-3.5 + - env: EMBER_TRY_SCENARIO=ember-3.6 + - env: EMBER_TRY_SCENARIO=ember-3.7 - env: EMBER_TRY_SCENARIO=ember-release - env: EMBER_TRY_SCENARIO=ember-beta - env: EMBER_TRY_SCENARIO=ember-canary diff --git a/addon/.gitkeep b/addon/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/.gitkeep b/app/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/config/ember-try.js b/config/ember-try.js index ec08a5b..62a0538 100644 --- a/config/ember-try.js +++ b/config/ember-try.js @@ -11,6 +11,30 @@ module.exports = function() { return { useYarn: true, scenarios: [ + { + name: 'ember-lts-2.12', + env: { + EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true }), + }, + npm: { + devDependencies: { + '@ember/jquery': '^0.5.1', + 'ember-source': '~2.12.0', + }, + }, + }, + { + name: 'ember-lts-2.16', + env: { + EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true }), + }, + npm: { + devDependencies: { + '@ember/jquery': '^0.5.1', + 'ember-source': '~2.16.0', + }, + }, + }, { name: 'ember-lts-2.18', env: { @@ -31,6 +55,30 @@ module.exports = function() { }, }, }, + { + name: 'ember-3.5', + npm: { + devDependencies: { + 'ember-source': '~3.5.0', + }, + }, + }, + { + name: 'ember-3.6', + npm: { + devDependencies: { + 'ember-source': '~3.6.0', + }, + }, + }, + { + name: 'ember-3.7', + npm: { + devDependencies: { + 'ember-source': '~3.7.0', + }, + }, + }, { name: 'ember-release', npm: { diff --git a/index.js b/index.js index 0ca063d..77a51e2 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,44 @@ 'use strict'; +const VersionChecker = require('ember-cli-version-checker'); + module.exports = { name: require('./package').name, + + init() { + this._super.init && this._super.init.apply(this, arguments); + + let checker = new VersionChecker(this.project); + let emberVersion = checker.forEmber(); + + this.shouldPolyfill = emberVersion.lt('3.8.0-alpha.1'); + }, + + included() { + this._super.included.apply(this, arguments); + + if (!this.shouldPolyfill) { + return; + } + + this.import('vendor/ember-modifier-manager-polyfill.js'); + }, + + treeForVendor(rawVendorTree) { + if (!this.shouldPolyfill) { + return; + } + + let babelAddon = this.addons.find(addon => addon.name === 'ember-cli-babel'); + + let transpiledVendorTree = babelAddon.transpileTree(rawVendorTree, { + babel: this.options.babel, + + 'ember-cli-babel': { + compileModules: false, + }, + }); + + return transpiledVendorTree; + }, }; diff --git a/package.json b/package.json index f0b1c7a..399c4c9 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "test:all": "ember try:each" }, "dependencies": { - "ember-cli-babel": "^7.4.0" + "ember-cli-babel": "^7.4.0", + "ember-cli-version-checker": "^2.1.2", + "ember-compatibility-helpers": "^1.2.0-beta.1" }, "devDependencies": { "@ember/optional-features": "^0.7.0", diff --git a/tests/integration/components/modifier-manager-test.js b/tests/integration/components/modifier-manager-test.js new file mode 100644 index 0000000..f84fc85 --- /dev/null +++ b/tests/integration/components/modifier-manager-test.js @@ -0,0 +1,202 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; +import Ember from 'ember'; +import { assign } from '@ember/polyfills'; + +module('Integration | Component | modifier-manager', function(hooks) { + setupRenderingTest(hooks); + + hooks.beforeEach(function(assert) { + assert.namedEquals = function(actual, expected, message) { + // this is needed because older versions of Ember pass an `EmptyObject` + // based object and QUnit fails due to the prototypes not matching + let sanitizedActual = assign({}, actual); + + assert.deepEqual(sanitizedActual, expected, message); + }; + }); + + module('installModifier', function(hooks) { + hooks.beforeEach(function() { + class DidInsertModifier {} + + Ember._setModifierManager( + () => ({ + createModifier(_factory, args) { + return args.positional[0]; + }, + + installModifier(_state, element, args) { + let [fn, ...positional] = args.positional; + + fn(element, positional, args.named); + }, + + updateModifier() {}, + destroyModifier() {}, + }), + DidInsertModifier + ); + this.owner.register('modifier:did-insert', DidInsertModifier); + }); + + test('it basically works', async function(assert) { + assert.expect(2); + + this.someMethod = element => { + assert.equal(element.tagName, 'DIV', 'correct element tagName'); + assert.dom(element).hasAttribute('data-foo', 'some-thing'); + }; + await render(hbs`
`); + }); + + test('it can accept arguments', async function(assert) { + assert.expect(4); + + this.someMethod = (element, positional, named) => { + assert.equal(element.tagName, 'DIV', 'correct element tagName'); + assert.dom(element).hasAttribute('data-foo', 'some-thing'); + + assert.namedEquals(named, { some: 'hash-value' }, 'named args match'); + assert.deepEqual(positional, ['some-positional-value'], 'positional args match'); + }; + + await render( + hbs`` + ); + }); + + test('it is not invoked again when arguments change', async function(assert) { + assert.expect(4); + + this.someMethod = (element, positional, named) => { + assert.equal(element.tagName, 'DIV', 'correct element tagName'); + assert.dom(element).hasAttribute('data-foo', 'some-thing'); + + assert.namedEquals(named, {}, 'named args match'); + assert.deepEqual(positional, ['initial'], 'positional args match'); + }; + + this.set('firstArg', 'initial'); + await render( + hbs`` + ); + this.set('firstArg', 'updated'); + }); + }); + + module('updateModifier', function(hooks) { + hooks.beforeEach(function() { + class DidUpdateModifier {} + + Ember._setModifierManager( + () => ({ + createModifier() { + return {}; + }, + installModifier(state, element) { + state.element = element; + }, + + updateModifier({ element }, args) { + let [fn, ...positional] = args.positional; + + fn(element, positional, args.named); + }, + + destroyModifier() {}, + }), + DidUpdateModifier + ); + this.owner.register('modifier:did-update', DidUpdateModifier); + }); + + test('it basically works', async function(assert) { + assert.expect(4); + + this.someMethod = (element, positional, named) => { + assert.equal(element.tagName, 'DIV', 'correct element tagName'); + assert.dom(element).hasAttribute('data-foo', 'some-thing'); + + assert.namedEquals(named, {}, 'named args match'); + assert.deepEqual(positional, ['update'], 'positional args match'); + }; + + this.set('boundValue', 'initial'); + await render( + hbs`` + ); + + this.set('boundValue', 'update'); + }); + }); + + module('destroyModifier', function(hooks) { + hooks.beforeEach(function() { + class WillDestroyModifier {} + + Ember._setModifierManager( + () => ({ + createModifier() { + return {}; + }, + + installModifier(state, element) { + state.element = element; + }, + + updateModifier() {}, + + destroyModifier({ element }, args) { + let [fn, ...positional] = args.positional; + + fn(element, positional, args.named); + }, + }), + WillDestroyModifier + ); + + this.owner.register('modifier:will-destroy', WillDestroyModifier); + }); + + test('it basically works', async function(assert) { + assert.expect(2); + + this.someMethod = element => { + assert.equal(element.tagName, 'DIV', 'correct element tagName'); + assert.dom(element).hasAttribute('data-foo', 'some-thing'); + }; + this.set('show', true); + + await render( + hbs`{{#if show}}{{/if}}` + ); + + // trigger destroy + this.set('show', false); + }); + + test('it can accept arguments', async function(assert) { + assert.expect(4); + + this.someMethod = (element, positional, named) => { + assert.equal(element.tagName, 'DIV', 'correct element tagName'); + assert.dom(element).hasAttribute('data-foo', 'some-thing'); + + assert.namedEquals(named, { some: 'hash-value' }, 'named args match'); + assert.deepEqual(positional, ['some-positional-value'], 'positional args match'); + }; + + this.set('show', true); + + await render( + hbs`{{#if show}}{{/if}}` + ); + + // trigger destroy + this.set('show', false); + }); + }); +}); diff --git a/vendor/.gitkeep b/vendor/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/ember-modifier-manager-polyfill.js b/vendor/ember-modifier-manager-polyfill.js new file mode 100644 index 0000000..d058a32 --- /dev/null +++ b/vendor/ember-modifier-manager-polyfill.js @@ -0,0 +1,228 @@ +/* globals Ember */ +/* eslint-disable ember/new-module-imports */ + +import { lte, gte } from 'ember-compatibility-helpers'; + +(() => { + 'use strict'; + + const getPrototypeOf = Object.getPrototypeOf; + const { Application } = Ember; + let MODIFIER_MANAGERS = new WeakMap(); + Ember._setModifierManager = function Polyfilled_setModifierManager(managerFactory, modifier) { + MODIFIER_MANAGERS.set(modifier, managerFactory); + }; + + let getModifierManager = obj => { + let pointer = obj; + while (pointer !== undefined && pointer !== null) { + if (MODIFIER_MANAGERS.has(pointer)) { + return MODIFIER_MANAGERS.get(pointer); + } + + pointer = getPrototypeOf(pointer); + } + + return; + }; + + let valueForCapturedArgs = function valueForCapturedArgs(args) { + return { + named: args.named.value(), + positional: args.positional.value(), + }; + }; + + Application.reopenClass({ + buildRegistry() { + let registry = this._super(...arguments); + + class CustomModifierState { + constructor(element, delegate, modifier, args) { + this.element = element; + this.delegate = delegate; + this.modifier = modifier; + this.args = args; + } + + destroy() { + const { delegate, modifier, args } = this; + let modifierArgs = valueForCapturedArgs(args); + delegate.destroyModifier(modifier, modifierArgs); + } + } + + class Polyfilled_CustomModifierManager { + //create(element: Simple.Element, state: ModifierDefinitionState, args: IArguments, dynamicScope: DynamicScope, dom: DOMChanges): ModifierInstanceState; + create(element, definition, args) { + let capturedArgs = gte('2.15.0-alpha.1') ? args.capture() : args; + let modifierArgs = valueForCapturedArgs(capturedArgs); + + let instance = definition.delegate.createModifier(definition.ModifierClass, modifierArgs); + + return new CustomModifierState(element, definition.delegate, instance, capturedArgs); + } + + //getTag(modifier: ModifierInstanceState): Tag; + getTag({ args }) { + return args.tag; + } + + //install(modifier: ModifierInstanceState): void; + install(state) { + let { element, args, delegate, modifier } = state; + let modifierArgs = valueForCapturedArgs(args); + delegate.installModifier(modifier, element, modifierArgs); + } + + //update(modifier: ModifierInstanceState): void; + update(state) { + let { args, delegate, modifier } = state; + let modifierArgs = valueForCapturedArgs(args); + delegate.updateModifier(modifier, modifierArgs); + } + + //getDestructor(modifier: ModifierInstanceState): Option