From 5b02310a6012733d2dc9a841a173f8aeb24e386f Mon Sep 17 00:00:00 2001 From: tangcent Date: Sat, 8 Jul 2023 22:26:32 +0800 Subject: [PATCH] fix: process correct class when clicking class in multi-class Kotlin file --- .codecov.yml | 3 +- .github/workflows/co.yml | 4 + build.gradle.kts | 18 +- .../itangcent/common/constant/HttpMethod.kt | 2 +- .../com/itangcent/common/kit/KVUtils.kt | 6 + .../com/itangcent/common/model/Request.kt | 4 +- .../itangcent/common/utils/FileSizeUtils.kt | 1 - .../com/itangcent/http/ApacheHttpClient.kt | 110 +++-- .../kotlin/com/itangcent/http/HttpRequest.kt | 4 +- .../common/constant/HttpMethodTest.kt | 56 ++- .../com/itangcent/common/kit/KVUtilsTest.kt | 376 ++++++++++++--- .../com/itangcent/utils/AnyKitKtTest.kt | 15 +- .../com/itangcent/utils/FuncKitKtTest.kt | 56 +++ .../org/apache/http/util/EntityKitsKtTest.kt | 87 ++++ gradle.properties | 2 +- .../com/itangcent/cache/CacheSwitcher.kt | 6 + .../cache/DefaultHttpContextCacheHelper.kt | 2 +- .../itangcent/condition/AnnotatedCondition.kt | 33 +- .../api/cache/CachedRequestClassExporter.kt | 2 +- .../idea/plugin/api/cache/FileApiCache.kt | 2 +- .../idea/plugin/api/export/UrlSelector.kt | 2 +- .../api/export/core/CompositeClassExporter.kt | 3 - .../generic/GenericRequestClassExporter.kt | 2 +- .../SimpleGenericMethodDocClassExporter.kt | 2 +- .../export/jaxrs/JAXRSRequestClassExporter.kt | 2 +- .../jaxrs/SimpleJAXRSRequestClassExporter.kt | 2 +- .../export/postman/DefaultPostmanApiHelper.kt | 2 +- .../plugin/api/export/rule/RequestRuleWrap.kt | 2 +- .../idea/plugin/dialog/ApiDashboardDialog.kt | 2 +- .../plugin/dialog/ScriptExecutorDialog.kt | 2 +- .../idea/plugin/dialog/YapiDashboardDialog.kt | 2 +- .../idea/plugin/rule/RuleToolUtils.kt | 8 +- .../idea/plugin/rule/StandardJdkRuleParser.kt | 2 +- .../itangcent/idea/plugin/utils/KtHelper.kt | 80 ---- .../itangcent/idea/plugin/utils/RegexUtils.kt | 36 +- .../com/itangcent/idea/utils/ExtensibleKit.kt | 9 - .../com/itangcent/idea/utils/IOUtils.kt | 94 ---- .../utils/LazilyParsedNumberTypeAdapter.kt | 39 -- .../utils/NumberFixedObjectTypeAdapter.kt | 132 ------ .../com/itangcent/idea/utils/SwingUtils.kt | 54 ++- .../intellij/extend/ActionContextKit.kt | 37 +- .../com/itangcent/intellij/extend/AnyKit.kt | 71 +-- .../com/itangcent/intellij/extend/GsonKit.kt | 18 + .../kotlin/com/itangcent/order/Ordered.kt | 18 +- .../http/ConfigurableHttpClientProvider.kt | 75 ++- .../com/itangcent/utils/ExtensibleKit.kt | 22 +- .../kotlin/com/itangcent/utils/FileKit.kt | 4 + .../com/itangcent/utils/GiteeSupport.kt | 31 +- .../com/itangcent/utils/ResourceUtils.kt | 33 +- .../kotlin/com/itangcent/utils/StringKit.kt | 19 +- .../kotlin/com/itangcent/utils/TemplateKit.kt | 24 +- .../com/itangcent/cache/CacheSwitcherTest.kt | 99 ++++ .../condition/AnnotatedConditionTest.kt | 140 ++++++ .../idea/plugin/api/call/ApiCallerTest.kt | 3 +- .../idea/plugin/api/export/UrlSelectorTest.kt | 150 ++++++ .../core/DefaultFormatFolderHelperTest.kt | 6 +- .../postman/PostmanFormatFolderHelperTest.kt | 6 +- .../config/RecommendConfigReaderTest.kt | 2 - ...ConfigurableShellFileMarkdownRenderTest.kt | 3 + .../com/itangcent/idea/utils/AnyKitTest.kt | 116 +++-- .../idea/utils/ExtensibleKitKtTest.kt | 26 - .../itangcent/idea/utils/SwingUtilsTest.kt | 448 ++++++++++++++++++ .../intellij/extend/ActionContextKitTest.kt | 226 ++++++++- .../kotlin/com/itangcent/order/OrderedTest.kt | 33 +- .../http/AbstractHttpClientProviderTest.kt | 67 +++ .../ConfigurableHttpClientProviderTest.kt | 119 ++++- .../suv/http/HttpClientProviderTest.kt | 420 +++++++++++++++- .../com/itangcent/test/ActionContextKit.kt | 7 +- .../com/itangcent/utils/ExtensibleKitTest.kt | 111 +++-- .../com/itangcent/utils/FileKitKtTest.kt | 26 + .../com/itangcent/utils/GiteeSupportTest.kt | 25 +- .../com/itangcent/utils/ResourceUtilsTest.kt | 32 +- .../com/itangcent/utils/StringKitKtTest.kt | 36 +- .../com/itangcent/utils/TemplateKitTest.kt | 39 +- 74 files changed, 2906 insertions(+), 852 deletions(-) create mode 100644 common-api/src/test/kotlin/com/itangcent/utils/FuncKitKtTest.kt create mode 100644 common-api/src/test/kotlin/org/apache/http/util/EntityKitsKtTest.kt delete mode 100644 idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/KtHelper.kt delete mode 100644 idea-plugin/src/main/kotlin/com/itangcent/idea/utils/ExtensibleKit.kt delete mode 100644 idea-plugin/src/main/kotlin/com/itangcent/idea/utils/IOUtils.kt delete mode 100644 idea-plugin/src/main/kotlin/com/itangcent/idea/utils/LazilyParsedNumberTypeAdapter.kt delete mode 100644 idea-plugin/src/main/kotlin/com/itangcent/idea/utils/NumberFixedObjectTypeAdapter.kt create mode 100644 idea-plugin/src/test/kotlin/com/itangcent/cache/CacheSwitcherTest.kt create mode 100644 idea-plugin/src/test/kotlin/com/itangcent/condition/AnnotatedConditionTest.kt create mode 100644 idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/UrlSelectorTest.kt delete mode 100644 idea-plugin/src/test/kotlin/com/itangcent/idea/utils/ExtensibleKitKtTest.kt create mode 100644 idea-plugin/src/test/kotlin/com/itangcent/idea/utils/SwingUtilsTest.kt create mode 100644 idea-plugin/src/test/kotlin/com/itangcent/suv/http/AbstractHttpClientProviderTest.kt create mode 100644 idea-plugin/src/test/kotlin/com/itangcent/utils/FileKitKtTest.kt diff --git a/.codecov.yml b/.codecov.yml index 2d17a7e11..7e9ce3412 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -30,4 +30,5 @@ ignore: - "gradlew.bat" - "**/*Dialog.kt" - "**/*Configurable.kt" - - "**/*Action.kt" \ No newline at end of file + - "**/*Action.kt" + - ".*model.*" \ No newline at end of file diff --git a/.github/workflows/co.yml b/.github/workflows/co.yml index 8b63c1188..a8a3944cd 100644 --- a/.github/workflows/co.yml +++ b/.github/workflows/co.yml @@ -24,6 +24,10 @@ jobs: with: java-version: ${{ matrix.java-version }} distribution: 'zulu' + - name: Install Xvfb + run: sudo apt-get update && sudo apt-get install -y xvfb + - name: Start Xvfb + run: Xvfb :99 -screen 0 1024x768x24 & - name: Generate coverage report run: | ./gradlew check --stacktrace diff --git a/build.gradle.kts b/build.gradle.kts index 31277379b..41b53c9f3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,10 +46,22 @@ tasks.create("codeCoverageReport", JacocoReport::class) { executionData( fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec") ) - subprojects.forEach { - sourceDirectories.from(it.file("src/main/kotlin")) - classDirectories.from(it.file("build/classes/kotlin/main")) + + val exclusiveDirectories = listOf("**/common/model/**") + + subprojects.forEach { project -> + sourceDirectories.from(project.files("src/main/kotlin").map { + fileTree(it).matching { + exclude(exclusiveDirectories) + } + }) + classDirectories.from(project.files("build/classes/kotlin/main").map { + fileTree(it).matching { + exclude(exclusiveDirectories) + } + }) } + reports { xml.required.set(true) xml.outputLocation.set(file("${buildDir}/reports/jacoco/report.xml").apply { parentFile.mkdirs() }) diff --git a/common-api/src/main/kotlin/com/itangcent/common/constant/HttpMethod.kt b/common-api/src/main/kotlin/com/itangcent/common/constant/HttpMethod.kt index 807ce812b..ff6b099a3 100644 --- a/common-api/src/main/kotlin/com/itangcent/common/constant/HttpMethod.kt +++ b/common-api/src/main/kotlin/com/itangcent/common/constant/HttpMethod.kt @@ -21,7 +21,7 @@ object HttpMethod { if (method.isBlank()) { return NO_METHOD } - val standardMethod = method.toUpperCase() + val standardMethod = method.uppercase() if (ALL_METHODS.contains(standardMethod)) { return standardMethod } diff --git a/common-api/src/main/kotlin/com/itangcent/common/kit/KVUtils.kt b/common-api/src/main/kotlin/com/itangcent/common/kit/KVUtils.kt index 0478d69bb..39e93202b 100644 --- a/common-api/src/main/kotlin/com/itangcent/common/kit/KVUtils.kt +++ b/common-api/src/main/kotlin/com/itangcent/common/kit/KVUtils.kt @@ -196,6 +196,12 @@ object KVUtils { useFieldAsAttr(value, attr) } } + + is Extensible -> { + model.getPropertyValue("value")?.let { + model.setExt(attr, it) + } + } } } diff --git a/common-api/src/main/kotlin/com/itangcent/common/model/Request.kt b/common-api/src/main/kotlin/com/itangcent/common/model/Request.kt index 51ddc66d8..06c78b404 100644 --- a/common-api/src/main/kotlin/com/itangcent/common/model/Request.kt +++ b/common-api/src/main/kotlin/com/itangcent/common/model/Request.kt @@ -98,10 +98,10 @@ fun Request.header(name: String): String? { if (this.headers.isNullOrEmpty()) { return null } - val lowerName = name.toLowerCase() + val lowerName = name.lowercase() return this.headers!! .stream() - .filter { it.name?.toLowerCase() == lowerName } + .filter { it.name?.lowercase() == lowerName } .map { it.value } .firstOrNull() } diff --git a/common-api/src/main/kotlin/com/itangcent/common/utils/FileSizeUtils.kt b/common-api/src/main/kotlin/com/itangcent/common/utils/FileSizeUtils.kt index 1739c4065..454817f07 100644 --- a/common-api/src/main/kotlin/com/itangcent/common/utils/FileSizeUtils.kt +++ b/common-api/src/main/kotlin/com/itangcent/common/utils/FileSizeUtils.kt @@ -198,5 +198,4 @@ object FileSizeUtils { require(directory.exists()) { "$directory does not exist" } require(directory.isDirectory) { "$directory is not a directory" } } - } \ No newline at end of file diff --git a/common-api/src/main/kotlin/com/itangcent/http/ApacheHttpClient.kt b/common-api/src/main/kotlin/com/itangcent/http/ApacheHttpClient.kt index 785708770..f425af16e 100644 --- a/common-api/src/main/kotlin/com/itangcent/http/ApacheHttpClient.kt +++ b/common-api/src/main/kotlin/com/itangcent/http/ApacheHttpClient.kt @@ -14,6 +14,7 @@ import org.apache.http.client.entity.UrlEncodedFormEntity import org.apache.http.client.methods.RequestBuilder import org.apache.http.client.protocol.HttpClientContext import org.apache.http.config.SocketConfig +import org.apache.http.conn.ssl.NoopHostnameVerifier import org.apache.http.conn.ssl.SSLConnectionSocketFactory import org.apache.http.entity.ContentType import org.apache.http.entity.StringEntity @@ -24,7 +25,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager import org.apache.http.impl.cookie.BasicClientCookie import org.apache.http.impl.cookie.BasicClientCookie2 import org.apache.http.message.BasicNameValuePair -import org.apache.http.ssl.SSLContextBuilder +import org.apache.http.ssl.SSLContexts import org.apache.http.util.toByteArray import java.io.Closeable import java.io.File @@ -35,6 +36,16 @@ import javax.net.ssl.SSLContext @ScriptTypeName("httpClient") open class ApacheHttpClient : HttpClient { + companion object { + private fun String.createContentType(charset: String = "UTF-8"): ContentType { + val contentType = ContentType.parse(this) + return if (contentType.charset == null) { + contentType.withCharset(charset) + } else { + contentType + } + } + } private val apacheCookieStore: ApacheCookieStore @@ -47,25 +58,25 @@ open class ApacheHttpClient : HttpClient { this.apacheCookieStore = ApacheCookieStore(basicCookieStore) this.httpClientContext!!.cookieStore = basicCookieStore this.httpClient = HttpClients.custom() - .setConnectionManager(PoolingHttpClientConnectionManager().also { - it.maxTotal = 50 - it.defaultMaxPerRoute = 20 - }) - .setDefaultSocketConfig( - SocketConfig.custom() - .setSoTimeout(30 * 1000) - .build() - ) - .setDefaultRequestConfig( - RequestConfig.custom() - .setConnectTimeout(30 * 1000) - .setConnectionRequestTimeout(30 * 1000) - .setSocketTimeout(30 * 1000) - .setCookieSpec(CookieSpecs.STANDARD).build() - ) - .setSSLHostnameVerifier(NOOP_HOST_NAME_VERIFIER) - .setSSLSocketFactory(SSLSF) - .build() + .setConnectionManager(PoolingHttpClientConnectionManager().also { + it.maxTotal = 50 + it.defaultMaxPerRoute = 20 + }) + .setDefaultSocketConfig( + SocketConfig.custom() + .setSoTimeout(30 * 1000) + .build() + ) + .setDefaultRequestConfig( + RequestConfig.custom() + .setConnectTimeout(30 * 1000) + .setConnectionRequestTimeout(30 * 1000) + .setSocketTimeout(30 * 1000) + .setCookieSpec(CookieSpecs.STANDARD).build() + ) + .setSSLHostnameVerifier(NOOP_HOST_NAME_VERIFIER) + .setSSLSocketFactory(SSLSF) + .build() } constructor(httpClient: org.apache.http.client.HttpClient) { @@ -102,13 +113,13 @@ open class ApacheHttpClient : HttpClient { } val requestBuilder = RequestBuilder.create(request.method()) - .setUri(url) + .setUri(url) request.headers()?.forEach { requestBuilder.addHeader(it.name(), it.value()) } - if (request.method().toUpperCase() != "GET") { + if (request.method().uppercase() != "GET") { var requestEntity: HttpEntity? = null if (request.params().notNullOrEmpty()) { if (request.contentType()?.startsWith("application/x-www-form-urlencoded") == true) { @@ -141,15 +152,31 @@ open class ApacheHttpClient : HttpClient { requestEntity = entityBuilder.build() } } - if (request.body() != null) { + val body = request.body() + if (body != null) { if (requestEntity != null) { SpiUtils.loadService(ILogger::class) - ?.warn("The request with a body should not set content-type:${request.contentType()}") + ?.warn("The request with a body should not set content-type:${request.contentType()}") + } + requestEntity = when (body) { + is HttpEntity -> { + body + } + + is String -> { + StringEntity( + body, + request.contentType()?.createContentType() ?: ContentType.APPLICATION_JSON + ) + } + + else -> { + StringEntity( + body.toJson(), + ContentType.APPLICATION_JSON + ) + } } - requestEntity = StringEntity( - request.body().toJson(), - ContentType.APPLICATION_JSON - ) } if (requestEntity != null) { requestBuilder.entity = requestEntity @@ -218,7 +245,7 @@ class ApacheCookieStore : CookieStore { */ override fun addCookies(cookies: Array?) { cookies?.map { cookie -> cookie.asApacheCookie() } - ?.forEach { cookieStore.addCookie(it) } + ?.forEach { cookieStore.addCookie(it) } } /** @@ -247,8 +274,8 @@ class ApacheCookieStore : CookieStore { */ @ScriptTypeName("response") class ApacheHttpResponse( - private val request: HttpRequest, - private val response: org.apache.http.HttpResponse + private val request: HttpRequest, + private val response: org.apache.http.HttpResponse ) : AbstractHttpResponse() { /** @@ -393,11 +420,11 @@ fun Cookie.asApacheCookie(): org.apache.http.cookie.Cookie { return this.getWrapper() } val cookie = - if (this.getPorts() == null || this.getCommentURL() == null) { - BasicClientCookie(this.getName(), this.getValue()) - } else { - BasicClientCookie2(this.getName(), this.getValue()) - } + if (this.getPorts() == null || this.getCommentURL() == null) { + BasicClientCookie(this.getName(), this.getValue()) + } else { + BasicClientCookie2(this.getName(), this.getValue()) + } cookie.comment = this.getComment() cookie.domain = this.getDomain() cookie.path = this.getPath() @@ -412,14 +439,13 @@ fun Cookie.asApacheCookie(): org.apache.http.cookie.Cookie { return cookie } -var SSLCONTEXT: SSLContext = SSLContextBuilder().loadTrustMaterial(null -) { _, _ -> true }.build() +var SSLCONTEXT: SSLContext = SSLContexts.createSystemDefault() /** * Never authenticate the host */ -val NOOP_HOST_NAME_VERIFIER: HostnameVerifier = HostnameVerifier { _, _ -> true } +val NOOP_HOST_NAME_VERIFIER: HostnameVerifier = NoopHostnameVerifier.INSTANCE -var SSLSF: SSLConnectionSocketFactory = SSLConnectionSocketFactory( - SSLCONTEXT, arrayOf("TLSv1", "TLSv1.1", "TLSv1.2"), null, - NOOP_HOST_NAME_VERIFIER) \ No newline at end of file +val SSLSF: SSLConnectionSocketFactory = SSLConnectionSocketFactory( + SSLCONTEXT, NOOP_HOST_NAME_VERIFIER +) \ No newline at end of file diff --git a/common-api/src/main/kotlin/com/itangcent/http/HttpRequest.kt b/common-api/src/main/kotlin/com/itangcent/http/HttpRequest.kt index 48ff3cad4..d3bc85e74 100644 --- a/common-api/src/main/kotlin/com/itangcent/http/HttpRequest.kt +++ b/common-api/src/main/kotlin/com/itangcent/http/HttpRequest.kt @@ -947,7 +947,7 @@ interface HttpResponse : Closeable { * @param headerName the header name to find * @return {@code true} if at least one header with the name be found, {@code false} otherwise */ - fun containsHeader(headerName: String): Boolean? + fun containsHeader(headerName: String): Boolean /** * Gets all of the headers with the given name. The returned array @@ -1026,7 +1026,7 @@ abstract class AbstractHttpResponse : HttpResponse { * @param headerName the header name to find * @return {@code true} if at least one header with the name be found, {@code false} otherwise */ - override fun containsHeader(headerName: String): Boolean? { + override fun containsHeader(headerName: String): Boolean { return headers()?.any { it.name().equalIgnoreCase(headerName) } ?: false } diff --git a/common-api/src/test/kotlin/com/itangcent/common/constant/HttpMethodTest.kt b/common-api/src/test/kotlin/com/itangcent/common/constant/HttpMethodTest.kt index 0d53f33c5..5ad2dc040 100644 --- a/common-api/src/test/kotlin/com/itangcent/common/constant/HttpMethodTest.kt +++ b/common-api/src/test/kotlin/com/itangcent/common/constant/HttpMethodTest.kt @@ -1,7 +1,7 @@ package com.itangcent.common.constant -import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test +import kotlin.test.assertEquals /** * Test case for [HttpMethod] @@ -9,24 +9,40 @@ import org.junit.jupiter.api.Test internal class HttpMethodTest { @Test - fun preferMethod() { - - Assertions.assertEquals("ALL", HttpMethod.preferMethod("")) - - Assertions.assertEquals("GET", HttpMethod.preferMethod("GET")) - Assertions.assertEquals("POST", HttpMethod.preferMethod("POST")) - - Assertions.assertEquals("GET", HttpMethod.preferMethod("XXX.GET")) - Assertions.assertEquals("POST", HttpMethod.preferMethod("XXX.POST")) - Assertions.assertEquals("GET", HttpMethod.preferMethod("POST.GET")) - Assertions.assertEquals("POST", HttpMethod.preferMethod("GET.POST")) - - Assertions.assertEquals("GET", HttpMethod.preferMethod("POST_GET")) - Assertions.assertEquals("GET", HttpMethod.preferMethod("GET_POST")) - - Assertions.assertEquals("GET", HttpMethod.preferMethod("[GET]")) - Assertions.assertEquals("POST", HttpMethod.preferMethod("[POST]")) - Assertions.assertEquals("GET", HttpMethod.preferMethod("[GET][POST]")) - Assertions.assertEquals("GET", HttpMethod.preferMethod("[POST][GET]")) + fun `test preferMethod function`() { + + assertEquals("ALL", HttpMethod.preferMethod("")) + assertEquals("ALL", HttpMethod.preferMethod("foo")) + + assertEquals("GET", HttpMethod.preferMethod("GET")) + assertEquals("POST", HttpMethod.preferMethod("POST")) + assertEquals("DELETE", HttpMethod.preferMethod("DELETE")) + assertEquals("PUT", HttpMethod.preferMethod("PUT")) + assertEquals("PATCH", HttpMethod.preferMethod("PATCH")) + assertEquals("OPTIONS", HttpMethod.preferMethod("OPTIONS")) + assertEquals("TRACE", HttpMethod.preferMethod("TRACE")) + assertEquals("HEAD", HttpMethod.preferMethod("HEAD")) + + assertEquals("GET", HttpMethod.preferMethod("get")) + assertEquals("POST", HttpMethod.preferMethod("post")) + assertEquals("DELETE", HttpMethod.preferMethod("delete")) + assertEquals("PUT", HttpMethod.preferMethod("put")) + assertEquals("PATCH", HttpMethod.preferMethod("patch")) + assertEquals("OPTIONS", HttpMethod.preferMethod("options")) + assertEquals("TRACE", HttpMethod.preferMethod("trace")) + assertEquals("HEAD", HttpMethod.preferMethod("head")) + + assertEquals("GET", HttpMethod.preferMethod("XXX.GET")) + assertEquals("POST", HttpMethod.preferMethod("XXX.POST")) + assertEquals("GET", HttpMethod.preferMethod("POST.GET")) + assertEquals("POST", HttpMethod.preferMethod("GET.POST")) + + assertEquals("GET", HttpMethod.preferMethod("POST_GET")) + assertEquals("GET", HttpMethod.preferMethod("GET_POST")) + + assertEquals("GET", HttpMethod.preferMethod("[GET]")) + assertEquals("POST", HttpMethod.preferMethod("[POST]")) + assertEquals("GET", HttpMethod.preferMethod("[GET][POST]")) + assertEquals("GET", HttpMethod.preferMethod("[POST][GET]")) } } \ No newline at end of file diff --git a/common-api/src/test/kotlin/com/itangcent/common/kit/KVUtilsTest.kt b/common-api/src/test/kotlin/com/itangcent/common/kit/KVUtilsTest.kt index 69d5d6afd..e82289f31 100644 --- a/common-api/src/test/kotlin/com/itangcent/common/kit/KVUtilsTest.kt +++ b/common-api/src/test/kotlin/com/itangcent/common/kit/KVUtilsTest.kt @@ -1,49 +1,50 @@ package com.itangcent.common.kit -import com.itangcent.common.utils.asArrayList -import com.itangcent.common.utils.asHashMap -import com.itangcent.common.utils.getAs +import com.itangcent.common.utils.* import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import java.util.* /** - * Test case for [KitUtils] + * Test case for [KVUtils] */ @ExtendWith class KVUtilsTest { private fun options(): List> = listOf( - mapOf("value" to 1, "desc" to "ONE"), - mapOf("value" to 2, "desc" to "TWO"), - mapOf("value" to 3, "desc" to "THREE"), - mapOf("value" to 4, "desc" to null), - mapOf("value" to null, "desc" to "FIVE") + mapOf("value" to 1, "desc" to "ONE"), + mapOf("value" to 2, "desc" to "TWO"), + mapOf("value" to 3, "desc" to "THREE"), + mapOf("value" to 4, "desc" to null), + mapOf("value" to null, "desc" to "FIVE") ) private fun constants(): List> = listOf( - mapOf("name" to "ONE", "desc" to "first"), - mapOf("name" to "TWO", "desc" to "second"), - mapOf("name" to "THREE", "desc" to "third"), - mapOf("name" to 4, "desc" to null), - mapOf("name" to null, "desc" to "FIVE") + mapOf("name" to "ONE", "desc" to "first"), + mapOf("name" to "TWO", "desc" to "second"), + mapOf("name" to "THREE", "desc" to "third"), + mapOf("name" to 4, "desc" to null), + mapOf("name" to null, "desc" to "FIVE") ) @Test fun testGetOptionDesc() { val options: List> = options() - assertEquals("1 :ONE\n" + - "2 :TWO\n" + - "3 :THREE\n4", KVUtils.getOptionDesc(options)) + assertEquals( + "1 :ONE\n" + + "2 :TWO\n" + + "3 :THREE\n4", KVUtils.getOptionDesc(options) + ) } @Test fun testGetConstantDesc() { val constants: List> = constants() - assertEquals("ONE :first\n" + - "TWO :second\n" + - "THREE :third\n4", KVUtils.getConstantDesc(constants)) + assertEquals( + "ONE :first\n" + + "TWO :second\n" + + "THREE :third\n4", KVUtils.getConstantDesc(constants) + ) } @Test @@ -57,28 +58,52 @@ class KVUtilsTest { fun testAddKeyComments() { val info: HashMap = hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3)) assertTrue(KVUtils.addKeyComment(info, "x", "The value of the x axis")) - assertEquals(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), - "@comment" to hashMapOf("x" to "The value of the x axis")), info) + assertEquals( + hashMapOf( + "x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), + "@comment" to hashMapOf("x" to "The value of the x axis") + ), info + ) assertTrue(KVUtils.addKeyComment(info, "x", "The value of the x axis")) assertTrue(KVUtils.addKeyComment(info, "y", "The value of the y axis")) assertTrue(KVUtils.addKeyComment(info, "next.x", "The value of the next.x axis")) - assertEquals(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3, - "@comment" to hashMapOf("x" to "The value of the next.x axis")), - "@comment" to hashMapOf("x" to "The value of the x axis\nThe value of the x axis", - "y" to "The value of the y axis")), info) + assertEquals( + hashMapOf( + "x" to 1, "y" to 2, "next" to hashMapOf( + "x" to 2, "y" to 3, + "@comment" to hashMapOf("x" to "The value of the next.x axis") + ), + "@comment" to hashMapOf( + "x" to "The value of the x axis\nThe value of the x axis", + "y" to "The value of the y axis" + ) + ), info + ) //list val list = listOf(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3))) assertTrue(KVUtils.addKeyComment(list, "x", "The value of the x axis")) - assertEquals(listOf(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), - "@comment" to hashMapOf("x" to "The value of the x axis"))), list) + assertEquals( + listOf( + hashMapOf( + "x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), + "@comment" to hashMapOf("x" to "The value of the x axis") + ) + ), list + ) assertFalse(KVUtils.addKeyComment(emptyList(), "x", "The value of the x axis")) //array val array = arrayOf(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3))) assertTrue(KVUtils.addKeyComment(array, "x", "The value of the x axis")) - assertArrayEquals(arrayOf(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), - "@comment" to hashMapOf("x" to "The value of the x axis"))), array) + assertArrayEquals( + arrayOf( + hashMapOf( + "x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), + "@comment" to hashMapOf("x" to "The value of the x axis") + ) + ), array + ) assertFalse(KVUtils.addKeyComment(emptyArray(), "x", "The value of the x axis")) //others @@ -91,71 +116,129 @@ class KVUtilsTest { fun testAddOptions() { val info: HashMap = hashMapOf("x" to 1, "y" to 2) KVUtils.addOptions(info, "x", options().map { it.asHashMap() }.asArrayList()) - assertEquals(hashMapOf("x" to 1, "y" to 2, "@comment" to hashMapOf("x@options" to - listOf(mapOf("value" to 1, "desc" to "ONE"), - mapOf("value" to 2, "desc" to "TWO"), - mapOf("value" to 3, "desc" to "THREE"), - mapOf("value" to 4, "desc" to null), - mapOf("value" to null, "desc" to "FIVE")))), info) + assertEquals( + hashMapOf( + "x" to 1, "y" to 2, "@comment" to hashMapOf( + "x@options" to + listOf( + mapOf("value" to 1, "desc" to "ONE"), + mapOf("value" to 2, "desc" to "TWO"), + mapOf("value" to 3, "desc" to "THREE"), + mapOf("value" to 4, "desc" to null), + mapOf("value" to null, "desc" to "FIVE") + ) + ) + ), info + ) KVUtils.addOptions(info, "x", arrayListOf(hashMapOf("value" to 6, "desc" to "SIX"))) - assertEquals(hashMapOf("x" to 1, "y" to 2, "@comment" to hashMapOf("x@options" to - listOf(mapOf("value" to 1, "desc" to "ONE"), - mapOf("value" to 2, "desc" to "TWO"), - mapOf("value" to 3, "desc" to "THREE"), - mapOf("value" to 4, "desc" to null), - mapOf("value" to null, "desc" to "FIVE"), - mapOf("value" to 6, "desc" to "SIX")))), info) + assertEquals( + hashMapOf( + "x" to 1, "y" to 2, "@comment" to hashMapOf( + "x@options" to + listOf( + mapOf("value" to 1, "desc" to "ONE"), + mapOf("value" to 2, "desc" to "TWO"), + mapOf("value" to 3, "desc" to "THREE"), + mapOf("value" to 4, "desc" to null), + mapOf("value" to null, "desc" to "FIVE"), + mapOf("value" to 6, "desc" to "SIX") + ) + ) + ), info + ) } @Test fun testAddKeyOptions() { val info: HashMap = hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3)) KVUtils.addKeyOptions(info, "x", options().map { it.asHashMap() }.asArrayList()) - assertEquals(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), - "@comment" to hashMapOf("x@options" to - listOf(mapOf("value" to 1, "desc" to "ONE"), + assertEquals( + hashMapOf( + "x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), + "@comment" to hashMapOf( + "x@options" to + listOf( + mapOf("value" to 1, "desc" to "ONE"), mapOf("value" to 2, "desc" to "TWO"), mapOf("value" to 3, "desc" to "THREE"), mapOf("value" to 4, "desc" to null), - mapOf("value" to null, "desc" to "FIVE")))), info) + mapOf("value" to null, "desc" to "FIVE") + ) + ) + ), info + ) KVUtils.addKeyOptions(info, "next.x", options().map { it.asHashMap() }.asArrayList()) - assertEquals(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3, "@comment" to hashMapOf("x@options" to - listOf(mapOf("value" to 1, "desc" to "ONE"), - mapOf("value" to 2, "desc" to "TWO"), - mapOf("value" to 3, "desc" to "THREE"), - mapOf("value" to 4, "desc" to null), - mapOf("value" to null, "desc" to "FIVE")))), - "@comment" to hashMapOf("x@options" to - listOf(mapOf("value" to 1, "desc" to "ONE"), + assertEquals( + hashMapOf( + "x" to 1, "y" to 2, "next" to hashMapOf( + "x" to 2, "y" to 3, "@comment" to hashMapOf( + "x@options" to + listOf( + mapOf("value" to 1, "desc" to "ONE"), + mapOf("value" to 2, "desc" to "TWO"), + mapOf("value" to 3, "desc" to "THREE"), + mapOf("value" to 4, "desc" to null), + mapOf("value" to null, "desc" to "FIVE") + ) + ) + ), + "@comment" to hashMapOf( + "x@options" to + listOf( + mapOf("value" to 1, "desc" to "ONE"), mapOf("value" to 2, "desc" to "TWO"), mapOf("value" to 3, "desc" to "THREE"), mapOf("value" to 4, "desc" to null), - mapOf("value" to null, "desc" to "FIVE")))), info) + mapOf("value" to null, "desc" to "FIVE") + ) + ) + ), info + ) //list val list = listOf(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3))) assertTrue(KVUtils.addKeyOptions(list, "x", options().map { it.asHashMap() }.asArrayList())) - assertEquals(listOf(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), - "@comment" to hashMapOf("x@options" to - listOf(mapOf("value" to 1, "desc" to "ONE"), - mapOf("value" to 2, "desc" to "TWO"), - mapOf("value" to 3, "desc" to "THREE"), - mapOf("value" to 4, "desc" to null), - mapOf("value" to null, "desc" to "FIVE"))))), list) + assertEquals( + listOf( + hashMapOf( + "x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), + "@comment" to hashMapOf( + "x@options" to + listOf( + mapOf("value" to 1, "desc" to "ONE"), + mapOf("value" to 2, "desc" to "TWO"), + mapOf("value" to 3, "desc" to "THREE"), + mapOf("value" to 4, "desc" to null), + mapOf("value" to null, "desc" to "FIVE") + ) + ) + ) + ), list + ) assertFalse(KVUtils.addKeyOptions(emptyList(), "x", options().map { it.asHashMap() }.asArrayList())) //array val array = arrayOf(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3))) assertTrue(KVUtils.addKeyOptions(array, "x", options().map { it.asHashMap() }.asArrayList())) - assertArrayEquals(arrayOf(hashMapOf("x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), - "@comment" to hashMapOf("x@options" to - listOf(mapOf("value" to 1, "desc" to "ONE"), - mapOf("value" to 2, "desc" to "TWO"), - mapOf("value" to 3, "desc" to "THREE"), - mapOf("value" to 4, "desc" to null), - mapOf("value" to null, "desc" to "FIVE"))))), array) + assertArrayEquals( + arrayOf( + hashMapOf( + "x" to 1, "y" to 2, "next" to hashMapOf("x" to 2, "y" to 3), + "@comment" to hashMapOf( + "x@options" to + listOf( + mapOf("value" to 1, "desc" to "ONE"), + mapOf("value" to 2, "desc" to "TWO"), + mapOf("value" to 3, "desc" to "THREE"), + mapOf("value" to 4, "desc" to null), + mapOf("value" to null, "desc" to "FIVE") + ) + ) + ) + ), array + ) assertFalse(KVUtils.addKeyOptions(emptyArray(), "x", options().map { it.asHashMap() }.asArrayList())) //others @@ -170,15 +253,150 @@ class KVUtilsTest { KVUtils.addComment(info, "y", "The value of the y axis") KVUtils.addKeyOptions(info, "x", options().map { it.asHashMap() }.asArrayList()) KVUtils.addKeyOptions(info, "z", options().map { it.asHashMap() }.asArrayList()) - assertEquals("The value of the x axis\n" + - "1 :ONE\n" + - "2 :TWO\n" + - "3 :THREE\n4", KVUtils.getUltimateComment(info.getAs("@comment"), "x")) + assertEquals( + "The value of the x axis\n" + + "1 :ONE\n" + + "2 :TWO\n" + + "3 :THREE\n4", KVUtils.getUltimateComment(info.getAs("@comment"), "x") + ) assertEquals("The value of the y axis", KVUtils.getUltimateComment(info.getAs("@comment"), "y")) - assertEquals("1 :ONE\n" + - "2 :TWO\n" + - "3 :THREE\n4", KVUtils.getUltimateComment(info.getAs("@comment"), "z")) + assertEquals( + "1 :ONE\n" + + "2 :TWO\n" + + "3 :THREE\n4", KVUtils.getUltimateComment(info.getAs("@comment"), "z") + ) assertEquals("", KVUtils.getUltimateComment(null, "x")) assertEquals("", KVUtils.getUltimateComment(info.getAs("@comment"), null)) } -} \ No newline at end of file + + private class MockExtensible : SimpleExtensible() { + var value: String? = null + } + + @Test + @Suppress("UNCHECKED_CAST") + fun testUseFieldAsAttr() { + val map = mutableMapOf( + "name" to "Bob", + "age" to 32, + "role" to "admin", + "@attrs" to mutableMapOf("name" to "", "age" to 0), + "list" to listOf( + mutableMapOf( + "name" to "Alice", + "age" to 16, + "@attrs" to mutableMapOf("name" to "", "age" to 0) + ), + mutableMapOf( + "name" to "Chris", + "age" to 18, + "@attrs" to mutableMapOf("name" to "", "age" to 0) + ) + ), + "empty-list" to emptyList(), + "array" to arrayOf( + mutableMapOf( + "name" to "Alice", + "age" to 16, + "@attrs" to mutableMapOf("name" to "", "age" to 0) + ), + mutableMapOf( + "name" to "Chris", + "age" to 18, + "@attrs" to mutableMapOf("name" to "", "age" to 0) + ) + ), + "empty-array" to emptyArray(), + "extensible" to MockExtensible().apply { + value = "David" + }, + "non-value-extensible" to SimpleExtensible(), + "null" to null, + 1 to "number", + "mutable-immutable" to mutableMapOf( + "name" to "Bob", "age" to 32, "attrs" to mapOf("name" to "", "age" to 0) + ), + "immutable-immutable" to mapOf( + "name" to "Bob", "age" to 32, "attrs" to mapOf("name" to "", "age" to 0) + ) + ) + + fun Map<*, Any?>.attrs(): Map = this.sub("@attrs") as Map + + KVUtils.useFieldAsAttr(map, "@attrs") + + assertEquals("Bob", map.attrs()["name"]) + assertEquals(32, map.attrs()["age"]) + assertEquals("admin", map.attrs()["role"]) + assertEquals("Alice", (map["list"] as List>)[0].attrs()["name"]) + assertEquals(16, (map["list"] as List>)[0].attrs()["age"]) + assertEquals("", (map["list"] as List>)[1].attrs()["name"]) + assertEquals(0, (map["list"] as List>)[1].attrs()["age"]) + assertEquals("Alice", (map["array"] as Array>)[0].attrs()["name"]) + assertEquals(16, (map["array"] as Array>)[0].attrs()["age"]) + assertEquals("", (map["array"] as Array>)[1].attrs()["name"]) + assertEquals(0, (map["array"] as Array>)[1].attrs()["age"]) + assertEquals("David", (map["extensible"] as MockExtensible).getExt("@attrs")) + assertEquals("Bob", (map["mutable-immutable"] as Map).attrs()["name"]) + assertEquals("Bob", (map["immutable-immutable"] as Map).attrs()["name"]) + } + + @Test + @Suppress("UNCHECKED_CAST") + fun testUseAttrAsValue() { + val map = mutableMapOf( + "name" to "", + "age" to 0, + "role" to "", + "@attrs" to mapOf("name" to "Bob", "age" to 32), + "list" to listOf( + mutableMapOf( + "name" to "", + "age" to 0, + "@attrs" to mapOf("name" to "Alice", "age" to 16) + ), + mutableMapOf( + "name" to "Chris", + "age" to 18, + "@attrs" to mapOf("name" to "Bob", "age" to 32) + ) + ), + "empty-list" to emptyList(), + "array" to arrayOf( + mutableMapOf( + "name" to "", + "age" to 0, + "@attrs" to mapOf("name" to "Alice", "age" to 16) + ), + mutableMapOf( + "name" to "Chris", + "age" to 18, + "@attrs" to mapOf("name" to "Bob", "age" to 32) + ) + ), + "empty-array" to emptyArray(), + "extensible" to MockExtensible().apply { + setExt("@attrs", "David") + }, + "non-value-extensible" to SimpleExtensible().apply { + setExt("@attrs", "David") + }, + "null" to null + ) + + KVUtils.useAttrAsValue(map, "@attrs") + + assertEquals("Bob", map["name"]) + assertEquals(32, map["age"]) + assertEquals("", map["role"]) + assertEquals("Alice", (map["list"] as List>)[0]["name"]) + assertEquals(16, (map["list"] as List>)[0]["age"]) + assertEquals("Chris", (map["list"] as List>)[1]["name"]) + assertEquals(18, (map["list"] as List>)[1]["age"]) + assertEquals("Alice", (map["array"] as Array>)[0]["name"]) + assertEquals(16, (map["array"] as Array>)[0]["age"]) + assertEquals("Chris", (map["array"] as Array>)[1]["name"]) + assertEquals(18, (map["array"] as Array>)[1]["age"]) + assertEquals("David", (map["extensible"] as MockExtensible).value) + } +} diff --git a/common-api/src/test/kotlin/com/itangcent/utils/AnyKitKtTest.kt b/common-api/src/test/kotlin/com/itangcent/utils/AnyKitKtTest.kt index 3ba513ee3..fff153f7b 100644 --- a/common-api/src/test/kotlin/com/itangcent/utils/AnyKitKtTest.kt +++ b/common-api/src/test/kotlin/com/itangcent/utils/AnyKitKtTest.kt @@ -3,7 +3,6 @@ package com.itangcent.utils import com.itangcent.common.kit.toJson import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test -import kotlin.test.assertEquals class AnyKitKtTest { @@ -62,5 +61,19 @@ class AnyKitKtTest { it.subMutable("x")!!["b"] = "c" }.toJson() ) + + assertNull( + ImmutableMap(hashMapOf("a" to mapOf("x" to "y"))).subMutable("a") + ) + assertNull( + ImmutableMap(hashMapOf("a" to mapOf("x" to "y"))).subMutable("b") + ) + } + + private class ImmutableMap(m: MutableMap?) : HashMap(m) { + + override fun put(key: K, value: V): V? { + throw UnsupportedOperationException() + } } } \ No newline at end of file diff --git a/common-api/src/test/kotlin/com/itangcent/utils/FuncKitKtTest.kt b/common-api/src/test/kotlin/com/itangcent/utils/FuncKitKtTest.kt new file mode 100644 index 000000000..434820f88 --- /dev/null +++ b/common-api/src/test/kotlin/com/itangcent/utils/FuncKitKtTest.kt @@ -0,0 +1,56 @@ +package com.itangcent.utils + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class FuncKitKtTest { + + @Test + fun `test and function`() { + val isEven = { num: Int -> num % 2 == 0 } + val isPositive = { num: Int -> num > 0 } + + val isEvenAndPositive = isEven.and(isPositive) + + assertEquals(true, isEvenAndPositive(4)) + assertEquals(false, isEvenAndPositive(3)) + assertEquals(false, isEvenAndPositive(-4)) + assertEquals(false, isEvenAndPositive(0)) + } + + @Test + fun `test then function with one parameter`() { + var result = "" + + val addHello = { str: String -> result = "$result $str Hello" } + val addWorld = { str: String -> result = "$result $str World" } + + val addHelloThenWorld = addHello.then(addWorld) + + result = "" + addHelloThenWorld("Hi") + assertEquals(" Hi Hello Hi World", result) + + result = "" + addHelloThenWorld("") + assertEquals(" Hello World", result) + } + + @Test + fun `test then function with three parameters`() { + var result = "" + + val addHello = { str: String, num: Int, flag: Boolean -> result = "$result $str Hello $num $flag" } + val addWorld = { str: String, num: Int, flag: Boolean -> result = "$result $str World $num $flag" } + + val addHelloThenWorld = addHello.then(addWorld) + + result = "" + addHelloThenWorld("Hi", 42, true) + assertEquals(" Hi Hello 42 true Hi World 42 true", result) + + result = "" + addHelloThenWorld("", 0, false) + assertEquals(" Hello 0 false World 0 false", result) + } +} \ No newline at end of file diff --git a/common-api/src/test/kotlin/org/apache/http/util/EntityKitsKtTest.kt b/common-api/src/test/kotlin/org/apache/http/util/EntityKitsKtTest.kt new file mode 100644 index 000000000..3644d46a6 --- /dev/null +++ b/common-api/src/test/kotlin/org/apache/http/util/EntityKitsKtTest.kt @@ -0,0 +1,87 @@ +package org.apache.http.util + +import org.apache.http.HttpEntity +import org.apache.http.entity.ContentType +import org.apache.http.entity.StringEntity +import org.junit.jupiter.api.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class EntityKitsKtTest { + + @Test + fun `test HttpEntity toByteArray`() { + val content = "Hello, world!" + val entity = StringEntity(content, ContentType.TEXT_PLAIN) + val bytes = entity.toByteArray() + assertContentEquals(content.toByteArray(), bytes) + } + + @Test + fun `test HttpEntity consume`() { + object : HttpEntity { + override fun getContent() = null + override fun getContentLength() = 0L + override fun getContentType() = null + override fun isChunked() = false + override fun isRepeatable() = false + override fun isStreaming() = false + override fun writeTo(output: java.io.OutputStream?) {} + override fun getContentEncoding() = null + override fun consumeContent() {} + }.consume() + object : HttpEntity { + override fun getContent() = "content".byteInputStream(Charsets.UTF_8) + override fun getContentLength() = 0L + override fun getContentType() = null + override fun isChunked() = false + override fun isRepeatable() = false + override fun isStreaming() = true + override fun writeTo(output: java.io.OutputStream?) {} + override fun getContentEncoding() = null + override fun consumeContent() {} + }.consume() + } + + @Test + fun `test HttpEntity getContentCharSet`() { + val contentType = ContentType.create("text/plain", "UTF-8") + val entity = StringEntity("Hello, world!", contentType) + val charset = entity.getContentCharSet() + assertEquals("UTF-8", charset) + } + + @Test + fun `test HttpEntity getContentMimeType`() { + val contentType = ContentType.create("text/plain", "UTF-8") + val entity = StringEntity("Hello, world!", contentType) + val mimeType = entity.getContentMimeType() + assertEquals("text/plain", mimeType) + } + + @Test + fun `test HttpEntity readString`() { + val content = "Hello, world!" + val entity = StringEntity(content, ContentType.TEXT_PLAIN) + assertEquals(content, entity.readString()) + + val wildcardEntity = StringEntity(content, ContentType.WILDCARD) + assertEquals(content, wildcardEntity.readString()) + assertEquals(content, wildcardEntity.readString(Charsets.ISO_8859_1)) + } + + @Test + fun `test HttpEntity readString with default charset`() { + val content = "Hello, world!" + val entity = StringEntity(content, ContentType.TEXT_PLAIN.withCharset("ISO-8859-1")) + assertEquals(content, entity.readString()) + } + + @Test + fun `test HttpEntity readString with specified charset`() { + val content = "Hello, world!" + val entity = StringEntity(content, ContentType.TEXT_PLAIN.withCharset("ISO-8859-1")) + val result = entity.readString("ISO-8859-1") + assertEquals(content, result) + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 959fa7343..1e7fc222e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,4 @@ plugin_version=2.5.9.212.0 kotlin.code.style=official kotlin_version=1.8.0 junit_version=5.9.2 -itangcent_intellij_version=1.5.13-SNAPSHOT \ No newline at end of file +itangcent_intellij_version=1.5.3 \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/cache/CacheSwitcher.kt b/idea-plugin/src/main/kotlin/com/itangcent/cache/CacheSwitcher.kt index 81303ec04..f1b22a6ba 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/cache/CacheSwitcher.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/cache/CacheSwitcher.kt @@ -1,5 +1,8 @@ package com.itangcent.cache +/** + * This interface defines a way to enable or disable caching behavior within an object or system. + */ interface CacheSwitcher { fun notUserCache() @@ -7,6 +10,9 @@ interface CacheSwitcher { fun userCache() } +/** + * allow a block of code to be executed with caching disabled. + */ fun CacheSwitcher.withoutCache(call: () -> Unit) { this.notUserCache() try { diff --git a/idea-plugin/src/main/kotlin/com/itangcent/cache/DefaultHttpContextCacheHelper.kt b/idea-plugin/src/main/kotlin/com/itangcent/cache/DefaultHttpContextCacheHelper.kt index e2f26665b..8a7456b0b 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/cache/DefaultHttpContextCacheHelper.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/cache/DefaultHttpContextCacheHelper.kt @@ -82,7 +82,7 @@ class DefaultHttpContextCacheHelper : HttpContextCacheHelper { override fun addCookies(cookies: List) { val httpContextCache = httpContextCacheBinder.read() - val cachedCookies = httpContextCache.cookies?.toMutableList()?.let { HashSet(it) } ?: HashSet() + val cachedCookies = httpContextCache.cookies?.toMutableSet() ?: HashSet() cookies.forEach { cachedCookies.add(it.json()) } httpContextCache.cookies = ArrayList(cachedCookies) httpContextCacheBinder.save(httpContextCache) diff --git a/idea-plugin/src/main/kotlin/com/itangcent/condition/AnnotatedCondition.kt b/idea-plugin/src/main/kotlin/com/itangcent/condition/AnnotatedCondition.kt index 17f652c50..b5ce20457 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/condition/AnnotatedCondition.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/condition/AnnotatedCondition.kt @@ -32,11 +32,15 @@ abstract class AnnotatedCondition : Condition, ConditionSupporte } } + /** + * Collect all annotations of the specified type for a given bean class and its superclasses, + * using reflection to access the annotations. + */ @Suppress("UNCHECKED_CAST") private fun collectAnnotations(beanClass: KClass<*>): Array { val annotations = ArrayList() beanClass.superClasses { kClass -> - beanClass.annotations.filter { supportedConditionClass.isInstance(it) } + kClass.annotations.filter { supportedConditionClass.isInstance(it) } .forEach { annotations.add(it as T) } @@ -45,17 +49,32 @@ abstract class AnnotatedCondition : Condition, ConditionSupporte return annotations.toArray(array) } + /** + * Returns the annotation class for the given type parameter T by inspecting the class hierarchy of the implementing class. + */ + protected open fun annClass(): KClass { + return findAnnClass(this::class) + ?: throw IllegalArgumentException("failed get condition class of ${this::class}") + } + @Suppress("UNCHECKED_CAST") - open fun annClass(): KClass { - for (supertype in this::class.supertypes) { + private fun findAnnClass(cls: KClass<*>): KClass? { + for (supertype in cls.supertypes) { val classifier = supertype.classifier - if (classifier != AnnotatedCondition::class) { - continue + if (classifier == AnnotatedCondition::class) { + return supertype.arguments[0].type!!.classifier as KClass + } + if (classifier is KClass<*>) { + findAnnClass(classifier)?.let { + return it + } } - return supertype.arguments[0].type!!.classifier as KClass } - throw IllegalArgumentException("failed get condition class of ${this::class}") + return null } + /** + * Abstract function that must be implemented by subclasses to check whether a given ActionContext matches a specific annotation. + */ protected abstract fun matches(actionContext: ActionContext, annotation: T): Boolean } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/cache/CachedRequestClassExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/cache/CachedRequestClassExporter.kt index 7c8b14842..25c53c5c8 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/cache/CachedRequestClassExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/cache/CachedRequestClassExporter.kt @@ -58,7 +58,7 @@ class CachedRequestClassExporter : ClassExporter, CacheSwitcher { val psiFile = actionContext.callInReadUI { cls.containingFile }!! val text = actionContext.callInReadUI { psiFile.text } ?: "" - val path = ActionUtils.findCurrentPath(psiFile)!! + val path = ActionUtils.findCurrentPath(psiFile) .replace(File.separator, "_") actionContext.runAsync { try { diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/cache/FileApiCache.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/cache/FileApiCache.kt index bb90289a0..d6bca6016 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/cache/FileApiCache.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/cache/FileApiCache.kt @@ -2,7 +2,7 @@ package com.itangcent.idea.plugin.api.cache import com.itangcent.common.constant.HttpMethod import com.itangcent.common.model.* -import com.itangcent.idea.utils.setExts +import com.itangcent.utils.setExts class FileApiCache { /** diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/UrlSelector.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/UrlSelector.kt index b714ca33c..580687854 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/UrlSelector.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/UrlSelector.kt @@ -25,7 +25,7 @@ class UrlSelector { } val pathMultiResolve = ruleComputer!!.computer(ClassExportRuleKeys.PATH_MULTI, request.resource()!!)?.let { - ResolveMultiPath.valueOf(it.toUpperCase()) + ResolveMultiPath.valueOf(it.uppercase()) } ?: ResolveMultiPath.FIRST when (pathMultiResolve) { diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/CompositeClassExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/CompositeClassExporter.kt index efb874d2a..9efc0e7fc 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/CompositeClassExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/CompositeClassExporter.kt @@ -2,10 +2,7 @@ package com.itangcent.idea.plugin.api.export.core import com.google.inject.Inject import com.google.inject.Singleton -import com.itangcent.common.utils.stream import com.itangcent.intellij.context.ActionContext -import com.itangcent.intellij.extend.callWithBoundary -import com.itangcent.intellij.extend.withBoundary import com.itangcent.spi.SpiCompositeLoader import kotlin.reflect.KClass diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/GenericRequestClassExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/GenericRequestClassExporter.kt index 6b9d33b2f..0ef088510 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/GenericRequestClassExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/GenericRequestClassExporter.kt @@ -253,7 +253,7 @@ open class GenericRequestClassExporter : RequestClassExporter() { ): String { var ultimateComment = (paramDesc ?: "") parameterExportContext.element().getType()?.let { duckType -> - commentResolver!!.resolveCommentForType(duckType, parameterExportContext.psi())?.let { + commentResolver.resolveCommentForType(duckType, parameterExportContext.psi())?.let { ultimateComment = "$ultimateComment $it" } } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/SimpleGenericMethodDocClassExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/SimpleGenericMethodDocClassExporter.kt index b95c27635..445d92680 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/SimpleGenericMethodDocClassExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/SimpleGenericMethodDocClassExporter.kt @@ -136,7 +136,7 @@ open class SimpleGenericMethodDocClassExporter : ClassExporter { docHandle: DocHandle, ) { - actionContext!!.checkStatus() + actionContext.checkStatus() val methodDoc = MethodDoc() diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/JAXRSRequestClassExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/JAXRSRequestClassExporter.kt index c22807903..fc9e3a74d 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/JAXRSRequestClassExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/JAXRSRequestClassExporter.kt @@ -99,7 +99,7 @@ open class JAXRSRequestClassExporter : RequestClassExporter() { var ultimateComment = (paramDesc ?: "") exportContext.type()?.let { duckType -> - commentResolver!!.resolveCommentForType(duckType, exportContext.psi())?.let { + commentResolver.resolveCommentForType(duckType, exportContext.psi())?.let { ultimateComment = "$ultimateComment $it" } } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/SimpleJAXRSRequestClassExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/SimpleJAXRSRequestClassExporter.kt index 603a073c0..6bdd9d7b0 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/SimpleJAXRSRequestClassExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/SimpleJAXRSRequestClassExporter.kt @@ -96,7 +96,7 @@ open class SimpleJAXRSRequestClassExporter : ClassExporter { private fun exportMethodApi(psiClass: PsiClass, method: PsiMethod, docHandle: DocHandle) { - actionContext!!.checkStatus() + actionContext.checkStatus() if (!JAXRSBaseAnnotationParser.isApi(method)) { return } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/DefaultPostmanApiHelper.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/DefaultPostmanApiHelper.kt index 1d8c4b3aa..c0614cc5c 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/DefaultPostmanApiHelper.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/DefaultPostmanApiHelper.kt @@ -187,7 +187,7 @@ open class DefaultPostmanApiHelper : PostmanApiHelper { .onEach { response -> val responseCode = response["code"] if (responseCode != null) { - (response as MutableMap)["code"] = when (responseCode) { + response["code"] = when (responseCode) { is Map<*, *> -> responseCode["value"].asInt() ?: 200 is LazilyParsedNumber -> responseCode.toInt() is String -> responseCode.toInt() diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/rule/RequestRuleWrap.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/rule/RequestRuleWrap.kt index 1aa769a95..1aa268b3d 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/rule/RequestRuleWrap.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/rule/RequestRuleWrap.kt @@ -8,7 +8,7 @@ import com.itangcent.common.utils.* import com.itangcent.idea.plugin.api.export.* import com.itangcent.idea.plugin.api.export.core.* import com.itangcent.idea.psi.resource -import com.itangcent.idea.utils.setExts +import com.itangcent.utils.setExts import com.itangcent.intellij.context.ActionContext import com.itangcent.intellij.jvm.DuckTypeHelper import com.itangcent.intellij.jvm.JsonOption diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiDashboardDialog.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiDashboardDialog.kt index b5df62e31..0ff0513e9 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiDashboardDialog.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiDashboardDialog.kt @@ -596,7 +596,7 @@ class ApiDashboardDialog : AbstractApiDashboardDialog() { actionContext.runAsync { val collection = postmanNodeData.collection val collectionId = collection["id"].toString() - if (postmanCachedApiHelper!!.deleteCollectionInfo(collectionId) != null) { + if (postmanCachedApiHelper.deleteCollectionInfo(collectionId) != null) { logger.info("delete success") postmanNodeData.asTreeNode().removeFromParent() postmanApiTree!!.model.reload(postmanApiTree!!.model.root as TreeNode) diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ScriptExecutorDialog.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ScriptExecutorDialog.kt index d972104b0..1f52b229d 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ScriptExecutorDialog.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ScriptExecutorDialog.kt @@ -690,7 +690,7 @@ class ScriptExecutorDialog : ContextDialog() { if (element == null) { return "select context" } - name = actionContext!!.callInReadUI { + name = actionContext.callInReadUI { when (element) { is PsiElement -> PsiClassUtils.fullNameOfMember(element as PsiElement) else -> element.toString() diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/YapiDashboardDialog.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/YapiDashboardDialog.kt index 036d2360d..9316c9ffc 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/YapiDashboardDialog.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/YapiDashboardDialog.kt @@ -518,7 +518,7 @@ class YapiDashboardDialog : AbstractApiDashboardDialog() { private fun goToYapi() { val yapiNodeData = selectedYapiNode() ?: return - LOG!!.trace("go to:[$yapiNodeData]") + LOG.trace("go to:[$yapiNodeData]") yapiNodeData.getUrl(this)?.let { actionContext.instance(IdeaSupport::class).openUrl(it) } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/RuleToolUtils.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/RuleToolUtils.kt index 18e5605d2..39d071e91 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/RuleToolUtils.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/RuleToolUtils.kt @@ -734,7 +734,7 @@ object RuleToolUtils { if (str.isNullOrBlank()) { return str } - val strLow = str.toLowerCase() + val strLow = str.lowercase() val strLen = strLow.length val newCodePoints = IntArray(strLen) { 0 } var outOffset = 0 @@ -805,9 +805,9 @@ object RuleToolUtils { val buffer = StringBuffer() while (matcher.find()) { if (matcher.start() > 0) { - matcher.appendReplacement(buffer, "_" + matcher.group(0).toLowerCase()) + matcher.appendReplacement(buffer, "_" + matcher.group(0).lowercase()) } else { - matcher.appendReplacement(buffer, matcher.group(0).toLowerCase()) + matcher.appendReplacement(buffer, matcher.group(0).lowercase()) } } matcher.appendTail(buffer) @@ -837,7 +837,7 @@ object RuleToolUtils { } return TemplateUtils.render( str, - TemplateKit.resolvePlaceHolder(placeHolder) ?: arrayOf('$'), + TemplateKit.resolvePlaceHolder(placeHolder) ?: charArrayOf('$'), context as Map ) } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/StandardJdkRuleParser.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/StandardJdkRuleParser.kt index cde2d122e..a61006e3f 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/StandardJdkRuleParser.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/StandardJdkRuleParser.kt @@ -166,7 +166,7 @@ abstract class StandardJdkRuleParser : ScriptRuleParser() { .union(TemplateEvaluator.from { configReader.first(it) }) } return TemplateUtils.render(str) - .placeholder(TemplateKit.resolvePlaceHolder(placeHolder) ?: arrayOf('$')) + .placeholder(TemplateKit.resolvePlaceHolder(placeHolder) ?: charArrayOf('$')) .templateEvaluator(templateEvaluator) .render() } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/KtHelper.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/KtHelper.kt deleted file mode 100644 index 776067d7b..000000000 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/KtHelper.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.itangcent.idea.plugin.utils - -import java.util.function.Consumer -import java.util.function.Function - -object KtHelper { - - @Deprecated(message = "use [asConsumer]", replaceWith = ReplaceWith( - "asConsumer(callBack)", - "com.itangcent.idea.plugin.utils.KtHelper.asConsumer")) - @Suppress("UNCHECKED_CAST", "UNUSED") - fun ktFunction(callBack: Any): (T) -> kotlin.Unit { - return asConsumer(callBack) - } - - @Suppress("UNCHECKED_CAST", "UNUSED") - fun asConsumer(callBack: Any): (T) -> kotlin.Unit { - try { - (callBack as (T) -> kotlin.Unit) - return callBack - } catch (e: Exception) { - } - when (callBack) { - is Function1<*, *> -> { - return { - (callBack as Function1).invoke(it) - } - } - is Function<*, *> -> { - return { - (callBack as Function).apply(it as Any?) - } - } - is Consumer<*> -> { - return { - (callBack as Consumer).accept(it as Any?) - } - } - is groovy.lang.Closure<*> -> { - return { - (callBack as groovy.lang.Closure).call(it as Any?) - } - } - else -> throw ClassCastException("$callBack cannot be cast to kotlin.jvm.functions.Function1") - } - } - - @Suppress("UNCHECKED_CAST", "UNUSED") - fun asFunction(callBack: Any): (T) -> (R?) { - try { - return (callBack as (T) -> R) - } catch (e: Exception) { - } - when (callBack) { - is Function1<*, *> -> { - return { - (callBack as Function1).invoke(it) as R? - } - } - is Function<*, *> -> { - return { - (callBack as Function).apply(it as Any?) as R? - } - } - is Consumer<*> -> { - return { - (callBack as Consumer).accept(it as Any?) - null - } - } - is groovy.lang.Closure<*> -> { - return { - (callBack as groovy.lang.Closure).call(it as Any?) as R? - } - } - else -> throw ClassCastException("$callBack cannot be cast to kotlin.jvm.functions.Function1") - } - } - -} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/RegexUtils.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/RegexUtils.kt index 53e53f01b..d4ec32b37 100755 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/RegexUtils.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/RegexUtils.kt @@ -99,7 +99,7 @@ object RegexUtils { return extract(pattern!!, content, template) } - private fun extract(pattern: Pattern, content: String, template: String): String? { + private fun extract(pattern: Pattern, content: String, template: String): String { return if (template.contains("$")) { extract(pattern, content) { template } } else { @@ -125,7 +125,7 @@ object RegexUtils { * Remove the first subString of the input String that matches the * pattern with the given replacement string. */ - fun delFirst(pattern: String, content: String): String? { + fun delFirst(pattern: String, content: String): String { return delFirst(getPattern(pattern)!!, content) } @@ -298,38 +298,8 @@ object RegexUtils { return value } - private class RegexWithFlag(private val regex: String?, private val flag: Int) { + data class RegexWithFlag(private val regex: String?, private val flag: Int) - override fun hashCode(): Int { - val prime = 31 - var result = 1 - result = prime * result + flag - result = prime * result + (regex?.hashCode() ?: 0) - return result - } - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - if (other == null) { - return false - } - if (javaClass != other.javaClass) { - return false - } - val regexWithFlag = other as RegexWithFlag? - if (flag != regexWithFlag!!.flag) { - return false - } - return if (regex == null) { - regexWithFlag.regex == null - } else - regex == regexWithFlag.regex - } - - } - private const val GROUP_VAR_PATTERN = "\\$(\\d+)" private val GROUP_VAR = Pattern.compile(GROUP_VAR_PATTERN)!! diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/ExtensibleKit.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/ExtensibleKit.kt deleted file mode 100644 index d9416ff2d..000000000 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/ExtensibleKit.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.itangcent.idea.utils - -import com.itangcent.common.utils.Extensible - -fun Extensible.setExts(exts: Map) { - exts.forEach { (t, u) -> - this.setExt(t, u) - } -} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/IOUtils.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/IOUtils.kt deleted file mode 100644 index 1ed7a82b6..000000000 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/IOUtils.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.itangcent.idea.utils - -import java.io.InputStream - -/** - * - there's some function copy from [com.sun.org.apache.xerces.internal.xinclude.XIncludeTextReader] - */ -object IOUtils { - - fun getEncodingName(stream: InputStream): String? { - val b4 = ByteArray(4) - var encoding: String? = null - - // this has the potential to throw an exception - // it will be fixed when we ensure the stream is rewindable (see note above) - stream.mark(4) - val count = stream.read(b4, 0, 4) - stream.reset() - if (count == 4) { - encoding = getEncodingName(b4) - } - - return encoding - } - - /** - * REVISIT: This code is taken from com.sun.org.apache.xerces.internal.impl.XMLEntityManager. - * Is there any way we can share the code, without having it implemented twice? - * I think we should make it public and static in XMLEntityManager. --PJM - * - * Returns the IANA encoding name that is auto-detected from - * the bytes specified, with the endian-ness of that encoding where appropriate. - * - * @param b4 The first four bytes of the input. - * @return the encoding name, or null if no encoding could be detected - */ - fun getEncodingName(b4: ByteArray): String? { - - // UTF-16, with BOM - val b0: Int = (b4[0].toInt() and 0xFF) - val b1: Int = (b4[1].toInt() and 0xFF) - if (b0 == 0xFE && b1 == 0xFF) { - // UTF-16, big-endian - return "UTF-16BE" - } - if (b0 == 0xFF && b1 == 0xFE) { - // UTF-16, little-endian - return "UTF-16LE" - } - - // UTF-8 with a BOM - val b2: Int = (b4[2].toInt() and 0xFF) - if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) { - return "UTF-8" - } - - // other encodings - val b3: Int = (b4[3].toInt() and 0xFF) - if (b0 == 0x00 && b1 == 0x00 && b2 == 0x00 && b3 == 0x3C) { - // UCS-4, big endian (1234) - return "ISO-10646-UCS-4" - } - if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) { - // UCS-4, little endian (4321) - return "ISO-10646-UCS-4" - } - if (b0 == 0x00 && b1 == 0x00 && b2 == 0x3C && b3 == 0x00) { - // UCS-4, unusual octet order (2143) - return "ISO-10646-UCS-4" - } - if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x00) { - // UCS-4, unusual octect order (3412) - return "ISO-10646-UCS-4" - } - if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) { - // UTF-16, big-endian, no BOM - // (or could turn out to be UCS-2... - return "UTF-16BE" - } - if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) { - // UTF-16, little-endian, no BOM - // (or could turn out to be UCS-2... - return "UTF-16LE" - } - if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) { - // EBCDIC - // a la xerces1, return CP037 instead of EBCDIC here - return "CP037" - } - - // this signals us to use the value from the encoding attribute - return null - } -} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/LazilyParsedNumberTypeAdapter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/LazilyParsedNumberTypeAdapter.kt deleted file mode 100644 index d6cefdd34..000000000 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/LazilyParsedNumberTypeAdapter.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.itangcent.idea.utils - -import com.google.gson.TypeAdapter -import com.google.gson.internal.LazilyParsedNumber -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonToken -import com.google.gson.stream.JsonWriter -import java.io.IOException - -/** - * support LazilyParsedNumber - * write as raw number instead of {"value":number} - */ -class LazilyParsedNumberTypeAdapter : TypeAdapter() { - - @Throws(IOException::class) - override fun read(reader: JsonReader): LazilyParsedNumber? { - return when (reader.peek()) { - JsonToken.STRING -> LazilyParsedNumber(reader.nextString()) - JsonToken.NUMBER -> { - LazilyParsedNumber(reader.nextString()) - } - JsonToken.NULL -> { - reader.nextNull() - null - } - else -> throw IllegalStateException() - } - } - - @Throws(IOException::class) - override fun write(out: JsonWriter, value: LazilyParsedNumber?) { - if (value == null) { - out.nullValue() - return - } - out.jsonValue(value.toString()) - } -} diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/NumberFixedObjectTypeAdapter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/NumberFixedObjectTypeAdapter.kt deleted file mode 100644 index b0953c301..000000000 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/NumberFixedObjectTypeAdapter.kt +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * - * Licensed 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 com.itangcent.idea.utils - -import com.google.gson.Gson -import com.google.gson.TypeAdapter -import com.google.gson.TypeAdapterFactory -import com.google.gson.reflect.TypeToken -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonToken -import com.google.gson.stream.JsonWriter -import java.io.IOException -import java.util.* -import kotlin.collections.HashMap - -/** - * By default ObjectTypeAdapter,all number are deserialized to double. - * It's not necessary to keep long to long,float to float. - * But shouldn't deserialize short/int/long to double - * So fix it. - */ -class NumberFixedObjectTypeAdapter : TypeAdapter { - - private var gson: Gson? = null - - constructor(gson: Gson) : super() { - this.gson = gson - } - - constructor() : super() - - fun setGson(gson: Gson) { - this.gson = gson - } - - @Throws(IOException::class) - override fun read(reader: JsonReader): Any? { - val token = reader.peek() - when (token) { - JsonToken.BEGIN_ARRAY -> { - val list = ArrayList() - reader.beginArray() - while (reader.hasNext()) { - list.add(read(reader)) - } - reader.endArray() - return list - } - - JsonToken.BEGIN_OBJECT -> { - val map = HashMap() - reader.beginObject() - while (reader.hasNext()) { - map[reader.nextName()] = read(reader) - } - reader.endObject() - return map - } - - JsonToken.STRING -> return reader.nextString() - - JsonToken.NUMBER -> { - //read as String - val numberStr = reader.nextString() - - // deserialized as double if ./e/E be found - if (numberStr.contains(".") || numberStr.contains("e") - || numberStr.contains("E") - ) { - return numberStr.toDouble() - } - - return try { - numberStr.toInt() - } catch (e: Exception) { - numberStr.toLong() - } - } - - JsonToken.BOOLEAN -> return reader.nextBoolean() - - JsonToken.NULL -> { - reader.nextNull() - return null - } - - else -> throw IllegalStateException() - } - } - - @Throws(IOException::class) - override fun write(out: JsonWriter, value: Any?) { - if (value == null) { - out.nullValue() - return - } - - val typeAdapter = gson!!.getAdapter(value.javaClass) as TypeAdapter - if (typeAdapter is NumberFixedObjectTypeAdapter) { - out.beginObject() - out.endObject() - return - } - - typeAdapter.write(out, value) - } - - @Suppress("UNCHECKED_CAST") - companion object { - val FACTORY: TypeAdapterFactory = object : TypeAdapterFactory { - override fun create(gson: Gson, type: TypeToken): TypeAdapter? { - return if (type.rawType == Any::class.java) { - NumberFixedObjectTypeAdapter(gson) as TypeAdapter - } else null - } - } - } -} diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/SwingUtils.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/SwingUtils.kt index 1686fc515..5e0024d29 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/SwingUtils.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/SwingUtils.kt @@ -13,49 +13,54 @@ import javax.swing.* import javax.swing.table.TableColumn import javax.swing.tree.* - object SwingUtils { - - fun focus(apiCallDialog: Dialog) { + // Sets the focus on the specified Dialog component in the Swing UI thread. + fun focus(dialog: Dialog) { ActionContext.getContext()!!.runInSwingUI { - apiCallDialog.requestFocus() + dialog.requestFocus() } } + // Expands or collapses all nodes in a JTree component. fun expandOrCollapseNode(tree: JTree, expanded: Boolean) { val node = tree.model.root as DefaultMutableTreeNode expandOrCollapseNode(tree, node, expanded) } + // Expands or collapses the specified DefaultMutableTreeNode in a JTree component. fun expandOrCollapseNode(tree: JTree, node: DefaultMutableTreeNode, expanded: Boolean) { - + // Recursively expand or collapse child nodes of the specified node. for (treeNode in node.children()) { expandOrCollapseNode(tree, treeNode as DefaultMutableTreeNode, expanded) } if (!expanded && node.isRoot) { + // If collapsing the root node, do nothing. return } val path = TreePath(node.path) if (expanded) { + // Expand the node if expanded is true. tree.expandPath(path) } else { + // Collapse the node if expanded is false. tree.collapsePath(path) } } + // Adds an underline border to the specified JComponent. fun underLine(component: JComponent) { -// component.isOpaque = true component.border = BorderFactory.createMatteBorder(0, 0, 1, 0, component.foreground) component.background = component.parent.background } + // Removes the border from the specified JComponent. fun immersed(component: JComponent) { -// component.isOpaque = true component.border = BorderFactory.createMatteBorder(0, 0, 0, 0, component.foreground) component.background = component.parent.background } + // Centers a Component on the screen. fun centerWindow(component: Component) { val toolkit = Toolkit.getDefaultToolkit() val scmSize = toolkit.screenSize @@ -68,11 +73,12 @@ object SwingUtils { ) } + // Returns the active window of the current project in the IntelliJ IDEA IDE. fun preferableWindow(): Window? { val context = ActionContext.getContext() ?: return null try { context.instance(ActiveWindowProvider::class).activeWindow().cast(Window::class)?.let { return it } - } catch (e: com.google.inject.ConfigurationException) { + } catch (_: com.google.inject.ConfigurationException) { } WindowManager.getInstance().suggestParentWindow(context.instance(Project::class)) ?.let { return it } @@ -80,6 +86,7 @@ object SwingUtils { } } +// Returns true if the mouse event is a double-click event. fun MouseEvent?.isDoubleClick(): Boolean { if (this == null || this.isConsumed) return false @@ -92,18 +99,23 @@ fun MouseEvent?.isDoubleClick(): Boolean { return false } +// Returns the TableColumn object at the specified column index. fun JTable.findColumn(index: Int): TableColumn? { + if (index < 0 || index >= columnCount) return null return this.getColumn(this.getColumnName(index)) } +// Reloads the entire tree model. fun TreeModel.reload() { (this as? DefaultTreeModel)?.reload() } +// Reloads the specified TreeNode in the tree model. fun TreeModel.reload(node: TreeNode) { (this as? DefaultTreeModel)?.reload(node) } +// Removes all child nodes of the specified TreeNode. fun TreeModel.clear(node: TreeNode) { if (node !is DefaultMutableTreeNode) return if (node.childCount > 0) { @@ -112,16 +124,19 @@ fun TreeModel.clear(node: TreeNode) { } } +// Removes all child nodes of the root node in the tree model. fun TreeModel.clear() { (this.root as? TreeNode)?.let { this.clear(it) } } +// Removes the specified TreeNode from the tree model. fun TreeModel.remove(node: TreeNode) { if (node !is DefaultMutableTreeNode) return node.removeFromParent() this.reload(node) } +// Performs an action after the component is shown. fun Component.initAfterShown(init: () -> Unit) { var notInit = true @@ -133,6 +148,7 @@ fun Component.initAfterShown(init: () -> Unit) { } override fun componentShown(e: ComponentEvent?) { + // Only perform the action once. synchronized(this) { if (notInit) { notInit = false @@ -148,6 +164,7 @@ fun Component.initAfterShown(init: () -> Unit) { }) } +// Performs an action when the component is resized, moved, shown, or hidden. fun Component.onResized(handle: (ComponentEvent?) -> Unit) { this.addComponentListener(object : ComponentListener { override fun componentResized(e: ComponentEvent?) { @@ -168,22 +185,22 @@ fun Component.onResized(handle: (ComponentEvent?) -> Unit) { }) } +// Calculates the available height of the component after subtracting the heights of the specified components and margins. fun Component.minusHeight(margin: Int, vararg components: Component): Int { - var h = this.height + var height = this.height for (component in components) { - if (component.isVisible) { - h -= component.height - h -= margin - } + height -= component.height + margin } - return h + return height } +// Aligns the bottom edge of the component with the bottom edge of the specified Component. fun Component.bottomAlignTo(component: Component) { val h = component.location.y + component.height - this.location.y this.setSizeIfNecessary(this.width, h) } +// Sets the size of the component if it is different from the specified size. fun Component.setSizeIfNecessary(width: Int, height: Int) { if (this.width != width || this.height != height) { this.setSize(width, height) @@ -191,10 +208,12 @@ fun Component.setSizeIfNecessary(width: Int, height: Int) { } } +// Adds the specified gap to the width of the component if it is different from the current width. fun Component.adjustWithIfNecessary(gap: Int) { setSizeIfNecessary(this.width + gap, this.height) } +// Visits all child components of the container and performs an action on each component. fun Container.visit(handle: (Component) -> Unit) { for (component in this.components) { handle(component) @@ -204,7 +223,7 @@ fun Container.visit(handle: (Component) -> Unit) { } } - +// Returns the elements of the ListModel of the JList component as a List. @Suppress("UNCHECKED_CAST") fun JList.getModelElements(): List { val model = this.model @@ -216,14 +235,17 @@ fun JList.getModelElements(): List { return modelElements } +// Returns the MutableComboBoxModel of the JList component if it is mutable. fun JList.getMutableComboBoxModel(): MutableComboBoxModel? { - return this.model as? MutableComboBoxModel + return model as? MutableComboBoxModel } +// Adds an element to the MutableComboBoxModel of the JList component. fun JList.addElement(element: T) { getMutableComboBoxModel()?.addElement(element) } +// Removes the element at the specified index from the MutableComboBoxModel of the JList component. fun JList.removeElementAt(index: Int) { if (index > -1 && index < this.model.size) { getMutableComboBoxModel()?.removeElementAt(index) diff --git a/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt b/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt index 831ec41b9..c3ee337b2 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt @@ -12,6 +12,10 @@ import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicInteger +/** + * Runs the specified action asynchronously with the given ActionContext instance. + * If the instance is null, the action is run synchronously. + */ fun ActionContext?.tryRunAsync(action: () -> Unit) { if (this == null) { action() @@ -20,6 +24,10 @@ fun ActionContext?.tryRunAsync(action: () -> Unit) { } } +/** + * Calls the specified action with a timeout and returns the result. + * If the action takes longer than the specified timeout, a TimeoutException is thrown. + */ fun ActionContext.callWithTimeout(timeout: Long, action: () -> T): T? { val resultHolder = ValueHolder() @@ -39,9 +47,14 @@ fun ActionContext.callWithTimeout(timeout: Long, action: () -> T): T? { } catch (e: Exception) { //ignore } + return resultHolder.value() } +/** + * Creates a boundary and executes the specified action within the boundary. + * The boundary is waited for completion before returning. + */ fun ActionContext.withBoundary(action: () -> Unit) { val boundary = this.createBoundary() try { @@ -53,6 +66,10 @@ fun ActionContext.withBoundary(action: () -> Unit) { } } +/** + * Executes the specified action only if the current ActionContext is not reentrant for the specified flag. + * This is useful for preventing recursive calls to the same method. + */ private val reentrantIdx = AtomicInteger() fun ActionContext.notReentrant(flag: String, action: () -> Unit) { @@ -66,6 +83,10 @@ fun ActionContext.notReentrant(flag: String, action: () -> Unit) { } } +/** + * Creates a boundary and executes the specified action within the boundary. + * The boundary is waited for completion before returning the result. + */ fun ActionContext.callWithBoundary(action: () -> T): T? { val boundary = this.createBoundary() var ret: T? = null @@ -78,6 +99,10 @@ fun ActionContext.callWithBoundary(action: () -> T): T? { return ret } +/** + * Creates a boundary and executes the specified action within the boundary. + * The boundary is waited for completion with a timeout. + */ fun ActionContext.withBoundary(timeOut: Long, action: () -> Unit) { val boundary = this.createBoundary() try { @@ -90,6 +115,10 @@ fun ActionContext.withBoundary(timeOut: Long, action: () -> Unit) { } } +/** + * Runs the specified action with the current ActionContext instance. + * If no instance is available, the action is run asynchronously. + */ fun ActionContext.runWithContext(action: () -> Unit) { val context = ActionContext.getContext() if (context == null) { @@ -99,6 +128,9 @@ fun ActionContext.runWithContext(action: () -> Unit) { } } +/** + * Runs the specified action in a normal thread if the current ActionContext is not already in an asynchronous thread. + */ fun ActionContext.runInNormalThread(action: () -> Unit) { val flag = ActionContext.getFlag() if (flag == ThreadFlag.ASYNC.value) { @@ -108,8 +140,11 @@ fun ActionContext.runInNormalThread(action: () -> Unit) { } } +/** + * Finds the current method which is selected by the current Action. + */ fun ActionContext.findCurrentMethod(): PsiMethod? { return this.cacheOrCompute("_currentMethod") { ActionUtils.findCurrentMethod() } -} +} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/AnyKit.kt b/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/AnyKit.kt index c10d37c9c..ffeee83b3 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/AnyKit.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/AnyKit.kt @@ -1,29 +1,12 @@ package com.itangcent.intellij.extend +import com.itangcent.common.utils.asHashMap import com.itangcent.common.utils.isOriginal fun Boolean.toInt(): Int { return if (this) 1 else 0 } -fun Map.asHashMap(): HashMap { - if (this is HashMap) { - return this - } - val map: HashMap = HashMap() - this.entries.forEach { map[it.key] = it.value } - return map -} - -fun List.asArrayList(): ArrayList { - if (this is ArrayList) { - return this - } - val list: ArrayList = ArrayList() - this.forEach { list.add(it) } - return list -} - fun Any?.toPrettyString(): String? { if (this == null) return null if (this is String) return this @@ -36,19 +19,28 @@ fun Any?.toPrettyString(): String? { } @Suppress("UNCHECKED_CAST") -fun Any.asHashMap(): HashMap { +fun Any.asHashMap(): HashMap { if (this is HashMap<*, *>) { - return this as HashMap + return this as HashMap } if (this is Map<*, *>) { - val map: HashMap = HashMap() - this.forEach { (k, v) -> map[k.toString()] = v } - return map + return this.asHashMap() as HashMap } return HashMap() } +/** + * Return the object if it is not an "original" value, or null otherwise. + * + * An "original" value is defined as follows: + * + * - The default value of a primitive type (e.g., 0 for Int) + * - The default value of a nullable primitive type (e.g., null for Int?) + * - The empty string ("") + * - The string "0" + * - An array or collection containing only original values + */ fun Any?.takeIfNotOriginal(): Any? { return if (this.isOriginal()) { null @@ -58,26 +50,41 @@ fun Any?.takeIfNotOriginal(): Any? { } /** - * check if the object is original - * like: - * default primary: 0, 0.0 - * default blank string: "","0" - * array with original: [0],[0.0],[""] - * list with original: [0],[0.0],[""] - * map with original: {"key":0} + * Check if the object is "special" (not an original value), as defined by the takeIfNotOriginal function. + * + * An "original" value is defined as follows: + * + * - The default value of a primitive type (e.g., 0 for Int) + * - The default value of a nullable primitive type (e.g., null for Int?) + * - The empty string ("") + * - The string "0" + * - An array or collection containing only original values */ fun Any?.isSpecial(): Boolean { return when (val obj = this) { null -> { false } + is String -> { obj.isNotBlank() && obj != "0" && obj != "0.0" } + else -> !this.isOriginal() } } +/** + * Return the string if it is "special" (not an original value), or null otherwise. + * + * An "original" value is defined as follows: + * + * - The default value of a primitive type (e.g., 0 for Int) + * - The default value of a nullable primitive type (e.g., null for Int?) + * - The empty string ("") + * - The string "0" + * - An array or collection containing only original values + */ fun String?.takeIfSpecial(): String? { return if (this.isSpecial()) { this @@ -86,6 +93,10 @@ fun String?.takeIfSpecial(): String? { } } +/** + * Return the first element of an array or a collection, + * or the object itself if it is not an array or a collection. + */ fun Any?.unbox(): Any? { if (this is Array<*>) { return this.firstOrNull().unbox() diff --git a/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/GsonKit.kt b/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/GsonKit.kt index 7c85de9b6..0d22b1251 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/GsonKit.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/GsonKit.kt @@ -6,12 +6,18 @@ import com.google.gson.JsonObject import com.google.gson.JsonPrimitive import com.itangcent.common.utils.GsonUtils +/** + * Convert a JsonObject to a mutable map with string keys and nullable values. + */ fun JsonObject.asHashMap(): MutableMap { val map = LinkedHashMap() this.entrySet().forEach { map[it.key] = it.value.unbox() } return map } +/** + * Convert a JsonElement to a map with string keys and nullable values. + */ fun JsonElement.asMap(dumb: Boolean = true): Map { return when { this.isJsonObject -> this.asJsonObject.asHashMap() @@ -34,6 +40,9 @@ fun JsonElement.asList(dumb: Boolean = true): List { } } +/** + * Unbox a JsonElement. + */ fun JsonElement.unbox(): Any? { return when { this.isJsonNull -> null @@ -53,10 +62,19 @@ fun JsonPrimitive.unbox(): Any? { } } +/** + * Parse a string as a JsonElement. + */ fun String.asJsonElement(): JsonElement? { return GsonUtils.parseToJsonTree(this) } +/** + * Get a property of a JsonObject as a JsonElement. + * + * If the JsonElement is not a JsonObject or the property does not exist, this function + * returns null. + */ fun JsonElement?.sub(property: String): JsonElement? { if (this == null || !this.isJsonObject) { return null diff --git a/idea-plugin/src/main/kotlin/com/itangcent/order/Ordered.kt b/idea-plugin/src/main/kotlin/com/itangcent/order/Ordered.kt index 467569ddb..ac7d04ee1 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/order/Ordered.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/order/Ordered.kt @@ -3,14 +3,30 @@ package com.itangcent.order import kotlin.reflect.full.findAnnotation /** - * Ordered is an interface that can be implemented by objects that should be orderable. + * Objects that implement this interface can be sorted according to their order value. + * The order value is typically an integer that represents the relative order of the + * object in a sequence or collection. */ interface Ordered { + /** + * Returns the order value of this object. + */ fun order(): Int companion object { + /** + * The highest possible order value. + */ const val HIGHEST_PRECEDENCE = Int.MIN_VALUE + + /** + * The lowest possible order value. + */ const val LOWEST_PRECEDENCE = Int.MAX_VALUE + + /** + * The default order value. + */ const val DEFAULT_PRECEDENCE = 0 } } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/suv/http/ConfigurableHttpClientProvider.kt b/idea-plugin/src/main/kotlin/com/itangcent/suv/http/ConfigurableHttpClientProvider.kt index b5ce95749..94eb2e1f5 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/suv/http/ConfigurableHttpClientProvider.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/suv/http/ConfigurableHttpClientProvider.kt @@ -21,6 +21,10 @@ import java.io.InputStream import java.nio.charset.Charset import java.util.concurrent.TimeUnit +/** + * An implementation of the HttpClientProvider interface + * that provides a configurable HttpClient implementation. + */ @Singleton class ConfigurableHttpClientProvider : AbstractHttpClientProvider() { @@ -34,28 +38,37 @@ class ConfigurableHttpClientProvider : AbstractHttpClientProvider() { protected val ruleComputer: RuleComputer? = null @Inject - protected val logger: Logger? = null + protected lateinit var logger: Logger + /** + * Builds an instance of HttpClient using configuration options such as timeouts and SSL settings. + * + * @return An instance of HttpClient. + */ override fun buildHttpClient(): HttpClient { val httpClientBuilder = HttpClients.custom() val config = readHttpConfig() httpClientBuilder - .setConnectionManager(PoolingHttpClientConnectionManager().also { - it.maxTotal = 50 - it.defaultMaxPerRoute = 20 - }) - .setDefaultSocketConfig(SocketConfig.custom() - .setSoTimeout(config.timeOut) - .build()) - .setDefaultRequestConfig(RequestConfig.custom() - .setConnectTimeout(config.timeOut) - .setConnectionRequestTimeout(config.timeOut) - .setSocketTimeout(config.timeOut) - .setCookieSpec(CookieSpecs.STANDARD).build()) - .setSSLHostnameVerifier(NOOP_HOST_NAME_VERIFIER) - .setSSLSocketFactory(SSLSF) + .setConnectionManager(PoolingHttpClientConnectionManager().also { + it.maxTotal = 50 + it.defaultMaxPerRoute = 20 + }) + .setDefaultSocketConfig( + SocketConfig.custom() + .setSoTimeout(config.timeOut) + .build() + ) + .setDefaultRequestConfig( + RequestConfig.custom() + .setConnectTimeout(config.timeOut) + .setConnectionRequestTimeout(config.timeOut) + .setSocketTimeout(config.timeOut) + .setCookieSpec(CookieSpecs.STANDARD).build() + ) + .setSSLHostnameVerifier(NOOP_HOST_NAME_VERIFIER) + .setSSLSocketFactory(SSLSF) return HttpClientWrapper(ApacheHttpClient(httpClientBuilder.build())) } @@ -70,14 +83,18 @@ class ConfigurableHttpClientProvider : AbstractHttpClientProvider() { if (configReader != null) { try { configReader.first("http.timeOut")?.toLong() - ?.let { httpConfig.timeOut = TimeUnit.SECONDS.toMillis(it).toInt() } + ?.let { httpConfig.timeOut = TimeUnit.SECONDS.toMillis(it).toInt() } } catch (e: NumberFormatException) { + logger.warn("http.timeOut must be a number") } } return httpConfig } + /** + * A wrapper class that implements the HttpClient interface and delegates to a wrapped HttpClient instance. + */ @ScriptTypeName("httpClient") private inner class HttpClientWrapper(private val httpClient: HttpClient) : HttpClient { @@ -85,11 +102,17 @@ class ConfigurableHttpClientProvider : AbstractHttpClientProvider() { return httpClient.cookieStore() } + /** + * Wraps the request in a custom HttpRequestWrapper implementation and delegates to the wrapped HttpClient instance. + */ override fun request(): HttpRequest { return HttpRequestWrapper(httpClient.request()) } } + /** + * A wrapper class that implements the HttpRequest interface and delegates to a wrapped HttpRequest instance. + */ @ScriptTypeName("request") private inner class HttpRequestWrapper(private val httpRequest: HttpRequest) : HttpRequest by httpRequest { @@ -213,8 +236,9 @@ class ConfigurableHttpClientProvider : AbstractHttpClientProvider() { override fun call(): HttpResponse { val url = url() ?: throw IllegalArgumentException("url not be set") if (httpSettingsHelper != null - && !httpSettingsHelper.checkTrustUrl(url, false)) { - logger?.warn("[access forbidden] call:$url") + && !httpSettingsHelper.checkTrustUrl(url, false) + ) { + logger.warn("[access forbidden] call:$url") return EmptyHttpResponse(this) } var i = 0 @@ -235,6 +259,9 @@ class ConfigurableHttpClientProvider : AbstractHttpClientProvider() { } } + /** + * An implementation of the HttpResponse interface that returns empty or null values for all methods. + */ class EmptyHttpResponse(private val request: HttpRequest) : HttpResponse { override fun code(): Int { return 404 @@ -268,7 +295,7 @@ class ConfigurableHttpClientProvider : AbstractHttpClientProvider() { return null } - override fun containsHeader(headerName: String): Boolean? { + override fun containsHeader(headerName: String): Boolean { return false } @@ -290,14 +317,17 @@ class ConfigurableHttpClientProvider : AbstractHttpClientProvider() { } + /** + * A custom implementation of the HttpResponse interface that wraps a delegate HttpResponse instance and adds a + * discard() method that can be used to discard the current response and recall the request. + */ @ScriptTypeName("response") class DiscardAbleHttpResponse(httpResponse: HttpResponse) : HttpResponse by httpResponse { private var discarded = false /** - * Discard current response. - * Recall the request. + * Discards the current response and returns a new HttpResponse instance for the original request. */ fun discard() { this.discarded = true @@ -309,6 +339,9 @@ class ConfigurableHttpClientProvider : AbstractHttpClientProvider() { } } + /** + * A data class that holds configuration settings for the HTTP client. + */ class HttpConfig { //default 10s diff --git a/idea-plugin/src/main/kotlin/com/itangcent/utils/ExtensibleKit.kt b/idea-plugin/src/main/kotlin/com/itangcent/utils/ExtensibleKit.kt index d9df15b5c..3b59bc3b2 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/utils/ExtensibleKit.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/utils/ExtensibleKit.kt @@ -15,18 +15,20 @@ object ExtensibleKit { val jsonElement = GsonUtils.parseToJsonTree(json)!! val t = GSON.fromJson(jsonElement, this.java) jsonElement.asJsonObject.entrySet() - .filter { it.key.startsWith(Attrs.PREFIX) } - .forEach { t.setExt(it.key, it.value.unbox()) } + .filter { it.key.startsWith(Attrs.PREFIX) } + .forEach { t.setExt(it.key, it.value.unbox()) } return t } fun KClass.fromJson(json: String, vararg exts: String): T { - val extNames = exts.removePrefix(Attrs.PREFIX) + val extNames = exts.toSet() + + exts.map { it.removePrefix(Attrs.PREFIX) } + + exts.map { it.addPrefix(Attrs.PREFIX) } val jsonElement = GsonUtils.parseToJsonTree(json)!! val t = GSON.fromJson(jsonElement, this.java) jsonElement.asJsonObject.entrySet() - .filter { extNames.contains(it.key) } - .forEach { t.setExt(it.key.addPrefix(Attrs.PREFIX), it.value.unbox()) } + .filter { extNames.contains(it.key) } + .forEach { t.setExt(it.key.addPrefix(Attrs.PREFIX), it.value.unbox()) } return t } @@ -41,10 +43,10 @@ object ExtensibleKit { return this } - /** - * Remove [prefix] from each string in the original array. - */ - private fun Array.removePrefix(prefix: CharSequence): Array { - return this.mapToTypedArray { it.removePrefix(prefix) } +} + +fun Extensible.setExts(exts: Map) { + exts.forEach { (t, u) -> + this.setExt(t, u) } } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/utils/FileKit.kt b/idea-plugin/src/main/kotlin/com/itangcent/utils/FileKit.kt index f96bc41a9..09321855e 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/utils/FileKit.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/utils/FileKit.kt @@ -2,6 +2,10 @@ package com.itangcent.utils import java.io.File +/** + * Returns a path string with `/` converted to the OS-specific separator character. + * On OSes where the separator is already `/`, the original string is returned. + */ fun String.localPath(): String { if (File.separatorChar != '/') { return this.replace('/', File.separatorChar) diff --git a/idea-plugin/src/main/kotlin/com/itangcent/utils/GiteeSupport.kt b/idea-plugin/src/main/kotlin/com/itangcent/utils/GiteeSupport.kt index b8bf66bb9..4ed41d8b2 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/utils/GiteeSupport.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/utils/GiteeSupport.kt @@ -3,21 +3,36 @@ package com.itangcent.utils import java.util.regex.Pattern /** + * Converts a raw GitHub URL to a Gitee raw URL. + * + * Raw URLs have the format: + * `https://raw.githubusercontent.com/$user/$project/$file` + * + * This converts it to the Gitee raw URL format: + * `https://gitee.com/$user/$project/raw/$file` + * * @author tangcent */ object GiteeSupport { + + private val GITHUB_RAW_URL_REGEX = + Pattern.compile("https://raw.githubusercontent.com/(.*?)/(.*?)/(.*?)") + /** + * Converts a raw GitHub URL to a Gitee raw URL. + * * https://raw.githubusercontent.com/$user/$project/$path * -> * https://gitee.com/$user/$project/raw/$path */ - fun convertUrlFromGithub(url: String): String? { - val matcher = - Pattern.compile("https://raw.githubusercontent.com/(.*?)/(.*?)/(.*?)") - .matcher(url) - if (!matcher.matches()) { - return null - } - return "https://gitee.com/${matcher.group(1)}/${matcher.group(2)}/raw/${matcher.group(3)}" + fun convertUrlFromGithub(githubUrl: String): String? { + val matcher = GITHUB_RAW_URL_REGEX.matcher(githubUrl) + + if (!matcher.matches()) return null + + val giteeUser = matcher.group(1) + val giteeProject = matcher.group(2) + val giteeFilePath = matcher.group(3) + return "https://gitee.com/$giteeUser/$giteeProject/raw/$giteeFilePath" } } \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/utils/ResourceUtils.kt b/idea-plugin/src/main/kotlin/com/itangcent/utils/ResourceUtils.kt index 439bf71b9..1aaaf5de0 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/utils/ResourceUtils.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/utils/ResourceUtils.kt @@ -5,10 +5,26 @@ import com.itangcent.common.utils.readString import com.itangcent.common.utils.safeComputeIfAbsent import java.net.URL +/** + * A utility class for reading resources from the classpath. + * + * @author tangcent + */ object ResourceUtils { + // A map that caches the contents of resources that have been read private val resourceCache: KV = KV() + // A map that caches the URLs of resources that have been found + private val resourceURLCache: KV = KV() + + /** + * Reads the contents of the specified resource from the classpath and returns it as a String. + * If the resource has already been read, then the cached value is returned. + * + * @param resourceName The name of the resource to read. + * @return The contents of the resource as a String, or an empty String if the resource could not be found. + */ fun readResource(resourceName: String): String { return resourceCache.safeComputeIfAbsent(resourceName) { (ResourceUtils::class.java.classLoader.getResourceAsStream(resourceName) @@ -17,16 +33,27 @@ object ResourceUtils { } ?: "" } - private val resourceURLCache: KV = KV() - + /** + * Attempts to find the URL of the specified resource on the classpath. + * If the resource URL has already been found, then the cached value is returned. + * + * @param resourceName The name of the resource to find. + * @return The URL of the resource, or null if the resource could not be found. + */ fun findResource(resourceName: String): URL? { return resourceURLCache.safeComputeIfAbsent(resourceName) { doFindResource(resourceName) } } + /** + * Attempts to find the URL of the specified resource using the class loader. + * + * @param resourceName The name of the resource to find. + * @return The URL of the resource, or null if the resource could not be found. + */ private fun doFindResource(resourceName: String): URL? { return ResourceUtils::class.java.classLoader.getResource(resourceName) ?: ResourceUtils::class.java.getResource(resourceName) } -} +} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/utils/StringKit.kt b/idea-plugin/src/main/kotlin/com/itangcent/utils/StringKit.kt index be72ef086..77d725364 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/utils/StringKit.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/utils/StringKit.kt @@ -1,16 +1,21 @@ package com.itangcent.utils - +/** + * A map of escape characters to their unescaped versions. + */ private val escapeCharacters = mapOf( - 'b' to '\b', - 't' to '\t', - 'n' to '\n', - 'r' to '\r', - '\\' to '\\', + 'b' to '\b', // Backspace + 't' to '\t', // Tab + 'n' to '\n', // Newline + 'r' to '\r', // Carriage return + '\\' to '\\', // Backslash ) /** - * Used to convert escaped characters to their unescaped version. + * Used to convert escaped characters in a string to their unescaped version. + * For example, "\\n" would be converted to "\n". + * + * @return The unescaped string. */ fun String.unescape(): String { val sb = StringBuilder() diff --git a/idea-plugin/src/main/kotlin/com/itangcent/utils/TemplateKit.kt b/idea-plugin/src/main/kotlin/com/itangcent/utils/TemplateKit.kt index 9d1084d2d..a80f9460e 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/utils/TemplateKit.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/utils/TemplateKit.kt @@ -1,27 +1,45 @@ package com.itangcent.utils +/** + * A utility object for working with templates. + */ object TemplateKit { - fun resolvePlaceHolder(placeHolder: Any?): Array? { + /** + * Resolves placeholders that may contain characters in a variety of formats. + * + * @param placeHolder The placeholder to resolve. This can be null or an object of type Char, String, Array, or Collection. + * @return An array of characters, or null if the `placeHolder` argument is null or does not contain any characters. + */ + fun resolvePlaceHolder(placeHolder: Any?): CharArray? { if (placeHolder == null) { return null } - val placeHolders = ArrayList() + val placeHolders = LinkedHashSet() resolvePlaceHolder(placeHolder) { placeHolders.add(it) } - return placeHolders.takeIf { it.isNotEmpty() }?.toTypedArray() + return placeHolders.takeIf { it.isNotEmpty() }?.toCharArray() } + /** + * A helper function used to recursively resolve placeholders. + * + * @param placeHolder The placeholder to resolve. This can be an object of type Char, String, Array, or Collection. + * @param handle A function used to handle each character found in the placeholder. + */ private fun resolvePlaceHolder(placeHolder: Any?, handle: (Char) -> Unit) { when (placeHolder) { is Char -> { handle(placeHolder) } + is String -> { placeHolder.toCharArray().forEach(handle) } + is Array<*> -> { placeHolder.forEach { resolvePlaceHolder(it, handle) } } + is Collection<*> -> { placeHolder.forEach { resolvePlaceHolder(it, handle) } } diff --git a/idea-plugin/src/test/kotlin/com/itangcent/cache/CacheSwitcherTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/cache/CacheSwitcherTest.kt new file mode 100644 index 000000000..8a6de6248 --- /dev/null +++ b/idea-plugin/src/test/kotlin/com/itangcent/cache/CacheSwitcherTest.kt @@ -0,0 +1,99 @@ +package com.itangcent.cache + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +/** + * Test for [CacheSwitcher] + * + * @author tangcent + */ +class CacheSwitcherTest { + + // This class implements the CacheSwitcher interface and provides an getObject() function that returns a cached object if caching is enabled, and a new object if caching is disabled. + class TestCacheSwitcher : CacheSwitcher { + // This variable holds the cached object, if caching is enabled. + private var cachedObject: Any? = null + + // This variable tracks whether caching is currently enabled or disabled. + var cachingEnabled = true + + // This function returns the cached object, if caching is enabled. + // Otherwise, it returns a new object and caches it. + fun getObject(): Any { + return if (cachingEnabled && cachedObject != null) { + cachedObject!! + } else { + val newObject = Any() + cachedObject = newObject + newObject + } + } + + override fun notUserCache() { + // Disable caching. + cachingEnabled = false + } + + override fun userCache() { + // Enable caching. + cachingEnabled = true + } + } + + @Test + fun `notUserCache disables caching for non-user data`() { + // Create a new TestCacheSwitcher object with caching enabled. + val cacheSwitcher = TestCacheSwitcher() + cacheSwitcher.userCache() + + // Call notUserCache() to disable caching. + cacheSwitcher.notUserCache() + + // Call getObject() twice and verify that different objects are returned each time. + val obj1 = cacheSwitcher.getObject() + val obj2 = cacheSwitcher.getObject() + assertNotEquals(obj1, obj2) + } + + @Test + fun `userCache enables caching for data that was previously disabled`() { + // Create a new TestCacheSwitcher object with caching disabled. + val cacheSwitcher = TestCacheSwitcher() + cacheSwitcher.notUserCache() + + // Call userCache() to enable caching. + cacheSwitcher.userCache() + + // Call getObject() twice and verify that the same object is returned both times. + val obj1 = cacheSwitcher.getObject() + val obj2 = cacheSwitcher.getObject() + assertSame(obj1, obj2) + } + + @Test + fun `withoutCache prevents caching for the duration of the call`() { + // Create a new TestCacheSwitcher object with caching enabled. + val cacheSwitcher = TestCacheSwitcher() + cacheSwitcher.userCache() + + // Call the withoutCache() extension function and verify that caching is disabled for the duration of the call. + cacheSwitcher.withoutCache { + // Verify that caching is currently disabled. + assertFalse(cacheSwitcher.cachingEnabled) + + // Call getObject() twice and verify that different objects are returned each time. + val obj1 = cacheSwitcher.getObject() + val obj2 = cacheSwitcher.getObject() + assertNotEquals(obj1, obj2) + } + + // Verify that caching is currently enabled. + assertTrue(cacheSwitcher.cachingEnabled) + + // Call getObject() twice and verify that the same object is returned both times. + val obj1 = cacheSwitcher.getObject() + val obj2 = cacheSwitcher.getObject() + assertSame(obj1, obj2) + } +} \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/condition/AnnotatedConditionTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/condition/AnnotatedConditionTest.kt new file mode 100644 index 000000000..be3fa2dc2 --- /dev/null +++ b/idea-plugin/src/test/kotlin/com/itangcent/condition/AnnotatedConditionTest.kt @@ -0,0 +1,140 @@ +package com.itangcent.condition + +import com.itangcent.intellij.context.ActionContext +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.kotlin.mock +import kotlin.reflect.KClass + +class AnnotatedConditionTest { + + @Test + fun `matches returns true for matching annotation`() { + val condition = object : AnnotatedCondition() { + override fun matches(actionContext: ActionContext, annotation: MyAnnotation): Boolean { + return annotation.value == "test" + } + } + + @MyAnnotation("test") + class MyClass + + assertTrue(condition.matches(mock(), MyClass::class)) + } + + + @Test + fun `matches returns false for non-matching annotation`() { + val condition = object : AnnotatedCondition() { + override fun matches(actionContext: ActionContext, annotation: MyAnnotation): Boolean { + return annotation.value == "test" + } + } + + @MyAnnotation("other") + class MyClass + + assertFalse(condition.matches(mock(), MyClass::class)) + } + + @Test + fun `matches returns false for any non-matching annotation`() { + val condition = object : AnnotatedCondition() { + override fun matches(actionContext: ActionContext, annotation: MyAnnotation): Boolean { + return annotation.value == "test" + } + } + + @MyAnnotation("test") + open class MyBaseClass + + @MyAnnotation("other") + class MyClass : MyBaseClass() + + assertFalse(condition.matches(mock(), MyClass::class)) + } + + @Test + fun `supported returns true for supported bean class`() { + val condition = object : AnnotatedCondition() { + override fun matches(actionContext: ActionContext, annotation: MyAnnotation): Boolean { + return true + } + } + + @MyAnnotation("test") + class MyClass + + assertTrue(condition.supported(MyClass::class)) + } + + @Test + fun `supported returns false for unsupported bean class`() { + val condition = object : AnnotatedCondition() { + override fun matches(actionContext: ActionContext, annotation: MyAnnotation): Boolean { + return true + } + } + + class MyClass + + assertFalse(condition.supported(MyClass::class)) + } + + @Test + fun `supported returns false for super class`() { + val condition = object : AnnotatedCondition() { + override fun matches(actionContext: ActionContext, annotation: MyAnnotation): Boolean { + return annotation.value == "test" + } + } + + @MyAnnotation("test") + open class MyBaseClass + + class MyClass : MyBaseClass() + + assertTrue(condition.supported(MyClass::class)) + } + + @Test + fun `annClass returns the correct annotation class for the given type parameter`() { + val condition = object : AnnotatedCondition() { + override fun matches(actionContext: ActionContext, annotation: MyAnnotation): Boolean { + return true + } + + public override fun annClass(): KClass { + return super.annClass() + } + } + + assertEquals(MyAnnotation::class, condition.annClass()) + } + + @Test + fun `sub-sub-class returns the correct annotation class for the given type parameter`() { + abstract class SubAnnotatedCondition : AnnotatedCondition() + abstract class SubSubAnnotatedCondition : SubAnnotatedCondition() + + val condition = object : SubSubAnnotatedCondition() { + override fun matches(actionContext: ActionContext, annotation: MyAnnotation): Boolean { + return annotation.value == "test" + } + + public override fun annClass(): KClass { + return super.annClass() + } + } + + @MyAnnotation("test") + class MyClass + + assertEquals(MyAnnotation::class, condition.annClass()) + + assertTrue(condition.matches(mock(), MyClass::class)) + } +} + + +annotation class MyAnnotation(val value: String) \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/call/ApiCallerTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/call/ApiCallerTest.kt index 51ba14f2c..bcfc7d40b 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/call/ApiCallerTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/call/ApiCallerTest.kt @@ -16,7 +16,6 @@ import com.itangcent.test.assertLinesContain import com.itangcent.test.mock import com.itangcent.test.workAt import com.itangcent.testFramework.PluginContextLightCodeInsightFixtureTestCase -import junit.framework.Assert import org.mockito.Mockito import org.mockito.kotlin.any import org.mockito.kotlin.mock @@ -57,7 +56,7 @@ internal abstract class ApiCallerTest : PluginContextLightCodeInsightFixtureTest } }) builder.mock(ApiCallUI::class) { apiCallUI -> - this.apiCallUI = apiCallUI + this@ApiCallerTest.apiCallUI = apiCallUI Mockito.`when`(apiCallUI.updateRequestList(any())) .thenAnswer { requestListInUI = it.getArgument>(0) diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/UrlSelectorTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/UrlSelectorTest.kt new file mode 100644 index 000000000..3cab829be --- /dev/null +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/UrlSelectorTest.kt @@ -0,0 +1,150 @@ +package com.itangcent.idea.plugin.api.export + +import com.google.inject.Inject +import com.intellij.psi.PsiElement +import com.itangcent.common.model.Request +import com.itangcent.common.model.URL +import com.itangcent.idea.plugin.api.export.core.ClassExportRuleKeys.PATH_MULTI +import com.itangcent.idea.plugin.api.export.core.ResolveMultiPath +import com.itangcent.intellij.config.rule.Rule +import com.itangcent.intellij.config.rule.RuleLookUp +import com.itangcent.intellij.config.rule.StringRule +import com.itangcent.intellij.context.ActionContext +import com.itangcent.mock.BaseContextTest +import com.itangcent.test.mock +import org.junit.jupiter.api.Test +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import kotlin.reflect.KClass +import kotlin.test.assertEquals + +/** + * Test for [com.itangcent.idea.plugin.api.export.UrlSelector] + */ + +abstract class BaseUrlSelectorAllTest : BaseContextTest() { + + @Inject + protected lateinit var urlSelector: UrlSelector + + override fun bind(builder: ActionContext.ActionContextBuilder) { + super.bind(builder) + builder.mock { + on( + it.lookUp( + PATH_MULTI.name(), + PATH_MULTI.mode().targetType() as KClass + ) + ) doReturn listOf( + StringRule.of { mode().name } + ) as List> + } + } + + abstract fun mode(): ResolveMultiPath +} + +class UrlSelectorAllTest : BaseUrlSelectorAllTest() { + override fun mode(): ResolveMultiPath = ResolveMultiPath.ALL + + @Test + fun `select urls`() { + assertEquals(URL.nil(), urlSelector.selectUrls(Request().apply { + path = null + })) + assertEquals(URL.nil(), urlSelector.selectUrls(Request().apply { + path = URL.nil() + })) + assertEquals(URL.of("/single"), urlSelector.selectUrls(Request().apply { + path = URL.of("/single") + })) + assertEquals(URL.of("/short", "/middle", "/long-long"), urlSelector.selectUrls(Request().apply { + path = URL.of("/short", "/middle", "/long-long") + resource = mock {} + })) + } +} + +class UrlSelectorFirstTest : BaseUrlSelectorAllTest() { + override fun mode(): ResolveMultiPath = ResolveMultiPath.FIRST + + @Test + fun `select urls`() { + assertEquals(URL.nil(), urlSelector.selectUrls(Request().apply { + path = null + })) + assertEquals(URL.nil(), urlSelector.selectUrls(Request().apply { + path = URL.nil() + })) + assertEquals(URL.of("/single"), urlSelector.selectUrls(Request().apply { + path = URL.of("/single") + })) + assertEquals(URL.of("/short"), urlSelector.selectUrls(Request().apply { + path = URL.of("/short", "/middle", "/long-long") + resource = mock {} + })) + } +} + +class UrlSelectorLastTest : BaseUrlSelectorAllTest() { + override fun mode(): ResolveMultiPath = ResolveMultiPath.LAST + + @Test + fun `select urls`() { + assertEquals(URL.nil(), urlSelector.selectUrls(Request().apply { + path = null + })) + assertEquals(URL.nil(), urlSelector.selectUrls(Request().apply { + path = URL.nil() + })) + assertEquals(URL.of("/single"), urlSelector.selectUrls(Request().apply { + path = URL.of("/single") + })) + assertEquals(URL.of("/long-long"), urlSelector.selectUrls(Request().apply { + path = URL.of("/short", "/middle", "/long-long") + resource = mock {} + })) + } +} + +class UrlSelectorShortestTest : BaseUrlSelectorAllTest() { + override fun mode(): ResolveMultiPath = ResolveMultiPath.SHORTEST + + @Test + fun `select urls`() { + assertEquals(URL.nil(), urlSelector.selectUrls(Request().apply { + path = null + })) + assertEquals(URL.nil(), urlSelector.selectUrls(Request().apply { + path = URL.nil() + })) + assertEquals(URL.of("/single"), urlSelector.selectUrls(Request().apply { + path = URL.of("/single") + })) + assertEquals(URL.of("/short"), urlSelector.selectUrls(Request().apply { + path = URL.of("/short", "/middle", "/long-long") + resource = mock {} + })) + } +} + +class UrlSelectorLongestTest : BaseUrlSelectorAllTest() { + override fun mode(): ResolveMultiPath = ResolveMultiPath.LONGEST + + @Test + fun `select urls`() { + assertEquals(URL.nil(), urlSelector.selectUrls(Request().apply { + path = null + })) + assertEquals(URL.nil(), urlSelector.selectUrls(Request().apply { + path = URL.nil() + })) + assertEquals(URL.of("/single"), urlSelector.selectUrls(Request().apply { + path = URL.of("/single") + })) + assertEquals(URL.of("/long-long"), urlSelector.selectUrls(Request().apply { + path = URL.of("/short", "/middle", "/long-long") + resource = mock {} + })) + } +} \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/core/DefaultFormatFolderHelperTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/core/DefaultFormatFolderHelperTest.kt index f027e12a2..177393040 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/core/DefaultFormatFolderHelperTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/core/DefaultFormatFolderHelperTest.kt @@ -43,14 +43,14 @@ internal class DefaultFormatFolderHelperTest : PluginContextLightCodeInsightFixt fun testResolveFolder() { - //test of PsiClass & PsiMethod + //test for PsiClass & PsiMethod assertEquals(Folder("apis about user", "apis about user\n" + "access user info"), formatFolderHelper.resolveFolder(userCtrlPsiClass)) assertEquals(Folder("apis about user", "apis about user\n" + "access user info"), formatFolderHelper.resolveFolder(userCtrlPsiClass.methods[0])) assertEquals(Folder("update-apis", ""), formatFolderHelper.resolveFolder(userCtrlPsiClass.methods[1])) - //test of ExplicitClass & ExplicitMethod + //test for ExplicitClass & ExplicitMethod val explicitClass = duckTypeHelper.explicit(userCtrlPsiClass) assertEquals(Folder("apis about user", "apis about user\n" + "access user info"), formatFolderHelper.resolveFolder(explicitClass)) @@ -58,7 +58,7 @@ internal class DefaultFormatFolderHelperTest : PluginContextLightCodeInsightFixt "access user info"), formatFolderHelper.resolveFolder(explicitClass.methods()[0])) assertEquals(Folder("update-apis", ""), formatFolderHelper.resolveFolder(explicitClass.methods()[1])) - //test of PsiClassResource & PsiMethodResource + //test for PsiClassResource & PsiMethodResource assertEquals(Folder("apis about user", "apis about user\n" + "access user info"), formatFolderHelper.resolveFolder(PsiClassResource(userCtrlPsiClass))) assertEquals(Folder("apis about user", "apis about user\n" + diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/postman/PostmanFormatFolderHelperTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/postman/PostmanFormatFolderHelperTest.kt index f5cc515cf..d52f6ae90 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/postman/PostmanFormatFolderHelperTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/postman/PostmanFormatFolderHelperTest.kt @@ -51,7 +51,7 @@ internal class PostmanFormatFolderHelperTest : PluginContextLightCodeInsightFixt } fun testResolveFolder() { - //test of PsiClass & PsiMethod + //test for PsiClass & PsiMethod formatFolderHelper.resolveFolder(userCtrlPsiClass).let { assertEquals(Folder("apis about user", "apis about user\n" + "access user info"), it) @@ -74,7 +74,7 @@ internal class PostmanFormatFolderHelperTest : PluginContextLightCodeInsightFixt assertNull(it.getExt(ClassExportRuleKeys.POST_TEST.name())) } - //test of ExplicitClass & ExplicitMethod + //test for ExplicitClass & ExplicitMethod val explicitClass = duckTypeHelper.explicit(userCtrlPsiClass) formatFolderHelper.resolveFolder(explicitClass).let { assertEquals(Folder("apis about user", "apis about user\n" + @@ -98,7 +98,7 @@ internal class PostmanFormatFolderHelperTest : PluginContextLightCodeInsightFixt assertNull(it.getExt(ClassExportRuleKeys.POST_TEST.name())) } - //test of PsiClassResource & PsiMethodResource + //test for PsiClassResource & PsiMethodResource formatFolderHelper.resolveFolder(PsiClassResource(userCtrlPsiClass)).let { assertEquals(Folder("apis about user", "apis about user\n" + "access user info"), it) diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/config/RecommendConfigReaderTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/config/RecommendConfigReaderTest.kt index 1ca5c6b76..6a3ff7f3c 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/config/RecommendConfigReaderTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/config/RecommendConfigReaderTest.kt @@ -132,8 +132,6 @@ internal abstract class RecommendConfigReaderTest : AdvancedContextTest() { @Test fun test() { - var mock = Mockito.mock(ConfigReader::class.java, Mockito.withSettings() - .extraInterfaces(Initializable::class.java)) assertEquals(ResultLoader.load("log"), LoggerCollector.getLog().toUnixString()) assertNull(recommendConfigReader.first("ignore")) } diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/render/ConfigurableShellFileMarkdownRenderTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/render/ConfigurableShellFileMarkdownRenderTest.kt index 59df739f3..b99768cb9 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/render/ConfigurableShellFileMarkdownRenderTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/render/ConfigurableShellFileMarkdownRenderTest.kt @@ -6,6 +6,8 @@ import com.itangcent.intellij.extend.guice.with import com.itangcent.mock.AdvancedContextTest import org.intellij.lang.annotations.Language import org.junit.jupiter.api.Test +import org.junit.jupiter.api.condition.EnabledOnOs +import org.junit.jupiter.api.condition.OS import kotlin.test.assertEquals import kotlin.test.assertNull @@ -61,6 +63,7 @@ internal open class ConfigurableShellFileMarkdownRenderTest : AdvancedContextTes } } + @EnabledOnOs(value = [OS.LINUX, OS.MAC], disabledReason = "Only for Linux and Mac") internal open class SuccessConfigurableShellFileMarkdownRenderTest : ConfigurableShellFileMarkdownRenderTest() { override fun customConfig(): String { diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/AnyKitTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/AnyKitTest.kt index a45643d66..802174b12 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/AnyKitTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/AnyKitTest.kt @@ -1,9 +1,6 @@ package com.itangcent.idea.utils -import com.itangcent.intellij.extend.asArrayList -import com.itangcent.intellij.extend.asHashMap -import com.itangcent.intellij.extend.toInt -import com.itangcent.intellij.extend.toPrettyString +import com.itangcent.intellij.extend.* import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test @@ -14,50 +11,101 @@ import org.junit.jupiter.api.Test class AnyKitTest { @Test - fun testToInt() { + fun testBooleanToInt() { assertEquals(1, true.toInt()) assertEquals(0, false.toInt()) } + @Test + fun testToPrettyString() { + val nullObj: Any? = null + assertNull(nullObj.toPrettyString()) + + assertEquals("abc", "abc".toPrettyString()) + + val array = arrayOf(1, 2, 3) + assertEquals("[1, 2, 3]", array.toPrettyString()) + + val list = listOf(1, 2, 3) + assertEquals("[1, 2, 3]", list.toPrettyString()) + + val map = mapOf("a" to 1, "b" to 2) + assertEquals("{a: 1, b: 2}", map.toPrettyString()) + + val obj = Any() + assertEquals(obj.toString(), obj.toPrettyString()) + } + @Test fun testAsHashMap() { - val map = mapOf("x" to 1) - val hashMap = map.asHashMap() - assertEquals(HashMap::class, hashMap::class) - assertFalse(map === hashMap) - assertEquals(map, hashMap) - assertSame(hashMap, hashMap.asHashMap()) + val map = mapOf("a" to 1, "b" to 2) + val hashMap = map.asHashMap() + assertInstanceOf(HashMap::class.java, hashMap) + assertEquals(mapOf("a" to 1, "b" to 2), hashMap) + + val singleMap = mapOf("a" to 1) + val singleHashMap = singleMap.asHashMap() + assertInstanceOf(HashMap::class.java, singleHashMap) + assertEquals(mapOf("a" to 1), singleHashMap) + + val emptyMap = emptyMap() + val emptyHashMap = emptyMap.asHashMap() + assertInstanceOf(HashMap::class.java, emptyHashMap) + assertTrue(emptyHashMap.isEmpty()) + + val obj = Any() + val otherEmptyHashMap = obj.asHashMap() + assertInstanceOf(HashMap::class.java, otherEmptyHashMap) + assertTrue(otherEmptyHashMap.isEmpty()) } @Test - fun testAnyAsHashMap() { - val map: Any = mapOf("x" to 1) - val hashMap: Any = map.asHashMap() - assertEquals(HashMap::class, hashMap::class) - assertFalse(map === hashMap) - assertEquals(map, hashMap) - assertSame(hashMap, hashMap.asHashMap()) - assertTrue(0.asHashMap().isEmpty()) + fun testTakeIfNotOriginal() { + val obj1 = Any() + val obj2 = 0 + val obj3 = "" + val obj4 = arrayOf(0, 1) + assertEquals(obj1, obj1.takeIfNotOriginal()) + assertNull(obj2.takeIfNotOriginal()) + assertNull(obj3.takeIfNotOriginal()) + assertEquals(obj4, obj4.takeIfNotOriginal()) } @Test - fun testAsArrayList() { - val list = listOf("x", "y") - val arrayList = list.asArrayList() - assertEquals(ArrayList::class, arrayList::class) - assertFalse(list === arrayList) - assertEquals(list, arrayList) - assertTrue(arrayList === arrayList.asArrayList()) + fun testIsSpecial() { + val obj1: String? = null + val obj2 = "" + val obj3 = "0" + val obj4 = "abc" + val obj5 = arrayOf(0, 1) + assertFalse(obj1.isSpecial()) + assertFalse(obj2.isSpecial()) + assertFalse(obj3.isSpecial()) + assertTrue(obj4.isSpecial()) + assertTrue(obj5.isSpecial()) } @Test - fun testToPrettyString() { - assertEquals(null, null.toPrettyString()) - assertEquals("hello world", "hello world".toPrettyString()) - assertEquals("1", 1.toPrettyString()) - assertEquals("1.0", 1.0.toPrettyString()) - assertEquals("[x, 1]", listOf("x", 1).toPrettyString()) - assertEquals("[y, 2]", arrayOf("y", 2).toPrettyString()) - assertEquals("{x: 1, y: 2}", mapOf("x" to 1, "y" to 2).toPrettyString()) + fun testTakeIfSpecial() { + val obj1: String? = null + val obj2 = "" + val obj3 = "0" + val obj4 = "abc" + assertNull(obj1.takeIfSpecial()) + assertNull(obj2.takeIfSpecial()) + assertNull(obj3.takeIfSpecial()) + assertEquals(obj4, obj4.takeIfSpecial()) + } + + @Test + fun testUnbox() { + val obj1 = arrayOf(1, 2, 3) + val obj2 = listOf(4, 5, 6) + val obj3 = "abc" + val obj4 = Any() + assertEquals(1, obj1.unbox()) + assertEquals(4, obj2.unbox()) + assertEquals(obj3, obj3.unbox()) + assertEquals(obj4, obj4.unbox()) } } \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/ExtensibleKitKtTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/ExtensibleKitKtTest.kt deleted file mode 100644 index 5d3285474..000000000 --- a/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/ExtensibleKitKtTest.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.itangcent.idea.utils - -import com.itangcent.common.model.* -import com.itangcent.common.utils.Extensible -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource - -/** - * Test case of [com.itangcent.idea.utils.ExtensibleKit] - */ -internal class ExtensibleKitKtTest { - - @ParameterizedTest - @ValueSource(classes = [Doc::class, FormParam::class, - Header::class, MethodDoc::class, - Param::class, PathParam::class, - Request::class, Response::class - ]) - fun testSetExts(cls: Class) { - val extensible = cls.newInstance() as Extensible//{} - extensible.setExts(mapOf("a" to 1, "b" to 2)) - kotlin.test.assertEquals(mapOf("a" to 1, "b" to 2), extensible.exts()) - } - -} \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/SwingUtilsTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/SwingUtilsTest.kt new file mode 100644 index 000000000..7e6d42568 --- /dev/null +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/SwingUtilsTest.kt @@ -0,0 +1,448 @@ +package com.itangcent.idea.utils + +import com.intellij.mock.MockApplication +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.wm.WindowManager +import com.intellij.ui.JBColor +import com.intellij.ui.components.JBList +import com.intellij.ui.table.JBTable +import com.itangcent.common.utils.cast +import com.itangcent.idea.swing.ActiveWindowProvider +import com.itangcent.idea.swing.MutableActiveWindowProvider +import com.itangcent.idea.swing.SimpleActiveWindowProvider +import com.itangcent.intellij.context.ActionContext +import com.itangcent.intellij.extend.guice.with +import com.itangcent.intellij.extend.withBoundary +import com.itangcent.mock.BaseContextTest +import com.itangcent.utils.WaitHelper +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.condition.DisabledIf +import org.junit.jupiter.api.condition.EnabledOnOs +import org.junit.jupiter.api.condition.OS +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import java.awt.* +import java.awt.event.MouseEvent +import javax.swing.* +import javax.swing.table.DefaultTableModel +import javax.swing.tree.DefaultMutableTreeNode +import javax.swing.tree.DefaultTreeModel +import javax.swing.tree.TreeModel +import javax.swing.tree.TreePath + +/** + * Test for [com.itangcent.idea.utils.SwingUtils]. + * + * @author tangcent + */ +@DisabledIf("headLess") +@EnabledOnOs(value = [OS.LINUX], disabledReason = "Only for Linux") +@Suppress("UndesirableClassUsage") +class SwingUtilsTest : BaseContextTest() { + + companion object { + @JvmStatic + fun headLess(): Boolean { + return GraphicsEnvironment.isHeadless() + } + } + + override fun bind(builder: ActionContext.ActionContextBuilder) { + super.bind(builder) + builder.bind(ActiveWindowProvider::class) { + it.with(SimpleActiveWindowProvider::class) + } + } + + @Test + fun testFocus() { + // Create a Dialog and set its focus. + val dialog = Dialog(null as Window?, "Test Dialog") + dialog.isVisible = true + actionContext.withBoundary { + SwingUtils.focus(dialog) + } + WaitHelper.waitUtil(10000) { + KeyboardFocusManager.getCurrentKeyboardFocusManager().focusedWindow != null + } + assertEquals(dialog, KeyboardFocusManager.getCurrentKeyboardFocusManager().focusedWindow) + dialog.dispose() + } + + @Test + fun testExpandOrCollapseNode() { + // Create a JTree with some nodes. + val rootNode = DefaultMutableTreeNode("Root") + val child1 = DefaultMutableTreeNode("Child 1") + rootNode.add(child1) + val child2 = DefaultMutableTreeNode("Child 2") + rootNode.add(child2) + val grandChild1 = DefaultMutableTreeNode("Grandchild 1") + child1.add(grandChild1) + val grandChild2 = DefaultMutableTreeNode("Grandchild 2") + child1.add(grandChild2) + val treeModel = DefaultTreeModel(rootNode) + val tree = JTree(treeModel) + + // Collapse all nodes. + SwingUtils.expandOrCollapseNode(tree, expanded = false) + assertFalse(tree.isExpanded(TreePath(child1.path))) + assertFalse(tree.isExpanded(TreePath(child2.path))) + assertFalse(tree.isExpanded(TreePath(grandChild1.path))) + assertFalse(tree.isExpanded(TreePath(grandChild2.path))) + + // Expand all nodes. + SwingUtils.expandOrCollapseNode(tree, expanded = true) + assertTrue(tree.isExpanded(TreePath(child1.path))) + assertFalse(tree.isExpanded(TreePath(child2.path))) + assertFalse(tree.isExpanded(TreePath(grandChild1.path))) + assertFalse(tree.isExpanded(TreePath(grandChild2.path))) + } + + @Test + fun testUnderLine() { + val parent = JPanel() + parent.background = JBColor.WHITE + // Create a JLabel and underline its text. + val label = JLabel("Test Label") + parent.add(label) + label.foreground = UIManager.getColor("activeCaption") + SwingUtils.underLine(label) + //assertEquals(BorderFactory.createMatteBorder(0, 0, 1, 0, label.foreground), label.border) + assertEquals(label.parent.background, label.background) + } + + @Test + fun testImmersed() { + val parent = JPanel() + parent.background = JBColor.WHITE + // Create a JLabel and remove its border. + val label = JLabel("Test Label") + parent.add(label) + label.border = BorderFactory.createMatteBorder(1, 1, 1, 1, label.foreground) + label.foreground = UIManager.getColor("activeCaption") + SwingUtils.immersed(label) + //assertEquals(BorderFactory.createMatteBorder(0, 0, 0, 0, label.foreground), label.border) + assertEquals(JBColor.WHITE, label.background) + } + + @Test + fun testCenterWindow() { + // Create a JFrame and center it. + val frame = JFrame() + frame.setSize(200, 200) + SwingUtils.centerWindow(frame) + assertEquals(Toolkit.getDefaultToolkit().screenSize.width / 2 - frame.width / 2, frame.x) + assertEquals(Toolkit.getDefaultToolkit().screenSize.height / 2 - frame.height / 2, frame.y) + frame.dispose() + } + + @Test + fun testPreferableWindow() { + val disposable = mock() + val mockApplication = MockApplication(disposable) + ApplicationManager.setApplication(mockApplication, disposable) + + val window = mock() + val windowManager = mock { + on(it.suggestParentWindow(any())).thenReturn(null, window) + } + mockApplication.registerService(WindowManager::class.java, windowManager) + + // Test with a null context. + assertNull(SwingUtils.preferableWindow()) + assertEquals(window, SwingUtils.preferableWindow()) + + // Test with a mock ActiveWindowProvider. + val window2 = mock() + actionContext.instance(ActiveWindowProvider::class).cast(MutableActiveWindowProvider::class)!! + .setActiveWindow(window2) + assertEquals(window2, SwingUtils.preferableWindow()) + } + + @Test + fun testIsDoubleClick() { + // Test with a null MouseEvent. + assertFalse(null.isDoubleClick()) + + val label = JLabel("Test Label") + + // Test with a single-click MouseEvent. + val singleClickEvent = MouseEvent(label, 0, 0, 0, 0, 0, 1, false) + assertFalse(singleClickEvent.isDoubleClick()) + + // Test with a double-click MouseEvent. + val doubleClickEvent = MouseEvent(label, 0, 0, 0, 0, 0, 2, false, MouseEvent.BUTTON1) + assertTrue(doubleClickEvent.isDoubleClick()) + + // Test with a double-click-right MouseEvent. + val doubleClickRightEvent = MouseEvent(label, 0, 0, 0, 0, 0, 2, false, MouseEvent.BUTTON2) + assertFalse(doubleClickRightEvent.isDoubleClick()) + } + + @Test + fun testFindColumn() { + val table = JBTable( + DefaultTableModel( + arrayOf( + arrayOf("A", "B", "C"), + arrayOf("1", "2", "3") + ), + arrayOf("Column 1", "Column 2", "Column 3") + ) + ) + assertEquals("Column 1", table.findColumn(0)?.identifier) + assertEquals("Column 2", table.findColumn(1)?.identifier) + assertEquals("Column 3", table.findColumn(2)?.identifier) + assertNull(table.findColumn(3)) + } + + @Test + fun testReloadTreeModel() { + val root = DefaultMutableTreeNode("Root") + val child = DefaultMutableTreeNode("Child") + val model: TreeModel = DefaultTreeModel(root) + root.add(child) + + model.reload() + assertEquals(root, model.root) + assertEquals(1, model.getChildCount(root)) + + model.reload(child) + assertEquals(root, model.root) + assertEquals(1, model.getChildCount(root)) + } + + @Test + fun testClearTreeModel() { + val root = DefaultMutableTreeNode("Root") + val child1 = DefaultMutableTreeNode("Child1") + val child2 = DefaultMutableTreeNode("Child2") + val grandChild1 = DefaultMutableTreeNode("Grandchild1") + val grandChild2 = DefaultMutableTreeNode("Grandchild2") + child1.add(grandChild1) + child1.add(grandChild2) + val model = DefaultTreeModel(root) + root.add(child1) + root.add(child2) + + assertEquals(2, model.getChildCount(root)) + assertEquals(2, model.getChildCount(child1)) + + model.clear(child1) + assertEquals(2, model.getChildCount(root)) + assertEquals(0, model.getChildCount(child1)) + + model.clear() + assertEquals(0, model.getChildCount(root)) + assertEquals(0, model.getChildCount(child1)) + } + + @Test + fun testRemoveFromTreeModel() { + val root = DefaultMutableTreeNode("Root") + val child = DefaultMutableTreeNode("Child") + val model = DefaultTreeModel(root) + root.add(child) + + model.remove(child) + assertEquals(0, model.getChildCount(root)) + } + + @Test + fun testInitAfterShown() { + val frame = JFrame() + frame.isVisible = true + frame.initAfterShown { + assertTrue(frame.isVisible) + } + frame.isVisible = false + } + + @Test + fun testOnResized() { + val frame = JFrame() + var called = 0 + + frame.onResized { + called += 1 + } + + called = 0 + frame.isVisible = true + WaitHelper.waitUtil(10000) { + called > 0 + } + assertTrue(called > 0) + + called = 0 + frame.setSize(100, 100) + WaitHelper.waitUtil(10000) { + called > 0 + } + assertTrue(called > 0) + + called = 0 + frame.location = Point(0, 0) + WaitHelper.waitUtil(10000) { + called > 0 + } + assertTrue(called > 0) + + called = 0 + frame.isVisible = false + WaitHelper.waitUtil(10000) { + called > 0 + } + assertTrue(called > 0) + + frame.dispose() + } + + @Test + fun testMinusHeight() { + val panel = JPanel() + panel.setSize(100, 100) + val component1 = JLabel("Component 1") + val component2 = JLabel("Component 2") + val component3 = JLabel("Component 3") + val margin = 10 + + panel.add(component1) + panel.add(component2) + panel.add(component3) + + assertEquals( + 100 - component1.height - component2.height - margin * 2, + panel.minusHeight(margin, component1, component2) + ) + assertEquals( + 100 - component1.height - component2.height - component3.height - margin * 3, + panel.minusHeight(margin, component1, component2, component3) + ) + } + + @Test + fun testBottomAlignTo() { + val frame = JFrame() + val component1 = JLabel("Component 1") + val component2 = JLabel("Component 2") + + frame.add(component1) + frame.add(component2) + component1.bottomAlignTo(component2) + + assertEquals(component2.location.y + component2.height, component1.height) + } + + @Test + fun testSetSizeIfNecessary() { + val panel = JPanel() + panel.setSize(100, 100) + + panel.setSizeIfNecessary(100, 100) + assertEquals(100, panel.width) + assertEquals(100, panel.height) + + panel.setSizeIfNecessary(200, 200) + assertEquals(200, panel.width) + assertEquals(200, panel.height) + } + + @Test + fun testAdjustWithIfNecessary() { + val panel = JPanel() + panel.setSize(100, 100) + + panel.adjustWithIfNecessary(10) + assertEquals(110, panel.width) + assertEquals(100, panel.height) + + panel.adjustWithIfNecessary(10) + assertEquals(120, panel.width) + assertEquals(100, panel.height) + } + + @Test + fun testVisit() { + val panel = JPanel() + val component1 = JLabel("Component 1") + val component2 = JLabel("Component 2") + val component3 = JLabel("Component 3") + + panel.add(component1) + panel.add(component2) + component2.add(component3) + + var count = 0 + panel.visit { + count++ + } + + assertEquals(3, count) + } + + @Test + fun testGetModelElements() { + val list = JList(arrayOf("Item 1", "Item 2", "Item 3")) + val elements = list.getModelElements().toList() + + assertEquals(list.model.size, elements.size) + assertEquals(list.model.getElementAt(0), elements[0]) + assertEquals(list.model.getElementAt(1), elements[1]) + assertEquals(list.model.getElementAt(2), elements[2]) + } + + @Test + fun testGetMutableComboBoxModel() { + + val list = JBList(arrayOf("A", "B", "C")) + assertNull(list.getMutableComboBoxModel()) + + val dataModel = DefaultComboBoxModel() + val comboBoxJBList = JBList(dataModel) + assertNotNull(comboBoxJBList.getMutableComboBoxModel()) + + val model = comboBoxJBList.getMutableComboBoxModel() + assertNotNull(model) + model!!.addElement("Item") + assertEquals(1, model.size) + } + + @Test + fun testAddElement() { + val comboBox = DefaultComboBoxModel() + val comboBoxJBList = JBList(comboBox) + comboBoxJBList.addElement("A") + + assertEquals(1, comboBox.size) + assertEquals("A", comboBox.getElementAt(0)) + + comboBoxJBList.addElement("B") + assertEquals(2, comboBox.size) + assertEquals("B", comboBox.getElementAt(1)) + } + + @Test + fun testRemoveElementAt() { + val comboBox = DefaultComboBoxModel(arrayOf("A", "B", "C")) + val comboBoxJBList = JBList(comboBox) + assertEquals(3, comboBox.size) + assertEquals("A", comboBox.getElementAt(0)) + assertEquals("B", comboBox.getElementAt(1)) + comboBoxJBList.removeElementAt(1) + + assertEquals(2, comboBox.size) + assertEquals("A", comboBox.getElementAt(0)) + assertEquals("C", comboBox.getElementAt(1)) + + assertDoesNotThrow { + comboBoxJBList.removeElementAt(-1) + } + assertDoesNotThrow { + comboBoxJBList.removeElementAt(999) + } + } +} \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/intellij/extend/ActionContextKitTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/intellij/extend/ActionContextKitTest.kt index 7ca721f43..bcbc3187b 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/intellij/extend/ActionContextKitTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/intellij/extend/ActionContextKitTest.kt @@ -1,9 +1,19 @@ package com.itangcent.intellij.extend +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.psi.PsiMethod +import com.itangcent.intellij.context.ActionContext +import com.itangcent.intellij.context.ThreadFlag import com.itangcent.mock.BaseContextTest +import com.itangcent.testFramework.PluginContextLightCodeInsightFixtureTestCase +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.mockito.kotlin.mock import java.util.concurrent.TimeoutException +import java.util.concurrent.atomic.AtomicInteger +import kotlin.concurrent.thread import kotlin.test.assertEquals /** @@ -11,41 +21,219 @@ import kotlin.test.assertEquals */ internal class ActionContextKitTest : BaseContextTest() { - @Volatile - private var x = 0 - @Test - fun tryRunAsync() { + fun `test tryRunAsync`() { + var ran = false + + // Test with a null context null.tryRunAsync { - x = 1 + Thread.sleep(500) + ran = true } - assertEquals(1, x) + assertTrue(ran) + + // Test with a no-null context + ran = false actionContext.tryRunAsync { - x = 2 + Thread.sleep(500) + ran = true } - actionContext.waitComplete() - assertEquals(2, x) + assertFalse(ran) + Thread.sleep(1000) + assertTrue(ran) } @Test - fun callWithTimeout() { + fun `test callWithTimeout`() { + // Test with a long-running task assertThrows { - actionContext.callWithTimeout(500) { + actionContext.callWithTimeout(1000) { Thread.sleep(2000) - logger.info("timeout") - x = 1 + "Hello world" } } + + // Test with a short task + val result2 = actionContext.callWithTimeout(1000) { + "Hello world" + } + assertEquals("Hello world", result2) // The result should be "Hello world" + + assertThrows { + actionContext.callWithTimeout(500) { + Thread.sleep(1000) + throw IllegalArgumentException() + } + } + assertThrows { - actionContext.callWithTimeout(500) { + actionContext.callWithTimeout(1500) { + Thread.sleep(1000) + throw IllegalArgumentException() + } + } + } + + @Test + fun `test withBoundary`() { + var ran = false + + actionContext.withBoundary { + actionContext.runAsync { + ran = true + } + } + + assertTrue(ran) + } + + @Test + fun `test notReentrant`() { + var ran = false + + actionContext.notReentrant("test") { + ran = true + } + assertTrue(ran) + + // Test with a recursive call + ran = false + actionContext.notReentrant("test") { + actionContext.notReentrant("test") { + ran = true + } + } + assertFalse(ran) // The inner action should not have run because it was recursive + } + + @Test + fun `test callWithBoundary`() { + val times = AtomicInteger() + val result = actionContext.callWithBoundary { + repeat(10) { + actionContext.runAsync { + Thread.sleep(100) + times.getAndIncrement() + } + } + times.get() + }!! + assertTrue(10 > result) + assertEquals(10, times.get()) + } + + @Test + fun `test callWithBoundary with error`() { + assertDoesNotThrow { + actionContext.callWithBoundary { throw IllegalArgumentException() } } - assertEquals(2, actionContext.callWithTimeout(2000) { + } + + @Test + fun `test withBoundary with timeout`() { + var ran = false + + actionContext.withBoundary(1000) { Thread.sleep(500) - x = 2 - x - }) - assertEquals(2, x) + ran = true + } + assertTrue(ran) + + ran = false + actionContext.withBoundary(1000) { + actionContext.runAsync { + Thread.sleep(500) + ran = true + } + } + assertTrue(ran) + + // Test with a long-running sync task + ran = false + actionContext.withBoundary(1000) { + Thread.sleep(2000) + ran = true + } + assertTrue(ran) + + // Test with a long-running async task + ran = false + actionContext.withBoundary(1000) { + actionContext.runAsync { + Thread.sleep(2000) + ran = true + } + } + assertFalse(ran) + } + + @Test + fun `test withBoundary with error`() { + assertThrows { + actionContext.withBoundary { + throw IllegalArgumentException() + } + } + assertDoesNotThrow { + actionContext.withBoundary(1000) { + throw IllegalArgumentException() + } + } + } + + @Test + fun `test runWithContext`() { + var inContext = false + + actionContext.runWithContext { + inContext = ActionContext.getContext() != null + } + assertTrue(inContext) + + // Test with no context + inContext = false + actionContext.withBoundary { + thread { + actionContext.runWithContext { + inContext = ActionContext.getContext() != null + } + }.join() + } + assertTrue(inContext) // The action should have run asynchronously because there was no context + } + + @Test + fun `test runInNormalThread`() = actionContext.withBoundary { + actionContext.runInNormalThread { + assertEquals(ThreadFlag.ASYNC.value, ActionContext.getFlag()) + } + actionContext.runInSwingUI { + actionContext.runInNormalThread { + assertEquals(ThreadFlag.ASYNC.value, ActionContext.getFlag()) + } + } + actionContext.runInNormalThread { + actionContext.runInNormalThread { + assertEquals(ThreadFlag.ASYNC.value, ActionContext.getFlag()) + } + } + } + +} + +internal class PsiActionContextKitTest : PluginContextLightCodeInsightFixtureTestCase() { + + override fun bind(builder: ActionContext.ActionContextBuilder) { + builder.bindInstance(DataContext::class, mock()) + } + + fun `test findCurrentMethod`() { + val psiElement = mock { + on(it.context).thenReturn(it) + } + actionContext.cache(CommonDataKeys.PSI_ELEMENT.name, psiElement) + kotlin.test.assertEquals(psiElement, actionContext.findCurrentMethod()) } } \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/order/OrderedTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/order/OrderedTest.kt index bbe7e2bc9..d96f67462 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/order/OrderedTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/order/OrderedTest.kt @@ -5,7 +5,7 @@ import kotlin.test.assertEquals /** - * Test case ot [Ordered] + * Test case for [Ordered] */ internal class OrderedTest { @@ -15,6 +15,37 @@ internal class OrderedTest { assertEquals(Ordered.LOWEST_PRECEDENCE, LowestOrderedBean().order()) assertEquals(Ordered.DEFAULT_PRECEDENCE, UnOrderedBean().order()) } + + data class MyObject(val name: String, val orderValue: Int) : Ordered { + override fun order(): Int { + return orderValue + } + } + + @Test + fun testOrdering() { + val objects = listOf( + "Hello", + 42, + MyObject("Object A", 10), + MyObject("Object B", 5), + MyObject("Object C", 20), + MyObject("Object D", Ordered.HIGHEST_PRECEDENCE), + MyObject("Object E", Ordered.LOWEST_PRECEDENCE) + ) + + // Sort the list using the order function + val sortedObjects = objects.sortedBy { it.order() } + + // Check that the objects are sorted in the expected order + assertEquals("Object D", (sortedObjects[0] as MyObject).name) + assertEquals("Hello", sortedObjects[1]) + assertEquals(42, sortedObjects[2]) + assertEquals("Object B", (sortedObjects[3] as MyObject).name) + assertEquals("Object A", (sortedObjects[4] as MyObject).name) + assertEquals("Object C", (sortedObjects[5] as MyObject).name) + assertEquals("Object E", (sortedObjects[6] as MyObject).name) + } } class HighestOrderedBean : Ordered { diff --git a/idea-plugin/src/test/kotlin/com/itangcent/suv/http/AbstractHttpClientProviderTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/suv/http/AbstractHttpClientProviderTest.kt new file mode 100644 index 000000000..85faca675 --- /dev/null +++ b/idea-plugin/src/test/kotlin/com/itangcent/suv/http/AbstractHttpClientProviderTest.kt @@ -0,0 +1,67 @@ +package com.itangcent.suv.http + +import com.itangcent.http.ApacheHttpClient +import com.itangcent.http.HttpClient +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicReference + +/** + * Test case for [AbstractHttpClientProvider] + * + * @author tangcent + */ +class AbstractHttpClientProviderTest { + + @Test + fun `test getHttpClient`() { + val provider = object : AbstractHttpClientProvider() { + override fun buildHttpClient(): HttpClient { + return ApacheHttpClient() + } + } + + val numThreads = 10 + val latch = CountDownLatch(numThreads) + val httpClientRef = AtomicReference() + + // Create and start multiple threads that call getHttpClient() concurrently + for (i in 1..numThreads) { + Thread { + val httpClient = provider.getHttpClient() + if (httpClientRef.get() == null) { + httpClientRef.set(httpClient) + } else { + assertEquals(httpClientRef.get(), httpClient) + } + latch.countDown() + }.start() + } + + // Wait for all threads to finish + latch.await() + + // Verify that only one instance of the HttpClient was created + assertNotNull(httpClientRef.get()) + } + + @Test + fun `test httpClientInstance is accessible`() { + val testProvider = object : AbstractHttpClientProvider() { + fun updateHttpClientInstance(httpClient: HttpClient) { + httpClientInstance = httpClient + } + + override fun buildHttpClient(): HttpClient { + return ApacheHttpClient() + } + } + val httpClient = testProvider.getHttpClient() + assertNotNull(httpClient) + val newHttpClient = ApacheHttpClient() + testProvider.updateHttpClientInstance(newHttpClient) + assertEquals(newHttpClient, testProvider.getHttpClient()) + } +} \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/suv/http/ConfigurableHttpClientProviderTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/suv/http/ConfigurableHttpClientProviderTest.kt index 2928b0abe..4eb5aba13 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/suv/http/ConfigurableHttpClientProviderTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/suv/http/ConfigurableHttpClientProviderTest.kt @@ -1,9 +1,10 @@ package com.itangcent.suv.http -import com.itangcent.idea.plugin.settings.SettingBinder -import com.itangcent.idea.plugin.settings.Settings -import com.itangcent.intellij.context.ActionContext -import com.itangcent.mock.SettingBinderAdaptor +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue /** * Test case of [ConfigurableHttpClientProvider] @@ -12,16 +13,108 @@ internal class ConfigurableHttpClientProviderTest : HttpClientProviderTest() { override val httpClientProviderClass get() = ConfigurableHttpClientProvider::class - override fun bind(builder: ActionContext.ActionContextBuilder) { - super.bind(builder) - builder.bind(SettingBinder::class) { - it.toInstance(SettingBinderAdaptor(Settings().also { settings -> - settings.trustHosts = arrayOf("https://www.apache.org") - })) - } + override fun customConfig(): String { + return "http.call.before=groovy:logger.info(\"call:\"+request.url())\nhttp.call.after=groovy:logger.info(\"response:\"+response.string())\nhttp.timeOut=3" + } + + @Test + fun `test buildHttpClient`() { + // Build an instance of HttpClient using the provider. + val httpClient = httpClientProvider.getHttpClient() + + // Assert that the HttpClient instance is not null. + Assertions.assertNotNull(httpClient) + + // Assert that the HttpClient instance is an instance of HttpClientWrapper. + Assertions.assertEquals( + "com.itangcent.suv.http.ConfigurableHttpClientProvider.HttpClientWrapper", + httpClient::class.qualifiedName + ) } + @Test + fun `test callForbiddenRequest`() { + + // Build an instance of HttpClient using the provider. + val httpClient = httpClientProvider.getHttpClient() + + // Create an instance of HttpRequest using the HttpClient instance. + val httpRequest = httpClient.request() + .method("GET") + .url("http://forbidden.com") + + // Send the request and receive the response. + val emptyHttpResponse = httpRequest.call() + + // Assert that the response code is 404. + assertEquals(404, emptyHttpResponse.code()) + + // Assert that the response headers are null. + assertNull(emptyHttpResponse.headers()) + + // Assert that the response headers for a specific name are null. + assertNull(emptyHttpResponse.headers("Content-Type")) + + // Assert that the response string is null. + assertNull(emptyHttpResponse.string()) + + // Assert that the response string with a specific charset is null. + assertNull(emptyHttpResponse.string(Charsets.UTF_8)) + + // Assert that the response stream is empty. + assertTrue(emptyHttpResponse.stream().readBytes().isEmpty()) + + // Assert that the response content type is null. + assertNull(emptyHttpResponse.contentType()) + + // Assert that the response bytes are null. + assertNull(emptyHttpResponse.bytes()) + + // Assert that the response does not contain a specific header. + Assertions.assertFalse(emptyHttpResponse.containsHeader("Content-Type")) + + // Assert that the first header for a specific name is null. + assertNull(emptyHttpResponse.firstHeader("Content-Type")) + + // Assert that the last header for a specific name is null. + assertNull(emptyHttpResponse.lastHeader("Content-Type")) + + // Assert that the response request is the same as the sample request. + assertEquals(httpRequest, emptyHttpResponse.request()) + } +} + + +internal class NonConfigConfigurableHttpClientProviderTest : HttpClientProviderTest() { + + override val httpClientProviderClass get() = ConfigurableHttpClientProvider::class + + @Test + fun `test buildHttpClient`() { + // Build an instance of HttpClient using the provider. + val httpClient = httpClientProvider.getHttpClient() + + // Assert that the HttpClient instance is not null. + Assertions.assertNotNull(httpClient) + } +} + +internal class IllegalConfigConfigurableHttpClientProviderTest : HttpClientProviderTest() { + + override val httpClientProviderClass get() = ConfigurableHttpClientProvider::class + override fun customConfig(): String { - return "http.call.before=groovy:logger.info(\"call:\"+request.url())\nhttp.call.after=groovy:logger.info(\"response:\"+response.string())\nhttp.timeOut=3" + return "http.timeOut=illegal" } -} \ No newline at end of file + + @Test + fun `test buildHttpClient`() { + // Build an instance of HttpClient using the provider. + val httpClient = httpClientProvider.getHttpClient() + + // Assert that the HttpClient instance is not null. + Assertions.assertNotNull(httpClient) + } +} + + diff --git a/idea-plugin/src/test/kotlin/com/itangcent/suv/http/HttpClientProviderTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/suv/http/HttpClientProviderTest.kt index f19df4666..3494442b4 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/suv/http/HttpClientProviderTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/suv/http/HttpClientProviderTest.kt @@ -1,12 +1,26 @@ package com.itangcent.suv.http import com.google.inject.Inject -import com.itangcent.mock.AdvancedContextTest +import com.itangcent.common.utils.DateUtils +import com.itangcent.common.utils.notNullOrBlank +import com.itangcent.http.* +import com.itangcent.idea.plugin.settings.SettingBinder +import com.itangcent.idea.plugin.settings.Settings import com.itangcent.intellij.context.ActionContext import com.itangcent.intellij.extend.guice.with +import com.itangcent.mock.AdvancedContextTest +import com.itangcent.mock.SettingBinderAdaptor +import org.apache.http.entity.ContentType +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow import kotlin.reflect.KClass import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertSame +import kotlin.test.assertTrue /** * Test case of [HttpClientProvider] @@ -14,21 +28,411 @@ import kotlin.test.assertEquals internal abstract class HttpClientProviderTest : AdvancedContextTest() { @Inject - private lateinit var httpClientProvider: HttpClientProvider + protected lateinit var httpClientProvider: HttpClientProvider abstract val httpClientProviderClass: KClass override fun bind(builder: ActionContext.ActionContextBuilder) { super.bind(builder) builder.bind(HttpClientProvider::class) { it.with(httpClientProviderClass) } + builder.bind(SettingBinder::class) { + it.toInstance(SettingBinderAdaptor(Settings().also { settings -> + settings.trustHosts = arrayOf( + "https://jsonplaceholder.typicode.com", + "!http://forbidden.com" + ) + })) + } + } + + @Test + fun `test callHttpGetRequest`() { + // Build an instance of HttpClient using the provider. + val httpClient = httpClientProvider.getHttpClient() + + // Create an instance of HttpRequest using the HttpClient instance. + val httpRequest = httpClient.request() + .method("GET") + .url("https://jsonplaceholder.typicode.com/todos/1") + + // Send the request and receive the response. + val httpResponse = httpRequest.call() + + // Assert that the response is not null. + assertNotNull(httpResponse) + + // Assert that the response has a status code of 200. + assertEquals(200, httpResponse.code()) + + // Assert that the response has a non-empty entity. + assertNotNull(httpResponse.bytes()) + } + + @Test + fun `test callHttpPostRequest`() { + // Build an instance of HttpClient using the provider. + val httpClient = httpClientProvider.getHttpClient() + + // Create an instance of HttpRequest using the HttpClient instance. + val httpRequest = httpClient.request() + .method("POST") + .url("https://jsonplaceholder.typicode.com/posts") + .contentType(ContentType.TEXT_PLAIN.mimeType) + .body("Hello, world!") + + // Send the request and receive the response. + val httpResponse = httpRequest.call() + + // Assert that the response is not null. + assertNotNull(httpResponse) + + // Assert that the response has a status code of 201. + assertEquals(201, httpResponse.code()) + + // Assert that the response has a non-empty entity. + assertNotNull(httpResponse.bytes()) + + // Assert that the response entity contains the expected text. + assertTrue(httpResponse.string().notNullOrBlank()) + } + + @Test + fun `test callHttpPutRequest`() { + // Build an instance of HttpClient using the provider. + val httpClient = httpClientProvider.getHttpClient() + + // Create an instance of HttpRequest using the HttpClient instance. + val httpRequest = httpClient.request() + .method("PUT") + .url("https://jsonplaceholder.typicode.com/posts/1") + .contentType(ContentType.TEXT_PLAIN.mimeType) + .body("Hello, world!") + + // Send the request and receive the response. + val httpResponse = httpRequest.call() + + // Assert that the response is not null. + assertNotNull(httpResponse) + + // Assert that the response has a status code of 200. + assertEquals(200, httpResponse.code()) + + // Assert that the response has a non-empty entity. + assertNotNull(httpResponse.bytes()) + + // Assert that the response entity contains the expected text. + assertTrue(httpResponse.string().notNullOrBlank()) + } + + @Test + fun `test callHttpDeleteRequest`() { + // Build an instance of HttpClient using the provider. + val httpClient = httpClientProvider.getHttpClient() + + // Create an instance of HttpRequest using the HttpClient instance. + val httpRequest = httpClient.request() + .method("DELETE") + .url("https://jsonplaceholder.typicode.com/posts/1") + + // Send the request and receive the response. + val httpResponse = httpRequest.call() + + // Assert that the response is not null. + assertNotNull(httpResponse) + + // Assert that the response has a status code of 200. + assertEquals(200, httpResponse.code()) + + // Assert that the response has a non-empty entity. + assertNotNull(httpResponse.bytes()) + + // Assert that the response entity contains the expected text. + assertEquals("{}", httpResponse.string()) + } + + @Test + fun testMethods() { + val httpClient = httpClientProvider.getHttpClient() + + //GET + httpClient.get().url("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("GET", it.method()) + } + httpClient.get("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("GET", it.method()) + } + + //POST + httpClient.post().url("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("POST", it.method()) + } + httpClient.post("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("POST", it.method()) + } + + //PUT + httpClient.put().url("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("PUT", it.method()) + } + httpClient.put("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("PUT", it.method()) + } + + //DELETE + httpClient.delete().url("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("DELETE", it.method()) + } + httpClient.delete("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("DELETE", it.method()) + } + + //OPTIONS + httpClient.options().url("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("OPTIONS", it.method()) + } + httpClient.options("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("OPTIONS", it.method()) + } + + //TRACE + httpClient.trace().url("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("TRACE", it.method()) + } + httpClient.trace("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("TRACE", it.method()) + } + + //PATCH + httpClient.patch().url("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("PATCH", it.method()) + } + httpClient.patch("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("PATCH", it.method()) + } + + //HEAD + httpClient.head().url("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("HEAD", it.method()) + } + httpClient.head("https://github.com/tangcent/easy-yapi/pulls").let { + assertEquals("https://github.com/tangcent/easy-yapi/pulls", it.url()) + assertEquals("HEAD", it.method()) + } + + } + + @Test + fun testHeaders() { + val httpClient = httpClientProvider.getHttpClient() + val request = httpClient.request() + + assertFalse(request.containsHeader("x-token")) + assertNull(request.headers("x-token")) + assertNull(request.firstHeader("x-token")) + assertNull(request.lastHeader("x-token")) + + assertDoesNotThrow { request.removeHeaders("x-token") } + assertDoesNotThrow { request.removeHeader("x-token", "222222") } + + assertFalse(request.containsHeader("x-token")) + assertNull(request.headers("x-token")) + assertNull(request.firstHeader("x-token")) + assertNull(request.lastHeader("x-token")) + + request.header("x-token", "111111") + assertTrue(request.containsHeader("x-token")) + Assertions.assertArrayEquals(arrayOf("111111"), request.headers("x-token")) + assertEquals("111111", request.firstHeader("x-token")) + assertEquals("111111", request.lastHeader("x-token")) + + request.header(BasicHttpHeader("x-token", null)) + request.header(BasicHttpHeader("x-token", "222222")) + assertTrue(request.containsHeader("x-token")) + Assertions.assertArrayEquals(arrayOf("111111", "222222"), request.headers("x-token")) + assertEquals("111111", request.firstHeader("x-token")) + assertEquals("222222", request.lastHeader("x-token")) + + request.removeHeader("x-token", "222222") + assertTrue(request.containsHeader("x-token")) + Assertions.assertArrayEquals(arrayOf("111111"), request.headers("x-token")) + assertEquals("111111", request.firstHeader("x-token")) + assertEquals("111111", request.lastHeader("x-token")) + + request.removeHeaders("x-token") + assertFalse(request.containsHeader("x-token")) + + request.header("x-token", "111111") + request.setHeader("x-token", "222222") + Assertions.assertArrayEquals(arrayOf("222222"), request.headers("x-token")) + assertEquals("222222", request.firstHeader("x-token")) + assertEquals("222222", request.lastHeader("x-token")) + } + + @Test + fun testQuery() { + val httpClient = httpClientProvider.getHttpClient() + val request = httpClient.request() + assertNull(request.querys()) + request.query("q", "test") + kotlin.test.assertNotNull(request.querys()) } @Test - fun buildHttpClient() { - val response = httpClientProvider.getHttpClient() - .get("https://www.apache.org/licenses/LICENSE-1.1") - .call() - assertEquals(200, response.code()) - logger.info("call LICENSE-1.1:[${response.code()}]\n${response.string()}") + fun testBody() { + val httpClient = httpClientProvider.getHttpClient() + val request = httpClient.request() + assertNull(request.body()) + request.body("body") + assertEquals("body", request.body()) + request.body(1) + assertEquals(1, request.body()) + } + + @Test + fun testContentType() { + val httpClient = httpClientProvider.getHttpClient() + val request = httpClient.request() + assertNull(request.contentType()) + request.contentType("application/json") + assertEquals("application/json", request.contentType()) + assertEquals("application/json", request.firstHeader("content-type")) + request.contentType(ContentType.IMAGE_PNG) + assertEquals("image/png", request.contentType()) + assertEquals("image/png", request.firstHeader("content-type")) + } + + @Test + fun testParams() { + val httpClient = httpClientProvider.getHttpClient() + val request = httpClient.request() + assertFalse(request.containsParam("auth")) + assertNull(request.params("auth")) + assertNull(request.firstParam("auth")) + assertNull(request.lastParam("auth")) + + request.param("auth", "111111") + assertTrue(request.containsParam("auth")) + Assertions.assertArrayEquals(arrayOf("111111"), request.paramValues("auth")) + assertEquals("111111", request.firstParamValue("auth")) + assertEquals("111111", request.lastParamValue("auth")) + request.firstParam("auth")?.let { + assertEquals("auth", it.name()) + assertEquals("111111", it.value()) + assertEquals("text", it.type()) + } + + request.param("token", "xxxxx") + request.param("auth", null) + request.fileParam("auth", "222222") + assertTrue(request.containsParam("auth")) + Assertions.assertArrayEquals(arrayOf("111111", "222222"), request.paramValues("auth")) + assertEquals("111111", request.firstParamValue("auth")) + assertEquals("222222", request.lastParamValue("auth")) + request.lastParam("auth")?.let { + assertEquals("auth", it.name()) + assertEquals("222222", it.value()) + assertEquals("file", it.type()) + } + } + + @Test + fun testCookies() { + val httpClient = httpClientProvider.getHttpClient() + val cookieStore = httpClient.cookieStore() + assertTrue(cookieStore.cookies().isEmpty()) + + val token = cookieStore.newCookie() + token.setName("token") + token.setValue("111111") + token.setExpiryDate(DateUtils.parse("2021-01-01").time) + token.setDomain("github.com") + token.setPorts(intArrayOf(9999)) + token.setComment("for auth") + token.setCommentURL("http://www.apache.org/licenses/LICENSE-2.0") + token.setSecure(false) + token.setPath("/") + token.setVersion(100) + assertTrue(token.isPersistent()) + + //add cookie which is expired + cookieStore.addCookie(token) + assertTrue(cookieStore.cookies().isEmpty()) + + token.setExpiryDate(DateUtils.parse("2099-01-01").time) + cookieStore.addCookie(token) + + val cookies = cookieStore.cookies() + assertEquals(1, cookies.size) + cookies.first().let { + assertEquals("token", it.getName()) + assertEquals("111111", it.getValue()) + assertEquals("github.com", it.getDomain()) + assertEquals("for auth", it.getComment()) + assertEquals("http://www.apache.org/licenses/LICENSE-2.0", it.getCommentURL()) + assertEquals("/", it.getPath()) + assertEquals(100, it.getVersion()) + assertEquals(false, it.isSecure()) + assertEquals(DateUtils.parse("2099-01-01").time, it.getExpiryDate()) + + val fromJson = BasicCookie.fromJson(it.json()) + assertEquals("token", fromJson.getName()) + assertEquals("111111", fromJson.getValue()) + assertEquals("github.com", fromJson.getDomain()) + assertEquals("for auth", fromJson.getComment()) + assertEquals("http://www.apache.org/licenses/LICENSE-2.0", fromJson.getCommentURL()) + assertEquals("/", fromJson.getPath()) + assertEquals(100, fromJson.getVersion()) + assertEquals(false, fromJson.isSecure()) + assertEquals(DateUtils.parse("2099-01-01").time, fromJson.getExpiryDate()) + + val mutable = it.mutable() + assertSame(mutable, mutable.mutable()) + assertEquals("token", mutable.getName()) + assertEquals("111111", mutable.getValue()) + assertEquals("github.com", mutable.getDomain()) + assertEquals("for auth", mutable.getComment()) + assertEquals("http://www.apache.org/licenses/LICENSE-2.0", mutable.getCommentURL()) + assertEquals("/", mutable.getPath()) + assertEquals(100, mutable.getVersion()) + assertEquals(false, mutable.isSecure()) + assertEquals(DateUtils.parse("2099-01-01").time, mutable.getExpiryDate()) + + val str = it.toString() + assertTrue(str.contains("token")) + assertTrue(str.contains("111111")) + assertTrue(str.contains("github.com")) + } + + cookieStore.clear() + assertTrue(cookieStore.cookies().isEmpty()) + cookieStore.addCookies(cookies.toTypedArray()) + assertEquals(1, cookies.size) + + token.setPorts(null) + val apacheCookie = token.asApacheCookie() + assertNull(apacheCookie.commentURL) + assertTrue(apacheCookie.isPersistent) + + val packageApacheCookie = ApacheCookie(apacheCookie) + assertEquals("token", packageApacheCookie.getName()) + assertEquals("111111", packageApacheCookie.getValue()) + assertEquals("github.com", packageApacheCookie.getDomain()) + assertEquals("for auth", packageApacheCookie.getComment()) + assertTrue(packageApacheCookie.isPersistent()) } } \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/test/ActionContextKit.kt b/idea-plugin/src/test/kotlin/com/itangcent/test/ActionContextKit.kt index 478a365c6..d4d902427 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/test/ActionContextKit.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/test/ActionContextKit.kt @@ -14,9 +14,12 @@ fun com.itangcent.intellij.context.ActionContext.ActionContextBuilder.mock(type: @Suppress("UNCHECKED_CAST") fun com.itangcent.intellij.context.ActionContext.ActionContextBuilder.mock( type: KClass, - mock: (T) -> Unit + mock: KStubbing.(T) -> Unit ) { - this.bindInstance(type as KClass, Mockito.mock(type.java).also { mock(it as T) } as Any) + val mockCls = type.java + this.bindInstance(type as KClass, Mockito.mock(mockCls).also { + KStubbing(it).mock(it) + } as Any) } @Suppress("UNCHECKED_CAST") diff --git a/idea-plugin/src/test/kotlin/com/itangcent/utils/ExtensibleKitTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/utils/ExtensibleKitTest.kt index fd94abb3a..0eefb9886 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/utils/ExtensibleKitTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/utils/ExtensibleKitTest.kt @@ -1,52 +1,87 @@ package com.itangcent.utils -import com.itangcent.common.constant.Attrs -import com.itangcent.common.model.Header -import com.itangcent.idea.plugin.api.export.yapi.setExample +import com.itangcent.common.model.* +import com.itangcent.common.utils.Extensible +import com.itangcent.common.utils.SimpleExtensible import com.itangcent.utils.ExtensibleKit.fromJson import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource /** * Test case for [ExtensibleKit] */ -class ExtensibleKitTest { +class ExtensibleTest { + + class Person( + val name: String, + val age: Int + ) : SimpleExtensible() { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Person + + if (name != other.name) return false + if (age != other.age) return false + return exts() == other.exts() + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + age + result = 31 * result + exts().hashCode() + return result + } + } @Test - fun testFromJson() { - val acceptHeader = Header() - - acceptHeader.name = "Accept" - acceptHeader.value = "*/*" - acceptHeader.desc = "authentication" - acceptHeader.required = true - - assertEquals(acceptHeader, - Header::class.fromJson("{name: \"Accept\",value: \"*/*\",desc: \"authentication\",required:true}")) - assertEquals(acceptHeader, - Header::class.fromJson("{name: \"Accept\",value: \"*/*\",desc: \"authentication\",required:true, demo:\"token123\"}")) - - assertEquals(acceptHeader, - Header::class.fromJson("{name: \"Accept\",value: \"*/*\",desc: \"authentication\",required:true}", Attrs.DEMO_ATTR)) - assertNotEquals(acceptHeader, - Header::class.fromJson("{name: \"Accept\",value: \"*/*\",desc: \"authentication\",required:true, demo:\"token123\"}", Attrs.DEMO_ATTR)) - - acceptHeader.setExample("token123") - - assertNotEquals(acceptHeader, - Header::class.fromJson("{name: \"Accept\",value: \"*/*\",desc: \"authentication\",required:true}")) - assertNotEquals(acceptHeader, - Header::class.fromJson("{name: \"Accept\",value: \"*/*\",desc: \"authentication\",required:true, demo:\"token123\"}")) - - assertNotEquals(acceptHeader, - Header::class.fromJson("{name: \"Accept\",value: \"*/*\",desc: \"authentication\",required:true}", Attrs.DEMO_ATTR)) - assertEquals(acceptHeader, - Header::class.fromJson("{name: \"Accept\",value: \"*/*\",desc: \"authentication\",required:true, demo:\"token123\"}", Attrs.DEMO_ATTR)) - - //ext with '@' - assertEquals(acceptHeader, - Header::class.fromJson("{name: \"Accept\",value: \"*/*\",desc: \"authentication\",required:true, \"@demo\":\"token123\"}")) + fun `fromJson deserializes JSON into an Extensible object`() { + val json = """{ + "name": "John", + "age": 30, + "@ext1": "value1", + "@ext2": "value2" + }""" + val expected = Person("John", 30).apply { + setExt("@ext1", "value1") + setExt("@ext2", "value2") + } + val result = Person::class.fromJson(json) + assertEquals(expected, result) + } + + @Test + fun `fromJson deserializes JSON into an Extensible object with selected attributes`() { + val json = """{ + "name": "John", + "age": 30, + "@ext1": "value1", + "@ext2": "value2", + "ext3": "value3" + }""" + val expected = Person("John", 30).apply { + setExt("@ext1", "value1") + setExt("@ext3", "value3") + } + val result = Person::class.fromJson(json, "@ext1", "ext3") + assertEquals(expected, result) + } + + @ParameterizedTest + @ValueSource( + classes = [Doc::class, FormParam::class, + Header::class, MethodDoc::class, + Param::class, PathParam::class, + Request::class, Response::class + ] + ) + fun testSetExts(cls: Class) { + val extensible = cls.newInstance() as Extensible//{} + extensible.setExts(mapOf("a" to 1, "b" to 2)) + kotlin.test.assertEquals(mapOf("a" to 1, "b" to 2), extensible.exts()) } } \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/utils/FileKitKtTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/utils/FileKitKtTest.kt new file mode 100644 index 000000000..dd5b73111 --- /dev/null +++ b/idea-plugin/src/test/kotlin/com/itangcent/utils/FileKitKtTest.kt @@ -0,0 +1,26 @@ +package com.itangcent.utils + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.condition.EnabledOnOs +import org.junit.jupiter.api.condition.OS + +/** + * Test case for [FileKit] + */ +class FileKitKtTest { + @Test + @EnabledOnOs(OS.WINDOWS, disabledReason = "Only for Windows") + fun testLocalPath_windows() { + val input = "/foo/bar" + val expected = "\\foo\\bar" + assertEquals(expected, input.localPath()) + } + + @Test + @EnabledOnOs(value = [OS.LINUX, OS.MAC], disabledReason = "Only for Linux and Mac") + fun testLocalPath_linux() { + val input = "/foo/bar" + assertEquals(input, input.localPath()) + } +} \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/utils/GiteeSupportTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/utils/GiteeSupportTest.kt index 81e5027cb..fd77036d9 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/utils/GiteeSupportTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/utils/GiteeSupportTest.kt @@ -1,6 +1,7 @@ package com.itangcent.utils import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test /** @@ -11,14 +12,20 @@ import org.junit.jupiter.api.Test internal class GiteeSupportTest { @Test - fun convertUrlFromGithubToGitee() { - assertEquals( - "https://gitee.com/tangcent/easy-yapi/raw/master/third/markdown.cn.config", - GiteeSupport.convertUrlFromGithub("https://raw.githubusercontent.com/tangcent/easy-yapi/master/third/markdown.cn.config") - ) - assertEquals( - "https://gitee.com/tangcent/easy-yapi/raw/master/third/swagger.config", - GiteeSupport.convertUrlFromGithub("https://raw.githubusercontent.com/tangcent/easy-yapi/master/third/swagger.config") - ) + fun testConvertUrlFromGithub() { + val githubUrl = "https://raw.githubusercontent.com/user/project/file.txt" + val expectedGiteeUrl = "https://gitee.com/user/project/raw/file.txt" + + val actualGiteeUrl = GiteeSupport.convertUrlFromGithub(githubUrl) + + assertEquals(expectedGiteeUrl, actualGiteeUrl) + } + + @Test + fun testInvalidUrl() { + val invalidUrl = "some invalid url" + val giteeUrl = GiteeSupport.convertUrlFromGithub(invalidUrl) + + assertNull(giteeUrl) } } \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/utils/ResourceUtilsTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/utils/ResourceUtilsTest.kt index 257841555..05017b9ab 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/utils/ResourceUtilsTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/utils/ResourceUtilsTest.kt @@ -1,25 +1,47 @@ package com.itangcent.utils import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test +import java.net.URL import kotlin.test.assertNull -import kotlin.test.assertTrue /** * Test case for [ResourceUtils] + * + * @author tangcent */ class ResourceUtilsTest { @Test fun testReadResource() { - assertEquals("token=111111", ResourceUtils.readResource("demo.properties")) - assertEquals("", ResourceUtils.readResource("demo-not-existed.properties")) + // Test reading a valid resource + val contents = ResourceUtils.readResource("demo.properties") + assertEquals("token=111111", contents) + + // Test reading a non-existent resource + val nonExistentContents = ResourceUtils.readResource("does-not-exist.properties") + assertEquals("", nonExistentContents) + + // Test reading the same resource twice to ensure caching + val cachedContents = ResourceUtils.readResource("demo.properties") + assertEquals("token=111111", cachedContents) } @Test fun testFindResource() { - assertTrue(ResourceUtils.findResource("demo.properties")!!.toString().endsWith("demo.properties")) - assertNull(ResourceUtils.findResource("demo-not-existed.properties")) + // Test finding a valid resource + val resourceURL: URL? = ResourceUtils.findResource("demo.properties") + assertNotNull(resourceURL) + + // Test finding a non-existent resource + val nonExistentResourceURL: URL? = ResourceUtils.findResource("does-not-exist.properties") + assertNull(nonExistentResourceURL) + + // Test finding the same resource URL twice to ensure caching + val cachedResourceURL: URL? = ResourceUtils.findResource("demo.properties") + assertNotNull(cachedResourceURL) + assertEquals(resourceURL, cachedResourceURL) } } \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/utils/StringKitKtTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/utils/StringKitKtTest.kt index bbaa28c00..6d0ad9da9 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/utils/StringKitKtTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/utils/StringKitKtTest.kt @@ -3,14 +3,36 @@ package com.itangcent.utils import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -class StringKitKtTest { +class StringKitTest { @Test - fun unescape() { - assertEquals("", "".unescape()) - assertEquals("abcd", "abcd".unescape()) - assertEquals("\\", "\\".unescape()) - assertEquals("\b\t\n\r\\", "\\b\\t\\n\\r\\\\".unescape()) - assertEquals("\bX\tX\nX\rX\\X\\", "\\bX\\tX\\nX\\rX\\\\X\\".unescape()) + fun testUnescape() { + // Test unescaping an empty string + val emptyString = "".unescape() + assertEquals("", emptyString) + + // Test unescaping a string with no escape characters + val noEscapeChars = "hello world".unescape() + assertEquals("hello world", noEscapeChars) + + // Test unescaping a string with a single escape character + val singleEscapeChar = "\\n".unescape() + assertEquals("\n", singleEscapeChar) + + // Test unescaping a string with multiple escape characters + val multipleEscapeChars = "first line\\nsecond line\\ttab character".unescape() + assertEquals("first line\nsecond line\ttab character", multipleEscapeChars) + + // Test unescaping a string with an unrecognized escape character + val unrecognizedEscapeChar = "\\x".unescape() + assertEquals("\\x", unrecognizedEscapeChar) + + // Test unescaping a string with a trailing backslash + val trailingBackslash = "no escape character here\\".unescape() + assertEquals("no escape character here\\", trailingBackslash) + + // Test unescaping a string with consecutive backslashes + val consecutiveBackslashes = "two backslashes \\\\ here".unescape() + assertEquals("two backslashes \\ here", consecutiveBackslashes) } } \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/utils/TemplateKitTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/utils/TemplateKitTest.kt index 16f631b44..8edecd01d 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/utils/TemplateKitTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/utils/TemplateKitTest.kt @@ -9,17 +9,32 @@ internal class TemplateKitTest { @Test fun resolvePlaceHolder() { - assertNull(TemplateKit.resolvePlaceHolder(null)) - assertNull(TemplateKit.resolvePlaceHolder("")) - assertContentEquals(arrayOf('$'), TemplateKit.resolvePlaceHolder("$")) - assertContentEquals(arrayOf('$', '#'), TemplateKit.resolvePlaceHolder("$#")) - assertContentEquals(arrayOf('$'), TemplateKit.resolvePlaceHolder(arrayOf("$"))) - assertContentEquals(arrayOf('$', '#'), TemplateKit.resolvePlaceHolder(arrayOf("$#"))) - assertContentEquals(arrayOf('$'), TemplateKit.resolvePlaceHolder(arrayOf('$'))) - assertContentEquals(arrayOf('$', '#'), TemplateKit.resolvePlaceHolder(arrayOf("$", '#'))) - assertContentEquals(arrayOf('$', '#'), TemplateKit.resolvePlaceHolder(arrayOf("$", "#"))) - assertContentEquals(arrayOf('$'), TemplateKit.resolvePlaceHolder(listOf('$'))) - assertContentEquals(arrayOf('$', '#'), TemplateKit.resolvePlaceHolder(listOf("$", '#'))) - assertContentEquals(arrayOf('$', '#'), TemplateKit.resolvePlaceHolder(listOf("$", "#"))) + // Test resolving a null placeholder + val nullPlaceholder = TemplateKit.resolvePlaceHolder(null) + assertArrayEquals(null, nullPlaceholder) + + // Test resolving a single character placeholder + val singleCharPlaceholder = TemplateKit.resolvePlaceHolder('$') + assertArrayEquals(charArrayOf('$'), singleCharPlaceholder) + + // Test resolving a string placeholder + val stringPlaceholder = TemplateKit.resolvePlaceHolder("#hello") + assertArrayEquals(charArrayOf('#', 'h', 'e', 'l', 'o'), stringPlaceholder) + + // Test resolving an array of placeholders + val arrayPlaceholder = TemplateKit.resolvePlaceHolder(arrayOf("$", "#bc", '#', arrayOf('#', '$'))) + assertArrayEquals(charArrayOf('$', '#', 'b', 'c'), arrayPlaceholder) + + // Test resolving a collection of placeholders + val collectionPlaceholder = TemplateKit.resolvePlaceHolder(listOf('#', "\$bc", arrayOf('#', '$'))) + assertArrayEquals(charArrayOf('#', '$', 'b', 'c'), collectionPlaceholder) + + // Test resolving a placeholder with nested collections + val nestedCollectionPlaceholder = TemplateKit.resolvePlaceHolder(listOf('$', listOf('#', "#c"), arrayOf('#', arrayOf('$', '#')))) + assertArrayEquals(charArrayOf('$', '#', 'c'), nestedCollectionPlaceholder) + + // Test resolving a placeholder with no characters + val emptyPlaceholder = TemplateKit.resolvePlaceHolder("") + assertArrayEquals(null, emptyPlaceholder) } } \ No newline at end of file