Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .claude/skills/android-testing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,42 @@ description: Testing strategies for Android/KMP. Use when creating or reviewing
- Path: `src/test/java/...` espelhando o pacote do arquivo original
- Todo novo componente deve ter teste unitário cobrindo: construção, defaults, `copy()`, `equals/hashCode`, e o `craft()` do builder

## Testes gerados por IA (CI / Claude API)

O workflow de CI pode gerar testes automaticamente via Claude API. Esses testes **frequentemente falham ao compilar ou rodar** e precisam de revisão. Checklist de problemas recorrentes:

### Erros de compilação

| Problema | Sintoma | Fix |
|---|---|---|
| Code fence markdown | Arquivo começa com ` ```kotlin ` | Remover ` ```kotlin ` e ` ``` ` do início/fim |
| MockK: `throws()` solto | `mockk<Foo> { throws(...) }` | Usar `mockk<Foo>(); every { mock.prop } throws ...` |
| `apply { val = }` em data class | `copy().apply { valProp = ... }` | `val` não é mutável — usar `copy(prop = ...)` |
| Enum inexistente | `CraftDAlign.START`, `CraftDTextStyle.REGULAR` | Verificar o enum real e substituir |
| AbstractMap entries incompat. | Override de `entries` com tipo errado | Remover o objeto anônimo se for código morto |
| `assertNotEquals` para ref. | `assertNotEquals(a as Any, b as Any)` quando `a == b` | Usar `assertTrue(a !== b)` |
| Type inference: `assertNotEquals` | Dois tipos diferentes sem parâmetro explícito | `assertNotEquals<Any?>(a, b)` |

### Erros de dependências em `build.gradle.kts`

Para o sourceSet `androidUnitTest` (path `src/test/java/`), garantir:
```kotlin
androidUnitTest.dependencies {
implementation(libs.junit)
implementation(libs.mockk)
implementation(kotlin("test-junit"))
}
```

### Erros de runtime

| Problema | Sintoma | Fix |
|---|---|---|
| Anotações `@Stable`/`@Immutable`/`@Serializable` | `assertTrue(isStable)` falha em runtime | Retenção BINARY — não visível via reflection. Remover o teste |
| Mock de extension function | `MockKException: Missing mocked calls` em `every { stream.bufferedReader() }` | Extension functions Kotlin precisam de `mockkStatic`. Alternativa: usar `ByteArrayInputStream` |
| Jackson `convertValue` com String | `ClassCastException` ao tentar `jsonString.convertToVO<Foo>()` | `convertValue` não parseia JSON string — remover o teste ou usar `readValue` |
| Jackson `convertValue` com List genérica | Retorna `List<LinkedHashMap>` em vez de `List<Foo>` | Genéricos apagados em runtime — remover o teste |

## Screenshot tests (Roborazzi)

```bash
Expand Down
6 changes: 6 additions & 0 deletions android_kmp/craftd-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ kotlin {
publishLibraryVariants("release", "debug")
}
sourceSets {
androidUnitTest.dependencies {
implementation(libs.junit)
implementation(libs.mockk)
implementation(kotlin("test-junit"))
}

androidMain.dependencies {
implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package com.github.codandotv.craftd.androidcore

import androidx.recyclerview.widget.DiffUtil
import com.github.codandotv.craftd.androidcore.data.model.base.SimpleProperties
import io.mockk.every
import io.mockk.mockk
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import java.util.AbstractMap

@RunWith(JUnit4::class)
class CraftDSimplePropertiesDiffCallbackTest {

private val callback = CraftDSimplePropertiesItemCallback

@Test
fun `given same key when areItemsTheSame then returns true`() {
val oldItem = SimpleProperties(key = "test_key", value = JsonPrimitive("value1"))
val newItem = SimpleProperties(key = "test_key", value = JsonPrimitive("value2"))

val result = callback.areItemsTheSame(oldItem, newItem)

assertTrue(result)
}

@Test
fun `given different key when areItemsTheSame then returns false`() {
val oldItem = SimpleProperties(key = "key1", value = JsonPrimitive("value"))
val newItem = SimpleProperties(key = "key2", value = JsonPrimitive("value"))

val result = callback.areItemsTheSame(oldItem, newItem)

assertFalse(result)
}

@Test
fun `given exception during key comparison when areItemsTheSame then returns false`() {
val mockOldItem = mockk<SimpleProperties>()
every { mockOldItem.key } throws RuntimeException("Test exception")
val newItem = SimpleProperties(key = "key", value = JsonPrimitive("value"))

val result = callback.areItemsTheSame(mockOldItem, newItem)

assertFalse(result)
}

@Test
fun `given identical primitive values when areContentsTheSame then returns true`() {
val value = JsonPrimitive("same_value")
val oldItem = SimpleProperties(key = "key1", value = value)
val newItem = SimpleProperties(key = "key1", value = value)

val result = callback.areContentsTheSame(oldItem, newItem)

assertTrue(result)
}

@Test
fun `given different primitive values when areContentsTheSame then returns false`() {
val oldItem = SimpleProperties(key = "key1", value = JsonPrimitive("value1"))
val newItem = SimpleProperties(key = "key1", value = JsonPrimitive("value2"))

val result = callback.areContentsTheSame(oldItem, newItem)

assertFalse(result)
}

@Test
fun `given identical AbstractMap values when areContentsTheSame then returns true`() {
val value = JsonPrimitive("same_value")
val oldItem = SimpleProperties(key = "key1", value = value)
val newItem = SimpleProperties(key = "key1", value = value)

val result = callback.areContentsTheSame(oldItem, newItem)

assertTrue(result)
}

@Test
fun `given different AbstractMap values when areContentsTheSame then returns false`() {
val oldItem = SimpleProperties(key = "key1", value = JsonPrimitive("value1"))
val newItem = SimpleProperties(key = "key1", value = JsonPrimitive("value2"))

val result = callback.areContentsTheSame(oldItem, newItem)

assertFalse(result)
}

@Test
fun `given null values when areContentsTheSame then returns true`() {
val oldItem = SimpleProperties(key = "key1", value = JsonPrimitive(null))
val newItem = SimpleProperties(key = "key1", value = JsonPrimitive(null))

val result = callback.areContentsTheSame(oldItem, newItem)

assertTrue(result)
}

@Test
fun `given one null and one non-null value when areContentsTheSame then returns false`() {
val oldItem = SimpleProperties(key = "key1", value = JsonPrimitive(null))
val newItem = SimpleProperties(key = "key1", value = JsonPrimitive("value"))

val result = callback.areContentsTheSame(oldItem, newItem)

assertFalse(result)
}

@Test
fun `given numeric JsonElement values when areContentsTheSame then returns true`() {
val value = JsonPrimitive(42)
val oldItem = SimpleProperties(key = "key1", value = value)
val newItem = SimpleProperties(key = "key1", value = value)

val result = callback.areContentsTheSame(oldItem, newItem)

assertTrue(result)
}

@Test
fun `given boolean JsonElement values when areContentsTheSame then returns true`() {
val value = JsonPrimitive(true)
val oldItem = SimpleProperties(key = "key1", value = value)
val newItem = SimpleProperties(key = "key1", value = value)

val result = callback.areContentsTheSame(oldItem, newItem)

assertTrue(result)
}

@Test
fun `given callback is ItemCallback instance then callback is properly initialized`() {
assertTrue(callback is DiffUtil.ItemCallback<SimpleProperties>)
}

@Test
fun `given different numeric values when areContentsTheSame then returns false`() {
val oldItem = SimpleProperties(key = "key1", value = JsonPrimitive(42))
val newItem = SimpleProperties(key = "key1", value = JsonPrimitive(43))

val result = callback.areContentsTheSame(oldItem, newItem)

assertFalse(result)
}

@Test
fun `given empty AbstractMap when areContentsTheSame with AbstractMap comparison then returns true`() {
val oldItem = SimpleProperties(key = "key1", value = JsonPrimitive("{}"))
val newItem = SimpleProperties(key = "key1", value = JsonPrimitive("{}"))

val result = callback.areContentsTheSame(oldItem, newItem)

assertTrue(result)
}

@Test
fun `given SimpleProperties with all parameters when constructing then all fields are set correctly`() {
val key = "test_key"
val value = JsonPrimitive("test_value")

val simpleProperties = SimpleProperties(key = key, value = value)

assertTrue(simpleProperties.key == key)
assertTrue(simpleProperties.value == value)
}

@Test
fun `given SimpleProperties with copy when copying with different key then new instance has different key`() {
val original = SimpleProperties(key = "original_key", value = JsonPrimitive("value"))
val copied = original.copy(key = "new_key")

assertTrue(copied.key == "new_key")
assertTrue(original.key == "original_key")
}

@Test
fun `given two SimpleProperties with same values when comparing with equals then returns true`() {
val properties1 = SimpleProperties(key = "key", value = JsonPrimitive("value"))
val properties2 = SimpleProperties(key = "key", value = JsonPrimitive("value"))

assertTrue(properties1 == properties2)
}

@Test
fun `given two SimpleProperties with different values when comparing with equals then returns false`() {
val properties1 = SimpleProperties(key = "key1", value = JsonPrimitive("value"))
val properties2 = SimpleProperties(key = "key2", value = JsonPrimitive("value"))

assertFalse(properties1 == properties2)
}

@Test
fun `given two SimpleProperties with same values when comparing hashCode then returns same hash`() {
val properties1 = SimpleProperties(key = "key", value = JsonPrimitive("value"))
val properties2 = SimpleProperties(key = "key", value = JsonPrimitive("value"))

assertTrue(properties1.hashCode() == properties2.hashCode())
}

private class SimpleEntry<K, V>(
override val key: K,
override val value: V
) : Map.Entry<K, V>

}
Loading
Loading