From 2806032d616c529cd50f2cdcbea1cf806341eb4e Mon Sep 17 00:00:00 2001 From: vitaliy Date: Fri, 3 Jul 2026 11:51:48 +0300 Subject: [PATCH 1/3] Fixed BlockTemplateLineMarkerProvider --- .../php/BlockTemplateLineMarkerProvider.java | 146 +++++++++++++++++- .../project/PhpFrameworkSettingsForm.java | 5 + .../magento2plugin/project/SettingsForm.form | 2 +- .../magento2plugin/project/SettingsForm.java | 14 ++ .../BlockTemplateLinemarkerRegistrarTest.java | 36 +++++ 5 files changed, 196 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/magento/idea/magento2plugin/linemarker/php/BlockTemplateLineMarkerProvider.java b/src/main/java/com/magento/idea/magento2plugin/linemarker/php/BlockTemplateLineMarkerProvider.java index a05fdea8f..58fc729c5 100644 --- a/src/main/java/com/magento/idea/magento2plugin/linemarker/php/BlockTemplateLineMarkerProvider.java +++ b/src/main/java/com/magento/idea/magento2plugin/linemarker/php/BlockTemplateLineMarkerProvider.java @@ -5,15 +5,23 @@ package com.magento.idea.magento2plugin.linemarker.php; +import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; import com.intellij.codeInsight.daemon.LineMarkerInfo; import com.intellij.codeInsight.daemon.LineMarkerProvider; import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder; +import com.intellij.codeInsight.navigation.NavigationUtil; +import com.intellij.codeInsight.navigation.PsiTargetNavigator; import com.intellij.icons.AllIcons; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.platform.backend.presentation.TargetPresentation; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.ui.awt.RelativePoint; import com.intellij.util.indexing.FileBasedIndex; import com.jetbrains.php.PhpIndex; import com.jetbrains.php.lang.psi.elements.PhpClass; @@ -23,9 +31,13 @@ import com.magento.idea.magento2plugin.util.magento.BlockTemplateNavigationUtil; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Function; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -98,11 +110,24 @@ public void collectSlowLineMarkers( ? PsiTreeUtil.getDeepestFirst(phpClass) : phpClass.getNameIdentifier(); + final List preparedTemplates = prepareTargets( + templates, + BlockTemplateLineMarkerProvider::getPresentableTemplateTargetName + ); + return NavigationGutterIconBuilder .create(AllIcons.FileTypes.Html) - .setTargets(templates) + .setTargets(preparedTemplates) + .setNamer(BlockTemplateLineMarkerProvider::getPresentableTemplateTargetName) .setTooltipText(TEMPLATE_TOOLTIP_TEXT) - .createLineMarkerInfo(anchor); + .createLineMarkerInfo( + anchor, + createNavigationHandler( + preparedTemplates, + TEMPLATE_TOOLTIP_TEXT, + BlockTemplateLineMarkerProvider::getPresentableTemplateTargetName + ) + ); } private @Nullable LineMarkerInfo createTemplateLineMarker( @@ -115,14 +140,27 @@ public void collectSlowLineMarkers( return null; } + final List preparedBlocks = prepareTargets( + blocks, + BlockTemplateLineMarkerProvider::getPresentableBlockTargetName + ); + return NavigationGutterIconBuilder .create(AllIcons.Nodes.Class) - .setTargets(blocks) + .setTargets(preparedBlocks) + .setNamer(BlockTemplateLineMarkerProvider::getPresentableBlockTargetName) .setTooltipText(BLOCK_TOOLTIP_TEXT) - .createLineMarkerInfo(anchor); + .createLineMarkerInfo( + anchor, + createNavigationHandler( + preparedBlocks, + BLOCK_TOOLTIP_TEXT, + BlockTemplateLineMarkerProvider::getPresentableBlockTargetName + ) + ); } - private @NotNull List collectTemplates(final @NotNull PhpClass phpClass) { + @NotNull List collectTemplates(final @NotNull PhpClass phpClass) { final List results = new ArrayList<>(); final Collection> templateSets = FileBasedIndex.getInstance() .getValues( @@ -141,7 +179,7 @@ public void collectSlowLineMarkers( return results; } - private @NotNull List collectBlocks(final @NotNull PsiFile psiFile) { + @NotNull List collectBlocks(final @NotNull PsiFile psiFile) { final List results = new ArrayList<>(); final Project project = psiFile.getProject(); final PhpIndex phpIndex = PhpIndex.getInstance(project); @@ -164,6 +202,102 @@ public void collectSlowLineMarkers( return results; } + static @NotNull String getPresentableBlockTargetName(final @NotNull PsiElement target) { + if (target instanceof PhpClass) { + return ((PhpClass) target).getPresentableFQN(); + } + + return target.getText(); + } + + static @NotNull String getPresentableTemplateTargetName(final @NotNull PsiElement target) { + final PsiFile psiFile = target instanceof PsiFile + ? (PsiFile) target + : target.getContainingFile(); + + if (psiFile == null) { + return target.getText(); + } + final VirtualFile virtualFile = psiFile.getVirtualFile(); + + if (virtualFile == null) { + return psiFile.getName(); + } + final String templatePath = getTemplatePath(psiFile); + + if (templatePath == null) { + return virtualFile.getName(); + } + + return templatePath; + } + + private static @NotNull GutterIconNavigationHandler createNavigationHandler( + final @NotNull List targets, + final @NotNull String title, + final @NotNull Function namer + ) { + return (event, element) -> { + if (targets.isEmpty()) { + return; + } + if (targets.size() == 1) { + NavigationUtil.activateFileWithPsiElement(targets.get(0), true); + return; + } + final Project project = element.getProject(); + final JBPopup popup = new PsiTargetNavigator<>(targets) + .presentationProvider(target -> TargetPresentation + .builder(namer.apply(target)) + .icon(target.getIcon(Iconable.ICON_FLAG_VISIBILITY)) + .presentation()) + .title(title) + .createPopup(project, title); + + if (event != null && event.getComponent().isShowing()) { + popup.show(new RelativePoint(event)); + } else { + popup.showCenteredInCurrentWindow(project); + } + }; + } + + private static @NotNull List prepareTargets( + final @NotNull List targets, + final @NotNull Function namer + ) { + final Map uniqueTargets = new LinkedHashMap<>(); + + for (final PsiElement target : targets) { + uniqueTargets.putIfAbsent(getStableKey(target), target); + } + final List sortedTargets = new ArrayList<>(uniqueTargets.values()); + sortedTargets.sort(Comparator + .comparing(namer) + .thenComparing(BlockTemplateLineMarkerProvider::getStableKey)); + + return sortedTargets; + } + + private static @NotNull String getStableKey(final @NotNull PsiElement target) { + final PsiFile psiFile = target instanceof PsiFile + ? (PsiFile) target + : target.getContainingFile(); + final VirtualFile virtualFile = psiFile == null ? null : psiFile.getVirtualFile(); + + if (virtualFile != null) { + return virtualFile.getPath() + ":" + target.getTextRange(); + } + + return target.getTextRange().toString() + ":" + target.getText(); + } + + private static @Nullable String getTemplatePath(final @NotNull PsiFile psiFile) { + final Set templatePaths = BlockTemplateNavigationUtil.getInstance().getTemplatePaths(psiFile); + + return templatePaths.isEmpty() ? null : templatePaths.iterator().next(); + } + private boolean isPhtmlFile(final @NotNull PsiFile psiFile) { return psiFile.getVirtualFile() != null && "phtml".equals(psiFile.getVirtualFile().getExtension()); diff --git a/src/main/java/com/magento/idea/magento2plugin/project/PhpFrameworkSettingsForm.java b/src/main/java/com/magento/idea/magento2plugin/project/PhpFrameworkSettingsForm.java index e5ec251f7..cccfd4c03 100644 --- a/src/main/java/com/magento/idea/magento2plugin/project/PhpFrameworkSettingsForm.java +++ b/src/main/java/com/magento/idea/magento2plugin/project/PhpFrameworkSettingsForm.java @@ -20,6 +20,11 @@ public boolean isBeingUsed() { return isMagentoSupportEnabled(); } + @Override + protected boolean isPhpDependentControlsVisible() { + return true; + } + @Override protected void afterSettingsApplied(final @NotNull Settings.State state) { ConfigurationManager.getInstance().refreshIncludePaths(state, getProject()); diff --git a/src/main/java/com/magento/idea/magento2plugin/project/SettingsForm.form b/src/main/java/com/magento/idea/magento2plugin/project/SettingsForm.form index 38a1e4135..10b94d872 100644 --- a/src/main/java/com/magento/idea/magento2plugin/project/SettingsForm.form +++ b/src/main/java/com/magento/idea/magento2plugin/project/SettingsForm.form @@ -40,7 +40,7 @@ - + diff --git a/src/main/java/com/magento/idea/magento2plugin/project/SettingsForm.java b/src/main/java/com/magento/idea/magento2plugin/project/SettingsForm.java index 166698c85..d292f7f95 100644 --- a/src/main/java/com/magento/idea/magento2plugin/project/SettingsForm.java +++ b/src/main/java/com/magento/idea/magento2plugin/project/SettingsForm.java @@ -64,6 +64,7 @@ public class SettingsForm implements SearchableConfigurable { private final SettingsFormValidator validator = new SettingsFormValidator(this); private JLabel magentoVersionLabel;//NOPMD private JLabel magentoPathLabel;//NOPMD + private JLabel moduleDefaultLicenseNameLabel;//NOPMD public SettingsForm(@NotNull final Project project) { this.project = project; @@ -101,6 +102,7 @@ public JComponent createComponent() { } skillAgentTargetSelect.setSelectedItem(AgentTarget.PROJECT_SKILLS); + refreshPhpDependentControlsVisibility(); refreshFormStatus(getSettings().pluginEnabled); pluginEnabled.addActionListener(e -> refreshFormStatus(pluginEnabled.isSelected())); @@ -132,6 +134,18 @@ private void refreshFormStatus(final boolean isEnabled) { skillAgentTargetSelect.setEnabled(isEnabled); } + private void refreshPhpDependentControlsVisibility() { + final boolean isVisible = isPhpDependentControlsVisible(); + + moduleDefaultLicenseNameLabel.setVisible(isVisible); + moduleDefaultLicenseName.setVisible(isVisible); + mftfSupportEnabled.setVisible(isVisible); + } + + protected boolean isPhpDependentControlsVisible() { + return false; + } + protected void reindex() { MagentoAdditionalLibraryRootsProvider.refreshRoots(project); IndexManager.manualReindex(); diff --git a/src/test/java/com/magento/idea/magento2plugin/linemarker/php/BlockTemplateLinemarkerRegistrarTest.java b/src/test/java/com/magento/idea/magento2plugin/linemarker/php/BlockTemplateLinemarkerRegistrarTest.java index 8047e5657..fa9ac0ccb 100644 --- a/src/test/java/com/magento/idea/magento2plugin/linemarker/php/BlockTemplateLinemarkerRegistrarTest.java +++ b/src/test/java/com/magento/idea/magento2plugin/linemarker/php/BlockTemplateLinemarkerRegistrarTest.java @@ -10,6 +10,7 @@ import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.php.lang.psi.elements.PhpClass; import com.magento.idea.magento2plugin.linemarker.LinemarkerFixtureTestCase; +import java.util.List; public class BlockTemplateLinemarkerRegistrarTest extends LinemarkerFixtureTestCase { /** @@ -24,6 +25,24 @@ public void testBlockClassShouldHaveTemplateLinemarker() { assertProviderHasLinemarker(phpClass, "Navigate to block template"); } + /** + * Block-to-template navigation popups should show template names, not PHP file content. + */ + public void testBlockTemplateTargetsUseTemplateNames() { + myFixture.configureFromTempProjectFile( + "app/code/Foo/Bar/Block/Widget/CustomerCreations.php" + ); + final PhpClass phpClass = PsiTreeUtil.findChildOfType(myFixture.getFile(), PhpClass.class); + final List targets = new BlockTemplateLineMarkerProvider() + .collectTemplates(phpClass); + + assertEquals(1, targets.size()); + assertEquals( + "Foo_Bar::widget/customer_creations.phtml", + BlockTemplateLineMarkerProvider.getPresentableTemplateTargetName(targets.get(0)) + ); + } + /** * Layout referenceBlock classes should navigate to their declared regular templates. */ @@ -48,6 +67,23 @@ public void testTemplateShouldHaveBlockLinemarker() { assertProviderHasLinemarker(anchor, "Navigate to template block"); } + /** + * Template-to-block navigation popups should show block class names, not PHP file content. + */ + public void testTemplateBlockTargetsUseBlockClassNames() { + myFixture.configureFromTempProjectFile( + "app/code/Foo/Bar/view/frontend/templates/widget/customer_creations.phtml" + ); + final List targets = new BlockTemplateLineMarkerProvider() + .collectBlocks(myFixture.getFile()); + + assertEquals(1, targets.size()); + assertEquals( + "Foo\\Bar\\Block\\Widget\\TestMethod", + BlockTemplateLineMarkerProvider.getPresentableBlockTargetName(targets.get(0)) + ); + } + /** * Regular templates should navigate back to layout referenceBlock classes that declare them. */ From ff2dbad2eaf1f2aa03d644b931b21c36dd5f815e Mon Sep 17 00:00:00 2001 From: vitaliy Date: Fri, 3 Jul 2026 13:07:12 +0300 Subject: [PATCH 2/3] 2693: java.lang.IllegalStateException: 'runBlockingCancellable' is forbidden - fix --- CHANGELOG.md | 1 + .../generation/dialog/AbstractDialog.java | 8 +-- .../generation/dialog/NewEntityDialog.java | 47 ++++++++++---- .../util/ClassPropertyFormatterUtil.java | 33 +++++++--- .../generator/util/DbSchemaGeneratorUtil.java | 34 +++++++---- .../generation/dialog/AbstractDialogTest.java | 59 ++++++++++++++++++ .../dialog/NewEntityDialogTest.java | 17 ++++++ .../util/ClassPropertyFormatterUtilTest.java | 40 ++++++++++++ .../util/DbSchemaGeneratorUtilTest.java | 61 +++++++++++++++++++ 9 files changed, 264 insertions(+), 36 deletions(-) create mode 100644 src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialogTest.java create mode 100644 src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/NewEntityDialogTest.java create mode 100644 src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/util/ClassPropertyFormatterUtilTest.java create mode 100644 src/test/java/com/magento/idea/magento2plugin/actions/generation/generator/util/DbSchemaGeneratorUtilTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 44ef41076..ca72746cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). - Class cast exception when resolving observer name references outside a valid `event` tag in `events.xml`. - JetBrains MCP Server is now an optional plugin dependency; Magento MCP tools are registered only when MCP support is available. +- File generation dialogs no longer wrap the whole OK action in a write action, preventing `runBlockingCancellable` write-action errors during PSI file creation. ## 2026.2.2 diff --git a/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialog.java b/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialog.java index affe8b7e6..30c8ce4cb 100644 --- a/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialog.java +++ b/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialog.java @@ -5,7 +5,6 @@ package com.magento.idea.magento2plugin.actions.generation.dialog; -import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; @@ -113,16 +112,17 @@ protected void exit() { } /** - * Executes onOK within a WriteAction context. + * Executes onOK after validation. */ protected final void executeOnOk() { - WriteAction.run(this::onWriteActionOK); + onWriteActionOK(); } /** * This method should contain the core logic for onOk. * Subclasses can override to provide their implementation. - * Must be invoked via executeOnOk(). + * Must be invoked via executeOnOk(). PSI and VFS mutations should be wrapped + * by the generator that performs them. */ protected abstract void onWriteActionOK(); diff --git a/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/NewEntityDialog.java b/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/NewEntityDialog.java index 738fd4b3d..addef4ecf 100644 --- a/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/NewEntityDialog.java +++ b/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/NewEntityDialog.java @@ -568,22 +568,21 @@ private List getFields() { for (int count = 0; count < model.getRowCount(); count++) { - final String name = model.getValueAt(count, 0).toString(); - final String dataType = model.getValueAt(count, 1).toString(); + final String name = getTrimmedTableValue(model, count, 0); + final String dataType = getTrimmedTableValue(model, count, 1); - final String label = Arrays.stream(name.split("_")).map( - string -> string.substring(0, 1).toUpperCase(Locale.getDefault()) - + string.substring(1)).collect(Collectors.joining(" ") - ); + if (name.isEmpty() || dataType.isEmpty()) { + continue; + } + final String label = formatSnakeCaseLabel(name); final String sortOrder = String.valueOf(count).concat("0"); final String fieldset = "general"; - final PropertiesTypes property = - PropertiesTypes.getByValue(model.getValueAt(count, 1).toString()); + final PropertiesTypes property = PropertiesTypes.getByValue(dataType); final String formElementType = FormElementType.getDefaultForProperty(property).getType(); - final String source = model.getValueAt(count, 0).toString(); //todo: convert + final String source = name; //todo: convert final UiComponentFormFieldData fieldsetData = new UiComponentFormFieldData(//NOPMD name, @@ -644,10 +643,7 @@ private void autoCompleteIdentifiers() { return; } final String entityName = CamelCaseToSnakeCase.getInstance().convert(entityNameValue); - final String entityNameLabel = Arrays.stream(entityName.split("_")).map( - string -> string.substring(0, 1).toUpperCase(Locale.getDefault()) - + string.substring(1) - ).collect(Collectors.joining(" ")); + final String entityNameLabel = formatSnakeCaseLabel(entityName); dbTableName.setText(entityName); entityId.setText(entityName.concat("_id")); @@ -661,6 +657,31 @@ private void autoCompleteIdentifiers() { menuTitle.setText(entityNameLabel.concat(" Management")); } + static String formatSnakeCaseLabel(final String value) { + return Arrays.stream(value.split("_")) + .filter(string -> !string.isEmpty()) + .map(NewEntityDialog::capitalizeLabelPart) + .collect(Collectors.joining(" ")); + } + + private static String capitalizeLabelPart(final String value) { + return value.substring(0, 1).toUpperCase(Locale.getDefault()) + value.substring(1); + } + + private static String getTrimmedTableValue( + final DefaultTableModel model, + final int row, + final int column + ) { + final Object value = model.getValueAt(row, column); + + if (value == null) { + return ""; + } + + return value.toString().trim(); + } + /** * Resolve ui components panel state. */ diff --git a/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/util/ClassPropertyFormatterUtil.java b/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/util/ClassPropertyFormatterUtil.java index c7ae405cd..35f3e452e 100644 --- a/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/util/ClassPropertyFormatterUtil.java +++ b/src/main/java/com/magento/idea/magento2plugin/actions/generation/dialog/util/ClassPropertyFormatterUtil.java @@ -28,9 +28,12 @@ public static List formatProperties(final DefaultTableModel table) { final List properties = new ArrayList<>(); for (int index = 0; index < table.getRowCount(); index++) { - final String name = table.getValueAt(index, 0).toString(); - final String type = table.getValueAt(index, 1).toString(); + final String name = getTrimmedValue(table, index, 0); + final String type = getTrimmedValue(table, index, 1); + if (name.isEmpty() || type.isEmpty()) { + continue; + } properties.add(ClassPropertyFormatterUtil.formatSingleProperty(name, type)); } @@ -46,12 +49,14 @@ public static List formatProperties(final DefaultTableModel table) { * @return String */ public static String formatSingleProperty(final String name, final String type) { + final String propertyName = name.trim(); + return new ClassPropertyData( - type, - CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name), - CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name), - name, - formatNameToConstant(name) + type.trim(), + CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, propertyName), + CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, propertyName), + propertyName, + formatNameToConstant(propertyName) ).string(); } @@ -88,4 +93,18 @@ public static String formatNameToConstant(final String name, final String typeFq public static String formatNameToConstant(final String name) { return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_UNDERSCORE, name); } + + private static String getTrimmedValue( + final DefaultTableModel table, + final int row, + final int column + ) { + final Object value = table.getValueAt(row, column); + + if (value == null) { + return StringUtils.EMPTY; + } + + return value.toString().trim(); + } } diff --git a/src/main/java/com/magento/idea/magento2plugin/actions/generation/generator/util/DbSchemaGeneratorUtil.java b/src/main/java/com/magento/idea/magento2plugin/actions/generation/generator/util/DbSchemaGeneratorUtil.java index d84f10318..bb40e8e8b 100644 --- a/src/main/java/com/magento/idea/magento2plugin/actions/generation/generator/util/DbSchemaGeneratorUtil.java +++ b/src/main/java/com/magento/idea/magento2plugin/actions/generation/generator/util/DbSchemaGeneratorUtil.java @@ -10,10 +10,12 @@ import com.magento.idea.magento2plugin.magento.packages.database.ColumnAttributes; import com.magento.idea.magento2plugin.magento.packages.database.PropertyToDefaultTypeMapperUtil; import com.magento.idea.magento2plugin.magento.packages.database.TableColumnTypes; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; public final class DbSchemaGeneratorUtil { @@ -39,7 +41,12 @@ public static List> complementShortPropertiesByDefaults( final String name = property.get(PROPERTY_NAME); final String type = property.get(PROPERTY_TYPE); - final PropertiesTypes propType = PropertiesTypes.getByValue(type); + if (name == null || name.trim().isEmpty() || type == null || type.trim().isEmpty()) { + continue; + } + final String trimmedName = name.trim(); + final String trimmedType = type.trim(); + final PropertiesTypes propType = PropertiesTypes.getByValue(trimmedType); final TableColumnTypes tableColumnType = PropertyToDefaultTypeMapperUtil.map(propType); final List allowedAttributes = ModuleDbSchemaXml.getAllowedAttributes( @@ -48,7 +55,7 @@ public static List> complementShortPropertiesByDefaults( final Map columnData = new LinkedHashMap<>(); columnData.put(ColumnAttributes.TYPE.getName(), tableColumnType.getColumnType()); - columnData.put(ColumnAttributes.NAME.getName(), name); + columnData.put(ColumnAttributes.NAME.getName(), trimmedName); for (final String columnAttributeName : allowedAttributes) { final ColumnAttributes attribute = ColumnAttributes.getByName(columnAttributeName); @@ -58,7 +65,7 @@ public static List> complementShortPropertiesByDefaults( } columnData.put(columnAttributeName, attribute.getDefault()); } - columnData.put(ColumnAttributes.COMMENT.getName(), getColumnCommentByName(name)); + columnData.put(ColumnAttributes.COMMENT.getName(), getColumnCommentByName(trimmedName)); complemented.add(columnData); } @@ -96,16 +103,19 @@ public static Map getTableIdentityColumnData(final @NotNull Stri */ @SuppressWarnings("PMD.UseLocaleWithCaseConversions") private static String getColumnCommentByName(final @NotNull String name) { - final StringBuilder commentStringBuilder = new StringBuilder(); - final String[] nameParts = name.split("_"); - - for (final String namePart : nameParts) { - commentStringBuilder - .append(namePart.substring(0, 1).toUpperCase()) - .append(namePart.substring(1)) - .append(' '); + final String formattedName = Arrays.stream(name.split("_")) + .filter(namePart -> !namePart.isEmpty()) + .map(DbSchemaGeneratorUtil::capitalizeCommentPart) + .collect(Collectors.joining(" ")); + + if (formattedName.isEmpty()) { + return "Column"; } - return commentStringBuilder.append("Column").toString(); + return formattedName.concat(" Column"); + } + + private static String capitalizeCommentPart(final @NotNull String namePart) { + return namePart.substring(0, 1).toUpperCase() + namePart.substring(1); } } diff --git a/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialogTest.java b/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialogTest.java new file mode 100644 index 000000000..899c0adde --- /dev/null +++ b/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialogTest.java @@ -0,0 +1,59 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.dialog; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.testFramework.fixtures.BasePlatformTestCase; +import javax.swing.JComponent; +import javax.swing.JPanel; +import org.jetbrains.annotations.Nullable; + +public class AbstractDialogTest extends BasePlatformTestCase { + + public void testOkActionDoesNotWrapHandlerInWriteAction() { + final TestDialog dialog = new TestDialog(); + + dialog.doOKAction(); + + assertTrue(dialog.wasOkHandlerCalled()); + assertFalse(dialog.wasWriteAccessAllowedInOkHandler()); + } + + private static final class TestDialog extends AbstractDialog { + + private boolean okHandlerCalled; + private boolean writeAccessAllowedInOkHandler; + + private TestDialog() { + super(); + } + + @Override + protected @Nullable JComponent createCenterPanel() { + return new JPanel(); + } + + @Override + protected boolean validateFormFields() { + return true; + } + + @Override + protected void onWriteActionOK() { + okHandlerCalled = true; + writeAccessAllowedInOkHandler = + ApplicationManager.getApplication().isWriteAccessAllowed(); + } + + private boolean wasOkHandlerCalled() { + return okHandlerCalled; + } + + private boolean wasWriteAccessAllowedInOkHandler() { + return writeAccessAllowedInOkHandler; + } + } +} diff --git a/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/NewEntityDialogTest.java b/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/NewEntityDialogTest.java new file mode 100644 index 000000000..be8152dd3 --- /dev/null +++ b/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/NewEntityDialogTest.java @@ -0,0 +1,17 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.dialog; + +import junit.framework.TestCase; + +public class NewEntityDialogTest extends TestCase { + + public void testFormatSnakeCaseLabelSkipsEmptyParts() { + assertEquals("", NewEntityDialog.formatSnakeCaseLabel("")); + assertEquals("", NewEntityDialog.formatSnakeCaseLabel("_")); + assertEquals("Product Entity", NewEntityDialog.formatSnakeCaseLabel("_product__entity_")); + } +} diff --git a/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/util/ClassPropertyFormatterUtilTest.java b/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/util/ClassPropertyFormatterUtilTest.java new file mode 100644 index 000000000..d0bee1a2e --- /dev/null +++ b/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/util/ClassPropertyFormatterUtilTest.java @@ -0,0 +1,40 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.dialog.util; + +import java.util.List; +import javax.swing.table.DefaultTableModel; +import junit.framework.TestCase; + +public class ClassPropertyFormatterUtilTest extends TestCase { + + public void testFormatPropertiesSkipsBlankRows() { + final DefaultTableModel table = new DefaultTableModel( + new Object[][] { + {"", "string"}, + {" ", "int"}, + {"description", null}, + {"product_name", "string"}, + }, + new Object[] {"Name", "Type"} + ); + + final List properties = ClassPropertyFormatterUtil.formatProperties(table); + + assertEquals(1, properties.size()); + assertEquals( + "PRODUCT_NAME;product_name;string;ProductName;productName", + properties.get(0) + ); + } + + public void testFormatSinglePropertyTrimsInput() { + assertEquals( + "PRODUCT_NAME;product_name;string;ProductName;productName", + ClassPropertyFormatterUtil.formatSingleProperty(" product_name ", " string ") + ); + } +} diff --git a/src/test/java/com/magento/idea/magento2plugin/actions/generation/generator/util/DbSchemaGeneratorUtilTest.java b/src/test/java/com/magento/idea/magento2plugin/actions/generation/generator/util/DbSchemaGeneratorUtilTest.java new file mode 100644 index 000000000..693a2a19a --- /dev/null +++ b/src/test/java/com/magento/idea/magento2plugin/actions/generation/generator/util/DbSchemaGeneratorUtilTest.java @@ -0,0 +1,61 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +package com.magento.idea.magento2plugin.actions.generation.generator.util; + +import com.magento.idea.magento2plugin.magento.packages.PropertiesTypes; +import com.magento.idea.magento2plugin.magento.packages.database.ColumnAttributes; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import junit.framework.TestCase; + +public class DbSchemaGeneratorUtilTest extends TestCase { + + private static final String PROPERTY_NAME = "Name"; + private static final String PROPERTY_TYPE = "Type"; + + public void testComplementShortPropertiesSkipsBlankRowsAndHandlesEmptyNamePartsInComment() { + final List> columnsData = + DbSchemaGeneratorUtil.complementShortPropertiesByDefaults(List.of( + createProperty(""), + createProperty("_"), + createProperty("_product__entity_"), + createProperty(" product_name ", " string ") + )); + + assertEquals(3, columnsData.size()); + assertEquals("Column", columnsData.get(0).get(ColumnAttributes.COMMENT.getName())); + assertEquals( + "Product Entity Column", + columnsData.get(1).get(ColumnAttributes.COMMENT.getName()) + ); + assertEquals("product_name", columnsData.get(2).get(ColumnAttributes.NAME.getName())); + assertEquals( + "Product Name Column", + columnsData.get(2).get(ColumnAttributes.COMMENT.getName()) + ); + } + + public void testTableIdentityColumnHandlesEmptyNamePartsInComment() { + assertEquals( + "Column", + DbSchemaGeneratorUtil.getTableIdentityColumnData("_") + .get(ColumnAttributes.COMMENT.getName()) + ); + } + + private static Map createProperty(final String name) { + return createProperty(name, PropertiesTypes.STRING.getPropertyType()); + } + + private static Map createProperty(final String name, final String type) { + final Map property = new HashMap<>(); + property.put(PROPERTY_NAME, name); + property.put(PROPERTY_TYPE, type); + + return property; + } +} From 580af086939b27c4c2eafb12eabcb04af0015de5 Mon Sep 17 00:00:00 2001 From: vitaliy Date: Fri, 3 Jul 2026 13:29:32 +0300 Subject: [PATCH 3/3] 2693: test fixes --- .../generation/dialog/AbstractDialogTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialogTest.java b/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialogTest.java index 899c0adde..ef4efecf7 100644 --- a/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialogTest.java +++ b/src/test/java/com/magento/idea/magento2plugin/actions/generation/dialog/AbstractDialogTest.java @@ -6,13 +6,30 @@ package com.magento.idea.magento2plugin.actions.generation.dialog; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; import com.intellij.testFramework.fixtures.BasePlatformTestCase; +import java.nio.file.Paths; import javax.swing.JComponent; import javax.swing.JPanel; import org.jetbrains.annotations.Nullable; public class AbstractDialogTest extends BasePlatformTestCase { + @Override + public void setUp() throws Exception { + VfsRootAccess.allowRootAccess( + getTestRootDisposable(), + toSystemIndependentPath("."), + PathManager.getHomePath(), + PathManager.getConfigPath(), + PathManager.getSystemPath(), + PathManager.getPluginsPath() + ); + super.setUp(); + } + public void testOkActionDoesNotWrapHandlerInWriteAction() { final TestDialog dialog = new TestDialog(); @@ -56,4 +73,10 @@ private boolean wasWriteAccessAllowedInOkHandler() { return writeAccessAllowedInOkHandler; } } + + private static String toSystemIndependentPath(final String path) { + return FileUtil.toSystemIndependentName( + Paths.get(path).toAbsolutePath().normalize().toString() + ); + } }