Skip to content

Commit a13904e

Browse files
author
John Jenkins
committed
chore: more tests.. and ready?
1 parent 21bcf2a commit a13904e

File tree

8 files changed

+255
-77
lines changed

8 files changed

+255
-77
lines changed

src/compiler/transformers/decorators-to-static/prop-decorator.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,25 @@ const parsePropDecorator = (
117117
propMeta.defaultValue = prop.initializer.getText();
118118
} else if (ts.isGetAccessorDeclaration(prop)) {
119119
// shallow comb to find default value for a getter
120-
const returnSt = prop.body?.statements.find((st) => ts.isReturnStatement(st)) as ts.ReturnStatement;
121-
const retExp = returnSt.expression;
122-
123-
if (retExp && ts.isLiteralExpression(retExp)) {
124-
propMeta.defaultValue = retExp.getText();
125-
} else if (retExp && ts.isPropertyAccessExpression(retExp)) {
126-
const nameToFind = retExp.name.getText();
120+
const returnStatement = prop.body?.statements.find((st) => ts.isReturnStatement(st)) as ts.ReturnStatement;
121+
const returnExpression = returnStatement.expression;
122+
123+
if (returnExpression && ts.isLiteralExpression(returnExpression)) {
124+
// the getter has a literal return value
125+
propMeta.defaultValue = returnExpression.getText();
126+
} else if (returnExpression && ts.isPropertyAccessExpression(returnExpression)) {
127+
const nameToFind = returnExpression.name.getText();
127128
const foundProp = findGetProp(nameToFind, newMembers);
128-
if (foundProp && foundProp.initializer) propMeta.defaultValue = foundProp.initializer.getText();
129+
130+
if (foundProp && foundProp.initializer) {
131+
propMeta.defaultValue = foundProp.initializer.getText();
132+
133+
if (propMeta.type === 'unknown') {
134+
const type = typeChecker.getTypeAtLocation(foundProp);
135+
propMeta.type = propTypeFromTSType(type);
136+
propMeta.complexType = getComplexType(typeChecker, foundProp, type, program);
137+
}
138+
}
129139
}
130140
}
131141

src/compiler/transformers/test/parse-props.spec.ts

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ describe('parse props', () => {
566566
expect(t.property?.attribute).toBe('val');
567567
});
568568

569-
it('prop getter w/ inferred string type', () => {
569+
it('should infer string type from `get()` return value', () => {
570570
const t = transpileModule(`
571571
@Component({tag: 'cmp-a'})
572572
export class CmpA {
@@ -603,7 +603,7 @@ describe('parse props', () => {
603603
expect(t.property?.attribute).toBe('val');
604604
});
605605

606-
it('prop getter w/ inferred number type from property access expression', () => {
606+
it('should infer number type from `get()` property access expression', () => {
607607
const t = transpileModule(`
608608
@Component({tag: 'cmp-a'})
609609
export class CmpA {
@@ -641,7 +641,7 @@ describe('parse props', () => {
641641
expect(t.property?.attribute).toBe('val');
642642
});
643643

644-
it('prop getter and setter w/ inferred boolean type from property access expression', () => {
644+
it('should infer boolean type from `get()` property access expression', () => {
645645
const t = transpileModule(`
646646
@Component({tag: 'cmp-a'})
647647
export class CmpA {
@@ -650,9 +650,6 @@ describe('parse props', () => {
650650
get val() {
651651
return this._boolVal;
652652
};
653-
set val(newVal: boolean) {
654-
this._boolVal = newVal;
655-
};
656653
}
657654
`);
658655

@@ -675,10 +672,89 @@ describe('parse props', () => {
675672
required: false,
676673
type: 'boolean',
677674
getter: true,
678-
setter: true,
675+
setter: false,
679676
},
680677
});
681678
expect(t.property?.type).toBe('boolean');
682679
expect(t.property?.attribute).toBe('val');
683680
});
681+
682+
it('should correctly parse a get / set prop with an inferred enum type', () => {
683+
const t = transpileModule(`
684+
export enum Mode {
685+
DEFAULT = 'default'
686+
}
687+
@Component({tag: 'cmp-a'})
688+
export class CmpA {
689+
private _val: Mode;
690+
@Prop()
691+
get val() {
692+
return this._val;
693+
};
694+
}
695+
`);
696+
697+
// Using the `properties` array directly here since the `transpileModule`
698+
// method doesn't like the top-level enum export with the current `target` and
699+
// `module` values for the tsconfig
700+
expect(t.properties[0]).toEqual({
701+
name: 'val',
702+
type: 'string',
703+
attribute: 'val',
704+
reflect: false,
705+
mutable: false,
706+
required: false,
707+
optional: false,
708+
defaultValue: undefined,
709+
complexType: {
710+
original: 'Mode',
711+
resolved: 'Mode',
712+
references: {
713+
Mode: { location: 'local', path: 'module.tsx', id: 'module.tsx::Mode' },
714+
},
715+
},
716+
docs: { tags: [], text: '' },
717+
internal: false,
718+
getter: true,
719+
setter: false,
720+
});
721+
});
722+
723+
it('should not infer type from `get()` property access expression when getter type is explicit', () => {
724+
const t = transpileModule(`
725+
@Component({tag: 'cmp-a'})
726+
export class CmpA {
727+
private _boolVal: boolean = false;
728+
@Prop()
729+
get val(): string {
730+
return this._boolVal;
731+
};
732+
}
733+
`);
734+
735+
expect(getStaticGetter(t.outputText, 'properties')).toEqual({
736+
val: {
737+
attribute: 'val',
738+
complexType: {
739+
references: {},
740+
resolved: 'string',
741+
original: 'string',
742+
},
743+
docs: {
744+
text: '',
745+
tags: [],
746+
},
747+
defaultValue: `false`,
748+
mutable: false,
749+
optional: false,
750+
reflect: false,
751+
required: false,
752+
type: 'string',
753+
getter: true,
754+
setter: false,
755+
},
756+
});
757+
expect(t.property?.type).toBe('string');
758+
expect(t.property?.attribute).toBe('val');
759+
});
684760
});

src/hydrate/platform/proxy-host-element.ts

Lines changed: 13 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -71,52 +71,19 @@ export function proxyHostElement(elm: d.HostElement, cmpMeta: d.ComponentRuntime
7171
delete (elm as any)[memberName];
7272
}
7373

74-
if ((memberFlags & MEMBER_FLAGS.Getter) === 0) {
75-
// create the getter/setter on the host element for this property name
76-
Object.defineProperty(elm, memberName, {
77-
get(this: d.RuntimeRef) {
78-
// proxyComponent, get value
79-
return getValue(this, memberName);
80-
},
81-
set(this: d.RuntimeRef, newValue) {
82-
// proxyComponent, set value
83-
setValue(this, memberName, newValue, cmpMeta);
84-
},
85-
configurable: true,
86-
enumerable: true,
87-
});
88-
} else {
89-
// lazily maps the element get / set to the class get / set
90-
// proxyComponent - lazy prop getter
91-
Object.defineProperty(elm, memberName, {
92-
get(this: d.RuntimeRef) {
93-
const ref = getHostRef(this);
94-
return ref?.$lazyInstance$ ? ref.$lazyInstance$[memberName] : undefined;
95-
},
96-
configurable: true,
97-
enumerable: true,
98-
});
99-
if (memberFlags & MEMBER_FLAGS.Setter) {
100-
// proxyComponent - lazy prop setter
101-
Object.defineProperty(elm, memberName, {
102-
set(this: d.RuntimeRef, newValue) {
103-
const ref = getHostRef(this);
104-
if (!ref) return;
105-
106-
const setVal = (init = false) => {
107-
if (ref.$lazyInstance$) ref.$lazyInstance$[memberName] = newValue;
108-
setValue(this, memberName, newValue, cmpMeta, !init);
109-
};
110-
// If there's a value from an attribute, (before the class is defined), queue & set async
111-
if (ref.$lazyInstance$) {
112-
setVal();
113-
} else if (ref.$onInstancePromise$) {
114-
ref.$onInstancePromise$.then(() => setVal(true));
115-
}
116-
},
117-
});
118-
}
119-
}
74+
// create the getter/setter on the host element for this property name
75+
Object.defineProperty(elm, memberName, {
76+
get(this: d.RuntimeRef) {
77+
// proxyComponent, get value
78+
return getValue(this, memberName);
79+
},
80+
set(this: d.RuntimeRef, newValue) {
81+
// proxyComponent, set value
82+
setValue(this, memberName, newValue, cmpMeta);
83+
},
84+
configurable: true,
85+
enumerable: true,
86+
});
12087
} else if (memberFlags & MEMBER_FLAGS.Method) {
12188
Object.defineProperty(elm, memberName, {
12289
value(this: d.HostElement, ...args: any[]) {

src/runtime/proxy-component.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BUILD } from '@app-data';
2-
import { consoleDevWarn, getHostRef, plt } from '@platform';
2+
import { consoleDevWarn, getHostRef, parsePropertyValue, plt } from '@platform';
33
import { CMP_FLAGS } from '@utils';
44

55
import type * as d from '../declarations';
@@ -117,24 +117,44 @@ export const proxyComponent = (
117117
// non-lazy setter - amends original set to fire update
118118
const ref = getHostRef(this);
119119
if (origSetter) {
120-
origSetter.apply(this, [newValue]);
120+
const currentValue = ref.$hostElement$[memberName as keyof d.HostElement];
121+
if (!ref.$instanceValues$.get(memberName) && currentValue) {
122+
// the prop `set()` doesn't fire during `constructor()`:
123+
// no initial value gets set (in instanceValues)
124+
// meaning watchers fire even though the value hasn't changed.
125+
// So if there's a current value and no initial value, let's set it now.
126+
ref.$instanceValues$.set(memberName, currentValue);
127+
}
128+
// this sets the value via the `set()` function which
129+
// might not end up changing the underlying value
130+
origSetter.apply(this, [parsePropertyValue(newValue, cmpMeta.$members$[memberName][0])]);
121131
setValue(this, memberName, ref.$hostElement$[memberName as keyof d.HostElement], cmpMeta);
122132
return;
123133
}
124134
if (!ref) return;
125135

126136
// we need to wait for the lazy instance to be ready
127137
// before we can set it's value via it's setter function
128-
const setterSetVal = (init = false) => {
129-
ref.$lazyInstance$[memberName] = newValue;
130-
setValue(this, memberName, ref.$lazyInstance$[memberName], cmpMeta, !init);
138+
const setterSetVal = () => {
139+
const currentValue = ref.$lazyInstance$[memberName];
140+
if (!ref.$instanceValues$.get(memberName) && currentValue) {
141+
// the prop `set()` doesn't fire during `constructor()`:
142+
// no initial value gets set (in instanceValues)
143+
// meaning watchers fire even though the value hasn't changed.
144+
// So if there's a current value and no initial value, let's set it now.
145+
ref.$instanceValues$.set(memberName, currentValue);
146+
}
147+
// this sets the value via the `set()` function which
148+
// might not end up changing the underlying value
149+
ref.$lazyInstance$[memberName] = parsePropertyValue(newValue, cmpMeta.$members$[memberName][0]);
150+
setValue(this, memberName, ref.$lazyInstance$[memberName], cmpMeta);
131151
};
132152

133153
// If there's a value from an attribute, (before the class is defined), queue & set async
134154
if (ref.$lazyInstance$) {
135155
setterSetVal();
136156
} else {
137-
ref.$onReadyPromise$.then(() => setterSetVal(true));
157+
ref.$onReadyPromise$.then(() => setterSetVal());
138158
}
139159
},
140160
});

src/runtime/set-value.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@ import { scheduleUpdate } from './update-component';
88

99
export const getValue = (ref: d.RuntimeRef, propName: string) => getHostRef(ref).$instanceValues$.get(propName);
1010

11-
export const setValue = (
12-
ref: d.RuntimeRef,
13-
propName: string,
14-
newVal: any,
15-
cmpMeta: d.ComponentRuntimeMeta,
16-
fireWatchers = true,
17-
) => {
11+
export const setValue = (ref: d.RuntimeRef, propName: string, newVal: any, cmpMeta: d.ComponentRuntimeMeta) => {
1812
// check our new property value against our internal value
1913
const hostRef = getHostRef(ref);
2014

@@ -78,7 +72,7 @@ export const setValue = (
7872

7973
if (!BUILD.lazyLoad || instance) {
8074
// get an array of method names of watch functions to call
81-
if (BUILD.watchCallback && cmpMeta.$watchers$ && flags & HOST_FLAGS.isWatchReady && fireWatchers) {
75+
if (BUILD.watchCallback && cmpMeta.$watchers$ && flags & HOST_FLAGS.isWatchReady) {
8276
const watchMethods = cmpMeta.$watchers$[propName];
8377

8478
if (watchMethods) {

src/runtime/test/attr.spec.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,14 @@ describe('attribute', () => {
262262

263263
@Prop({ reflect: true, mutable: true }) dynamicStr: string;
264264
@Prop({ reflect: true }) dynamicNu: number;
265+
private _getset = 'prop via getter';
266+
@Prop({ reflect: true })
267+
get getSet() {
268+
return this._getset;
269+
}
270+
set getSet(newVal: string) {
271+
this._getset = newVal;
272+
}
265273

266274
componentWillLoad() {
267275
this.dynamicStr = 'value';
@@ -275,7 +283,7 @@ describe('attribute', () => {
275283
});
276284

277285
expect(root).toEqualHtml(`
278-
<cmp-a str="single" nu="2" other-bool dynamic-str="value" dynamic-nu="123"></cmp-a>
286+
<cmp-a str="single" nu="2" other-bool dynamic-str="value" dynamic-nu="123" get-set="prop via getter"></cmp-a>
279287
`);
280288

281289
root.str = 'second';
@@ -284,11 +292,12 @@ describe('attribute', () => {
284292
root.null = 'no null';
285293
root.bool = true;
286294
root.otherBool = false;
295+
root.getSet = 'prop set via setter';
287296

288297
await waitForChanges();
289298

290299
expect(root).toEqualHtml(`
291-
<cmp-a str="second" nu="-12.2" undef="no undef" null="no null" bool dynamic-str="value" dynamic-nu="123"></cmp-a>
300+
<cmp-a str="second" nu="-12.2" undef="no undef" null="no null" bool dynamic-str="value" dynamic-nu="123" get-set="prop set via setter"></cmp-a>
292301
`);
293302
});
294303

0 commit comments

Comments
 (0)