I’m building a plugin with a feature using HTML injected in JS like that :
import {LitElement, html} from 'lit';
import {customElement} from 'lit/decorators.js';
@customElement('test-grid-item-loader')
export class TestGridItemLoader extends LitElement {
render() {
return html`<demo-test></demo-test>`;
}
}
I managed to get HTML PSI items contained in JSStringTemplateExpression in my plugin using com.intellij.lang.injection.InjectedLanguageManager#findInjectedElementAt
But in unit test context, this returns null instead of HTML PSI element. I didn’t find any way to make it works. I didn’t find any documentation about that topic.
I tried in HeavyPlatformTestCase or BasePlatformTestCase, with a test like this:
public class GridJsUtilsTest extends BasePlatformTestCase {
public void testDemo() throws IOException {
PsiFile jsFile = myFixture.configureByFile("demo.js");
// Find all template literals in the file
Collection<JSStringTemplateExpression> embeddedContents = PsiTreeUtil.findChildrenOfType(jsFile, JSStringTemplateExpression.class);
System.out.println("Found embedded contents: " + embeddedContents.size());
assertEquals(1, embeddedContents.size()); // OK
InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(getProject());
for (JSStringTemplateExpression templateExpression : embeddedContents) {
System.out.println("Template expression: " + templateExpression.getText()); // <demo-test></demo-test>
System.out.println("Injected element: " + injectedLanguageManager.findInjectedElementAt(jsFile, templateExpression.getTextOffset() + 5)); // null
var injections = injectedLanguageManager.getInjectedPsiFiles(templateExpression);
System.out.println("Injection: " + injections); // null
System.out.println("Injection host: " + injectedLanguageManager.getInjectionHost(templateExpression)); // null
}
}
}
With some reverse engineering I found com.intellij.javascript.testFramework.web.JSHtmlParsingTest.MockInjectedLanguageManager but without source I’m not able to get how this works.
I can’t explain why .findInjectedElementAt() doesn’t work, but here’s what I do (in Kotlin, sorry):
class InjectedTestCase : BasePlatformTestCase() {
private val injectedLanguageManager: InjectedLanguageManager
get() = InjectedLanguageManager.getInstance(myFixture.project)
private val hostFile: PsiFile
get() = injectedLanguageManager.getTopLevelFile(myFixture.file)
private val PsiElement.injectedElements: List<PsiElement>
get() = injectedLanguageManager.getInjectedPsiFiles(this)?.map { it.first } ?: emptyList()
/**
* All injected elements in [this], including nested ones.
*/
private val PsiElement.fragments: Sequence<PsiElement>
get() = children.asSequence().flatMap { it.injectedElements.asSequence() + it.fragments }
fun test() {
myFixture.configureByFile("some_file.py") // Host file: Python
val allInjectedElements = hostFile.fragments.toList()
// Injected file: TOML
allInjectedElements.forEach { assertInstanceOf(it, TomlFile::class.java) }
}
}
My code is here, in case you want to inspect further.
Thank you for the answer.
Converted to java I get following code, but nothing is found by getFragments.
I think I’m missing a dependency for testing or something like that, I think JS/HTML language injection is coming from IntelliJ Ultimate.
I have that configuration in build.gradle.kts
dependencies {
testImplementation(libs.junit)
testImplementation(libs.opentest4j)
intellijPlatform {
create(providers.gradleProperty("platformType"), providers.gradleProperty("platformVersion"))
// Plugin Dependencies. Uses `platformBundledPlugins` property from the gradle.properties file for bundled IntelliJ Platform plugins.
bundledPlugins(providers.gradleProperty("platformBundledPlugins").map { it.split(',') })
// Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file for plugin from JetBrains Marketplace.
plugins(providers.gradleProperty("platformPlugins").map { it.split(',') })
// testFramework(TestFrameworkType.Starter)
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.Plugin.Java)
testFramework(TestFrameworkType.Plugin.JavaScript)
}
}
With following properties
platformType = IU
platformVersion = 2025.1
platformPlugins =
platformBundledPlugins = com.intellij.java, JavaScript, com.intellij.database
Unit test:
package com.test;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.fixtures.BasePlatformTestCase;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class InjectedTestCase extends BasePlatformTestCase {
private InjectedLanguageManager getInjectedLanguageManager() {
return InjectedLanguageManager.getInstance(myFixture.getProject());
}
private PsiFile getHostFile() {
return getInjectedLanguageManager().getTopLevelFile(myFixture.getFile());
}
private List<PsiElement> getInjectedElements(PsiElement element) {
List<Pair<PsiElement, TextRange>> injectedPsiFiles = getInjectedLanguageManager().getInjectedPsiFiles(element);
if (injectedPsiFiles == null) {
return Collections.emptyList();
}
return injectedPsiFiles.stream()
.map(pair -> pair.getFirst())
.collect(Collectors.toList());
}
/**
* All injected elements in the given element, including nested ones.
*/
private Stream<PsiElement> getFragments(PsiElement element) {
return Stream.of(element.getChildren())
.flatMap(child ->
Stream.concat(
getInjectedElements(child).stream(),
getFragments(child)
)
);
}
public void test() {
myFixture.configureByFile("test.js");
System.out.println("Host file: " + getHostFile()); // Host file: JSFile:test.js
List<PsiElement> allInjectedElements = getFragments(getHostFile()).toList();
System.out.println("Found injected elements: " + allInjectedElements.size()); // Found injected elements: 0
// Injected file: TOML
for (PsiElement element : allInjectedElements) {
System.out.println(element);
System.out.println(element.getText());
}
}
@Override
protected String getTestDataPath() {
return "src/test/testData";
}
}