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
62 changes: 48 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ on:
pull_request:
branches: [main]
# Skip CI when a PR only touches documentation / media / repo metadata.
# Spotless, JVM tests, WASM compile, and iOS compile all only care about
# actual source code and build configuration. paths-ignore is matched
# against the entire PR diff — a PR that edits README AND a .kt file
# still runs CI, because the .kt file is not in this list.
# Spotless and the per-platform test jobs all only care about actual source
# code and build configuration. paths-ignore is matched against the entire
# PR diff — a PR that edits README AND a .kt file still runs CI, because the
# .kt file is not in this list.
paths-ignore:
- '**/*.md'
- 'docs/**'
Expand All @@ -21,8 +21,12 @@ concurrency:
cancel-in-progress: true

jobs:
validate:
name: Validate (JVM, Lint, WASM)
# Lint + JVM/JS/WASM tests. Every target below runs the shared commonTest
# suite (which includes the end-to-end "user can draw a stroke" tests), so a
# green run proves drawing actually works on that platform — not just that it
# compiles.
test:
name: Test (JVM, JS, WASM) + Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
Expand All @@ -32,14 +36,30 @@ jobs:
distribution: temurin
java-version: 21

# JS and WASM browser tests run under Karma against headless Chrome, so a
# real browser must be on PATH for the runner to launch it. Export its
# path as CHROME_BIN, which karma-chrome-launcher uses to locate Chrome.
- name: Setup Chrome
id: setup-chrome
uses: browser-actions/setup-chrome@v1

- name: Expose Chrome to Karma
run: echo "CHROME_BIN=${{ steps.setup-chrome.outputs.chrome-path }}" >> "$GITHUB_ENV"

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6

- name: Spotless check
run: ./gradlew :DrawBox:spotlessCheck
run: ./gradlew :DrawBox:spotlessCheck :drawbox-ui:spotlessCheck

- name: JVM tests
run: ./gradlew :DrawBox:jvmTest
run: ./gradlew :DrawBox:jvmTest :drawbox-ui:jvmTest

- name: JS (browser) tests
run: ./gradlew :DrawBox:jsTest :drawbox-ui:jsTest

- name: WASM (browser) tests
run: ./gradlew :DrawBox:wasmJsTest :drawbox-ui:wasmJsTest

- name: WASM sample build
run: ./gradlew :webApp:wasmJsBrowserDistribution
Expand All @@ -48,12 +68,16 @@ jobs:
if: failure()
uses: actions/upload-artifact@v7
with:
name: jvm-test-reports
path: DrawBox/build/reports/tests/jvmTest
name: multiplatform-test-reports
path: |
DrawBox/build/reports/tests
drawbox-ui/build/reports/tests
if-no-files-found: ignore

ios-compile:
name: iOS compile check
# iOS runs the same commonTest suite on the Kotlin/Native simulator target,
# so drawing is verified on Apple platforms too — not just compiled.
ios-test:
name: iOS test (simulator)
runs-on: macos-latest
steps:
- uses: actions/checkout@v7
Expand All @@ -66,5 +90,15 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6

- name: Compile iosSimulatorArm64
run: ./gradlew :DrawBox:compileKotlinIosSimulatorArm64
- name: iOS simulator tests
run: ./gradlew :DrawBox:iosSimulatorArm64Test :drawbox-ui:iosSimulatorArm64Test

- name: Upload test reports
if: failure()
uses: actions/upload-artifact@v7
with:
name: ios-test-reports
path: |
DrawBox/build/reports/tests
drawbox-ui/build/reports/tests
if-no-files-found: ignore
15 changes: 15 additions & 0 deletions DrawBox/karma.config.d/no-sandbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Kotlin/JS and Kotlin/WASM browser tests run under Karma against headless
// Chrome. On Linux CI runners Chrome's setuid sandbox cannot initialize
// (ZygoteHostImpl fatal -> "cannot start"), so launch with --no-sandbox.
// Harmless on local macOS/Windows runs. The Kotlin Gradle plugin concatenates
// every file in this directory into the generated Karma config, where `config`
// is in scope.
config.set({
browsers: ["ChromeHeadlessNoSandbox"],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: "ChromeHeadless",
flags: ["--no-sandbox"],
},
},
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.ak1.drawbox.presentation.viewmodel

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import io.ak1.drawbox.domain.model.Element
import io.ak1.drawbox.domain.model.Intent
import io.ak1.drawbox.domain.model.Mode
import io.ak1.drawbox.domain.usecase.UseCase
import io.ak1.drawbox.presentation.reducer.Reducer
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand All @@ -12,13 +15,56 @@ import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

@OptIn(ExperimentalCoroutinesApi::class)
class DrawBoxControllerTest {

private fun newController() = DrawBoxController(Reducer(UseCase()))

// ===== End-to-end "user draws a stroke" =====
//
// These drive the same intent sequence the DrawBox gesture layer dispatches
// when a user presses, drags, and lifts in PEN mode. They live in commonTest
// so every published target — JVM, JS, WASM, iOS — proves a user can actually
// draw, not merely that the code compiles.

@Test
fun userCanDrawAFreehandStroke() {
val controller = newController()
controller.setMode(Mode.PEN)

// Press down, then drag through a few points and lift.
controller.onIntent(Intent.InsertNewPath(Offset(10f, 10f)))
controller.onIntent(Intent.UpdateLatestPath(Offset(20f, 15f)))
controller.onIntent(Intent.UpdateLatestPath(Offset(30f, 25f)))
controller.onIntent(Intent.UpdateLatestPath(Offset(40f, 40f)))

val elements = controller.state.value.elements
assertEquals(1, elements.size, "the drag should have produced exactly one stroke")
val path = elements[0] as Element.Path
assertEquals(4, path.samples.size, "seed point plus three drag samples")
assertEquals(Offset(10f, 10f), path.samples.first().position)
assertEquals(Offset(40f, 40f), path.samples.last().position)
assertTrue(controller.canUndo.value, "a finished stroke must be undoable")
}

@Test
fun undoRemovesADrawnStroke() {
val controller = newController()
controller.setMode(Mode.PEN)
controller.onIntent(Intent.InsertNewPath(Offset(0f, 0f)))
controller.onIntent(Intent.UpdateLatestPath(Offset(50f, 50f)))
assertEquals(1, controller.state.value.elements.size)

controller.undo()

assertTrue(controller.state.value.elements.isEmpty(), "undo should clear the stroke")
assertFalse(controller.canUndo.value)
assertTrue(controller.canRedo.value, "the undone stroke should be redoable")
}

@Test
fun intentsFlowEmitsEveryProcessedIntent() = runTest(StandardTestDispatcher()) {
val controller = newController()
Expand Down
15 changes: 15 additions & 0 deletions drawbox-ui/karma.config.d/no-sandbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Kotlin/JS and Kotlin/WASM browser tests run under Karma against headless
// Chrome. On Linux CI runners Chrome's setuid sandbox cannot initialize
// (ZygoteHostImpl fatal -> "cannot start"), so launch with --no-sandbox.
// Harmless on local macOS/Windows runs. The Kotlin Gradle plugin concatenates
// every file in this directory into the generated Karma config, where `config`
// is in scope.
config.set({
browsers: ["ChromeHeadlessNoSandbox"],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: "ChromeHeadless",
flags: ["--no-sandbox"],
},
},
});
Loading