mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 11:44:49 +08:00
FEATURE: Use Glimmer compiler for widget templates
Widgets can now specify a template which is precompiled using Glimmer's AST and then converted into our virtual dom code. Example: ```javascript createWidget('post-link-arrow', { template: hbs` {{#if attrs.above}} <a class="post-info arrow" title={{i18n "topic.jump_reply_up"}}> {{fa-icon "arrow-up"}} </a> {{else}} <a class="post-info arrow" title={{i18n "topic.jump_reply_down"}}> {{fa-icon "arrow-down"}} </a> {{/if}} `, click() { DiscourseURL.routeTo(this.attrs.shareUrl); } }); ```
This commit is contained in:
parent
7f8a90ef63
commit
dffb1fc4ee
|
@ -2,21 +2,21 @@ import PostCooked from 'discourse/widgets/post-cooked';
|
|||
import DecoratorHelper from 'discourse/widgets/decorator-helper';
|
||||
import { createWidget } from 'discourse/widgets/widget';
|
||||
import { h } from 'virtual-dom';
|
||||
import { iconNode } from 'discourse-common/lib/icon-library';
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import hbs from 'discourse/widgets/hbs-compiler';
|
||||
|
||||
createWidget('post-link-arrow', {
|
||||
html(attrs) {
|
||||
if (attrs.above) {
|
||||
return h('a.post-info.arrow', {
|
||||
attributes: { title: I18n.t('topic.jump_reply_up') }
|
||||
}, iconNode('arrow-up'));
|
||||
} else {
|
||||
return h('a.post-info.arrow', {
|
||||
attributes: { title: I18n.t('topic.jump_reply_down') }
|
||||
}, iconNode('arrow-down'));
|
||||
}
|
||||
},
|
||||
template: hbs`
|
||||
{{#if attrs.above}}
|
||||
<a class="post-info arrow" title={{i18n "topic.jump_reply_up"}}>
|
||||
{{fa-icon "arrow-up"}}
|
||||
</a>
|
||||
{{else}}
|
||||
<a class="post-info arrow" title={{i18n "topic.jump_reply_down"}}>
|
||||
{{fa-icon "arrow-down"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
`,
|
||||
|
||||
click() {
|
||||
DiscourseURL.routeTo(this.attrs.shareUrl);
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export default function hbs() {
|
||||
console.log('Templates should be precompiled server side');
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import hbs from 'discourse/widgets/hbs-compiler';
|
||||
import { createWidget } from 'discourse/widgets/widget';
|
||||
import { h } from 'virtual-dom';
|
||||
|
||||
|
@ -23,14 +24,17 @@ createWidget('menu-links', {
|
|||
|
||||
createWidget('menu-panel', {
|
||||
tagName: 'div.menu-panel',
|
||||
template: hbs`
|
||||
<div class='panel-body'>
|
||||
<div class='panel-body-contents clearfix'>
|
||||
{{yield}}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
buildAttributes(attrs) {
|
||||
if (attrs.maxWidth) {
|
||||
return { 'data-max-width': attrs.maxWidth };
|
||||
}
|
||||
},
|
||||
|
||||
html(attrs) {
|
||||
return h('div.panel-body', h('div.panel-body-contents.clearfix', attrs.contents()));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import { createWidget } from 'discourse/widgets/widget';
|
||||
import { h } from 'virtual-dom';
|
||||
import hbs from 'discourse/widgets/hbs-compiler';
|
||||
|
||||
export default createWidget('post-placeholder', {
|
||||
tagName: 'article.placeholder',
|
||||
|
||||
html() {
|
||||
return h('div.row', [
|
||||
h('div.topic-avatar', h('div.placeholder-avatar')),
|
||||
h('div.topic-body', [
|
||||
h('div.placeholder-text'),
|
||||
h('div.placeholder-text'),
|
||||
h('div.placeholder-text')
|
||||
])
|
||||
]);
|
||||
}
|
||||
template: hbs`
|
||||
<div class='row'>
|
||||
<div class='topic-avatar'>
|
||||
<div class='placeholder-avatar'></div>
|
||||
</div>
|
||||
<div class='topic-body'>
|
||||
<div class='placeholder-text'></div>
|
||||
<div class='placeholder-text'></div>
|
||||
<div class='placeholder-text'></div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
|
|
@ -2,13 +2,11 @@ import { iconNode } from 'discourse-common/lib/icon-library';
|
|||
import { createWidget } from 'discourse/widgets/widget';
|
||||
import { h } from 'virtual-dom';
|
||||
import { avatarFor } from 'discourse/widgets/post';
|
||||
import hbs from 'discourse/widgets/hbs-compiler';
|
||||
|
||||
createWidget('pm-remove-group-link', {
|
||||
tagName: 'a.remove-invited',
|
||||
|
||||
html() {
|
||||
return iconNode('times');
|
||||
},
|
||||
template: hbs`{{fa-icon "times"}}`,
|
||||
|
||||
click() {
|
||||
bootbox.confirm(I18n.t("private_message_info.remove_allowed_group", {name: this.attrs.name}), (confirmed) => {
|
||||
|
@ -35,10 +33,7 @@ createWidget('pm-map-user-group', {
|
|||
|
||||
createWidget('pm-remove-link', {
|
||||
tagName: 'a.remove-invited',
|
||||
|
||||
html() {
|
||||
return iconNode('times');
|
||||
},
|
||||
template: hbs`{{fa-icon "times"}}`,
|
||||
|
||||
click() {
|
||||
bootbox.confirm(I18n.t("private_message_info.remove_allowed_user", {name: this.attrs.username}), (confirmed) => {
|
||||
|
|
|
@ -112,6 +112,10 @@ export function createWidget(name, opts) {
|
|||
opts.html = opts.html || emptyContent;
|
||||
opts.draw = drawWidget;
|
||||
|
||||
if (opts.template) {
|
||||
opts.html = opts.template;
|
||||
}
|
||||
|
||||
Object.keys(opts).forEach(k => result.prototype[k] = opts[k]);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ module Tilt
|
|||
# timeout any eval that takes longer than 15 seconds
|
||||
ctx = MiniRacer::Context.new(timeout: 15000)
|
||||
ctx.eval("var self = this; #{File.read("#{Rails.root}/vendor/assets/javascripts/babel.js")}")
|
||||
ctx.eval(File.read(Ember::Source.bundled_path_for('ember-template-compiler.js')))
|
||||
ctx.eval("module = {}; exports = {};");
|
||||
ctx.attach("rails.logger.info", proc { |err| Rails.logger.info(err.to_s) })
|
||||
ctx.attach("rails.logger.error", proc { |err| Rails.logger.error(err.to_s) })
|
||||
|
@ -36,7 +37,13 @@ module Tilt
|
|||
log: function(msg){ rails.logger.info(console.prefix + msg); },
|
||||
error: function(msg){ rails.logger.error(console.prefix + msg); }
|
||||
}
|
||||
|
||||
JS
|
||||
source = File.read("#{Rails.root}/lib/javascripts/widget-hbs-compiler.js.es6")
|
||||
js_source = ::JSON.generate(source, quirks_mode: true)
|
||||
js = ctx.eval("Babel.transform(#{js_source}, { ast: false, plugins: ['check-es2015-constants', 'transform-es2015-arrow-functions', 'transform-es2015-block-scoped-functions', 'transform-es2015-block-scoping', 'transform-es2015-classes', 'transform-es2015-computed-properties', 'transform-es2015-destructuring', 'transform-es2015-duplicate-keys', 'transform-es2015-for-of', 'transform-es2015-function-name', 'transform-es2015-literals', 'transform-es2015-object-super', 'transform-es2015-parameters', 'transform-es2015-shorthand-properties', 'transform-es2015-spread', 'transform-es2015-sticky-regex', 'transform-es2015-template-literals', 'transform-es2015-typeof-symbol', 'transform-es2015-unicode-regex'] }).code")
|
||||
ctx.eval(js)
|
||||
|
||||
ctx
|
||||
end
|
||||
|
||||
|
@ -105,7 +112,11 @@ JS
|
|||
klass = self.class
|
||||
klass.protect do
|
||||
klass.v8.eval("console.prefix = 'BABEL: babel-eval: ';")
|
||||
transpiled = babel_source(source, module_name: module_name(root_path, logical_path))
|
||||
transpiled = babel_source(
|
||||
source,
|
||||
module_name: module_name(root_path, logical_path),
|
||||
filename: logical_path
|
||||
)
|
||||
@output = klass.v8.eval(transpiled)
|
||||
end
|
||||
end
|
||||
|
@ -116,7 +127,14 @@ JS
|
|||
klass = self.class
|
||||
klass.protect do
|
||||
klass.v8.eval("console.prefix = 'BABEL: #{scope.logical_path}: ';")
|
||||
@output = klass.v8.eval(babel_source(data, module_name: module_name(scope.root_path, scope.logical_path)))
|
||||
|
||||
source = babel_source(
|
||||
data,
|
||||
module_name: module_name(scope.root_path, scope.logical_path),
|
||||
filename: scope.logical_path
|
||||
)
|
||||
|
||||
@output = klass.v8.eval(source)
|
||||
end
|
||||
|
||||
# For backwards compatibility with plugins, for now export the Global format too.
|
||||
|
@ -156,15 +174,15 @@ JS
|
|||
end
|
||||
|
||||
def babel_source(source, opts = nil)
|
||||
|
||||
opts ||= {}
|
||||
|
||||
js_source = ::JSON.generate(source, quirks_mode: true)
|
||||
|
||||
if opts[:module_name]
|
||||
"Babel.transform(#{js_source}, { moduleId: '#{opts[:module_name]}', ast: false, presets: ['es2015'], plugins: [['transform-es2015-modules-amd', {noInterop: true}], 'transform-decorators-legacy'] }).code"
|
||||
filename = opts[:filename] || 'unknown'
|
||||
"Babel.transform(#{js_source}, { moduleId: '#{opts[:module_name]}', filename: '#{filename}', ast: false, presets: ['es2015'], plugins: [['transform-es2015-modules-amd', {noInterop: true}], 'transform-decorators-legacy', exports.WidgetHbsCompiler] }).code"
|
||||
else
|
||||
"Babel.transform(#{js_source}, { ast: false, plugins: ['check-es2015-constants', 'transform-es2015-arrow-functions', 'transform-es2015-block-scoped-functions', 'transform-es2015-block-scoping', 'transform-es2015-classes', 'transform-es2015-computed-properties', 'transform-es2015-destructuring', 'transform-es2015-duplicate-keys', 'transform-es2015-for-of', 'transform-es2015-function-name', 'transform-es2015-literals', 'transform-es2015-object-super', 'transform-es2015-parameters', 'transform-es2015-shorthand-properties', 'transform-es2015-spread', 'transform-es2015-sticky-regex', 'transform-es2015-template-literals', 'transform-es2015-typeof-symbol', 'transform-es2015-unicode-regex', 'transform-regenerator', 'transform-decorators-legacy'] }).code"
|
||||
"Babel.transform(#{js_source}, { ast: false, plugins: ['check-es2015-constants', 'transform-es2015-arrow-functions', 'transform-es2015-block-scoped-functions', 'transform-es2015-block-scoping', 'transform-es2015-classes', 'transform-es2015-computed-properties', 'transform-es2015-destructuring', 'transform-es2015-duplicate-keys', 'transform-es2015-for-of', 'transform-es2015-function-name', 'transform-es2015-literals', 'transform-es2015-object-super', 'transform-es2015-parameters', 'transform-es2015-shorthand-properties', 'transform-es2015-spread', 'transform-es2015-sticky-regex', 'transform-es2015-template-literals', 'transform-es2015-typeof-symbol', 'transform-es2015-unicode-regex', 'transform-regenerator', 'transform-decorators-legacy', exports.WidgetHbsCompiler] }).code"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
196
lib/javascripts/widget-hbs-compiler.js.es6
Normal file
196
lib/javascripts/widget-hbs-compiler.js.es6
Normal file
|
@ -0,0 +1,196 @@
|
|||
function resolve(path) {
|
||||
return (path.indexOf('settings') === 0) ? `this.${path}` : path;
|
||||
}
|
||||
|
||||
function mustacheValue(node) {
|
||||
let path = node.path.original;
|
||||
|
||||
switch(path) {
|
||||
case 'attach':
|
||||
const widgetName = node.hash.pairs.find(p => p.key === "widget").value.value;
|
||||
return `this.attach("${widgetName}", attrs, state)`;
|
||||
break;
|
||||
case 'yield':
|
||||
return `this.attrs.contents()`;
|
||||
break;
|
||||
case 'i18n':
|
||||
let value;
|
||||
if (node.params[0].type === "StringLiteral") {
|
||||
value = `"${node.params[0].value}"`;
|
||||
} else if (node.params[0].type === "PathExpression") {
|
||||
value = node.params[0].original;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
return `I18n.t(${value})`;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'fa-icon':
|
||||
let icon = node.params[0].value;
|
||||
return `virtualDom.h('i.fa.fa-${icon}')`;
|
||||
break;
|
||||
default:
|
||||
return `${resolve(path)}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class Compiler {
|
||||
constructor(ast) {
|
||||
this.idx = 0;
|
||||
this.ast = ast;
|
||||
}
|
||||
|
||||
newAcc() {
|
||||
return `_a${this.idx++}`;
|
||||
}
|
||||
|
||||
processNode(parentAcc, node) {
|
||||
let instructions = [];
|
||||
let innerAcc;
|
||||
|
||||
switch(node.type) {
|
||||
case "Program":
|
||||
node.body.forEach(bodyNode => {
|
||||
instructions = instructions.concat(this.processNode(parentAcc, bodyNode));
|
||||
});
|
||||
break;
|
||||
case "ElementNode":
|
||||
innerAcc = this.newAcc();
|
||||
instructions.push(`var ${innerAcc} = [];`);
|
||||
node.children.forEach(child => {
|
||||
instructions = instructions.concat(this.processNode(innerAcc, child));
|
||||
});
|
||||
|
||||
if (node.attributes.length) {
|
||||
|
||||
let attributes = [];
|
||||
node.attributes.forEach(a => {
|
||||
const name = a.name === 'class' ? 'className' : a.name;
|
||||
if (a.value.type === "MustacheStatement") {
|
||||
attributes.push(`"${name}":${mustacheValue(a.value)}`);
|
||||
} else {
|
||||
attributes.push(`"${name}":"${a.value.chars}"`);
|
||||
}
|
||||
});
|
||||
|
||||
const attrString = `{${attributes.join(', ')}}`;
|
||||
instructions.push(`${parentAcc}.push(virtualDom.h('${node.tag}', ${attrString}, ${innerAcc}));`);
|
||||
} else {
|
||||
instructions.push(`${parentAcc}.push(virtualDom.h('${node.tag}', ${innerAcc}));`);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "TextNode":
|
||||
return `${parentAcc}.push(${JSON.stringify(node.chars)});`;
|
||||
|
||||
case "MustacheStatement":
|
||||
const value = mustacheValue(node);
|
||||
if (value) {
|
||||
instructions.push(`${parentAcc}.push(${value})`);
|
||||
}
|
||||
break;
|
||||
case "BlockStatement":
|
||||
switch(node.path.original) {
|
||||
case 'if':
|
||||
instructions.push(`if (${node.params[0].original}) {`);
|
||||
node.program.body.forEach(child => {
|
||||
instructions = instructions.concat(this.processNode(parentAcc, child));
|
||||
});
|
||||
|
||||
if (node.inverse) {
|
||||
instructions.push(`} else {`);
|
||||
node.inverse.body.forEach(child => {
|
||||
instructions = instructions.concat(this.processNode(parentAcc, child));
|
||||
});
|
||||
}
|
||||
instructions.push(`}`);
|
||||
break;
|
||||
case 'each':
|
||||
const collection = node.params[0].original;
|
||||
instructions.push(`if (${collection} && ${collection}.length) {`);
|
||||
instructions.push(` ${collection}.forEach(${node.program.blockParams[0]} => {`);
|
||||
node.program.body.forEach(child => {
|
||||
instructions = instructions.concat(this.processNode(parentAcc, child));
|
||||
});
|
||||
instructions.push(` });`);
|
||||
instructions.push('}');
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return instructions.join("\n");
|
||||
}
|
||||
|
||||
compile() {
|
||||
return this.processNode('_r', this.ast);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function compile(template) {
|
||||
const preprocessor = Ember.__loader.require('@glimmer/syntax');
|
||||
const compiled = preprocessor.preprocess(template);
|
||||
const compiler = new Compiler(compiled);
|
||||
|
||||
return `function(attrs, state) { var _r = [];\n${compiler.compile()}\nreturn _r; }`;
|
||||
}
|
||||
|
||||
exports.compile = compile;
|
||||
|
||||
function error(path, state, msg) {
|
||||
const filename = state.file.opts.filename;
|
||||
return path.replaceWithSourceString(`function() { console.error("${filename}: ${msg}"); }`);
|
||||
}
|
||||
|
||||
exports.WidgetHbsCompiler = function(babel) {
|
||||
let t = babel.types;
|
||||
return {
|
||||
visitor: {
|
||||
ImportDeclaration(path, state) {
|
||||
let node = path.node;
|
||||
if (t.isLiteral(node.source, { value: "discourse/widgets/hbs-compiler" })) {
|
||||
let first = node.specifiers && node.specifiers[0];
|
||||
if (!t.isImportDefaultSpecifier(first)) {
|
||||
let input = state.file.code;
|
||||
let usedImportStatement = input.slice(node.start, node.end);
|
||||
let msg = `Only \`import hbs from 'discourse/widgets/hbs-compiler'\` is supported. You used: \`${usedImportStatement}\``;
|
||||
throw path.buildCodeFrameError(msg);
|
||||
}
|
||||
|
||||
state.importId = state.importId || path.scope.generateUidIdentifierBasedOnNode(path.node.id);
|
||||
path.scope.rename(first.local.name, state.importId.name);
|
||||
path.remove();
|
||||
}
|
||||
},
|
||||
|
||||
TaggedTemplateExpression(path, state) {
|
||||
if (!state.importId) { return; }
|
||||
|
||||
let tagPath = path.get('tag');
|
||||
if (tagPath.node.name !== state.importId.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.node.quasi.expressions.length) {
|
||||
return error(path, state, "placeholders inside a tagged template string are not supported");
|
||||
}
|
||||
|
||||
let template = path.node.quasi.quasis.map(quasi => quasi.value.cooked).join('');
|
||||
|
||||
try {
|
||||
path.replaceWithSourceString(compile(template));
|
||||
} catch(e) {
|
||||
return error(path, state, e.toString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
25
script/compile_hbs.rb
Normal file
25
script/compile_hbs.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
ctx = MiniRacer::Context.new(timeout: 15000)
|
||||
ctx.eval("var self = this; #{File.read("#{Rails.root}/vendor/assets/javascripts/babel.js")}")
|
||||
ctx.eval(File.read(Ember::Source.bundled_path_for('ember-template-compiler.js')))
|
||||
ctx.eval("module = {}; exports = {};");
|
||||
ctx.attach("rails.logger.info", proc{|err| puts(">> #{err.to_s}")})
|
||||
ctx.attach("rails.logger.error", proc{|err| puts(">> #{err.to_s}")})
|
||||
ctx.eval <<JS
|
||||
console = {
|
||||
prefix: "",
|
||||
log: function(msg){ rails.logger.info(console.prefix + msg); },
|
||||
error: function(msg){ rails.logger.error(console.prefix + msg); }
|
||||
}
|
||||
|
||||
JS
|
||||
source = File.read("#{Rails.root}/lib/javascripts/widget-hbs-compiler.js.es6")
|
||||
js_source = ::JSON.generate(source, quirks_mode: true)
|
||||
js = ctx.eval("Babel.transform(#{js_source}, { ast: false, plugins: ['check-es2015-constants', 'transform-es2015-arrow-functions', 'transform-es2015-block-scoped-functions', 'transform-es2015-block-scoping', 'transform-es2015-classes', 'transform-es2015-computed-properties', 'transform-es2015-destructuring', 'transform-es2015-duplicate-keys', 'transform-es2015-for-of', 'transform-es2015-function-name', 'transform-es2015-literals', 'transform-es2015-object-super', 'transform-es2015-parameters', 'transform-es2015-shorthand-properties', 'transform-es2015-spread', 'transform-es2015-sticky-regex', 'transform-es2015-template-literals', 'transform-es2015-typeof-symbol', 'transform-es2015-unicode-regex'] }).code")
|
||||
ctx.eval(js)
|
||||
|
||||
if ARGV[0].present?
|
||||
source = File.read(ARGV[0])
|
||||
js_source = ::JSON.generate(source, quirks_mode: true)
|
||||
puts ctx.eval("exports.compile(#{js_source})");
|
||||
end
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { moduleForWidget, widgetTest } from 'helpers/widget-test';
|
||||
import { createWidget } from 'discourse/widgets/widget';
|
||||
import { withPluginApi } from 'discourse/lib/plugin-api';
|
||||
import hbs from 'discourse/widgets/hbs-compiler';
|
||||
|
||||
moduleForWidget('base');
|
||||
|
||||
|
@ -10,10 +11,7 @@ widgetTest('widget attributes are passed in via args', {
|
|||
beforeEach() {
|
||||
createWidget('hello-test', {
|
||||
tagName: 'div.test',
|
||||
|
||||
html(attrs) {
|
||||
return `Hello ${attrs.name}`;
|
||||
},
|
||||
template: hbs`Hello {{attrs.name}}`
|
||||
});
|
||||
|
||||
this.set('args', { name: 'Robin' });
|
||||
|
@ -24,6 +22,39 @@ widgetTest('widget attributes are passed in via args', {
|
|||
}
|
||||
});
|
||||
|
||||
widgetTest("hbs template - no tagName", {
|
||||
template: `{{mount-widget widget="hbs-test" args=args}}`,
|
||||
|
||||
beforeEach() {
|
||||
createWidget('hbs-test', {
|
||||
template: hbs`<div class='test'>Hello {{attrs.name}}</div>`
|
||||
});
|
||||
|
||||
this.set('args', { name: 'Robin' });
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.equal(this.$('div.test').text(), "Hello Robin");
|
||||
}
|
||||
});
|
||||
|
||||
widgetTest("hbs template - with tagName", {
|
||||
template: `{{mount-widget widget="hbs-test" args=args}}`,
|
||||
|
||||
beforeEach() {
|
||||
createWidget('hbs-test', {
|
||||
tagName: 'div.test',
|
||||
template: hbs`Hello {{attrs.name}}`
|
||||
});
|
||||
|
||||
this.set('args', { name: 'Robin' });
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.equal(this.$('div.test').text(), "Hello Robin");
|
||||
}
|
||||
});
|
||||
|
||||
widgetTest('buildClasses', {
|
||||
template: `{{mount-widget widget="classname-test" args=args}}`,
|
||||
|
||||
|
@ -90,15 +121,12 @@ widgetTest('widget state', {
|
|||
createWidget('state-test', {
|
||||
tagName: 'button.test',
|
||||
buildKey: () => `button-test`,
|
||||
template: hbs`{{state.clicks}} clicks`,
|
||||
|
||||
defaultState() {
|
||||
return { clicks: 0 };
|
||||
},
|
||||
|
||||
html(attrs, state) {
|
||||
return `${state.clicks} clicks`;
|
||||
},
|
||||
|
||||
click() {
|
||||
this.state.clicks++;
|
||||
}
|
||||
|
@ -123,10 +151,13 @@ widgetTest('widget update with promise', {
|
|||
createWidget('promise-test', {
|
||||
tagName: 'button.test',
|
||||
buildKey: () => 'promise-test',
|
||||
|
||||
html(attrs, state) {
|
||||
return state.name || "No name";
|
||||
},
|
||||
template: hbs`
|
||||
{{#if state.name}}
|
||||
{{state.name}}
|
||||
{{else}}
|
||||
No name
|
||||
{{/if}}
|
||||
`,
|
||||
|
||||
click() {
|
||||
return new Ember.RSVP.Promise(resolve => {
|
||||
|
@ -140,11 +171,11 @@ widgetTest('widget update with promise', {
|
|||
},
|
||||
|
||||
test(assert) {
|
||||
assert.equal(this.$('button.test').text(), "No name");
|
||||
assert.equal(this.$('button.test').text().trim(), "No name");
|
||||
|
||||
click(this.$('button'));
|
||||
andThen(() => {
|
||||
assert.equal(this.$('button.test').text(), "Robin");
|
||||
assert.equal(this.$('button.test').text().trim(), "Robin");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -157,9 +188,7 @@ widgetTest('widget attaching', {
|
|||
|
||||
createWidget('attach-test', {
|
||||
tagName: 'div.container',
|
||||
html() {
|
||||
return this.attach('test-embedded');
|
||||
},
|
||||
template: hbs`{{attach widget="test-embedded"}}`
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -169,15 +198,78 @@ widgetTest('widget attaching', {
|
|||
}
|
||||
});
|
||||
|
||||
widgetTest("handlebars fa-icon", {
|
||||
template: `{{mount-widget widget="hbs-icon-test" args=args}}`,
|
||||
|
||||
beforeEach() {
|
||||
createWidget('hbs-icon-test', {
|
||||
template: hbs`{{fa-icon "arrow-down"}}`
|
||||
});
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.equal(this.$('i.fa.fa-arrow-down').length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
widgetTest("handlebars i18n", {
|
||||
template: `{{mount-widget widget="hbs-i18n-test" args=args}}`,
|
||||
|
||||
beforeEach() {
|
||||
createWidget('hbs-i18n-test', {
|
||||
template: hbs`
|
||||
<span class='string'>{{i18n "hbs_test0"}}</span>
|
||||
<span class='var'>{{i18n attrs.key}}</span>
|
||||
<a href title={{i18n "hbs_test0"}}>test</a>
|
||||
`
|
||||
});
|
||||
I18n.extras = [ {
|
||||
"hbs_test0": "evil",
|
||||
"hbs_test1": "trout"
|
||||
} ];
|
||||
this.set('args', { key: 'hbs_test1' });
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
// comin up
|
||||
assert.equal(this.$('span.string').text(), 'evil');
|
||||
assert.equal(this.$('span.var').text(), 'trout');
|
||||
assert.equal(this.$('a').prop('title'), 'evil');
|
||||
}
|
||||
});
|
||||
|
||||
widgetTest('handlebars #each', {
|
||||
template: `{{mount-widget widget="hbs-each-test" args=args}}`,
|
||||
|
||||
beforeEach() {
|
||||
createWidget('hbs-each-test', {
|
||||
tagName: 'ul',
|
||||
template: hbs`
|
||||
{{#each attrs.items as |item|}}
|
||||
<li>{{item}}</li>
|
||||
{{/each}}
|
||||
`
|
||||
});
|
||||
|
||||
this.set('args', {
|
||||
items: ['one', 'two', 'three']
|
||||
});
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.equal(this.$('ul li').length, 3);
|
||||
assert.equal(this.$('ul li:eq(0)').text(), "one");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
widgetTest('widget decorating', {
|
||||
template: `{{mount-widget widget="decorate-test"}}`,
|
||||
|
||||
beforeEach() {
|
||||
createWidget('decorate-test', {
|
||||
tagName: 'div.decorate',
|
||||
html() {
|
||||
return "main content";
|
||||
},
|
||||
template: hbs`main content`
|
||||
});
|
||||
|
||||
withPluginApi('0.1', api => {
|
||||
|
@ -204,14 +296,8 @@ widgetTest('widget settings', {
|
|||
beforeEach() {
|
||||
createWidget('settings-test', {
|
||||
tagName: 'div.settings',
|
||||
|
||||
settings: {
|
||||
age: 36
|
||||
},
|
||||
|
||||
html() {
|
||||
return `age is ${this.settings.age}`;
|
||||
},
|
||||
template: hbs`age is {{settings.age}}`,
|
||||
settings: { age: 36 }
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -226,14 +312,8 @@ widgetTest('override settings', {
|
|||
beforeEach() {
|
||||
createWidget('ov-settings-test', {
|
||||
tagName: 'div.settings',
|
||||
|
||||
settings: {
|
||||
age: 36
|
||||
},
|
||||
|
||||
html() {
|
||||
return `age is ${this.settings.age}`;
|
||||
},
|
||||
template: hbs`age is {{settings.age}}`,
|
||||
settings: { age: 36 },
|
||||
});
|
||||
|
||||
withPluginApi('0.1', api => {
|
||||
|
|
Loading…
Reference in New Issue
Block a user