Skip to content

Commit

Permalink
fix(engine): tests and karma tests fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
caridy committed Aug 29, 2019
1 parent 719c41e commit 91c2d97
Show file tree
Hide file tree
Showing 16 changed files with 251 additions and 207 deletions.
27 changes: 9 additions & 18 deletions packages/@lwc/engine/src/framework/base-lightning-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ import assert from '../shared/assert';
import {
freeze,
create,
getOwnPropertyNames,
isFunction,
isNull,
defineProperties,
seal,
ArrayReduce,
isObject,
isFalse,
} from '../shared/language';
Expand Down Expand Up @@ -143,7 +141,7 @@ export interface LightningElementConstructor {

export declare var LightningElement: LightningElementConstructor;

export interface LightningElement extends EventTarget {
export interface LightningElement {
// DOM - The good parts
dispatchEvent(event: Event): boolean;
addEventListener(
Expand Down Expand Up @@ -561,22 +559,15 @@ BaseLightningElementConstructor.prototype = {
},
};

// Typescript is inferring the wrong function type for this particular
// overloaded method: https://github.com/Microsoft/TypeScript/issues/27972
// @ts-ignore type-mismatch
const baseDescriptors: PropertyDescriptorMap = ArrayReduce.call(
getOwnPropertyNames(HTMLElementOriginalDescriptors),
(descriptors: PropertyDescriptorMap, propName: string) => {
descriptors[propName] = createBridgeToElementDescriptor(
propName,
HTMLElementOriginalDescriptors[propName]
);
return descriptors;
},
create(null)
);
export const lightningBasedDescriptors: PropertyDescriptorMap = create(null);
for (const propName in HTMLElementOriginalDescriptors) {
lightningBasedDescriptors[propName] = createBridgeToElementDescriptor(
propName,
HTMLElementOriginalDescriptors[propName]
);
}

defineProperties(BaseLightningElementConstructor.prototype, baseDescriptors);
defineProperties(BaseLightningElementConstructor.prototype, lightningBasedDescriptors);

if (process.env.NODE_ENV !== 'production') {
patchLightningElementPrototypeWithRestrictions(BaseLightningElementConstructor.prototype);
Expand Down
11 changes: 0 additions & 11 deletions packages/@lwc/engine/src/framework/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { tagNameGetter } from '../env/element';
import { Template } from './template';
import { ReactiveObserver } from '../libs/mutation-tracker';
import { LightningElementConstructor, LightningElement } from './base-lightning-element';
import { installWireAdapters } from './wiring';

export type ErrorCallback = (error: any, stack: string) => void;
export interface ComponentInterface extends LightningElement {
Expand Down Expand Up @@ -77,16 +76,6 @@ export function createComponent(uninitializedVm: UninitializedVM, Ctor: Componen
}
}

export function linkComponent(vm: VM) {
if (process.env.NODE_ENV !== 'production') {
assert.isTrue(vm && 'cmpRoot' in vm, `${vm} is not a vm.`);
}
// initializing the wire decorator per instance only when really needed
if (vm.def.wire.length > 0) {
installWireAdapters(vm);
}
}

export function getTemplateReactiveObserver(vm: VM): ReactiveObserver {
return new ReactiveObserver(() => {
if (process.env.NODE_ENV !== 'production') {
Expand Down
15 changes: 10 additions & 5 deletions packages/@lwc/engine/src/framework/decorators/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ import { isFunction } from '../../shared/language';
* LWC Components. This function implements the internals of this
* decorator.
*/
// TODO: how to make api a decoratorFunction type as well?
export default function api(target: any, propertyKey: string, descriptor: PropertyDescriptor);
export default function api() {
if (process.env.NODE_ENV !== 'production') {
if (arguments.length !== 0) {
assert.fail(`@api decorator can only be used as a decorator function.`);
}
assert.fail(`@api decorator can only be used as a decorator function.`);
}
throw new Error();
}

export function createPublicPropertyDescriptor(key: string): PropertyDescriptor {
Expand Down Expand Up @@ -78,7 +77,13 @@ export function createPublicAccessorDescriptor(
): PropertyDescriptor {
const { get, set, enumerable, configurable } = descriptor;
if (!isFunction(get)) {
throw new TypeError();
if (process.env.NODE_ENV !== 'production') {
assert.invariant(
isFunction(get),
`Invalid compiler output for public accessor ${toString(key)} decorated with @api`
);
}
throw new Error();
}
return {
get(this: ComponentInterface): any {
Expand Down
149 changes: 89 additions & 60 deletions packages/@lwc/engine/src/framework/decorators/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
defineProperty,
getOwnPropertyDescriptor,
isFunction,
ArrayPush,
create,
toString,
isFalse,
} from '../../shared/language';
Expand All @@ -25,6 +25,8 @@ import {
storeWiredFieldMeta,
ConfigCallback,
} from '../wiring';
import { EmptyObject } from '../utils';
import { createObservedFieldPropertyDescriptor } from '../observed-fields';

// data produced by compiler
type WireCompilerMeta = Record<string, WireCompilerDef>;
Expand Down Expand Up @@ -54,36 +56,48 @@ interface RegisterDecoratorMeta {
readonly fields?: string[];
}

function validateObservedField(Ctor: ComponentConstructor, fieldName: string) {
function validateObservedField(
Ctor: ComponentConstructor,
fieldName: string,
descriptor: PropertyDescriptor | undefined
) {
if (process.env.NODE_ENV !== 'production') {
const descriptor = getOwnPropertyDescriptor(Ctor.prototype, fieldName);
if (!isUndefined(descriptor)) {
assert.fail(`Compiler Error: Invalid field ${fieldName} declaration.`);
}
}
}

function validateFieldDecoratedWithTrack(Ctor: ComponentConstructor, fieldName: string) {
function validateFieldDecoratedWithTrack(
Ctor: ComponentConstructor,
fieldName: string,
descriptor: PropertyDescriptor | undefined
) {
if (process.env.NODE_ENV !== 'production') {
const descriptor = getOwnPropertyDescriptor(Ctor.prototype, fieldName);
if (!isUndefined(descriptor)) {
assert.fail(`Compiler Error: Invalid @track ${fieldName} declaration.`);
}
}
}

function validateFieldDecoratedWithWire(Ctor: ComponentConstructor, fieldName: string) {
function validateFieldDecoratedWithWire(
Ctor: ComponentConstructor,
fieldName: string,
descriptor: PropertyDescriptor | undefined
) {
if (process.env.NODE_ENV !== 'production') {
const descriptor = getOwnPropertyDescriptor(Ctor.prototype, fieldName);
if (!isUndefined(descriptor)) {
assert.fail(`Compiler Error: Invalid @wire(...) ${fieldName} field declaration.`);
}
}
}

function validateMethodDecoratedWithWire(Ctor: ComponentConstructor, methodName: string) {
function validateMethodDecoratedWithWire(
Ctor: ComponentConstructor,
methodName: string,
descriptor: PropertyDescriptor | undefined
) {
if (process.env.NODE_ENV !== 'production') {
const descriptor = getOwnPropertyDescriptor(Ctor.prototype, methodName);
if (
isUndefined(descriptor) ||
!isFunction(descriptor.value) ||
Expand All @@ -94,18 +108,24 @@ function validateMethodDecoratedWithWire(Ctor: ComponentConstructor, methodName:
}
}

function validateFieldDecoratedWithApi(Ctor: ComponentConstructor, fieldName: string) {
function validateFieldDecoratedWithApi(
Ctor: ComponentConstructor,
fieldName: string,
descriptor: PropertyDescriptor | undefined
) {
if (process.env.NODE_ENV !== 'production') {
const descriptor = getOwnPropertyDescriptor(Ctor.prototype, fieldName);
if (!isUndefined(descriptor)) {
assert.fail(`Compiler Error: Invalid @api ${fieldName} field declaration.`);
}
}
}

function validateAccessorDecoratedWithApi(Ctor: ComponentConstructor, fieldName: string) {
function validateAccessorDecoratedWithApi(
Ctor: ComponentConstructor,
fieldName: string,
descriptor: PropertyDescriptor | undefined
) {
if (process.env.NODE_ENV !== 'production') {
const descriptor = getOwnPropertyDescriptor(Ctor.prototype, fieldName);
if (isUndefined(descriptor)) {
assert.fail(`Compiler Error: Invalid @api get ${fieldName} accessor declaration.`);
} else if (isFunction(descriptor.set)) {
Expand All @@ -121,9 +141,12 @@ function validateAccessorDecoratedWithApi(Ctor: ComponentConstructor, fieldName:
}
}

function validateMethodDecoratedWithApi(Ctor: ComponentConstructor, methodName: string) {
function validateMethodDecoratedWithApi(
Ctor: ComponentConstructor,
methodName: string,
descriptor: PropertyDescriptor | undefined
) {
if (process.env.NODE_ENV !== 'production') {
const descriptor = getOwnPropertyDescriptor(Ctor.prototype, methodName);
if (
isUndefined(descriptor) ||
!isFunction(descriptor.value) ||
Expand All @@ -144,117 +167,123 @@ export function registerDecorators(
): ComponentConstructor {
const proto = Ctor.prototype;
const { publicProps, publicMethods, wire, track, fields } = meta;
const apiMethods = [];
const apiFields = [];
const wiredMethods = [];
const wiredFields = [];
const apiMethods: PropertyDescriptorMap = create(null);
const apiFields: PropertyDescriptorMap = create(null);
const wiredMethods: PropertyDescriptorMap = create(null);
const wiredFields: PropertyDescriptorMap = create(null);
const observedFields: PropertyDescriptorMap = create(null);
let descriptor: PropertyDescriptor | undefined;
if (!isUndefined(publicProps)) {
for (const fieldName in publicProps) {
const propConfig = publicProps[fieldName];
let descriptor: PropertyDescriptor | undefined;
descriptor = getOwnPropertyDescriptor(proto, fieldName);
if (propConfig.config > 0) {
// accessor declaration
if (process.env.NODE_ENV !== 'production') {
validateAccessorDecoratedWithApi(Ctor, fieldName);
validateAccessorDecoratedWithApi(Ctor, fieldName, descriptor);
}
if (isUndefined(descriptor)) {
throw new Error();
}
descriptor = getOwnPropertyDescriptor(proto, fieldName);
descriptor = createPublicAccessorDescriptor(
fieldName,
descriptor as PropertyDescriptor
);
descriptor = createPublicAccessorDescriptor(fieldName, descriptor);
} else {
// field declaration
if (process.env.NODE_ENV !== 'production') {
validateFieldDecoratedWithApi(Ctor, fieldName);
validateFieldDecoratedWithApi(Ctor, fieldName, descriptor);
}
descriptor = createPublicPropertyDescriptor(fieldName);
}
ArrayPush.call(apiFields, fieldName);
apiFields[fieldName] = descriptor;
defineProperty(proto, fieldName, descriptor);
}
}
if (!isUndefined(publicMethods)) {
forEach.call(publicMethods, methodName => {
descriptor = getOwnPropertyDescriptor(proto, methodName);
if (process.env.NODE_ENV !== 'production') {
validateMethodDecoratedWithApi(Ctor, methodName);
validateMethodDecoratedWithApi(Ctor, methodName, descriptor);
}
if (isUndefined(descriptor)) {
throw new Error();
}
ArrayPush.call(apiMethods, methodName);
apiMethods[methodName] = descriptor;
});
}
if (!isUndefined(wire)) {
for (const fieldOrMethodName in wire) {
const { adapter, method } = wire[fieldOrMethodName];
const configCallback = wire[fieldOrMethodName].config;
descriptor = getOwnPropertyDescriptor(proto, fieldOrMethodName);
if (method === 1) {
if (process.env.NODE_ENV !== 'production') {
validateMethodDecoratedWithWire(Ctor, fieldOrMethodName);
validateMethodDecoratedWithWire(Ctor, fieldOrMethodName, descriptor);
}
ArrayPush.call(wiredMethods, fieldOrMethodName);
storeWiredMethodMeta(
Ctor,
fieldOrMethodName,
adapter,
proto[fieldOrMethodName] as (data: any) => void,
configCallback
);
if (isUndefined(descriptor)) {
throw new Error();
}
wiredMethods[fieldOrMethodName] = descriptor;
storeWiredMethodMeta(descriptor, adapter, configCallback);
} else {
if (process.env.NODE_ENV !== 'production') {
validateFieldDecoratedWithWire(Ctor, fieldOrMethodName);
validateFieldDecoratedWithWire(Ctor, fieldOrMethodName, descriptor);
}
storeWiredFieldMeta(Ctor, fieldOrMethodName, adapter, configCallback);
ArrayPush.call(wiredFields, fieldOrMethodName);
defineProperty(
proto,
fieldOrMethodName,
internalWireFieldDecorator(fieldOrMethodName)
);
descriptor = internalWireFieldDecorator(fieldOrMethodName);
wiredFields[fieldOrMethodName] = descriptor;
storeWiredFieldMeta(descriptor, adapter, configCallback);
defineProperty(proto, fieldOrMethodName, descriptor);
}
}
}
if (!isUndefined(track)) {
for (const fieldName in track) {
descriptor = getOwnPropertyDescriptor(proto, fieldName);
if (process.env.NODE_ENV !== 'production') {
validateFieldDecoratedWithTrack(Ctor, fieldName);
validateFieldDecoratedWithTrack(Ctor, fieldName, descriptor);
}
defineProperty(proto, fieldName, internalTrackDecorator(fieldName));
descriptor = internalTrackDecorator(fieldName);
defineProperty(proto, fieldName, descriptor);
}
}
if (!isUndefined(fields)) {
for (let i = 0, n = fields.length; i < n; i++) {
const fieldName = fields[i];
descriptor = getOwnPropertyDescriptor(proto, fieldName);
if (process.env.NODE_ENV !== 'production') {
validateObservedField(Ctor, fields[i]);
validateObservedField(Ctor, fieldName, descriptor);
}
observedFields[fieldName] = createObservedFieldPropertyDescriptor(fieldName);
}
}
setDecoratorsMeta(Ctor, {
apiMethods,
apiFields,
wiredMethods,
wiredFields,
fields,
observedFields,
});
return Ctor;
}

const signedDecoratorToMetaMap: Map<ComponentConstructor, DecoratorMeta> = new Map();

interface DecoratorMeta {
readonly apiMethods: string[];
readonly apiFields: string[];
readonly wiredMethods: string[];
readonly wiredFields: string[];
readonly fields?: string[];
readonly apiMethods: PropertyDescriptorMap;
readonly apiFields: PropertyDescriptorMap;
readonly wiredMethods: PropertyDescriptorMap;
readonly wiredFields: PropertyDescriptorMap;
readonly observedFields: PropertyDescriptorMap;
}

function setDecoratorsMeta(Ctor: ComponentConstructor, meta: DecoratorMeta) {
signedDecoratorToMetaMap.set(Ctor, meta);
}

const defaultMeta: DecoratorMeta = {
apiMethods: [],
apiFields: [],
wiredMethods: [],
wiredFields: [],
apiMethods: EmptyObject,
apiFields: EmptyObject,
wiredMethods: EmptyObject,
wiredFields: EmptyObject,
observedFields: EmptyObject,
};

export function getDecoratorsMeta(Ctor: ComponentConstructor): DecoratorMeta {
Expand Down
Loading

0 comments on commit 91c2d97

Please sign in to comment.