Skip to content

Commit

Permalink
feat(lwc-template-compiler): Add new compiler API compileToFunction (#…
Browse files Browse the repository at this point in the history
…528)

## Details

This PR updates the `lwc-template-compiler` package to add `compileToFunction` API. This method takes as parameter a string template and a configuration and returns the instantiated template function.

The primary use-case of this new API is to allow inline compilation of template for unit tests. This PR also introduce the `compileTemplate` test utility to the `lwc-engine` package unit tests. Once this utility is stable, we will move it to the official `lwc-tests-utils` package for the general consumption.

## Does this PR introduce a breaking change?

* [ ] Yes
* [X] No
  • Loading branch information
pmdartus authored Jul 24, 2018
1 parent 79cfa4d commit 9cd6952
Show file tree
Hide file tree
Showing 14 changed files with 423 additions and 138 deletions.
5 changes: 5 additions & 0 deletions packages/lwc-engine/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
const path = require('path');
const BASE_CONFIG = require('../../scripts/jest/base.config');

module.exports = {
...BASE_CONFIG,

displayName: 'lwc-engine',
moduleNameMapper: {
'test-utils': path.resolve(__dirname, 'scripts/jest/test-utils.js'),
},
};
1 change: 1 addition & 0 deletions packages/lwc-engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"devDependencies": {
"concurrently": "^3.5.1",
"lwc-template-compiler": "0.24.2",
"rollup": "0.60.1",
"rollup-plugin-inject": "^1.4.1",
"rollup-plugin-node-resolve": "^3.0.2",
Expand Down
27 changes: 27 additions & 0 deletions packages/lwc-engine/scripts/jest/test-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { compileToFunction } = require('lwc-template-compiler');

const TEMPLATE_CACHE = Object.create(null);

/**
* Compiles a template string and returns the instantiated function.
*
* @param {string} source The template string
* @param {object=} config The template configuration
* @param {object=} config.modules The map of the modules used in the template
* @returns {function}
*/
function compileTemplate(source, config = {}) {
const { modules = {} } = config;

// Check if the same template has already been compiled
if (!(source in TEMPLATE_CACHE)) {
TEMPLATE_CACHE[source] = compileToFunction(source);
}

const templateFactory = TEMPLATE_CACHE[source];
return templateFactory(modules);
}

module.exports = {
compileTemplate,
};
16 changes: 13 additions & 3 deletions packages/lwc-engine/src/framework/__tests__/class-list.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import { compileTemplate } from 'test-utils';

import { createElement, LightningElement } from '../main';
import { getHostShadowRoot } from '../html-element';

describe('class-list', () => {
describe('integration', () => {
it('should support outer className', () => {
class ChildComponent extends LightningElement {}
function html($api) {
return [$api.c('x-child', ChildComponent, { className: 'foo' })];
}

const html = compileTemplate(
`<template>
<x-child class="foo"></x-child>
</template>`,
{
modules: { 'x-child': ChildComponent }
}
);

class MyComponent extends LightningElement {
render() {
return html;
}
}

const elm = createElement('x-foo', { is: MyComponent });
document.body.appendChild(elm);
const childElm = getHostShadowRoot(elm).querySelector('x-child');
Expand Down
161 changes: 94 additions & 67 deletions packages/lwc-engine/src/framework/__tests__/component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { compileTemplate } from 'test-utils';

import { createElement, LightningElement } from '../main';
import { getHostShadowRoot } from '../html-element';

Expand All @@ -10,15 +12,20 @@ describe('component', function() {
return this.value;
}
}

MyComponent.publicProps = {
breakfast: {
config: 1
}
};
function html($api) {
return [$api.c('x-component', MyComponent, {})];
}

const html = compileTemplate(
`<template>
<x-child></x-child>
</template>`,
{
modules: { "x-child": MyComponent }
}
);
class Parent extends LightningElement {
value = 'salad';
get lunch() {
Expand All @@ -29,7 +36,6 @@ describe('component', function() {
return html;
}
}

Parent.publicProps = {
lunch: {
config: 1
Expand All @@ -39,7 +45,7 @@ describe('component', function() {
const elm = createElement('x-foo', { is: Parent });
document.body.appendChild(elm);
expect(elm.lunch).toBe('salad');
expect(getHostShadowRoot(elm).querySelector('x-component').breakfast).toBe('pancakes');
expect(getHostShadowRoot(elm).querySelector('x-child').breakfast).toBe('pancakes');
});

it('should allow calling public getters when element is accessed by querySelector', function() {
Expand All @@ -54,9 +60,15 @@ describe('component', function() {
config: 0
}
};
function html($api) {
return [$api.c('x-child', MyChild, {})];
}

const html = compileTemplate(
`<template>
<x-child></x-child>
</template>`,
{
modules: { "x-child": MyChild }
}
);
class MyComponent extends LightningElement {
callChildM() {
value = this.template.querySelector('x-child').m;
Expand Down Expand Up @@ -93,9 +105,11 @@ describe('component', function() {
});

it('should be render reactive', function() {
function html($api, $cmp, $slotset, $ctx) {
return [$api.h('div', { key: 0 }, [$api.d($cmp.validity)])];
}
const html = compileTemplate(
`<template>
<div>{validity}</div>
</template>`
);
class MyComponent extends LightningElement {
state = { value: 0 };

Expand Down Expand Up @@ -172,9 +186,15 @@ describe('component', function() {
config: 3
}
};
function html($api) {
return [$api.c('x-child', MyChild, {})];
}

const html = compileTemplate(
`<template>
<x-child></x-child>
</template>`,
{
modules: { "x-child": MyChild }
}
);
class MyComponent extends LightningElement {
render() {
return html;
Expand All @@ -185,6 +205,7 @@ describe('component', function() {
}
}
MyComponent.publicMethods = ['run'];

const elm = createElement('x-foo', { is: MyComponent });
document.body.appendChild(elm);
expect(elm.run()).toBe('eggs');
Expand Down Expand Up @@ -335,16 +356,12 @@ describe('component', function() {
describe('styles', function() {
it('should handle string styles', function() {
let calledCSSText = false;
function html($api, $cmp) {
return [$api.h(
"section",
{
key: 0,
style: $cmp.state.customStyle
},
[]
)];
}

const html = compileTemplate(
`<template>
<section style={state.customStyle}></section>
</template>`,
);
class MyComponent extends LightningElement {
state = {
customStyle: 'color: red'
Expand Down Expand Up @@ -373,16 +390,12 @@ describe('component', function() {

it('should handle undefined properly', function() {
let calledCSSTextWithUndefined = false;
function html($api, $cmp, $slotset, $ctx) {
return [$api.h(
"section",
{
key: 0,
style: $cmp.state.customStyle
},
[]
)];
}

const html = compileTemplate(
`<template>
<section style={state.customStyle}></section>
</template>`,
);
class MyComponent extends LightningElement {
state = {
customStyle: undefined
Expand Down Expand Up @@ -412,16 +425,11 @@ describe('component', function() {
});

it('should handle null properly', function() {
function html($api, $cmp) {
return [$api.h(
"section",
{
key: 0,
style: $cmp.state.customStyle
},
[]
)];
}
const html = compileTemplate(
`<template>
<section style={state.customStyle}></section>
</template>`,
);
class MyComponent extends LightningElement {
state = {
customStyle: null
Expand All @@ -438,16 +446,11 @@ describe('component', function() {
});

it('should diff between style objects and strings correctly', function() {
function html($api, $cmp, $slotset, $ctx) {
return [$api.h(
"section",
{
key: 0,
style: $cmp.customStyle
},
[]
)];
}
const html = compileTemplate(
`<template>
<section style={customStyle}></section>
</template>`,
);
class MyComponent extends LightningElement {
customStyle: {
color: 'red'
Expand Down Expand Up @@ -559,9 +562,15 @@ describe('component', function() {
}
}
MyChild.publicMethods = ['m'];
function html($api) {
return [$api.c('x-child', MyChild, {})];
}

const html = compileTemplate(
`<template>
<x-child></x-child>
</template>`,
{
modules: { "x-child": MyChild }
}
);
class MyComponent extends LightningElement {
callChildM() {
this.template.querySelector('x-child').m();
Expand All @@ -588,9 +597,15 @@ describe('component', function() {
}
}
MyChild.publicMethods = ['m'];
function html($api) {
return [$api.c('x-child', MyChild, {})];
}

const html = compileTemplate(
`<template>
<x-child></x-child>
</template>`,
{
modules: { "x-child": MyChild }
}
);
class MyComponent extends LightningElement {
getChildAttribute() {
this.template.querySelector('x-child').getAttribute('title');
Expand All @@ -616,9 +631,15 @@ describe('component', function() {
}
}
MyChild.publicMethods = ['m'];
function html($api) {
return [$api.c('x-child', MyChild, {})];
}

const html = compileTemplate(
`<template>
<x-child></x-child>
</template>`,
{
modules: { "x-child": MyChild }
}
);
class MyComponent extends LightningElement {
setChildAttribute() {
this.template.querySelector('x-child').setAttribute('title', 'foo');
Expand All @@ -644,9 +665,15 @@ describe('component', function() {
}
}
MyChild.publicMethods = ['m'];
function html($api) {
return [$api.c('x-child', MyChild, {})];
}

const html = compileTemplate(
`<template>
<x-child></x-child>
</template>`,
{
modules: { "x-child": MyChild }
}
);
class MyComponent extends LightningElement {
removeChildAttribute() {
this.template.querySelector('x-child').removeAttribute('title');
Expand Down
16 changes: 13 additions & 3 deletions packages/lwc-engine/src/framework/__tests__/html-element.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { compileTemplate } from 'test-utils';

import { createElement, register, unwrap } from '../main';
import { getHostShadowRoot, LightningElement } from '../html-element';
import assertLogger from '../../shared/assert';
Expand All @@ -12,11 +14,19 @@ describe('html-element', () => {
}
Child.publicMethods = ['setFoo'];

const html = compileTemplate(`
<template>
<x-child></x-child>
</template>
`, {
modules: {
'x-child': Child
}
});

class Parent extends LightningElement {
render() {
return ($api) => {
return [$api.c('x-child', Child, {})]
}
return html;
}
}
const element = createElement('should-set-attribute-on-host-element-when-element-is-nested-in-template', { is: Parent });
Expand Down
Loading

0 comments on commit 9cd6952

Please sign in to comment.