Skip to content

Commit

Permalink
Fix normalize type options (#1850)
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrodurek committed Oct 19, 2022
1 parent e599bd5 commit 002d9f4
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 5 deletions.
11 changes: 8 additions & 3 deletions index.d.ts
Expand Up @@ -824,18 +824,23 @@ type TypeOptionsFallback<TranslationValue, Option, MatchingValue> = Option exten
/**
* Checks if user has enabled `returnEmptyString` and `returnNull` options to retrieve correct values.
*/
interface CustomTypeParameters {
returnNull?: boolean;
returnEmptyString?: boolean;
}
export type NormalizeByTypeOptions<
TranslationValue,
R = TypeOptionsFallback<TranslationValue, TypeOptions['returnEmptyString'], ''>,
> = TypeOptionsFallback<R, TypeOptions['returnNull'], null>;
Options extends CustomTypeParameters = TypeOptions,
R = TypeOptionsFallback<TranslationValue, Options['returnEmptyString'], ''>,
> = TypeOptionsFallback<R, Options['returnNull'], null>;

type StringIfPlural<T> = TypeOptions['jsonFormat'] extends 'v4'
? T extends `${string}_${PluralSuffix}`
? string
: never
: never;

type NormalizeReturn<
export type NormalizeReturn<
T,
V,
S extends string | false = TypeOptions['keySeparator'],
Expand Down
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -102,12 +102,12 @@
"watchify": "3.9.0"
},
"scripts": {
"pretest": "npm run test:typescript && npm run test:custom-typescript && npm run test:typescript:noninterop",
"pretest": "npm run test:typescript && npm run test:typescript:customtypes && npm run test:typescript:noninterop",
"test": "npm run test:new && npm run test:compat",
"test:new": "karma start karma.conf.js --singleRun",
"test:compat": "karma start karma.backward.conf.js --singleRun",
"test:typescript": "tslint --project tsconfig.json",
"test:custom-typescript": "tslint --project test/typescript/custom-types/tsconfig.json",
"test:typescript:customtypes": "tslint --project test/typescript/custom-types/tsconfig.json",
"test:typescript:noninterop": "tslint --project tsconfig.nonEsModuleInterop.json",
"tdd": "karma start karma.conf.js",
"tdd:compat": "karma start karma.backward.conf.js",
Expand Down
65 changes: 65 additions & 0 deletions test/typescript/returnTypes.test.ts
@@ -0,0 +1,65 @@
import { KeysWithSeparator, NormalizeByTypeOptions, NormalizeReturn } from 'i18next';

// Test cases for TypeOptions['returnNull']: true
type ReturnNull = NormalizeByTypeOptions<null, { returnNull: true }>; // Returns null

const nullableValue: ReturnNull = null;

// @ts-expect-error: null is not assignable to string
const nonNullableValue: ReturnNull = '';

// Test cases for TypeOptions['returnNull']: false
type ReturnNonNullable = NormalizeByTypeOptions<null, { returnNull: false }>; // Returns string

// @ts-expect-error: null is not assignable to string
const nullableValue2: ReturnNonNullable = null;

const nonNullableValue2: ReturnNonNullable = '';

// Test cases for TypeOptions['returnEmptyString']: false
type ReturnNonEmptyString = NormalizeByTypeOptions<'', { returnEmptyString: false }>; // Returns string

const emptyStringValue: ReturnNonEmptyString = '';

// Emtpy string should always be assignable to string, but not viceversa
const nonEmptyStringValue: ReturnNonEmptyString = 'non-empty-string';

// Test cases for TypeOptions['returnEmptyString']: true
type ReturnEmptyString = NormalizeByTypeOptions<'', { returnEmptyString: true }>; // Returns ""

const emptyStringValue2: ReturnEmptyString = '';

// @ts-expect-error: '"non-empty-string"' is not assignable to type '""'
const nonEmptyStringValue2: ReturnEmptyString = 'non-empty-string';

// Test cases for TypeOptions['keySeparator']: '.' (default)
type DefaultCase = KeysWithSeparator<'namespace', 'key' | 'key2'>;
const defaultCaseExpectedResult = 'namespace.key';
const defaultCaseExpectedResult2 = 'namespace.key2';
const defaultCase: DefaultCase = defaultCaseExpectedResult;
const defaultCase2: DefaultCase = defaultCaseExpectedResult2;

// Test cases for TypeOptions['keySeparator']: '>>>' (arbitrary separator)
type ArbitrarySeparatorCase = KeysWithSeparator<'namespace', 'key' | 'key2', '>>>'>;
const arbitrarySeparatorExpectedResult = 'namespace>>>key';
const arbitrarySeparatorExpectedResult2 = 'namespace>>>key2';
const arbitrarySeparatorCase: ArbitrarySeparatorCase = arbitrarySeparatorExpectedResult;
const arbitrarySeparatorCase2: ArbitrarySeparatorCase = arbitrarySeparatorExpectedResult2;

// Test cases for TypeOptions['keySeparator']: false (nesting not supported)
interface MockDictionary {
key: { nested: 'value' };
notNested: 'value';
}

type ReturnGivenKey = NormalizeReturn<MockDictionary, 'key.nested', false>;
const shouldBeGivenKey: ReturnGivenKey = 'key.nested';

type ReturnGivenKey2 = NormalizeReturn<MockDictionary, 'keyfalsenested', false>;
const shouldBeGivenKey2: ReturnGivenKey2 = 'keyfalsenested';

type ReturnValue = NormalizeReturn<MockDictionary, 'notNested', false>;
const shouldBeTranslationValue: ReturnValue = 'value';

type ReturnValue2 = NormalizeReturn<MockDictionary, 'key//nested', '//'>;
const shouldBeTranslationValue2: ReturnValue2 = 'value';

0 comments on commit 002d9f4

Please sign in to comment.