Skip to content

Commit

Permalink
PHP: implemented the ability to run a test method when clicking the r…
Browse files Browse the repository at this point in the history
…un icon in the gutter editor.
  • Loading branch information
troizet committed Jan 16, 2025
1 parent b556042 commit 75bd763
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.php.project;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.netbeans.api.editor.EditorRegistry;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodController;
import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodController.TestMethod;
import org.netbeans.modules.php.api.editor.EditorSupport;
import org.netbeans.modules.php.api.editor.PhpClass;
import org.netbeans.modules.php.api.editor.PhpType;
import org.netbeans.modules.php.api.phpmodule.PhpModule;
import org.netbeans.modules.php.api.util.FileUtils;
import org.netbeans.modules.php.project.ui.actions.support.CommandUtils;
import org.netbeans.modules.php.project.util.PhpProjectUtils;
import org.netbeans.modules.php.spi.testing.PhpTestingProvider;
import org.netbeans.spi.project.SingleMethod;
import org.openide.filesystems.FileObject;
import org.openide.util.Lookup;
import org.openide.util.RequestProcessor;


public class ComputeTestMethodAnnotations implements DocumentListener, PropertyChangeListener, Runnable {

private static final Logger LOGGER = Logger.getLogger(ComputeTestMethodAnnotations.class.getName());

private static final RequestProcessor RP = new RequestProcessor(ComputeTestMethodAnnotations.class);
private final RequestProcessor.Task task = RP.create(this);

private static final ComputeTestMethodAnnotations INSTANCE = new ComputeTestMethodAnnotations();

private static final AtomicInteger usagesCount = new AtomicInteger(0);

private volatile Document handledDocument;

public static ComputeTestMethodAnnotations getInstance() {
return INSTANCE;
}

private ComputeTestMethodAnnotations() {
}

public void register() {
if (usagesCount.getAndIncrement() == 0) {
EditorRegistry.addPropertyChangeListener(this);
}
}

public void unregister() {
if (usagesCount.decrementAndGet() == 0) {
EditorRegistry.removePropertyChangeListener(this);
}
}

@Override
public void propertyChange(PropertyChangeEvent event) {
JTextComponent textComponent = EditorRegistry.lastFocusedComponent();
if (textComponent != null) {
Document document = textComponent.getDocument();
if (document != null) {
FileObject fileObject = NbEditorUtilities.getFileObject(document);
if (fileObject != null) {
if (FileUtils.isPhpFile(fileObject)) {
if (event.getPropertyName().equals(EditorRegistry.FOCUS_GAINED_PROPERTY)) {
handleFileChange(document);
document.addDocumentListener(ComputeTestMethodAnnotations.this);
} else if (event.getPropertyName().equals(EditorRegistry.FOCUS_LOST_PROPERTY)) {
document.removeDocumentListener(this);
}
}
}
}
}
}

@Override
public void insertUpdate(DocumentEvent event) {
handleFileChange(event.getDocument());
}

@Override
public void removeUpdate(DocumentEvent event) {
handleFileChange(event.getDocument());
}

@Override
public void changedUpdate(DocumentEvent event) {
handleFileChange(event.getDocument());
}

private void handleFileChange(Document doc) {
handledDocument = doc;
task.schedule(500);
}

@Override
public void run() {
List<TestMethod> testMethods = new ArrayList<>();

List<TestMethod> methods = getTestMethods(handledDocument);
testMethods.addAll(methods);

/*
* Apparently, this method should update the list of annotations at each call of this method,
* when the passed collection of methods changes.
* Вut I didn't manage to achieve correct work in this case.
* So I made the method call first with the empty collection, to clear the annotation list,
* then already with the method collection.
*/
TestMethodController.setTestMethods(handledDocument, Collections.emptyList());
TestMethodController.setTestMethods(handledDocument, testMethods);
}

public List<TestMethod> getTestMethods(Document document) {
List<TestMethod> testMethods = new ArrayList<>();
FileObject fileObject = NbEditorUtilities.getFileObject(document);
if (fileObject != null) {
PhpProject project = PhpProjectUtils.getPhpProject(fileObject);
assert project != null;
PhpModule phpModule = project.getPhpModule();
for (PhpTestingProvider testingProvider : project.getTestingProviders()) {
if (testingProvider.isTestFile(phpModule, fileObject)) {
EditorSupport editorSupport = Lookup.getDefault().lookup(EditorSupport.class);
assert editorSupport != null;
Collection<PhpType> elements = editorSupport.getTypes(fileObject);
for (PhpType element : elements) {
if (element instanceof PhpClass) {
for (PhpClass.Method method : element.getMethods()) {
if (!(method instanceof PhpType.Method)) {
continue;
}

if (testingProvider.isTestCase(phpModule, method)) {
try {
testMethods.add(new TestMethod(
element.getName(),
new SingleMethod(fileObject, CommandUtils.encodeMethod(method.getPhpType().getFullyQualifiedName(), method.getName())),
document.createPosition(method.getOffset()),
document.createPosition(method.getOffset() + method.getName().length())
));
} catch (BadLocationException exception) {
LOGGER.log(Level.WARNING, "Unable to create position: offset: {0}; method: {1}; class: {2}.", new Object[] {exception.offsetRequested(), method.getName(), element.getName()}); // NOI18N
}
}
}

if (!testMethods.isEmpty()) {
return testMethods;
}
}
}

}
}
}

return testMethods;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,8 @@ protected void projectOpened() {
// autoconfigured?
checkAutoconfigured();

ComputeTestMethodAnnotations.getInstance().register();

// #187060 - exception in projectOpened => project IS NOT opened (so move it at the end of the hook)
getCopySupport().projectOpened();

Expand Down Expand Up @@ -885,6 +887,8 @@ protected void projectClosed() {
jsTestingProvider.projectClosed(PhpProject.this);
}

ComputeTestMethodAnnotations.getInstance().unregister();

} finally {
// #187060 - exception in projectClosed => project IS closed (so do it in finally block)
getCopySupport().projectClosed();
Expand Down

0 comments on commit 75bd763

Please sign in to comment.