Skip to content

Commit 2018270

Browse files
Merge pull request #98 from contentstack/feat/dx-4450-add-asset-localisation-support
Feat/dx 4450 add asset localisation support
2 parents 608073d + 4e63754 commit 2018270

File tree

7 files changed

+120
-72
lines changed

7 files changed

+120
-72
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## Version 4.2.0
4+
5+
### Date: 02-Mar-2026
6+
7+
- Added asset localisation support
8+
39
## Version 4.1.0
410

511
### Date: 15-Sept-2025

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2012 - 2025 Contentstack
3+
Copyright (c) 2012 - 2026 Contentstack
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

contentstack/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ plugins {
77
ext {
88
PUBLISH_GROUP_ID = 'com.contentstack.sdk'
99
PUBLISH_ARTIFACT_ID = 'android'
10-
PUBLISH_VERSION = '4.1.0'
10+
PUBLISH_VERSION = '4.2.0'
1111
}
1212

1313
android {

contentstack/src/androidTest/java/com/contentstack/sdk/AssetTestCase.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ public void onCompletion(ResponseType responseType, Error error) {
124124
latch.await(5, TimeUnit.SECONDS);
125125
}
126126

127+
@Test
128+
public void test_setLocale_fetch() throws InterruptedException {
129+
final CountDownLatch latch = new CountDownLatch(1);
130+
final Asset asset = stack.asset(assetUid);
131+
asset.setLocale("en-us");
132+
asset.fetch(new FetchResultCallback() {
133+
@Override
134+
public void onCompletion(ResponseType responseType, Error error) {
135+
assertNotNull(asset.getAssetUid());
136+
latch.countDown();
137+
}
138+
});
139+
latch.await(5, TimeUnit.SECONDS);
140+
assertEquals("Query was not completed in time", 0, latch.getCount());
141+
}
142+
127143
@Test
128144
public void test_include_branch() {
129145
final Asset asset = stack.asset(assetUid);

contentstack/src/main/java/com/contentstack/sdk/Asset.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,4 +616,22 @@ public Asset includeBranch() {
616616
return this;
617617
}
618618

619+
/**
620+
* <p>
621+
* <br><br><b>Example :</b><br>
622+
* <pre class="prettyprint">
623+
* Asset asset = asset.setLocale("en-hi");
624+
* </pre>
625+
* </p>
626+
*/
627+
public Asset setLocale(String locale) {
628+
if (locale != null) {
629+
try {
630+
urlQueries.put("locale", locale);
631+
} catch (JSONException e) {
632+
Log.e(TAG, e.getLocalizedMessage());
633+
}
634+
}
635+
return this;
636+
}
619637
}

contentstack/src/test/java/com/contentstack/sdk/TestAssetAdvanced.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,37 @@ public void testAddParamOverwrite() {
189189
assertNotNull(asset);
190190
}
191191

192+
// ==================== SET LOCALE Tests ====================
193+
194+
@Test
195+
public void testSetLocale() {
196+
Asset result = asset.setLocale("en-us");
197+
assertNotNull(result);
198+
assertSame(asset, result);
199+
assertEquals("en-us", asset.urlQueries.optString("locale", ""));
200+
}
201+
202+
@Test
203+
public void testSetLocaleReturnsThis() {
204+
Asset result = asset.setLocale("en-hi");
205+
assertSame(asset, result);
206+
}
207+
208+
@Test
209+
public void testSetLocaleWithNull() {
210+
asset.setLocale("en-us");
211+
Asset result = asset.setLocale(null);
212+
assertSame(asset, result);
213+
assertEquals("en-us", asset.urlQueries.optString("locale", ""));
214+
}
215+
216+
@Test
217+
public void testSetLocaleChainedWithFetch() {
218+
asset.setLocale("en-us").includeFallback();
219+
assertTrue(asset.urlQueries.has("locale"));
220+
assertEquals("en-us", asset.urlQueries.optString("locale", ""));
221+
}
222+
192223
// ==================== GET METHODS Tests ====================
193224

194225
@Test

contentstack/src/test/java/com/contentstack/sdk/TestContentType.java

Lines changed: 47 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
import java.lang.reflect.Field;
1515
import java.lang.reflect.Method;
1616
import java.util.HashMap;
17+
import java.util.Iterator;
1718
import java.util.Map;
18-
import static org.mockito.Mockito.*;
19+
import java.util.concurrent.atomic.AtomicReference;
20+
1921
@RunWith(RobolectricTestRunner.class)
2022
@Config(sdk = 28, manifest = Config.NONE)
2123
public class TestContentType {
@@ -336,27 +338,8 @@ private ContentType createBareContentType(String contentTypeUid) {
336338
return new ContentType(contentTypeUid);
337339
}
338340

339-
private ContentType createContentTypeWithStackAndHeaders(String contentTypeUid) throws Exception {
340-
ContentType contentType = new ContentType(contentTypeUid);
341-
342-
// mock Stack and inject a stackHeader / localHeader field if present
343-
Stack mockStack = mock(Stack.class);
344-
345-
// We will inject "localHeader" field into Stack if it exists
346-
try {
347-
Field localHeaderField = Stack.class.getDeclaredField("localHeader");
348-
localHeaderField.setAccessible(true);
349-
ArrayMap<String, Object> stackHeaders = new ArrayMap<>();
350-
stackHeaders.put("environment", "prod-env");
351-
stackHeaders.put("stackKey", "stackVal");
352-
localHeaderField.set(mockStack, stackHeaders);
353-
} catch (NoSuchFieldException ignored) {
354-
// If Stack doesn't have localHeader, getHeader will just use localHeader or
355-
// null.
356-
}
357-
358-
contentType.setStackInstance(mockStack);
359-
return contentType;
341+
private ContentType createContentTypeWithStackAndHeaders(String contentTypeUid) {
342+
return stack.contentType(contentTypeUid);
360343
}
361344

362345
private ArrayMap<String, Object> getLocalHeader(ContentType contentType) throws Exception {
@@ -484,82 +467,68 @@ public void testQueryHasFormHeaderNonNull() throws Exception {
484467
@Test
485468
public void testFetchWithEmptyContentTypeNameCallsOnRequestFail() throws Exception {
486469
ContentType contentType = createBareContentType("");
487-
488-
// make sure stackInstance is not null
489-
contentType.setStackInstance(mock(Stack.class));
490-
491-
ContentTypesCallback callback = mock(ContentTypesCallback.class);
470+
contentType.setStackInstance(stack);
471+
472+
final AtomicReference<Error> errorRef = new AtomicReference<>();
473+
ContentTypesCallback callback = new ContentTypesCallback() {
474+
@Override
475+
public void onCompletion(ContentTypesModel contentTypesModel, Error error) {
476+
if (error != null) {
477+
errorRef.set(error);
478+
}
479+
}
480+
};
492481

493482
contentType.fetch(new JSONObject(), callback);
494483

495-
verify(callback).onRequestFail(eq(ResponseType.UNKNOWN), any(Error.class));
484+
assertNotNull(errorRef.get());
496485
}
497486

498487
@Test
499488
public void testFetchExceptionCallsOnRequestFail() throws Exception {
500489
ContentType contentType = createBareContentType("blog");
501-
contentType.setStackInstance(mock(Stack.class));
490+
contentType.setStackInstance(stack);
502491

503-
// Force an exception by using bad JSONObject for params
504-
JSONObject badParams = mock(JSONObject.class);
505-
when(badParams.keys()).thenThrow(new RuntimeException("boom"));
492+
final AtomicReference<Error> errorRef = new AtomicReference<>();
493+
ContentTypesCallback callback = new ContentTypesCallback() {
494+
@Override
495+
public void onCompletion(ContentTypesModel contentTypesModel, Error error) {
496+
if (error != null) {
497+
errorRef.set(error);
498+
}
499+
}
500+
};
506501

507-
ContentTypesCallback callback = mock(ContentTypesCallback.class);
502+
contentType.fetch(new ThrowingJSONObject(), callback);
508503

509-
contentType.fetch(badParams, callback);
510-
511-
verify(callback).onRequestFail(eq(ResponseType.UNKNOWN), any(Error.class));
504+
assertNotNull(errorRef.get());
512505
}
513506

514507
@Test
515508
public void testFetchNullParamsAndEnvironmentHeader() throws Exception {
516509
ContentType contentType = createBareContentType("blog");
510+
contentType.setStackInstance(stack);
517511

518-
// Create a fake Stack with environment in its localHeader (so getHeader picks
519-
// it)
520-
Stack mockStack = mock(Stack.class);
521-
522-
// Inject stack.localHeader if it exists
523-
try {
524-
Field localHeaderField = Stack.class.getDeclaredField("localHeader");
525-
localHeaderField.setAccessible(true);
526-
ArrayMap<String, Object> stackHeaders = new ArrayMap<>();
527-
stackHeaders.put("environment", "prod-env");
528-
localHeaderField.set(mockStack, stackHeaders);
529-
} catch (NoSuchFieldException ignored) {
530-
}
512+
ContentTypesCallback callback = new ContentTypesCallback() {
513+
@Override
514+
public void onCompletion(ContentTypesModel contentTypesModel, Error error) {}
515+
};
531516

532-
// Inject VERSION field if exists so URL is built properly (not strictly
533-
// necessary for coverage)
534-
try {
535-
Field versionField = Stack.class.getDeclaredField("VERSION");
536-
versionField.setAccessible(true);
537-
versionField.set(mockStack, "v3");
538-
} catch (NoSuchFieldException ignored) {
539-
}
540-
541-
contentType.setStackInstance(mockStack);
542-
543-
ContentTypesCallback callback = mock(ContentTypesCallback.class);
544-
545-
// this will hit:
546-
// if (params == null) params = new JSONObject();
547-
// then iterate keys (none)
548-
// then add environment if headers contains it
549517
contentType.fetch(null, callback);
550-
551-
// We don't verify callback interactions here; this is just to cover branches.
552518
}
553519

554520
@Test
555521
public void testFetchNormalCallDoesNotCrash() throws Exception {
556522
ContentType contentType = createBareContentType("blog");
557-
contentType.setStackInstance(mock(Stack.class));
523+
contentType.setStackInstance(stack);
558524

559525
JSONObject params = new JSONObject();
560526
params.put("limit", 3);
561527

562-
ContentTypesCallback callback = mock(ContentTypesCallback.class);
528+
ContentTypesCallback callback = new ContentTypesCallback() {
529+
@Override
530+
public void onCompletion(ContentTypesModel contentTypesModel, Error error) {}
531+
};
563532

564533
contentType.fetch(params, callback);
565534
}
@@ -593,4 +562,12 @@ public void testGetUrlParamsNullOrEmptyReturnsNull() throws Exception {
593562
HashMap<String, Object> resultEmpty = invokeGetUrlParams(contentType, empty);
594563
assertNull(resultEmpty);
595564
}
565+
566+
/** JSONObject that throws when keys() is called – used to trigger exception path in fetch() without Mockito. */
567+
private static class ThrowingJSONObject extends JSONObject {
568+
@Override
569+
public Iterator<String> keys() {
570+
throw new RuntimeException("boom");
571+
}
572+
}
596573
}

0 commit comments

Comments
 (0)