Skip to content

Commit 635ca57

Browse files
authored
Add support to read dcf file from res/raw (#1778)
Add support in DesignSettings and DocServer to use R.raw.* Add unit test for loading HelloWorld dcf file from res/raw Add test methods in DesignSettings and DocServer Add more steps to clear state in ClearStateTestRule Test: ./gradlew designcompose:testDebugUnitTest# Please enter the commit message for your changes. Lines starting
1 parent 24823f7 commit 635ca57

File tree

5 files changed

+137
-5
lines changed

5 files changed

+137
-5
lines changed

designcompose/src/main/java/com/android/designcompose/DocServer.kt

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616

1717
package com.android.designcompose
1818

19+
import android.content.res.Resources
1920
import android.os.Handler
2021
import android.os.Looper
2122
import android.util.Log
2223
import android.widget.Toast
2324
import androidx.activity.ComponentActivity
25+
import androidx.annotation.RawRes
2426
import androidx.annotation.RestrictTo
2527
import androidx.annotation.VisibleForTesting
2628
import androidx.compose.runtime.Composable
@@ -39,6 +41,7 @@ import com.android.designcompose.common.DocumentServerParams
3941
import java.io.BufferedInputStream
4042
import java.io.File
4143
import java.io.FileInputStream
44+
import java.io.InputStream
4245
import java.lang.ref.WeakReference
4346
import java.net.ConnectException
4447
import java.net.SocketException
@@ -96,6 +99,11 @@ object DesignSettings {
9699
internal var isDocumentLive = mutableStateOf(false)
97100
private var fontDb: HashMap<String, FontFamily> = HashMap()
98101
internal var fileFetchStatus: HashMap<DesignDocId, DesignDocStatus> = HashMap()
102+
internal var rawResourceId: Int = Resources.ID_NULL
103+
104+
@VisibleForTesting
105+
@RestrictTo(RestrictTo.Scope.TESTS)
106+
fun testOnlyClearFileFetchStatus() = fileFetchStatus.clear()
99107

100108
@VisibleForTesting
101109
@RestrictTo(RestrictTo.Scope.TESTS)
@@ -149,6 +157,18 @@ object DesignSettings {
149157
toastsEnabled = false
150158
}
151159

160+
/**
161+
* Sets the raw resource id for serialized doc. Once set, the design doc will only be read from
162+
* res/raw/the_dcf_file. Live updates do not work when this is set. All other files will be
163+
* ignored: downloaded/cached files, assets/figma/
164+
*
165+
* @param resourceId of the raw dcf file placed in res/raw
166+
* @sample R.raw.the_dcf_file
167+
*/
168+
fun setRawResourceId(@RawRes resourceId: Int) {
169+
rawResourceId = resourceId
170+
}
171+
152172
fun addFontFamily(name: String, family: FontFamily) {
153173
fontDb[name] = family
154174
}
@@ -236,6 +256,10 @@ internal object DocServer {
236256
scheduleLiveUpdate()
237257
}
238258
}
259+
260+
@VisibleForTesting
261+
@RestrictTo(RestrictTo.Scope.TESTS)
262+
fun testOnlyClearDocuments() = documents.clear()
239263
}
240264

241265
internal fun DocServer.initializeLiveUpdate() {
@@ -508,9 +532,21 @@ internal fun DocServer.doc(
508532
}
509533

510534
// Use the LocalContext to locate this doc in the precompiled DesignComposeDefinitionuments
511-
val assetManager = context.assets
512535
try {
513-
val assetDoc = assetManager.open("figma/$id.dcf")
536+
val assetDoc: InputStream =
537+
if (DesignSettings.rawResourceId != Resources.ID_NULL) {
538+
Log.i(
539+
TAG,
540+
"Loaded design doc from R.raw." +
541+
context.resources.getResourceEntryName(DesignSettings.rawResourceId),
542+
)
543+
context.resources.openRawResource(DesignSettings.rawResourceId)
544+
} else {
545+
val fileName = "figma/$id.dcf"
546+
Log.i(TAG, "Loaded design doc from assets/$fileName")
547+
context.assets.open(fileName)
548+
}
549+
514550
val decodedDoc = decodeDiskDoc(assetDoc, null, docId, Feedback)
515551
if (decodedDoc != null) {
516552
synchronized(documents) { documents[docId] = decodedDoc }
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2024 Google LLC
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 com.android.designcompose
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.ui.test.junit4.createComposeRule
21+
import androidx.compose.ui.test.onNodeWithText
22+
import androidx.test.ext.junit.runners.AndroidJUnit4
23+
import com.android.designcompose.TestUtils.ClearStateTestRule
24+
import com.android.designcompose.annotation.Design
25+
import com.android.designcompose.annotation.DesignComponent
26+
import com.android.designcompose.annotation.DesignDoc
27+
import com.android.designcompose.test.R
28+
import com.android.designcompose.test.assertRenderStatus
29+
import com.android.designcompose.test.onDCDoc
30+
import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers
31+
import org.junit.Rule
32+
import org.junit.Test
33+
import org.junit.runner.RunWith
34+
import org.robolectric.annotation.Config
35+
import org.robolectric.annotation.GraphicsMode
36+
37+
const val helloWorldDocId = "pxVlixodJqZL95zo2RzTHl"
38+
39+
@DesignDoc(id = helloWorldDocId)
40+
interface HelloWorld {
41+
@DesignComponent(node = "#MainFrame", hideDesignSwitcher = true)
42+
fun mainFrame(@Design(node = "#Name") name: String)
43+
}
44+
45+
@Composable
46+
fun HelloWorld() {
47+
HelloWorldDoc.mainFrame(name = "Testers!")
48+
}
49+
50+
@RunWith(AndroidJUnit4::class)
51+
@GraphicsMode(GraphicsMode.Mode.NATIVE)
52+
@Config(qualifiers = RobolectricDeviceQualifiers.SmallPhone, sdk = [34])
53+
class DesignRawResourceTest {
54+
// Must reset the DocServer and DesignSettings state to prevent reusing cached files
55+
@get:Rule val clearStateTestRule = ClearStateTestRule()
56+
@get:Rule val composeTestRule = createComposeRule()
57+
58+
@Test
59+
fun testHelloWorldDoc_setRawResourceId_passes() {
60+
with(composeTestRule) {
61+
DesignSettings.setRawResourceId(R.raw.raw_resource_test_hello_world_doc)
62+
composeTestRule.setContent { HelloWorld() }
63+
64+
onDCDoc(HelloWorldDoc).assertRenderStatus(DocRenderStatus.Rendered)
65+
onNodeWithText("Testers!", substring = true).assertExists()
66+
onNodeWithText("Hello", substring = true).assertExists()
67+
}
68+
}
69+
70+
@Test
71+
fun testHelloWorldDoc_noRawResource_fails() {
72+
with(composeTestRule) {
73+
composeTestRule.setContent { HelloWorld() }
74+
75+
onDCDoc(HelloWorldDoc).assertDoesNotExist()
76+
}
77+
}
78+
}
Binary file not shown.

designcompose/src/testFixtures/java/com/android/designcompose/TestUtils.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import static com.android.designcompose.TestUtilsKt.testOnlyTriggerLiveUpdate;
2020

21+
import android.content.res.Resources;
22+
2123
import androidx.test.platform.app.InstrumentationRegistry;
2224

2325
import org.junit.rules.TestRule;
@@ -48,11 +50,23 @@ private static void clearInteractionStates() {
4850
InteractionStateManager.INSTANCE.getStates().clear();
4951
}
5052

53+
private static void clearDocServer() {
54+
DocServer.INSTANCE.testOnlyClearDocuments();
55+
}
56+
57+
private static void clearDesignSettings() {
58+
DesignSettings.INSTANCE.testOnlyClearFileFetchStatus();
59+
DesignSettings.INSTANCE.setRawResourceId(Resources.ID_NULL);
60+
}
61+
62+
5163
public static class ClearStateTestRule implements TestRule {
5264

5365
@Override
5466
public Statement apply(Statement base, Description description) {
5567
clearInteractionStates();
68+
clearDocServer();
69+
clearDesignSettings();
5670
return base;
5771
}
5872
}

dev-scripts/common-functions.sh

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,14 @@ function fetch_design_switcher {
5959

6060
function fetch_helloworld {
6161
DOC_ID=pxVlixodJqZL95zo2RzTHl
62+
OUTPUT_FILE_REFERENCE_APP="$GIT_ROOT/reference-apps/helloworld/helloworld-app/src/main/assets/figma/HelloWorldDoc_$DOC_ID.dcf"
63+
OUTPUT_FILE_RAW_RES_TEST="$GIT_ROOT/designcompose/src/testDebug/res/raw/raw_resource_test_hello_world_doc"
6264
cargo run --bin fetch --features=fetch -- \
6365
--doc-id="$DOC_ID" \
6466
--nodes="#MainFrame" \
65-
--output="$GIT_ROOT/reference-apps/helloworld/helloworld-app/src/main/assets/figma/HelloWorldDoc_$DOC_ID.dcf"
66-
67+
--output=$OUTPUT_FILE_REFERENCE_APP
68+
# Reuse Hello World doc for testing doc loading from res/raw
69+
cp $OUTPUT_FILE_REFERENCE_APP $OUTPUT_FILE_RAW_RES_TEST
6770
}
6871

6972
function fetch_tutorial {
@@ -112,4 +115,5 @@ function fetch_state_customizations {
112115
--doc-id="$DOC_ID" \
113116
--nodes="#root" \
114117
--output="$GIT_ROOT/integration-tests/validation/src/main/assets/figma/StateCustomizationsDoc_$DOC_ID.dcf"
115-
}
118+
}
119+

0 commit comments

Comments
 (0)