Skip to content

Commit 9315f7b

Browse files
committed
added tests and UI tests
fixed feedback form behaviour added manifest options
1 parent 1a758a2 commit 9315f7b

File tree

20 files changed

+841
-41
lines changed

20 files changed

+841
-41
lines changed

sentry-android-core/api/sentry-android-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ public final class io/sentry/android/core/SentryUserFeedbackDialog : android/app
390390
public fun <init> (Landroid/content/Context;I)V
391391
public fun <init> (Landroid/content/Context;ZLandroid/content/DialogInterface$OnCancelListener;)V
392392
public fun setCancelable (Z)V
393+
public fun show ()V
393394
}
394395

395396
public class io/sentry/android/core/SpanFrameMetricsCollector : io/sentry/IPerformanceContinuousCollector, io/sentry/android/core/internal/util/SentryFrameMetricsCollector$FrameMetricsCollectorListener {

sentry-android-core/src/main/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
33
<uses-permission android:name="android.permission.INTERNET"/>
44

5-
<application>
5+
<application
6+
android:supportsRtl="true">
67
<!-- 'android:authorities' must be unique in the device, across all apps -->
78
<provider
89
android:name=".SentryInitProvider"

sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.sentry.ILogger;
77
import io.sentry.InitPriority;
88
import io.sentry.ProfileLifecycle;
9+
import io.sentry.SentryFeedbackOptions;
910
import io.sentry.SentryIntegrationPackageStorage;
1011
import io.sentry.SentryLevel;
1112
import io.sentry.protocol.SdkVersion;
@@ -126,6 +127,18 @@ final class ManifestMetadataReader {
126127
static final String ENABLE_AUTO_TRACE_ID_GENERATION =
127128
"io.sentry.traces.enable-auto-id-generation";
128129

130+
static final String FEEDBACK_NAME_REQUIRED = "io.sentry.feedback.is-name-required";
131+
132+
static final String FEEDBACK_SHOW_NAME = "io.sentry.feedback.show-name";
133+
134+
static final String FEEDBACK_EMAIL_REQUIRED = "io.sentry.feedback.is-email-required";
135+
136+
static final String FEEDBACK_SHOW_EMAIL = "io.sentry.feedback.show-email";
137+
138+
static final String FEEDBACK_USE_SENTRY_USER = "io.sentry.feedback.use-sentry-user";
139+
140+
static final String FEEDBACK_SHOW_BRANDING = "io.sentry.feedback.show-branding";
141+
129142
/** ManifestMetadataReader ctor */
130143
private ManifestMetadataReader() {}
131144

@@ -477,6 +490,21 @@ static void applyMetadata(
477490
options
478491
.getLogs()
479492
.setEnabled(readBool(metadata, logger, ENABLE_LOGS, options.getLogs().isEnabled()));
493+
494+
final @NotNull SentryFeedbackOptions feedbackOptions = options.getFeedbackOptions();
495+
feedbackOptions.setNameRequired(
496+
readBool(metadata, logger, FEEDBACK_NAME_REQUIRED, feedbackOptions.isNameRequired()));
497+
feedbackOptions.setShowName(
498+
readBool(metadata, logger, FEEDBACK_SHOW_NAME, feedbackOptions.isShowName()));
499+
feedbackOptions.setEmailRequired(
500+
readBool(metadata, logger, FEEDBACK_EMAIL_REQUIRED, feedbackOptions.isEmailRequired()));
501+
feedbackOptions.setShowEmail(
502+
readBool(metadata, logger, FEEDBACK_SHOW_EMAIL, feedbackOptions.isShowEmail()));
503+
feedbackOptions.setUseSentryUser(
504+
readBool(
505+
metadata, logger, FEEDBACK_USE_SENTRY_USER, feedbackOptions.isUseSentryUser()));
506+
feedbackOptions.setShowBranding(
507+
readBool(metadata, logger, FEEDBACK_SHOW_BRANDING, feedbackOptions.isShowBranding()));
480508
}
481509
options
482510
.getLogger()

sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackDialog.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
import android.widget.ImageView;
1010
import android.widget.TextView;
1111
import android.widget.Toast;
12+
import io.sentry.IScopes;
1213
import io.sentry.Sentry;
1314
import io.sentry.SentryFeedbackOptions;
15+
import io.sentry.SentryLevel;
16+
import io.sentry.SentryOptions;
1417
import io.sentry.protocol.Feedback;
1518
import io.sentry.protocol.SentryId;
1619
import io.sentry.protocol.User;
@@ -23,7 +26,6 @@ public final class SentryUserFeedbackDialog extends AlertDialog {
2326

2427
public SentryUserFeedbackDialog(final @NotNull Context context) {
2528
super(context);
26-
isCancelable = false;
2729
}
2830

2931
public SentryUserFeedbackDialog(
@@ -36,7 +38,6 @@ public SentryUserFeedbackDialog(
3638

3739
public SentryUserFeedbackDialog(final @NotNull Context context, final int themeResId) {
3840
super(context, themeResId);
39-
isCancelable = false;
4041
}
4142

4243
@Override
@@ -107,15 +108,35 @@ protected void onCreate(Bundle savedInstanceState) {
107108
}
108109

109110
lblMessage.setText(feedbackOptions.getMessageLabel());
111+
lblMessage.append(feedbackOptions.getIsRequiredLabel());
110112
edtMessage.setHint(feedbackOptions.getMessagePlaceholder());
111113
lblTitle.setText(feedbackOptions.getFormTitle());
112114

113115
btnSend.setText(feedbackOptions.getSubmitButtonLabel());
114116
btnSend.setOnClickListener(
115117
v -> {
116-
final @NotNull Feedback feedback = new Feedback(edtMessage.getText().toString());
117-
feedback.setName(edtName.getText().toString());
118-
feedback.setContactEmail(edtEmail.getText().toString());
118+
final @NotNull String name = edtName.getText().toString().trim();
119+
final @NotNull String email = edtEmail.getText().toString().trim();
120+
final @NotNull String message = edtMessage.getText().toString().trim();
121+
final @NotNull Feedback feedback = new Feedback(message);
122+
123+
if (name.isEmpty() && feedbackOptions.isNameRequired()) {
124+
edtName.setError(lblName.getText());
125+
return;
126+
}
127+
128+
if (email.isEmpty() && feedbackOptions.isEmailRequired()) {
129+
edtEmail.setError(lblEmail.getText());
130+
return;
131+
}
132+
133+
if (message.isEmpty()) {
134+
edtMessage.setError(lblMessage.getText());
135+
return;
136+
}
137+
138+
feedback.setName(name);
139+
feedback.setContactEmail(email);
119140

120141
SentryId id = Sentry.captureFeedback(feedback);
121142
if (!id.equals(SentryId.EMPTY_ID)) {
@@ -156,4 +177,17 @@ protected void onStart() {
156177
onFormOpen.run();
157178
}
158179
}
180+
181+
@Override
182+
public void show() {
183+
final @NotNull IScopes scopes = Sentry.getCurrentScopes();
184+
final @NotNull SentryOptions options = scopes.getOptions();
185+
if (!scopes.isEnabled() || !options.isEnabled()) {
186+
options
187+
.getLogger()
188+
.log(SentryLevel.WARNING, "Sentry is disabled. Feedback dialog won't be shown.");
189+
return;
190+
}
191+
super.show();
192+
}
159193
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

sentry-android-core/src/main/res/layout/sentry_dialog_user_feedback.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:tools="http://schemas.android.com/tools"
4+
android:id="@+id/sentry_dialog_user_feedback_layout"
45
android:layout_width="match_parent"
56
android:layout_height="match_parent"
67
tools:ignore="HardcodedText"
@@ -26,8 +27,9 @@
2627
android:layout_alignTop="@+id/sentry_dialog_user_feedback_title"
2728
android:layout_alignBottom="@+id/sentry_dialog_user_feedback_title"
2829
android:layout_alignParentEnd="true"
30+
android:contentDescription="Logo of the brand"
2931
android:tint="?android:attr/colorForeground"
30-
android:src="@drawable/sentry_logo_dark_400x352"/>
32+
android:src="@drawable/sentry_logo_dark"/>
3133

3234
<TextView
3335
android:id="@+id/sentry_dialog_user_feedback_txt_name"

sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,4 +1615,154 @@ class ManifestMetadataReaderTest {
16151615
// Assert
16161616
assertTrue(fixture.options.logs.isEnabled)
16171617
}
1618+
1619+
@Test
1620+
fun `applyMetadata reads feedback name required and keep default value if not found`() {
1621+
// Arrange
1622+
val context = fixture.getContext()
1623+
1624+
// Act
1625+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1626+
1627+
// Assert
1628+
assertFalse(fixture.options.feedbackOptions.isNameRequired)
1629+
}
1630+
1631+
@Test
1632+
fun `applyMetadata reads feedback name required to options`() {
1633+
// Arrange
1634+
val bundle = bundleOf(ManifestMetadataReader.FEEDBACK_NAME_REQUIRED to true)
1635+
val context = fixture.getContext(metaData = bundle)
1636+
1637+
// Act
1638+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1639+
1640+
// Assert
1641+
assertTrue(fixture.options.feedbackOptions.isNameRequired)
1642+
}
1643+
1644+
@Test
1645+
fun `applyMetadata reads feedback show name and keep default value if not found`() {
1646+
// Arrange
1647+
val context = fixture.getContext()
1648+
1649+
// Act
1650+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1651+
1652+
// Assert
1653+
assertTrue(fixture.options.feedbackOptions.isShowName)
1654+
}
1655+
1656+
@Test
1657+
fun `applyMetadata reads feedback show name to options`() {
1658+
// Arrange
1659+
val bundle = bundleOf(ManifestMetadataReader.FEEDBACK_SHOW_NAME to false)
1660+
val context = fixture.getContext(metaData = bundle)
1661+
1662+
// Act
1663+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1664+
1665+
// Assert
1666+
assertFalse(fixture.options.feedbackOptions.isShowName)
1667+
}
1668+
1669+
@Test
1670+
fun `applyMetadata reads feedback email required and keep default value if not found`() {
1671+
// Arrange
1672+
val context = fixture.getContext()
1673+
1674+
// Act
1675+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1676+
1677+
// Assert
1678+
assertFalse(fixture.options.feedbackOptions.isEmailRequired)
1679+
}
1680+
1681+
@Test
1682+
fun `applyMetadata reads feedback email required to options`() {
1683+
// Arrange
1684+
val bundle = bundleOf(ManifestMetadataReader.FEEDBACK_EMAIL_REQUIRED to true)
1685+
val context = fixture.getContext(metaData = bundle)
1686+
1687+
// Act
1688+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1689+
1690+
// Assert
1691+
assertTrue(fixture.options.feedbackOptions.isEmailRequired)
1692+
}
1693+
1694+
@Test
1695+
fun `applyMetadata reads feedback show email and keep default value if not found`() {
1696+
// Arrange
1697+
val context = fixture.getContext()
1698+
1699+
// Act
1700+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1701+
1702+
// Assert
1703+
assertTrue(fixture.options.feedbackOptions.isShowEmail)
1704+
}
1705+
1706+
@Test
1707+
fun `applyMetadata reads feedback show email to options`() {
1708+
// Arrange
1709+
val bundle = bundleOf(ManifestMetadataReader.FEEDBACK_SHOW_EMAIL to false)
1710+
val context = fixture.getContext(metaData = bundle)
1711+
1712+
// Act
1713+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1714+
1715+
// Assert
1716+
assertFalse(fixture.options.feedbackOptions.isShowEmail)
1717+
}
1718+
1719+
@Test
1720+
fun `applyMetadata reads feedback use sentry user and keep default value if not found`() {
1721+
// Arrange
1722+
val context = fixture.getContext()
1723+
1724+
// Act
1725+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1726+
1727+
// Assert
1728+
assertTrue(fixture.options.feedbackOptions.isUseSentryUser)
1729+
}
1730+
1731+
@Test
1732+
fun `applyMetadata reads feedback use sentry user to options`() {
1733+
// Arrange
1734+
val bundle = bundleOf(ManifestMetadataReader.FEEDBACK_USE_SENTRY_USER to false)
1735+
val context = fixture.getContext(metaData = bundle)
1736+
1737+
// Act
1738+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1739+
1740+
// Assert
1741+
assertFalse(fixture.options.feedbackOptions.isUseSentryUser)
1742+
}
1743+
1744+
@Test
1745+
fun `applyMetadata reads feedback show branding and keep default value if not found`() {
1746+
// Arrange
1747+
val context = fixture.getContext()
1748+
1749+
// Act
1750+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1751+
1752+
// Assert
1753+
assertTrue(fixture.options.feedbackOptions.isShowBranding)
1754+
}
1755+
1756+
@Test
1757+
fun `applyMetadata reads feedback show branding to options`() {
1758+
// Arrange
1759+
val bundle = bundleOf(ManifestMetadataReader.FEEDBACK_SHOW_BRANDING to false)
1760+
val context = fixture.getContext(metaData = bundle)
1761+
1762+
// Act
1763+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1764+
1765+
// Assert
1766+
assertFalse(fixture.options.feedbackOptions.isShowBranding)
1767+
}
16181768
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package io.sentry.android.core
2+
3+
import io.sentry.ILogger
4+
import io.sentry.IScopes
5+
import io.sentry.Sentry
6+
import io.sentry.SentryLevel
7+
import org.mockito.Mockito.mockStatic
8+
import org.mockito.kotlin.eq
9+
import org.mockito.kotlin.mock
10+
import org.mockito.kotlin.verify
11+
import org.mockito.kotlin.verifyNoInteractions
12+
import org.mockito.kotlin.whenever
13+
import kotlin.test.AfterTest
14+
import kotlin.test.BeforeTest
15+
import kotlin.test.Test
16+
17+
class SentryUserFeedbackDialogTest {
18+
19+
class Fixture {
20+
private val mockDsn = "http://key@localhost/proj"
21+
22+
val mockedSentry = mockStatic(Sentry::class.java)
23+
val mockScopes = mock<IScopes>()
24+
val mockLogger = mock<ILogger>()
25+
val options = SentryAndroidOptions().apply {
26+
dsn = mockDsn
27+
profilesSampleRate = 1.0
28+
isDebug = true
29+
setLogger(mockLogger)
30+
}
31+
32+
init {
33+
whenever(mockScopes.options).thenReturn(options)
34+
whenever(mockScopes.isEnabled).thenReturn(true)
35+
}
36+
37+
fun getSut(): SentryUserFeedbackDialog {
38+
return SentryUserFeedbackDialog(mock())
39+
}
40+
}
41+
42+
private val fixture = Fixture()
43+
44+
@BeforeTest
45+
fun setUp() {
46+
fixture.mockedSentry.`when`<Any> { Sentry.getCurrentScopes() }.thenReturn(fixture.mockScopes)
47+
}
48+
49+
@AfterTest
50+
fun cleanup() {
51+
fixture.mockedSentry.close()
52+
}
53+
54+
@Test
55+
fun `feedback dialog is shown when sdk is enabled`() {
56+
fixture.options.isEnabled = true
57+
val sut = fixture.getSut()
58+
verifyNoInteractions(fixture.mockLogger)
59+
sut.show()
60+
verifyNoInteractions(fixture.mockLogger)
61+
}
62+
63+
@Test
64+
fun `feedback dialog is not shown when sdk is disabled`() {
65+
fixture.options.isEnabled = false
66+
val sut = fixture.getSut()
67+
sut.show()
68+
verify(fixture.mockLogger).log(eq(SentryLevel.WARNING), eq("Sentry is disabled. Feedback dialog won't be shown."))
69+
}
70+
}

0 commit comments

Comments
 (0)