Skip to content

Commit e8a3f9e

Browse files
wisemujil2hyunwoo
andauthored
test: add unit tests for jindong-core module (#75)
* test: add HapticPatternScopeTest * test: add DelayElementTest * test: add PatternElementTest * test: add RepeatElementTest * test: add SequenceElementTest * test: add VibrationElementTest * test: add HapticIntensityTest * test: add DurationExtensionsTest * test: add HapticManagerTest with fake object * Update jindong-core/src/commonTest/kotlin/io/github/compose/jindong/core/dsl/HapticPatternScopeTest.kt Co-authored-by: HyunWoo Lee (Nunu Lee) <54518925+l2hyunwoo@users.noreply.github.com> * fix: initialize executor on internal tests without changing the public API * fix: remove redundant manager release logic --------- Co-authored-by: HyunWoo Lee (Nunu Lee) <54518925+l2hyunwoo@users.noreply.github.com>
1 parent 660fb6b commit e8a3f9e

11 files changed

Lines changed: 1431 additions & 1 deletion

File tree

jindong-core/src/commonMain/kotlin/io/github/compose/jindong/core/HapticManager.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ object HapticManager {
9999
fun initialize(context: Any? = null) {
100100
withStateLockBlocking {
101101
if (executor == null) {
102-
executor = createHapticExecutor(context)
102+
initializeExecutor(createHapticExecutor(context))
103103
}
104104
}
105105
}
@@ -172,6 +172,12 @@ object HapticManager {
172172
}
173173
}
174174

175+
internal fun initializeExecutor(executor: HapticExecutor) {
176+
withStateLockBlocking {
177+
this.executor = executor
178+
}
179+
}
180+
175181
private suspend fun <T> withStateLock(action: () -> T): T = stateMutex.withLock { action() }
176182

177183
private fun <T> withStateLockBlocking(action: () -> T): T = runBlocking {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (C) 2026 compose-jindong
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.compose.jindong.core
17+
18+
import io.kotest.assertions.assertSoftly
19+
import io.kotest.core.spec.style.FunSpec
20+
import io.kotest.matchers.shouldBe
21+
import kotlin.time.Duration.Companion.milliseconds
22+
import kotlin.time.Duration.Companion.seconds
23+
24+
class DurationExtensionsTest :
25+
FunSpec({
26+
context("milliseconds extensions") {
27+
test("Int.ms should create Duration in milliseconds") {
28+
assertSoftly {
29+
100.ms shouldBe 100.milliseconds
30+
0.ms shouldBe 0.milliseconds
31+
1500.ms shouldBe 1500.milliseconds
32+
Int.MAX_VALUE.ms shouldBe Int.MAX_VALUE.milliseconds
33+
}
34+
}
35+
36+
test("Long.ms should create Duration in milliseconds") {
37+
assertSoftly {
38+
100L.ms shouldBe 100.milliseconds
39+
0L.ms shouldBe 0.milliseconds
40+
1500L.ms shouldBe 1500.milliseconds
41+
Long.MAX_VALUE.ms shouldBe Long.MAX_VALUE.milliseconds
42+
}
43+
}
44+
}
45+
46+
context("conversion consistency") {
47+
test("500.ms should equal 0.5.seconds") {
48+
500.ms shouldBe 0.5.seconds
49+
}
50+
51+
test("1000.ms should equal 1.seconds") {
52+
1000.ms shouldBe 1.seconds
53+
}
54+
}
55+
})
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright (C) 2026 compose-jindong
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.compose.jindong.core
17+
18+
import io.github.compose.jindong.core.dsl.buildHapticPattern
19+
import io.github.compose.jindong.core.fake.FakeHapticExecutor
20+
import io.github.compose.jindong.core.model.HapticIntensity
21+
import io.kotest.assertions.assertSoftly
22+
import io.kotest.assertions.throwables.shouldNotThrowAny
23+
import io.kotest.core.spec.style.FunSpec
24+
import io.kotest.matchers.collections.shouldBeEmpty
25+
import io.kotest.matchers.collections.shouldHaveSize
26+
import io.kotest.matchers.shouldBe
27+
28+
class HapticManagerTest :
29+
FunSpec({
30+
lateinit var fakeExecutor: FakeHapticExecutor
31+
32+
beforeEach {
33+
fakeExecutor = FakeHapticExecutor()
34+
HapticManager.initializeExecutor(fakeExecutor)
35+
}
36+
37+
afterEach {
38+
HapticManager.release()
39+
}
40+
41+
test("isSupported should return true when executor supports haptics") {
42+
HapticManager.isSupported shouldBe true
43+
}
44+
45+
test("isSupported should return false when executor does not support haptics") {
46+
HapticManager.initializeExecutor(FakeHapticExecutor(isSupported = false))
47+
48+
HapticManager.isSupported shouldBe false
49+
}
50+
51+
test("execute should execute pattern synchronously") {
52+
val pattern = buildHapticPattern {
53+
haptic(100.ms)
54+
}
55+
56+
HapticManager.execute(pattern)
57+
58+
assertSoftly {
59+
fakeExecutor.executedPatterns shouldHaveSize 1
60+
fakeExecutor.executedPatterns[0] shouldBe pattern
61+
}
62+
}
63+
64+
test("execute should handle empty pattern") {
65+
val emptyPattern = buildHapticPattern { }
66+
67+
HapticManager.execute(emptyPattern)
68+
69+
assertSoftly {
70+
fakeExecutor.executedPatterns shouldHaveSize 1
71+
fakeExecutor.executedPatterns[0].events.shouldBeEmpty()
72+
}
73+
}
74+
75+
test("executeAsync should execute pattern and return handle") {
76+
val pattern = buildHapticPattern {
77+
haptic(100.ms, HapticIntensity.STRONG)
78+
}
79+
80+
val handle = HapticManager.executeAsync(pattern)
81+
82+
assertSoftly {
83+
fakeExecutor.asyncExecutedPatterns shouldHaveSize 1
84+
fakeExecutor.asyncExecutedPatterns[0] shouldBe pattern
85+
handle.isActive shouldBe true
86+
}
87+
}
88+
89+
test("executeAsync should cancel previous handle when executing new pattern") {
90+
val pattern1 = buildHapticPattern { haptic(100.ms) }
91+
val pattern2 = buildHapticPattern { haptic(200.ms) }
92+
93+
val handle1 = HapticManager.executeAsync(pattern1)
94+
HapticManager.executeAsync(pattern2)
95+
96+
assertSoftly {
97+
fakeExecutor.asyncExecutedPatterns shouldHaveSize 2
98+
handle1.isActive shouldBe false
99+
}
100+
}
101+
102+
test("cancel should cancel current execution") {
103+
val pattern = buildHapticPattern { haptic(100.ms) }
104+
val handle = HapticManager.executeAsync(pattern)
105+
106+
HapticManager.cancel()
107+
108+
handle.isActive shouldBe false
109+
}
110+
111+
test("cancel should not throw when no active execution exists") {
112+
shouldNotThrowAny {
113+
HapticManager.cancel()
114+
}
115+
}
116+
117+
test("release should release executor resources") {
118+
HapticManager.release()
119+
120+
fakeExecutor.releaseCalled shouldBe true
121+
}
122+
123+
test("release should cancel active execution") {
124+
val pattern = buildHapticPattern { haptic(100.ms) }
125+
val handle = HapticManager.executeAsync(pattern)
126+
127+
HapticManager.release()
128+
129+
assertSoftly {
130+
handle.isActive shouldBe false
131+
fakeExecutor.releaseCalled shouldBe true
132+
}
133+
}
134+
135+
test("should allow reuse after release") {
136+
val firstExecutor = FakeHapticExecutor()
137+
HapticManager.initializeExecutor(firstExecutor)
138+
HapticManager.release()
139+
140+
val secondExecutor = FakeHapticExecutor()
141+
HapticManager.initializeExecutor(secondExecutor)
142+
val pattern = buildHapticPattern { haptic(100.ms) }
143+
HapticManager.executeAsync(pattern)
144+
145+
assertSoftly {
146+
firstExecutor.releaseCalled shouldBe true
147+
firstExecutor.asyncExecutedPatterns.shouldBeEmpty()
148+
secondExecutor.asyncExecutedPatterns shouldHaveSize 1
149+
}
150+
}
151+
})

0 commit comments

Comments
 (0)