Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remove FormItemTopLabel multiline prop #7578

Merged
merged 9 commits into from
Sep 17, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { FormItem } from '@vkontakte/vkui';
import React from 'react';
import '@vkontakte/vkui/dist/vkui.css';

const App = () => {
const flag = true;
const calculateMultiline = () => true;
return (
<React.Fragment>
{/* test 1: should find TopLabel inside top prop */}
<FormItem
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about" multiline>Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
>
<div/>
</FormItem>

{/* test 2: should find TopLabel inside topNode prop */}
<FormItem
topNode={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about" multiline>Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
>
<div/>
</FormItem>

{/* test 3: TopLabel multiline={true} -> FormItem topMultiline */}
<FormItem
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about" multiline={true}>Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
topMultiline={false}
>
<div/>
</FormItem>

{/* test 4: TopLabel multiline={false} -> FormItem topMultiline={false} */}
<FormItem
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about" multiline={false}>Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
topMultiline={true}
>
<div/>
</FormItem>

{/* test 5: should find multiline in child of FormItem */}
<FormItem>
<FormItem.Top>
<FormItem.TopLabel htmlFor="about" multiline>Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
</FormItem>

{/* test 6: should move multiline expression in topMultiline */}
<FormItem
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about" multiline={flag && calculateMultiline()}>Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
>
<div/>
</FormItem>

{/* test 6: should replace topMultiline expression by multiline expression */}
<FormItem
topMultiline={!flag || calculateMultiline()}
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about" multiline={flag && calculateMultiline()}>Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
>
<div/>
</FormItem>

{/* test 7: do nothing because multiline not set */}
<FormItem
topMultiline
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
>
<div/>
</FormItem>
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`form-item transforms correctly 1`] = `
"import { FormItem } from '@vkontakte/vkui';
import React from 'react';
import '@vkontakte/vkui/dist/vkui.css';

const App = () => {
const flag = true;
const calculateMultiline = () => true;
return (
(<React.Fragment>
{/* test 1: should find TopLabel inside top prop */}
<FormItem
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
topMultiline>
<div/>
</FormItem>
{/* test 2: should find TopLabel inside topNode prop */}
<FormItem
topNode={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
topMultiline>
<div/>
</FormItem>
{/* test 3: TopLabel multiline={true} -> FormItem topMultiline */}
<FormItem
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
topMultiline
>
<div/>
</FormItem>
{/* test 4: TopLabel multiline={false} -> FormItem topMultiline={false} */}
<FormItem
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}>
<div/>
</FormItem>
{/* test 5: should find multiline in child of FormItem */}
<FormItem topMultiline>
<FormItem.Top>
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
</FormItem>
{/* test 6: should move multiline expression in topMultiline */}
<FormItem
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
topMultiline={flag && calculateMultiline()}>
<div/>
</FormItem>
{/* test 6: should replace topMultiline expression by multiline expression */}
<FormItem
topMultiline={flag && calculateMultiline()}
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
>
<div/>
</FormItem>
{/* test 7: do nothing because multiline not set */}
<FormItem
topMultiline
top={
<FormItem.Top>
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel>
<FormItem.TopAside>0/100</FormItem.TopAside>
</FormItem.Top>
}
>
<div/>
</FormItem>
</React.Fragment>)
);
};"
`;
11 changes: 11 additions & 0 deletions packages/codemods/src/transforms/v7/__tests__/form-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
jest.autoMockOff();
import { defineSnapshotTestFromFixture } from '../../../testHelpers/testHelper';

const name = 'form-item';
const fixtures = ['basic'] as const;

describe(name, () => {
fixtures.forEach((test) =>
defineSnapshotTestFromFixture(__dirname, name, global.TRANSFORM_OPTIONS, `${name}/${test}`),
);
});
150 changes: 150 additions & 0 deletions packages/codemods/src/transforms/v7/form-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { API, FileInfo, JSXAttribute, JSXElement, JSXExpressionContainer } from 'jscodeshift';
import { getImportInfo } from '../../codemod-helpers';
import { JSCodeShiftOptions } from '../../types';

export const parser = 'tsx';

export default function transformer(file: FileInfo, api: API, options: JSCodeShiftOptions) {
const { alias } = options;
const j = api.jscodeshift;
const source = j(file.source);
const { localName } = getImportInfo(j, file, 'FormItem', alias);

if (!localName) {
return source.toSource();
}

function processTopLabel(element: JSXElement): JSXExpressionContainer | true | undefined {
if (
element.openingElement.name.type === 'JSXMemberExpression' &&
element.openingElement.name.property?.name === 'TopLabel'
) {
let multilineValue: JSXExpressionContainer | true | undefined;
element.openingElement.attributes = element.openingElement.attributes?.filter((attr) => {
if (attr.type === 'JSXAttribute' && attr.name.name === 'multiline') {
if (!attr.value) {
multilineValue = true;
} else if (attr.value?.type === 'JSXExpressionContainer') {
multilineValue = attr.value;
}
return false;
}
return true;
});
return multilineValue;
}
return undefined;
}

function findTopLabelRecursive(element: JSXElement): JSXExpressionContainer | true | undefined {
let result = processTopLabel(element);
if (result !== undefined) {
return result;
}

if (!element.children) {
return undefined;
}

for (const child of element.children) {
if (child.type === 'JSXElement') {
result = findTopLabelRecursive(child);
if (result !== undefined) {
return result;
}
}
}

return undefined;
}

source.find(j.JSXElement, { openingElement: { name: { name: localName } } }).forEach((path) => {
const formItem = path.node;
let topMultiline: JSXAttribute | undefined;
let topLabelMultiline: JSXExpressionContainer | true | undefined;
const formItemAttributes = formItem.openingElement.attributes;

// Проверяем существующий topMultiline проп
formItemAttributes?.forEach((attr) => {
if (attr.type === 'JSXAttribute' && attr.name.name === 'topMultiline') {
topMultiline = attr;
}
});

// Ищем FormItem.TopLabel в пропе top и topNode
formItemAttributes?.forEach((attr) => {
if (
attr.type === 'JSXAttribute' &&
(attr.name.name === 'top' || attr.name.name === 'topNode')
) {
if (attr.value?.type === 'JSXElement') {
topLabelMultiline = findTopLabelRecursive(attr.value);
} else if (
attr.value?.type === 'JSXExpressionContainer' &&
attr.value.expression.type === 'JSXElement'
) {
topLabelMultiline = findTopLabelRecursive(attr.value.expression);
}
}
});

// Ищем FormItem.TopLabel в children
if (topLabelMultiline === undefined && formItem.children) {
for (const child of formItem.children) {
if (child.type === 'JSXElement') {
topLabelMultiline = findTopLabelRecursive(child);
if (topLabelMultiline !== undefined) {
break;
}
}
}
}
if (!topLabelMultiline) {
return;
}

// Обновляем или добавляем topMultiline проп
if (topMultiline) {
// Если у FormItem задан topMultiline
if (!topLabelMultiline) {
// Если у FormItemTopLabel multiline не задан, значит
// topMultiline нужно вообще убрать из FormItem
formItemAttributes?.splice(formItemAttributes.indexOf(topMultiline), 1);
andrey-medvedev-vk marked this conversation as resolved.
Show resolved Hide resolved
} else if (topLabelMultiline === true) {
// Если у FormItemTopLabel multiline == true, значит
// У FormField у topMultiline можно убрать значение
topMultiline.value = null;
} else {
// Для остальных случаев для topMultiline устанавливаем то же
// самое значение, что было в FormItemTopLabel multiline
topMultiline.value = topLabelMultiline;
}
} else if (topLabelMultiline) {
// Если у FormItem не задан topMultiline
// добавляем его в аргументы
const newAttribute = j.jsxAttribute(
j.jsxIdentifier('topMultiline'),
topLabelMultiline === true ? null : topLabelMultiline,
);
formItemAttributes?.push(newAttribute);
}

// Избавляемся от лишнего:
// если topMultiline={false} можно убрать аттрибут
// если topMultiline={true} можно убрать {true}
if (topMultiline && formItemAttributes?.includes(topMultiline)) {
if (
topMultiline?.value?.type === 'JSXExpressionContainer' &&
topMultiline.value.expression.type === 'BooleanLiteral'
) {
if (topMultiline.value.expression.value) {
topMultiline.value = null;
} else {
formItemAttributes.splice(formItemAttributes.indexOf(topMultiline), 1);
}
}
}
});

return source.toSource();
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ export const FormItemTopAsidePlayground = (props: ComponentPlaygroundProps) => {
children: [<Input key={0} placeholder="Введите ваше значение" />],
},
{
topMultiline: [true],
topLabel: [
<FormItem.TopLabel key="0" multiline>
<FormItem.TopLabel key="0">
Дополнительная информация с достаточно длинным описанием
</FormItem.TopLabel>,
],
Expand Down
Loading