diff --git a/README.md b/README.md index 9a7c0bab1..fd10aa6a5 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,6 @@ See our [Migration Guide](https://google.github.io/accompanist/navigation-materi ### ๐Ÿซ [System UI Controller](./systemuicontroller/) (Deprecated) We recommend migrating to edge to edge. See our [Migration Guide](https://google.github.io/accompanist/systemuicontroller/) for more details. -### ๐Ÿ—œ [Test Harness](./testharness/) (Deprecated) -Utilities for testing Compose layouts. - --- ## Future? diff --git a/docs/testharness.md b/docs/testharness.md deleted file mode 100644 index 84ab4c31d..000000000 --- a/docs/testharness.md +++ /dev/null @@ -1,188 +0,0 @@ -# Test Harness for Jetpack Compose - -[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-testharness)](https://search.maven.org/search?q=g:com.google.accompanist) - -!!! warning - **This library is deprecated, with a superseding version in androidx.compose.ui.test**. The migration guide and original documentation is below. - -## Migration - -`DeviceConfigurationOverride` from `ui-test` is the replacement for `TestHarness`. - -The top-level [`@Composable DeviceConfigurationOverride`](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/package-summary#DeviceConfigurationOverride(androidx.compose.ui.test.DeviceConfigurationOverride,kotlin.Function0)) -provides the same structure as `TestHarness`, applying overrides to a piece of `content` under test. - -Instead of all of the overrides appearing as parameters to `TestHarness`, the -`DeviceConfigurationOverride` top-level function takes a particular implementation of the -[`fun interface DeviceConfigurationOverride`](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/DeviceConfigurationOverride). - -The [built-in `DeviceConfigurationOverride`s](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/DeviceConfigurationOverride.Companion) -are available as extension methods on the companion object of `DeviceConfigurationOverride`, and -the built-in overrides cover all of the parameters of `TestHarness`. - -Multiple `DeviceConfigurationOverride`s can be combined with [then](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/package-summary#(androidx.compose.ui.test.DeviceConfigurationOverride).then(androidx.compose.ui.test.DeviceConfigurationOverride)) - -## Migration steps: - -1. Replace `TestHarness()` with `DeviceConfigurationOverride()` (a deprecation replacement is - available) -1. Remove the override for any argument that was previously using a default value. - Because the overrides have been split into independent overrides, the "default" behavior can - be achieved by not specifying that override. - - Example: If you want to keep the current dark mode setting and not override it, instead of - querying for and specifying the current dark mode theme to apply in an override, don't apply the - `DeviceConfigurationOverride.DarkMode` override. - -## Original docs - -A library providing a test harness for UI components. - -## Background - -Device configuration (locale, font size, screen size, folding features, etc.) are device-wide -properties, which makes it hard to automate tests that wants to vary these properties. -One current solution is to run tests across a range of emulators or devices with different -properties, and potentially filter tests to only run when specific conditions are met. -This has the downside of increasing the number of devices to manage, higher complexity of -configuring those devices, and more complicated test suites. - -With a Compose-only app, it is less common that the โ€œphysicalโ€ constraints of the device are -directly used. -Instead, state hoisting encourages isolating such constraints, and providing them to components via -state that is observable via snapshots. -The mechanism to do so is primarily via a set of composition locals, such as `LocalConfiguration`, -`LocalDensity`, and others. -The composition local mechanism provides a layer of indirection that permits overriding these -constraints via those composition local hooks. - -## Test Harness - -`TestHarness` is an `@Composable` function, which takes a single slot of `@Composable` content. -This content is the `@Composable` UI under test, so standard usage would look like the following: - -```kotlin -@Test -fun example() { - composeTestRule.setContent { - TestHarness { - MyComponent() - } - } - - // assertions -} -``` - -When no parameters of `TestHarness` are specified, `TestHarness` has no direct effect, and it would -be equivalent to calling `MyComponent` directly. - -Specifying parameters of `TestHarness` results in overriding the default configuration for the -content under-test, and will affect `MyComponent`. - -For example, specifying the `fontScale` parameter will change the effective font scale within -the `TestHarness`: - -```kotlin -@Test -fun example() { - composeTestRule.setContent { - TestHarness(fontScale = 1.5f) { - Text("Configuration: ${LocalConfiguration.current.fontScale}") - Text("Density: ${LocalDensity.current.fontScale}") - } - } - - composeTestRule.onNodeWithText("Configuration: 1.5").assertExists() - composeTestRule.onNodeWithText("Density: 1.5").assertExists() -} -``` - -This allows testing UI for different font scales in a isolated way, without having to directly -configure the device to use a different font scale. - -`TestHarness` also takes a `size: DpSize` parameter, to test a Composable at a particular size. - -```kotlin -@Test -fun example() { - composeTestRule.setContent { - TestHarness(size = DpSize(800.dp, 1000.dp)) { - MyComponent() // will be rendered at 800dp by 1000dp, even if the window is smaller - } - } -} -``` - -See the full list of parameters and effects below. - -## Parameters - -The full list of parameters and their effects: - -| Parameter | Default value | Effect | -|-------------------------------------|-----------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| -| `size: DpSize` | `DpSize.Unspecified` | If specified, overrides `LocalDensity` if needed to give the `DpSize` amount of space to the composable under test | -| `darkMode: Boolean` | `isSystemInDarkTheme()` | Overrides `LocalConfiguration.current.uiMode` | -| `fontScale: Float` | `LocalDensity.current.fontScale` | Overrides `LocalDensity.current.fontScale` and `LocalConfiguration.current.fontScale` | -| `fontWeightAdjustment: Int?` | `LocalConfiguration.current.fontWeightAdjustment` on API 31 and above, otherwise `null` | Overrides `LocalConfiguration.current.fontWeightAdjustment` on API 31 and above and not-null | -| `locales: LocaleListCompat` | `ConfigurationCompat.getLocales(LocalConfiguration.current)` | Overrides `LocalConfiguration.current.locales` | -| `layoutDirection: LayoutDirection?` | `null` (which uses the resulting locale layout direction) | Overrides `LocalLayoutDirection.current` and `LocalConfiguration.current.screenLayout` | - -## Implementation - -`TestHarness` works by overriding a set of composition locals provided to the content under test. - -The full list of composition locals that may be overridden by various parameters are: - -- `LocalConfiguration` -- `LocalContext` -- `LocalLayoutDirection` -- `LocalDensity` -- `LocalFontFamilyResolver` - -Any composable that depends on these composition locals should be testable via the test harness, -because they will pull the overridden configuration information from them. -This includes configuration-specific resources, because these are pulled from `LocalContext`. - -Testing a composable at a smaller size than the real screen space available is straightforward, but -testing a composable at a larger size than the real screen space available is not. This is because -the library and the testing APIs are sensitive to whether or not a composable is actually rendered -within the window of the application. - -As a solution, `TestHarness` will override the `LocalDensity` to shrink the content as necessary -for all of the specified `size: DpSize` to be displayed at once in the window space that is -available. This results in the composable under test believing it has the specified space to work -with, even if that is larger than the window of the application. - -## Limitations - -The test harness is simulating alternate configurations and sizes, so it does not exactly represent -what a user would see on a real device. -For that reason, the platform edges where Composables interact with the system more is where the -test harness may break down and have issues. -An incomplete list includes: dialogs (due to different `Window` instances), insets, soft keyboard -interactions, and interop with `View`s. -The density overriding when specifying a specific size to test a composable at also means that UI -might be rendered in atypical ways, especially at the extreme of rendering a very large desktop-size -UI on a small portrait phone. -The mechanism that the test harness uses is also not suitable for production code: in production, -the default configuration as specified by the user and the system should be used. - -The mechanism that the test harness uses to override the configuration (`ContextThemeWrapper`) is -not fully supported by layoutlib. In particular, alternate resources are available just by using -`TestHarness`. - -## Download - -[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-testharness)](https://search.maven.org/search?q=g:com.google.accompanist) - -```groovy -repositories { - mavenCentral() -} - -dependencies { - implementation "com.google.accompanist:accompanist-testharness:" -} -``` diff --git a/mkdocs.yml b/mkdocs.yml index 2883c554f..5c6154b41 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -33,9 +33,6 @@ nav: - 'Adaptive': - 'Guide': adaptive.md - 'API': api/adaptive - - 'Test Harness': - - 'Guide': testharness.md - - 'API': api/testharness - 'Snapshots': using-snapshot-version.md - 'Contributing': contributing.md - 'Maintainers': diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index e0835e5d4..28c512b59 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -71,9 +71,6 @@ dependencies { implementation(project(":navigation-material")) implementation(project(":permissions")) implementation(project(":systemuicontroller")) - implementation(project(":testharness")) // Don't use in production! Use the configurations below - testImplementation(project(":testharness")) - androidTestImplementation(project(":testharness")) implementation(libs.androidx.appcompat) implementation(libs.mdc) diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 15144a1b7..011c09cae 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -192,16 +192,6 @@ - - - - - - - diff --git a/sample/src/main/java/com/google/accompanist/sample/testharness/TestHarnessSample.kt b/sample/src/main/java/com/google/accompanist/sample/testharness/TestHarnessSample.kt deleted file mode 100644 index 4bddef204..000000000 --- a/sample/src/main/java/com/google/accompanist/sample/testharness/TestHarnessSample.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress("DEPRECATION") - -package com.google.accompanist.sample.testharness - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Surface -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import androidx.core.os.LocaleListCompat -import com.google.accompanist.sample.AccompanistSampleTheme -import com.google.accompanist.sample.R -import com.google.accompanist.testharness.TestHarness -import java.util.Locale - -/** - * A visual sample for the TestHarness Composable. Note that it should not be used in production. - */ -class TestHarnessSample : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - TestHarnessSampleScreen() - } - } -} - -@Preview -@Composable -fun TestHarnessSampleScreen() { - Column( - modifier = Modifier - .padding(16.dp) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - TestHarnessScreen() - TestHarness(size = DpSize(100.dp, 100.dp)) { - TestHarnessScreen("with a set size") - } - TestHarness(darkMode = true) { - TestHarnessScreen("with darkMode enabled") - } - TestHarness(fontScale = 2f) { - TestHarnessScreen("with a big font scale") - } - TestHarness(layoutDirection = LayoutDirection.Rtl) { - TestHarnessScreen("in RTL") - } - TestHarness(locales = LocaleListCompat.create(Locale("ar"))) { - TestHarnessScreen("in Arabic") - } - } -} - -@Preview -@Composable -fun TestHarnessScreen(text: String = "") { - AccompanistSampleTheme { - Surface( - modifier = Modifier - .border(1.dp, Color.LightGray) - .height(100.dp) - .fillMaxWidth() - ) { - Text( - stringResource(R.string.this_is_content, text), - modifier = Modifier.padding(8.dp) - ) - } - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 770e56349..c61be1bcc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -40,7 +40,6 @@ include(":permissions") include(":permissions-lint") include(":systemuicontroller") include(":sample") -include(":testharness") rootProject.name = "accompanist" diff --git a/testharness/README.md b/testharness/README.md deleted file mode 100644 index db01fd7f8..000000000 --- a/testharness/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Test Harness for Jetpack Compose - -[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-testharness)](https://search.maven.org/search?q=g:com.google.accompanist) - -For more information, visit the documentation: https://google.github.io/accompanist/testharness - -## Download - -```groovy -repositories { - mavenCentral() -} - -dependencies { - implementation "com.google.accompanist:accompanist-testharness:" -} -``` - -Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. These are updated on every commit. - - [snap]: https://oss.sonatype.org/content/repositories/snapshots/com/google/accompanist/accompanist-testharness/ diff --git a/testharness/api/current.api b/testharness/api/current.api deleted file mode 100644 index 67b6d3fc8..000000000 --- a/testharness/api/current.api +++ /dev/null @@ -1,9 +0,0 @@ -// Signature format: 4.0 -package com.google.accompanist.testharness { - - public final class TestHarnessKt { - method @Deprecated @androidx.compose.runtime.Composable public static void TestHarness(optional long size, optional boolean darkMode, optional androidx.core.os.LocaleListCompat locales, optional androidx.compose.ui.unit.LayoutDirection? layoutDirection, optional float fontScale, optional Integer? fontWeightAdjustment, optional Boolean? isScreenRound, kotlin.jvm.functions.Function0 content); - } - -} - diff --git a/testharness/build.gradle.kts b/testharness/build.gradle.kts deleted file mode 100644 index a2076502d..000000000 --- a/testharness/build.gradle.kts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@file:Suppress("UnstableApiUsage") - -plugins { - alias(libs.plugins.accompanist.android.library) - alias(libs.plugins.accompanist.android.library.compose) - alias(libs.plugins.accompanist.android.library.published) -} - -android { - namespace = "com.google.accompanist.testharness" - - buildTypes { - getByName("debug") { - enableUnitTestCoverage = true - } - } - - testOptions { - unitTests.all { - it.useJUnit { - excludeCategories("com.google.accompanist.internal.test.IgnoreOnRobolectric") - } - } - } - - sourceSets { - named("test") { - java.srcDirs("src/sharedTest/kotlin") - res.srcDirs("src/sharedTest/res") - } - named("androidTest") { - java.srcDirs("src/sharedTest/kotlin") - res.srcDirs("src/sharedTest/res") - } - } -} - -dependencies { - implementation(libs.compose.foundation.foundation) - implementation(libs.androidx.core) - testImplementation(libs.androidx.core) - implementation(libs.kotlin.coroutines.android) - implementation(libs.compose.ui.test) - - // ====================== - // Test dependencies - // ====================== - - androidTestImplementation(project(":internal-testutils")) - testImplementation(project(":internal-testutils")) - - androidTestImplementation(libs.junit) - testImplementation(libs.junit) - - androidTestImplementation(libs.truth) - testImplementation(libs.truth) - - androidTestImplementation(libs.compose.ui.test.junit4) - testImplementation(libs.compose.ui.test.junit4) - - androidTestImplementation(libs.compose.ui.test.manifest) - testImplementation(libs.compose.ui.test.manifest) - - androidTestImplementation(libs.androidx.test.runner) - testImplementation(libs.androidx.test.runner) - - testImplementation(libs.robolectric) -} diff --git a/testharness/gradle.properties b/testharness/gradle.properties deleted file mode 100644 index 4e7df7dcf..000000000 --- a/testharness/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -POM_ARTIFACT_ID=accompanist-testharness -POM_NAME=Accompanist Test Harness -POM_PACKAGING=aar diff --git a/testharness/src/androidTest/kotlin/org/robolectric/annotation/Config.kt b/testharness/src/androidTest/kotlin/org/robolectric/annotation/Config.kt deleted file mode 100644 index ddeee9b27..000000000 --- a/testharness/src/androidTest/kotlin/org/robolectric/annotation/Config.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.robolectric.annotation - -// No-op annotation for instrumented tests to build -annotation class Config(val sdk: IntArray) diff --git a/testharness/src/main/AndroidManifest.xml b/testharness/src/main/AndroidManifest.xml deleted file mode 100644 index 928e8bf1f..000000000 --- a/testharness/src/main/AndroidManifest.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/testharness/src/main/java/com/google/accompanist/testharness/ContextThemeWrapper.java b/testharness/src/main/java/com/google/accompanist/testharness/ContextThemeWrapper.java deleted file mode 100644 index 6dbd7bf79..000000000 --- a/testharness/src/main/java/com/google/accompanist/testharness/ContextThemeWrapper.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.accompanist.testharness; - -import android.content.Context; -import android.content.ContextWrapper; -import android.content.res.AssetManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Build; -import android.view.LayoutInflater; - -import androidx.annotation.RequiresApi; -import androidx.annotation.StyleRes; - -/** - * A context wrapper that allows you to modify or replace the theme of the wrapped context. - *

- * This is a modification of androidx.appcompat.view.ContextThemeWrapper to allow the - * TestHarness to work in previews. - */ -class ContextThemeWrapper extends ContextWrapper { - /** - * Lazily-populated configuration object representing an empty, default configuration. - */ - private static Configuration sEmptyConfig; - - private int mThemeResource; - private Resources.Theme mTheme; - private LayoutInflater mInflater; - private Configuration mOverrideConfiguration; - private Resources mResources; - - /** - * Creates a new context wrapper with no theme and no base context. - *

- * Note: A base context must be attached - * using {@link #attachBaseContext(Context)} before calling any other - * method on the newly constructed context wrapper. - */ - public ContextThemeWrapper() { - super(null); - } - - /** - * Creates a new context wrapper with the specified theme. - *

- * The specified theme will be applied on top of the base context's theme. - * Any attributes not explicitly defined in the theme identified by - * themeResId will retain their original values. - * - * @param base the base context - * @param themeResId the resource ID of the theme to be applied on top of - * the base context's theme - */ - public ContextThemeWrapper(Context base, @StyleRes int themeResId) { - super(base); - mThemeResource = themeResId; - } - - /** - * Creates a new context wrapper with the specified theme. - *

- * Unlike {@link #ContextThemeWrapper(Context, int)}, the theme passed to - * this constructor will completely replace the base context's theme. - * - * @param base the base context - * @param theme the theme against which resources should be inflated - */ - public ContextThemeWrapper(Context base, Resources.Theme theme) { - super(base); - mTheme = theme; - } - - @Override - protected void attachBaseContext(Context newBase) { - super.attachBaseContext(newBase); - } - - /** - * Call to set an "override configuration" on this context -- this is - * a configuration that replies one or more values of the standard - * configuration that is applied to the context. See - * {@link Context#createConfigurationContext(Configuration)} for more - * information. - * - *

This method can only be called once, and must be called before any - * calls to {@link #getResources()} or {@link #getAssets()} are made. - */ - public void applyOverrideConfiguration(Configuration overrideConfiguration) { - if (mResources != null) { - throw new IllegalStateException( - "getResources() or getAssets() has already been called"); - } - if (mOverrideConfiguration != null) { - throw new IllegalStateException("Override configuration has already been set"); - } - mOverrideConfiguration = new Configuration(overrideConfiguration); - } - - @Override - public Resources getResources() { - return getResourcesInternal(); - } - - private Resources getResourcesInternal() { - if (mResources == null) { - if (mOverrideConfiguration == null - || (Build.VERSION.SDK_INT >= 26 - && isEmptyConfiguration(mOverrideConfiguration))) { - // If we're not applying any overrides, use the base context's resources. On API - // 26+, this will avoid pulling in resources that share a backing implementation - // with the application context. - mResources = super.getResources(); - } else { - final Context resContext = createConfigurationContext(mOverrideConfiguration); - - // MAIN UPDATE: If resContext is null, fallback to the deprecated Resources - // constructor instead of crashing - if (resContext != null) { - mResources = resContext.getResources(); - } else { - Resources res = super.getResources(); - Configuration newConfig = new Configuration(res.getConfiguration()); - newConfig.updateFrom(mOverrideConfiguration); - mResources = new Resources(res.getAssets(), res.getDisplayMetrics(), newConfig); - } - } - } - return mResources; - } - - @Override - public void setTheme(int resid) { - if (mThemeResource != resid) { - mThemeResource = resid; - initializeTheme(); - } - } - - /** - * Returns the resource ID of the theme that is to be applied on top of the base context's - * theme. - */ - public int getThemeResId() { - return mThemeResource; - } - - @Override - public Resources.Theme getTheme() { - if (mTheme != null) { - return mTheme; - } - - initializeTheme(); - - return mTheme; - } - - @Override - public Object getSystemService(String name) { - if (LAYOUT_INFLATER_SERVICE.equals(name)) { - if (mInflater == null) { - mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); - } - return mInflater; - } - return getBaseContext().getSystemService(name); - } - - /** - * Called by {@link #setTheme} and {@link #getTheme} to apply a theme - * resource to the current Theme object. Can override to change the - * default (simple) behavior. This method will not be called in multiple - * threads simultaneously. - * - * @param theme The Theme object being modified. - * @param resid The theme style resource being applied to theme. - * @param first Set to true if this is the first time a style is being - * applied to theme. - */ - protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) { - theme.applyStyle(resid, true); - } - - private void initializeTheme() { - final boolean first = mTheme == null; - if (first) { - mTheme = getResources().newTheme(); - Resources.Theme theme = getBaseContext().getTheme(); - if (theme != null) { - mTheme.setTo(theme); - } - } - onApplyThemeResource(mTheme, mThemeResource, first); - } - - @Override - public AssetManager getAssets() { - // Ensure we're returning assets with the correct configuration. - return getResources().getAssets(); - } - - /** - * @return {@code true} if the specified configuration is {@code null} or is a no-op when - * used as a configuration overlay - */ - @RequiresApi(26) - private static boolean isEmptyConfiguration(Configuration overrideConfiguration) { - if (overrideConfiguration == null) { - return true; - } - - if (sEmptyConfig == null) { - Configuration emptyConfig = new Configuration(); - // Workaround for incorrect default fontScale on earlier SDKs (b/29924927). Note - // that Configuration.setToDefaults() is *not* a no-op configuration overlay. - emptyConfig.fontScale = 0.0f; - sEmptyConfig = emptyConfig; - } - - return overrideConfiguration.equals(sEmptyConfig); - } -} diff --git a/testharness/src/main/java/com/google/accompanist/testharness/TestHarness.kt b/testharness/src/main/java/com/google/accompanist/testharness/TestHarness.kt deleted file mode 100644 index d33e3bc85..000000000 --- a/testharness/src/main/java/com/google/accompanist/testharness/TestHarness.kt +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.accompanist.testharness - -import android.content.res.Configuration -import android.os.Build -import android.os.LocaleList -import android.util.DisplayMetrics -import android.view.View -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.requiredSize -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalFontFamilyResolver -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.text.font.createFontFamilyResolver -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.LayoutDirection -import androidx.core.os.ConfigurationCompat -import androidx.core.os.LocaleListCompat -import kotlin.math.floor - -/** - * Render [content] in a [Box] within a harness, overriding various device configuration values to - * make testing easier. - * - * @param size if not [DpSize.Unspecified], the [content] will be forced to be drawn with at this - * size, overriding [LocalDensity] if necessary to ensure that there is enough space. This - * defaults to [DpSize.Unspecified]. - * - * @param darkMode if true, the content will be rendered with dark mode. This defaults to the - * current dark mode value as reported by [isSystemInDarkTheme]. - * - * @param locales the list of locales to render the app with. This defaults to the list of locales - * returned by [LocalConfiguration.current]. - * - * @param layoutDirection an overriding layout direction. This defaults to `null`, which means - * that the layout direction from the [locales] is used instead. - * - * @param fontScale the font scale to render text at. This defaults to the current - * [Density.fontScale]. - * - * @param fontWeightAdjustment the font weight adjustment for fonts. This defaults to the current - * [fontWeightAdjustment] (if any). If `null`, the [fontWeightAdjustment] will be left unchanged. - * - * @param isScreenRound the device roundness. This defaults to null. If `null`, - * the [isScreenRound] will be left unchanged. - */ -@Composable -@Deprecated( - replaceWith = ReplaceWith( - "DeviceConfigurationOverride(DeviceConfigurationOverride.ForcedSize(size) " + - "then DeviceConfigurationOverride.DarkMode(darkMode) " + - "then DeviceConfigurationOverride.Locales(LocaleList(locales.toLanguageTags()))" + - "then DeviceConfigurationOverride.LayoutDirection(layoutDirection)" + - "then DeviceConfigurationOverride.FontScale(fontScale)" + - "then DeviceConfigurationOverride.FontWeightAdjustment(fontWeightAdjustment)" + - "then DeviceConfigurationOverride.RoundScreen(isScreenRound), " + - "content)", - "androidx.compose.ui.test.DeviceConfigurationOverride", - "androidx.compose.ui.test.ForcedSize", - "androidx.compose.ui.test.DarkMode", - "androidx.compose.ui.test.Locales", - "androidx.compose.ui.test.LayoutDirection", - "androidx.compose.ui.test.FontScale", - "androidx.compose.ui.test.FontWeightAdjustment", - "androidx.compose.ui.test.RoundScreen", - "androidx.compose.ui.test.then", - "androidx.compose.ui.text.intl.LocaleList", - ), - message = "TestHarness has been superceded by DeviceConfigurationOverride in ui-test. " + - "Each argument in TestHarness have been replaced with an individual " + - "DeviceConfigurationOverride, so the suggested replacement is likely unnecessarily " + - "adding overrides for anything that was previously using the default arguments." -) -public fun TestHarness( - size: DpSize = DpSize.Unspecified, - darkMode: Boolean = isSystemInDarkTheme(), - locales: LocaleListCompat = ConfigurationCompat.getLocales(LocalConfiguration.current), - layoutDirection: LayoutDirection? = null, - fontScale: Float = LocalDensity.current.fontScale, - fontWeightAdjustment: Int? = - if (Build.VERSION.SDK_INT >= 31) LocalConfiguration.current.fontWeightAdjustment else null, - isScreenRound: Boolean? = null, - content: @Composable () -> Unit -) { - // Use the DensityForcedSize content wrapper if specified - val sizeContentWrapper: @Composable (@Composable () -> Unit) -> Unit = - if (size == DpSize.Unspecified) { - { it() } - } else { - { DensityForcedSize(size, it) } - } - - // First override the density. Doing this first allows using the resulting density in the - // overridden configuration. - sizeContentWrapper { - // Second, override the configuration, with the current configuration modified by the - // given parameters. - OverriddenConfiguration( - configuration = Configuration().apply { - // Initialize from the current configuration - updateFrom(LocalConfiguration.current) - // Set dark mode directly - uiMode = uiMode and Configuration.UI_MODE_NIGHT_MASK.inv() or if (darkMode) { - Configuration.UI_MODE_NIGHT_YES - } else { - Configuration.UI_MODE_NIGHT_NO - } - // Update the locale list - if (Build.VERSION.SDK_INT >= 24) { - setLocales(LocaleList.forLanguageTags(locales.toLanguageTags())) - } else { - setLocale(locales[0]) - } - // Override densityDpi - densityDpi = - floor(LocalDensity.current.density * DisplayMetrics.DENSITY_DEFAULT).toInt() - // Override font scale - this.fontScale = fontScale - // Maybe override fontWeightAdjustment - if (Build.VERSION.SDK_INT >= 31 && fontWeightAdjustment != null) { - this.fontWeightAdjustment = fontWeightAdjustment - } - // override isRound for Wear - if (Build.VERSION.SDK_INT >= 23 && isScreenRound != null) { - screenLayout = when (isScreenRound) { - true -> (screenLayout and Configuration.SCREENLAYOUT_ROUND_MASK.inv()) or - Configuration.SCREENLAYOUT_ROUND_YES - false -> (screenLayout and Configuration.SCREENLAYOUT_ROUND_MASK.inv()) or - Configuration.SCREENLAYOUT_ROUND_NO - } - } - }, - ) { - // Finally, override the layout direction again if manually specified, potentially - // overriding the one from the locale. - CompositionLocalProvider( - LocalLayoutDirection provides (layoutDirection ?: LocalLayoutDirection.current) - ) { - content() - } - } - } -} - -/** - * Overrides the compositions locals related to the given [configuration]. - * - * There currently isn't a single source of truth for these values, so we update them all - * according to the given [configuration]. - */ -@Composable -internal fun OverriddenConfiguration( - configuration: Configuration, - content: @Composable () -> Unit -) { - // We don't override the theme, but we do want to override the configuration and this seems - // convenient to do so - val newContext = ContextThemeWrapper(LocalContext.current, 0).apply { - applyOverrideConfiguration(configuration) - } - - CompositionLocalProvider( - LocalContext provides newContext, - LocalConfiguration provides configuration, - LocalLayoutDirection provides - if (configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) { - LayoutDirection.Ltr - } else { - LayoutDirection.Rtl - }, - LocalDensity provides Density( - configuration.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT, - configuration.fontScale - ), - LocalFontFamilyResolver provides createFontFamilyResolver(newContext), - content = content - ) -} - -/** - * Render [content] in a [Box] that is forced to have the given [size] without clipping. - * - * This is only suitable for tests, since this will override [LocalDensity] to ensure that the - * [size] is met (as opposed to [Modifier.requiredSize] which will result in clipping). - */ -@Composable -internal fun DensityForcedSize( - size: DpSize, - content: @Composable () -> Unit -) { - BoxWithConstraints( - // Try to set the size naturally, we'll be overriding the density below if this fails - modifier = Modifier.size(size) - ) { - // Compute the minimum density required so that both the requested width and height both - // fit - val density = LocalDensity.current.density * minOf( - maxWidth / maxOf(maxWidth, size.width), - maxHeight / maxOf(maxHeight, size.height), - ) - // Configuration requires the density DPI to be an integer, so round down to ensure we - // have enough space - val densityDpi = floor(density * DisplayMetrics.DENSITY_DEFAULT).toInt() - - CompositionLocalProvider( - LocalDensity provides Density( - // Override the density with the factor needed to meet both the minimum width and - // height requirements, and the configuration override requirements. - density = densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT, - // Pass through the font scale - fontScale = LocalDensity.current.fontScale - ) - ) { - Box( - // This size will now be guaranteed to be able to match the constraints - modifier = Modifier - .size(size) - .fillMaxSize() - ) { - content() - } - } - } -} diff --git a/testharness/src/sharedTest/kotlin/com/google/accompanist/testharness/FakeTests.kt b/testharness/src/sharedTest/kotlin/com/google/accompanist/testharness/FakeTests.kt deleted file mode 100644 index 24a948b79..000000000 --- a/testharness/src/sharedTest/kotlin/com/google/accompanist/testharness/FakeTests.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.accompanist.testharness - -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -/** - * Fake tests to help with sharding: https://github.com/android/android-test/issues/973 - */ -@RunWith(JUnit4::class) -class FakeTests { - @Test - fun fake1() = Unit - - @Test - fun fake2() = Unit - - @Test - fun fake3() = Unit - - @Test - fun fake4() = Unit - - @Test - fun fake5() = Unit - - @Test - fun fake6() = Unit - - @Test - fun fake7() = Unit - - @Test - fun fake8() = Unit - - @Test - fun fake9() = Unit - - @Test - fun fake10() = Unit -} diff --git a/testharness/src/sharedTest/kotlin/com/google/accompanist/testharness/TestHarnessTest.kt b/testharness/src/sharedTest/kotlin/com/google/accompanist/testharness/TestHarnessTest.kt deleted file mode 100644 index 501e00dc6..000000000 --- a/testharness/src/sharedTest/kotlin/com/google/accompanist/testharness/TestHarnessTest.kt +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress("DEPRECATION") - -package com.google.accompanist.testharness - -import android.content.res.Configuration -import android.content.res.Configuration.UI_MODE_NIGHT_MASK -import android.content.res.Configuration.UI_MODE_NIGHT_NO -import android.content.res.Configuration.UI_MODE_NIGHT_YES -import androidx.activity.ComponentActivity -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.requiredSize -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.text.BasicText -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.LayoutCoordinates -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import androidx.core.os.LocaleListCompat -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SdkSuppress -import com.google.accompanist.testharness.test.R -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotEquals -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import java.util.Locale - -@RunWith(AndroidJUnit4::class) -class TestHarnessTest { - @get:Rule - val composeTestRule = createAndroidComposeRule() - - @Test - fun size_SmallerThanOuterBox_measuredWidthIsCorrect() { - var width = 0.dp - composeTestRule.setContent { - Box(Modifier.requiredSize(300.dp)) { - TestHarness(size = DpSize(200.dp, 200.dp)) { - BoxOfSize(200.dp, onWidth = { width = it }) - } - } - } - composeTestRule.waitForIdle() - - val ratio = width / 200.dp - assertEquals(ratio, 1f, 0.01f) - } - - @Test - fun size_BiggerThanOuterBox_measuredWidthIsCorrect() { - var width = 0.dp - composeTestRule.setContent { - Box(Modifier.requiredSize(100.dp)) { - TestHarness(size = DpSize(200.dp, 200.dp)) { - BoxOfSize(200.dp, onWidth = { width = it }) - } - } - } - composeTestRule.waitForIdle() - - val ratio = width / 200.dp - assertEquals(ratio, 1f, 0.01f) - } - - @Test - fun size_ExtremelyBig_measuredWidthIsCorrect() { - var width = 0.dp - composeTestRule.setContent { - TestHarness(size = DpSize(5000.dp, 5000.dp)) { - BoxOfSize(5000.dp, onWidth = { width = it }) - } - } - composeTestRule.waitForIdle() - - val ratio = width / 5000.dp - assertEquals(ratio, 1f, 0.01f) - } - - @Test - fun darkMode_enabled() { - var darkMode: Int = -1 - composeTestRule.setContent { - TestHarness(darkMode = true) { - darkMode = LocalConfiguration.current.uiMode - } - } - composeTestRule.waitForIdle() - - assertEquals(darkMode and UI_MODE_NIGHT_MASK, UI_MODE_NIGHT_YES) - } - - @Test - fun darkMode_disabled() { - var darkMode: Int = -1 - composeTestRule.setContent { - TestHarness(darkMode = false) { - darkMode = LocalConfiguration.current.uiMode - } - } - composeTestRule.waitForIdle() - - assertEquals(darkMode and UI_MODE_NIGHT_MASK, UI_MODE_NIGHT_NO) - } - - @Test - @SdkSuppress(minSdkVersion = 24) - fun locales_api24_allLocalesApplied() { - val expectedLocales = LocaleListCompat.create(Locale.CANADA, Locale.ITALY) - lateinit var locales: LocaleListCompat - composeTestRule.setContent { - TestHarness(locales = expectedLocales) { - locales = LocaleListCompat.wrap(LocalConfiguration.current.locales) - } - } - - composeTestRule.waitForIdle() - - // All locales are expected in Sdk>=24 - assertEquals(expectedLocales, locales) - } - - @Test - fun usLocale_usesCorrectResource() { - composeTestRule.setContent { - TestHarness(locales = LocaleListCompat.forLanguageTags("us")) { - BasicText(text = stringResource(R.string.this_is_content, "abc")) - } - } - composeTestRule.onNodeWithText("This is content\nabc").assertExists() - } - - @Test - fun arLocale_usesCorrectResource() { - composeTestRule.setContent { - TestHarness(locales = LocaleListCompat.forLanguageTags("ar")) { - BasicText(text = stringResource(R.string.this_is_content, "abc")) - } - } - composeTestRule.onNodeWithText("ู‡ุฐุง ู…ุถู…ูˆู† \nabc").assertExists() - } - - @Test - fun layoutDirection_RtlLocale_usesOverride() { - lateinit var direction: LayoutDirection - val initialLocale = LocaleListCompat.create(Locale("ar")) // Arabic - val initialLayoutDirection = LayoutDirection.Ltr - - // Given test harness setting an RTL Locale but it also forcing the opposite - // layout direction - composeTestRule.setContent { - TestHarness( - layoutDirection = initialLayoutDirection, - locales = initialLocale - ) { - direction = LocalLayoutDirection.current - } - } - composeTestRule.waitForIdle() - - // The used locale should be the one overriden with the test harness, ignoring the Locale's. - assertEquals(initialLayoutDirection, direction) - } - - @Test - fun layoutDirection_default_RtlLocale() { - lateinit var direction: LayoutDirection - val initialLocale = LocaleListCompat.create(Locale("ar")) // Arabic - - // Given an initial layout direction, when the test harness sets an RTL Locale and doesn't - // force the layout direction - composeTestRule.setContent { - TestHarness( - layoutDirection = null, - locales = initialLocale - ) { - direction = LocalLayoutDirection.current - } - } - composeTestRule.waitForIdle() - - // The used locale should be the Locale's. - assertEquals(LayoutDirection.Rtl, direction) - } - - @Test - fun layoutDirection_default_usesLocales() { - lateinit var direction: LayoutDirection - val initialLocale = LocaleListCompat.create(Locale("ar")) // Arabic - val initialLayoutDirection = LayoutDirection.Ltr - - // Given no layout direction, when the test harness sets an RTL Locale with an initial - // LTR direction - composeTestRule.setContent { - CompositionLocalProvider( - LocalLayoutDirection provides initialLayoutDirection - ) { - TestHarness( - layoutDirection = null, - locales = initialLocale - ) { - direction = LocalLayoutDirection.current - } - } - } - composeTestRule.waitForIdle() - - // The default should be the one provided by the Locale - assertNotEquals(initialLayoutDirection, direction) - } - - @Test - fun layoutDirection_setLtr() { - lateinit var direction: LayoutDirection - val initialLayoutDirection = LayoutDirection.Rtl - val expected = LayoutDirection.Ltr - - // Given a content with an initial RTL layout direction, when the test harness overrides it - composeTestRule.setContent { - CompositionLocalProvider( - LocalLayoutDirection provides initialLayoutDirection - ) { - TestHarness(layoutDirection = expected) { - direction = LocalLayoutDirection.current - } - } - } - composeTestRule.waitForIdle() - - // The direction should be the one forced by the test harness - assertEquals(expected, direction) - } - - @Test - fun layoutDirection_setRtl() { - lateinit var direction: LayoutDirection - val initialLayoutDirection = LayoutDirection.Ltr - val expected = LayoutDirection.Rtl - - // Given a content with an initial RTL layout direction, when the test harness overrides it - composeTestRule.setContent { - CompositionLocalProvider( - LocalLayoutDirection provides initialLayoutDirection - ) { - TestHarness(layoutDirection = expected) { - direction = LocalLayoutDirection.current - } - } - } - composeTestRule.waitForIdle() - - // The direction should be the one forced by the test harness - assertEquals(expected, direction) - } - - @Test - fun layoutDirection_default_followsLocaleLtr() { - lateinit var direction: LayoutDirection - - // Given an initial layout direction and no overrides - composeTestRule.setContent { - CompositionLocalProvider( - LocalConfiguration provides Configuration().apply { - setLocale(Locale.ENGLISH) - } - ) { - TestHarness(layoutDirection = null) { - direction = LocalLayoutDirection.current - } - } - } - composeTestRule.waitForIdle() - - // The direction should be set by the Locale - assertEquals(LayoutDirection.Ltr, direction) - } - - @Test - fun layoutDirection_default_followsLocaleRtl() { - lateinit var direction: LayoutDirection - - // Given an initial layout direction and no overrides - composeTestRule.setContent { - CompositionLocalProvider( - LocalConfiguration provides Configuration().apply { - setLocale(Locale("ar")) - } - ) { - TestHarness(layoutDirection = null) { - direction = LocalLayoutDirection.current - } - } - } - composeTestRule.waitForIdle() - - // The direction should be set by the Locale - assertEquals(LayoutDirection.Rtl, direction) - } - - @Test - fun fontScale() { - val expectedFontScale = 5f - var fontScale = 0f - // Given - composeTestRule.setContent { - TestHarness(fontScale = expectedFontScale) { - fontScale = LocalConfiguration.current.fontScale - } - } - - composeTestRule.waitForIdle() - - assertEquals(expectedFontScale, fontScale) - } - - @Test - @SdkSuppress(minSdkVersion = 31) - fun fontWeightAdjustment() { - val expectedFontWeightAdjustment = 10 - var fontWeightAdjustment = 0 - composeTestRule.setContent { - TestHarness(fontWeightAdjustment = expectedFontWeightAdjustment) { - fontWeightAdjustment = LocalConfiguration.current.fontWeightAdjustment - } - } - - composeTestRule.waitForIdle() - - assertEquals(expectedFontWeightAdjustment, fontWeightAdjustment) - } - - @Test - @SdkSuppress(minSdkVersion = 23) // SCREENLAYOUT_ROUND_YES supported on API 23+ - fun isScreenRound() { - var defaultRound: Boolean? = null - var forcedRound: Boolean? = null - var forcedNotRound: Boolean? = null - - composeTestRule.setContent { - defaultRound = LocalConfiguration.current.isScreenRound - TestHarness(isScreenRound = false) { - TestHarness(isScreenRound = true) { - forcedRound = LocalConfiguration.current.isScreenRound - TestHarness(isScreenRound = false) { - forcedNotRound = LocalConfiguration.current.isScreenRound - } - } - } - } - - assertEquals( - composeTestRule.activity.resources.configuration.isScreenRound, - defaultRound - ) - assertEquals(true, forcedRound) - assertEquals(false, forcedNotRound) - } - - @Composable - private fun BoxOfSize(size: Dp, onWidth: (Dp) -> Unit) { - val localDensity = LocalDensity.current - Box( - Modifier - .size(size) - .background(color = Color.Black) - .onGloballyPositioned { it: LayoutCoordinates -> - onWidth(with(localDensity) { it.size.width.toDp() }) - } - ) - } -} diff --git a/testharness/src/sharedTest/kotlin/com/google/accompanist/testharness/TestHarnessTestApi23.kt b/testharness/src/sharedTest/kotlin/com/google/accompanist/testharness/TestHarnessTestApi23.kt deleted file mode 100644 index 025062255..000000000 --- a/testharness/src/sharedTest/kotlin/com/google/accompanist/testharness/TestHarnessTestApi23.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress("DEPRECATION") - -package com.google.accompanist.testharness - -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.core.os.LocaleListCompat -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SdkSuppress -import org.junit.Assert.assertEquals -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.annotation.Config -import java.util.Locale - -@RunWith(AndroidJUnit4::class) -@Config(sdk = [23]) -class TestHarnessTestApi23 { - @get:Rule - val composeTestRule = createComposeRule() - - @Test - @SdkSuppress(maxSdkVersion = 23) - fun locales_api23_onlyfirstLocaleApplied() { - val expectedLocales = LocaleListCompat.create(Locale.CANADA, Locale.ITALY) - lateinit var locales: LocaleListCompat - composeTestRule.setContent { - TestHarness(locales = expectedLocales) { - @Suppress("DEPRECATION") - locales = LocaleListCompat.create(LocalConfiguration.current.locale) - } - } - - composeTestRule.waitForIdle() - - // Only one of the locales is used in Sdk<24 - assertEquals(LocaleListCompat.create(Locale.CANADA), locales) - } -} diff --git a/testharness/src/sharedTest/res/values-ar/strings.xml b/testharness/src/sharedTest/res/values-ar/strings.xml deleted file mode 100644 index 695a1d044..000000000 --- a/testharness/src/sharedTest/res/values-ar/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - ู‡ุฐุง ู…ุถู…ูˆู† \n%s - diff --git a/testharness/src/sharedTest/res/values/strings.xml b/testharness/src/sharedTest/res/values/strings.xml deleted file mode 100644 index 04e487adb..000000000 --- a/testharness/src/sharedTest/res/values/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - This is content\n%s - diff --git a/testharness/src/test/resources/robolectric.properties b/testharness/src/test/resources/robolectric.properties deleted file mode 100644 index d472dcb0b..000000000 --- a/testharness/src/test/resources/robolectric.properties +++ /dev/null @@ -1,3 +0,0 @@ -# Pin SDK to 30 since Robolectric does not currently support API 31: -# https://github.com/robolectric/robolectric/issues/6635 -sdk=33