Skip to content

Commit

Permalink
test: Migrate jest to use projects feature (#322)
Browse files Browse the repository at this point in the history
## Details

This PR changes the way jest is configured in the mono-repo, to leverage the `projects` feature properly. This would allow us to have a different jest config for each project.

Changes:
* Remove component registry in the engine
* Fix tests that are using `root` instead of `template`
* Fix tests with duplicate component names

> The test running will also print the package name next to the test 💃 

<img width="710" alt="screen shot 2018-05-21 at 8 41 41 am" src="https://user-images.githubusercontent.com/2567083/40316332-db609542-5cd2-11e8-9144-9085667896d4.png">

----

In upcoming a PR I want to get rid of all the remaining warnings by introducing a custom matcher that will trap the warnings and the errors (#94, #104). All the unexpected console `log`, `warn` and `error` should make the test fail. Doing this would greatly improve quality and the consistency of the warnings the engine surfaces. 

## Does this PR introduce a breaking change?

* [ ] Yes
* [X] No
  • Loading branch information
pmdartus committed May 31, 2018
1 parent 2790bc6 commit f44d252
Show file tree
Hide file tree
Showing 30 changed files with 188 additions and 122 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -7,7 +7,7 @@
"clean": "lerna run clean && lerna clean --yes && rm -rf node_modules",
"lint": "tslint -p tsconfig.json && npm run types",
"types": "tsc --noEmit",
"test": "jest",
"test": "jest --config ./scripts/jest/root.config.js",
"test:integration": "yarn run build && lerna exec --scope lwc-integration -- yarn sauce",
"test:performance": "lerna exec --scope benchmark -- best --runner remote",
"build": "lerna run build --ignore benchmark --ignore lwc-integration",
Expand Down
6 changes: 6 additions & 0 deletions packages/babel-plugin-transform-lwc-class/jest.config.js
@@ -0,0 +1,6 @@
const BASE_CONFIG = require('../../scripts/jest/base.config');

module.exports = {
...BASE_CONFIG,
displayName: 'babel-plugin-transform-lwc-class',
};
6 changes: 6 additions & 0 deletions packages/lwc-compiler/jest.config.js
@@ -0,0 +1,6 @@
const BASE_CONFIG = require('../../scripts/jest/base.config');

module.exports = {
...BASE_CONFIG,
displayName: 'lwc-compiler',
};
6 changes: 6 additions & 0 deletions packages/lwc-engine/jest.config.js
@@ -0,0 +1,6 @@
const BASE_CONFIG = require('../../scripts/jest/base.config');

module.exports = {
...BASE_CONFIG,
displayName: 'lwc-engine',
};
6 changes: 3 additions & 3 deletions packages/lwc-engine/package.json
Expand Up @@ -9,9 +9,9 @@
"clean": "rm -rf dist",
"build": "concurrently \"yarn build:es-and-cjs\" \"yarn build:umd:prod\" \"yarn build:umd:dev\"",
"test": "DIR=`pwd` && cd ../../ && yarn test $DIR",
"build:umd:dev": "rollup -c scripts/rollup.config.umd.dev.js",
"build:umd:prod": "rollup -c scripts/rollup.config.umd.prod.js",
"build:es-and-cjs": "rollup -c scripts/rollup.config.es-and-cjs.js"
"build:umd:dev": "rollup -c scripts/rollup/rollup.config.umd.dev.js",
"build:umd:prod": "rollup -c scripts/rollup/rollup.config.umd.prod.js",
"build:es-and-cjs": "rollup -c scripts/rollup/rollup.config.es-and-cjs.js"
},
"devDependencies": {
"concurrently": "^3.5.1",
Expand Down
Expand Up @@ -3,11 +3,11 @@ const typescript = require('rollup-plugin-typescript');
const nodeResolve = require('rollup-plugin-node-resolve');

const { generateTargetName, ignoreCircularDependencies } = require('./engine.rollup.config.util');
const { version } = require('../package.json');
const { version } = require('../../package.json');

const entry = path.resolve(__dirname, '../src/framework/main.ts');
const commonJSDirectory = path.resolve(__dirname, '../dist/commonjs');
const modulesDirectory = path.resolve(__dirname, '../dist/modules');
const entry = path.resolve(__dirname, '../../src/framework/main.ts');
const commonJSDirectory = path.resolve(__dirname, '../../dist/commonjs');
const modulesDirectory = path.resolve(__dirname, '../../dist/modules');

const banner = (`/* proxy-compat-disable */`);
const footer = `/** version: ${version} */`;
Expand Down
Expand Up @@ -3,11 +3,11 @@ const replace = require('rollup-plugin-replace');
const typescript = require('rollup-plugin-typescript');
const nodeResolve = require('rollup-plugin-node-resolve');

const { version } = require('../package.json');
const { version } = require('../../package.json');
const { generateTargetName, ignoreCircularDependencies } = require('./engine.rollup.config.util');

const input = path.resolve(__dirname, '../src/framework/main.ts');
const outputDir = path.resolve(__dirname, '../dist/umd');
const input = path.resolve(__dirname, '../../src/framework/main.ts');
const outputDir = path.resolve(__dirname, '../../dist/umd');

const banner = (`/* proxy-compat-disable */`);
const footer = `/** version: ${version} */`;
Expand Down
Expand Up @@ -5,11 +5,11 @@ const typescript = require('typescript');
const rollupTypescriptPlugin = require('rollup-plugin-typescript');
const nodeResolve = require('rollup-plugin-node-resolve');
const babelMinify = require('babel-minify');
const { version } = require('../package.json');
const { version } = require('../../package.json');
const { generateTargetName, ignoreCircularDependencies } = require('./engine.rollup.config.util');

const entry = path.resolve(__dirname, '../src/framework/main.ts');
const outputDir = path.resolve(__dirname, '../dist/umd');
const entry = path.resolve(__dirname, '../../src/framework/main.ts');
const outputDir = path.resolve(__dirname, '../../dist/umd');
const banner = (`/* proxy-compat-disable */`);
const footer = `/** version: ${version} */`;

Expand Down
18 changes: 12 additions & 6 deletions packages/lwc-engine/src/framework/__tests__/api.spec.ts
Expand Up @@ -106,22 +106,28 @@ describe('api', () => {
expect(span.getAttribute('is')).toEqual('x-bar');
});

it('should throw when forceTagName cannot have a shadow root attached to it', () => {
it('should throw if the forceTagName value is a reserved standard element name', () => {
class Bar extends Element {
static forceTagName = 'div'; // it can't be a div
static forceTagName = 'div';
}

expect(() => {
createElement('x-foo', { is: Bar });
}).toThrow();
}).toThrow(
/Invalid static forceTagName property set to "div"/
);
});

it('should throw when forceTagName cannot have a shadow root attached to it', () => {
it('should throw if the forceTagName is a custom element name', () => {
class Bar extends Element {
static forceTagName = 'x-bar'; // it can't be a custom element name
static forceTagName = 'x-bar';
}

expect(() => {
createElement('x-foo', { is: Bar });
}).toThrow();
}).toThrow(
/Invalid static forceTagName property set to "x-bar"/
);
});

});
Expand Down
Expand Up @@ -76,7 +76,7 @@ describe('error boundary component', () => {
return html;
}
}
const boundaryHostElm = createElement('x-boundary', {is: BoundryHost});
const boundaryHostElm = createElement('x-parent', {is: BoundryHost});

document.body.appendChild(boundaryHostElm);
expect(querySelectorAll.call(boundaryHostElm, 'x-boundary-sibling').length).toBe(1);
Expand Down Expand Up @@ -198,7 +198,7 @@ describe('error boundary component', () => {
return html;
}
}
const boundaryHostElm = createElement('x-boundary', {is: BoundryHost});
const boundaryHostElm = createElement('x-parent', {is: BoundryHost});
document.body.appendChild(boundaryHostElm);

expect(querySelectorAll.call(boundaryHostElm, 'x-boundary-sibling').length).toBe(1);
Expand Down Expand Up @@ -434,7 +434,7 @@ describe('error boundary component', () => {
return html;
}
}
const boundaryHostElm = createElement('x-boundary', {is: BoundryHost});
const boundaryHostElm = createElement('x-parent', {is: BoundryHost});
document.body.appendChild(boundaryHostElm);

expect(querySelectorAll.call(boundaryHostElm, 'x-boundary-sibling').length).toBe(1);
Expand Down Expand Up @@ -530,7 +530,7 @@ describe('error boundary component', () => {
return html;
}
}
const boundaryHostElm = createElement('x-boundary', {is: BoundryHost});
const boundaryHostElm = createElement('x-parent', {is: BoundryHost});
document.body.appendChild(boundaryHostElm);

expect(querySelectorAll.call(boundaryHostElm, 'x-boundary-sibling').length).toBe(1);
Expand Down
10 changes: 5 additions & 5 deletions packages/lwc-engine/src/framework/__tests__/events.spec.ts
Expand Up @@ -94,7 +94,7 @@ describe('Events on Custom Elements', () => {
elm = createElement('x-foo', { is: Foo });
elm.addEventListener('click', clicked2);
document.body.appendChild(elm);
cmp.root.querySelector('div').click();
cmp.template.querySelector('div').click();
expect(result).toEqual([1, 2]);
});

Expand All @@ -119,7 +119,7 @@ describe('Events on Custom Elements', () => {
}
elm = createElement('x-foo', { is: Foo });
document.body.appendChild(elm);
cmp.root.querySelector('div').click();
cmp.template.querySelector('div').click();
expect(result).toEqual([1]);
});

Expand All @@ -144,7 +144,7 @@ describe('Events on Custom Elements', () => {
elm = createElement('x-foo', { is: Foo });
elm.addEventListener('click', clicked2);
document.body.appendChild(elm);
cmp.root.querySelector('div').click();
cmp.template.querySelector('div').click();
expect(result).toEqual([1]);
});

Expand All @@ -167,7 +167,7 @@ describe('Events on Custom Elements', () => {
}
elm = createElement('x-foo', { is: Foo });
document.body.appendChild(elm);
cmp.root.querySelector('div').dispatchEvent(new CustomEvent('test', { bubbles: true })); // intentionally without composed: true to see if the root captures can that
cmp.template.querySelector('div').dispatchEvent(new CustomEvent('test', { bubbles: true })); // intentionally without composed: true to see if the root captures can that
expect(result).toHaveLength(1);
});

Expand All @@ -190,7 +190,7 @@ describe('Events on Custom Elements', () => {
}
elm = createElement('x-foo', { is: Foo });
document.body.appendChild(elm);
cmp.root.querySelector('div').click();
cmp.template.querySelector('div').click();
expect(result).toHaveLength(2);
expect(result[0]).toBe(undefined); // context must be the component
expect(result[1]).toBeInstanceOf(Event);
Expand Down
32 changes: 16 additions & 16 deletions packages/lwc-engine/src/framework/__tests__/html-element.spec.ts
Expand Up @@ -11,23 +11,23 @@ import { querySelector } from "../dom/element";
describe('html-element', () => {
describe('#setAttributeNS()', () => {
it('should set attribute on host element when element is nested in template', () => {
class MyComponent extends Element {
class Child extends Element {
setFoo() {
this.setAttributeNS('x', 'foo', 'bar');
}
}
MyComponent.publicMethods = ['setFoo'];
Child.publicMethods = ['setFoo'];

class Parent extends Element {
render() {
return ($api) => {
return [$api.c('should-set-attribute-on-host-element-when-element-is-nested-in-template-child', MyComponent, {})]
return [$api.c('x-child', Child, {})]
}
}
}
const element = createElement('should-set-attribute-on-host-element-when-element-is-nested-in-template', { is: Parent });
document.body.appendChild(element);
const child = querySelector.call(element, 'should-set-attribute-on-host-element-when-element-is-nested-in-template-child');
const child = querySelector.call(element, 'x-child');
child.setFoo();
expect(child.hasAttributeNS('x', 'foo')).toBe(true);
expect(child.getAttributeNS('x', 'foo')).toBe('bar');
Expand Down Expand Up @@ -61,23 +61,23 @@ describe('html-element', () => {

describe('#setAttribute()', () => {
it('should set attribute on host element when element is nested in template', () => {
class MyComponent extends Element {
class Child extends Element {
setFoo() {
this.setAttribute('foo', 'bar');
}
}
MyComponent.publicMethods = ['setFoo'];
Child.publicMethods = ['setFoo'];

class Parent extends Element {
render() {
return ($api) => {
return [$api.c('should-set-attribute-on-host-element-when-element-is-nested-in-template-child', MyComponent, {})]
return [$api.c('x-child', Child, {})]
}
}
}
const element = createElement('should-set-attribute-on-host-element-when-element-is-nested-in-template', { is: Parent });
document.body.appendChild(element);
const child = querySelector.call(element, 'should-set-attribute-on-host-element-when-element-is-nested-in-template-child');
const child = querySelector.call(element, 'x-child');
child.setFoo();
expect(child.hasAttribute('foo')).toBe(true);
expect(child.getAttribute('foo')).toBe('bar');
Expand Down Expand Up @@ -111,17 +111,17 @@ describe('html-element', () => {

describe('#removeAttributeNS()', () => {
it('should remove namespaced attribute on host element when element is nested in template', () => {
class MyComponent extends Element {
class Child extends Element {
removeTitle() {
this.removeAttributeNS('x', 'title');
}
}
MyComponent.publicMethods = ['removeTitle'];
Child.publicMethods = ['removeTitle'];

class Parent extends Element {
render() {
return ($api) => {
return [$api.c('remove-namespaced-attribute-on-host-element-child', MyComponent, {
return [$api.c('x-child', Child, {
attrs: {
'x:title': 'foo',
}
Expand All @@ -131,7 +131,7 @@ describe('html-element', () => {
}
const element = createElement('remove-namespaced-attribute-on-host-element', { is: Parent });
document.body.appendChild(element);
const child = querySelector.call(element, 'remove-namespaced-attribute-on-host-element-child');
const child = querySelector.call(element, 'x-child');
child.removeTitle();
expect(child.hasAttributeNS('x', 'title')).toBe(false);
});
Expand All @@ -152,17 +152,17 @@ describe('html-element', () => {

describe('#removeAttribute()', () => {
it('should remove attribute on host element when element is nested in template', () => {
class MyComponent extends Element {
class Child extends Element {
removeTitle() {
this.removeAttribute('title');
}
}
MyComponent.publicMethods = ['removeTitle'];
Child.publicMethods = ['removeTitle'];

class Parent extends Element {
render() {
return ($api) => {
return [$api.c('element-is-nested-in-template-child', MyComponent, {
return [$api.c('x-child', Child, {
attrs: {
title: 'foo',
}
Expand All @@ -172,7 +172,7 @@ describe('html-element', () => {
}
const element = createElement('element-is-nested-in-template', { is: Parent });
document.body.appendChild(element);
const child = querySelector.call(element, 'element-is-nested-in-template-child');
const child = querySelector.call(element, 'x-child');
child.removeTitle();
expect(child.hasAttribute('title')).toBe(false);
});
Expand Down
Expand Up @@ -154,7 +154,7 @@ describe('invoker', () => {
}
}
function html($api) {
return [$api.c('x-foo', Child, {})];
return [$api.c('x-child', Child, {})];
}
class MyComponent3 extends Element {
renderedCallback() {
Expand Down
9 changes: 9 additions & 0 deletions packages/lwc-engine/src/framework/__tests__/upgrade.spec.ts
Expand Up @@ -4,6 +4,15 @@ import { ComponentConstructor } from "../component";

describe('upgrade', () => {
describe('#createElement()', () => {
it('should support constructors with circular dependencies', () => {
const factory = () => class extends Element { };
factory.__circular__ = true;

expect(
() => createElement('x-foo', { is: factory })
).not.toThrow();
});

it('should allow access to profixied default values for public props', () => {
const x = [1, 2, 3], y = { foo: 1 };
type MyComponentElement = HTMLElement & {
Expand Down
19 changes: 6 additions & 13 deletions packages/lwc-engine/src/framework/api.ts
@@ -1,9 +1,8 @@
import assert from "./assert";
import { vmBeingRendered, invokeEventListener, EventListenerContext } from "./invoker";
import { freeze, isArray, isUndefined, isNull, isFunction, isObject, isString, ArrayPush, assign, create, forEach, StringSlice, StringCharCodeAt, isNumber, hasOwnProperty, isTrue } from "./language";
import { EmptyArray, SPACE_CHAR, ViewModelReflection } from "./utils";
import { freeze, isArray, isUndefined, isNull, isFunction, isObject, isString, ArrayPush, assign, create, forEach, StringSlice, StringCharCodeAt, isNumber, isTrue } from "./language";
import { EmptyArray, SPACE_CHAR, ViewModelReflection, resolveCircularModuleDependency } from "./utils";
import { renderVM, createVM, appendVM, removeVM, VM, getCustomElementVM, Slotset, allocateInSlot } from "./vm";
import { registerComponent } from "./def";
import { ComponentConstructor } from "./component";
import { VNode, VNodeData, VNodes, VElement, VComment, VText, Hooks } from "../3rdparty/snabbdom/types";
import { patchShadowDomEvent, isValidEventForCustomElement } from "./events";
Expand Down Expand Up @@ -79,8 +78,8 @@ const hook: Hooks = {
renderVM(vm);
},
create(oldVNode: VNode, vnode: VNode) {
const { fallback, mode } = vnode.data;
createVM(vnode.sel as string, vnode.elm as HTMLElement, {
const { fallback, mode, ctor } = vnode.data;
createVM(vnode.sel as string, vnode.elm as HTMLElement, ctor, {
mode,
fallback,
});
Expand Down Expand Up @@ -215,12 +214,7 @@ export function s(slotName: string, data: VNodeData, children: VNodes, slotset:

// [c]ustom element node
export function c(sel: string, Ctor: ComponentConstructor, data: VNodeData, children?: VNodes): VElement {
// The compiler produce AMD modules that do not support circular dependencies
// We need to create an indirection to circumvent those cases.
// We could potentially move this check to the definition
if (hasOwnProperty.call(Ctor, '__circular__')) {
Ctor = Ctor();
}
Ctor = resolveCircularModuleDependency(Ctor);

if (process.env.NODE_ENV !== 'production') {
assert.isTrue(isString(sel), `c() 1st argument sel must be a string.`);
Expand Down Expand Up @@ -256,9 +250,8 @@ export function c(sel: string, Ctor: ComponentConstructor, data: VNodeData, chil
attrs = assign({}, attrs);
attrs.is = sel;
}
registerComponent(sel, Ctor);

data = { hook, key, slotset, attrs, on, props };
data = { hook, key, slotset, attrs, on, props, ctor: Ctor };
data.class = classMap || getMapFromClassName(normalizeStyleString(className));
data.style = styleMap || normalizeStyleString(style);
data.token = getCurrentTplToken();
Expand Down

0 comments on commit f44d252

Please sign in to comment.