Wizard: add common tests for iOS/Android template

This commit is contained in:
Ilya Kirillov
2020-07-23 10:59:10 +03:00
committed by Kirill Shmakov
parent 5ffcaf6508
commit b3d48cda8c
18 changed files with 191 additions and 47 deletions
@@ -42,7 +42,9 @@ kotlin {
}
}
androidTest {
dependencies {
implementation 'junit:junit:4.12'
}
}
iosMain {
@@ -39,7 +39,11 @@ kotlin {
implementation("androidx.core:core-ktx:1.2.0")
}
}
val androidTest by getting
val androidTest by getting {
dependencies {
implementation("junit:junit:4.12")
}
}
val iosMain by getting
val iosTest by getting
}
@@ -40,7 +40,9 @@ kotlin {
}
}
androidTest {
dependencies {
implementation 'junit:junit:4.12'
}
}
iosMain {
@@ -37,7 +37,11 @@ kotlin {
implementation("androidx.core:core-ktx:1.2.0")
}
}
val androidTest by getting
val androidTest by getting {
dependencies {
implementation("junit:junit:4.12")
}
}
val iosMain by getting
val iosTest by getting
}
@@ -0,0 +1,10 @@
import org.junit.Assert.assertTrue
import org.junit.Test
class GreetingTest {
@Test
fun testExample() {
assertTrue("Check Android is mentioned", Greeting().greeting().contains("Android"))
}
}
@@ -0,0 +1,10 @@
import kotlin.test.Test
import kotlin.test.assertTrue
class GreetingTest {
@Test
fun testExample() {
assertTrue(Greeting().greeting().contains("iOS"), "Check iOS is mentioned")
}
}
@@ -1,7 +1,7 @@
import XCTest
@testable import appName
@testable import $moduleName
class appNameTests: XCTestCase {
class ${moduleName}Tests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
@@ -12,6 +12,7 @@ object Versions {
val KOTLIN = version("1.4-M1") // used as fallback version
val GRADLE = version("6.3")
val KTOR: (kotlinVersion: Version) -> Version = { kotlinVersion -> version("1.3.2-$kotlinVersion") }
val JUNIT = version("4.12")
object ANDROID {
val ANDROIDX_CORE_KTX = version("1.2.0")
@@ -69,7 +69,9 @@ data class ProjectImportingError(val kotlinVersion: String, @Nls val reason: Str
""".trimMargin()
}
data class InvalidModuleDependencyError(val from: Module, val to: Module, @Nls val reason: String? = null) : Error() {
data class InvalidModuleDependencyError(val from: String, val to: String, @Nls val reason: String? = null) : Error() {
constructor(from: Module, to: Module, reason: String? = null) : this(from.name, to.name, reason)
override val message: String
get() = KotlinNewProjectWizardBundle.message("error.invalid.module.dependency", from.name, to.name) + reason?.let { ": $it" }
get() = KotlinNewProjectWizardBundle.message("error.invalid.module.dependency", from, to) + reason?.let { ": $it" }
}
@@ -35,7 +35,8 @@ abstract class TemplateEngineService : WizardService {
is FileTemplateDescriptor -> renderTemplate(descriptor, template.data)
is FileTextDescriptor -> descriptor.text
}
val text = formatter.formatFile(unformattedText, template.descriptor.relativePath.fileName.toString())
val fileName = template.descriptor.relativePath?.fileName?.toString()
val text = fileName?.let { formatter.formatFile(unformattedText, it) } ?: unformattedText
return service<FileSystemWizardService>().createFile(template.rootPath / template.descriptor.relativePath, text)
}
}
@@ -153,7 +153,18 @@ object AndroidTargetConfigurator : TargetConfigurator,
modulePath: Path
): TaskResult<Unit> = computeM {
val javaPackage = module.javaPackage(configurationData.pomIr)
val settings = mapOf("package" to javaPackage.asCodePackage())
val sharedModule = configurationData.getDependentModules(module).get().find { dependency ->
dependency.configurator is MppModuleConfigurator
}
val sharedPackage = sharedModule?.javaPackage(configurationData.pomIr)
val settings = mapOf(
"package" to javaPackage.asCodePackage(),
"sharedPackage" to sharedPackage?.asCodePackage()
)
TemplatesPlugin.addFileTemplates.execute(
listOf(
FileTemplate(AndroidModuleConfigurator.FileTemplateDescriptors.androidManifestForLibraryXml, modulePath, settings)
@@ -161,6 +172,17 @@ object AndroidTargetConfigurator : TargetConfigurator,
)
}
override fun createModuleIRs(reader: Reader, configurationData: ModulesToIrConversionData, module: Module): List<BuildSystemIR> =
buildList {
+super<AndroidModuleConfigurator>.createModuleIRs(reader, configurationData, module)
+ArtifactBasedLibraryDependencyIR(
MavenArtifact(DefaultRepository.MAVEN_CENTRAL, "junit", "junit"),
version = Versions.JUNIT,
dependencyType = DependencyType.TEST
)
}
val androidPlugin by enumSetting<AndroidGradlePlugin>(
KotlinNewProjectWizardBundle.message("module.configurator.android.setting.android.plugin"),
neededAtPhase = GenerationPhase.PROJECT_GENERATION
@@ -66,7 +66,7 @@ object IOSSinglePlatformModuleConfigurator : SinglePlatformModuleConfigurator,
+fileTemplate(DEFAULT_APP_NAME / "Preview_Content" / "Preview_Assets.xcassets" / "Contents.json")
+fileTemplate("${DEFAULT_APP_NAME}Tests" / "Info.plist")
+fileTemplate("${DEFAULT_APP_NAME}Tests" / "${DEFAULT_APP_NAME}Tests.swift")
+fileTemplate("${DEFAULT_APP_NAME}Tests" / "${DEFAULT_APP_NAME}Tests.swift.vm")
+fileTemplate("${DEFAULT_APP_NAME}UITests" / "Info.plist")
+fileTemplate("${DEFAULT_APP_NAME}UITests" / "${DEFAULT_APP_NAME}UITests.swift")
@@ -17,7 +17,10 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugi
import org.jetbrains.kotlin.tools.projectWizard.settings.JavaPackage
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.templates.FileDescriptor
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplate
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplateDescriptorWithPath
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTextDescriptor
import java.nio.file.Path
@@ -43,23 +46,33 @@ object MppModuleConfigurator : ModuleConfigurator,
version = configurationData.kotlinVersion
)
val mppFiles by listProperty<MppFile>()
val mppSources by listProperty<MppSources>()
override fun getConfiguratorProperties(): List<ModuleConfiguratorProperty<*>> =
listOf(mppFiles)
listOf(mppSources)
override fun Writer.runArbitraryTask(
configurationData: ModulesToIrConversionData,
module: Module,
modulePath: Path,
): TaskResult<Unit> = compute {
createMppFiles(module, modulePath).ensure()
createSimpleFiles(module, modulePath).ensure()
}
private fun Writer.createMppFiles(
module: Module,
modulePath: Path
): TaskResult<Unit> = inContextOfModuleConfigurator(module) {
val mppFiles = mppFiles.reference.propertyValue.distinctBy { it.fileNameWithPackage } // todo merge files with the same name here
val mppFiles = mppSources.reference.propertyValue.flatMap { it.mppFiles }
.distinctBy { it.fileNameWithPackage } // todo merge files with the same name here
mppFiles.mapSequenceIgnore { file ->
module.subModules.mapSequenceIgnore mapTargets@{ target ->
val moduleSubType = //TODO handle for non-simple target configurator
target.configurator.safeAs<SimpleTargetConfigurator>()?.moduleSubType ?: return@mapTargets UNIT_SUCCESS
val path = pathForFileInTarget(modulePath, module, file.javaPackage, file.filename, target, SourcesetType.main)
val fileTemplate = FileTemplate(
FileTextDescriptor(file.printForModuleSubType(moduleSubType), pathForFileInTarget(modulePath, module, file, target)),
FileTextDescriptor(file.printForModuleSubType(moduleSubType), path),
projectPath,
)
TemplatesPlugin.fileTemplatesToRender.addValues(fileTemplate)
@@ -67,13 +80,38 @@ object MppModuleConfigurator : ModuleConfigurator,
}
}
private fun pathForFileInTarget(mppModulePath: Path, mppModule: Module, file: MppFile, target: Module) =
mppModulePath /
Defaults.SRC_DIR /
"${target.name}Main" /
mppModule.configurator.kotlinDirectoryName /
file.javaPackage?.asPath() /
file.filename
private fun Writer.createSimpleFiles(
module: Module,
modulePath: Path
): TaskResult<Unit> = inContextOfModuleConfigurator(module) {
val mppFiles = mppSources.reference.propertyValue
module.subModules.mapSequenceIgnore mapTargets@{ target ->
val moduleSubType = //TODO handle for non-simple target configurator
target.configurator.safeAs<SimpleTargetConfigurator>()?.moduleSubType ?: return@mapTargets UNIT_SUCCESS
val files = mppFiles.flatMap { it.getFilesFor(moduleSubType) }.distinctBy { it.uniqueIdentifier }
val fileTemplates = files.map { file ->
FileTemplate(
file.fileDescriptor,
projectPath / pathForFileInTarget(modulePath, module, file.javaPackage, file.filename, target, file.type),
)
}
TemplatesPlugin.fileTemplatesToRender.addValues(fileTemplates)
}
}
private fun pathForFileInTarget(
mppModulePath: Path,
mppModule: Module,
javaPackage: JavaPackage?,
filename: String,
target: Module,
sourcesetType: SourcesetType,
) = mppModulePath /
Defaults.SRC_DIR /
"${target.name}${sourcesetType.name.capitalize()}" /
mppModule.configurator.kotlinDirectoryName /
javaPackage?.asPath() /
filename
}
@@ -123,19 +161,55 @@ data class MppFile(
}
}
@ExpectFileDSL
class MppFilesListBuilder {
private val expectFiles = mutableListOf<MppFile>()
class MppSources(val mppFiles: List<MppFile>, val simpleFiles: List<SimpleFiles>) {
fun mppfile(filename: String, init: MppFile.Builder.() -> Unit) {
expectFiles += MppFile.Builder(filename).apply(init).build()
fun getFilesFor(moduleSubType: ModuleSubType): List<SimpleFile> =
simpleFiles.filter { moduleSubType in it.moduleSubTypes }.flatMap { it.files }
class Builder {
private val mppFiles = mutableListOf<MppFile>()
private val simpleFiles = mutableListOf<SimpleFiles>()
fun mppFile(filename: String, init: MppFile.Builder.() -> Unit) {
mppFiles += MppFile.Builder(filename).apply(init).build()
}
fun filesFor(vararg moduleSubTypes: ModuleSubType, init: SimpleFiles.Builder.() -> Unit) {
simpleFiles += SimpleFiles.Builder(moduleSubTypes.toList()).apply(init).build()
}
fun build() = MppSources(mppFiles, simpleFiles)
}
fun build(): List<MppFile> = expectFiles
}
fun mppFiles(init: MppFilesListBuilder.() -> Unit): List<MppFile> =
MppFilesListBuilder().apply(init).build()
data class SimpleFiles(val moduleSubTypes: List<ModuleSubType>, val files: List<SimpleFile>) {
class Builder(private val moduleSubTypes: List<ModuleSubType>) {
private val files = mutableListOf<SimpleFile>()
fun file(fileDescriptor: FileDescriptor, filename: String, type: SourcesetType, init: SimpleFile.Builder.() -> Unit = {}) {
files += SimpleFile.Builder(fileDescriptor, filename, type).apply(init).build()
}
fun build() = SimpleFiles(moduleSubTypes, files)
}
}
data class SimpleFile(val fileDescriptor: FileDescriptor, val javaPackage: JavaPackage?, val filename: String, val type: SourcesetType) {
val uniqueIdentifier get() = "${type}/${javaPackage}/${filename}"
class Builder(private val fileDescriptor: FileDescriptor, private val filename: String, private val type: SourcesetType) {
var javaPackage: JavaPackage? = null
fun build() = SimpleFile(fileDescriptor, javaPackage, filename, type)
}
}
fun mppSources(init: MppSources.Builder.() -> Unit): MppSources =
MppSources.Builder().apply(init).build()
sealed class MppDeclaration {
@Suppress("SpellCheckingInspection")
@@ -16,8 +16,6 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemT
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.isGradle
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
@@ -28,6 +28,11 @@ data class ModulesToIrConversionData(
val isSingleRootModuleMode = rootModules.size == 1
val moduleByPath = rootModules.withAllSubModules(includeSourcesets = true).associateBy(Module::path)
fun getDependentModules(from: Module): TaskResult<List<Module>> =
from.dependencies.mapSequence { to ->
moduleByPath[to.path].toResult { InvalidModuleDependencyError(from.name, to.path.toString()) }
}
}
private data class ModulesToIrsState(
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.projectPath
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplate
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplateDescriptor
import org.jetbrains.kotlin.tools.projectWizard.templates.asSrcOf
import java.nio.file.Path
import kotlin.reflect.KClass
@@ -112,7 +113,7 @@ sealed class ModuleDependencyType(
to: Module,
toModulePath: Path,
): TaskResult<Unit> {
val needDummyFile = inContextOfModuleConfigurator(to) { MppModuleConfigurator.mppFiles.reference.propertyValue.isEmpty() }
val needDummyFile = inContextOfModuleConfigurator(to) { MppModuleConfigurator.mppSources.reference.propertyValue.isEmpty() }
return if (needDummyFile) {
val dummyFilePath =
Defaults.SRC_DIR / "${to.iosTargetSafe()!!.name}Main" / to.configurator.kotlinDirectoryName / "dummyFile.kt"
@@ -184,8 +185,8 @@ sealed class ModuleDependencyType(
}
private fun Writer.addExpectFilesForMppModuleForAndroidAndIos(mppModule: Module): TaskResult<Unit> {
val expectFiles = mppFiles {
mppfile("Platform.kt") {
val expectFiles = mppSources {
mppFile("Platform.kt") {
`class`("Platform") {
expectBody = "val platform: String"
actualFor(
@@ -202,6 +203,14 @@ private fun Writer.addExpectFilesForMppModuleForAndroidAndIos(mppModule: Module)
}
}
}
filesFor(ModuleSubType.android) {
file(FileTemplateDescriptor("android/androidTest.kt.vm", relativePath = null), "androidTest.kt", SourcesetType.test)
}
filesFor(ModuleSubType.iosArm64, ModuleSubType.iosX64) {
file(FileTemplateDescriptor("ios/iosTest.kt.vm", relativePath = null), "iosTest.kt", SourcesetType.test)
}
}
return inContextOfModuleConfigurator(mppModule) { MppModuleConfigurator.mppFiles.reference.addValues(expectFiles) }
return inContextOfModuleConfigurator(mppModule) { MppModuleConfigurator.mppSources.reference.addValues(listOf(expectFiles)) }
}
@@ -9,10 +9,10 @@ import java.nio.file.Paths
// Should be used to create any kind of files in the generated project
// Except build files as they will be generated using IR
sealed class FileDescriptor {
abstract val relativePath: Path
abstract val relativePath: Path?
}
data class FileTemplateDescriptor(@NonNls val templateId: String, override val relativePath: Path) : FileDescriptor() {
data class FileTemplateDescriptor(@NonNls val templateId: String, override val relativePath: Path?) : FileDescriptor() {
constructor(templateId: String) : this(
templateId,
Paths.get(templateId).fileName.toString().removeSuffix(".vm").asPath()
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.mppFiles
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.mppSources
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.junit.Test
import kotlin.test.assertEquals
@@ -13,8 +13,8 @@ import kotlin.test.assertEquals
class ExpectedFileTest {
@Test
fun `it should generate valid expect functions`() {
val file = mppFiles {
mppfile("file") {
val file = mppSources {
mppFile("file") {
function("f(x: Int): String")
function("y(x: Int): String")
}
@@ -30,8 +30,8 @@ class ExpectedFileTest {
@Test
fun `it should generate valid expect classes`() {
val file = mppFiles {
mppfile("file") {
val file = mppSources {
mppFile("file") {
`class`("A")
`class`("B")
}
@@ -47,8 +47,8 @@ class ExpectedFileTest {
@Test
fun `it should generate valid actual functions`() {
val file = mppFiles {
mppfile("file") {
val file = mppSources {
mppFile("file") {
function("foo()") {
actualFor(
ModuleSubType.jvm,
@@ -92,8 +92,8 @@ class ExpectedFileTest {
@Test
fun `it should generate valid actual classes`() {
val file = mppFiles {
mppfile("file") {
val file = mppSources {
mppFile("file") {
`class`("A") {
actualFor(
ModuleSubType.jvm,