fix: Add file tests + migrate to IntelliJ Platform Gradle Plugin v2

The tests use fixtures and operate on real files in a temp directory.
This change will allow us to confidently migrate from java.io.File to VFS.

Note: These tests are headless and starts the Continue binary. This gives us many possibilities because we can write fast and valuable tests without runIdeForUiTests.

The IntelliJ Platform plugin already includes classes like BasePlatformTestCase written in JUnit 3, which we should be using for unit testing.
We also have JUnit 5 for e2e testing. As a result, we had two different runners, which caused problems because we couldn’t use classes like UsefulTest since they required a different runner.
In order to fix that, I had to add `org.junit.vintage:junit-vintage-engine` to enable running both JUnit 3 and JUnit 5 tests.

Note: ideally, we should have a sourceSet named testIntegration with its own dependencies for E2E (robot, JUnit 5, video recorder).
That would give us a clean separation between E2E tests and unit tests. That would prevent accidental use of JUnit 5 classes in unit tests. We should do that in the future.

The new platform simplifies plugin config and allows us to remove some of the custom code.
I left the original Qodana and Kover configs, but looking at our workflow, i think it's currently dead code.
If we don’t plan to use them, I think we can remove these dependencies in the future.

Note: along with this migration, I had to bump a wrapper to latest version.

For some reason, we included the Kotlin standard library with the plugin, even though we had a property specifying we didn’t want to.
I saw no objections to removing this dependency, so I decided to do that.

From now on, we rely on older stdlib that's bundled with 2022.3+.

More context: https://github.com/continuedev/continue/pull/6431#issuecomment-3031838189

This proves my point regarding the removal of the standard library dependency.

I added plugin verification against versions 2022.3.3 (oldest) and 2025.1.4 (newest). Everything is working.

Plugin can be verified with `./gradlew verifyPlugin`. We can consider adding this to CI later.

I made a few small improvements that simplify the build scripts:
* Some properties were removed because they duplicated content from `plugin.xml` (e.g., supported version numbers, plugin ID, and similar - all were unnecessarily overridden inside `intellij {}`/`intellijPlatform {}`).
* I replaced `prepareSandbox` with a separate task that is now a dependency of all `runIde` tasks, so there is no need to declare each case individually.
* From now on, properties are read via delegation instead of calling our custom `properties(...)` function. This is type-safe (if a property is not declared, the build will fail).
* I removed the `kotlin("test")` dependency, which we were only using for `assertTrue`.

The e2e Autocomplete tests are very flaky, and unfortunately, I haven’t been able to find a solution yet. I will try to address this in upcoming fixes.
I believe that ultimately we should have max one e2e test describing the happy path, and the rest of the features should be tested with unit tests.
Our e2e tests are very inconvenient, which results in no one running them (because we hijack the input), so they should be kept to a minimum.
The rest of the e2e tests were already disabled (I assume I know why :D), so I didn't touch them.

Fun fact: with the removal of the standard library, we managed to remove all unnecessary dependencies except for Sentry and PostHog (which are included for obvious reasons).
This commit is contained in:
Jakub Blach 2025-07-28 18:47:53 +02:00
parent 076ff3efc1
commit 5f647afdf2
14 changed files with 285 additions and 236 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.0" />
<option name="version" value="1.8.0" />
</component>
</project>

View File

@ -110,6 +110,23 @@ You can selectively increase the log granularity (e.g., debug-level logs) as fol
You can find more information about this feature in [official docs](https://youtrack.jetbrains.com/articles/SUPPORT-A-43/How-to-enable-debug-logging-in-IntelliJ-IDEA).
### Developing `build.plugin.kts`
If in doubt, check out the
official [IntelliJ Platform Plugin Template](https://github.com/JetBrains/intellij-platform-plugin-template).
These templates are the most up-to-date examples of how to correctly customize the plugin build scripts.
Also, check out
the [useful recipes](https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-recipes.html)
for common problems.
### Adding new extensions in `plugin.xml`
There's a tool called [JetBrains Platform Explorer](https://plugins.jetbrains.com/intellij-platform-explorer) that
aggregates plugin metadata and allows you to filter by specific
extension points. If you're having trouble implementing a feature that's not officially documented,
you can learn from other open source plugins.
### Reloading changes
- `extensions/intellij`: Attempt to reload changed classes by selecting

View File

@ -1,77 +1,75 @@
import org.jetbrains.changelog.markdownToHTML
fun properties(key: String) = providers.gradleProperty(key)
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.PrepareSandboxTask
fun environment(key: String) = providers.environmentVariable(key)
fun Sync.prepareSandbox() {
from("../../binary/bin") { into("${intellij.pluginName.get()}/core/") }
}
val remoteRobotVersion = "0.11.23"
val platformType: String by project
val platformVersion: String by project
val pluginGroup: String by project
val pluginVersion: String by project
val pluginSinceBuild: String by project
val pluginRepositoryUrl: String by project
val isEap get() = environment("RELEASE_CHANNEL").orNull == "eap"
val pluginVersion = properties("pluginVersion").get()
plugins {
id("java")
kotlin("jvm") version "1.9.0"
id("org.jetbrains.intellij") version "1.15.0"
kotlin("jvm") version "1.8.0"
id("org.jetbrains.intellij.platform") version "2.6.0"
id("org.jetbrains.changelog") version "2.1.2"
id("org.jetbrains.qodana") version "0.1.13"
id("org.jetbrains.kotlinx.kover") version "0.7.3"
kotlin("plugin.serialization") version "1.8.0"
id("io.sentry.jvm.gradle") version "5.8.0"
}
group = properties("pluginGroup").get()
group = pluginGroup
version = if (isEap) "$pluginVersion-eap" else pluginVersion
repositories {
mavenCentral()
maven { url = uri("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies") }
intellijPlatform {
defaultRepositories()
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.4.32")
implementation("com.posthog.java:posthog:1.+")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
intellijPlatform {
create(platformType, platformVersion)
plugins(listOf("org.jetbrains.plugins.terminal:223.8214.6"))
testFramework(TestFrameworkType.Platform)
}
implementation("com.posthog.java:posthog:1.2.0")
testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
testImplementation("io.mockk:mockk:1.14.2")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.2")
testImplementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
testImplementation("com.automation-remarks:video-recorder-junit5:2.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
testImplementation(kotlin("test"))
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.10.0") // required to run both JUnit 5 and JUnit 3
}
// Set the JVM language level used to build the project. Use Java 11 for 2020.3+, and Java 17 for
// 2022.2+.
kotlin { jvmToolchain(17) }
// Configure Gradle IntelliJ Plugin - read more:
// https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
intellij {
pluginName = properties("pluginName")
version = properties("platformVersion")
type = properties("platformType")
// Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file.
plugins =
properties("platformPlugins").map {
it.split(',').map(String::trim).filter(String::isNotEmpty)
intellijPlatform {
pluginConfiguration {
ideaVersion {
sinceBuild = pluginSinceBuild
}
}
pluginVerification {
ides {
ide("IC", "2025.1.4")
ide("IC", "2022.3.3")
}
}
}
// Configure Gradle Changelog Plugin - read more:
// https://github.com/JetBrains/gradle-changelog-plugin
changelog {
groups.empty()
repositoryUrl = properties("pluginRepositoryUrl")
repositoryUrl = pluginRepositoryUrl
}
// Configure Gradle Qodana Plugin - read more: https://github.com/JetBrains/gradle-qodana-plugin
qodana {
cachePath = provider { file(".qodana").canonicalPath }
reportPath = provider { file("build/reports/inspections").canonicalPath }
@ -79,75 +77,52 @@ qodana {
showReport = environment("QODANA_SHOW_REPORT").map { it.toBoolean() }.getOrElse(false)
}
// Configure Gradle Kover Plugin - read more: https://github.com/Kotlin/kotlinx-kover#configuration
koverReport { defaults { xml { onCheck = true } } }
intellijPlatformTesting {
runIde {
register("runIdeForUiTests") {
task {
environment(
"CONTINUE_GLOBAL_DIR",
"${rootProject.projectDir}/src/test/kotlin/com/github/continuedev/continueintellijextension/e2e/test-continue"
)
jvmArgumentProviders += CommandLineArgumentProvider {
listOf(
"-Drobot-server.port=8082",
"-Dide.mac.message.dialogs.as.sheets=false",
"-Djb.privacy.policy.text=<!--999.999-->",
"-Djb.consents.confirmation.enabled=false",
"-Dide.mac.file.chooser.native=false",
"-DjbScreenMenuBar.enabled=false",
"-Dapple.laf.useScreenMenuBar=false",
"-Didea.trust.all.projects=true",
"-Dide.show.tips.on.startup.default.value=false",
"-Dide.browser.jcef.jsQueryPoolSize=10000",
"-Dide.browser.jcef.contextMenu.devTools.enabled=true"
)
}
}
plugins {
robotServerPlugin()
}
}
}
}
tasks {
downloadRobotServerPlugin {
version = remoteRobotVersion
}
prepareSandbox {
prepareSandbox()
}
prepareTestingSandbox {
prepareSandbox()
}
prepareUiTestingSandbox {
prepareSandbox()
}
wrapper {
gradleVersion = properties("gradleVersion").get()
withType<PrepareSandboxTask> {
from("../../binary/bin") {
into(pluginName.map { "$it/core" })
}
}
patchPluginXml {
sinceBuild = properties("pluginSinceBuild")
untilBuild = properties("pluginUntilBuild")
pluginDescription =
providers.fileContents(layout.projectDirectory.file("README.md")).asText.map {
val start = "<!-- Plugin description -->"
val end = "<!-- Plugin description end -->"
with(it.lines()) {
if (!containsAll(listOf(start, end))) {
throw GradleException(
"Plugin description section not found in README.md:\n$start ... $end"
)
}
subList(indexOf(start) + 1, indexOf(end)).joinToString("\n").let(::markdownToHTML)
}
}
}
// Configure UI tests plugin
// Read more: https://github.com/JetBrains/intellij-ui-test-robot
runIdeForUiTests {
environment(
"CONTINUE_GLOBAL_DIR",
"${rootProject.projectDir}/src/test/kotlin/com/github/continuedev/continueintellijextension/e2e/test-continue"
)
systemProperty("robot-server.port", "8082")
systemProperty("ide.mac.message.dialogs.as.sheets", "false")
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
systemProperty("jb.consents.confirmation.enabled", "false")
systemProperty("ide.mac.file.chooser.native", "false")
systemProperty("jbScreenMenuBar.enabled", "false")
systemProperty("apple.laf.useScreenMenuBar", "false")
systemProperty("idea.trust.all.projects", "true")
systemProperty("ide.show.tips.on.startup.default.value", "false")
systemProperty("ide.browser.jcef.jsQueryPoolSize", "10000")
systemProperty("ide.browser.jcef.contextMenu.devTools.enabled", "true")
// This is to ensure we load the GUI with OSR enabled. We have logic that
// renders with OSR disabled below a particular IDE version.
// See ContinueExtensionSettingsService.kt for more info.
// Currently commented out however since test fail in CI with this version
// intellij {
// version = "2024.1"
// }
pluginDescription = providers.fileContents(layout.projectDirectory.file("README.md")).asText.get()
.substringAfter("<!-- Plugin description -->")
.substringBefore("<!-- Plugin description end -->")
.let(::markdownToHTML)
check(pluginDescription.get().isNotEmpty()) { "Plugin description section not found in README.md" }
}
signPlugin {
@ -164,8 +139,10 @@ tasks {
}
runIde {
val dir = "${rootProject.projectDir.parentFile.parentFile.absolutePath}/manual-testing-sandbox"
args = listOf(dir, "$dir/test.kt")
val openProject = "$projectDir/../../manual-testing-sandbox"
argumentProviders += CommandLineArgumentProvider {
listOf(openProject, "$openProject/test.kt")
}
}
test {

View File

@ -1,26 +1,9 @@
# IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html
pluginGroup=com.github.continuedev.continueintellijextension
pluginName=continue-intellij-extension
pluginRepositoryUrl=https://github.com/continuedev/continue
# SemVer format -> https://semver.org
pluginVersion=1.0.33
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild=223
# IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension
platformType=IC
platformVersion=2022.3.3
# org.gradle.java.home=/opt/homebrew/opt/openjdk@17
#platformVersion = LATEST-EAP-SNAPSHOT
# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
platformPlugins=org.jetbrains.plugins.terminal
# Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion=8.3
# Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
kotlin.stdlib.default.dependency=false
# Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
org.gradle.configuration-cache=true
# Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
org.gradle.caching=true
# Enable Gradle Kotlin DSL Lazy Property Assignment -> https://docs.gradle.org/current/userguide/kotlin_dsl.html#kotdsl:assignment
systemProp.org.gradle.unsafe.kotlin.assignment=true

View File

@ -1,8 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
#org.gradle.java.home=/opt/homebrew/opt/openjdk@17

View File

@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -202,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

View File

@ -3,6 +3,7 @@ package com.github.continuedev.continueintellijextension.`continue`
import com.github.continuedev.continueintellijextension.*
import com.github.continuedev.continueintellijextension.constants.ContinueConstants
import com.github.continuedev.continueintellijextension.constants.getContinueGlobalPath
import com.github.continuedev.continueintellijextension.`continue`.file.FileUtils
import com.github.continuedev.continueintellijextension.error.ContinueErrorService
import com.github.continuedev.continueintellijextension.services.ContinueExtensionSettings
import com.github.continuedev.continueintellijextension.services.ContinuePluginService
@ -38,9 +39,7 @@ import java.awt.Toolkit
import java.awt.datatransfer.DataFlavor
import java.io.BufferedReader
import java.io.File
import java.io.FileInputStream
import java.io.InputStreamReader
import java.nio.charset.Charset
class IntelliJIDE(
private val project: Project,
@ -49,7 +48,7 @@ class IntelliJIDE(
) : IDE {
private val gitService = GitService(project, continuePluginService)
private val fileUtils = FileUtils()
private val ripgrep: String = getRipgrepPath()
init {
@ -204,16 +203,11 @@ class IntelliJIDE(
return configs as List<ContinueRcJson>
}
override suspend fun fileExists(filepath: String): Boolean {
val file = UriUtils.uriToFile(filepath)
return file.exists()
}
override suspend fun fileExists(filepath: String): Boolean =
fileUtils.fileExists(filepath)
override suspend fun writeFile(path: String, contents: String) {
val file = UriUtils.uriToFile(path)
file.parentFile?.mkdirs()
file.writeText(contents)
}
override suspend fun writeFile(path: String, contents: String) =
fileUtils.writeFile(path, contents)
override suspend fun showVirtualFile(title: String, contents: String) {
val virtualFile = LightVirtualFile(title, contents)
@ -321,39 +315,8 @@ class IntelliJIDE(
}
}
override suspend fun readFile(filepath: String): String {
return try {
val content = ApplicationManager.getApplication().runReadAction<String?> {
val virtualFile = LocalFileSystem.getInstance().findFileByPath(UriUtils.parseUri(filepath).path)
if (virtualFile != null && FileDocumentManager.getInstance().isFileModified(virtualFile)) {
return@runReadAction FileDocumentManager.getInstance().getDocument(virtualFile)?.text
}
return@runReadAction null
}
if (content != null) {
content
} else {
val file = UriUtils.uriToFile(filepath)
if (!file.exists() || file.isDirectory) return ""
withContext(Dispatchers.IO) {
FileInputStream(file).use { fis ->
val sizeToRead = minOf(100000, file.length()).toInt()
val buffer = ByteArray(sizeToRead)
val bytesRead = fis.read(buffer, 0, sizeToRead)
if (bytesRead <= 0) return@use ""
val content = String(buffer, 0, bytesRead, Charset.forName("UTF-8"))
// Remove `\r` characters but preserve trailing newlines to prevent line count discrepancies
val contentWithoutCR = content.replace("\r\n", "\n").replace("\r", "\n")
contentWithoutCR
}
}
}
} catch (e: Exception) {
e.printStackTrace()
""
}
}
override suspend fun readFile(filepath: String): String =
fileUtils.readFile(filepath)
override suspend fun readRangeInFile(filepath: String, range: Range): String {
val fullContents = readFile(filepath)
@ -662,13 +625,8 @@ class IntelliJIDE(
}
}
override suspend fun listDir(dir: String): List<List<Any>> {
val files = UriUtils.uriToFile(dir).listFiles()?.map {
listOf(it.name, if (it.isDirectory) FileType.DIRECTORY.value else FileType.FILE.value)
} ?: emptyList()
return files
}
override suspend fun listDir(dir: String): List<List<Any>> =
fileUtils.listDir(dir)
override suspend fun getFileStats(files: List<String>): Map<String, FileStats> {
return files.associateWith { file ->

View File

@ -0,0 +1,69 @@
package com.github.continuedev.continueintellijextension.`continue`.file
import com.github.continuedev.continueintellijextension.FileType
import com.github.continuedev.continueintellijextension.`continue`.UriUtils
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.vfs.LocalFileSystem
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.FileInputStream
import java.nio.charset.Charset
class FileUtils {
// todo: use VFS (it's moved from IntellijIde)
fun fileExists(uri: String): Boolean {
val file = UriUtils.uriToFile(uri)
return file.exists()
}
fun writeFile(uri: String, contents: String) {
val file = UriUtils.uriToFile(uri)
file.parentFile?.mkdirs()
file.writeText(contents)
}
fun listDir(dir: String): List<List<Any>> {
val files = UriUtils.uriToFile(dir).listFiles()?.map {
listOf(it.name, if (it.isDirectory) FileType.DIRECTORY.value else FileType.FILE.value)
} ?: emptyList()
return files
}
fun readFile(uri: String): String {
return try {
val content = ApplicationManager.getApplication().runReadAction<String?> {
val virtualFile = LocalFileSystem.getInstance().findFileByPath(UriUtils.parseUri(uri).path)
if (virtualFile != null && FileDocumentManager.getInstance().isFileModified(virtualFile)) {
return@runReadAction FileDocumentManager.getInstance().getDocument(virtualFile)?.text
}
return@runReadAction null
}
if (content != null) {
content
} else {
val file = UriUtils.uriToFile(uri)
if (!file.exists() || file.isDirectory) return ""
FileInputStream(file).use { fis ->
val sizeToRead = minOf(100000, file.length()).toInt()
val buffer = ByteArray(sizeToRead)
val bytesRead = fis.read(buffer, 0, sizeToRead)
if (bytesRead <= 0) return@use ""
val content = String(buffer, 0, bytesRead, Charset.forName("UTF-8"))
// Remove `\r` characters but preserve trailing newlines to prevent line count discrepancies
val contentWithoutCR = content.replace("\r\n", "\n").replace("\r", "\n")
contentWithoutCR
}
}
} catch (e: Exception) {
e.printStackTrace()
""
}
}
}

View File

@ -14,9 +14,6 @@
com.intellij.modules.json
</depends>
<!-- com.intellij.openapi.module.ModuleManager.Companion is only available since this build -->
<idea-version since-build="223.7571.182"/>
<extensions defaultExtensionNs="com.intellij">
<editorFactoryListener
implementation="com.github.continuedev.continueintellijextension.autocomplete.AutocompleteEditorListener"/>

View File

@ -12,13 +12,12 @@ import com.github.continuedev.continueintellijextension.services.ContinuePluginS
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import io.mockk.*
import junit.framework.TestCase
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@ExperimentalCoroutinesApi
class ApplyToFileHandlerTest {
class ApplyToFileHandlerTest : TestCase() {
// Mock all dependencies
private val mockProject = mockk<Project>(relaxed = true)
private val mockContinuePluginService = mockk<ContinuePluginService>(relaxed = true)
@ -39,8 +38,7 @@ class ApplyToFileHandlerTest {
"tool-call-123"
)
@BeforeEach
fun setUp() {
override fun setUp() {
// Common setup
every { mockEditorUtils.editor } returns mockEditor
every { mockContinuePluginService.coreMessenger } returns mockCoreMessenger
@ -56,8 +54,7 @@ class ApplyToFileHandlerTest {
)
}
@Test
fun `should insert text directly when document is empty`() = runTest {
fun `test should insert text directly when document is empty`() = runTest {
// Given
every { mockEditorUtils.isDocumentEmpty() } returns true

View File

@ -1,48 +1,39 @@
package com.github.continuedev.continueintellijextension.unit
import com.github.continuedev.continueintellijextension.FimResult
import com.github.continuedev.continueintellijextension.`continue`.UriUtils
import com.github.continuedev.continueintellijextension.utils.checkFim
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import java.io.File
import kotlin.test.assertEquals
import junit.framework.TestCase
class CheckFimTest {
@Test
fun testFim() {
class CheckFimTest : TestCase() {
fun `test FIM`() {
val oldEditRange = "hello world"
val newEditRange = "hello this world"
val cursorPosition = Pair(0, 6)
assertEquals(checkFim(oldEditRange, newEditRange, cursorPosition), FimResult.FimEdit("this "))
}
@Test
fun testMultilineFim() {
fun `test multiline FIM`() {
val oldEditRange = "hello {\n world\n}"
val newEditRange = "hello {\n print()\n world\n}"
val cursorPosition = Pair(1, 2)
assertEquals(checkFim(oldEditRange, newEditRange, cursorPosition), FimResult.FimEdit("print()\n "))
}
@Test
fun testNotFim() {
fun `test not FIM`() {
val oldEditRange = "hello world"
val newEditRange = "hello this world"
val cursorPosition = Pair(0, 8)
assertEquals(checkFim(oldEditRange, newEditRange, cursorPosition), FimResult.NotFimEdit)
}
@Test
fun testMultilineNotFim() {
fun `test multiline not FIM`() {
val oldEditRange = "hello {\n world\n}"
val newEditRange = "hello {\n world\n}\nprint()"
val cursorPosition = Pair(1, 2)
assertEquals(checkFim(oldEditRange, newEditRange, cursorPosition), FimResult.NotFimEdit)
}
@Test
fun testFimEdge() {
fun `test FIM edge`() {
val oldEditRange = ""
val newEditRange = ""
val cursorPosition = Pair(0, 0)

View File

@ -0,0 +1,77 @@
package com.github.continuedev.continueintellijextension.unit
import com.github.continuedev.continueintellijextension.FileType
import com.github.continuedev.continueintellijextension.`continue`.file.FileUtils
import com.intellij.testFramework.UsefulTestCase
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
class FileUtilsTest : UsefulTestCase() {
val fileUtils = FileUtils()
val myFixture = setupRealFilesFixture()
val tmpFixture get() = myFixture.tempDirFixture
val tmpDir get() = myFixture.tempDirPath
override fun setUp() =
myFixture.setUp()
override fun tearDown() =
myFixture.tearDown()
fun `test fileExists`() {
val file = tmpFixture.createFile("test.txt", "")
val uri = "file://${file.path}"
assertTrue(fileUtils.fileExists(uri))
}
fun `test fileExists inside directory`() {
val file = tmpFixture.createFile("dir/dir2/file.txt", "")
val uri = "file://${file.path}"
assertTrue(fileUtils.fileExists(uri))
}
fun `test fileExists fails when file doesn't exists`() {
val uri = "file:///tmp/aaaa.txt"
assertFalse(fileUtils.fileExists(uri))
}
fun `test readFile`() {
val uri = "file://$tmpDir/bbbb.txt"
tmpFixture.createFile("bbbb.txt", "contents")
val text = fileUtils.readFile(uri)
assertEquals("contents", text)
}
fun `test listDir`() {
tmpFixture.createFile("a.txt", "a")
tmpFixture.createFile("b.txt", "b")
tmpFixture.createFile("c.txt", "c")
tmpFixture.createFile("dir/invisibleA.txt", "d")
tmpFixture.createFile("dir/invisibleB.txt", "e")
val result = fileUtils.listDir("file://$tmpDir")
assertTrue(result.any { it[0] == "a.txt" && it[1] == FileType.FILE.value })
assertTrue(result.any { it[0] == "b.txt" && it[1] == FileType.FILE.value })
assertTrue(result.any { it[0] == "c.txt" && it[1] == FileType.FILE.value })
assertTrue(result.any { it[0] == "dir" && it[1] == FileType.DIRECTORY.value })
assertTrue(result.any { it[0] == "dir" && it[1] == FileType.DIRECTORY.value })
assertFalse(result.any { it[0] == "invisibleA.txt" })
assertFalse(result.any { it[0] == "invisibleB.txt" })
assertEquals(4, result.size)
}
private companion object {
// note: with this, files will actually exist inside /tmp/unitTest/ dir, otherwise it's virtual
fun setupRealFilesFixture(): CodeInsightTestFixture {
val factory = IdeaTestFixtureFactory.getFixtureFactory()
return factory.createCodeInsightFixture(
factory.createLightFixtureBuilder("my_continue_project").fixture,
factory.createTempDirTestFixture()
)
}
}
}

View File

@ -1,64 +1,48 @@
package com.github.continuedev.continueintellijextension.unit
import com.github.continuedev.continueintellijextension.`continue`.UriUtils
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import junit.framework.TestCase
import java.io.File
import kotlin.test.assertEquals
class UriUtilsTest {
@Test
fun testUriToFile() {
class UriUtilsTest : TestCase() {
fun `test URI to File`() {
val uri = "file:///path/to/file"
val file = UriUtils.uriToFile(uri)
assertEquals(File("/path/to/file"), file)
}
@Disabled("Not working")
@Test
fun testUriToFileWithWindowsPath() {
val uri = "file:///C:/path/to/file"
val file = UriUtils.uriToFile(uri)
assertEquals(File("C:/path/to/file"), file)
}
// fixme it's not working
// fun `test Windows path`() {
// val uri = "file:///C:/path/to/file"
// val file = UriUtils.uriToFile(uri)
// assertEquals(File("C:/path/to/file"), file)
// }
@Test
fun shouldHandleAuthorityComponent() {
val uri = "file://C:/path/to/file"
val file = UriUtils.uriToFile(uri)
assertEquals(File("/C:/path/to/file"), file)
}
@Test
fun testUriToFileWithSpaces() {
fun `test spaces`() {
val uri = "file:///path/to/file%20with%20spaces"
val file = UriUtils.uriToFile(uri)
assertEquals(File("/path/to/file with spaces"), file)
}
@Test
fun testUriToFileWithSpecialCharacters() {
fun `test special characters`() {
val uri = "file:///path/to/file%23with%25special%26chars"
val file = UriUtils.uriToFile(uri)
assertEquals(File("/path/to/file#with%special&chars"), file)
}
@Test
fun testUriToFileWithQueryParams() {
fun `test query params`() {
val uri = "file:///path/to/file?param=value"
val file = UriUtils.uriToFile(uri)
assertEquals(File("/path/to/file"), file)
}
@Test
fun testUriToFileWithWSLPath() {
fun `test WSL path`() {
val uri = "file:///wsl$/Ubuntu/home/user/file.txt"
val file = UriUtils.uriToFile(uri)
assertEquals(File("/wsl$/Ubuntu/home/user/file.txt"), file)
}
@Test
fun testUriToFileWithWSLLocalhostPath() {
fun `test WSL localhost path`() {
val uri = "file:///wsl.localhost/Ubuntu/home/user/file.txt"
val file = UriUtils.uriToFile(uri)
assertEquals(File("/wsl.localhost/Ubuntu/home/user/file.txt"), file)