Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ server

# Tests
coverage
tsconfig.vitest-temp.json

# Backups
backups
Expand Down
1 change: 1 addition & 0 deletions library/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export * from './metadata/index.ts';
export * from './mimeType/index.ts';
export * from './minBytes/index.ts';
export * from './minEntries/index.ts';
export * from './minDigitChars/index.ts';
Comment on lines 65 to +66
export * from './minGraphemes/index.ts';
export * from './minLength/index.ts';
export * from './minSize/index.ts';
Expand Down
1 change: 1 addition & 0 deletions library/src/actions/minDigitChars/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './minDigitChars.ts';
50 changes: 50 additions & 0 deletions library/src/actions/minDigitChars/minDigitChars.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, expectTypeOf, test } from 'vitest';
import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts';
import {
minDigitChars,
type MinDigitCharsAction,
type MinDigitCharsIssue,
} from './minDigitChars.ts';

describe('minDigitChars', () => {
describe('should return action object', () => {
test('with undefined message', () => {
type Action = MinDigitCharsAction<string, 10, undefined>;
expectTypeOf(minDigitChars<string, 10>(10)).toEqualTypeOf<Action>();
expectTypeOf(
minDigitChars<string, 10, undefined>(10, undefined)
).toEqualTypeOf<Action>();
});

test('with string message', () => {
expectTypeOf(
minDigitChars<string, 10, 'message'>(10, 'message')
).toEqualTypeOf<MinDigitCharsAction<string, 10, 'message'>>();
});

test('with function message', () => {
expectTypeOf(
minDigitChars<string, 10, () => string>(10, () => 'message')
).toEqualTypeOf<MinDigitCharsAction<string, 10, () => string>>();
});
});

describe('should infer correct types', () => {
type Input = 'example string';
type Action = MinDigitCharsAction<Input, 10, undefined>;

test('of input', () => {
expectTypeOf<InferInput<Action>>().toEqualTypeOf<Input>();
});

test('of output', () => {
expectTypeOf<InferOutput<Action>>().toEqualTypeOf<Input>();
});

test('of issue', () => {
expectTypeOf<InferIssue<Action>>().toEqualTypeOf<
MinDigitCharsIssue<Input, 10>
>();
});
});
});
98 changes: 98 additions & 0 deletions library/src/actions/minDigitChars/minDigitChars.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { describe, expect, test } from 'vitest';
import type { StringIssue } from '../../schemas/index.ts';
import { _getDigitCount } from '../../utils/index.ts';
import { expectActionIssue, expectNoActionIssue } from '../../vitest/index.ts';
import {
minDigitChars,
type MinDigitCharsAction,
type MinDigitCharsIssue,
} from './minDigitChars.ts';

describe('minDigitChars', () => {
describe('should return action object', () => {
const baseAction: Omit<MinDigitCharsAction<string, 5, never>, 'message'> = {
kind: 'validation',
type: 'min_digits',
reference: minDigitChars,
expects: '>=5',
requirement: 5,
async: false,
'~run': expect.any(Function),
};

test('with undefined message', () => {
const action: MinDigitCharsAction<string, 5, undefined> = {
...baseAction,
message: undefined,
};
expect(minDigitChars(5)).toStrictEqual(action);
expect(minDigitChars(5, undefined)).toStrictEqual(action);
});

test('with string message', () => {
expect(minDigitChars(5, 'message')).toStrictEqual({
...baseAction,
message: 'message',
} satisfies MinDigitCharsAction<string, 5, string>);
});

test('with function message', () => {
const message = () => 'message';
expect(minDigitChars(5, message)).toStrictEqual({
...baseAction,
message,
} satisfies MinDigitCharsAction<string, 5, typeof message>);
});
});

describe('should return dataset without issues', () => {
const action = minDigitChars(5);

test('for untyped inputs', () => {
const issues: [StringIssue] = [
{
kind: 'schema',
type: 'string',
input: null,
expected: 'string',
received: 'null',
message: 'message',
},
];
expect(
action['~run']({ typed: false, value: null, issues }, {})
).toStrictEqual({
typed: false,
value: null,
issues,
});
});

test('for valid strings', () => {
expectNoActionIssue(action, ['12345', '123456', '45foobarbaz123']);
});
});

describe('should return dataset with issues', () => {
const action = minDigitChars(5, 'message');
const baseIssue: Omit<
MinDigitCharsIssue<string, 5>,
'input' | 'received'
> = {
kind: 'validation',
type: 'min_digits',
expected: '>=5',
message: 'message',
requirement: 5,
};

test('for invalid strings', () => {
expectActionIssue(
action,
baseIssue,
['', 'foo', 'abc1234'],
(value) => `${_getDigitCount(value)}`
);
});
});
});
135 changes: 135 additions & 0 deletions library/src/actions/minDigitChars/minDigitChars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import type {
BaseIssue,
BaseValidation,
ErrorMessage,
} from '../../types/index.ts';
import { _addIssue, _getDigitCount } from '../../utils/index.ts';

/**
* Min digits issue interface.
*/
export interface MinDigitCharsIssue<
TInput extends string,
TRequirement extends number,
> extends BaseIssue<TInput> {
/**
* The issue kind.
*/
readonly kind: 'validation';
/**
* The issue type.
*/
readonly type: 'min_digits';
/**
* The expected property.
*/
readonly expected: `>=${TRequirement}`;
/**
* The received property.
*/
readonly received: `${number}`;
/**
* The minimum digits.
*/
readonly requirement: TRequirement;
}

/**
* Min digits action interface.
*/
export interface MinDigitCharsAction<
TInput extends string,
TRequirement extends number,
TMessage extends
| ErrorMessage<MinDigitCharsIssue<TInput, TRequirement>>
| undefined,
> extends BaseValidation<
TInput,
TInput,
MinDigitCharsIssue<TInput, TRequirement>
> {
/**
* The action type.
*/
readonly type: 'min_digits';
/**
* The action reference.
*/
readonly reference: typeof minDigitChars;
/**
* The expected property.
*/
readonly expects: `>=${TRequirement}`;
/**
* The minimum digits.
*/
readonly requirement: TRequirement;
/**
* The error message.
*/
readonly message: TMessage;
}

/**
* Creates a min digits validation action.
*
* @param requirement The minimum digits.
*
* @returns A min digits action.
*/
export function minDigitChars<
TInput extends string,
const TRequirement extends number,
>(
requirement: TRequirement
): MinDigitCharsAction<TInput, TRequirement, undefined>;
Comment on lines +73 to +85

/**
* Creates a min digits validation action.
*
* @param requirement The minimum digits.
* @param message The error message.
*
* @returns A min digits action.
*/
export function minDigitChars<
TInput extends string,
const TRequirement extends number,
const TMessage extends
| ErrorMessage<MinDigitCharsIssue<TInput, TRequirement>>
| undefined,
>(
requirement: TRequirement,
message: TMessage
): MinDigitCharsAction<TInput, TRequirement, TMessage>;

// @__NO_SIDE_EFFECTS__
export function minDigitChars(
requirement: number,
message?: ErrorMessage<MinDigitCharsIssue<string, number>>
): MinDigitCharsAction<
string,
number,
ErrorMessage<MinDigitCharsIssue<string, number>> | undefined
> {
return {
kind: 'validation',
type: 'min_digits',
reference: minDigitChars,
async: false,
expects: `>=${requirement}`,
requirement,
message,
'~run'(dataset, config) {
if (dataset.typed) {
const count = _getDigitCount(dataset.value);
if (count < this.requirement) {
_addIssue(this, 'digits', dataset, config, {
received: `${count}`,
});
}
}
return dataset;
},
};
}
14 changes: 14 additions & 0 deletions library/src/utils/_getDigitCount/_getDigitCount.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { describe, expect, test } from 'vitest';
import { _getDigitCount } from './_getDigitCount.ts';

describe('_getDigitCount', () => {
test('should return digit count', () => {
expect(_getDigitCount('')).toBe(0);
expect(_getDigitCount('hello world')).toBe(0);
expect(_getDigitCount('he22o world')).toBe(2);
expect(_getDigitCount('hello world111')).toBe(3);
expect(_getDigitCount('111hello world111')).toBe(6);
expect(_getDigitCount('123')).toBe(3);
expect(_getDigitCount('馃榾1')).toBe(1);
});
});
13 changes: 13 additions & 0 deletions library/src/utils/_getDigitCount/_getDigitCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Returns the digit count of the input.
*
* @param input The input to be measured.
*
* @returns The digit count.
*
* @internal
*/
// @__NO_SIDE_EFFECTS__
export function _getDigitCount(input: string): number {
return (input.match(/\d/gu) || []).length;
}
1 change: 1 addition & 0 deletions library/src/utils/_getDigitCount/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './_getDigitCount.ts';
1 change: 1 addition & 0 deletions library/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './_addIssue/index.ts';
export * from './_getByteCount/index.ts';
export * from './_getDigitCount/index.ts';
export * from './_getGraphemeCount/index.ts';
export * from './_getStandardProps/index.ts';
export * from './_getWordCount/index.ts';
Expand Down
Loading