Skip to content

Commit

Permalink
Merge pull request #15848 from rwjblue/ensure-helpers-are-consistent
Browse files Browse the repository at this point in the history
[BUGFIX beta] Ensure helpers have a consistent API.
  • Loading branch information
rwjblue authored Nov 13, 2017
2 parents 5b3a22c + f98688e commit 05a89f0
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ moduleFor('Ember.Application Dependency Injection - Integration - default resolv

let lookedUpShorthandHelper = this.applicationInstance.factoryFor('helper:shorthand').class;

assert.ok(lookedUpShorthandHelper.isHelperInstance, 'shorthand helper isHelper');
assert.ok(lookedUpShorthandHelper.isHelperFactory, 'shorthand helper isHelper');

let lookedUpHelper = this.applicationInstance.factoryFor('helper:complete').class;

Expand All @@ -115,7 +115,7 @@ moduleFor('Ember.Application Dependency Injection - Integration - default resolv

let lookedUpShorthandHelper = this.applicationInstance.factoryFor('helper:shorthand').class;

assert.ok(lookedUpShorthandHelper.isHelperInstance, 'shorthand helper isHelper');
assert.ok(lookedUpShorthandHelper.isHelperFactory, 'shorthand helper isHelper');

let lookedUpHelper = this.applicationInstance.factoryFor('helper:complete').class;

Expand Down
14 changes: 11 additions & 3 deletions packages/ember-glimmer/lib/environment.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {
Reference,
PathReference,
} from '@glimmer/reference';
import {
AttributeManager,
CapturedArguments,
CompilableLayout,
CompiledDynamicProgram,
compileLayout,
Expand All @@ -15,6 +17,7 @@ import {
PrimitiveReference,
PartialDefinition,
Simple,
VM
} from '@glimmer/runtime';
import {
Destroyable, Opaque,
Expand Down Expand Up @@ -285,13 +288,18 @@ export default class Environment extends GlimmerEnvironment {
let helperFactory = owner.factoryFor(`helper:${name}`, options) || owner.factoryFor(`helper:${name}`);

// TODO: try to unify this into a consistent protocol to avoid wasteful closure allocations
if (helperFactory.class.isHelperInstance) {
return (_vm, args) => SimpleHelperReference.create(helperFactory.class.compute, args.capture());
let HelperReference: {
create: (HelperFactory: any, vm: VM, args: CapturedArguments ) => PathReference<Opaque>;
};
if (helperFactory.class.isSimpleHelperFactory) {
HelperReference = SimpleHelperReference;
} else if (helperFactory.class.isHelperFactory) {
return (vm, args) => ClassBasedHelperReference.create(helperFactory, vm, args.capture());
HelperReference = ClassBasedHelperReference;
} else {
throw new Error(`${name} is not a helper`);
}

return (vm, args) => HelperReference.create(helperFactory, vm, args.capture());
}

hasModifier(name: string) {
Expand Down
28 changes: 24 additions & 4 deletions packages/ember-glimmer/lib/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,29 @@ Helper.reopenClass({
isHelperFactory: true,
});

export interface HelperInstance {
isHelperInstance: true;
compute: Function;
}

export class SimpleHelperFactory {
isHelperFactory = true;
isSimpleHelperFactory = true;

private instance: HelperInstance;

constructor(compute: (positionalValue: any[], namedValue: any[]) => any) {
this.instance = {
isHelperInstance: true,
compute,
};
}

create(): HelperInstance {
return this.instance;
}
}

/**
In many cases, the ceremony of a full `Helper` class is not required.
The `helper` method create pure-function helpers without instances. For
Expand All @@ -127,10 +150,7 @@ Helper.reopenClass({
@since 1.13.0
*/
export function helper(helperFn: (params: any[], hash?: any) => string) {
return {
isHelperInstance: true,
compute: helperFn,
};
return new SimpleHelperFactory(helperFn);
}

export default Helper;
13 changes: 9 additions & 4 deletions packages/ember-glimmer/lib/utils/references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ import {
EMBER_GLIMMER_DETECT_BACKTRACKING_RERENDER,
MANDATORY_SETTER,
} from 'ember/features';
import { RECOMPUTE_TAG } from '../helper';
import {
RECOMPUTE_TAG,
SimpleHelperFactory,
} from '../helper';
import emberToBool from './to-bool';

export const UPDATE = symbol('UPDATE');
Expand Down Expand Up @@ -329,7 +332,9 @@ export class SimpleHelperReference extends CachedReference {
public helper: (positionalValue: any, namedValue: any) => any;
public args: any;

static create(helper: (positionalValue: any, namedValue: any) => any, args: CapturedArguments) {
static create(Helper: SimpleHelperFactory, _vm: VM, args: CapturedArguments) {
let helper = Helper.create();

if (isConst(args)) {
let { positional, named } = args;

Expand All @@ -341,15 +346,15 @@ export class SimpleHelperReference extends CachedReference {
maybeFreeze(namedValue);
}

let result = helper(positionalValue, namedValue);
let result = helper.compute(positionalValue, namedValue);

if (typeof result === 'object' && result !== null || typeof result === 'function') {
return new RootReference(result);
} else {
return PrimitiveReference.create(result);
}
} else {
return new SimpleHelperReference(helper, args);
return new SimpleHelperReference(helper.compute, args);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,32 @@ moduleFor('Helpers test: custom helpers', class extends RenderingTest {

equal(destroyCount, 1, 'destroy is called after a view is destroyed');
}

['@test simple helper can be invoked manually via `owner.factoryFor(...).create().compute()'](assert) {
this.registerHelper('some-helper', () => {
assert.ok(true, 'some-helper helper invoked');
return 'lolol';
});

let instance = this.owner.factoryFor('helper:some-helper').create();

assert.equal(typeof instance.compute, 'function', 'expected instance.compute to be present');
assert.equal(instance.compute(), 'lolol', 'can invoke `.compute`');
}

['@test class-based helper can be invoked manually via `owner.factoryFor(...).create().compute()']() {
this.registerHelper('some-helper', {
compute() {
assert.ok(true, 'some-helper helper invoked');
return 'lolol';
}
});

let instance = this.owner.factoryFor('helper:some-helper').create();

assert.equal(typeof instance.compute, 'function', 'expected instance.compute to be present');
assert.equal(instance.compute(), 'lolol', 'can invoke `.compute`');
}
});

// these feature detects prevent errors in these tests
Expand Down

0 comments on commit 05a89f0

Please sign in to comment.