Skip to content

Commit

Permalink
feat: remove FormItemTopLabel multiline prop (#7578)
Browse files Browse the repository at this point in the history
h2. Описание

Сейчас в компонент `FormItemTopLabel` можно прокинуть проп `multiline`. А также данное значение можно получить из контекста `FormItemContext` из поля `topMultiline`. Поэтому нужно выпилить проп `multiline` и всегда брать значение из контекста.

h2. Изменения

- Выпилил проп `multiline` из `FormItemTopLabel`
- Обновил скриншотные тесты
- Реализованы кодмоды для правильного обновления, чтобы визуально ничего не сломалось

h2. Release notes
h2. BREAKING CHANGE

- FormItem: у сабкомпонента `FormItem.TopLabel` параметр `multiline` был удален, теперь он всегда берется из параметра `topMultiline` у компонент `FormItem`
  ```diff
  <FormItem
  +  topMultiline
     top={
       <FormItem.Top>
  -      <FormItem.TopLabel htmlFor="about" multiline>Дополнительная информация</FormItem.TopLabel>
  +      <FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel>
         <FormItem.TopAside>0/100</FormItem.TopAside>
       </FormItem.Top>
     }
  >
    <div/>
  </FormItem>
  ```
  • Loading branch information
EldarMuhamethanov authored Sep 17, 2024
1 parent a5dba7b commit 4902ab8
Show file tree
Hide file tree
Showing 17 changed files with 410 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
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>

{/* test 8: remove unnecessary attribute */}
<FormItem
topMultiline={false}
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,112 @@
// 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>
{/* test 8: remove unnecessary attribute */}
<FormItem
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}`),
);
});
140 changes: 140 additions & 0 deletions packages/codemods/src/transforms/v7/form-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
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;

const prettifyTopMultilineAttribute = () => {
// Избавляемся от лишнего:
// если 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);
}
}
}
};

// Проверяем существующий 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) {
prettifyTopMultilineAttribute();
return;
}
const newTopMultilineValue = topLabelMultiline === true ? null : topLabelMultiline;
// Обновляем или добавляем topMultiline проп
if (topMultiline) {
// Если у FormItem задан topMultiline -> переопределяем
topMultiline.value = newTopMultilineValue;
} else if (topLabelMultiline) {
// Если у FormItem не задан topMultiline
// добавляем его в аргументы
formItemAttributes?.push(
j.jsxAttribute(j.jsxIdentifier('topMultiline'), newTopMultilineValue),
);
}
prettifyTopMultilineAttribute();
});

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

0 comments on commit 4902ab8

Please sign in to comment.