Skip to content

Commit f79f8fb

Browse files
committed
test(compose): guard the reactive contract for frozen parameters and key re-fire
1 parent 9690d9f commit f79f8fb

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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+
@file:OptIn(ExperimentalTestApi::class)
17+
18+
package io.github.compose.jindong
19+
20+
import androidx.compose.runtime.CompositionLocalProvider
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.ui.test.ExperimentalTestApi
24+
import androidx.compose.ui.test.runComposeUiTest
25+
import io.github.compose.jindong.core.ms
26+
import io.github.compose.jindong.dsl.Haptic
27+
import io.github.compose.jindong.executor.LocalHapticExecutor
28+
import io.github.compose.jindong.executor.RecordingHapticExecutor
29+
import io.kotest.core.spec.style.FunSpec
30+
import io.kotest.matchers.shouldBe
31+
32+
/**
33+
* Guards the reactive contract at the [Jindong] level: keys are the only playback trigger, and a
34+
* value read inside `content` but left out of the keys is frozen at the last key change.
35+
*
36+
* The Clip-level counterpart lives in [ClipTest]; this covers a parameter read directly by an
37+
* inline node.
38+
*/
39+
class JindongContractTest :
40+
FunSpec({
41+
test("a parameter read inside content but absent from keys does not re-fire") {
42+
runComposeUiTest {
43+
val recorder = RecordingHapticExecutor()
44+
val triggerKey = mutableStateOf(0)
45+
val durationMs = mutableStateOf(50)
46+
47+
setContent {
48+
val key by triggerKey
49+
val duration by durationMs
50+
CompositionLocalProvider(LocalHapticExecutor provides recorder) {
51+
// duration shapes the pattern but is not a key, so changing it must not fire.
52+
Jindong(key) {
53+
Haptic(duration.ms)
54+
}
55+
}
56+
}
57+
58+
waitForIdle()
59+
recorder.executedPatterns.size shouldBe 1
60+
recorder.executedPatterns.last().events.single().durationMs shouldBe 50
61+
62+
durationMs.value = 200
63+
waitForIdle()
64+
65+
// Contract: only a key change triggers playback. Mutating a parameter leaves the executor
66+
// untouched, so no new pattern is recorded.
67+
recorder.executedPatterns.size shouldBe 1
68+
recorder.executedPatterns.last().events.single().durationMs shouldBe 50
69+
}
70+
}
71+
72+
test("a key change re-fires and recompiles content with the current parameter") {
73+
runComposeUiTest {
74+
val recorder = RecordingHapticExecutor()
75+
val triggerKey = mutableStateOf(0)
76+
val durationMs = mutableStateOf(50)
77+
78+
setContent {
79+
val key by triggerKey
80+
val duration by durationMs
81+
CompositionLocalProvider(LocalHapticExecutor provides recorder) {
82+
Jindong(key) {
83+
Haptic(duration.ms)
84+
}
85+
}
86+
}
87+
88+
waitForIdle()
89+
recorder.executedPatterns.size shouldBe 1
90+
recorder.executedPatterns.last().events.single().durationMs shouldBe 50
91+
92+
// Update the parameter first: it stays frozen until a key change picks it up.
93+
durationMs.value = 200
94+
triggerKey.value = 1
95+
waitForIdle()
96+
97+
// The key changed, so playback fires again and the recompiled pattern reflects the
98+
// parameter value read at that point.
99+
recorder.executedPatterns.size shouldBe 2
100+
recorder.executedPatterns.last().events.single().durationMs shouldBe 200
101+
}
102+
}
103+
})

0 commit comments

Comments
 (0)