diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
index 35c41ae48..5bca77ca9 100644
--- a/resources/META-INF/plugin.xml
+++ b/resources/META-INF/plugin.xml
@@ -78,6 +78,7 @@
+
diff --git a/resources/fileTemplates/internal/Magento Module Observer File.php.ft b/resources/fileTemplates/internal/Magento Module Observer File.php.ft
new file mode 100644
index 000000000..052c02f93
--- /dev/null
+++ b/resources/fileTemplates/internal/Magento Module Observer File.php.ft
@@ -0,0 +1,26 @@
+getEvent();
+ // TODO: Implement observer method.
+ }
+}
diff --git a/resources/fileTemplates/internal/Magento Module Observer File.php.html b/resources/fileTemplates/internal/Magento Module Observer File.php.html
new file mode 100644
index 000000000..6f627a70a
--- /dev/null
+++ b/resources/fileTemplates/internal/Magento Module Observer File.php.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+ |
+ Observers are a certain type of Magento class that can influence
+ general behavior, performance, or change business logic. Observers are executed whenever the
+ event they are configured to watch is dispatched by the event manager.
+
+ Read more about observers.
+
+ |
+
+
+ |
+ Check out the
+
+ Observers best practices to have a clean and professional implementation.
+ |
+
+
+
+
+ | Predefined variables explanation: |
+
+
+ | ${NAMESPACE} |
+ |
+ Namespace for the class. |
+
+
+ | ${CLASS_NAME} |
+ |
+ Class name. |
+
+
+ | ${EVENT_NAME} |
+ |
+ The name of the observer for the event definition. |
+
+
+
+
diff --git a/src/com/magento/idea/magento2plugin/actions/context/php/NewObserverAction.java b/src/com/magento/idea/magento2plugin/actions/context/php/NewObserverAction.java
new file mode 100644
index 000000000..87af99e92
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/context/php/NewObserverAction.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.context.php;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.magento.idea.magento2plugin.actions.context.CustomGeneratorContextAction;
+import com.magento.idea.magento2plugin.actions.generation.dialog.NewObserverDialog;
+import com.magento.idea.magento2plugin.magento.packages.ComponentType;
+import com.magento.idea.magento2plugin.magento.packages.Package;
+import com.magento.idea.magento2plugin.util.magento.GetMagentoModuleUtil;
+import org.jetbrains.annotations.NotNull;
+
+public class NewObserverAction extends CustomGeneratorContextAction {
+
+ public static final String ACTION_NAME = "Magento 2 Observer";
+ public static final String ACTION_DESCRIPTION = "Create a new Magento 2 Observer";
+ public static final String ROOT_DIRECTORY = "Observer";
+
+ public NewObserverAction() {
+ super(ACTION_NAME, ACTION_DESCRIPTION);
+ }
+
+ @Override
+ public void actionPerformed(final @NotNull AnActionEvent event) {
+ final GetMagentoModuleUtil.MagentoModuleData moduleData = getModuleData();
+
+ if (event.getProject() == null || moduleData == null || getDirectory() == null) {
+ return;
+ }
+ final String[] templateData = moduleData.getName().split(Package.vendorModuleNameSeparator);
+
+ if (templateData.length != 2) { //NOPMD
+ return;
+ }
+
+ NewObserverDialog.open(
+ event.getProject(),
+ getDirectory(),
+ templateData[0],
+ templateData[1]
+ );
+ }
+
+ @Override
+ protected boolean isVisible(
+ final @NotNull GetMagentoModuleUtil.MagentoModuleData moduleData,
+ final PsiDirectory targetDirectory,
+ final PsiFile targetFile
+ ) {
+ if (!moduleData.getType().equals(ComponentType.module)
+ || !moduleData.getModuleDir().equals(targetDirectory.getParentDirectory())) {
+ return false;
+ }
+
+ return ROOT_DIRECTORY.equals(targetDirectory.getName());
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/ModuleObserverData.java b/src/com/magento/idea/magento2plugin/actions/generation/ModuleObserverData.java
new file mode 100644
index 000000000..490a21b57
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/ModuleObserverData.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.generation;
+
+import com.intellij.psi.PsiDirectory;
+import org.jetbrains.annotations.NotNull;
+
+public class ModuleObserverData {
+
+ private final String packageName;
+ private final String moduleName;
+ private final String classFqn;
+ private final String evenName;
+ private final PsiDirectory baseDir;
+ private final String className;
+
+ /**
+ * Constructor.
+ *
+ * @param packageName String
+ * @param moduleName String
+ * @param classFqn String
+ * @param evenName String
+ * @param baseDir PsiDirectory
+ * @param className PsiDirectory
+ */
+ public ModuleObserverData(
+ final @NotNull String packageName,
+ final @NotNull String moduleName,
+ final @NotNull String classFqn,
+ final @NotNull String evenName,
+ final @NotNull PsiDirectory baseDir,
+ final @NotNull String className
+ ) {
+ this.packageName = packageName;
+ this.moduleName = moduleName;
+ this.classFqn = classFqn;
+ this.evenName = evenName;
+ this.baseDir = baseDir;
+ this.className = className;
+ }
+
+ public @NotNull String getPackageName() {
+ return packageName;
+ }
+
+ public @NotNull String getModuleName() {
+ return moduleName;
+ }
+
+ public @NotNull String getClassFqn() {
+ return classFqn;
+ }
+
+ public @NotNull PsiDirectory getBaseDir() {
+ return baseDir;
+ }
+
+ public @NotNull String getClassName() {
+ return className;
+ }
+
+ public @NotNull String getEventName() {
+ return evenName;
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewObserverDialog.form b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewObserverDialog.form
new file mode 100644
index 000000000..720f151f3
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewObserverDialog.form
@@ -0,0 +1,184 @@
+
+
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewObserverDialog.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewObserverDialog.java
new file mode 100644
index 000000000..178b97d98
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewObserverDialog.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.generation.dialog;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.ComboBox;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.ui.DocumentAdapter;
+import com.intellij.util.indexing.FileBasedIndex;
+import com.magento.idea.magento2plugin.actions.context.php.NewObserverAction;
+import com.magento.idea.magento2plugin.actions.generation.ModuleObserverData;
+import com.magento.idea.magento2plugin.actions.generation.data.ObserverEventsXmlData;
+import com.magento.idea.magento2plugin.actions.generation.data.ui.ComboBoxItemData;
+import com.magento.idea.magento2plugin.actions.generation.dialog.validator.annotation.FieldValidation;
+import com.magento.idea.magento2plugin.actions.generation.dialog.validator.annotation.RuleRegistry;
+import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.DirectoryRule;
+import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.IdentifierWithColonRule;
+import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.NotEmptyRule;
+import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.PhpClassRule;
+import com.magento.idea.magento2plugin.actions.generation.generator.ModuleObserverGenerator;
+import com.magento.idea.magento2plugin.actions.generation.generator.ObserverEventsXmlGenerator;
+import com.magento.idea.magento2plugin.actions.generation.generator.util.DirectoryGenerator;
+import com.magento.idea.magento2plugin.lang.roots.MagentoTestSourceFilter;
+import com.magento.idea.magento2plugin.magento.files.ModuleObserverFile;
+import com.magento.idea.magento2plugin.magento.packages.Areas;
+import com.magento.idea.magento2plugin.magento.packages.Package;
+import com.magento.idea.magento2plugin.stubs.indexes.EventNameIndex;
+import com.magento.idea.magento2plugin.ui.FilteredComboBox;
+import com.magento.idea.magento2plugin.util.CamelCaseToSnakeCase;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.event.DocumentEvent;
+import org.jetbrains.annotations.NotNull;
+
+@SuppressWarnings({
+ "PMD.TooManyFields",
+ "PMD.ExcessiveImports",
+ "PMD.AvoidInstantiatingObjectsInLoops",
+ "PMD.ReturnEmptyCollectionRatherThanNull"
+})
+public class NewObserverDialog extends AbstractDialog {
+
+ private static final String OBSERVER_NAME = "Observer Name";
+ private static final String CLASS_NAME = "Class Name";
+ private final Project project;
+ private final PsiDirectory baseDir;
+ private final String moduleName;
+ private final String modulePackage;
+ private JPanel contentPanel;
+ private JButton buttonOK;
+ private JButton buttonCancel;
+
+ @FieldValidation(rule = RuleRegistry.NOT_EMPTY,
+ message = {NotEmptyRule.MESSAGE, OBSERVER_NAME})
+ @FieldValidation(rule = RuleRegistry.IDENTIFIER_WITH_COLON,
+ message = {IdentifierWithColonRule.MESSAGE, OBSERVER_NAME})
+ private JTextField observerName;
+ @FieldValidation(rule = RuleRegistry.NOT_EMPTY,
+ message = {NotEmptyRule.MESSAGE, CLASS_NAME})
+ @FieldValidation(rule = RuleRegistry.PHP_CLASS,
+ message = {PhpClassRule.MESSAGE, CLASS_NAME})
+ private JTextField className;
+ private JTextField directoryStructure;
+ private FilteredComboBox eventName;
+ private JComboBox observerArea;
+ private JLabel observerNameLabel;// NOPMD
+ private JLabel observerNameErrorMessage;// NOPMD
+ private JLabel classNameLabel;// NOPMD
+ private JLabel classNameErrorMessage;// NOPMD
+ private JLabel directoryStructureLabel;// NOPMD
+ private JLabel directoryStructureErrorMessage;// NOPMD
+ private JLabel evenNamesLabel;// NOPMD
+ private JLabel targetAreaLabel;// NOPMD
+
+ /**
+ * Constructor.
+ *
+ * @param project Project
+ * @param directory PsiDirectory
+ * @param modulePackage String
+ * @param moduleName String
+ */
+ public NewObserverDialog(
+ final Project project,
+ final PsiDirectory directory,
+ final String modulePackage,
+ final String moduleName
+ ) {
+ super();
+
+ this.project = project;
+ this.baseDir = directory;
+ this.modulePackage = modulePackage;
+ this.moduleName = moduleName;
+
+ setContentPane(contentPanel);
+ setModal(true);
+ setTitle(NewObserverAction.ACTION_DESCRIPTION);
+ getRootPane().setDefaultButton(buttonOK);
+
+ buttonOK.addActionListener((final ActionEvent event) -> onOK());
+ buttonCancel.addActionListener((final ActionEvent event) -> onCancel());
+
+ // call onCancel() when cross is clicked
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(final WindowEvent event) {
+ onCancel();
+ }
+ });
+
+ // call onCancel() on ESCAPE
+ contentPanel.registerKeyboardAction(
+ (final ActionEvent event) -> onCancel(),
+ KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
+ );
+
+ className.getDocument().addDocumentListener(new DocumentAdapter() {
+ @SuppressWarnings("PMD.AccessorMethodGeneration")
+ @Override
+ public void textChanged(final @NotNull DocumentEvent event) {
+ autoCompleteObserverName();
+ }
+ });
+
+ addComponentListener(
+ new FocusOnAFieldListener(() -> className.requestFocusInWindow())
+ );
+ }
+
+ /**
+ * Open dialog.
+ *
+ * @param project Project
+ * @param directory PsiDirectory
+ */
+ public static void open(
+ final Project project,
+ final PsiDirectory directory,
+ final String modulePackage,
+ final String moduleName
+ ) {
+ final NewObserverDialog dialog = new NewObserverDialog(
+ project,
+ directory,
+ modulePackage,
+ moduleName
+ );
+ dialog.pack();
+ dialog.centerDialog(dialog);
+ dialog.setVisible(true);
+ }
+
+ private String getModuleName() {
+ return modulePackage.concat(Package.fqnSeparator).concat(moduleName);
+ }
+
+ public String getObserverName() {
+ return observerName.getText().trim();
+ }
+
+ public String getClassName() {
+ return className.getText().trim();
+ }
+
+ public String getEvenName() {
+ return eventName.getSelectedItem().toString();
+ }
+
+ public String getObserverArea() {
+ return this.observerArea.getSelectedItem().toString();
+ }
+
+ public String getDirectoryStructure() {
+ return directoryStructure.getText().trim();
+ }
+
+ protected void onOK() {
+ if (validateFields()) {
+ PsiDirectory observerDirectory = baseDir;
+
+ if (!getDirectoryStructure().isEmpty()) {
+ observerDirectory = DirectoryGenerator.getInstance().findOrCreateSubdirectories(
+ baseDir,
+ getDirectoryStructure()
+ );
+ }
+ new ModuleObserverGenerator(
+ new ModuleObserverData(
+ modulePackage,
+ moduleName,
+ getObserverClassFqn(),
+ getEvenName(),
+ observerDirectory,
+ ModuleObserverFile.resolveClassNameFromInput(getClassName())
+ ),
+ project
+ ).generate(NewObserverAction.ACTION_NAME, true);
+
+ new ObserverEventsXmlGenerator(
+ new ObserverEventsXmlData(
+ getObserverArea(),
+ getModuleName().replace(
+ Package.fqnSeparator,
+ Package.vendorModuleNameSeparator
+ ),
+ getEvenName(),
+ getObserverName(),
+ getObserverClassFqn().concat(Package.fqnSeparator).concat(
+ ModuleObserverFile.resolveClassNameFromInput(getClassName())
+ )
+ ),
+ project
+ ).generate(NewObserverAction.ACTION_NAME);
+ exit();
+ }
+ }
+
+ private boolean validateFields() {
+ final PsiFile[] directoryFiles = getDirectoryFiles(baseDir);
+
+ if (directoryFiles != null) {
+ for (final PsiFile file : directoryFiles) {
+ final String className = ModuleObserverFile.resolveClassNameFromInput(
+ getClassName()
+ );
+ if (file.getName().equals(className + ModuleObserverFile.EXTENSION)) {
+ showErrorMessage(
+ fieldsValidationsList.get(1).getField(),
+ "Class name " + className + " already exist."
+ );
+
+ return false;
+ }
+ }
+ }
+ if (!getDirectoryStructure().isEmpty()
+ && !DirectoryRule.getInstance().check(getDirectoryStructure())
+ ) {
+ showErrorMessage(
+ this.getClass().getDeclaredFields()[11],
+ "The Directory Path field does not contain a valid directory."
+ );
+
+ return false;
+ }
+
+ return validateFormFields();
+ }
+
+ private PsiFile[] getDirectoryFiles(final PsiDirectory targetDirectory) {
+ PsiDirectory directory = targetDirectory;
+
+ if (!getDirectoryStructure().isEmpty()) {
+ final String[] directories = getDirectoryStructure().split(Package.V_FILE_SEPARATOR);
+
+ for (final String dir : directories) {
+ final PsiDirectory subdirectory = directory.findSubdirectory(dir);
+
+ if (subdirectory == null) {
+ return null;
+ }
+
+ directory = subdirectory;
+ }
+ }
+
+ return directory.getFiles();
+ }
+
+ private String getObserverClassFqn() {
+ final String folderStructureFqn = getDirectoryStructure().replace(
+ Package.V_FILE_SEPARATOR, Package.fqnSeparator
+ );
+ String folderFqn = NewObserverAction.ROOT_DIRECTORY;
+
+ if (!folderStructureFqn.isEmpty()) {
+ folderFqn = folderFqn.concat(Package.fqnSeparator).concat(folderStructureFqn);
+ }
+
+ return getModuleName().concat(Package.fqnSeparator).concat(folderFqn);
+ }
+
+ private void autoCompleteObserverName() {
+ final String className = getClassName();
+
+ if (className.isEmpty()) {
+ return;
+ }
+ final String modifiedClassName = ModuleObserverFile.resolveClassNameFromInput(className);
+ final String classNameInSnakeCase = CamelCaseToSnakeCase.getInstance()
+ .convert(modifiedClassName);
+
+ final String modulePackageModified = modulePackage.substring(0, 1)
+ .toLowerCase(Locale.getDefault()) + modulePackage.substring(1);
+ final String moduleNameModified = moduleName.substring(0, 1)
+ .toLowerCase(Locale.getDefault()) + moduleName.substring(1);
+
+ observerName.setText(
+ modulePackageModified + "_" + moduleNameModified + "_" + classNameInSnakeCase
+ );
+ }
+
+ @SuppressWarnings({"PMD.UnusedPrivateMethod"})
+ private void createUIComponents() {
+ observerArea = new ComboBox<>();
+
+ for (final Areas areaEntry : Areas.values()) {
+ observerArea.addItem(new ComboBoxItemData(areaEntry.toString(), areaEntry.toString()));
+ }
+ final Collection events = FileBasedIndex.getInstance().getAllKeys(
+ EventNameIndex.KEY, project
+ );
+ // Filter all events declared only for tests.
+ events.removeIf(event -> {
+ final Collection files = FileBasedIndex.getInstance()
+ .getContainingFiles(
+ EventNameIndex.KEY,
+ event,
+ GlobalSearchScope.allScope(project)
+ );
+ final List realObservers = new ArrayList<>();
+
+ for (final VirtualFile file : files) {
+ if (!MagentoTestSourceFilter.isTestSources(file, project)) {
+ realObservers.add(file);
+ }
+ }
+
+ return realObservers.isEmpty();
+ });
+ this.eventName = new FilteredComboBox(new ArrayList<>(events));
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/ModuleObserverGenerator.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/ModuleObserverGenerator.java
new file mode 100644
index 000000000..c3c36edba
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/ModuleObserverGenerator.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.generation.generator;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiFile;
+import com.magento.idea.magento2plugin.actions.generation.ModuleObserverData;
+import com.magento.idea.magento2plugin.actions.generation.generator.util.FileFromTemplateGenerator;
+import com.magento.idea.magento2plugin.magento.files.ModuleObserverFile;
+import java.util.Properties;
+import org.jetbrains.annotations.NotNull;
+
+public class ModuleObserverGenerator extends FileGenerator {
+
+ private final ModuleObserverData moduleObserverData;
+ private final FileFromTemplateGenerator fileFromTemplateGenerator;
+
+ /**
+ * Construct generator.
+ *
+ * @param moduleObserverData ModuleObserverData
+ * @param project Project
+ */
+ public ModuleObserverGenerator(
+ final @NotNull ModuleObserverData moduleObserverData,
+ final Project project
+ ) {
+ super(project);
+ this.moduleObserverData = moduleObserverData;
+ this.fileFromTemplateGenerator = new FileFromTemplateGenerator(project);
+ }
+
+ /**
+ * Generate file.
+ *
+ * @param actionName String
+ *
+ * @return PsiFile
+ */
+ @Override
+ public PsiFile generate(final String actionName) {
+ return fileFromTemplateGenerator.generate(
+ new ModuleObserverFile(moduleObserverData.getClassName()),
+ getAttributes(),
+ moduleObserverData.getBaseDir(),
+ actionName
+ );
+ }
+
+ /**
+ * Fill template properties.
+ *
+ * @param attributes Properties
+ */
+ @Override
+ protected void fillAttributes(final Properties attributes) {
+ attributes.setProperty("CLASS_NAME", ModuleObserverFile.resolveClassNameFromInput(
+ moduleObserverData.getClassName()
+ ));
+ attributes.setProperty("NAMESPACE", moduleObserverData.getClassFqn());
+ attributes.setProperty("EVENT_NAME", moduleObserverData.getEventName());
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/magento/files/ModuleObserverFile.java b/src/com/magento/idea/magento2plugin/magento/files/ModuleObserverFile.java
new file mode 100644
index 000000000..c6489a352
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/magento/files/ModuleObserverFile.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.magento.files;
+
+import com.intellij.lang.Language;
+import com.jetbrains.php.lang.PhpLanguage;
+import org.jetbrains.annotations.NotNull;
+
+public class ModuleObserverFile implements ModuleFileInterface {
+
+ public static final String FILE_SUFFIX = "Observer";
+ public static final String EXTENSION = ".php";
+ public static final String TEMPLATE = "Magento Module Observer File";
+ private final String className;
+
+ public ModuleObserverFile(
+ final @NotNull String className
+ ) {
+ this.className = resolveClassNameFromInput(className);
+ }
+
+ /**
+ * Resolve class name from user input.
+ *
+ * @param input String
+ *
+ * @return String
+ */
+ public static String resolveClassNameFromInput(final String input) {
+ if (input.length() <= FILE_SUFFIX.length()) {
+ return input + FILE_SUFFIX;
+ }
+ final String suffix = input.substring(input.length() - FILE_SUFFIX.length());
+
+ if (FILE_SUFFIX.equals(suffix)) {
+ return input;
+ } else {
+ return input + FILE_SUFFIX;
+ }
+ }
+
+ @Override
+ public String getFileName() {
+ return className + EXTENSION;
+ }
+
+ @Override
+ public String getTemplate() {
+ return TEMPLATE;
+ }
+
+ @Override
+ public Language getLanguage() {
+ return PhpLanguage.INSTANCE;
+ }
+}
diff --git a/testData/actions/generation/generator/ModuleObserverGenerator/generateModuleObserverFile/TestClassObserver.php b/testData/actions/generation/generator/ModuleObserverGenerator/generateModuleObserverFile/TestClassObserver.php
new file mode 100644
index 000000000..106eb8ae2
--- /dev/null
+++ b/testData/actions/generation/generator/ModuleObserverGenerator/generateModuleObserverFile/TestClassObserver.php
@@ -0,0 +1,25 @@
+getEvent();
+ // TODO: Implement observer method.
+ }
+}
diff --git a/tests/com/magento/idea/magento2plugin/actions/generation/generator/ModuleObserverGeneratorTest.java b/tests/com/magento/idea/magento2plugin/actions/generation/generator/ModuleObserverGeneratorTest.java
new file mode 100644
index 000000000..e7d3d70e4
--- /dev/null
+++ b/tests/com/magento/idea/magento2plugin/actions/generation/generator/ModuleObserverGeneratorTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.generation.generator;
+
+import com.intellij.psi.PsiFile;
+import com.magento.idea.magento2plugin.actions.generation.ModuleObserverData;
+import com.magento.idea.magento2plugin.magento.files.ModuleObserverFile;
+
+public final class ModuleObserverGeneratorTest extends BaseGeneratorTestCase {
+
+ private static final String CLASS_NAME = "TestClassObserver";
+
+ /**
+ * Test module README.md file generation.
+ */
+ public void testGenerateModuleObserverFile() {
+ final PsiFile expectedFile = myFixture.configureByFile(
+ getFixturePath(CLASS_NAME + ModuleObserverFile.EXTENSION)
+ );
+ final ModuleObserverGenerator generator = new ModuleObserverGenerator(
+ new ModuleObserverData(
+ "Foo",
+ "Bar",
+ "Foo\\Bar\\Observer",
+ "test_event_name",
+ getProjectDirectory(),
+ CLASS_NAME
+ ),
+ myFixture.getProject()
+ );
+ final PsiFile generatedFile = generator.generate("test");
+
+ assertGeneratedFileIsCorrect(
+ expectedFile,
+ generatedFile
+ );
+ }
+}