Skip to content
8 changes: 8 additions & 0 deletions library/src/actions/notValue/notValue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,14 @@ describe('notValue', () => {
);
});

test('for invalid NaN', () => {
expectActionIssue(
notValue(NaN, 'message'),
{ ...baseInfo, expected: '!NaN', requirement: NaN },
[NaN, Number.NaN]
);
});

test('for invalid non-numbers', () => {
expectActionIssue(
notValue(123, 'message'),
Expand Down
7 changes: 4 additions & 3 deletions library/src/actions/notValue/notValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
BaseValidation,
ErrorMessage,
} from '../../types/index.ts';
import { _addIssue, _stringify } from '../../utils/index.ts';
import { _addIssue, _sameValueZero, _stringify } from '../../utils/index.ts';
import type { ValueInput } from '../types.ts';

/**
Expand Down Expand Up @@ -117,8 +117,9 @@ export function notValue(
'~run'(dataset, config) {
if (
dataset.typed &&
this.requirement <= dataset.value &&
this.requirement >= dataset.value
(_sameValueZero(this.requirement, dataset.value) ||
(this.requirement <= dataset.value &&
this.requirement >= dataset.value))
Comment thread
yslpn marked this conversation as resolved.
Outdated
) {
_addIssue(this, 'value', dataset, config, {
received:
Expand Down
8 changes: 8 additions & 0 deletions library/src/actions/notValues/notValues.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,14 @@ describe('notValues', () => {
);
});

test('for invalid NaN', () => {
expectActionIssue(
notValues([NaN], 'message'),
{ ...baseInfo, expected: '!NaN', requirement: [NaN] },
[NaN, Number.NaN]
);
});

test('for invalid non-numbers', () => {
expectActionIssue(
notValues([10, 11, 12], 'message'),
Expand Down
11 changes: 9 additions & 2 deletions library/src/actions/notValues/notValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import type {
BaseValidation,
ErrorMessage,
} from '../../types/index.ts';
import { _addIssue, _joinExpects, _stringify } from '../../utils/index.ts';
import {
_addIssue,
_joinExpects,
_sameValueZero,
_stringify,
} from '../../utils/index.ts';
import type { ValueInput } from '../types.ts';

/**
Expand Down Expand Up @@ -120,7 +125,9 @@ export function notValues(
if (
dataset.typed &&
this.requirement.some(
(value) => value <= dataset.value && value >= dataset.value
(value) =>
_sameValueZero(value, dataset.value) ||
(value <= dataset.value && value >= dataset.value)
)
) {
_addIssue(this, 'value', dataset, config, {
Expand Down
4 changes: 4 additions & 0 deletions library/src/actions/value/value.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ describe('value', () => {
expectNoActionIssue(value(123), [123, 123.0, Number('123')]);
});

test('for valid NaN', () => {
expectNoActionIssue(value(NaN), [NaN, Number.NaN]);
});

test('for valid non-numbers', () => {
expectNoActionIssue(value(123), [
123n,
Expand Down
6 changes: 4 additions & 2 deletions library/src/actions/value/value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
BaseValidation,
ErrorMessage,
} from '../../types/index.ts';
import { _addIssue, _stringify } from '../../utils/index.ts';
import { _addIssue, _sameValueZero, _stringify } from '../../utils/index.ts';
import type { ValueInput } from '../types.ts';

/**
Expand Down Expand Up @@ -116,7 +116,9 @@ export function value(
if (
dataset.typed &&
!(
this.requirement <= dataset.value && this.requirement >= dataset.value
_sameValueZero(this.requirement, dataset.value) ||
(this.requirement <= dataset.value &&
this.requirement >= dataset.value)
)
) {
_addIssue(this, 'value', dataset, config, {
Expand Down
4 changes: 4 additions & 0 deletions library/src/actions/values/values.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ describe('values', () => {
expectNoActionIssue(values([0]), [0]);
});

test('for valid NaN', () => {
expectNoActionIssue(values([NaN]), [NaN, Number.NaN]);
});

test('for valid non-numbers', () => {
expectNoActionIssue(values([-123, 456]), [
-123n,
Expand Down
11 changes: 9 additions & 2 deletions library/src/actions/values/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import type {
BaseValidation,
ErrorMessage,
} from '../../types/index.ts';
import { _addIssue, _joinExpects, _stringify } from '../../utils/index.ts';
import {
_addIssue,
_joinExpects,
_sameValueZero,
_stringify,
} from '../../utils/index.ts';
import type { ValueInput } from '../types.ts';

/**
Expand Down Expand Up @@ -118,7 +123,9 @@ export function values(
if (
dataset.typed &&
!this.requirement.some(
(value) => value <= dataset.value && value >= dataset.value
(value) =>
_sameValueZero(value, dataset.value) ||
(value <= dataset.value && value >= dataset.value)
)
) {
_addIssue(this, 'value', dataset, config, {
Expand Down
28 changes: 28 additions & 0 deletions library/src/schemas/intersect/utils/_merge/_merge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ describe('_merge', () => {
});
});

test('for NaN primitives', () => {
const result = _merge(NaN, NaN);
expect(result.issue).toBeUndefined();
expect(Number.isNaN((result as { value: unknown }).value)).toBe(true);
});

test('for zero values with different signs', () => {
const negativeZeroResult = _merge(-0, 0);
expect(negativeZeroResult.issue).toBeUndefined();
expect(
Object.is((negativeZeroResult as { value: unknown }).value, -0)
).toBe(true);
const positiveZeroResult = _merge(0, -0);
expect(positiveZeroResult.issue).toBeUndefined();
expect(
Object.is((positiveZeroResult as { value: unknown }).value, 0)
).toBe(true);
});

test('for valid dates', () => {
const date = new Date();
expect(_merge(date, date)).toStrictEqual({ value: date });
Expand All @@ -21,6 +40,15 @@ describe('_merge', () => {
});
});

test('for invalid dates', () => {
const invalidDate = new Date(NaN);
const result = _merge(invalidDate, new Date(NaN));
expect(result.issue).toBeUndefined();
const value = (result as { value: Date }).value;
expect(value).toBeInstanceOf(Date);
expect(Number.isNaN(+value)).toBe(true);
});

Comment thread
coderabbitai[bot] marked this conversation as resolved.
test('for valid objects', () => {
expect(_merge({ key: 1 }, { key: 1 })).toStrictEqual({
value: { key: 1 },
Expand Down
8 changes: 6 additions & 2 deletions library/src/schemas/intersect/utils/_merge/_merge.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { _sameValueZero } from '../../../../utils/index.ts';

/**
* Merge dataset type.
*/
Expand All @@ -21,8 +23,10 @@ export function _merge(value1: unknown, value2: unknown): MergeDataset {
if (typeof value1 === typeof value2) {
// Return first value if both are equal
if (
value1 === value2 ||
(value1 instanceof Date && value2 instanceof Date && +value1 === +value2)
_sameValueZero(value1, value2) ||
(value1 instanceof Date &&
value2 instanceof Date &&
_sameValueZero(+value1, +value2))
) {
return { value: value1 };
}
Expand Down
32 changes: 32 additions & 0 deletions library/src/schemas/literal/literal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ describe('literal', () => {
expectNoSchemaIssue(literal(45.67), [45.67]);
});

test('for valid NaN literal', () => {
expectNoSchemaIssue(literal(NaN), [NaN, Number.NaN]);
});

test('for valid signed zero literal', () => {
expectNoSchemaIssue(literal(0), [-0]);
expectNoSchemaIssue(literal(-0), [0]);
});

test('for valid string literal', () => {
expectNoSchemaIssue(literal(''), ['']);
expectNoSchemaIssue(literal('foo'), ['foo']);
Expand Down Expand Up @@ -139,6 +148,29 @@ describe('literal', () => {
);
});

test('for invalid NaN literal', () => {
expectSchemaIssue(
literal(NaN, 'message'),
{ ...baseIssue, expected: 'NaN' },
[
123n,
true,
false,
null,
-123,
0,
45.67,
undefined,
'foo',
Symbol(),
{},
[],
// eslint-disable-next-line @typescript-eslint/no-empty-function
() => {},
]
);
});

test('for invalid string literal', () => {
expectSchemaIssue(
literal('123', 'message'),
Expand Down
9 changes: 7 additions & 2 deletions library/src/schemas/literal/literal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import type {
ErrorMessage,
OutputDataset,
} from '../../types/index.ts';
import { _addIssue, _getStandardProps, _stringify } from '../../utils/index.ts';
import {
_addIssue,
_getStandardProps,
_sameValueZero,
_stringify,
} from '../../utils/index.ts';

/**
* Literal type.
Expand Down Expand Up @@ -95,7 +100,7 @@ export function literal(
return _getStandardProps(this);
},
'~run'(dataset, config) {
if (dataset.value === this.literal) {
if (_sameValueZero(dataset.value, this.literal)) {
// @ts-expect-error
dataset.typed = true;
} else {
Expand Down
12 changes: 12 additions & 0 deletions library/src/utils/_sameValueZero/_sameValueZero.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { describe, expect, test } from 'vitest';
import { _sameValueZero } from './_sameValueZero.ts';

describe('_sameValueZero', () => {
test('should return whether values are equal', () => {
expect(_sameValueZero('foo', 'foo')).toBe(true);
expect(_sameValueZero('foo', 'bar')).toBe(false);
expect(_sameValueZero(NaN, NaN)).toBe(true);
expect(_sameValueZero(NaN, 0)).toBe(false);
expect(_sameValueZero(-0, 0)).toBe(true);
});
});
15 changes: 15 additions & 0 deletions library/src/utils/_sameValueZero/_sameValueZero.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Compares two values using the SameValueZero algorithm, which treats `NaN`
* as equal to itself unlike `===`.
*
* @param value1 The first value.
* @param value2 The second value.
*
* @returns Whether the values are equal.
*
* @internal
*/
// @__NO_SIDE_EFFECTS__
export function _sameValueZero(value1: unknown, value2: unknown): boolean {
return value1 === value2 || (Number.isNaN(value1) && Number.isNaN(value2));
}
1 change: 1 addition & 0 deletions library/src/utils/_sameValueZero/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './_sameValueZero.ts';
1 change: 1 addition & 0 deletions library/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './_getWordCount/index.ts';
export * from './_isLuhnAlgo/index.ts';
export * from './_isValidObjectKey/index.ts';
export * from './_joinExpects/index.ts';
export * from './_sameValueZero/index.ts';
export * from './_stringify/index.ts';
export * from './entriesFromList/index.ts';
export * from './entriesFromObjects/index.ts';
Expand Down
Loading