Skip to content

Commit

Permalink
Added kotest extentions (#25)
Browse files Browse the repository at this point in the history
* Added kotest extentions

* Added MVI kotest release workflow
  • Loading branch information
extmkv authored Mar 6, 2024
1 parent c192b02 commit 881b218
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 0 deletions.
49 changes: 49 additions & 0 deletions .github/workflows/release_kotest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# This is a basic workflow to help you get started with Actions

name: Release MVI Kotest

# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
tags:
- 'kotest/[0-9]+.[0-9]+.[0-9]+'

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: '17'
distribution: 'adopt'

- name: Grant Permission to Execute Gradle
run: chmod +x gradlew

- name: Build with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: build

- name: Publish Library
run: |
echo "Publishing library🚀"
./gradlew :mvi-kotest:publish --no-daemon --no-parallel
echo "Published✅"
echo "Releasing repository...🚀"
./gradlew closeAndReleaseRepository
echo "Released✅"
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Here are the current available mvi modules versions:
|-------------|:----------------------------------------------------------------------------------------------------------------------------------------------------:|
| mvi | [![Maven Central](https://img.shields.io/maven-central/v/com.adidas.mvi/mvi)](https://mvnrepository.com/artifact/com.adidas.mvi/mvi) |
| mvi-compose | [![Maven Central](https://img.shields.io/maven-central/v/com.adidas.mvi/mvi-compose)](https://mvnrepository.com/artifact/com.adidas.mvi/mvi-compose) |
| mvi-kotest | [![Maven Central](https://img.shields.io/maven-central/v/com.adidas.mvi/mvi-kotest)](https://mvnrepository.com/artifact/com.adidas.mvi/mvi-kotest) |

## Features

Expand Down
20 changes: 20 additions & 0 deletions mvi-kotest/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import org.jlleitschuh.gradle.ktlint.KtlintExtension

plugins {
kotlin("jvm") version libs.versions.kotlin.get()
alias(libs.plugins.ktlint)
alias(libs.plugins.mavenPublish)
}

kotlin {
explicitApi()
}

configure<KtlintExtension> {
version.set(libs.versions.ktlint.lib.get())
}

dependencies {
implementation(libs.kotest.runner)
implementation(libs.adidas.mvi)
}
7 changes: 7 additions & 0 deletions mvi-kotest/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
POM_ARTIFACT_ID=mvi-kotest
GROUP=com.adidas.mvi
VERSION_CODE=1
VERSION_NAME=0.0.1
POM_NAME=Adidas MVI Kotest
POM_DESCRIPTION=Adidas MVI Kotest
POM_PACKAGING=aar
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.adidas.mvi.kotest

import com.adidas.mvi.Intent
import com.adidas.mvi.LoggableState
import com.adidas.mvi.MviHost
import com.adidas.mvi.State
import io.kotest.core.names.TestName
import io.kotest.core.spec.style.scopes.BehaviorSpecRootScope
import io.kotest.core.spec.style.scopes.addContainer

@Suppress("FunctionName")
public inline fun <
TIntent : Intent,
TState : LoggableState,
TSideEffect : Any,
reified T : MviHost<TIntent, State<TState, TSideEffect>>,
> BehaviorSpecRootScope.GivenViewModel(
noinline viewModel: () -> T,
crossinline test: suspend ViewModelContainerScope<TIntent, TState, TSideEffect, T>.() -> Unit,
): Unit =
addContainer(
TestName("Given: ", "a ${T::class.simpleName}", true),
disabled = false,
null,
) {
ViewModelContainerScope(this, viewModel()).test()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.adidas.mvi.kotest

import com.adidas.mvi.Intent
import com.adidas.mvi.LoggableState
import com.adidas.mvi.MviHost
import com.adidas.mvi.State
import io.kotest.core.names.TestName
import io.kotest.core.spec.style.scopes.AbstractContainerScope
import io.kotest.core.test.TestScope

public class ViewModelContainerScope<
TIntent : Intent,
TState : LoggableState,
TSideEffect : Any,
T : MviHost<TIntent, State<TState, TSideEffect>>,
>(
testScope: TestScope,
private val viewModel: T,
) : AbstractContainerScope(testScope) {
@Suppress("FunctionName")
public suspend fun WhenIntent(
intent: TIntent,
test: suspend ViewModelWhenContainerScope<TIntent, TState, TSideEffect, T>.() -> Unit,
) {
registerContainer(
name =
TestName(
prefix = "When: ",
name = "${intent::class.simpleName} intent is called",
defaultAffixes = true,
),
disabled = false,
config = null,
) {
this@ViewModelContainerScope.viewModel.execute(intent)
ViewModelWhenContainerScope(
testScope = this,
viewModel = this@ViewModelContainerScope.viewModel,
).test()
}
}

@Suppress("FunctionName")
public suspend fun When(
name: String,
test: suspend ViewModelWhenContainerScope<TIntent, TState, TSideEffect, T>.() -> Unit,
) {
registerContainer(
name = TestName(prefix = "When: ", name = name, defaultAffixes = true),
disabled = false,
config = null,
) {
ViewModelWhenContainerScope(
this,
viewModel = this@ViewModelContainerScope.viewModel,
).test()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.adidas.mvi.kotest

import com.adidas.mvi.Intent
import com.adidas.mvi.LoggableState
import com.adidas.mvi.MviHost
import com.adidas.mvi.State
import io.kotest.core.names.TestName
import io.kotest.core.spec.style.scopes.AbstractContainerScope
import io.kotest.core.test.TestScope
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.types.shouldBeInstanceOf
import kotlinx.coroutines.flow.StateFlow

public class ViewModelWhenContainerScope<
TIntent : Intent,
TState : LoggableState,
TSideEffect : Any,
T : MviHost<TIntent, State<TState, TSideEffect>>,
>(
testScope: TestScope,
public val viewModel: T,
) : AbstractContainerScope(testScope) {
@Suppress("FunctionName")
public suspend inline fun <reified TActualState : TState> ThenState(
vararg sideEffects: TSideEffect,
noinline test: suspend TestScope.(state: State<TActualState, TSideEffect>) -> Unit = {},
) {
var name = "State should be [${TActualState::class.simpleName}]"
if (sideEffects.isNotEmpty()) {
name += " with sideEffect(s) ${sideEffects.map { it::class.java.simpleName }}"
}

registerTest(
TestName("Then: ", name, true),
false,
null,
) {
val state = (this@ViewModelWhenContainerScope.viewModel.state as StateFlow).value
state.view.shouldBeInstanceOf<TActualState>()
sideEffects.forEach {
state.sideEffects.shouldContain(it)
}

test((this@ViewModelWhenContainerScope.viewModel.state as StateFlow).value as State<TActualState, TSideEffect>)
}
}

@Suppress("FunctionName")
public suspend fun AndIntent(
intent: TIntent,
test: suspend ViewModelWhenContainerScope<TIntent, TState, TSideEffect, T>.() -> Unit,
) {
registerContainer(
TestName("And: ", "${intent::class.simpleName} intent is executed", true),
false,
null,
) {
this@ViewModelWhenContainerScope.viewModel.execute(intent)
ViewModelWhenContainerScope(
this,
viewModel = this@ViewModelWhenContainerScope.viewModel,
).test()
}
}
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ dependencyResolutionManagement {
rootProject.name = "mvi"
include(":mvi")
include(":mvi-compose")
include(":mvi-kotest")

0 comments on commit 881b218

Please sign in to comment.