From 559078d30d0951ba823d0337affcc3349cb412cb Mon Sep 17 00:00:00 2001 From: Mike North Date: Mon, 17 Sep 2018 01:08:12 -0700 Subject: [PATCH] [ember] refactor @ember/object types into their own package (#28835) * [ember] @ember/object types refactored into their own package * Update create-negative.ts --- types/ember__object/computed.d.ts | 38 ++++ types/ember__object/core.d.ts | 3 + types/ember__object/evented.d.ts | 6 + types/ember__object/events.d.ts | 5 + types/ember__object/index.d.ts | 19 ++ types/ember__object/internals.d.ts | 5 + types/ember__object/mixin.d.ts | 3 + types/ember__object/observable.d.ts | 5 + types/ember__object/observers.d.ts | 4 + types/ember__object/promise-proxy-mixin.d.ts | 5 + types/ember__object/proxy.d.ts | 3 + types/ember__object/test/access-modifier.ts | 21 ++ types/ember__object/test/computed.ts | 198 +++++++++++++++++++ types/ember__object/test/core.ts | 198 +++++++++++++++++++ types/ember__object/test/create-negative.ts | 9 + types/ember__object/test/create.ts | 54 +++++ types/ember__object/test/detect-instance.ts | 20 ++ types/ember__object/test/detect.ts | 20 ++ types/ember__object/test/event.ts | 61 ++++++ types/ember__object/test/extend.ts | 61 ++++++ types/ember__object/test/lib/assert.ts | 5 + types/ember__object/test/object.ts | 75 +++++++ types/ember__object/test/observable.ts | 114 +++++++++++ types/ember__object/test/reopen.ts | 72 +++++++ types/ember__object/tsconfig.json | 51 +++++ types/ember__object/tslint.json | 4 + 26 files changed, 1059 insertions(+) create mode 100644 types/ember__object/computed.d.ts create mode 100644 types/ember__object/core.d.ts create mode 100644 types/ember__object/evented.d.ts create mode 100644 types/ember__object/events.d.ts create mode 100644 types/ember__object/index.d.ts create mode 100644 types/ember__object/internals.d.ts create mode 100644 types/ember__object/mixin.d.ts create mode 100644 types/ember__object/observable.d.ts create mode 100644 types/ember__object/observers.d.ts create mode 100644 types/ember__object/promise-proxy-mixin.d.ts create mode 100644 types/ember__object/proxy.d.ts create mode 100644 types/ember__object/test/access-modifier.ts create mode 100644 types/ember__object/test/computed.ts create mode 100644 types/ember__object/test/core.ts create mode 100644 types/ember__object/test/create-negative.ts create mode 100644 types/ember__object/test/create.ts create mode 100755 types/ember__object/test/detect-instance.ts create mode 100755 types/ember__object/test/detect.ts create mode 100644 types/ember__object/test/event.ts create mode 100644 types/ember__object/test/extend.ts create mode 100644 types/ember__object/test/lib/assert.ts create mode 100644 types/ember__object/test/object.ts create mode 100644 types/ember__object/test/observable.ts create mode 100644 types/ember__object/test/reopen.ts create mode 100644 types/ember__object/tsconfig.json create mode 100644 types/ember__object/tslint.json diff --git a/types/ember__object/computed.d.ts b/types/ember__object/computed.d.ts new file mode 100644 index 0000000000..cbb68662cd --- /dev/null +++ b/types/ember__object/computed.d.ts @@ -0,0 +1,38 @@ +import Ember from 'ember'; + +type ComputedProperty = Ember.ComputedProperty; +declare const ComputedProperty: typeof Ember.ComputedProperty; +export default ComputedProperty; +export const alias: typeof Ember.computed.alias; +export const and: typeof Ember.computed.and; +export const bool: typeof Ember.computed.bool; +export const collect: typeof Ember.computed.collect; +export const deprecatingAlias: typeof Ember.computed.deprecatingAlias; +export const empty: typeof Ember.computed.empty; +export const equal: typeof Ember.computed.equal; +export const expandProperties: typeof Ember.expandProperties; +export const filter: typeof Ember.computed.filter; +export const filterBy: typeof Ember.computed.filterBy; +export const gt: typeof Ember.computed.gt; +export const gte: typeof Ember.computed.gte; +export const intersect: typeof Ember.computed.intersect; +export const lt: typeof Ember.computed.lt; +export const lte: typeof Ember.computed.lte; +export const map: typeof Ember.computed.map; +export const mapBy: typeof Ember.computed.mapBy; +export const match: typeof Ember.computed.match; +export const max: typeof Ember.computed.max; +export const min: typeof Ember.computed.min; +export const none: typeof Ember.computed.none; +export const not: typeof Ember.computed.not; +export const notEmpty: typeof Ember.computed.notEmpty; +export const oneWay: typeof Ember.computed.oneWay; +export const or: typeof Ember.computed.or; +export const readOnly: typeof Ember.computed.readOnly; +export const reads: typeof Ember.computed.reads; +export const setDiff: typeof Ember.computed.setDiff; +export const sort: typeof Ember.computed.sort; +export const sum: typeof Ember.computed.sum; +export const union: typeof Ember.computed.union; +export const uniq: typeof Ember.computed.uniq; +export const uniqBy: typeof Ember.computed.uniqBy; diff --git a/types/ember__object/core.d.ts b/types/ember__object/core.d.ts new file mode 100644 index 0000000000..9b2d396d6e --- /dev/null +++ b/types/ember__object/core.d.ts @@ -0,0 +1,3 @@ +import Ember from 'ember'; + +export default class CoreObject extends Ember.CoreObject { } diff --git a/types/ember__object/evented.d.ts b/types/ember__object/evented.d.ts new file mode 100644 index 0000000000..865cab6fa8 --- /dev/null +++ b/types/ember__object/evented.d.ts @@ -0,0 +1,6 @@ +import Ember from 'ember'; + +type Evented = Ember.Evented; +declare const Evented: typeof Ember.Evented; +export default Evented; +export const on: typeof Ember.on; diff --git a/types/ember__object/events.d.ts b/types/ember__object/events.d.ts new file mode 100644 index 0000000000..d0413d5e4b --- /dev/null +++ b/types/ember__object/events.d.ts @@ -0,0 +1,5 @@ +import Ember from 'ember'; + +export const addListener: typeof Ember.addListener; +export const removeListener: typeof Ember.removeListener; +export const sendEvent: typeof Ember.sendEvent; diff --git a/types/ember__object/index.d.ts b/types/ember__object/index.d.ts new file mode 100644 index 0000000000..4633ff534b --- /dev/null +++ b/types/ember__object/index.d.ts @@ -0,0 +1,19 @@ +// Type definitions for @ember/object 3.0 +// Project: http://emberjs.com/ +// Definitions by: Mike North +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.8 + +import Ember from 'ember'; +declare class EmberObject extends Ember.Object { } +export default EmberObject; +export const aliasMethod: typeof Ember.aliasMethod; +export const computed: typeof Ember.computed; +export const defineProperty: typeof Ember.defineProperty; +export const get: typeof Ember.get; +export const getProperties: typeof Ember.getProperties; +export const getWithDefault: typeof Ember.getWithDefault; +export const observer: typeof Ember.observer; +export const set: typeof Ember.set; +export const setProperties: typeof Ember.setProperties; +export const trySet: typeof Ember.trySet; diff --git a/types/ember__object/internals.d.ts b/types/ember__object/internals.d.ts new file mode 100644 index 0000000000..698b6e0b87 --- /dev/null +++ b/types/ember__object/internals.d.ts @@ -0,0 +1,5 @@ +import Ember from 'ember'; + +export const cacheFor: typeof Ember.cacheFor; +export const copy: typeof Ember.copy; +export const guidFor: typeof Ember.guidFor; diff --git a/types/ember__object/mixin.d.ts b/types/ember__object/mixin.d.ts new file mode 100644 index 0000000000..efb03d3994 --- /dev/null +++ b/types/ember__object/mixin.d.ts @@ -0,0 +1,3 @@ +import Ember from 'ember'; + +export default class Mixin extends Ember.Mixin {} diff --git a/types/ember__object/observable.d.ts b/types/ember__object/observable.d.ts new file mode 100644 index 0000000000..af87192228 --- /dev/null +++ b/types/ember__object/observable.d.ts @@ -0,0 +1,5 @@ +import Ember from 'ember'; + +type Observable = Ember.Observable; +declare const Observable: typeof Ember.Observable; +export default Observable; diff --git a/types/ember__object/observers.d.ts b/types/ember__object/observers.d.ts new file mode 100644 index 0000000000..e084f2da29 --- /dev/null +++ b/types/ember__object/observers.d.ts @@ -0,0 +1,4 @@ +import Ember from 'ember'; + +export const addObserver: typeof Ember.addObserver; +export const removeObserver: typeof Ember.removeObserver; diff --git a/types/ember__object/promise-proxy-mixin.d.ts b/types/ember__object/promise-proxy-mixin.d.ts new file mode 100644 index 0000000000..9ac285aa11 --- /dev/null +++ b/types/ember__object/promise-proxy-mixin.d.ts @@ -0,0 +1,5 @@ +import Ember from 'ember'; + +type PromiseProxyMixin = Ember.PromiseProxyMixin; +declare const PromiseProxyMixin: typeof Ember.PromiseProxyMixin; +export default PromiseProxyMixin; diff --git a/types/ember__object/proxy.d.ts b/types/ember__object/proxy.d.ts new file mode 100644 index 0000000000..d7eeb28ca6 --- /dev/null +++ b/types/ember__object/proxy.d.ts @@ -0,0 +1,3 @@ +import Ember from 'ember'; + +export default class ObjectProxy extends Ember.ObjectProxy { } diff --git a/types/ember__object/test/access-modifier.ts b/types/ember__object/test/access-modifier.ts new file mode 100644 index 0000000000..281d4e5624 --- /dev/null +++ b/types/ember__object/test/access-modifier.ts @@ -0,0 +1,21 @@ +import { assertType } from './lib/assert'; +import EmberObject from '@ember/object'; + +class Foo extends EmberObject { + hello() { return 'world'; } + protected bar() { return 'bar'; } + private baz() { return 'baz'; } +} +const f = new Foo(); +assertType(f.hello()); +assertType(f.bar()); // $ExpectError +assertType(f.baz()); // $ExpectError + +// TODO: enable after TS 3.0 https://github.com/typed-ember/ember-cli-typescript/issues/291 +// class Foo2 extends EmberObject.extend({ +// bar: '' +// }) { +// hello() { return 'world'; } +// protected bar() { return 'bar'; } // $ExpectError +// private baz() { return 'baz'; } +// } diff --git a/types/ember__object/test/computed.ts b/types/ember__object/test/computed.ts new file mode 100644 index 0000000000..481a513856 --- /dev/null +++ b/types/ember__object/test/computed.ts @@ -0,0 +1,198 @@ +import EmberObject, { computed } from '@ember/object'; +import ComputedProperty, { + alias, + or, + and, + filter, + equal, + empty, + filterBy, + notEmpty, + none, + not, + min, + max, + gt, + gte, + lt, + lte, + readOnly, + reads, + setDiff, + sort, + intersect, + mapBy, + match, + map, + oneWay, + sum, + union, + uniqBy, + uniq, + deprecatingAlias, + bool, + collect +} from '@ember/object/computed'; +import { assertType } from './lib/assert'; + +const Person = EmberObject.extend({ + firstName: '', + lastName: '', + age: 0, + + noArgs: computed(() => 'test'), + + fullName: computed('firstName', 'lastName', function() { + return `${this.get('firstName')} ${this.get('lastName')}`; + }), + + fullNameReadonly: computed('fullName', function() { + return this.get('fullName'); + }).readOnly(), + + fullNameWritable: computed('firstName', 'lastName', { + get() { + return this.get('fullName'); + }, + set(key, value) { + const [first, last] = value.split(' '); + this.set('firstName', first); + this.set('lastName', last); + return value; + } + }), + + fullNameGetOnly: computed('fullName', { + get() { + return this.get('fullName'); + } + }), + + fullNameSetOnly: computed('firstName', 'lastName', { + set(key, value) { + const [first, last] = value.split(' '); + this.set('firstName', first); + this.set('lastName', last); + return value; + } + }), + + combinators: computed(function() { + return this.get('firstName'); + }).property('firstName') + .meta({ foo: 'bar' }) + .volatile() + .readOnly(), + + explicitlyDeclared: alias('fullName') as ComputedProperty, +}); + +const person = Person.create({ + firstName: 'Fred', + lastName: 'Smith', + age: 29, +}); + +assertType(person.firstName); +assertType(person.age); +assertType>(person.noArgs); +assertType>(person.fullName); +assertType>(person.fullNameReadonly); +assertType>(person.fullNameWritable); +assertType>(person.fullNameGetOnly); +assertType>(person.fullNameSetOnly); +assertType>(person.combinators); +assertType>(person.explicitlyDeclared); + +assertType(person.get('firstName')); +assertType(person.get('age')); +assertType(person.get('noArgs')); +assertType(person.get('fullName')); +assertType(person.get('fullNameReadonly')); +assertType(person.get('fullNameWritable')); +assertType(person.get('fullNameGetOnly')); +assertType(person.get('fullNameSetOnly')); +assertType(person.get('combinators')); +assertType(person.get('explicitlyDeclared')); + +assertType<{ firstName: string, fullName: string, age: number }>(person.getProperties('firstName', 'fullName', 'age')); + +const person2 = Person.create({ + fullName: 'Fred Smith' +}); + +assertType(person2.get('firstName')); +assertType(person2.get('fullName')); + +const person3 = Person.extend({ + firstName: 'Fred', + fullName: 'Fred Smith' +}).create(); + +assertType(person3.get('firstName')); +assertType(person3.get('fullName')); + +const person4 = Person.extend({ + firstName: computed(() => 'Fred'), + fullName: computed(() => 'Fred Smith') +}).create(); + +assertType(person4.get('firstName')); +assertType(person4.get('fullName')); + +// computed property macros +const objectWithComputedProperties = EmberObject.extend({ + alias: alias('foo'), + and: and('foo', 'bar', 'baz', 'qux'), + bool: bool('foo'), + collect: collect('foo', 'bar', 'baz', 'qux'), + deprecatingAlias: deprecatingAlias('foo', { + id: 'hamster.deprecate-banana', + until: '3.0.0' + }), + empty: empty('foo'), + equalNumber: equal('foo', 1), + equalString: equal('foo', 'bar'), + equalObject: equal('foo', {}), + filter: filter('foo', (item) => item === 'bar'), + filterBy1: filterBy('foo', 'bar'), + filterBy2: filterBy('foo', 'bar', false), + gt: gt('foo', 3), + gte: gte('foo', 3), + intersect: intersect('foo', 'bar', 'baz', 'qux'), + lt: lt('foo', 3), + lte: lte('foo', 3), + map: map('foo', (item, index) => item.bar), + mapBy: mapBy('foo', 'bar'), + match: match('foo', /^tom.ter$/), + max: max('foo'), + min: min('foo'), + none: none('foo'), + not: not('foo'), + notEmpty: notEmpty('foo'), + oneWay: oneWay('foo'), + or: or('foo', 'bar', 'baz', 'qux'), + readOnly: readOnly('foo'), + reads: reads('foo'), + setDiff: setDiff('foo', 'bar'), + sort1: sort('foo', 'bar'), + sort2: sort('foo', (itemA, itemB) => { + if (itemA < itemB) { + return -1; + } else if (itemA > itemB) { + return 1; + } else { + return 0; + } + }), + sum: sum('foo'), + union: union('foo', 'bar', 'baz', 'qux'), + uniq: uniq('foo'), + uniqBy: uniqBy('foo', 'bar') +}); + +const component2 = EmberObject.extend({ + isAnimal: or('isDog', 'isCat') +}).create(); + +assertType(component2.get('isAnimal')); diff --git a/types/ember__object/test/core.ts b/types/ember__object/test/core.ts new file mode 100644 index 0000000000..984f5eec12 --- /dev/null +++ b/types/ember__object/test/core.ts @@ -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(c05b.foo); // $ExpectError +assertType(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(co6.foo); // $ExpectError +assertType(co6.bar); // $ExpectType number +assertType<() => Array>(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(co7.foo); // $ExpectError +assertType(co7.bar); // $ExpectType number +assertType(co7.money); // $ExpectType string +assertType<() => Array>(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(co8.foo); // $ExpectError +assertType(co8.bar); // $ExpectType number +assertType(co8.money); // $ExpectType string +assertType<() => Array>(co8.baz); // $ExpectType () => (string | number)[] diff --git a/types/ember__object/test/create-negative.ts b/types/ember__object/test/create-negative.ts new file mode 100644 index 0000000000..2298ae8cf3 --- /dev/null +++ b/types/ember__object/test/create-negative.ts @@ -0,0 +1,9 @@ +import { assertType } from './lib/assert'; +import Ember from 'ember'; +import { PersonWithNumberName, Person } from './create'; + +Person.create({ firstName: 99 }); // $ExpectError +Person.create({}, { firstName: 99 }); // $ExpectError +Person.create({}, {}, { firstName: 99 }); // $ExpectError + +const p4 = new PersonWithNumberName(); diff --git a/types/ember__object/test/create.ts b/types/ember__object/test/create.ts new file mode 100644 index 0000000000..fbc1b3600a --- /dev/null +++ b/types/ember__object/test/create.ts @@ -0,0 +1,54 @@ +import { assertType } from './lib/assert'; +import EmberObject, { computed } from '@ember/object'; +import ComputedProperty from '@ember/object/computed'; + +/** + * Zero-argument case + */ +const o = EmberObject.create(); +// create returns an object +assertType(o); +// object returned by create type-checks as an instance of Ember.Object +assertType(o.isDestroyed); // from instance +assertType(o.isDestroying); // from instance +assertType<(key: keyof EmberObject) => any>(o.get); // from prototype + +/** + * One-argument case + */ +const o1 = EmberObject.create({x: 9, y: 'hello', z: false}); +assertType(o1.x); +assertType(o1.y); +o1.y; // $ExpectType string +o1.z; // $ExpectType boolean + +const obj = EmberObject.create({ a: 1 }, { b: 2 }, { c: 3 }); +assertType(obj.b); +assertType(obj.a); +assertType(obj.c); + +export class Person extends EmberObject { + fullName = computed('firstName', 'lastName', function() { + return [this.firstName + this.lastName].join(' '); + }); + firstName: string; + lastName: string; + age: number; +} +const p = new Person(); + +assertType(p.firstName); +assertType>(p.fullName); +assertType(p.get('fullName')); + +const p2 = Person.create({ firstName: 'string' }); +const p2b = Person.create({}, { firstName: 'string' }); +const p2c = Person.create({}, {}, { firstName: 'string' }); + +export class PersonWithNumberName extends Person.extend({ + fullName: 6 +}) {} + +const p4 = new PersonWithNumberName(); +assertType(p4.firstName); +assertType(p4.fullName); diff --git a/types/ember__object/test/detect-instance.ts b/types/ember__object/test/detect-instance.ts new file mode 100755 index 0000000000..d65fa0f686 --- /dev/null +++ b/types/ember__object/test/detect-instance.ts @@ -0,0 +1,20 @@ +import { assertType } from './lib/assert'; +import EmberObject from '@ember/object'; + +const ExtendClass = EmberObject.extend({ + foo: 'hello' +}); + +class ES6Class extends EmberObject { + bar: string; +} + +const testObject = null; + +if (ExtendClass.detectInstance(testObject)) { + assertType(testObject.foo); +} + +if (ES6Class.detectInstance(testObject)) { + assertType(testObject.bar); +} diff --git a/types/ember__object/test/detect.ts b/types/ember__object/test/detect.ts new file mode 100755 index 0000000000..878acced29 --- /dev/null +++ b/types/ember__object/test/detect.ts @@ -0,0 +1,20 @@ +import { assertType } from './lib/assert'; +import EmberObject from '@ember/object'; + +const ExtendClass = EmberObject.extend({ + foo: 'hello' +}); + +class ES6Class extends EmberObject { + bar: string; +} + +const TestClass = EmberObject; + +if (ExtendClass.detect(TestClass)) { + assertType(TestClass.create().foo); +} + +if (ES6Class.detect(TestClass)) { + assertType(TestClass.create().bar); +} diff --git a/types/ember__object/test/event.ts b/types/ember__object/test/event.ts new file mode 100644 index 0000000000..b54529d587 --- /dev/null +++ b/types/ember__object/test/event.ts @@ -0,0 +1,61 @@ +import EmberObject, { observer } from '@ember/object'; +import { sendEvent, addListener, removeListener } from '@ember/object/events'; +import Evented, { on } from '@ember/object/evented'; + +function testOn() { + const Job = EmberObject.extend({ + logCompleted: on('completed', () => { + console.log('Job completed!'); + }) + }); + + const job = Job.create(); + + sendEvent(job, 'completed'); // Logs 'Job completed!' +} + +function testEvented() { + const Person = EmberObject.extend(Evented, { + greet() { + this.trigger('greet'); + } + }); + + const person = Person.create(); + + person.on('greet', () => { + console.log('Our person has greeted'); + }); + + person.on('greet', () => { + console.log('Our person has greeted'); + }).one('greet', () => { + console.log('Offer one-time special'); + }).off('event', {}, () => {}); + + person.greet(); +} + +function testObserver() { + EmberObject.extend({ + // TODO: enable after https://github.com/typed-ember/ember-cli-typescript/issues/290 + // valueObserver: observer('value', () => { + // // Executes whenever the "value" property changes + // }) + }); +} + +function testListener() { + EmberObject.extend({ + init() { + addListener(this, 'willDestroy', this, 'willDestroyListener'); + addListener(this, 'willDestroy', this, 'willDestroyListener', true); + addListener(this, 'willDestroy', this, this.willDestroyListener); + addListener(this, 'willDestroy', this, this.willDestroyListener, true); + removeListener(this, 'willDestroy', this, 'willDestroyListener'); + removeListener(this, 'willDestroy', this, this.willDestroyListener); + }, + willDestroyListener() { + } + }); +} diff --git a/types/ember__object/test/extend.ts b/types/ember__object/test/extend.ts new file mode 100644 index 0000000000..8d61168dc3 --- /dev/null +++ b/types/ember__object/test/extend.ts @@ -0,0 +1,61 @@ +import EmberObject from '@ember/object'; +import { assertType } from './lib/assert'; + +const Person = EmberObject.extend({ + firstName: '', + lastName: '', + + getFullName() { + return `${this.firstName} ${this.lastName}`; + }, + getFullName2(): string { + return `${this.get('firstName')} ${this.get('lastName')}`; + } +}); + +assertType(Person.prototype.firstName); +assertType<() => string>(Person.prototype.getFullName); + +const person = Person.create({ + firstName: 'Joe', + lastName: 'Blow', + extra: 42 +}); + +assertType(person.getFullName()); +assertType(person.extra); + +class ES6Person extends EmberObject { + firstName: string; + lastName: string; + + get fullName() { + return `${this.firstName} ${this.lastName}`; + } + get fullName2(): string { + return `${this.get('firstName')} ${this.get('lastName')}`; + } +} + +assertType(ES6Person.prototype.firstName); +assertType(ES6Person.prototype.fullName); + +const es6Person = ES6Person.create({ + firstName: 'Joe', + lastName: 'Blow', + extra: 42 +}); + +assertType(es6Person.fullName); +assertType(es6Person.extra); + +class PersonWithStatics extends EmberObject { + static isPerson = true; +} +const PersonWithStatics2 = PersonWithStatics.extend({}); +class PersonWithStatics3 extends PersonWithStatics {} +class PersonWithStatics4 extends PersonWithStatics2 {} +assertType(PersonWithStatics.isPerson); +assertType(PersonWithStatics2.isPerson); +assertType(PersonWithStatics3.isPerson); +assertType(PersonWithStatics4.isPerson); diff --git a/types/ember__object/test/lib/assert.ts b/types/ember__object/test/lib/assert.ts new file mode 100644 index 0000000000..262d18bc2c --- /dev/null +++ b/types/ember__object/test/lib/assert.ts @@ -0,0 +1,5 @@ +/** Static assertion that `value` has type `T` */ +// 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(value: T): T; diff --git a/types/ember__object/test/object.ts b/types/ember__object/test/object.ts new file mode 100644 index 0000000000..b47aa22ffb --- /dev/null +++ b/types/ember__object/test/object.ts @@ -0,0 +1,75 @@ +import EmberObject, { computed } from "@ember/object"; + +const LifetimeHooks = EmberObject.extend({ + resource: null as {} | null, + + init() { + this._super(); + this.resource = {}; + }, + + willDestroy() { + delete this.resource; + this._super(); + } +}); + +class MyObject30 extends EmberObject { + constructor() { + super(); + } +} + +class MyObject31 extends EmberObject { + constructor(properties: object) { + super(properties); + } +} + +class Foo extends EmberObject { + a = computed({ + get() { return ''; }, + set(key: string, newVal: string) { return ''; } + }); + b = 5; + baz() { + const y = this.b; // $ExpectType number + const z = this.a; // $ExpectType ComputedProperty + this.b = 10; + this.get('b').toFixed(4); // $ExpectType string + this.set('a', 'abc').split(','); // $ExpectType string[] + this.set('b', 10).toFixed(4); // $ExpectType string + + this.setProperties({ b: 11 }); + // this.setProperties({ b: '11' }); // $ExpectError + this.setProperties({ + a: 'def', + b: 11 + }); + } +} + +// TODO: enable after TS 3.0 https://github.com/typed-ember/ember-cli-typescript/issues/291 +// class Foo extends EmberObject.extend({ +// a: computed({ +// get() { return ''; }, +// set(key: string, newVal: string) { return ''; } +// }) +// }) { +// b = 5; +// baz() { +// const y = this.b; // $ExpectType number +// const z = this.a; // $ExpectType ComputedProperty +// this.b = 10; +// this.get('b').toFixed(4); // $ExpectType string +// this.set('a', 'abc').split(','); // $ExpectType string[] +// this.set('b', 10).toFixed(4); // $ExpectType string + +// this.setProperties({ b: 11 }); +// // this.setProperties({ b: '11' }); // $ExpectError +// this.setProperties({ +// a: 'def', +// b: 11 +// }); +// } +// } diff --git a/types/ember__object/test/observable.ts b/types/ember__object/test/observable.ts new file mode 100644 index 0000000000..baa8e3d09a --- /dev/null +++ b/types/ember__object/test/observable.ts @@ -0,0 +1,114 @@ +import { assertType } from './lib/assert'; +import EmberObject, { computed, getWithDefault, getProperties, get, setProperties, set } from '@ember/object'; +import { removeObserver, addObserver } from '@ember/object/observers'; + +class MyComponent extends EmberObject { + foo = 'bar'; + + init() { + this._super.apply(this, arguments); + this.addObserver('foo', this, 'fooDidChange'); + this.addObserver('foo', this, this.fooDidChange); + addObserver(this, 'foo', this, 'fooDidChange'); + addObserver(this, 'foo', this, this.fooDidChange); + this.removeObserver('foo', this, 'fooDidChange'); + this.removeObserver('foo', this, this.fooDidChange); + removeObserver(this, 'foo', this, 'fooDidChange'); + removeObserver(this, 'foo', this, this.fooDidChange); + const lambda = () => { + this.fooDidChange(this, 'foo'); + }; + this.addObserver('foo', lambda); + this.removeObserver('foo', lambda); + addObserver(this, 'foo', lambda); + removeObserver(this, 'foo', lambda); + } + + fooDidChange(sender: this, key: keyof this) { + // your code + } +} + +const myComponent = MyComponent.create(); +myComponent.addObserver('foo', null, () => {}); +myComponent.set('foo', 'baz'); + +const person = EmberObject.create({ + name: 'Fred', + age: 29, + capitalized: computed(function() { + return this.get('name').toUpperCase(); + }) +}); + +const pojo = { name: 'Fred', age: 29 }; + +function testGet() { + assertType(get(person, 'name')); + assertType(get(person, 'age')); + assertType(get(person, 'capitalized')); + assertType(person.get('name')); + assertType(person.get('age')); + assertType(person.get('capitalized')); + assertType(get(pojo, 'name')); +} + +function testGetProperties() { + assertType<{ name: string }>(getProperties(person, 'name')); + assertType<{ name: string, age: number }>(getProperties(person, 'name', 'age')); + assertType<{ name: string, age: number }>(getProperties(person, [ 'name', 'age' ])); + assertType<{ name: string, age: number, capitalized: string }>(getProperties(person, 'name', 'age', 'capitalized')); + assertType<{ name: string }>(person.getProperties('name')); + assertType<{ name: string, age: number }>(person.getProperties('name', 'age')); + assertType<{ name: string, age: number }>(person.getProperties([ 'name', 'age' ])); + assertType<{ name: string, age: number, capitalized: string }>(person.getProperties('name', 'age', 'capitalized')); + assertType<{ name: string, age: number }>(getProperties(pojo, 'name', 'age')); +} + +function testGetWithDefault() { + assertType(getWithDefault(person, 'name', 'Joe')); + assertType(getWithDefault(person, 'age', 20)); + assertType(getWithDefault(person, 'capitalized', 'JOE')); + assertType(person.getWithDefault('name', 'Joe')); + assertType(person.getWithDefault('age', 20)); + assertType(person.getWithDefault('capitalized', 'JOE')); + assertType(getWithDefault(pojo, 'name', 'JOE')); +} + +function testSet() { + assertType(set(person, 'name', 'Joe')); + assertType(set(person, 'age', 35)); + assertType(set(person, 'capitalized', 'JOE')); + assertType(person.set('name', 'Joe')); + assertType(person.set('age', 35)); + assertType(person.set('capitalized', 'JOE')); + assertType(set(pojo, 'name', 'Joe')); +} + +function testSetProperties() { + assertType<{ name: string }>(setProperties(person, { name: 'Joe' })); + assertType<{ name: string, age: number }>(setProperties(person, { name: 'Joe', age: 35 })); + assertType<{ name: string, capitalized: string }>(setProperties(person, { name: 'Joe', capitalized: 'JOE' })); + assertType<{ name: string }>(person.setProperties({ name: 'Joe' })); + assertType<{ name: string, age: number }>(person.setProperties({ name: 'Joe', age: 35 })); + assertType<{ name: string, capitalized: string }>(person.setProperties({ name: 'Joe', capitalized: 'JOE' })); + assertType<{ name: string, age: number }>(setProperties(pojo, { name: 'Joe', age: 35 })); +} + +function testDynamic() { + const obj: any = {}; + const dynamicKey: string = 'dummy'; // tslint:disable-line:no-inferrable-types + + assertType(get(obj, 'dummy')); + assertType(get(obj, dynamicKey)); + assertType(getWithDefault(obj, 'dummy', 'default')); + assertType(getWithDefault(obj, dynamicKey, 'default')); + assertType<{ dummy: any }>(getProperties(obj, 'dummy')); + assertType<{ dummy: any }>(getProperties(obj, [ 'dummy' ])); + assertType(getProperties(obj, dynamicKey)); + assertType(getProperties(obj, [ dynamicKey ])); + assertType(set(obj, 'dummy', 'value')); + assertType(set(obj, dynamicKey, 'value')); + assertType<{ dummy: string }>(setProperties(obj, { dummy: 'value '})); + assertType(setProperties(obj, { [dynamicKey]: 'value' })); +} diff --git a/types/ember__object/test/reopen.ts b/types/ember__object/test/reopen.ts new file mode 100644 index 0000000000..13084c4bce --- /dev/null +++ b/types/ember__object/test/reopen.ts @@ -0,0 +1,72 @@ +import { assertType } from "./lib/assert"; +import EmberObject from "@ember/object"; +import Mixin from "@ember/object/mixin"; + +type Person = typeof Person.prototype; +const Person = EmberObject.extend({ + name: '', + sayHello() { + alert(`Hello. My name is ${this.get('name')}`); + } +}); + +assertType(Person.reopen()); + +assertType(Person.create().name); +// tslint:disable-next-line no-void-expression +assertType(Person.create().sayHello()); + +const Person2 = Person.reopenClass({ + species: 'Homo sapiens', + + createPerson(name: string): Person { + return Person.create({ name }); + } +}); + +assertType(Person2.create().name); +// tslint:disable-next-line no-void-expression +assertType(Person2.create().sayHello()); +assertType(Person2.species); + +const tom = Person2.create({ + name: 'Tom Dale' +}); + +const badTom = Person2.create({ name: 99 }); // $ExpectError + +const yehuda = Person2.createPerson('Yehuda Katz'); + +tom.sayHello(); // "Hello. My name is Tom Dale" +yehuda.sayHello(); // "Hello. My name is Yehuda Katz" +alert(Person2.species); // "Homo sapiens" + +const Person3 = Person2.reopen({ + goodbyeMessage: 'goodbye', + + sayGoodbye() { + alert(`${this.get('goodbyeMessage')}, ${this.get('name')}`); + } +}); + +const person3 = Person3.create(); +person3.get('name'); +person3.get('goodbyeMessage'); +person3.sayHello(); +person3.sayGoodbye(); + +interface AutoResizeMixin { resizable: true; } +declare const AutoResizeMixin: Mixin; + +const ResizableTextArea = EmberObject.reopen(AutoResizeMixin, { + scaling: 1.0 +}); +const text = ResizableTextArea.create(); +// TODO fix upstream +// assertType(text.resizable); +assertType(text.scaling); + +const Reopened = EmberObject.reopenClass({ a: 1 }, { b: 2 }, { c: 3 }); +assertType(Reopened.a); +assertType(Reopened.b); +assertType(Reopened.c); diff --git a/types/ember__object/tsconfig.json b/types/ember__object/tsconfig.json new file mode 100644 index 0000000000..e8f6e13c92 --- /dev/null +++ b/types/ember__object/tsconfig.json @@ -0,0 +1,51 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "paths": { + "@ember/object": ["ember__object"], + "@ember/object/*": ["ember__object/*"] + }, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "computed.d.ts", + "core.d.ts", + "evented.d.ts", + "events.d.ts", + "index.d.ts", + "internals.d.ts", + "mixin.d.ts", + "observable.d.ts", + "observers.d.ts", + "promise-proxy-mixin.d.ts", + "proxy.d.ts", + "test/lib/assert.ts", + "test/access-modifier.ts", + "test/core.ts", + "test/computed.ts", + "test/create.ts", + "test/create-negative.ts", + "test/detect.ts", + "test/detect-instance.ts", + "test/event.ts", + "test/extend.ts", + "test/object.ts", + "test/observable.ts", + "test/reopen.ts" + ] +} diff --git a/types/ember__object/tslint.json b/types/ember__object/tslint.json new file mode 100644 index 0000000000..1a60b8a5dd --- /dev/null +++ b/types/ember__object/tslint.json @@ -0,0 +1,4 @@ +{ + "extends": "dtslint/dt.json", + "rules": { } +}