Skip to content

Commit

Permalink
refactor: convert to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
buschtoens committed Nov 29, 2018
1 parent fb4e95e commit 4f274ad
Show file tree
Hide file tree
Showing 27 changed files with 284 additions and 84 deletions.
File renamed without changes.
23 changes: 0 additions & 23 deletions addon/intl.js

This file was deleted.

30 changes: 30 additions & 0 deletions addon/intl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { decoratorWithParams } from '@ember-decorators/utils/decorator';
import { computedDecorator } from '@ember-decorators/utils/computed';
import { assert } from '@ember/debug';
import extractValue from './utils/extract-value';
import { IntlComputedProperty } from 'ember-intl';
import IntlService from 'ember-intl/services/intl';
import { Descriptor } from '@ember-decorators/utils/decorator';

export default decoratorWithParams(
(desc: Descriptor, dependentKeys: string[] = []) => {
const { initializer } = desc;
delete desc.initializer;

return computedDecorator(
desc =>
new IntlComputedProperty<object>(...dependentKeys, function(
intl: IntlService,
propertyKey: string
) {
const fn = extractValue({ ...desc, initializer }, this);
assert(
`@intl: You need to decorate a function, but you decorated '${fn}'.`,
typeof fn === 'function'
);

return fn.call(this, intl, propertyKey);
})
)(desc);
}
);
4 changes: 0 additions & 4 deletions addon/t.js

This file was deleted.

8 changes: 8 additions & 0 deletions addon/t.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { macro } from '@ember-decorators/object/computed';
import { translationMacro } from 'ember-intl';

const t: (key: string, options?: object) => PropertyDecorator = macro(
translationMacro
);

export default t;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default function extractValue(desc, ctx = null) {
export default function extractValue(desc: any, ctx: any = null) {
if ('value' in desc.descriptor) {
return desc.descriptor.value;
}
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
20 changes: 20 additions & 0 deletions tests/helper/setup-container-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import EmberObject from '@ember/object';
import { setOwner } from '@ember/application';
import TestContext from '../test-context';

export default function setupContainerObject(hooks: NestedHooks) {
// @TODO: re-enable once TypeScript parser works properly
// eslint-disable-next-line no-unused-vars
hooks.beforeEach(function(this: TestContext) {
const { owner } = this;

class ContainerObject extends EmberObject {
constructor() {
super();
setOwner(this, owner);
}
}

this.ContainerObject = ContainerObject;
});
}
6 changes: 6 additions & 0 deletions tests/test-context.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import EmberObject from '@ember/object';
import { TestContext as BaseTextContext } from 'ember-intl/test-support';

export default interface TestContext extends BaseTextContext {
ContainerObject: { new (): EmberObject };
}
47 changes: 21 additions & 26 deletions tests/unit/intl-test.js → tests/unit/intl-test.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,35 @@
import { setOwner } from '@ember/application';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import EmberObject, { get, set } from '@ember/object';
import { get, set } from '@ember/object';
import { run } from '@ember/runloop';
import { setupIntl } from 'ember-intl/test-support';
import { intl } from '@ember-intl/decorators';
import TestContext from '../test-context';
import setupContainerObject from '../helper/setup-container-object';
import IntlService from 'ember-intl/services/intl';

module('Unit | @intl', function(hooks) {
setupTest(hooks);
setupIntl(hooks, {
'no.interpolations': 'text with no interpolations',
'with.interpolations': 'Clicks: {clicks}'
});
setupContainerObject(hooks);

hooks.beforeEach(function() {
this.intl = this.owner.lookup('service:intl');

const { owner } = this;
this.ContainerObject = class extends EmberObject {
constructor() {
super();
setOwner(this, owner);
}
};
});

test('basic functionality', function(assert) {
const object = new class extends this.ContainerObject {
test('basic functionality', function(this: TestContext, assert) {
class TestObject extends this.ContainerObject {
amount = 1.23;
currency = 'EUR';

@intl('amount', 'currency')
formatted = intl =>
// @ts-ignore
formatted: string = (intl: IntlService) =>
intl.formatNumber(this.amount, {
style: 'currency',
currency: this.currency
});
}();
}
const object = new TestObject();

assert.strictEqual(
get(object, 'formatted'),
Expand Down Expand Up @@ -66,22 +59,23 @@ module('Unit | @intl', function(hooks) {
);
});

test('accepts arrow functions', function(assert) {
test('accepts arrow functions', function(this: TestContext, assert) {
assert.expect(4);

const intlService = this.intl;
const IDENTITY = {};

const object = new class extends this.ContainerObject {
class TestObject extends this.ContainerObject {
@intl
formatted = (i, propertyKey) => {
formatted = (i: IntlService, propertyKey: string) => {
assert.strictEqual(i, intlService, 'intl service is passed');
assert.strictEqual(propertyKey, 'formatted', 'propertyKey is passed');
assert.strictEqual(this, object, 'this context is correct');

return IDENTITY;
};
}();
}
const object = new TestObject();

assert.strictEqual(
get(object, 'formatted'),
Expand All @@ -90,22 +84,23 @@ module('Unit | @intl', function(hooks) {
);
});

test('accepts methods', function(assert) {
test('accepts methods', function(this: TestContext, assert) {
assert.expect(4);

const intlService = this.intl;
const IDENTITY = {};

const object = new class extends this.ContainerObject {
class TestObject extends this.ContainerObject {
@intl
formatted(i, propertyKey) {
formatted(i: IntlService, propertyKey: string) {
assert.strictEqual(i, intlService, 'intl service is passed');
assert.strictEqual(propertyKey, 'formatted', 'propertyKey is passed');
assert.strictEqual(this, object, 'this context is correct');

return IDENTITY;
}
}();
}
const object = new TestObject();

assert.strictEqual(
get(object, 'formatted'),
Expand Down
54 changes: 24 additions & 30 deletions tests/unit/t-test.js → tests/unit/t-test.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,65 @@
import { setOwner } from '@ember/application';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import EmberObject, { get, set } from '@ember/object';
import { get, set } from '@ember/object';
import { run } from '@ember/runloop';
import { setupIntl, addTranslations } from 'ember-intl/test-support';
import { t } from '@ember-intl/decorators';
import TestContext from '../test-context';
import setupContainerObject from '../helper/setup-container-object';

module('Unit | @t', function(hooks) {
setupTest(hooks);
setupIntl(hooks, {
'no.interpolations': 'text with no interpolations',
'with.interpolations': 'Clicks: {clicks}'
});
setupContainerObject(hooks);

hooks.beforeEach(function() {
this.intl = this.owner.lookup('service:intl');

const { owner } = this;
this.ContainerObject = class extends EmberObject {
constructor() {
super();
setOwner(this, owner);
}
};
});

test('defines a computed property that translates without interpolations', function(assert) {
const object = new class extends this.ContainerObject {
test('defines a computed property that translates without interpolations', function(this: TestContext, assert) {
class TestObject extends this.ContainerObject {
@t('no.interpolations')
property;
}();
property!: string;
}
const object = new TestObject();

assert.strictEqual(get(object, 'property'), 'text with no interpolations');
});

test('defines a computed property that translates with interpolations', function(assert) {
const object = new class extends this.ContainerObject {
test('defines a computed property that translates with interpolations', function(this: TestContext, assert) {
class TestObject extends this.ContainerObject {
numberOfClicks = 9;

@t('with.interpolations', { clicks: 'numberOfClicks' })
property;
}();
property!: string;
}
const object = new TestObject();

assert.strictEqual(get(object, 'property'), 'Clicks: 9');
});

test('defines a computed property with dependencies', function(assert) {
const object = new class extends this.ContainerObject {
test('defines a computed property with dependencies', function(this: TestContext, assert) {
class TestObject extends this.ContainerObject {
numberOfClicks = 9;

@t('with.interpolations', { clicks: 'numberOfClicks' })
property;
}();
property!: string;
}
const object = new TestObject();

run(() => set(object, 'numberOfClicks', 13));
assert.strictEqual(get(object, 'property'), 'Clicks: 13');
});

test('defines a computed property that depends on the locale', function(assert) {
test('defines a computed property that depends on the locale', function(this: TestContext, assert) {
addTranslations('es', {
'no.interpolations': 'texto sin interpolaciones'
});

const object = new class extends this.ContainerObject {
class TestObject extends this.ContainerObject {
@t('no.interpolations')
property;
}();
property!: string;
}
const object = new TestObject();

assert.strictEqual(get(object, 'property'), 'text with no interpolations');
run(() => this.intl.setLocale('es'));
Expand Down
8 changes: 8 additions & 0 deletions types/@ember-decorators/utils/computed.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @TODO: re-enable once TypeScript parser works properly
// eslint-disable-next-line no-unused-vars
import ComputedProperty from '@ember/object/computed';
import { Descriptor } from './decorator';

export function computedDecorator(
fn: (desc: Descriptor, params?: any[]) => ComputedProperty<any>
): PropertyDecorator & ((desc: Descriptor) => Descriptor);
7 changes: 7 additions & 0 deletions types/@ember-decorators/utils/decorator.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface Descriptor {
initializer?: () => any;
}

export function decoratorWithParams<Params extends any[]>(
fn: (desc: Descriptor, params?: Params) => Descriptor
): ((...params: Params) => PropertyDecorator) & PropertyDecorator;
3 changes: 3 additions & 0 deletions types/ember-intl/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as Service } from './services/intl';
export { default as translationMacro, raw } from './macro';
export { default as IntlComputedProperty } from './intl-computed-property';
16 changes: 16 additions & 0 deletions types/ember-intl/intl-computed-property.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import ComputedProperty from '@ember/object/computed';
import IntlService from './services/intl';

type GetterFn<Ctx> = (
this: Ctx,
intl: IntlService,
propertyKey: string,
ctx: Ctx
) => string;

export default class IntlComputedProperty<
Ctx extends object,
Fn extends GetterFn<Ctx> = GetterFn<Ctx>
> extends ComputedProperty<Fn> {
constructor(...dependentKeysAndGetterFn: (string | Fn)[]);
}
36 changes: 36 additions & 0 deletions types/ember-intl/macro.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import IntlComputedProperty from './intl-computed-property';

/**
* This class is used to box primitive types and mark them as raw literals that
* should be used as is by the translation macro.
*
* This class is internal. Instead of using this class directly, use the `raw`
* utility function, that creates an instance of this class.
*/
declare class Raw<Value> {
constructor(value: Value);
valueOf(): Value;
toString(): string;
}

/**
* Use this utility function to mark a value as a raw literal.
*
* @param {*} value The value to mark as a raw literal.
* @return The same value, but boxed in the `Raw` class so that the consuming
* macro understands that this value should be used as is.
*/
export function raw<Value>(value: Value): Raw<Value>;

type OptionsFor<Ctx extends object> = { [key: string]: Raw<any> | keyof Ctx };

declare class TranslationMacro<Ctx extends object> extends IntlComputedProperty<
Ctx
> {
constructor(translationKey: string, options?: OptionsFor<Ctx>);
}

export default function createTranslatedComputedProperty<Ctx extends object>(
key: string,
options?: OptionsFor<Ctx>
): TranslationMacro<Ctx>;

0 comments on commit 4f274ad

Please sign in to comment.