1 /*
<lambda>null2  * Copyright 2022 The Android Open Source Project
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 
17 package androidx.compose.ui.text.platform
18 
19 import android.graphics.Typeface
20 import androidx.compose.runtime.State
21 import androidx.emoji2.text.EmojiCompat
22 import androidx.emoji2.text.EmojiCompat.LOAD_STRATEGY_MANUAL
23 import androidx.emoji2.text.EmojiCompat.MetadataRepoLoader
24 import androidx.emoji2.text.MetadataRepo
25 import androidx.test.ext.junit.runners.AndroidJUnit4
26 import androidx.test.filters.SmallTest
27 import com.google.common.truth.Truth.assertThat
28 import kotlinx.coroutines.CompletableDeferred
29 import kotlinx.coroutines.CoroutineScope
30 import kotlinx.coroutines.Dispatchers
31 import kotlinx.coroutines.delay
32 import kotlinx.coroutines.launch
33 import kotlinx.coroutines.runBlocking
34 import kotlinx.coroutines.withTimeout
35 import org.junit.After
36 import org.junit.Before
37 import org.junit.Test
38 import org.junit.runner.RunWith
39 
40 @RunWith(AndroidJUnit4::class)
41 @SmallTest
42 class EmojiCompatStatusTest {
43 
44     @Before
45     fun reset() {
46         EmojiCompat.reset(null)
47         EmojiCompatStatus.setDelegateForTesting(null)
48     }
49 
50     @After
51     fun clean() {
52         EmojiCompat.reset(null)
53         EmojiCompatStatus.setDelegateForTesting(null)
54     }
55 
56     @Test
57     fun nonConfiguredEc_isNotLoaded() {
58         EmojiCompat.reset(null)
59         assertThat(EmojiCompatStatus.fontLoaded.value).isFalse()
60     }
61 
62     @Test
63     fun default_isNotLoaded() {
64         val (config, deferred) = makeEmojiConfig()
65         EmojiCompat.init(config)
66         assertThat(EmojiCompatStatus.fontLoaded.value).isFalse()
67         deferred.complete(null)
68     }
69 
70     @Test
71     fun loading_isNotLoaded() {
72         val (config, deferred) = makeEmojiConfig()
73         val ec = EmojiCompat.init(config)
74         ec.load()
75         assertThat(EmojiCompatStatus.fontLoaded.value).isFalse()
76         deferred.complete(null)
77     }
78 
79     @Test
80     fun error_isNotLoaded() {
81         val (config, deferred) = makeEmojiConfig()
82         val ec = EmojiCompat.init(config)
83         ec.load()
84         deferred.complete(null)
85         assertThat(EmojiCompatStatus.fontLoaded.value).isFalse()
86     }
87 
88     @Test
89     fun loaded_isLoaded() {
90         val (config, deferred) = makeEmojiConfig()
91         val ec = EmojiCompat.init(config)
92         deferred.complete(MetadataRepo.create(Typeface.DEFAULT))
93         ec.load()
94         // query now, after init EC
95         EmojiCompatStatus.setDelegateForTesting(null)
96         EmojiCompatStatus.fontLoaded.assertTrue()
97     }
98 
99     @Test
100     fun nonLoaded_toLoaded_updatesReturnState() {
101         val (config, deferred) = makeEmojiConfig()
102         val ec = EmojiCompat.init(config)
103         val state = EmojiCompatStatus.fontLoaded
104         assertThat(state.value).isFalse()
105 
106         deferred.complete(MetadataRepo.create(Typeface.DEFAULT))
107         ec.load()
108 
109         state.assertTrue()
110     }
111 
112     @Test
113     fun nonConfigured_canLoadLater() {
114         EmojiCompat.reset(null)
115         val initialFontLoad = EmojiCompatStatus.fontLoaded
116         assertThat(initialFontLoad.value).isFalse()
117 
118         val (config, deferred) = makeEmojiConfig()
119         val ec = EmojiCompat.init(config)
120         deferred.complete(MetadataRepo.create(Typeface.DEFAULT))
121         ec.load()
122 
123         EmojiCompatStatus.fontLoaded.assertTrue()
124     }
125 
126     private fun State<Boolean>.assertTrue() {
127         // there's too many async actors to do anything reasonable and non-flaky here. tie up the
128         // test thread until main posts the value
129         runBlocking {
130             withTimeout(1000) { while (!value) delay(0) }
131             assertThat(value).isTrue()
132         }
133     }
134 
135     private fun makeEmojiConfig(): Pair<EmojiCompat.Config, CompletableDeferred<MetadataRepo?>> {
136         val deferred = CompletableDeferred<MetadataRepo?>(null)
137         val loader = MetadataRepoLoader { cb ->
138             CoroutineScope(Dispatchers.Default).launch {
139                 val result = deferred.await()
140                 if (result != null) {
141                     cb.onLoaded(result)
142                 } else {
143                     cb.onFailed(IllegalStateException("No"))
144                 }
145             }
146         }
147         val config = object : EmojiCompat.Config(loader) {}
148         config.setMetadataLoadStrategy(LOAD_STRATEGY_MANUAL)
149         return config to deferred
150     }
151 }
152