Skip to content

Commit

Permalink
fix: missing config validators (#974)
Browse files Browse the repository at this point in the history
  • Loading branch information
quentinderoubaix authored Oct 28, 2024
1 parent fa33491 commit e311275
Show file tree
Hide file tree
Showing 20 changed files with 97 additions and 21 deletions.
1 change: 1 addition & 0 deletions angular/headless/src/utils/widget.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe('callWidgetFactoryWithConfig', () => {
{
onMyAction: typeFunction,
myValue: typeString,
onCounterChange: undefined,
},
);
const derivedValue$ = computed(createZoneCheckFn('computeDerivedValue', () => `derived from ${myValue$()}`));
Expand Down
8 changes: 5 additions & 3 deletions core-bootstrap/src/components/alert/alert.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type {AlertDirectives, AlertState as CoreState, AlertProps as CoreProps, AlertApi} from '@agnos-ui/core/components/alert';
import {createAlert as createCoreAlert, getAlertDefaultConfig as getCoreDefaultConfig} from '@agnos-ui/core/components/alert';
import type {ConfigValidator, SlotContent, Widget, WidgetFactory, WidgetSlotContext} from '@agnos-ui/core/types';
import {typeString} from '@agnos-ui/core/utils/writables';
import {createTypeEnum} from '@agnos-ui/core/utils/writables';
import {extendWidgetProps} from '@agnos-ui/core/services/extendWidget';
import {fadeTransition} from '../../services/transitions';
import type {BSContextualClass} from '../../types';
import {BS_CONTEXTUAL_CLASSES, type BSContextualClass} from '../../types';
import type {TransitionFn} from '@agnos-ui/core/services/transitions/baseTransitions';

export * from '@agnos-ui/core/components/alert';
Expand Down Expand Up @@ -51,7 +51,9 @@ const coreOverride: Partial<CoreProps> = {
transition: fadeTransition,
};
const configValidator: ConfigValidator<AlertExtraProps> = {
type: typeString as any,
type: createTypeEnum(BS_CONTEXTUAL_CLASSES),
structure: undefined,
children: undefined,
};

/**
Expand Down
9 changes: 8 additions & 1 deletion core-bootstrap/src/components/collapse/collapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {createTransition} from '@agnos-ui/core/services/transitions/baseTransiti
import type {ConfigValidator, Directive, PropsConfig, Widget} from '@agnos-ui/core/types';
import {stateStores, writablesForProps} from '@agnos-ui/core/utils/stores';
import {bindDirectiveNoArg} from '@agnos-ui/core/utils/directive';
import {typeBoolean} from '@agnos-ui/core/utils/writables';
import {typeBoolean, typeFunction, typeString} from '@agnos-ui/core/utils/writables';
import {collapseHorizontalTransition, collapseVerticalTransition} from '../../services/transitions/collapse';
import {asWritable, computed} from '@amadeus-it-group/tansu';

Expand Down Expand Up @@ -126,6 +126,13 @@ export function getCollapseDefaultConfig(): CollapseProps {

const commonCollapseConfigValidator: ConfigValidator<CollapseProps> = {
horizontal: typeBoolean,
onVisibleChange: typeFunction,
onHidden: typeFunction,
onShown: typeFunction,
animatedOnInit: typeBoolean,
animated: typeBoolean,
className: typeString,
visible: typeBoolean,
};

/**
Expand Down
6 changes: 6 additions & 0 deletions core-bootstrap/src/components/modal/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ const coreOverride: Partial<CoreProps> = {

const configValidator: ConfigValidator<ModalExtraProps<any>> = {
fullscreen: typeBoolean,
contentData: undefined,
children: undefined,
footer: undefined,
header: undefined,
structure: undefined,
title: undefined,
};
/**
* Retrieve a shallow copy of the default modal config
Expand Down
2 changes: 1 addition & 1 deletion core-bootstrap/src/components/pagination/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,4 @@ export function getPaginationDefaultConfig(): PaginationProps {
* @param config - an optional alert config
* @returns a PaginationWidget
*/
export const createPagination: WidgetFactory<PaginationWidget> = extendWidgetProps(createCorePagination, defaultConfigExtraProps, {});
export const createPagination: WidgetFactory<PaginationWidget> = extendWidgetProps(createCorePagination, defaultConfigExtraProps);
8 changes: 5 additions & 3 deletions core-bootstrap/src/components/progressbar/progressbar.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type {ProgressbarDirectives, ProgressbarState as CoreState, ProgressbarProps as CoreProps} from '@agnos-ui/core/components/progressbar';
import {createProgressbar as createCoreProgressbar, getProgressbarDefaultConfig as getCoreDefaultConfig} from '@agnos-ui/core/components/progressbar';
import type {ConfigValidator, SlotContent, Widget, WidgetFactory, WidgetSlotContext} from '@agnos-ui/core/types';
import {typeBoolean, typeString} from '@agnos-ui/core/utils/writables';
import {createTypeEnum, typeBoolean, typeString} from '@agnos-ui/core/utils/writables';
import {extendWidgetProps} from '@agnos-ui/core/services/extendWidget';
import type {BSContextualClass} from '../../types';
import {BS_CONTEXTUAL_CLASSES, type BSContextualClass} from '../../types';

export * from '@agnos-ui/core/components/progressbar';

Expand Down Expand Up @@ -61,7 +61,9 @@ const configValidator: ConfigValidator<ProgressbarExtraProps> = {
height: typeString,
striped: typeBoolean,
animated: typeBoolean,
type: typeString as any,
type: createTypeEnum([undefined, ...BS_CONTEXTUAL_CLASSES]),
structure: undefined,
children: undefined,
};

/**
Expand Down
2 changes: 1 addition & 1 deletion core-bootstrap/src/components/rating/rating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ export function getRatingDefaultConfig(): RatingProps {
* @returns a RatingWidget
*/

export const createRating: WidgetFactory<RatingWidget> = extendWidgetProps(createCoreRating, defaultConfigExtraProps, {});
export const createRating: WidgetFactory<RatingWidget> = extendWidgetProps(createCoreRating, defaultConfigExtraProps);
1 change: 0 additions & 1 deletion core-bootstrap/src/components/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,4 @@ export function getSelectDefaultConfig(): SelectProps<any> {
export const createSelect: <Item>(config?: PropsConfig<SelectProps<Item>>) => SelectWidget<Item> = extendWidgetProps(
createCoreSelect,
defaultConfigExtraProps,
{},
) as any;
2 changes: 1 addition & 1 deletion core-bootstrap/src/components/slider/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,4 @@ export function getSliderDefaultConfig(): SliderProps {
* @returns a SliderWidget
*/

export const createSlider: WidgetFactory<SliderWidget> = extendWidgetProps(createCoreSlider, defaultConfigExtraProps, {});
export const createSlider: WidgetFactory<SliderWidget> = extendWidgetProps(createCoreSlider, defaultConfigExtraProps);
11 changes: 10 additions & 1 deletion core-bootstrap/src/components/toast/toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,13 @@ export function getToastDefaultConfig(): ToastProps {
* @param config - an optional alert config
* @returns an ToastWidget
*/
export const createToast: WidgetFactory<ToastWidget> = extendWidgetProps(createCoreToast, defaultConfigExtraProps, {}, coreOverride);
export const createToast: WidgetFactory<ToastWidget> = extendWidgetProps(
createCoreToast,
defaultConfigExtraProps,
{
structure: undefined,
children: undefined,
header: undefined,
},
coreOverride,
);
11 changes: 11 additions & 0 deletions core-bootstrap/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
// Enumeration of all eight bootstrap contextual classes
export type BSContextualClass = 'success' | 'info' | 'warning' | 'danger' | 'primary' | 'secondary' | 'light' | 'dark';

export const BS_CONTEXTUAL_CLASSES: BSContextualClass[] = Object.values({
success: 'success',
info: 'info',
warning: 'warning',
danger: 'danger',
primary: 'primary',
secondary: 'secondary',
light: 'light',
dark: 'dark',
} satisfies {[K in BSContextualClass]: K});
11 changes: 10 additions & 1 deletion core/src/components/alert/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {ConfigValidator, Directive, PropsConfig, Widget} from '../../types'
import {noop} from '../../utils/internal/func';
import {stateStores, writablesForProps} from '../../utils/stores';
import {bindDirectiveNoArg} from '../../utils/directive';
import {typeBoolean} from '../../utils/writables';
import {typeBoolean, typeFunction, typeString} from '../../utils/writables';

export interface CommonAlertCommonPropsAndState extends WidgetsCommonPropsAndState {
/**
Expand Down Expand Up @@ -144,6 +144,15 @@ export function getCommonAlertDefaultConfig(): CommonAlertProps {

const commonAlertConfigValidator: ConfigValidator<CommonAlertProps> = {
dismissible: typeBoolean,
onVisibleChange: typeFunction,
onHidden: typeFunction,
onShown: typeFunction,
transition: typeFunction,
animatedOnInit: typeBoolean,
animated: typeBoolean,
visible: typeBoolean,
ariaCloseButtonLabel: typeString,
className: typeString,
};

/**
Expand Down
6 changes: 3 additions & 3 deletions core/src/components/pagination/pagination.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import {computed} from '@amadeus-it-group/tansu';
import type {ReadableSignal} from '@amadeus-it-group/tansu';
import {INVALID_VALUE} from '../../types';
import {bindableProp, stateStores, writablesForProps} from '../../utils/stores';
import {clamp, isNumber} from '../../utils/internal/checks';
import {typeBoolean, typeFunction, typeNumber, typeString} from '../../utils/writables';
import {createTypeEnum, typeBoolean, typeFunction, typeNumber, typeString} from '../../utils/writables';
import type {ConfigValidator, PropsConfig, Widget, Directive} from '../../types';
import {noop} from '../../utils/internal/func';
import type {WidgetsCommonPropsAndState} from '../commonProps';
Expand Down Expand Up @@ -373,7 +372,7 @@ const configValidator: ConfigValidator<PaginationProps> = {
disabled: typeBoolean,
directionLinks: typeBoolean,
boundaryLinks: typeBoolean,
size: {normalizeValue: (value) => (value === 'lg' || value === 'sm' || value === null ? value : INVALID_VALUE)},
size: createTypeEnum(['lg', 'sm', null]),
onPageChange: typeFunction,
pagesFactory: typeFunction,
ariaLabel: typeString,
Expand All @@ -386,6 +385,7 @@ const configValidator: ConfigValidator<PaginationProps> = {
ariaLiveLabel: typeFunction,
className: typeString,
pageLink: typeFunction,
ariaEllipsisLabel: typeString,
};

/**
Expand Down
3 changes: 2 additions & 1 deletion core/src/components/slider/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {ConfigValidator, Directive, PropsConfig, Widget} from '../../types'
import {noop} from '../../utils/internal/func';
import {getDecimalPrecision} from '../../utils/internal/math';
import {bindableProp, stateStores, writablesForProps} from '../../utils/stores';
import {typeArray, typeBoolean, typeFunction, typeNumber, typeNumberInRangeFactory} from '../../utils/writables';
import {typeArray, typeBoolean, typeFunction, typeNumber, typeNumberInRangeFactory, typeString} from '../../utils/writables';
import {createResizeObserver} from '../../services/resizeObserver';

export interface ProgressDisplayOptions {
Expand Down Expand Up @@ -317,6 +317,7 @@ const configValidator: ConfigValidator<SliderProps> = {
showValueLabels: typeBoolean,
showMinMaxLabels: typeBoolean,
rtl: typeBoolean,
className: typeString,
};

/**
Expand Down
2 changes: 1 addition & 1 deletion core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export interface WritableWithDefaultOptions<T> {
equal?: StoreOptions<T>['equal'];
}

export type ConfigValidator<T extends object> = {[K in keyof T]?: WritableWithDefaultOptions<T[K]>};
export type ConfigValidator<T extends object> = {[K in keyof T]: WritableWithDefaultOptions<T[K]> | undefined};

export type AttributeValue = string | number | boolean | undefined;
export type StyleKey = Exclude<
Expand Down
9 changes: 8 additions & 1 deletion core/src/utils/internal/checks.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {describe, expect, test, vi} from 'vitest';
import {allowNull, clamp, isArray, isBoolean, isFunction, isHTMLElement, isNumber, isString} from './checks';
import {allowNull, clamp, isArray, isBoolean, isFromEnum, isFunction, isHTMLElement, isNumber, isString} from './checks';

describe('Checks', () => {
test(`'isNumber' should check if value is a number`, () => {
Expand Down Expand Up @@ -140,4 +140,11 @@ describe('Checks', () => {
expect(withAllowNull(0)).toBe(false);
expect(alwaysFalse).toHaveBeenCalledTimes(2);
});

test(`'isFromEnum' should check if the value is part of the array`, () => {
const emptyEnumCheck = isFromEnum([]);
expect(emptyEnumCheck('value')).toBe(false);
const uniqEnumCheck = isFromEnum(['value']);
expect(uniqEnumCheck('value')).toBe(true);
});
});
9 changes: 9 additions & 0 deletions core/src/utils/internal/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,12 @@ export const allowNull =
<T>(isType: (value: any) => value is T) =>
(value: any): value is T | null =>
value === null || isType(value);

/**
* Builds a new type guard to check if an element belongs to an enum list
* @param list - the list of all enum values
* @returns the type guard
*/
export function isFromEnum<T>(list: T[]) {
return (value: any): value is T => list.includes(value);
}
1 change: 1 addition & 0 deletions core/src/utils/stores.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ describe(`Stores service`, () => {
return value;
},
},
b: undefined,
},
);
const a: number[] = [];
Expand Down
2 changes: 1 addition & 1 deletion core/src/utils/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ export const writablesWithDefault = <T extends object>(
export const writablesForProps = <T extends object>(
defConfig: T,
propsConfig?: PropsConfig<T>,
options?: {[K in keyof T]?: WritableWithDefaultOptions<T[K]>},
options?: {[K in keyof T]: WritableWithDefaultOptions<T[K]> | undefined},
): [ToWritableSignal<T>, ReturnType<typeof createPatch<T>>] => {
const stores = writablesWithDefault(defConfig, propsConfig, options);
return [stores, createPatch(stores)];
Expand Down
14 changes: 13 additions & 1 deletion core/src/utils/writables.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {allowNull, clamp, isArray, isBoolean, isFunction, isHTMLElement, isNumber, isString} from './internal/checks';
import {allowNull, clamp, isArray, isBoolean, isFromEnum, isFunction, isHTMLElement, isNumber, isString} from './internal/checks';
import type {WritableWithDefaultOptions} from '../types';
import {INVALID_VALUE} from '../types';

Expand Down Expand Up @@ -91,3 +91,15 @@ export const typeArray: WritableWithDefaultOptions<any[]> = {
}
},
};

/**
* Build an enum normalizer
* @template T - the enum type
* @param enumList - list of enum values to check
* @returns the enum normalizer
*/
export function createTypeEnum<T>(enumList: T[]): WritableWithDefaultOptions<T> {
return {
normalizeValue: testToNormalizeValue(isFromEnum(enumList)),
};
}

0 comments on commit e311275

Please sign in to comment.