From f60963b99160c2a2f692b000acc2cf0a827c758b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliz=C3=A9=20Debray?= Date: Mon, 13 Oct 2025 15:21:28 +0200 Subject: [PATCH 1/2] chore(component): update utils to prepare for slider --- .../components/src/types/property-types.ts | 5 +- packages/components/src/utils/clamp.ts | 3 + .../components/src/utils/is-value-empty.ts | 4 +- .../utils/property-checkers/check-array-of.ts | 18 +++++ .../src/utils/property-checkers/index.ts | 3 + .../tests/check-array-of.spec.ts | 65 +++++++++++++++++++ .../tests/required-and.spec.ts | 6 +- .../components/src/utils/tests/clamp.spec.ts | 15 +++++ 8 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 packages/components/src/utils/clamp.ts create mode 100644 packages/components/src/utils/property-checkers/check-array-of.ts create mode 100644 packages/components/src/utils/property-checkers/tests/check-array-of.spec.ts create mode 100644 packages/components/src/utils/tests/clamp.spec.ts diff --git a/packages/components/src/types/property-types.ts b/packages/components/src/types/property-types.ts index 16afa5488d..d51c6062ef 100644 --- a/packages/components/src/types/property-types.ts +++ b/packages/components/src/types/property-types.ts @@ -1 +1,4 @@ -export type PropertyType = 'boolean' | 'number' | 'string' | 'array' | 'object' | 'function'; +export type PrimitiveType = 'boolean' | 'number' | 'string'; +export type ReferenceType = 'array' | 'object' | 'function'; + +export type PropertyType = PrimitiveType | ReferenceType; diff --git a/packages/components/src/utils/clamp.ts b/packages/components/src/utils/clamp.ts new file mode 100644 index 0000000000..0210f95e70 --- /dev/null +++ b/packages/components/src/utils/clamp.ts @@ -0,0 +1,3 @@ +export function clamp(value: number, min: number, max: number): number { + return Math.min(Math.max(value, min), max); +} diff --git a/packages/components/src/utils/is-value-empty.ts b/packages/components/src/utils/is-value-empty.ts index 2e2740aa4e..53cd424d30 100644 --- a/packages/components/src/utils/is-value-empty.ts +++ b/packages/components/src/utils/is-value-empty.ts @@ -1,5 +1,3 @@ -import { EMPTY_VALUES } from './property-checkers/constants'; - export function isValueEmpty(value: unknown): boolean { - return EMPTY_VALUES.some(v => v === value); + return value == null || value === '' || (typeof value === 'number' && isNaN(value)); } diff --git a/packages/components/src/utils/property-checkers/check-array-of.ts b/packages/components/src/utils/property-checkers/check-array-of.ts new file mode 100644 index 0000000000..e6e1079091 --- /dev/null +++ b/packages/components/src/utils/property-checkers/check-array-of.ts @@ -0,0 +1,18 @@ +import { PrimitiveType } from '@/types/property-types'; + +export function checkArrayOf( + component: T, + prop: keyof T, + type: PrimitiveType, +) { + const componentName = component.host.localName; + const value = component[prop]; + + const message = `The prop \`${String( + prop, + )}\` of the \`${componentName}\` component must be an \`${type}\` array.`; + + if (!Array.isArray(value) || value.some(val => typeof val !== type)) { + console.error(message); + } +} diff --git a/packages/components/src/utils/property-checkers/index.ts b/packages/components/src/utils/property-checkers/index.ts index 2818ce75e1..9456f61320 100644 --- a/packages/components/src/utils/property-checkers/index.ts +++ b/packages/components/src/utils/property-checkers/index.ts @@ -4,13 +4,16 @@ import { checkPattern } from './check-pattern'; import { checkType } from './check-type'; import { checkUrl } from './check-url'; import { emptyOr } from './empty-or'; +import { checkArrayOf } from '@/utils/property-checkers/check-array-of'; export const checkEmptyOrOneOf = emptyOr(checkOneOf); export const checkEmptyOrPattern = emptyOr(checkPattern); export const checkEmptyOrType = emptyOr(checkType); export const checkEmptyOrUrl = emptyOr(checkUrl); +export const checkEmptyOrArrayOf = emptyOr(checkArrayOf); export const checkRequiredAndOneOf = requiredAnd(checkOneOf); export const checkRequiredAndPattern = requiredAnd(checkPattern); export const checkRequiredAndType = requiredAnd(checkType); export const checkRequiredAndUrl = requiredAnd(checkUrl); +export const checkRequiredAndArrayOf = requiredAnd(checkArrayOf); diff --git a/packages/components/src/utils/property-checkers/tests/check-array-of.spec.ts b/packages/components/src/utils/property-checkers/tests/check-array-of.spec.ts new file mode 100644 index 0000000000..2a692af02b --- /dev/null +++ b/packages/components/src/utils/property-checkers/tests/check-array-of.spec.ts @@ -0,0 +1,65 @@ +import { checkArrayOf } from '../check-array-of'; +import { PrimitiveType } from '@/types'; +import { describe } from 'node:test'; + +describe('checkArrayOf', () => { + const componentName = 'post-component'; + const propName = 'myProp'; + const mockValues = [ + undefined, + null, + true, + false, + 42, + NaN, + 'string', + '', + [], + {}, + () => { + /* empty */ + }, + ]; + + let consoleErrorSpy: jest.SpyInstance; + + beforeEach(() => { + consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleErrorSpy.mockRestore(); + }); + + const primitiveTypes: PrimitiveType[] = ['boolean', 'string', 'number']; + primitiveTypes.forEach(type => { + describe(type, () => { + const error = `The prop \`${propName}\` of the \`${componentName}\` component must be an \`${type}\` array.`; + + const runCheckForValue = (value: unknown) => { + const component = { host: { localName: componentName } as HTMLElement, [propName]: value }; + checkArrayOf(component, propName, type); + }; + + it('should log an error is the value is not an array', () => { + mockValues + .filter(value => !Array.isArray(value)) + .forEach(value => { + runCheckForValue(value); + expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error)); + }); + }); + + it('should log an error is the array contains some values that don\'t have the expected type', () => { + runCheckForValue(mockValues); + expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error)); + }); + + it('should not log an error if hte array contains only value with the expected type', () => { + const validArray = mockValues.filter(value => typeof value === type); + runCheckForValue(validArray); + expect(consoleErrorSpy).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/packages/components/src/utils/property-checkers/tests/required-and.spec.ts b/packages/components/src/utils/property-checkers/tests/required-and.spec.ts index 4aceacf057..ad8b50a888 100644 --- a/packages/components/src/utils/property-checkers/tests/required-and.spec.ts +++ b/packages/components/src/utils/property-checkers/tests/required-and.spec.ts @@ -1,12 +1,11 @@ import { requiredAnd } from '../required-and'; -import { EMPTY_VALUES } from '../constants'; describe('requiredAnd', () => { const mockCheck = jest.fn(); const mockRequiredAndCheck = requiredAnd(mockCheck); it('should throw error if the provided value is empty', () => { - EMPTY_VALUES.forEach(emptyValue => { + [undefined, null, '', NaN].forEach(emptyValue => { const component = { host: { localName: 'post-component' } as HTMLElement, prop: emptyValue }; const prop = component['prop']; const error = `The prop \`${emptyValue}\` of the \`post-component\` component is not defined.`; @@ -17,7 +16,6 @@ describe('requiredAnd', () => { it('should run the check if the provided value is not empty', () => { [ 0, - NaN, ' ', false, [], @@ -36,7 +34,7 @@ describe('requiredAnd', () => { }); it('should pass all provided arguments to the nested check function', () => { - const args = ['non empty value', true, false, ['arg in an array'], { arg: 'in an object' }]; + const args = [0, false, 'text', [], {}]; args.forEach(arg => { const component = { host: { localName: 'post-component' } as HTMLElement, prop: arg }; diff --git a/packages/components/src/utils/tests/clamp.spec.ts b/packages/components/src/utils/tests/clamp.spec.ts new file mode 100644 index 0000000000..26295fbf79 --- /dev/null +++ b/packages/components/src/utils/tests/clamp.spec.ts @@ -0,0 +1,15 @@ +import { clamp } from '../clamp'; + +describe('clamp', () => { + it('should return the provided value if within the bounds', () => { + expect(clamp(10, 0, 100)).toEqual(10); + }); + + it('should return the min if provided value is lower', () => { + expect(clamp(0, 10, 100)).toEqual(10); + }); + + it('should return the max if provided value is greater', () => { + expect(clamp(100, 0, 10)).toEqual(10); + }); +}); From e401a4a872acc37eb3703ee63170d3b326c050b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliz=C3=A9=20Debray?= Date: Thu, 16 Oct 2025 13:54:44 +0200 Subject: [PATCH 2/2] fix typos --- .../utils/property-checkers/tests/check-array-of.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/src/utils/property-checkers/tests/check-array-of.spec.ts b/packages/components/src/utils/property-checkers/tests/check-array-of.spec.ts index 2a692af02b..18cc543c52 100644 --- a/packages/components/src/utils/property-checkers/tests/check-array-of.spec.ts +++ b/packages/components/src/utils/property-checkers/tests/check-array-of.spec.ts @@ -41,7 +41,7 @@ describe('checkArrayOf', () => { checkArrayOf(component, propName, type); }; - it('should log an error is the value is not an array', () => { + it('should log an error if the value is not an array', () => { mockValues .filter(value => !Array.isArray(value)) .forEach(value => { @@ -50,12 +50,12 @@ describe('checkArrayOf', () => { }); }); - it('should log an error is the array contains some values that don\'t have the expected type', () => { + it('should log an error if the array contains some values that don\'t have the expected type', () => { runCheckForValue(mockValues); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error)); }); - it('should not log an error if hte array contains only value with the expected type', () => { + it('should not log an error if the array contains only values with the expected type', () => { const validArray = mockValues.filter(value => typeof value === type); runCheckForValue(validArray); expect(consoleErrorSpy).not.toHaveBeenCalled();