[ember] Refactor some private types into ghost modules (#28735)

* [ember] refactor some private types into ghost modules

* [ember] additional tests for low-level CP and Observable types

* [ember] fix: add tests/core-object
This commit is contained in:
Mike North
2018-09-13 09:56:56 -07:00
committed by Ryan Cavanaugh
parent 856deff048
commit 992b5b5f11
13 changed files with 627 additions and 65 deletions

5
types/ember/-private-types/mixin.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
import Ember from 'ember';
/**
* Ember.Object.extend(...) accepts any number of mixins or literals.
*/
declare type MixinOrLiteral<T, Base> = Ember.Mixin<T, Base> | T;

28
types/ember/-private-types/object.d.ts vendored Normal file
View File

@@ -0,0 +1,28 @@
/**
* Used to infer the type of ember classes of type `T`.
*
* Generally you would use `EmberClass.create()` instead of `new EmberClass()`.
*
* The single-arg constructor is required by the typescript compiler.
* The multi-arg constructor is included for better ergonomics.
*
* Implementation is carefully chosen for the reasons described in
* https://github.com/typed-ember/ember-typings/pull/29
*/
export type EmberClassConstructor<T> = (new (properties?: object) => T) & (new (...args: any[]) => T);
/**
* Check that any arguments to `create()` match the type's properties.
*
* Accept any additional properties and add merge them into the instance.
*/
export type EmberInstanceArguments<T> = Partial<T> & {
[key: string]: any;
};
/**
* Accept any additional properties and add merge them into the prototype.
*/
export interface EmberClassArguments {
[key: string]: any;
}

View File

@@ -0,0 +1,34 @@
import ComputedProperty from '@ember/object/computed';
/**
* Deconstructs computed properties into the types which would be returned by `.get()`.
*/
export type UnwrapComputedPropertyGetter<T> =
T extends ComputedProperty<infer U, any> ? U :
T;
export type UnwrapComputedPropertyGetters<T> = {
[P in keyof T]: UnwrapComputedPropertyGetter<T[P]>;
};
export type UnwrapComputedPropertySetter<T> =
T extends ComputedProperty<any, infer V> ? V :
T;
export type UnwrapComputedPropertySetters<T> = {
[P in keyof T]: UnwrapComputedPropertySetter<T[P]>;
};
export type ComputedPropertyGetterFunction<T> = (this: any, key: string) => T;
export type ComputedPropertySetterFunction<T> = (this: any, key: string, newVal: T, oldVal: T) => T;
export interface ComputedPropertyGetterObj<T> {
get(this: any, key: string): T;
}
export interface ComputedPropertySetterObj<T> {
set(this: any, key: string, value: T): T;
}
export type ComputedPropertyObj<T> = ComputedPropertyGetterObj<T> | ComputedPropertySetterObj<T> | (ComputedPropertyGetterObj<T> & ComputedPropertySetterObj<T>);
export type ComputedPropertyGetter<T> = ComputedPropertyGetterFunction<T> | ComputedPropertyGetterObj<T>;
export type ComputedPropertySetter<T> = ComputedPropertySetterFunction<T> | ComputedPropertySetterObj<T>;
export type ComputedPropertyCallback<T> = ComputedPropertyGetterFunction<T> | ComputedPropertyObj<T>;

14
types/ember/-private-types/utils.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
/**
* Map type `T` to a plain object hash with the identity mapping.
*
* Discards any additional object identity like the ability to `new()` up the class.
* The `new()` capability is added back later by merging `EmberClassConstructor<T>`
*
* Implementation is carefully chosen for the reasons described in
* https://github.com/typed-ember/ember-typings/pull/29
*/
export type Objectify<T> = Readonly<T>;
export type ExtractPropertyNamesOfType<T, S> = {
[K in keyof T]: T[K] extends S ? K : never
}[keyof T];
export type Fix<T> = { [K in keyof T]: T[K] };

View File

@@ -16,6 +16,16 @@
/// <reference types="handlebars" />
declare module 'ember' {
import {
UnwrapComputedPropertySetters,
UnwrapComputedPropertySetter,
UnwrapComputedPropertyGetters,
UnwrapComputedPropertyGetter,
ComputedPropertyCallback
} from 'ember/-private-types/object/computed';
import { Objectify, Fix } from 'ember/-private-types/utils';
import { EmberClassArguments, EmberClassConstructor, EmberInstanceArguments } from 'ember/-private-types/object';
// Capitalization is intentional: this makes it much easier to re-export RSVP on
// the Ember namespace.
import Rsvp from 'rsvp';
@@ -42,52 +52,6 @@ declare module 'ember' {
: F extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E) => any
? [A, B, C, D, E]
: never;
/**
* Deconstructs computed properties into the types which would be returned by `.get()`.
*/
type UnwrapComputedPropertyGetter<T> =
T extends Ember.ComputedProperty<infer U, any> ? U :
T;
type UnwrapComputedPropertyGetters<T> = {
[P in keyof T]: UnwrapComputedPropertyGetter<T[P]>;
};
type UnwrapComputedPropertySetter<T> =
T extends Ember.ComputedProperty<any, infer U> ? U :
T;
type UnwrapComputedPropertySetters<T> = {
[P in keyof T]: UnwrapComputedPropertySetter<T[P]>;
};
/**
* Check that any arguments to `create()` match the type's properties.
*
* Accept any additional properties and add merge them into the instance.
*/
type EmberInstanceArguments<T> = Partial<T> & {
[key: string]: any;
};
/**
* Accept any additional properties and add merge them into the prototype.
*/
interface EmberClassArguments {
[key: string]: any;
}
/**
* Map type `T` to a plain object hash with the identity mapping.
*
* Discards any additional object identity like the ability to `new()` up the class.
* The `new()` capability is added back later by merging `EmberClassConstructor<T>`
*
* Implementation is carefully chosen for the reasons described in
* https://github.com/typed-ember/ember-typings/pull/29
*/
type Objectify<T> = Readonly<T>;
type Fix<T> = { [K in keyof T]: T[K] };
/**
* Ember.Object.extend(...) accepts any number of mixins or literals.
*/
@@ -104,23 +68,6 @@ declare module 'ember' {
* Implementation is carefully chosen for the reasons described in
* https://github.com/typed-ember/ember-typings/pull/29
*/
type EmberClassConstructor<T> = (new (properties?: object) => T) & (new (...args: any[]) => T);
type ComputedPropertyGetterFunction<T> = (this: any, key: string) => T;
interface ComputedPropertyGet<T> {
get(this: any, key: string): T;
}
interface ComputedPropertySet<T> {
set(this: any, key: string, value: T): T;
}
type ComputedPropertyCallback<T> =
| ComputedPropertyGetterFunction<T>
| ComputedPropertyGet<T>
| ComputedPropertySet<T>
| (ComputedPropertyGet<T> & ComputedPropertySet<T>);
interface ActionsHash {
[index: string]: (...params: any[]) => any;

View File

@@ -1,3 +1,7 @@
/**
* Tests to ensure that access modifier keywords are appropriately
* respected and supported
*/
import Ember from 'ember';
import { assertType } from './lib/assert';
@@ -8,13 +12,16 @@ class Foo extends Ember.Object {
}
const f = new Foo();
assertType<string>(f.hello());
// protected property should not be visible from outside of Foo
assertType<string>(f.bar()); // $ExpectError
// private property should not be visible from outside of Foo
assertType<string>(f.baz()); // $ExpectError
class Foo2 extends Ember.Object.extend({
bar: ''
}) {
hello() { return 'world'; }
// Cannot override with a mis-matched property type
protected bar() { return 'bar'; } // $ExpectError
private baz() { return 'baz'; }
}

View File

@@ -3,6 +3,9 @@ import Component from '@ember/component';
import Object, { computed } from '@ember/object';
import hbs from 'htmlbars-inline-precompile';
import { assertType } from "./lib/assert";
import ComputedProperty from '@ember/object/computed';
import { ExtractPropertyNamesOfType } from 'ember/-private-types/utils';
import { UnwrapComputedPropertySetter } from 'ember/-private-types/object/computed';
Component.extend({
layout: hbs`

View File

@@ -0,0 +1,198 @@
import Ember from 'ember';
import { assertType } from './lib/assert';
/** Newable tests */
const co1 = new Ember.CoreObject();
// TODO: Enable in TS 3.0 see: https://github.com/typed-ember/ember-cli-typescript/issues/291
// co1.concatenatedProperties; // $ExpectType string[]
co1.isDestroyed; // $ExpectType boolean
co1.isDestroying; // $ExpectType boolean
co1.destroy(); // $ExpectType CoreObject
co1.toString(); // $ExpectType string
/** .create tests */
const co2 = Ember.CoreObject.create();
// TODO: Enable in TS 3.0 see: https://github.com/typed-ember/ember-cli-typescript/issues/291
// co2.concatenatedProperties; // $ExpectType string[]
co2.isDestroyed; // $ExpectType boolean
co2.isDestroying; // $ExpectType boolean
co2.destroy(); // $ExpectType CoreObject
co2.toString(); // $ExpectType string
/** .create tests w/ initial instance data passed in */
const co3 = Ember.CoreObject.create({ foo: '123', bar: 456 });
co3.foo; // $ExpectType string
co3.bar; // $ExpectType number
/** .extend with a zero-argument .create() */
const co4 = Ember.CoreObject.extend({
foo: '123',
bar: 456,
baz(): [string, number] {
return [this.foo, this.bar];
}
}).create();
co4.foo; // $ExpectType string
co4.bar; // $ExpectType number
co4.baz; // $ExpectType () => [string, number]
/** .extend with inconsistent arguments passed into .create() */
const class05 = Ember.CoreObject.extend({
foo: '123' as (string | boolean),
bar: 456,
baz() {
return [this.foo, this.bar];
}
});
const c05 = class05.create({ foo: 99 }); // $ExpectError
const c05b = class05.create({ foo: true });
const c05c = class05.create({ foo: 'abc' });
assertType<string>(c05b.foo); // $ExpectError
assertType<boolean>(c05c.foo); // $ExpectError
/** two .extend arguments with a zero-argument .create() */
const co6 = Ember.CoreObject.extend({
foo: '123',
bar: 456,
baz() {
return [this.foo, this.bar];
},
func1() {
// this includes stuff from CoreObject
this.init; // $ExpectType () => void
// this includes stuff from this extend-arg
this.foo; // $ExpectType string
// this does not include stuff from later extend args
this.bee; // $ExpectError
}
}, {
foo: 99,
bee: 'honey',
func2() {
// this includes stuff from CoreObject
this.init; // $ExpectType () => void
// this includes stuff from this extend-arg
// TODO: switch to "$ExpectType number" in TS 3.0 see: https://github.com/typed-ember/ember-cli-typescript/issues/291
this.foo; // $ExpectType string & number
// this includes stuff from earlier extend-args
this.bar; // $ExpectType number
}
}).create();
// TODO: enable in TS 3.0 see: https://github.com/typed-ember/ember-cli-typescript/issues/291
// assertType<string>(co6.foo); // $ExpectError
assertType<number>(co6.bar); // $ExpectType number
assertType<() => Array<string | number>>(co6.baz); // $ExpectType () => (string | number)[]
/** three .extend arguments with a zero-argument .create() */
const co7 = Ember.CoreObject.extend({
foo: '123',
bar: 456,
baz() {
return [this.foo, this.bar];
},
func1() {
// this includes stuff from CoreObject
this.init; // $ExpectType () => void
// this includes stuff from this extend-arg
this.foo; // $ExpectType string
// this does not include stuff from later extend args
this.bee; // $ExpectError
}
}, {
foo: 99,
bee: 'honey',
func2() {
// this includes stuff from CoreObject
this.init; // $ExpectType () => void
// this includes stuff from this extend-arg
// TODO: switch to "$ExpectType number" in TS 3.0 see: https://github.com/typed-ember/ember-cli-typescript/issues/291
this.foo; // $ExpectType string & number
// this includes stuff from earlier extend-args
this.bar; // $ExpectType number
}
}, {
foo: '99',
money: 'in the banana stand',
func3() {
// this includes stuff from CoreObject
this.init; // $ExpectType () => void
// this includes stuff from this extend-arg
this.money; // $ExpectType string
// this includes stuff from earlier extend-args
this.bee; // $ExpectType string
this.bar; // $ExpectType number
}
}).create();
// TODO: enable in TS 3.0 see: https://github.com/typed-ember/ember-cli-typescript/issues/291
// assertType<number>(co7.foo); // $ExpectError
assertType<number>(co7.bar); // $ExpectType number
assertType<string>(co7.money); // $ExpectType string
assertType<() => Array<string | number>>(co7.baz); // $ExpectType () => (string | number)[]
/** four .extend arguments with a zero-argument .create() */
const co8 = Ember.CoreObject.extend({
foo: '123',
bar: 456,
baz() {
return [this.foo, this.bar];
},
func1() {
// this includes stuff from CoreObject
this.init; // $ExpectType () => void
// this includes stuff from this extend-arg
this.foo; // $ExpectType string
// this does not include stuff from later extend args
this.bee; // $ExpectError
}
}, {
foo: 99,
bee: 'honey',
func2() {
// this includes stuff from CoreObject
this.init; // $ExpectType () => void
// this includes stuff from this extend-arg
// TODO: switch to "$ExpectType number" in TS 3.0 see: https://github.com/typed-ember/ember-cli-typescript/issues/291
this.foo; // $ExpectType string & number
// this includes stuff from earlier extend-args
this.bar; // $ExpectType number
// this does not include stuff from later extend args
this.money; // $ExpectError
}
}, {
foo: '99',
money: 'in the banana stand',
func3() {
// this includes stuff from CoreObject
this.init; // $ExpectType () => void
// this includes stuff from this extend-arg
this.money; // $ExpectType string
// this includes stuff from earlier extend-args
this.bee; // $ExpectType string
this.bar; // $ExpectType number
// this does not include stuff from later extend args
this.neighborhood; // $ExpectError
}
}, {
foo: '99',
neighborhood: 'sudden valley',
func4() {
// this includes stuff from CoreObject
this.init; // $ExpectType () => void
// this includes stuff from this extend-arg
this.neighborhood; // $ExpectType string
// this includes stuff from earlier extend-args
this.bee; // $ExpectType string
this.bar; // $ExpectType number
this.money; // $ExpectType string
}
}).create();
// TODO: enable in TS 3.0 see: https://github.com/typed-ember/ember-cli-typescript/issues/291
// assertType<number>(co8.foo); // $ExpectError
assertType<number>(co8.bar); // $ExpectType number
assertType<string>(co8.money); // $ExpectType string
assertType<() => Array<string | number>>(co8.baz); // $ExpectType () => (string | number)[]

View File

@@ -2,4 +2,4 @@
// Disable tslint here b/c the generic is used to let us do a type coercion and
// validate that coercion works for the type value "passed into" the function.
// tslint:disable-next-line:no-unnecessary-generics
export declare function assertType<T>(value: T): void;
export declare function assertType<T>(value: T): T;

View File

@@ -0,0 +1,82 @@
import { Ember } from "ember";
import { computed } from "@ember/object";
import { UnwrapComputedPropertySetters, UnwrapComputedPropertyGetters, UnwrapComputedPropertySetter, UnwrapComputedPropertyGetter } from "ember/-private-types/object/computed";
import ComputedProperty from "@ember/object/computed";
import { assertType } from "../lib/assert";
class Example1 extends Ember.Object.extend({
firstName: '',
lastName: '',
allNames: computed('fullName', function() {
return [this.fullName];
}),
fullName: computed('firstName', 'lastName', function() {
return `${this.firstName} ${this.lastName}`;
})
}) {
allNames!: ComputedProperty<string[]>;
fullName!: ComputedProperty<string>;
}
let unwrappedGetters1: UnwrapComputedPropertyGetters<Example1> = {} as any;
unwrappedGetters1.firstName; // $ExpectType string
unwrappedGetters1.lastName; // $ExpectType string
unwrappedGetters1.fullName; // $ExpectType string
unwrappedGetters1.allNames; // $ExpectType string[]
let unwrappedSetters1: UnwrapComputedPropertySetters<Example1> = {} as any;
unwrappedSetters1.firstName; // $ExpectType string
unwrappedSetters1.lastName; // $ExpectType string
unwrappedSetters1.fullName; // $ExpectType string
unwrappedSetters1.allNames; // $ExpectType string[]
class Example2 extends Ember.Object.extend({
allNames: computed('fullName', function() {
return [this.fullName + ''];
}) ,
fullName: computed('firstName', 'lastName', function() {
return `${this.firstName} ${this.lastName}`;
})
}) {
firstName = '';
lastName = '';
foo() {
this.fullName; // $ExpectType ComputedProperty<string, string>
this.allNames; // $ExpectType ComputedProperty<string[], string[]>
this.firstName; // $ExpectType string
this.lastName; // $ExpectType string
this.get('fullName').split(','); // $ExpectType string[]
this.get('allNames')[0]; // $ExpectType string
this.get('firstName').split(','); // $ExpectType string[]
this.get('lastName').split(','); // $ExpectType string[]
}
}
let ex2 = new Example2();
let unwrappedGetters2: UnwrapComputedPropertyGetters<typeof ex2> = (ex2 as any) as UnwrapComputedPropertyGetters<typeof ex2>;
assertType<string>(unwrappedGetters2.firstName); // $ExpectType string
assertType<string>(unwrappedGetters2.lastName); // $ExpectType string
assertType<string>(unwrappedGetters2.fullName); // $ExpectType string
assertType<string[]>(unwrappedGetters2.allNames); // $ExpectType string[]
let unwrappedSetters2: UnwrapComputedPropertySetters<typeof ex2> = null as any;
assertType<string>(unwrappedSetters2.firstName); // $ExpectType string
assertType<string>(unwrappedSetters2.lastName); // $ExpectType string
assertType<string>(unwrappedSetters2.fullName); // $ExpectType string
assertType<string[]>(unwrappedSetters2.allNames); // $ExpectType string[]
ex2.fullName; // $ExpectType ComputedProperty<string, string>
ex2.allNames; // $ExpectType ComputedProperty<string[], string[]>
ex2.firstName; // $ExpectType string
type UnwStringSet = UnwrapComputedPropertySetter<string>; // $ExpectType string
type UnwStringGet = UnwrapComputedPropertyGetter<string>; // $ExpectType string
type UnwCpStringSet1 = UnwrapComputedPropertySetter<ComputedProperty<string>>; // $ExpectType string
type UnwCpStringGet1 = UnwrapComputedPropertyGetter<ComputedProperty<string>>; // $ExpectType string
type UnwCpStringSet2 = UnwrapComputedPropertySetter<ComputedProperty<string, string>>; // $ExpectType string
type UnwCpStringGet2 = UnwrapComputedPropertyGetter<ComputedProperty<string, string>>; // $ExpectType string
type UnwCpStringSet3 = UnwrapComputedPropertySetter<ComputedProperty<number, string>>; // $ExpectType string
type UnwCpStringGet3 = UnwrapComputedPropertyGetter<ComputedProperty<number, string>>; // $ExpectType number
type UnwCpStringSet4 = UnwrapComputedPropertySetter<ComputedProperty<string, number>>; // $ExpectType number
type UnwCpStringGet4 = UnwrapComputedPropertyGetter<ComputedProperty<string, number>>; // $ExpectType string

View File

@@ -0,0 +1,175 @@
import Observable from "@ember/object/observable";
import { UnwrapComputedPropertyGetter, UnwrapComputedPropertyGetters, UnwrapComputedPropertySetters, UnwrapComputedPropertySetter } from "ember/-private-types/object/computed";
// tslint-disable-next-line
import { assertType } from "../lib/assert";
import { Ember } from "ember";
import { ExtractPropertyNamesOfType } from "ember/-private-types/utils";
class OtherThing {
observerOfDemo(target: DemoObservable, key: 'foo') {}
}
class DemoObservable implements Observable {
foo: string;
isFoo = true;
bar: [boolean, boolean];
baz?: number;
constructor() {
this.foo = 'hello';
this.bar = [false, true];
this.baz = 9;
this.addObserver('foo', this, 'fooDidChange');
this.addObserver('foo', this, 'fooDidChangeProtected'); // $ExpectError
this.addObserver('foo', this, this.fooDidChange);
this.addObserver('foo', this, this.fooDidChangeProtected);
const ot = new OtherThing();
this.addObserver('foo', ot, ot.observerOfDemo);
Ember.addObserver(this, 'foo', this, 'fooDidChange');
Ember.addObserver(this, 'foo', this, 'fooDidChangeProtected'); // $ExpectError
Ember.addObserver(this, 'foo', this, this.fooDidChange);
Ember.addObserver(this, 'foo', this, this.fooDidChangeProtected);
this.removeObserver('foo', this, 'fooDidChange');
this.removeObserver('foo', this, 'fooDidChangeProtected'); // $ExpectError
this.removeObserver('foo', this, this.fooDidChange);
this.removeObserver('foo', this, this.fooDidChangeProtected);
Ember.removeObserver(this, 'foo', this, 'fooDidChange');
Ember.removeObserver(this, 'foo', this, 'fooDidChangeProtected'); // $ExpectError
Ember.removeObserver(this, 'foo', this, this.fooDidChange);
const lambda = () => {
this.fooDidChange(this, 'foo');
};
this.addObserver('foo', lambda);
this.addObserver('foo', (sender, key, value, rev) => {
assertType<DemoObservable>(sender);
assertType<'foo'>(key);
assertType<string>(value);
assertType<number>(rev);
});
this.removeObserver('foo', lambda);
Ember.addObserver(this, 'foo', lambda);
Ember.removeObserver(this, 'foo', lambda);
}
fooDidChange(obj: this, propName: keyof this) {}
protected fooDidChangeProtected(obj: this, propName: keyof this) {}
get<K extends keyof this>(key: K): UnwrapComputedPropertyGetter<this[K]> {
throw new Error("Method not implemented.");
}
getProperties<K extends keyof this>(list: K[]): Pick<UnwrapComputedPropertyGetters<this>, K>;
getProperties<K extends keyof this>(...list: K[]): Pick<UnwrapComputedPropertyGetters<this>, K>;
getProperties(...rest: any[]): any {
throw new Error("Method not implemented.");
}
set<K extends keyof this>(key: K, value: this[K]): this[K] {
throw new Error("Method not implemented.");
}
setProperties<K extends keyof this>(hash: Pick<UnwrapComputedPropertySetters<this>, K>): Pick< UnwrapComputedPropertySetters<this>, K> {
throw new Error("Method not implemented.");
}
notifyPropertyChange(keyName: string): this {
throw new Error("Method not implemented.");
}
addObserver<Target>(key: keyof this, target: Target, method: keyof Target | ((this: Target, sender: this, key: keyof this, value: any, rev: number) => void)): void;
addObserver<K extends keyof this>(key: K, method: keyof this | ((this: this, sender: this, key: K, value: this[K], rev: number) => void)): void;
addObserver(key: any, target: any, method?: any) {
throw new Error("Method not implemented.");
}
removeObserver<Target>(key: keyof this, target: Target, method: keyof Target | ((this: Target, sender: this, key: keyof this, value: any, rev: number) => void)): void;
removeObserver(key: keyof this, method: keyof this | ((this: this, sender: this, key: keyof this, value: any, rev: number) => void)): void;
removeObserver(key: any, target: any, method?: any): void {
throw new Error("Method not implemented.");
}
getWithDefault<K extends keyof this>(key: K, defaultValue: UnwrapComputedPropertyGetter<this[K]>): UnwrapComputedPropertyGetter<this[K]> {
throw new Error("Method not implemented.");
}
incrementProperty(keyName: ExtractPropertyNamesOfType<this, number | undefined>, increment?: number): number {
throw new Error("Method not implemented.");
}
decrementProperty(keyName: ExtractPropertyNamesOfType<this, number | undefined>, decrement?: number): number {
throw new Error("Method not implemented.");
}
toggleProperty(keyName: ExtractPropertyNamesOfType<this, boolean | undefined>): boolean {
throw new Error("Method not implemented.");
}
cacheFor<K extends keyof this>(key: K): UnwrapComputedPropertyGetter<this[K]> | undefined {
throw new Error("Method not implemented.");
}
}
const o = new DemoObservable();
/**
* get
*/
assertType<string>(o.get('foo')); // $ExpectType string
assertType<[boolean, boolean]>(o.get('bar')); // $ExpectType [boolean, boolean]
assertType<number | undefined>(o.get('baz')); // $ExpectType number | undefined
/**
* incrementProperty, decrementProperty
*/
assertType<number>(o.incrementProperty('baz')); // $ExpectType number
assertType<number>(o.decrementProperty('baz')); // $ExpectType number
assertType<number>(o.incrementProperty('baz', 3)); // $ExpectType number
assertType<number>(o.decrementProperty('baz', 12)); // $ExpectType number
// non-numeric property case
assertType<number>(o.incrementProperty('bar')); // $ExpectError
assertType<number>(o.decrementProperty('bar')); // $ExpectError
// empty case
assertType<number>(o.incrementProperty()); // $ExpectError
assertType<number>(o.decrementProperty()); // $ExpectError
/**
* toggleProperty
*/
o.toggleProperty('isFoo'); // $ExpectType boolean
o.toggleProperty(); // $ExpectError
/**
* getWithDefault
*/
assertType<string>(o.getWithDefault('foo', 'zzz')); // $ExpectType string
assertType<[boolean, boolean]>(o.getWithDefault('bar', [false, false])); // $ExpectType [boolean, boolean]
assertType<number | undefined>(o.getWithDefault('baz', 10)); // $ExpectType number | undefined
// improper arguments cases
assertType<number | undefined>(o.getWithDefault('baz', '10')); // $ExpectError
assertType<number | undefined>(o.getWithDefault('baz')); // $ExpectError
assertType<number | undefined>(o.getWithDefault()); // $ExpectError
/**
* getProperties
*/
// ('foo', 'bar')
assertType<{ foo: string, bar: [boolean, boolean] }>(o.getProperties('foo', 'bar')); // $ExpectType { foo: string; bar: [boolean, boolean]; }
// ['foo', 'bar']
assertType<{ foo: string, bar: [boolean, boolean] }>(o.getProperties(['foo', 'bar'])); // $ExpectType { foo: string; bar: [boolean, boolean]; }
// empty cases
assertType<{}>(o.getProperties()); // $ExpectType {}
assertType<{}>(o.getProperties([])); // $ExpectType {}
// property that doesn't exist
assertType<any>(o.getProperties('jeanShorts', 'foo')); // $ExpectError
assertType<any>(o.getProperties(['foo', 'jeanShorts'])); // $ExpectError
/**
* set
*/
assertType<string>(o.set('foo', 'abc')); // $ExpectType string
assertType<[boolean, boolean]>(o.set('bar', [false, false])); // $ExpectType [boolean, boolean]
assertType<number | undefined>(o.set('baz', undefined)); // $ExpectType number | undefined
assertType<number | undefined>(o.set('baz', 10)); // $ExpectType number | undefined
// property that doesn't exist
assertType<any>(o.set('jeanShorts', 10)); // $ExpectError
/**
* setProperties
*/
assertType<{ foo: string, bar: [boolean, boolean] }>(o.setProperties({ foo: 'abc', bar: [true, true] })); // $ExpectType { foo: string; bar: [boolean, boolean]; }
// empty case
assertType<{}>(o.setProperties({})); // $ExpectType {}
// property that doesn't exist
assertType<any>(o.setProperties({ jeanShorts: 'under the pants' })); // $ExpectError
/**
* notifyPropertyChange
*/
assertType<DemoObservable>(o.notifyPropertyChange('foo')); // $ExpectType DemoObservable
assertType<Observable>(o.notifyPropertyChange('jeanShorts')); // $ExpectError

View File

@@ -0,0 +1,62 @@
/**
* These tests validate that the method of pulling property types off of this
* continues to work. We use this technique in the critical Observable interface
* that serves to implement a lot of Ember.CoreObject's functionality
*/
class BoxedProperty<Get, Set = Get> {
__getType: Get;
__setType: Set;
}
type UnboxGetProperty<T> = T extends BoxedProperty<infer V, any> ? V : T;
type UnboxSetProperty<T> = T extends BoxedProperty<any, infer V> ? V : T;
class GetAndSet {
get<K extends keyof this>(key: K): UnboxGetProperty<this[K]> {
return this[key] as any;
}
set<K extends keyof this>(key: K, newVal: UnboxSetProperty<this[K]>): UnboxSetProperty<this[K]> {
const rawVal: UnboxSetProperty<this[K]> = this[key] as any;
if (rawVal instanceof BoxedProperty) {
rawVal.__setType = newVal;
}
this[key] = newVal as any;
return newVal;
}
}
class Foo123 extends GetAndSet {
// tslint:disable-next-line:no-inferrable-types
a: number;
b: [boolean, boolean];
c: string;
cpA!: BoxedProperty<string>;
constructor() {
super();
this.a = 1;
this.b = [true, false];
this.c = 'hello';
}
}
let f = new Foo123();
f.get('a'); // $ExpectType number
f.set('a'); // $ExpectError
f.set('a', '1'); // $ExpectError
f.set('a', 1); // $ExpectType number
f.get('b'); // $ExpectType [boolean, boolean]
f.set('b', 1); // $ExpectError
f.set('b', []); // $ExpectError
f.set('b', [true]); // $ExpectError
f.set('b', [false, true]); // $ExpectType [boolean, boolean]
f.set('b', [false, true, false]); // $ExpectError
f.get('c'); // $ExpectType string
f.set('c', '1'); // $ExpectType string
f.get('cpA'); // $ExpectType string
f.set('cpA', ['newValue']); // $ExpectError
f.set('cpA', 'newValue'); // $ExpectType string

View File

@@ -20,7 +20,14 @@
},
"files": [
"index.d.ts",
"-private-types/utils.d.ts",
"-private-types/mixin.d.ts",
"-private-types/object.d.ts",
"-private-types/object/computed.d.ts",
"test/lib/assert.ts",
"test/private/computed-tests.ts",
"test/private/observable-tests.ts",
"test/techniques/properties-from-this.ts",
"test/access-modifier.ts",
"test/application-instance.ts",
"test/application.ts",
@@ -30,9 +37,9 @@
"test/component.ts",
"test/computed.ts",
"test/controller.ts",
"test/core-object.ts",
"test/create-negative.ts",
"test/create.ts",
"test/create.ts",
"test/detect-instance.ts",
"test/detect.ts",
"test/ember-tests.ts",