diff --git a/package.json b/package.json index 163a47a8ba2..9682446d327 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "git-repo-info": "^1.1.4", "git-repo-version": "^0.3.1", "github": "^0.2.3", - "glimmer-engine": "0.13.0", + "glimmer-engine": "0.13.1", "glob": "^5.0.13", "html-differ": "^1.3.4", "mocha": "^2.4.5", diff --git a/packages/ember-glimmer/lib/environment.js b/packages/ember-glimmer/lib/environment.js index 8acadb68df7..80fe09e2133 100644 --- a/packages/ember-glimmer/lib/environment.js +++ b/packages/ember-glimmer/lib/environment.js @@ -37,6 +37,7 @@ import { wrapComponentClassAttribute } from './utils/bindings'; import { default as action } from './helpers/action'; import { default as componentHelper } from './helpers/component'; import { default as concat } from './helpers/concat'; +import { default as debuggerHelper } from './helpers/debugger'; import { default as get } from './helpers/get'; import { default as hash } from './helpers/hash'; import { default as loc } from './helpers/loc'; @@ -107,6 +108,7 @@ export default class Environment extends GlimmerEnvironment { action, component: componentHelper, concat, + debugger: debuggerHelper, get, hash, loc, @@ -121,7 +123,7 @@ export default class Environment extends GlimmerEnvironment { '-input-type': inputTypeHelper, '-normalize-class': normalizeClassHelper, '-html-safe': htmlSafeHelper, - '-get-dynamic-var': { glimmerNativeHelper: getDynamicVar } + '-get-dynamic-var': getDynamicVar }; } @@ -202,7 +204,7 @@ export default class Environment extends GlimmerEnvironment { assert(`A helper named "${key}" could not be found`, !isBlock || this.hasHelper(path, symbolTable)); } - if ((!isSimple && appendType === 'unknown') || appendType === 'self-get') { + if (!isSimple && appendType === 'unknown') { return statement.original.deopt(); } @@ -275,12 +277,16 @@ export default class Environment extends GlimmerEnvironment { } let name = nameParts[0]; + + if (this.builtInHelpers[name]) { + return true; + } + let blockMeta = symbolTable.getMeta(); let owner = blockMeta.owner; let options = { source: `template:${blockMeta.moduleName}` }; - return !!this.builtInHelpers[name] || - owner.hasRegistration(`helper:${name}`, options) || + return owner.hasRegistration(`helper:${name}`, options) || owner.hasRegistration(`helper:${name}`); } @@ -288,23 +294,23 @@ export default class Environment extends GlimmerEnvironment { assert('The first argument passed into `lookupHelper` should be an array', Array.isArray(nameParts)); let name = nameParts[0]; + let helper = this.builtInHelpers[name]; + + if (helper) { + return helper; + } + let blockMeta = symbolTable.getMeta(); let owner = blockMeta.owner; let options = blockMeta.moduleName && { source: `template:${blockMeta.moduleName}` } || {}; - let helper = this.builtInHelpers[name] || - owner.lookup(`helper:${name}`, options) || - owner.lookup(`helper:${name}`); + helper = owner.lookup(`helper:${name}`, options) || owner.lookup(`helper:${name}`); // TODO: try to unify this into a consistent protocol to avoid wasteful closure allocations - if (helper.isInternalHelper) { - return (vm, args) => helper.toReference(args, this, symbolTable); - } else if (helper.isHelperInstance) { + if (helper.isHelperInstance) { return (vm, args) => SimpleHelperReference.create(helper.compute, args); } else if (helper.isHelperFactory) { return (vm, args) => ClassBasedHelperReference.create(helper, vm, args); - } else if (helper.glimmerNativeHelper) { - return helper.glimmerNativeHelper; } else { throw new Error(`${nameParts} is not a helper`); } diff --git a/packages/ember-glimmer/lib/helpers/-class.js b/packages/ember-glimmer/lib/helpers/-class.js index db13930a835..f5709df6f01 100644 --- a/packages/ember-glimmer/lib/helpers/-class.js +++ b/packages/ember-glimmer/lib/helpers/-class.js @@ -23,9 +23,6 @@ function classHelper({ positional }) { return value; } -export default { - isInternalHelper: true, - toReference(args) { - return new InternalHelperReference(classHelper, args); - } -}; +export default function(vm, args) { + return new InternalHelperReference(classHelper, args); +} diff --git a/packages/ember-glimmer/lib/helpers/-html-safe.js b/packages/ember-glimmer/lib/helpers/-html-safe.js index 70f7fb84440..72f538c5037 100644 --- a/packages/ember-glimmer/lib/helpers/-html-safe.js +++ b/packages/ember-glimmer/lib/helpers/-html-safe.js @@ -6,9 +6,6 @@ function htmlSafe({ positional }) { return new SafeString(path.value()); } -export default { - isInternalHelper: true, - toReference(args) { - return new InternalHelperReference(htmlSafe, args); - } -}; +export default function(vm, args) { + return new InternalHelperReference(htmlSafe, args); +} diff --git a/packages/ember-glimmer/lib/helpers/-input-type.js b/packages/ember-glimmer/lib/helpers/-input-type.js index 111afb7cb21..a51d2fbeb6a 100644 --- a/packages/ember-glimmer/lib/helpers/-input-type.js +++ b/packages/ember-glimmer/lib/helpers/-input-type.js @@ -8,9 +8,6 @@ function inputTypeHelper({ positional, named }) { return '-text-field'; } -export default { - isInternalHelper: true, - toReference(args) { - return new InternalHelperReference(inputTypeHelper, args); - } -}; +export default function(vm, args) { + return new InternalHelperReference(inputTypeHelper, args); +} diff --git a/packages/ember-glimmer/lib/helpers/-normalize-class.js b/packages/ember-glimmer/lib/helpers/-normalize-class.js index a916e86e823..c53b3d3293f 100644 --- a/packages/ember-glimmer/lib/helpers/-normalize-class.js +++ b/packages/ember-glimmer/lib/helpers/-normalize-class.js @@ -15,9 +15,6 @@ function normalizeClass({ positional, named }) { } } -export default { - isInternalHelper: true, - toReference(args) { - return new InternalHelperReference(normalizeClass, args); - } -}; +export default function(vm, args) { + return new InternalHelperReference(normalizeClass, args); +} diff --git a/packages/ember-glimmer/lib/helpers/action.js b/packages/ember-glimmer/lib/helpers/action.js index b1fc0d4e8e8..3fb14da767f 100644 --- a/packages/ember-glimmer/lib/helpers/action.js +++ b/packages/ember-glimmer/lib/helpers/action.js @@ -81,13 +81,9 @@ export class ClosureActionReference extends CachedReference { } } -export default { - isInternalHelper: true, - - toReference(args) { - return ClosureActionReference.create(args); - } -}; +export default function(vm, args) { + return ClosureActionReference.create(args); +} export function createClosureAction(target, action, valuePath, actionArgs) { let closureAction; diff --git a/packages/ember-glimmer/lib/helpers/component.js b/packages/ember-glimmer/lib/helpers/component.js index ef0d608941a..2ab0dd17a80 100644 --- a/packages/ember-glimmer/lib/helpers/component.js +++ b/packages/ember-glimmer/lib/helpers/component.js @@ -122,10 +122,6 @@ function curryArgs(definition, newArgs) { return mergedArgs; } -export default { - isInternalHelper: true, - - toReference(args, env, symbolTable) { - return ClosureComponentReference.create(args, symbolTable, env); - } -}; +export default function(vm, args, symbolTable) { + return ClosureComponentReference.create(args, symbolTable, vm.env); +} diff --git a/packages/ember-glimmer/lib/helpers/concat.js b/packages/ember-glimmer/lib/helpers/concat.js index 443a145ca9b..f6a8e7a38dd 100644 --- a/packages/ember-glimmer/lib/helpers/concat.js +++ b/packages/ember-glimmer/lib/helpers/concat.js @@ -1,4 +1,4 @@ -import { helper } from '../helper'; +import { InternalHelperReference } from '../utils/references'; import { normalizeTextValue } from 'glimmer-runtime'; /** @@ -22,8 +22,10 @@ import { normalizeTextValue } from 'glimmer-runtime'; @for Ember.Templates.helpers @since 1.13.0 */ -function concat(args) { - return args.map(normalizeTextValue).join(''); +function concat({ positional }) { + return positional.value().map(normalizeTextValue).join(''); } -export default helper(concat); +export default function(vm, args) { + return new InternalHelperReference(concat, args); +} diff --git a/packages/ember-glimmer/lib/helpers/debugger.js b/packages/ember-glimmer/lib/helpers/debugger.js new file mode 100644 index 00000000000..4038a87c11f --- /dev/null +++ b/packages/ember-glimmer/lib/helpers/debugger.js @@ -0,0 +1,92 @@ +/*jshint debug:true*/ + +/** +@module ember +@submodule ember-htmlbars +*/ + +import { info } from 'ember-metal/debug'; +import { UNDEFINED_REFERENCE, GetSyntax, CompileIntoList } from 'glimmer-runtime'; + +/** + Execute the `debugger` statement in the current template's context. + + ```handlebars + {{debugger}} + ``` + + When using the debugger helper you will have access to a `get` function. This + function retrieves values available in the context of the template. + For example, if you're wondering why a value `{{foo}}` isn't rendering as + expected within a template, you could place a `{{debugger}}` statement and, + when the `debugger;` breakpoint is hit, you can attempt to retrieve this value: + + ``` + > get('foo') + ``` + + `get` is also aware of block variables. So in this situation + + ```handlebars + {{#each items as |item|}} + {{debugger}} + {{/each}} + ``` + + You'll be able to get values from the current item: + + ``` + > get('item.name') + ``` + + You can also access the context of the view to make sure it is the object that + you expect: + + ``` + > context + ``` + + @method debugger + @for Ember.Templates.helpers + @public +*/ +function defaultCallback(context, get) { + /* jshint debug: true */ + + info('Use `context`, and `get()` to debug this template.'); + + debugger; +} + +let callback = defaultCallback; + +export default function debuggerHelper(vm, args, symbolTable) { + let context = vm.getSelf().value(); + + // Note: this is totally an overkill since we are only compiling + // expressions, but this is the only kind of SymbolLookup we can + // construct. The symbol table itself should really be sufficient + // here – we should refactor the Glimmer code to make that possible. + let symbolLookup = new CompileIntoList(vm.env, symbolTable); + + function get(path) { + // Problem: technically, we are getting a `PublicVM` here, but to + // evaluate an expression it requires the full VM. We happen to know + // that they are the same thing, so this would work for now. However + // this might break in the future. + return GetSyntax.build(path).compile(symbolLookup).evaluate(vm).value(); + } + + callback(context, get); + + return UNDEFINED_REFERENCE; +} + +// These are exported for testing +export function setDebuggerCallback(newCallback) { + callback = newCallback; +} + +export function resetDebuggerCallback() { + callback = defaultCallback; +} diff --git a/packages/ember-glimmer/lib/helpers/each-in.js b/packages/ember-glimmer/lib/helpers/each-in.js index c830e8f9965..f12f3ac9f5b 100644 --- a/packages/ember-glimmer/lib/helpers/each-in.js +++ b/packages/ember-glimmer/lib/helpers/each-in.js @@ -41,11 +41,8 @@ export function isEachIn(ref) { return ref && ref[EACH_IN_REFERENCE]; } -export default { - isInternalHelper: true, - toReference(args) { - let ref = Object.create(args.positional.at(0)); - ref[EACH_IN_REFERENCE] = true; - return ref; - } -}; +export default function(vm, args) { + let ref = Object.create(args.positional.at(0)); + ref[EACH_IN_REFERENCE] = true; + return ref; +} diff --git a/packages/ember-glimmer/lib/helpers/get.js b/packages/ember-glimmer/lib/helpers/get.js index 8377794ceb1..25e58a26c90 100644 --- a/packages/ember-glimmer/lib/helpers/get.js +++ b/packages/ember-glimmer/lib/helpers/get.js @@ -51,13 +51,9 @@ import { CONSTANT_TAG, UpdatableTag, combine, isConst, referenceFromParts } from @since 2.1.0 */ -export default { - isInternalHelper: true, - - toReference(args) { - return GetHelperReference.create(args.positional.at(0), args.positional.at(1)); - } -}; +export default function(vm, args) { + return GetHelperReference.create(args.positional.at(0), args.positional.at(1)); +} class GetHelperReference extends CachedReference { static create(sourceReference, pathReference) { diff --git a/packages/ember-glimmer/lib/helpers/hash.js b/packages/ember-glimmer/lib/helpers/hash.js index ad960504697..4c062863464 100644 --- a/packages/ember-glimmer/lib/helpers/hash.js +++ b/packages/ember-glimmer/lib/helpers/hash.js @@ -30,9 +30,6 @@ @public */ -export default { - isInternalHelper: true, - toReference(args) { - return args.named; - } -}; +export default function(vm, args) { + return args.named; +} diff --git a/packages/ember-glimmer/lib/helpers/if-unless.js b/packages/ember-glimmer/lib/helpers/if-unless.js index ee3b91102bb..385243c4fd7 100644 --- a/packages/ember-glimmer/lib/helpers/if-unless.js +++ b/packages/ember-glimmer/lib/helpers/if-unless.js @@ -58,20 +58,17 @@ class ConditionalHelperReference extends CachedReference { @for Ember.Templates.helpers @public */ -export const inlineIf = { - isInternalHelper: true, - toReference({ positional: pargs }) { - switch (pargs.length) { - case 2: return ConditionalHelperReference.create(pargs.at(0), pargs.at(1), null); - case 3: return ConditionalHelperReference.create(pargs.at(0), pargs.at(1), pargs.at(2)); - default: - assert( - 'The inline form of the `if` helper expects two or three arguments, e.g. ' + - '`{{if trialExpired "Expired" expiryDate}}`.' - ); - } +export function inlineIf(vm, { positional }) { + switch (positional.length) { + case 2: return ConditionalHelperReference.create(positional.at(0), positional.at(1), null); + case 3: return ConditionalHelperReference.create(positional.at(0), positional.at(1), positional.at(2)); + default: + assert( + 'The inline form of the `if` helper expects two or three arguments, e.g. ' + + '`{{if trialExpired "Expired" expiryDate}}`.' + ); } -}; +} /** The inline `unless` helper conditionally renders a single property or string. @@ -89,17 +86,14 @@ export const inlineIf = { @for Ember.Templates.helpers @public */ -export const inlineUnless = { - isInternalHelper: true, - toReference({ positional: pargs }) { - switch (pargs.length) { - case 2: return ConditionalHelperReference.create(pargs.at(0), null, pargs.at(1)); - case 3: return ConditionalHelperReference.create(pargs.at(0), pargs.at(2), pargs.at(1)); - default: - assert( - 'The inline form of the `unless` helper expects two or three arguments, e.g. ' + - '`{{unless isFirstLogin "Welcome back!"}}`.' - ); - } +export function inlineUnless(vm, { positional }) { + switch (positional.length) { + case 2: return ConditionalHelperReference.create(positional.at(0), null, positional.at(1)); + case 3: return ConditionalHelperReference.create(positional.at(0), positional.at(2), positional.at(1)); + default: + assert( + 'The inline form of the `unless` helper expects two or three arguments, e.g. ' + + '`{{unless isFirstLogin "Welcome back!"}}`.' + ); } -}; +} diff --git a/packages/ember-glimmer/lib/helpers/loc.js b/packages/ember-glimmer/lib/helpers/loc.js index 2770fd04fb0..7548e13955c 100644 --- a/packages/ember-glimmer/lib/helpers/loc.js +++ b/packages/ember-glimmer/lib/helpers/loc.js @@ -1,4 +1,4 @@ -import { helper } from '../helper'; +import { InternalHelperReference } from '../utils/references'; import { String as StringUtils } from 'ember-runtime'; /** @@ -38,8 +38,10 @@ import { String as StringUtils } from 'ember-runtime'; @see {Ember.String#loc} @public */ -function locHelper(params) { - return StringUtils.loc.apply(null, params); +function locHelper({ positional }) { + return StringUtils.loc.apply(null, positional.value()); } -export default helper(locHelper); +export default function(vm, args) { + return new InternalHelperReference(locHelper, args); +} diff --git a/packages/ember-glimmer/lib/helpers/log.js b/packages/ember-glimmer/lib/helpers/log.js index 6273370f3e1..19466ba0395 100644 --- a/packages/ember-glimmer/lib/helpers/log.js +++ b/packages/ember-glimmer/lib/helpers/log.js @@ -1,4 +1,4 @@ -import { helper } from '../helper'; +import { InternalHelperReference } from '../utils/references'; /** @module ember @submodule ember-templates @@ -19,8 +19,10 @@ import Logger from 'ember-console'; @param {Array} params @public */ -function log(params) { - Logger.log.apply(null, params); +function log({ positional }) { + Logger.log.apply(null, positional.value()); } -export default helper(log); +export default function(vm, args) { + return new InternalHelperReference(log, args); +} diff --git a/packages/ember-glimmer/lib/helpers/mut.js b/packages/ember-glimmer/lib/helpers/mut.js index 876df838e8d..21d50a1596e 100644 --- a/packages/ember-glimmer/lib/helpers/mut.js +++ b/packages/ember-glimmer/lib/helpers/mut.js @@ -60,36 +60,32 @@ export function unMut(ref) { return ref[SOURCE] || ref; } -export default { - isInternalHelper: true, +export default function(vm, args) { + let rawRef = args.positional.at(0); - toReference(args) { - let rawRef = args.positional.at(0); - - if (isMut(rawRef)) { - return rawRef; - } - - // TODO: Improve this error message. This covers at least two distinct - // cases: - // - // 1. (mut "not a path") – passing a literal, result from a helper - // invocation, etc - // - // 2. (mut receivedValue) – passing a value received from the caller - // that was originally derived from a literal, result from a helper - // invocation, etc - // - // This message is alright for the first case, but could be quite - // confusing for the second case. - assert('You can only pass a path to mut', rawRef[UPDATE]); - - let wrappedRef = Object.create(rawRef); - - wrappedRef[SOURCE] = rawRef; - wrappedRef[INVOKE] = rawRef[UPDATE]; - wrappedRef[MUT_REFERENCE] = true; - - return wrappedRef; + if (isMut(rawRef)) { + return rawRef; } -}; + + // TODO: Improve this error message. This covers at least two distinct + // cases: + // + // 1. (mut "not a path") – passing a literal, result from a helper + // invocation, etc + // + // 2. (mut receivedValue) – passing a value received from the caller + // that was originally derived from a literal, result from a helper + // invocation, etc + // + // This message is alright for the first case, but could be quite + // confusing for the second case. + assert('You can only pass a path to mut', rawRef[UPDATE]); + + let wrappedRef = Object.create(rawRef); + + wrappedRef[SOURCE] = rawRef; + wrappedRef[INVOKE] = rawRef[UPDATE]; + wrappedRef[MUT_REFERENCE] = true; + + return wrappedRef; +} diff --git a/packages/ember-glimmer/lib/helpers/query-param.js b/packages/ember-glimmer/lib/helpers/query-param.js index 7dd767a7b4f..1057bda1741 100644 --- a/packages/ember-glimmer/lib/helpers/query-param.js +++ b/packages/ember-glimmer/lib/helpers/query-param.js @@ -10,9 +10,6 @@ function queryParams({ positional, named }) { }); } -export default { - isInternalHelper: true, - toReference(args) { - return new InternalHelperReference(queryParams, args); - } -}; +export default function(vm, args) { + return new InternalHelperReference(queryParams, args); +} diff --git a/packages/ember-glimmer/lib/helpers/readonly.js b/packages/ember-glimmer/lib/helpers/readonly.js index 76d18284793..b1224ad2b80 100644 --- a/packages/ember-glimmer/lib/helpers/readonly.js +++ b/packages/ember-glimmer/lib/helpers/readonly.js @@ -1,16 +1,12 @@ import { UPDATE } from '../utils/references'; import { unMut } from './mut'; -export default { - isInternalHelper: true, +export default function(vm, args) { + let ref = unMut(args.positional.at(0)); - toReference(args) { - let ref = unMut(args.positional.at(0)); + let wrapped = Object.create(ref); - let wrapped = Object.create(ref); + wrapped[UPDATE] = undefined; - wrapped[UPDATE] = undefined; - - return wrapped; - } -}; + return wrapped; +} diff --git a/packages/ember-glimmer/lib/helpers/unbound.js b/packages/ember-glimmer/lib/helpers/unbound.js index 471c2d02dfb..c6b2d970537 100644 --- a/packages/ember-glimmer/lib/helpers/unbound.js +++ b/packages/ember-glimmer/lib/helpers/unbound.js @@ -34,14 +34,11 @@ import { UnboundReference } from '../utils/references'; @public */ -export default { - isInternalHelper: true, - toReference(args) { - assert( - 'unbound helper cannot be called with multiple params or hash params', - args.positional.values.length === 1 && args.named.keys.length === 0 - ); - - return UnboundReference.create(args.positional.at(0).value()); - } -}; +export default function(vm, args) { + assert( + 'unbound helper cannot be called with multiple params or hash params', + args.positional.values.length === 1 && args.named.keys.length === 0 + ); + + return UnboundReference.create(args.positional.at(0).value()); +} diff --git a/packages/ember-glimmer/lib/utils/iterable.js b/packages/ember-glimmer/lib/utils/iterable.js index bf38e4d237f..97b83f7e456 100644 --- a/packages/ember-glimmer/lib/utils/iterable.js +++ b/packages/ember-glimmer/lib/utils/iterable.js @@ -156,7 +156,7 @@ class ObjectKeysIterator { this.position++; - return { key, value: memo, memo: value }; + return { key, value, memo }; } } @@ -172,36 +172,10 @@ class EmptyIterator { const EMPTY_ITERATOR = new EmptyIterator(); -class AbstractIterable { +class EachInIterable { constructor(ref, keyFor) { this.ref = ref; this.keyFor = keyFor; - } - - iterate() { - throw new Error('Not implemented: iterate'); - } - - valueReferenceFor(item) { - return new UpdatableReference(item.value); - } - - updateValueReference(reference, item) { - reference.update(item.value); - } - - memoReferenceFor(item) { - return new UpdatablePrimitiveReference(item.memo); - } - - updateMemoReference(reference, item) { - reference.update(item.memo); - } -} - -class EachInIterable extends AbstractIterable { - constructor(ref, keyFor) { - super(ref, keyFor); let valueTag = this.valueTag = new UpdatableTag(CONSTANT_TAG); @@ -229,13 +203,34 @@ class EachInIterable extends AbstractIterable { return EMPTY_ITERATOR; } } + + // {{each-in}} yields |key value| instead of |value key|, so the memo and + // value are flipped + + valueReferenceFor(item) { + return new UpdatablePrimitiveReference(item.memo); + } + + updateValueReference(reference, item) { + reference.update(item.memo); + } + + memoReferenceFor(item) { + return new UpdatableReference(item.value); + } + + updateMemoReference(reference, item) { + reference.update(item.value); + } } -class ArrayIterable extends AbstractIterable { +class ArrayIterable { constructor(ref, keyFor) { - super(ref, keyFor); + this.ref = ref; + this.keyFor = keyFor; let valueTag = this.valueTag = new UpdatableTag(CONSTANT_TAG); + this.tag = combine([ref.tag, valueTag]); } @@ -264,4 +259,20 @@ class ArrayIterable extends AbstractIterable { return EMPTY_ITERATOR; } } + + valueReferenceFor(item) { + return new UpdatableReference(item.value); + } + + updateValueReference(reference, item) { + reference.update(item.value); + } + + memoReferenceFor(item) { + return new UpdatablePrimitiveReference(item.memo); + } + + updateMemoReference(reference, item) { + reference.update(item.memo); + } } diff --git a/packages/ember-glimmer/lib/utils/references.js b/packages/ember-glimmer/lib/utils/references.js index 4068237ab25..e3a6e01d455 100644 --- a/packages/ember-glimmer/lib/utils/references.js +++ b/packages/ember-glimmer/lib/utils/references.js @@ -201,7 +201,7 @@ export class NestedPropertyReference extends PropertyReference { return get(parentValue, _propertyKey); } else { - return null; + return undefined; } } diff --git a/packages/ember-glimmer/tests/integration/helpers/debugger-test.js b/packages/ember-glimmer/tests/integration/helpers/debugger-test.js new file mode 100644 index 00000000000..ceba2027338 --- /dev/null +++ b/packages/ember-glimmer/tests/integration/helpers/debugger-test.js @@ -0,0 +1,262 @@ +import { RenderingTest, moduleFor } from '../../utils/test-case'; +import { Component } from '../../utils/helpers'; +import { setDebuggerCallback, resetDebuggerCallback } from '../../../helpers/debugger'; +import { set } from 'ember-metal'; +import { A as emberA } from 'ember-runtime'; + +// This file is generally not I-N-U-R tested, because the {{debugger}} helper currently +// does not run during re-render. This is something we eventually want to do, and when +// we implement that feature these tests should be updated accordingly. + +moduleFor('Helpers test: {{debugger}}', class extends RenderingTest { + teardown() { + super.teardown(); + resetDebuggerCallback(); + } + + expectDebuggerCallback(callback, debuggerCallback, times = 1) { + let called = 0; + + setDebuggerCallback((context, get) => { + called++; + debuggerCallback(context, get); + }); + + callback(); + + this.assert.strictEqual(called, times, `Expect debugger callback to be called exactly ${times} time(s)`); + } + + expectNoDebuggerCallback(callback) { + let called = 0; + + setDebuggerCallback(() => called++); + + callback(); + + this.assert.strictEqual(called, 0, 'Expect no debugger callback'); + } + + ['@test should have the right context when used in a component layout'](assert) { + let instance; + + this.registerComponent('my-wrapper', { + template: `{{yield}}` + }); + + this.registerComponent('foo-bar', { + ComponentClass: Component.extend({ + init() { + this._super(); + instance = this; + } + }), + template: `{{debugger}}foo-bar` + }); + + this.expectDebuggerCallback( + () => { + this.render('{{#my-wrapper}}{{foo-bar}}{{/my-wrapper}}'); + }, + + context => { + assert.strictEqual(context, instance, 'context should be the component instance'); + } + ); + + this.assertText('foo-bar'); + + this.expectNoDebuggerCallback( + ()=> this.runTask(() => this.rerender()) + ); + + this.assertText('foo-bar'); + } + + ['@test should have the right context when yielded'](assert) { + let instance; + + this.registerComponent('my-wrapper', { + template: `{{yield}}` + }); + + this.registerComponent('foo-bar', { + ComponentClass: Component.extend({ + init() { + this._super(); + instance = this; + } + }), + template: `{{#my-wrapper}}{{debugger}}foo-bar{{/my-wrapper}}` + }); + + this.expectDebuggerCallback( + () => { + this.render('{{foo-bar}}'); + }, + + context => { + assert.strictEqual(context, instance, 'context should be the component instance'); + } + ); + + this.assertText('foo-bar'); + + this.expectNoDebuggerCallback( + ()=> this.runTask(() => this.rerender()) + ); + + this.assertText('foo-bar'); + } + + ['@test should be called once per iteration in a loop'](assert) { + let count = 0; + + setDebuggerCallback(() => count++); + + let items = emberA([1, 2, 3, 4, 5]); + + this.render('{{#each items as |item|}}{{debugger}}[{{item}}]{{/each}}', { items }); + + this.assertText('[1][2][3][4][5]'); + + assert.equal(count, 5, 'should have fired once per iteration'); + + count = 0; + + this.runTask(() => this.rerender()); + + this.assertText('[1][2][3][4][5]'); + + assert.strictEqual(count, 0, 'should not fire for re-render'); + + count = 0; + + this.runTask(() => items.pushObjects([6, 7, 8])); + + this.assertText('[1][2][3][4][5][6][7][8]'); + + assert.equal(count, 3, 'should fire once per new items added to the loop'); + } + + ['@test could `get` properties from "self"'](assert) { + this.registerComponent('foo-bar', { + ComponentClass: Component.extend({ + init() { + this._super(); + this.zomg = 'zomg'; + } + }), + template: `{{debugger not.here}}foo-bar` + }); + + this.expectDebuggerCallback( + () => { + this.render('{{foo-bar lol="lol" foo=foo}}', { foo: { bar: { baz: 'fooBarBaz' } } }); + }, + + (context, get) => { + assert.equal(get('this'), context, '{{this}}'); + + assert.equal(get('lol'), 'lol', '{{lol}}'); + assert.equal(get('this.lol'), 'lol', '{{this.lol}}'); + + assert.equal(get('zomg'), 'zomg', '{{zomg}}'); + assert.equal(get('this.zomg'), 'zomg', '{{this.zomg}}'); + + assert.equal(get('foo.bar.baz'), 'fooBarBaz', '{{foo.bar.baz}}'); + assert.equal(get('this.foo.bar.baz'), 'fooBarBaz', '{{this.foo.bar.baz}}'); + + assert.strictEqual(get('nope'), undefined, '{{nope}}'); + assert.strictEqual(get('this.nope'), undefined, '{{this.nope}}'); + + assert.strictEqual(get('not.here'), undefined, '{{not.here}}'); + assert.strictEqual(get('this.not.here'), undefined, '{{this.not.here}}'); + } + ); + + this.assertText('foo-bar'); + + this.expectNoDebuggerCallback( + ()=> this.runTask(() => this.rerender()) + ); + + this.assertText('foo-bar'); + } + + ['@test could `get` local variables'](assert) { + let obj = { + foo: 'foo', + bar: { baz: { bat: 'barBazBat' } } + }; + + this.expectDebuggerCallback( + () => { + this.render('{{#each-in obj as |key value|}}{{debugger}}[{{key}}]{{/each-in}}', { obj }); + }, + + (context, get) => { + assert.equal(get('this'), context, '{{this}}'); + + assert.equal(get('obj'), obj); + + // Glimmer bug: + // assert.strictEqual(get('this.key'), undefined, '{{this.key}}'); + // assert.strictEqual(get('this.value'), undefined, '{{this.value}}'); + + let key = get('key'); + + if (key === 'foo') { + assert.equal(get('value'), 'foo', '{{value}} for key=foo'); + assert.strictEqual(get('value.baz.bat'), undefined, '{{value.baz.bat}} for key=foo'); + assert.strictEqual(get('value.nope'), undefined, '{{value.nope}} for key=foo'); + } else if (key === 'bar') { + assert.equal(get('value'), obj.bar, '{{value}} for key=bar'); + assert.equal(get('value.baz.bat'), 'barBazBat', '{{value.baz.bat}} for key=bar'); + assert.strictEqual(get('value.nope'), undefined, '{{value.nope}} for key=bar'); + } else { + assert.ok(false, `Unknown key: ${key}`); + } + }, + + 2 + ); + + this.assertText('[foo][bar]'); + + this.expectNoDebuggerCallback( + ()=> this.runTask(() => this.rerender()) + ); + + this.assertText('[foo][bar]'); + + this.expectDebuggerCallback( + () => { + this.runTask(() => set(obj, 'baz', 'baz')); + }, + + (context, get) => { + assert.equal(get('this'), context, '{{this}}'); + + assert.equal(get('obj'), obj); + + assert.strictEqual(get('this.key'), undefined, '{{this.key}}'); + assert.strictEqual(get('this.value'), undefined, '{{this.value}}'); + + assert.equal(get('key'), 'baz', '{{key}} for key=baz'); + assert.equal(get('value'), 'baz', '{{value}} for key=baz'); + assert.strictEqual(get('value.baz.bat'), undefined, '{{value.baz.bat}} for key=baz'); + assert.strictEqual(get('value.nope'), undefined, '{{value.nope}} for key=baz'); + } + ); + + this.assertText('[foo][bar][baz]'); + + this.expectNoDebuggerCallback( + ()=> this.runTask(() => this.rerender()) + ); + + this.assertText('[foo][bar][baz]'); + } + +}); diff --git a/packages/ember-glimmer/tests/integration/syntax/each-in-test.js b/packages/ember-glimmer/tests/integration/syntax/each-in-test.js index 85645acc91e..37b8c8a238f 100644 --- a/packages/ember-glimmer/tests/integration/syntax/each-in-test.js +++ b/packages/ember-glimmer/tests/integration/syntax/each-in-test.js @@ -116,6 +116,55 @@ moduleFor('Syntax test: {{#each-in}}', class extends BasicEachInTest { `); } + [`@test it can render sub-paths of each item`]() { + this.render(strip` + + `, { + categories: { + 'Smartphones': { reports: { unitsSold: 8203 } }, + 'JavaScript Frameworks': { reports: { unitsSold: Infinity } } + } + }); + + this.assertHTML(strip` + + `); + + this.assertStableRerender(); + + this.runTask(() => { + set(this.context, 'categories.Smartphones.reports.unitsSold', 100); + set(this.context, 'categories.Tweets', { reports: { unitsSold: 443115 } }); + }); + + this.assertHTML(strip` + + `); + + this.runTask(() => set(this.context, 'categories', { + 'Smartphones': { reports: { unitsSold: 8203 } }, + 'JavaScript Frameworks': { reports: { unitsSold: Infinity } } + })); + + this.assertHTML(strip` + + `); + } + [`@test it can render duplicate items`]() { this.render(strip`