How to write a unit test with InjectedLanguageManager working?

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";
	}
}