User: rednesto Date: 07 Aug 24 20:35 Revision: abee07f2ea12aa639d1d93307386ddc652730c4d Summary: StringCreatorProperty tests TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=9575&personal=false Index: src/main/kotlin/creator/custom/CreatorContext.kt =================================================================== --- src/main/kotlin/creator/custom/CreatorContext.kt (revision 640f2e26027a7b059fcd0e0f1a96e2e57c295be2) +++ src/main/kotlin/creator/custom/CreatorContext.kt (revision abee07f2ea12aa639d1d93307386ddc652730c4d) @@ -28,4 +28,8 @@ val graph: PropertyGraph, val properties: Map>, val wizardContext: WizardContext, -) +) { + + @Suppress("UNCHECKED_CAST") + fun property(name: String): CreatorProperty = properties[name] as CreatorProperty +} Index: src/main/kotlin/creator/custom/CreatorTemplateProcessor.kt =================================================================== --- src/main/kotlin/creator/custom/CreatorTemplateProcessor.kt (revision 640f2e26027a7b059fcd0e0f1a96e2e57c295be2) +++ src/main/kotlin/creator/custom/CreatorTemplateProcessor.kt (revision abee07f2ea12aa639d1d93307386ddc652730c4d) @@ -82,8 +82,17 @@ private set private var properties: MutableMap> = mutableMapOf() - private var context: CreatorContext = CreatorContext(propertyGraph, properties, wizardContext) + var context: CreatorContext = CreatorContext(propertyGraph, properties, wizardContext) + fun initBuiltinProperties() { + val projectNameProperty = externalPropertyProvider.projectNameProperty + properties["PROJECT_NAME"] = ExternalCreatorProperty( + context = context, + graphProperty = projectNameProperty, + valueType = String::class.java + ) + } + fun createOptionsPanel(template: LoadedTemplate): DialogPanel? { properties = mutableMapOf() context = context.copy(properties = properties) @@ -92,12 +101,7 @@ return null } - val projectNameProperty = externalPropertyProvider.projectNameProperty - properties["PROJECT_NAME"] = ExternalCreatorProperty( - context = context, - graphProperty = projectNameProperty, - valueType = String::class.java - ) + initBuiltinProperties() return panel { val reporter = TemplateValidationReporterImpl() @@ -120,7 +124,7 @@ } } - private fun setupTemplate( + fun setupTemplate( template: LoadedTemplate, reporter: TemplateValidationReporterImpl ): List> { @@ -144,6 +148,11 @@ throw t } + if (t is TemplateValidationException && application.isUnitTestMode) { + // Rethrowing makes the exception properly catchable while not polluting the test outputs + throw t + } + thisLogger().error( "Unexpected error during template setup", t, Index: src/main/kotlin/creator/custom/TemplateValidationReporter.kt =================================================================== --- src/main/kotlin/creator/custom/TemplateValidationReporter.kt (revision 640f2e26027a7b059fcd0e0f1a96e2e57c295be2) +++ src/main/kotlin/creator/custom/TemplateValidationReporter.kt (revision abee07f2ea12aa639d1d93307386ddc652730c4d) @@ -36,6 +36,7 @@ class TemplateValidationReporterImpl : TemplateValidationReporter { private val validationItems: MutableMap> = linkedMapOf() + val items: Map> = validationItems var hasErrors = false private set var hasWarns = false @@ -99,7 +100,7 @@ class TemplateValidationException(message: String?, cause: Throwable? = null) : Exception(message, cause) -private sealed class TemplateValidationItem(val message: String, val color: JBColor) { +sealed class TemplateValidationItem(val message: String, val color: JBColor) { class Warn(message: String) : TemplateValidationItem(message, JBColor.YELLOW) class Error(message: String) : TemplateValidationItem(message, JBColor.RED) Index: src/test/kotlin/creator/CreatorTemplateProcessorTest.kt =================================================================== --- src/test/kotlin/creator/CreatorTemplateProcessorTest.kt (revision abee07f2ea12aa639d1d93307386ddc652730c4d) +++ src/test/kotlin/creator/CreatorTemplateProcessorTest.kt (revision abee07f2ea12aa639d1d93307386ddc652730c4d) @@ -0,0 +1,78 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.creator + +import com.demonwav.mcdev.creator.custom.TemplateDescriptor +import com.demonwav.mcdev.creator.custom.TemplateValidationException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +@DisplayName("Creator Template Processor Tests") +class CreatorTemplateProcessorTest : CreatorTemplateProcessorTestBase() { + + @Test + @DisplayName("Duplicate Property Name") + fun duplicatePropertyName() { + val exception = assertThrows { + makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "PROP", + "type": "string" + }, + { + "name": "PROP", + "type": "string" + } + ] + } + """.trimIndent() + ) + } + assertEquals("Duplicate property name PROP", exception.message?.replace("\r\n", "\n")) + } + + @Test + @DisplayName("Unknown Property Type") + fun unknownPropertyType() { + val exception = assertThrows { + makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "PROP", + "type": "bad_type" + } + ] + } + """.trimIndent() + ) + } + assertEquals("Unknown template property type bad_type", exception.message) + } +} Index: src/test/kotlin/creator/CreatorTemplateProcessorTestBase.kt =================================================================== --- src/test/kotlin/creator/CreatorTemplateProcessorTestBase.kt (revision abee07f2ea12aa639d1d93307386ddc652730c4d) +++ src/test/kotlin/creator/CreatorTemplateProcessorTestBase.kt (revision abee07f2ea12aa639d1d93307386ddc652730c4d) @@ -0,0 +1,90 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.creator + +import com.demonwav.mcdev.creator.custom.CreatorTemplateProcessor +import com.demonwav.mcdev.creator.custom.ExternalTemplatePropertyProvider +import com.demonwav.mcdev.creator.custom.TemplateDescriptor +import com.demonwav.mcdev.creator.custom.TemplateValidationReporterImpl +import com.demonwav.mcdev.creator.custom.providers.LoadedTemplate +import com.google.gson.Gson +import com.intellij.ide.util.projectWizard.WizardContext +import com.intellij.openapi.observable.properties.GraphProperty +import com.intellij.openapi.observable.properties.PropertyGraph +import com.intellij.testFramework.fixtures.BareTestFixture +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach + +class TestLoadedTemplate(descriptor: String) : LoadedTemplate { + + override val label: String = "Test template" + + override val tooltip: String? = null + + override val descriptor: TemplateDescriptor = + Gson().fromJson(descriptor, TemplateDescriptor::class.java) + + override val isValid: Boolean = true + + override fun loadTemplateContents(path: String): String? { + return null + } +} + +class TestExternalTemplatePropertyProvider(propertyGraph: PropertyGraph) : ExternalTemplatePropertyProvider { + + override val projectNameProperty: GraphProperty = propertyGraph.property("Test project") + + override val useGit: Boolean = false +} + +abstract class CreatorTemplateProcessorTestBase { + + lateinit var fixture: BareTestFixture + lateinit var propertyGraph: PropertyGraph + lateinit var processor: CreatorTemplateProcessor + + @BeforeEach + open fun setUp() { + fixture = IdeaTestFixtureFactory.getFixtureFactory().createBareFixture() + fixture.setUp() + + propertyGraph = PropertyGraph() + val wizardContext = WizardContext(null, fixture.testRootDisposable) + val externalPropertyProvider = TestExternalTemplatePropertyProvider(propertyGraph) + processor = CreatorTemplateProcessor(propertyGraph, wizardContext, externalPropertyProvider) + processor.initBuiltinProperties() + } + + @AfterEach + open fun tearDown() { + fixture.tearDown() + } + + fun makeTemplate(@Language("JSON") descriptor: String): TemplateValidationReporterImpl { + val reporter = TemplateValidationReporterImpl() + val loadedTemplate = TestLoadedTemplate(descriptor) + processor.setupTemplate(loadedTemplate, reporter) + return reporter + } +} Index: src/test/kotlin/creator/StringCreatorPropertyTest.kt =================================================================== --- src/test/kotlin/creator/StringCreatorPropertyTest.kt (revision abee07f2ea12aa639d1d93307386ddc652730c4d) +++ src/test/kotlin/creator/StringCreatorPropertyTest.kt (revision abee07f2ea12aa639d1d93307386ddc652730c4d) @@ -0,0 +1,334 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.creator + +import com.demonwav.mcdev.creator.custom.TemplateDescriptor +import com.demonwav.mcdev.creator.custom.TemplateValidationItem +import com.demonwav.mcdev.util.firstOfType +import kotlin.collections.singleOrNull +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +@DisplayName("String Creator Property Tests") +class StringCreatorPropertyTest : CreatorTemplateProcessorTestBase() { + + @Test + @DisplayName("Invalid Validator") + fun invalidValidator() { + val reporter = makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "STRING", + "type": "string", + "validator": "[invalid" + } + ] + } + """.trimIndent() + ) + + assertTrue(reporter.hasErrors) + assertEquals( + "Invalid validator regex: '[invalid': Unclosed character class near index 7\n[invalid\n ^", + reporter.items["STRING"]?.singleOrNull()?.message?.replace("\r\n", "\n") + ) + } + + @Test + @DisplayName("Replace Derivation") + fun replaceDerivation() { + makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "STRING", + "type": "string", + "derives": { + "parents": ["PROJECT_NAME"], + "method": "replace", + "parameters": { + "regex": "[^a-z0-9-_]+", + "replacement": "_", + "maxLength": 32 + } + } + } + ] + } + """.trimIndent() + ) + + val projectNameProperty = processor.context.property("PROJECT_NAME") + val stringProperty = processor.context.property("STRING") + + projectNameProperty.graphProperty.set("Sanitize This") + assertEquals("sanitize_this", stringProperty.get()) + + projectNameProperty.graphProperty.set("This string will get truncated at some point") + assertEquals("this_string_will_get_truncated_a", stringProperty.get()) + } + + @Test + @DisplayName("Replace Derivation Missing Parameters") + fun replaceDerivationMissingParameters() { + val reporter = makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "STRING", + "type": "string", + "derives": { + "parents": ["PROJECT_NAME"], + "method": "replace" + } + } + ] + } + """.trimIndent() + ) + + assertEquals("Missing parameters", reporter.items["STRING"]?.singleOrNull()?.message) + } + + @Test + @DisplayName("Replace Derivation Missing Parent Value") + fun replaceDerivationMissingParentValue() { + val reporter = makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "STRING", + "type": "string", + "derives": { + "parents": [], + "method": "replace", + "parameters": {} + } + } + ] + } + """.trimIndent() + ) + + assertEquals("Missing parent value", reporter.items["STRING"]?.singleOrNull()?.message) + } + + @Test + @DisplayName("Replace Derivation More Than One Parent Defined") + fun replaceDerivationMoreThanOneParentDefined() { + val reporter = makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "STRING", + "type": "string", + "derives": { + "parents": ["PROJECT_NAME", "PROJECT_NAME"], + "method": "replace", + "parameters": {} + } + } + ] + } + """.trimIndent() + ) + + assertEquals( + "More than one parent defined", + reporter.items["STRING"]?.firstOfType()?.message + ) + } + + @Test + @DisplayName("Replace Derivation Parent Property Must Produce A String Value") + fun replaceDerivationParentPropertyMustProduceAStringValue() { + val reporter = makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "BOOL", + "type": "boolean" + }, + { + "name": "STRING", + "type": "string", + "derives": { + "parents": ["BOOL"], + "method": "replace", + "parameters": {} + } + } + ] + } + """.trimIndent() + ) + + assertEquals("Parent property must produce a string value", reporter.items["STRING"]?.singleOrNull()?.message) + } + + @Test + @DisplayName("Replace Derivation Missing Regex Parameter") + fun replaceDerivationMissingRegexParameter() { + val reporter = makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "STRING", + "type": "string", + "derives": { + "parents": ["PROJECT_NAME"], + "method": "replace", + "parameters": { + "replacement": "_" + } + } + } + ] + } + """.trimIndent() + ) + + assertEquals("Missing 'regex' string parameter", reporter.items["STRING"]?.singleOrNull()?.message) + } + + @Test + @DisplayName("Replace Derivation Missing Replacement Parameter") + fun replaceDerivationMissingReplacementParameter() { + val reporter = makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "STRING", + "type": "string", + "derives": { + "parents": ["PROJECT_NAME"], + "method": "replace", + "parameters": { + "regex": "[^a-z0-9-_]+" + } + } + } + ] + } + """.trimIndent() + ) + + assertEquals("Missing 'replacement' string parameter", reporter.items["STRING"]?.singleOrNull()?.message) + } + + @Test + @DisplayName("Select Derivation") + fun selectDerivation() { + makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "STRING", + "type": "string", + "derives": { + "parents": ["PROJECT_NAME"], + "select": [ + { "condition": "${'$'}PROJECT_NAME == 'Name 1'", "value": "Value 1" }, + { "condition": "${'$'}PROJECT_NAME == 'Name 2'", "value": "Value 2" }, + { "condition": "${'$'}PROJECT_NAME == 'Name 3'", "value": "Value 3" } + ], + "default": "Default value" + } + } + ] + } + """.trimIndent() + ) + + val projectNameProperty = processor.context.property("PROJECT_NAME") + val stringProperty = processor.context.property("STRING") + + projectNameProperty.graphProperty.set("Name 1") + assertEquals("Value 1", stringProperty.get()) + + projectNameProperty.graphProperty.set("Name 2") + assertEquals("Value 2", stringProperty.get()) + + projectNameProperty.graphProperty.set("Name 3") + assertEquals("Value 3", stringProperty.get()) + + projectNameProperty.graphProperty.set("Name 4") + assertEquals("Default value", stringProperty.get()) + } + + @Test + @DisplayName("Select Derivation With Modification") + fun selectDerivationWithModification() { + makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "STRING", + "type": "string", + "derives": { + "parents": ["PROJECT_NAME"], + "select": [ + { "condition": "${'$'}PROJECT_NAME == 'Name 1'", "value": "Value 1" }, + { "condition": "${'$'}PROJECT_NAME == 'Name 2'", "value": "Value 2" } + ], + "default": "Default value", + "whenModified": true + } + } + ] + } + """.trimIndent() + ) + + val projectNameProperty = processor.context.property("PROJECT_NAME") + val stringProperty = processor.context.property("STRING") + + projectNameProperty.graphProperty.set("Name 1") + assertEquals("Value 1", stringProperty.get()) + + stringProperty.graphProperty.set("Custom value") + projectNameProperty.graphProperty.set("Name 2") + assertEquals("Custom value", stringProperty.get()) + } +}