Strict boolean int parse

141978bee116df80d35a06981813bb4a7de291cd
This commit is contained in:
4eb0da
2024-03-12 13:10:54 +03:00
parent d12ce6e7d8
commit f37b162829
18 changed files with 139 additions and 64 deletions
+2
View File
@@ -12076,6 +12076,7 @@
"client/web/divkit/src/utils/nonNegativeModulo.ts":"divkit/public/client/web/divkit/src/utils/nonNegativeModulo.ts",
"client/web/divkit/src/utils/padLeft.ts":"divkit/public/client/web/divkit/src/utils/padLeft.ts",
"client/web/divkit/src/utils/prepareBase64.ts":"divkit/public/client/web/divkit/src/utils/prepareBase64.ts",
"client/web/divkit/src/utils/previewValue.ts":"divkit/public/client/web/divkit/src/utils/previewValue.ts",
"client/web/divkit/src/utils/propToString.ts":"divkit/public/client/web/divkit/src/utils/propToString.ts",
"client/web/divkit/src/utils/pxToEm.ts":"divkit/public/client/web/divkit/src/utils/pxToEm.ts",
"client/web/divkit/src/utils/shadow.ts":"divkit/public/client/web/divkit/src/utils/shadow.ts",
@@ -14220,6 +14221,7 @@
"client/web/divkit/tests/templates/string_enum_property.test.ts":"divkit/public/client/web/divkit/tests/templates/string_enum_property.test.ts",
"client/web/divkit/tests/utils/__snapshots__/applyTemplate.test.ts.snap":"divkit/public/client/web/divkit/tests/utils/__snapshots__/applyTemplate.test.ts.snap",
"client/web/divkit/tests/utils/__snapshots__/background.test.ts.snap":"divkit/public/client/web/divkit/tests/utils/__snapshots__/background.test.ts.snap",
"client/web/divkit/tests/utils/__snapshots__/correctBooleanInt.test.ts.snap":"divkit/public/client/web/divkit/tests/utils/__snapshots__/correctBooleanInt.test.ts.snap",
"client/web/divkit/tests/utils/__snapshots__/filters.test.ts.snap":"divkit/public/client/web/divkit/tests/utils/__snapshots__/filters.test.ts.snap",
"client/web/divkit/tests/utils/__snapshots__/simpleCheckInput.test.ts.snap":"divkit/public/client/web/divkit/tests/utils/__snapshots__/simpleCheckInput.test.ts.snap",
"client/web/divkit/tests/utils/applyTemplate.test.ts":"divkit/public/client/web/divkit/tests/utils/applyTemplate.test.ts",
+4 -3
View File
@@ -77,6 +77,7 @@
import { arrayInsert, arrayRemove } from '../actions/array';
import { copyToClipboard } from '../actions/copyToClipboard';
import { filterEnabledActions } from '../utils/filterEnabledActions';
import { correctBooleanInt } from '../utils/correctBooleanInt';
export let id: string;
export let json: Partial<DivJson> = {};
@@ -718,7 +719,7 @@
const actionUrl = action.url ? String(action.url) : '';
const actionTyped = action.typed;
if (!filterEnabledActions(action)) {
if (!filterEnabledActions(action, logError)) {
return;
}
@@ -902,7 +903,7 @@
return;
}
const filtered = actions.filter(filterEnabledActions);
const filtered = actions.filter(action => filterEnabledActions(action, logError));
for (let i = 0; i < filtered.length; ++i) {
let action = filtered[i];
@@ -1529,7 +1530,7 @@
if (
// if condition is truthy
conditionResult.value &&
correctBooleanInt(conditionResult.value, false, logError) &&
// and trigger mode matches
(mode === 'on_variable' || mode === 'on_condition' && prevConditionResult === false)
) {
@@ -54,6 +54,7 @@
import { ContentAlignmentHorizontalMapped, correctContentAlignmentHorizontal } from '../../utils/correctContentAlignmentHorizontal';
import { Truthy } from '../../utils/truthy';
import { assignIfDifferent } from '../../utils/assignIfDifferent';
import { correctBooleanInt } from '../../utils/correctBooleanInt';
export let componentContext: ComponentContext<DivContainerData>;
export let layoutParams: LayoutParams | undefined = undefined;
@@ -137,7 +138,7 @@
}
const selectorVal = componentContext.getJsonWithVars(prototype.selector, additionalVars);
if (selectorVal) {
if (correctBooleanInt(selectorVal, true, componentContext.logError)) {
div = prototype.div;
break;
}
@@ -209,9 +210,12 @@
if (style) {
separator = {
show_at_start: Boolean($jsonSeparator.show_at_start ?? false),
show_at_end: Boolean($jsonSeparator.show_at_end ?? false),
show_between: Boolean($jsonSeparator.show_between ?? true),
show_at_start:
correctBooleanInt($jsonSeparator.show_at_start, false, componentContext.logError),
show_at_end:
correctBooleanInt($jsonSeparator.show_at_end, false, componentContext.logError),
show_between:
correctBooleanInt($jsonSeparator.show_between, true, componentContext.logError),
style,
margins: prepareMargins($jsonSeparator.margins)
};
@@ -233,9 +237,12 @@
if (style) {
lineSeparator = {
show_at_start: Boolean($jsonLineSeparator.show_at_start ?? false),
show_at_end: Boolean($jsonLineSeparator.show_at_end ?? false),
show_between: Boolean($jsonLineSeparator.show_between ?? true),
show_at_start:
correctBooleanInt($jsonLineSeparator.show_at_start, false, componentContext.logError),
show_at_end:
correctBooleanInt($jsonLineSeparator.show_at_end, false, componentContext.logError),
show_between:
correctBooleanInt($jsonLineSeparator.show_between, true, componentContext.logError),
style,
margins: prepareMargins($jsonLineSeparator.margins)
};
@@ -308,7 +315,7 @@
valign: contentVAlign,
halign: contentHAlign,
wrap,
overflow: ($jsonClipToBounds === false || $jsonClipToBounds === 0) ? 'visible' : undefined
overflow: correctBooleanInt($jsonClipToBounds, true, componentContext.logError) ? undefined : 'visible'
};
$: style = {
@@ -31,6 +31,7 @@
import { debounce } from '../../utils/debounce';
import { Truthy } from '../../utils/truthy';
import { nonNegativeModulo } from '../../utils/nonNegativeModulo';
import { correctBooleanInt } from '../../utils/correctBooleanInt';
export let componentContext: ComponentContext<DivGalleryData>;
export let layoutParams: LayoutParams | undefined = undefined;
@@ -228,6 +229,8 @@
[gridTemplate]: joinTemplateSizes(templateSizes)
};
$: restrictScroll = correctBooleanInt($jsonRestrictParentScroll, false, componentContext.logError);
$: mods = {
orientation,
'scroll-snap': scrollSnap,
@@ -502,7 +505,7 @@
{replaceItems}
>
<div
class="{css.gallery__scroller} {$jsonRestrictParentScroll ? rootCss['root_restrict-scroll'] : ''}"
class="{css.gallery__scroller} {restrictScroll ? rootCss['root_restrict-scroll'] : ''}"
bind:this={scroller}
on:scroll={shouldCheckArrows ? updateArrowsVisibility : null}
style={makeStyle(scrollerStyle)}
@@ -32,6 +32,7 @@
import { correctTintMode } from '../../utils/correctTintMode';
import { getCssFilter } from '../../utils/filters';
import { prepareBase64 } from '../../utils/prepareBase64';
import { correctBooleanInt } from '../../utils/correctBooleanInt';
export let componentContext: ComponentContext<DivImageData>;
export let layoutParams: LayoutParams | undefined = undefined;
@@ -145,6 +146,8 @@
$: alt = $jsonA11y?.description || '';
$: preload = correctBooleanInt($jsonPreloadRequired, false, componentContext.logError);
$: {
const newRatio = $jsonAspect?.ratio;
if (newRatio && isPositiveNumber(newRatio)) {
@@ -249,7 +252,7 @@
bind:this={img}
class={css.image__image}
src={state === STATE_ERROR ? FALLBACK_IMAGE : imageUrl}
loading={$jsonPreloadRequired ? 'eager' : 'lazy'}
loading={preload ? 'eager' : 'lazy'}
decoding="async"
style={makeStyle(style)}
{alt}
@@ -263,7 +266,7 @@
bind:this={img}
class={css.image__image}
src={state === STATE_ERROR ? FALLBACK_IMAGE : imageUrl}
loading={$jsonPreloadRequired ? 'eager' : 'lazy'}
loading={preload ? 'eager' : 'lazy'}
decoding="async"
style={makeStyle(style)}
{alt}
@@ -48,6 +48,7 @@
import { correctAlignmentHorizontal } from '../../utils/correctAlignmentHorizontal';
import { AlignmentVerticalMapped, correctAlignmentVertical } from '../../utils/correctAlignmentVertical';
import { calcSelectionOffset, setSelectionOffset } from '../../utils/contenteditable';
import { correctBooleanInt } from '../../utils/correctBooleanInt';
export let componentContext: ComponentContext<DivInputData>;
export let layoutParams: LayoutParams | undefined = undefined;
@@ -213,6 +214,8 @@
$: isMultiline = keyboardType === 'multi_line_text'/* && isPositiveNumber($jsonVisibleMaxLines) && $jsonVisibleMaxLines > 1*/;
$: selectAllOnFocus = correctBooleanInt($jsonSelectAll, false, componentContext.logError);
$: {
if (isPositiveNumber($jsonVisibleMaxLines)) {
maxHeight = `calc(${$jsonVisibleMaxLines * (lineHeight || 1.25) * (fontSize / 10) + 'em'} + ${pxToEmWithUnits(correctNonNegativeNumber($jsonPaddings?.top, 0) + correctNonNegativeNumber($jsonPaddings?.bottom, 0))})`;
@@ -450,8 +453,8 @@
bind:innerText={contentEditableValue}
on:input={onInput}
on:paste={onPaste}
on:mousedown={$jsonSelectAll ? onMousedown : undefined}
on:click={$jsonSelectAll ? onClick : undefined}
on:mousedown={selectAllOnFocus ? onMousedown : undefined}
on:click={selectAllOnFocus ? onClick : undefined}
on:focus={focusHandler}
on:blur={blurHandler}
>
@@ -470,8 +473,8 @@
{placeholder}
{value}
on:input={onInput}
on:mousedown={$jsonSelectAll ? onMousedown : undefined}
on:click={$jsonSelectAll ? onClick : undefined}
on:mousedown={selectAllOnFocus ? onMousedown : undefined}
on:click={selectAllOnFocus ? onClick : undefined}
on:focus={focusHandler}
on:blur={blurHandler}
>
@@ -27,6 +27,7 @@
import { debounce } from '../../utils/debounce';
import { Truthy } from '../../utils/truthy';
import { nonNegativeModulo } from '../../utils/nonNegativeModulo';
import { correctBooleanInt } from '../../utils/correctBooleanInt';
export let componentContext: ComponentContext<DivPagerData>;
export let layoutParams: LayoutParams | undefined = undefined;
@@ -140,6 +141,8 @@
}
}
$: restrictScroll = correctBooleanInt($jsonRestrictParentScroll, false, componentContext.logError);
$: style = {
'grid-gap': itemSpacing,
padding,
@@ -318,7 +321,7 @@
{replaceItems}
>
<div
class="{css.pager__items} {$jsonRestrictParentScroll ? rootCss['root_restrict-scroll'] : ''}"
class="{css.pager__items} {restrictScroll ? rootCss['root_restrict-scroll'] : ''}"
style={makeStyle(style)}
bind:this={pagerItemsWrapper}
on:scroll={onScrollDebounced}
@@ -37,7 +37,8 @@
import { correctNonNegativeNumber } from '../../utils/correctNonNegativeNumber';
import { edgeInsertsToCss } from '../../utils/edgeInsertsToCss';
import { filterEnabledActions } from '../../utils/filterEnabledActions';
import { nonNegativeModulo } from '../../utils/nonNegativeModulo';
import { nonNegativeModulo } from '../../utils/nonNegativeModulo';
import { correctBooleanInt } from '../../utils/correctBooleanInt';
export let componentContext: ComponentContext<DivTabsData>;
export let layoutParams: LayoutParams | undefined = undefined;
@@ -324,7 +325,7 @@
}
$: {
if ($jsonSeparator) {
if (correctBooleanInt($jsonSeparator, false, componentContext.logError)) {
if ($jsonSeparatorColor) {
separatorBackground = correctColor($jsonSeparatorColor, 1, separatorBackground);
}
@@ -338,14 +339,14 @@
margin: separatorMargins
};
$: isSwipeEnabled = typeof $jsonSwipeEnabled === 'undefined' ?
true :
Boolean($jsonSwipeEnabled);
$: isSwipeEnabled = correctBooleanInt($jsonSwipeEnabled, true, componentContext.logError);
$: {
titlePadding = correctEdgeInsertsObject($jsonTitlePaddings ? $jsonTitlePaddings : undefined, titlePadding);
}
$: restrictScroll = correctBooleanInt($jsonRestrictParentScroll, false, componentContext.logError);
function updateItems(items: MaybeMissing<TabItem>[]): void {
if (hasError) {
return;
@@ -661,7 +662,7 @@
<!-- svelte-ignore a11y-interactive-supports-focus -->
<div
bind:this={tabsElem}
class="{css.tabs__list} {$jsonRestrictParentScroll ? rootCss['root_restrict-scroll'] : ''}"
class="{css.tabs__list} {restrictScroll ? rootCss['root_restrict-scroll'] : ''}"
role="tablist"
style:--divkit-tabs-title-padding={titlePadding ? edgeInsertsToCss(titlePadding, $direction) : ''}
style:--divkit-tabs-font-size={pxToEm(tabFontSize)}
@@ -692,7 +693,8 @@
})}
actions={
item.title_click_action && !componentContext.fakeElement ?
[item.title_click_action].filter(filterEnabledActions) :
[item.title_click_action]
.filter(action => filterEnabledActions(action, componentContext.logError)) :
[]
}
attrs={{
@@ -714,7 +716,7 @@
></div>
{/if}
<div
class="{css.tabs__panels} {$jsonRestrictParentScroll ? rootCss['root_restrict-scroll'] : ''}"
class="{css.tabs__panels} {restrictScroll ? rootCss['root_restrict-scroll'] : ''}"
bind:this={panelsWrapper}
>
<div
@@ -141,7 +141,7 @@
newMaxLines = lines;
newLineClamp = lines;
newMultiline = true;
} else if ($jsonAutoEllipsize && $jsonMaxLines !== 1) {
} else if (correctBooleanInt($jsonAutoEllipsize, false, componentContext.logError) && $jsonMaxLines !== 1) {
newMultiline = true;
}
@@ -205,7 +205,7 @@
}
$: {
selectable = correctBooleanInt($jsonSelectable, selectable);
selectable = correctBooleanInt($jsonSelectable, selectable, componentContext.logError);
}
function updateRenderList(
@@ -326,7 +326,7 @@
newRenderList.push({
text: content.substring(prevIndex, index),
textStyles,
actions: item.type === 'rangeEnd' && item.range?.actions?.filter(filterEnabledActions) || undefined
actions: item.type === 'rangeEnd' && item.range?.actions?.filter(action => filterEnabledActions(action, componentContext.logError)) || undefined
});
}
@@ -362,7 +362,7 @@
height: imageHeight,
wrapperStyle,
svgFilterId,
preloadRequired: Boolean(item.image.preload_required)
preloadRequired: correctBooleanInt(item.image.preload_required, false, componentContext.logError)
}
});
}
@@ -71,6 +71,7 @@
import { isDeepEqual } from '../../utils/isDeepEqual';
import { filterEnabledActions } from '../../utils/filterEnabledActions';
import { isPrefersReducedMotion } from '../../utils/isPrefersReducedMotion';
import { correctBooleanInt } from '../../utils/correctBooleanInt';
import Actionable from './Actionable.svelte';
import OuterBackground from './OuterBackground.svelte';
@@ -262,7 +263,7 @@
let newBackgroundRadius = '';
if (border) {
if (border.has_shadow) {
if (correctBooleanInt(border.has_shadow, false, componentContext.logError)) {
const shadow = border.shadow;
if (shadow) {
newBorderStyle['box-shadow'] = shadowToCssBoxShadow(shadow);
@@ -348,7 +349,8 @@
) {
widthType = 'content';
if (
type === 'wrap_content' && $jsonWidth?.constrained ||
type === 'wrap_content' &&
correctBooleanInt($jsonWidth?.constrained, false, componentContext.logError) ||
(type === 'match_parent' || !type) && layoutParams.parentHorizontalWrapContent
) {
newWidthMods['width-constrained'] = true;
@@ -457,7 +459,8 @@
} else {
heightType = 'content';
if (
type === 'wrap_content' && $jsonHeight?.constrained ||
type === 'wrap_content' &&
correctBooleanInt($jsonHeight?.constrained, false, componentContext.logError) ||
type === 'match_parent' && layoutParams.parentVerticalWrapContent
) {
newHeightMods['height-constrained'] = true;
@@ -615,12 +618,14 @@
componentContext.logError(wrapError(new Error(`Cannot use action on component "${customActions}"`)));
}
const filterFn = (action: MaybeMissing<Action>) => filterEnabledActions(action, componentContext.logError);
// todo check parent actions with customActions
actions = newActions.filter(filterEnabledActions);
doubleTapActions = newDoubleTapActions.filter(filterEnabledActions);
longTapActions = newLongTapActions.filter(filterEnabledActions);
focusActions = newFocusActions.filter(filterEnabledActions);
blurActions = newBlurActions.filter(filterEnabledActions);
actions = newActions.filter(filterFn);
doubleTapActions = newDoubleTapActions.filter(filterFn);
longTapActions = newLongTapActions.filter(filterFn);
focusActions = newFocusActions.filter(filterFn);
blurActions = newBlurActions.filter(filterFn);
}
$: {
@@ -84,11 +84,11 @@
}
}
$: loop = correctBooleanInt($jsonRepeatable, loop);
$: loop = correctBooleanInt($jsonRepeatable, loop, componentContext.logError);
$: autoplay = correctBooleanInt($jsonAutostart, autoplay);
$: autoplay = correctBooleanInt($jsonAutostart, autoplay, componentContext.logError);
$: muted = correctBooleanInt($jsonMuted, muted);
$: muted = correctBooleanInt($jsonMuted, muted, componentContext.logError);
$: poster = typeof $jsonPreview === 'string' ? prepareBase64($jsonPreview) : poster;
@@ -5,6 +5,7 @@ import type { MaybeMissing } from '../expressions/json';
import type { ComponentContext } from '../types/componentContext';
import { getUrlSchema, isBuiltinSchema } from '../utils/url';
import { correctNonNegativeNumber } from '../utils/correctNonNegativeNumber';
import { correctBooleanInt } from '../utils/correctBooleanInt';
interface CalcedAction {
index: number | undefined;
@@ -30,10 +31,6 @@ function checkPercentage(isVisibility: boolean, val: number | undefined, default
return defaultVal;
}
function filterActions(it: CalcedAction): it is IndexedCalcedAction {
return it.is_enabled !== 0 && it.is_enabled !== false && it.index !== undefined;
}
export function visibilityAction(node: HTMLElement, {
visibilityActions,
disappearActions,
@@ -43,7 +40,7 @@ export function visibilityAction(node: HTMLElement, {
visibilityActions?: MaybeMissing<VisibilityAction>[];
disappearActions?: MaybeMissing<DisappearAction>[];
rootCtx: RootCtxValue;
componentContext: ComponentContext;
componentContext: ComponentContext
}) {
const visibilityStatus: {
type: 'visibility' | 'disappear';
@@ -110,8 +107,12 @@ export function visibilityAction(node: HTMLElement, {
const totalStore = derived(calcedList, values => values);
const filterActions = (it: CalcedAction): it is IndexedCalcedAction => {
return correctBooleanInt(it.is_enabled, true, componentContext.logError);
};
const unsubscribe = totalStore.subscribe(values => {
const filtered = values.filter(filterActions);
const filtered = values.filter<IndexedCalcedAction>(filterActions);
const map: Record<number, IndexedCalcedAction> = {};
filtered.forEach(it => {
@@ -1,6 +1,16 @@
export function correctBooleanInt(val: number | boolean | undefined, defaultVal: boolean): boolean {
import { previewValue } from './previewValue';
import { LogError, wrapError } from './wrapError';
export function correctBooleanInt(
val: unknown,
defaultVal: boolean,
logError: LogError
): boolean {
if (val === 1 || val === 0 || val === false || val === true) {
return Boolean(val);
}
if (val !== undefined) {
logError(wrapError(new Error(`Invalid value: ${previewValue(val)}. Expression<Bool> expected.`)));
}
return defaultVal;
}
@@ -1,6 +1,11 @@
import type { Action, DisappearAction, VisibilityAction } from '../../typings/common';
import type { MaybeMissing } from '../expressions/json';
import { correctBooleanInt } from './correctBooleanInt';
import { LogError } from './wrapError';
export function filterEnabledActions(action: MaybeMissing<Action | VisibilityAction | DisappearAction>): boolean {
return action.is_enabled !== 0 && action.is_enabled !== false;
export function filterEnabledActions(
action: MaybeMissing<Action | VisibilityAction | DisappearAction>,
logError: LogError
): boolean {
return correctBooleanInt(action.is_enabled, true, logError);
}
@@ -0,0 +1,13 @@
export function previewValue(value: unknown): string {
if (typeof value === 'object' && value) {
if (Array.isArray(value)) {
return 'array';
}
return 'object';
} else if (value === null) {
return 'null';
} else if (value === undefined) {
return 'undefined';
}
return JSON.stringify(value);
}
@@ -3,6 +3,7 @@ import type { MaskData } from './mask/baseInputMask';
import { FixedLengthInputMask } from './mask/fixedLengthInputMask';
import { MaybeMissing } from '../expressions/json';
import { FixedLengthInputMask as FixedLengthInputMaskType } from '../types/input';
import { correctBooleanInt } from './correctBooleanInt';
export function updateFixedMask(
mask: MaybeMissing<FixedLengthInputMaskType>,
@@ -15,7 +16,7 @@ export function updateFixedMask(
) {
const maskData: MaskData = {
pattern: mask.pattern,
alwaysVisible: Boolean(mask.always_visible),
alwaysVisible: correctBooleanInt(mask.always_visible, false, logError),
decoding: mask.pattern_elements.map(it => ({
key: it.key as string,
filter: it.regex && typeof it.regex === 'string' ? it.regex : undefined,
@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`correctBooleanInt simple 1`] = `
[
[
[Error: Invalid value: 2. Expression<Bool> expected.],
],
[
[Error: Invalid value: 2. Expression<Bool> expected.],
],
]
`;
@@ -2,17 +2,21 @@ import { correctBooleanInt } from '../../src/utils/correctBooleanInt';
describe('correctBooleanInt', () => {
test('simple', () => {
expect(correctBooleanInt(2, false)).toBe(false);
expect(correctBooleanInt(2, true)).toBe(true);
expect(correctBooleanInt(1, false)).toBe(true);
expect(correctBooleanInt(1, true)).toBe(true);
expect(correctBooleanInt(0, false)).toBe(false);
expect(correctBooleanInt(0, true)).toBe(false);
expect(correctBooleanInt(true, false)).toBe(true);
expect(correctBooleanInt(true, true)).toBe(true);
expect(correctBooleanInt(false, false)).toBe(false);
expect(correctBooleanInt(false, true)).toBe(false);
expect(correctBooleanInt(undefined, false)).toBe(false);
expect(correctBooleanInt(undefined, true)).toBe(true);
const logError = jest.fn();
expect(correctBooleanInt(2, false, logError)).toBe(false);
expect(correctBooleanInt(2, true, logError)).toBe(true);
expect(correctBooleanInt(1, false, logError)).toBe(true);
expect(correctBooleanInt(1, true, logError)).toBe(true);
expect(correctBooleanInt(0, false, logError)).toBe(false);
expect(correctBooleanInt(0, true, logError)).toBe(false);
expect(correctBooleanInt(true, false, logError)).toBe(true);
expect(correctBooleanInt(true, true, logError)).toBe(true);
expect(correctBooleanInt(false, false, logError)).toBe(false);
expect(correctBooleanInt(false, true, logError)).toBe(false);
expect(correctBooleanInt(undefined, false, logError)).toBe(false);
expect(correctBooleanInt(undefined, true, logError)).toBe(true);
expect(logError.mock.calls).toMatchSnapshot();
});
});