Skip to content

Commit

Permalink
Merge 55078b6 into d057e1d
Browse files Browse the repository at this point in the history
  • Loading branch information
stefan-schweiger committed May 14, 2024
2 parents d057e1d + 55078b6 commit 74255b2
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 11 deletions.
63 changes: 53 additions & 10 deletions test/typescript/custom-types/t.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,22 @@ describe('t', () => {

it('should accept a default context key as a valid `t` function key', () => {
expectTypeOf(t('beverage')).toMatchTypeOf('cold water');

expectTypeOf(t('beverage', { context: undefined })).toMatchTypeOf('cold water');
});

it('should throw error when no `context` is provided using and the context key has no default value ', () => {
// @ts-expect-error dessert has no default value, it needs a context
expectTypeOf(t('dessert')).toMatchTypeOf('error');

// @ts-expect-error dessert has no default value, it needs a context
expectTypeOf(t('dessert', { context: undefined })).toMatchTypeOf('error');

// TODO: edge case which is not correctly detected currently
// expectTypeOf(
// // @ts-expect-error no default context so it must give a type error
// t('dessert', { context: undefined as 'cake' | undefined }),
// ).toMatchTypeOf<never>();
});

it('should work with enum as a context value', () => {
Expand All @@ -174,18 +185,50 @@ describe('t', () => {
expectTypeOf(t('dessert', { context: ctx })).toMatchTypeOf<string>();
});

it('should trow error with string union with missing context value', () => {
it('should throw error with string union with missing context value', () => {
enum DessertMissingValue {
COOKIE = 'cookie',
CAKE = 'cake',
MUFFIN = 'muffin',
ANOTHER = 'another',
}

const ctxMissingValue = DessertMissingValue.ANOTHER;
const getRandomDessert = (): DessertMissingValue =>
Math.random() < 0.5 ? DessertMissingValue.CAKE : DessertMissingValue.ANOTHER;

const ctxRandomValue: DessertMissingValue = getRandomDessert();

// @ts-expect-error Dessert.ANOTHER is not mapped so it must give a type error
expectTypeOf(t('dessert', { context: ctxMissingValue })).toMatchTypeOf<string>();
expectTypeOf(t('dessert', { context: ctxRandomValue })).toMatchTypeOf<string>();

// @ts-expect-error Dessert.ANOTHER is not mapped so it must give a type error
expectTypeOf(t('dessert', { context: DessertMissingValue.ANOTHER })).toMatchTypeOf<string>();

expectTypeOf(
// @ts-expect-error 'another' is not mapped so it must give a type error
t('dessert', { context: 'cake' as 'cake' | 'another' }),
).toEqualTypeOf<'a nice cake'>();
});

it('should not throw error with string union with undefined context value if it has a default context', () => {
enum BeverageValue {
BEER = 'beer',
WATER = 'water',
}

const getRandomBeverage = (): BeverageValue | undefined =>
Math.random() < 0.5 ? BeverageValue.BEER : undefined;

const ctxRandomValue = getRandomBeverage();

expectTypeOf(
t('beverage', { context: ctxRandomValue }),
).toMatchTypeOf<'a classic beverage'>();

expectTypeOf(
t('beverage', { context: 'beer' as 'beer' | 'water' | undefined }),
).toEqualTypeOf<'a classic beverage'>();

expectTypeOf(t('beverage', { context: undefined })).toEqualTypeOf<'a classic beverage'>();
});

it('should work with string union as a context value', () => {
Expand All @@ -195,12 +238,12 @@ describe('t', () => {
});

// @see https://github.com/i18next/i18next/issues/2172
// it('should trow error with string union with missing context value', () => {
// expectTypeOf(
// // @ts-expect-error
// t('dessert', { context: 'muffin' as 'muffin' | 'cake' | 'pippo' }),
// ).toMatchTypeOf<string>();
// });
it('should throw error with string union with missing context value', () => {
expectTypeOf(
// @ts-expect-error
t('dessert', { context: 'muffin' as 'muffin' | 'cake' | 'pippo' }),
).toMatchTypeOf<string>();
});
});

describe('context + explicit namespace', () => {
Expand Down
24 changes: 23 additions & 1 deletion typescript/t.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,23 @@ export type KeyWithContext<Key, TOpt extends TOptions> = TOpt['context'] extends
? `${Key & string}${_ContextSeparator}${TOpt['context']}`
: Key;

type ContextOfKey<
Ns extends Namespace,
Key,
TOpt extends TOptions,
Keys extends $Dictionary = KeysByTOptions<TOpt>,
ActualNS extends Namespace = NsByTOptions<Ns, TOpt>,
ActualKeys = Keys[$FirstNamespace<ActualNS>],
> = $IsResourcesDefined extends true
? Key extends `${infer Nsp}${_NsSeparator}${infer RestKey}`
? Nsp extends Namespace
? ContextOfKey<Nsp, RestKey, TOpt>
: never
: ActualKeys extends `${Key extends string ? Key : never}${_ContextSeparator}${infer Context}`
? Context
: never
: string;

export type TFunctionReturn<
Ns extends Namespace,
Key,
Expand Down Expand Up @@ -264,7 +281,12 @@ export interface TFunction<Ns extends Namespace = DefaultNamespace, KPrefix = un
const ActualOptions extends TOpt & InterpolationMap<Ret> = TOpt & InterpolationMap<Ret>,
>(
...args:
| [key: Key | Key[], options?: ActualOptions]
| [
key: Key | Key[],
options?: Omit<ActualOptions, 'context'> & {
context?: ContextOfKey<Ns, Key, TOpt>;
},
]
| [key: string | string[], options: TOpt & $Dictionary & { defaultValue: string }]
| [key: string | string[], defaultValue: string, options?: TOpt & $Dictionary]
): TFunctionReturnOptionalDetails<Ret, TOpt>;
Expand Down

0 comments on commit 74255b2

Please sign in to comment.