Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .gemini/styleguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ enforce standard modern Java/Kotlin coding conventions, but strictly police the
as recommended by the modern SDK.
- **Backward Compatibility:** Avoid using `@ApiStatus.Internal` or `@ApiStatus.ScheduledForRemoval` APIs unless strictly necessary.
- **Logging:**
- Reject any use of `System.out.println` or `System.err.println` for logging.
- Enforce the use of the IntelliJ SDK's built-in logger: `com.intellij.openapi.diagnostic.Logger` or our own: `
io.flutter.logging.PluginLogger.
- Reject any use of `System.out.println` or `System.err.println` for logging in `src/` code (integration tests may use them for milestone logging).
- Enforce the use of the IntelliJ SDK's built-in logger (`com.intellij.openapi.diagnostic.Logger`) or our own (`io.flutter.logging.PluginLogger`).
- **Actions:**
- Classes extending `AnAction` must be completely stateless. Flag any `AnAction` class that defines mutable instance variables (fields),
as the platform instantiates a single instance of the action for the lifetime of the IDE.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2026 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/

package io.flutter.integrationTest

import com.intellij.driver.sdk.invokeAction
import com.intellij.driver.sdk.ui.components.UiComponent.Companion.waitFound
import com.intellij.driver.sdk.ui.components.common.ideFrame
import com.intellij.driver.sdk.ui.components.common.toolWindow
import com.intellij.driver.sdk.ui.components.common.toolwindows.projectView
import com.intellij.driver.sdk.waitForIndicators
import com.intellij.ide.starter.driver.engine.BackgroundRun
import com.intellij.ide.starter.driver.engine.runIdeWithDriver
import com.intellij.ide.starter.junit5.config.UseLatestDownloadedIdeBuild
import com.intellij.ide.starter.project.LocalProjectInfo
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.extension.ExtendWith
import java.nio.file.Paths
import kotlin.time.Duration.Companion.minutes
import io.flutter.integrationTest.utils.createFlutterProjectWithCli
import io.flutter.integrationTest.utils.deleteFlutterProject

@Tag("ui")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(UseLatestDownloadedIdeBuild::class)
class DeepLinksToolWindowTest {
Comment thread
helin24 marked this conversation as resolved.

private val testProjectName = "my_deep_links_test_project_${System.currentTimeMillis()}"
private lateinit var run: BackgroundRun

@BeforeAll
fun setup() {
createFlutterProjectWithCli(testProjectName, projectName = "deep_links_test")
}

@AfterEach
fun closeIde() {
if (::run.isInitialized) {
println("Closing IDE")
run.closeIdeAndWait()
} else {
println("IDE was not started, skipping close")
}
}

@AfterAll
fun teardown() {
deleteFlutterProject(testProjectName)
}

@Test
fun testDeepLinksToolWindow() {
println("Initializing IDE test context")
val projectPath = Paths.get(System.getProperty("java.io.tmpdir"), testProjectName)
run = Setup.setupTestContextIC("DeepLinksToolWindowTest", LocalProjectInfo(projectPath)).runIdeWithDriver()

run.driver.withContext {
ideFrame {
waitFound()
driver.waitForIndicators(5.minutes)
println("IDE is ready, accessing editor.")

// Make sure indexing is finished and project is healthy
projectView {
projectViewTree.pathExists("deep_links_test", "pubspec.yaml")
}

// Open the Deep Links Tool Window
driver.invokeAction("ActivateFlutterDeepLinksToolWindow")

// Wait for the tool window to be focused or exist
toolWindow(io.flutter.deeplinks.DeepLinksViewFactory.TOOL_WINDOW_ID) {
waitFound(1.minutes)

// Verify a JCEF or JxBrowser component exists. We use 'Browser' in class name robustly.
x("//div[contains(@class, 'Browser')]").waitFound(1.minutes)
}
}
}
}
}
53 changes: 53 additions & 0 deletions testSrc/integration/io/flutter/integrationTest/utils/NewProject.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.intellij.driver.sdk.ui.components.common.welcomeScreen
import com.intellij.driver.sdk.wait
import com.intellij.ide.starter.driver.engine.BackgroundRun
import org.junit.jupiter.api.fail
import java.nio.file.Paths
import kotlin.time.Duration.Companion.seconds

// A Kotlin extension function for the `Finder` class.
Expand Down Expand Up @@ -101,3 +102,55 @@ fun newProjectWelcomeScreen(run: BackgroundRun, testProjectName: String) {
}
}
}

/**
* Creates a new Flutter project using the Flutter CLI.
*
* @param testProjectName The folder name for the new project.
* @param projectName The Dart package name of the project.
* @param directory The parent directory where the project folder will be created.
*/
fun createFlutterProjectWithCli(
testProjectName: String,
projectName: String = "test_project",
directory: String = System.getProperty("java.io.tmpdir")
) {
val flutterSdk = System.getenv("FLUTTER_SDK")
?: throw IllegalStateException("FLUTTER_SDK environment variable not set")
val flutterExe = Paths.get(flutterSdk, "bin", "flutter").toString()

println("Creating project $testProjectName in $directory")
val process = ProcessBuilder(flutterExe, "create", "--project-name", projectName, testProjectName)
.directory(java.io.File(directory))
.redirectErrorStream(true)
.start()

val exitCode = process.waitFor()
if (exitCode != 0) {
val output = process.inputStream.bufferedReader().readText()
throw IllegalStateException("flutter create failed: $output")
}
}

/**
* Deletes a test project folder.
*
* @param testProjectName The folder name of the project.
* @param directory The parent directory where the project folder is located. Defaults to the system tmp directory.
*/
fun deleteFlutterProject(
testProjectName: String,
directory: String = System.getProperty("java.io.tmpdir")
) {
if (testProjectName.isNotEmpty()) {
val projectPath = Paths.get(directory, testProjectName)
val projectFile = projectPath.toFile()
if (projectFile.exists()) {
projectFile.deleteRecursively()
println("Successfully deleted test folder: $projectPath")
} else {
println("Test folder does not exist, skipping cleanup: $projectPath")
}
}
}

Loading