Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] add LocalClock to control time for screenshot test #1224

Merged
merged 14 commits into from
Sep 30, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import io.github.droidkaigi.confsched2023.data.di.AppAndroidBuildConfig
import io.github.droidkaigi.confsched2023.model.BuildConfigProvider
import kotlinx.datetime.Clock
import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
Expand All @@ -15,9 +16,19 @@ class AppModule {
@Singleton
@AppAndroidBuildConfig
fun provideBuildConfigProvider(): BuildConfigProvider = AppBuildConfigProvider()

@Provides
@Singleton
fun provideClockProvider(): ClockProvider = object : ClockProvider {
override fun clock(): Clock = Clock.System
}
}

class AppBuildConfigProvider(
override val versionName: String = BuildConfig.VERSION_NAME,
override val debugBuild: Boolean = BuildConfig.DEBUG,
) : BuildConfigProvider

interface ClockProvider {
fun clock(): Clock
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
Expand All @@ -19,12 +20,18 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.window.layout.DisplayFeature
import androidx.window.layout.WindowInfoTracker
import dagger.hilt.android.AndroidEntryPoint
import io.github.droidkaigi.confsched2023.ui.compositionlocal.LocalClock
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

@Inject
lateinit var clockProvider: ClockProvider

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
Expand Down Expand Up @@ -55,10 +62,12 @@ class MainActivity : ComponentActivity() {
setContent {
val windowSize = calculateWindowSizeClass(this)
val displayFeatures = calculateDisplayFeatures(this)
KaigiApp(
windowSize = windowSize,
displayFeatures = displayFeatures,
)
CompositionLocalProvider(LocalClock provides clockProvider.clock()) {
KaigiApp(
windowSize = windowSize,
displayFeatures = displayFeatures,
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.droidkaigi.confsched2023

import dagger.Module
import dagger.Provides
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import javax.inject.Singleton

@TestInstallIn(
components = [SingletonComponent::class],
replaces = [AppModule::class],
)
@Module
class FakeAppModule {
@Provides
@Singleton
fun provideClockProvider(): ClockProvider = object : ClockProvider {
override fun clock(): Clock = object : Clock {
override fun now() = Instant.parse("2023-09-14T10:00:00.00Z")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,11 @@ public enum class DroidKaigi2023Day(
/**
* @return appropriate initial day for now
*/
fun initialSelectedDay(isTest: Boolean = false): DroidKaigi2023Day {
// Timetable tab set initial tab with current date.
// To get the consistent test result, fix selected timetable tab to Day1 here.
if (isTest) return Day1
fun initialSelectedDay(clock: Clock): DroidKaigi2023Day {
val reversedEntries = entries.sortedByDescending { it.day }
var selectedDay = reversedEntries.last()
for (entry in reversedEntries) {
if (Clock.System.now() <= entry.end) selectedDay = entry
if (clock.now() <= entry.end) selectedDay = entry
}
return selectedDay
}
Expand Down
1 change: 1 addition & 0 deletions core/testing/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies {
implementation(projects.core.model)
implementation(projects.core.designsystem)
implementation(projects.core.data)
implementation(projects.core.ui)
implementation(projects.feature.main)
implementation(projects.feature.sessions)
implementation(projects.feature.about)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.droidkaigi.confsched2023.testing.robot

import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.isRoot
Expand All @@ -25,6 +26,8 @@ import io.github.droidkaigi.confsched2023.sessions.component.TimetableUiTypeChan
import io.github.droidkaigi.confsched2023.sessions.section.TimetableTabTestTag
import io.github.droidkaigi.confsched2023.testing.RobotTestRule
import io.github.droidkaigi.confsched2023.testing.coroutines.runTestWithLogging
import io.github.droidkaigi.confsched2023.ui.compositionlocal.FakeClock
import io.github.droidkaigi.confsched2023.ui.compositionlocal.LocalClock
import kotlinx.coroutines.test.TestDispatcher
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
Expand All @@ -50,12 +53,14 @@ class TimetableScreenRobot @Inject constructor(

fun setupTimetableScreenContent() {
composeTestRule.setContent {
KaigiTheme {
TimetableScreen(
onSearchClick = { },
onTimetableItemClick = { },
onBookmarkIconClick = { },
)
CompositionLocalProvider(LocalClock provides FakeClock) {
Copy link
Contributor Author

@tkhs0604 tkhs0604 Sep 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@takahirom
It does not seem like it works well...
Do you know how I can overwrite the CompositionLocal in test environment? 🤔

Maybe, since there is no setContent in KaigiAppTest, so the only way is to add like ClockTestRule or something like that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure too.
By the way, you might have to fix the commit history 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I'll investigate the cause again 🙏
And regarding your BTW comment, which commits? All?

Copy link
Member

@takahirom takahirom Sep 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I might see old commits 🙇

Copy link
Contributor Author

@tkhs0604 tkhs0604 Sep 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FakeClock worked well for only TimetableScreenTest on my local, but the screenshot results haven't been updated even if I pushed some commits 👀
Is there any way to update them on this PR's comment?

In case of 2023-09-14T10:00:00.000Z
TimetableScreenTest checkGridScrollShot_compare

In case of 2023-09-15T10:00:00.000Z (To check the diff)
TimetableScreenTest checkGridScrollShot_compare

Copy link
Member

@takahirom takahirom Sep 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the comment and rerun the test. Let's see what screenshots the tests take.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image It seems that there is no change 🎉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! 🙌

KaigiTheme {
TimetableScreen(
onSearchClick = { },
onTimetableItemClick = { },
onBookmarkIconClick = { },
)
}
}
}
waitUntilIdle()
Expand Down
1 change: 1 addition & 0 deletions core/ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ kotlin {
implementation(libs.kermit)
api(projects.core.common)
api(libs.composeImageLoader)
api(libs.kotlinxDatetime)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.github.droidkaigi.confsched2023.ui.compositionlocal

import androidx.compose.runtime.staticCompositionLocalOf
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant

@Suppress("CompositionLocalAllowlist")
val LocalClock = staticCompositionLocalOf<Clock> {
Clock.System
}

object FakeClock : Clock {
override fun now(): Instant = Instant.parse("2023-09-14T10:00:00.000Z")
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
Expand Down Expand Up @@ -51,6 +52,8 @@ import io.github.droidkaigi.confsched2023.sessions.section.TimetableListUiState
import io.github.droidkaigi.confsched2023.sessions.section.TimetableSheet
import io.github.droidkaigi.confsched2023.sessions.section.TimetableSheetUiState
import io.github.droidkaigi.confsched2023.ui.SnackbarMessageEffect
import io.github.droidkaigi.confsched2023.ui.compositionlocal.FakeClock
import io.github.droidkaigi.confsched2023.ui.compositionlocal.LocalClock
import kotlinx.collections.immutable.toPersistentMap
import kotlin.math.roundToInt

Expand Down Expand Up @@ -249,32 +252,34 @@ private fun TimetableScreen(
@MultiThemePreviews
@Composable
fun PreviewTimetableScreenDark() {
KaigiTheme {
TimetableScreen(
uiState = TimetableScreenUiState(
contentUiState = TimetableSheetUiState.ListTimetable(
mapOf(
DroidKaigi2023Day.Day1 to TimetableListUiState(
mapOf<String, List<TimetableItem>>().toPersistentMap(),
Timetable(),
),
DroidKaigi2023Day.Day2 to TimetableListUiState(
mapOf<String, List<TimetableItem>>().toPersistentMap(),
Timetable(),
CompositionLocalProvider(LocalClock provides FakeClock) {
KaigiTheme {
TimetableScreen(
uiState = TimetableScreenUiState(
contentUiState = TimetableSheetUiState.ListTimetable(
mapOf(
DroidKaigi2023Day.Day1 to TimetableListUiState(
mapOf<String, List<TimetableItem>>().toPersistentMap(),
Timetable(),
),
DroidKaigi2023Day.Day2 to TimetableListUiState(
mapOf<String, List<TimetableItem>>().toPersistentMap(),
Timetable(),
),
),
),
timetableUiType = TimetableUiType.Grid,
onBookmarkIconClickStatus = false,
),
timetableUiType = TimetableUiType.Grid,
onBookmarkIconClickStatus = false,
),
snackbarHostState = SnackbarHostState(),
onTimetableItemClick = {},
onBookmarkClick = { _, _ -> },
onBookmarkIconClick = {},
onSearchClick = {},
onTimetableUiChangeClick = {},
onReachAnimationEnd = {},
modifier = Modifier.statusBarsPadding(),
)
snackbarHostState = SnackbarHostState(),
onTimetableItemClick = {},
onBookmarkClick = { _, _ -> },
onBookmarkIconClick = {},
onSearchClick = {},
onTimetableUiChangeClick = {},
onReachAnimationEnd = {},
modifier = Modifier.statusBarsPadding(),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import io.github.droidkaigi.confsched2023.sessions.component.rememberTimetableTa
import io.github.droidkaigi.confsched2023.sessions.section.TimetableSheetUiState.Empty
import io.github.droidkaigi.confsched2023.sessions.section.TimetableSheetUiState.GridTimetable
import io.github.droidkaigi.confsched2023.sessions.section.TimetableSheetUiState.ListTimetable
import io.github.droidkaigi.confsched2023.ui.isTest
import io.github.droidkaigi.confsched2023.ui.compositionlocal.LocalClock

const val TimetableTabTestTag = "TimetableTab"

Expand All @@ -60,7 +60,8 @@ fun TimetableSheet(
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
var selectedDay by rememberSaveable { mutableStateOf(DroidKaigi2023Day.initialSelectedDay(isTest())) }
val clock = LocalClock.current
var selectedDay by rememberSaveable { mutableStateOf(DroidKaigi2023Day.initialSelectedDay(clock)) }
val corner by animateIntAsState(
if (timetableScreenScrollState.isScreenLayoutCalculating || timetableScreenScrollState.isSheetExpandable) 40 else 0,
label = "Timetable corner state",
Expand Down
Loading