Skip to content
Open
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
26 changes: 26 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,29 @@ JAVA_HOME="/c/Program Files/Android/Android Studio/jbr" ./gradlew assembleDebug
```bash
"/c/Users/Durham/AppData/Local/Android/Sdk/platform-tools/adb.exe" install -r app/build/outputs/apk/debug/app-debug.apk
```

### Tests

```bash
# Unit tests only (no device required)
./gradlew testDebugUnitTest

# All tests (unit + instrumented, requires connected device)
./gradlew allTests
```

## PR Workflow

Before creating a PR, run the self-review cycle locally:

1. **Review local changes**: `REVIEW=$(./scripts/review-pr.sh --local --no-post)` — reviews the branch diff against master (use `--base <branch>` to diff against a different branch), saves to `.claude/reviews/`.
2. **Read the review file** (`cat "$REVIEW"`) and address every actionable item by editing the code and committing.
3. **Verify fixes**: `./scripts/review-check.sh --local --no-post "$REVIEW"` — confirm all items are addressed.
4. If any items remain open, go back to step 2.
5. Once all items are resolved, **create the PR** and post the review:
- `gh pr create --draft` (or `gh pr create` if ready)
- `./scripts/review-pr.sh --post` (runs a fresh review and posts to the PR)
- `./scripts/review-check.sh --post "$REVIEW"` (posts the resolution checklist)
- `gh pr ready` (if created as draft)

The `--local` flag works without a remote or PR. The `--no-post` / `--post` flags make scripts non-interactive for agent use.
86 changes: 86 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ android {
targetSdk = 34
versionCode = 1
versionName = "0.1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["notAnnotation"] = "com.writer.recognition.DevTool"
}

buildTypes {
Expand All @@ -39,8 +41,21 @@ android {
jvmTarget = "17"
}

testOptions {
unitTests {
isReturnDefaultValues = true
isIncludeAndroidResources = true
all {
it.jvmArgs(
"--add-opens", "java.base/jdk.internal.access=ALL-UNNAMED",
)
}
}
}

buildFeatures {
viewBinding = true
aidl = true
}

packaging {
Expand All @@ -50,6 +65,40 @@ android {
}
}

tasks.register("captureFixture") {
description = "Capture handwriting fixture from device: -PfixtureName=hello -PexpectedText=\"hello\""
dependsOn("installDebug", "installDebugAndroidTest")
doLast {
val name = project.property("fixtureName") as String
val text = project.property("expectedText") as String
val lang = project.findProperty("language") as? String ?: "en-US"
val line = project.findProperty("lineIndex") as? String ?: "0"
val adb = android.adbExecutable.absolutePath
val appId = "com.writer.dev"

exec {
commandLine(adb, "shell", "am", "instrument", "-w",
"-e", "class", "com.writer.recognition.StrokeFixtureCapture",
"-e", "fixtureName", name,
"-e", "expectedText", text,
"-e", "language", lang,
"-e", "lineIndex", line,
"$appId.test/androidx.test.runner.AndroidJUnitRunner")
}

exec {
commandLine(adb, "pull",
"/sdcard/Download/inkup-fixtures/$name.json",
"app/src/androidTest/assets/fixtures/$name.json")
}

exec {
commandLine(adb, "shell", "rm",
"/sdcard/Download/inkup-fixtures/$name.json")
}
}
}

configurations.all {
// Onyx SDK pulls in old pre-AndroidX support libraries that clash with AndroidX
exclude(group = "com.android.support", module = "support-compat")
Expand Down Expand Up @@ -86,4 +135,41 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("com.google.android.material:material:1.12.0")

// Testing
testImplementation("junit:junit:4.13.2")
testImplementation("org.json:json:20231013")
testImplementation("org.robolectric:robolectric:4.14.1")

androidTestImplementation("androidx.test:runner:1.6.2")
androidTestImplementation("androidx.test:rules:1.6.1")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1")
}

tasks.withType<Test> {
testLogging {
events(org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED)
showStandardStreams = false
}
addTestListener(object : TestListener {
override fun beforeSuite(suite: TestDescriptor) {}
override fun afterSuite(suite: TestDescriptor, result: TestResult) {
if (suite.parent == null) {
println("${result.resultType}: ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped")
}
}
override fun beforeTest(testDescriptor: TestDescriptor) {}
override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) {}
})
}

tasks.register("allTests") {
description = "Runs all tests: unit tests and instrumented tests on connected device"
group = "verification"
dependsOn("testDebugUnitTest", "connectedDebugAndroidTest")
}
// Run unit tests first — fail fast before slower device tests
tasks.matching { it.name == "connectedDebugAndroidTest" }.configureEach {
mustRunAfter("testDebugUnitTest")
}
Loading
Loading