Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel;
import org.wordpress.android.ui.prefs.homepage.HomepageSettingsViewModel;
import org.wordpress.android.ui.prefs.language.LocalePickerViewModel;
import org.wordpress.android.ui.prefs.taxonomies.TaxonomiesNavMenuViewModel;
import org.wordpress.android.ui.prefs.timezone.SiteSettingsTimezoneViewModel;
import org.wordpress.android.ui.publicize.PublicizeListViewModel;
import org.wordpress.android.ui.reader.ReaderCommentListViewModel;
Expand Down Expand Up @@ -495,6 +496,12 @@ abstract class ViewModelModule {
@ViewModelKey(BloggingRemindersViewModel.class)
abstract ViewModel bloggingRemindersViewModel(BloggingRemindersViewModel viewModel);

@Binds
@IntoMap
@ViewModelKey(TaxonomiesNavMenuViewModel.class)
abstract ViewModel taxonomiesNavMenuViewModel(TaxonomiesNavMenuViewModel viewModel);


@Binds
@IntoMap
@ViewModelKey(LocalePickerViewModel.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
import org.wordpress.android.ui.prefs.EditTextPreferenceWithValidation.ValidationType;
import org.wordpress.android.ui.prefs.SiteSettingsFormatDialog.FormatType;
import org.wordpress.android.ui.prefs.homepage.HomepageSettingsDialog;
import org.wordpress.android.ui.prefs.taxonomies.TaxonomiesNavMenuViewModel;
import org.wordpress.android.ui.prefs.timezone.SiteSettingsTimezoneBottomSheet;
import org.wordpress.android.ui.utils.UiHelpers;
import org.wordpress.android.util.AppLog;
Expand Down Expand Up @@ -114,6 +115,7 @@
import javax.inject.Inject;

import kotlin.Triple;
import uniffi.wp_api.TaxonomyTypeDetailsWithEditContext;

import static org.wordpress.android.ui.prefs.WPComSiteSettings.supportsJetpackSiteAcceleratorSettings;

Expand Down Expand Up @@ -194,6 +196,8 @@

private BloggingRemindersViewModel mBloggingRemindersViewModel;

private TaxonomiesNavMenuViewModel mTaxonomiesNavMenuViewModel;

Check notice

Code scanning / Android Lint

Nullable/NonNull annotation missing on field Note

Missing null annotation

public SiteModel mSite;

// Can interface with WP.com or WP.org
Expand Down Expand Up @@ -1107,6 +1111,52 @@

initBloggingSection();
removeEmptyCategories();
initTaxonomies();
}

private void initTaxonomies() {
mTaxonomiesNavMenuViewModel = new ViewModelProvider(getAppCompatActivity(), mViewModelFactory)
.get(TaxonomiesNavMenuViewModel.class);
mTaxonomiesNavMenuViewModel.getTaxonomies().observe(getAppCompatActivity(), this::showTaxonomies);
mTaxonomiesNavMenuViewModel.fetchTaxonomies(mSite);
}

private void showTaxonomies(List<TaxonomyTypeDetailsWithEditContext> taxonomies) {
if (taxonomies.isEmpty()) {
return;
}
PreferenceGroup siteScreen = (PreferenceGroup) findPreference(getString(R.string.pref_key_site_screen));
if (siteScreen != null) {
// Create taxonomies preference group
final String taxonomiesPrefKey = getString(R.string.pref_key_taxonomies);
PreferenceGroup taxonomiesPreference = (PreferenceGroup) findPreference(taxonomiesPrefKey);
if (taxonomiesPreference != null) {
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_taxonomies);
}
taxonomiesPreference = new PreferenceCategory(getActivity());
taxonomiesPreference.setTitle(getString(R.string.taxonomies_title));
taxonomiesPreference.setKey(taxonomiesPrefKey);
siteScreen.addPreference(taxonomiesPreference);

for (TaxonomyTypeDetailsWithEditContext taxonomy : taxonomies) {
Preference pref = new Preference(getActivity());
pref.setTitle(taxonomy.getName());
pref.setKey(taxonomy.getSlug());
pref.setOnPreferenceClickListener(preference -> {
// TODO: Create generic taxonomies DataView and call it from here
// We are not accepting the taxonomy name as a parameter yet
// So Categories and Tags are still hardcoded
if ("category".equals(taxonomy.getSlug())) {
ActivityLauncher.showCategoriesList(getActivity(), mSite);
} else if ("post_tag".equals(taxonomy.getSlug())) {
SiteSettingsTagListActivity.showTagList(getActivity(), mSite);
}
return false;
}
);
taxonomiesPreference.addPreference(pref);
}
}
}

private void updateHomepageSummary() {
Expand Down Expand Up @@ -2033,18 +2083,7 @@
if (group != null) {
group.removeAll();
}
if (mSite.isUsingSelfHostedRestApi()) {
// Remove everything inside "Writing" preference but "Categories" and "Tags" which are now supported
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_category);
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_format);
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_date_format);
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_time_format);
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_week_start);
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_posts_per_page);
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_related_posts);
} else {
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_writing);
}
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_writing);
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_discussion);
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_advanced);
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_quota);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.wordpress.android.ui.prefs.taxonomies

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider
import org.wordpress.android.fluxc.utils.AppLogWrapper
import org.wordpress.android.util.AppLog
import rs.wordpress.api.kotlin.WpRequestResult
import uniffi.wp_api.TaxonomyListParams
import uniffi.wp_api.TaxonomyTypeDetailsWithEditContext
import javax.inject.Inject

class TaxonomiesNavMenuViewModel @Inject constructor(
private val wpApiClientProvider: WpApiClientProvider,
private val appLogWrapper: AppLogWrapper,
) : ViewModel() {
// LiveData because this is observed from Java
private val _taxonomies = MutableLiveData<List<TaxonomyTypeDetailsWithEditContext>>()
val taxonomies: LiveData<List<TaxonomyTypeDetailsWithEditContext>> = _taxonomies

fun fetchTaxonomies(site: SiteModel) {
if (!site.isUsingSelfHostedRestApi) {
appLogWrapper.d(
AppLog.T.API,
"Taxonomies - Taxonomies cannot be fetched: Application Password not available"
)
return
}
viewModelScope.launch {
val client = wpApiClientProvider.getWpApiClient(site)
val response = client.request { requestBuilder ->
requestBuilder.taxonomies().listWithEditContext(TaxonomyListParams())
}
when (response) {
is WpRequestResult.Success -> {
val list = response.response.data
appLogWrapper.d(AppLog.T.API, "Taxonomies - Fetched taxonomies ${list.taxonomyTypes.size}")
val taxonomies = mutableListOf<TaxonomyTypeDetailsWithEditContext>()
list.taxonomyTypes.forEach { type ->
appLogWrapper.d(AppLog.T.API, "Taxonomies - Taxonomy ${type.value.name}")
if (type.value.visibility.showInNavMenus) {
taxonomies.add(type.value)
}
}
_taxonomies.value = taxonomies
}

else -> {
appLogWrapper.e(AppLog.T.API, "Taxonomies - Error fetching taxonomies")
}
}
}
}
}
1 change: 1 addition & 0 deletions WordPress/src/main/res/values/key_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<string name="pref_key_language" translatable="false">wp_pref_language</string>
<string name="pref_key_app_theme" translatable="false">wp_pref_app_theme</string>
<string name="pref_key_whats_new" translatable="false">wp_pref_whats_new</string>
<string name="pref_key_taxonomies" translatable="false">wp_pref_taxonomies</string>
<string name="pref_notification_blogs" translatable="false">wp_pref_notification_blogs</string>
<string name="pref_notification_blogs_followed" translatable="false">pref_notification_blogs_followed</string>
<string name="pref_notification_other_category" translatable="false">wp_pref_notification_other_category</string>
Expand Down
2 changes: 2 additions & 0 deletions WordPress/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,8 @@
<string name="top_level_category_name">Top level</string>
<string name="dlg_confirm_delete_category">Permanently delete \'%s\' Category?</string>

<string name="taxonomies_title">Taxonomies</string>


<!-- action from share intents -->
<string name="share_action_post">Add to new post</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package org.wordpress.android.ui.prefs.taxonomies

import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.wordpress.android.BaseUnitTest
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider
import org.wordpress.android.fluxc.utils.AppLogWrapper
import org.wordpress.android.util.AppLog
import rs.wordpress.api.kotlin.WpApiClient
import rs.wordpress.api.kotlin.WpRequestResult
import uniffi.wp_api.TaxonomiesRequestListWithEditContextResponse
import uniffi.wp_api.TaxonomyType
import uniffi.wp_api.TaxonomyTypeDetailsWithEditContext
import uniffi.wp_api.TaxonomyTypesResponseWithEditContext
import uniffi.wp_api.TaxonomyTypeVisibility
import uniffi.wp_api.WpNetworkHeaderMap

@ExperimentalCoroutinesApi
class TaxonomiesNavMenuViewModelTest : BaseUnitTest() {
@Mock
private lateinit var wpApiClientProvider: WpApiClientProvider

@Mock
private lateinit var wpApiClient: WpApiClient

@Mock
private lateinit var appLogWrapper: AppLogWrapper

private lateinit var viewModel: TaxonomiesNavMenuViewModel

private var taxonomies: List<TaxonomyTypeDetailsWithEditContext> = listOf()

private val testSite = SiteModel().apply {
id = 123
url = "https://test.wordpress.com"
apiRestUsernamePlain = "user"
apiRestPasswordPlain = "pass"
setIsWPCom(false)
}

@Before
fun setUp() {
MockitoAnnotations.openMocks(this)

whenever(wpApiClientProvider.getWpApiClient(testSite)).thenReturn(wpApiClient)

viewModel = TaxonomiesNavMenuViewModel(
wpApiClientProvider,
appLogWrapper
)
viewModel.taxonomies.observeForever { taxonomies = it }
}

@Test
fun `when site does not support self-hosted rest api, then taxonomies are not fetched`() = test {
testSite.setIsWPCom(true)

viewModel.fetchTaxonomies(testSite)
advanceUntilIdle()

verify(wpApiClientProvider, never()).getWpApiClient(any(), any())
verify(appLogWrapper).d(
AppLog.T.API,
"Taxonomies - Taxonomies cannot be fetched: Application Password not available"
)
assertTrue(taxonomies.isEmpty())
}

@Test
fun `when LiveData is observed, it starts with null value`() {
assertNotNull(viewModel.taxonomies)
assertEquals(null, viewModel.taxonomies.value)
}

@Test
fun `fetch taxonomies with success response dispatches success action`() = runTest {
// Create the correct response structure following the MediaRSApiRestClientTest pattern
val response = TaxonomiesRequestListWithEditContextResponse(
createTestTaxonomyTypesResponseWithEditContext(),
mock<WpNetworkHeaderMap>(),
)

val successResponse: WpRequestResult<TaxonomiesRequestListWithEditContextResponse> = WpRequestResult.Success(
response = response
)

whenever(wpApiClient.request<TaxonomiesRequestListWithEditContextResponse>(any())).thenReturn(successResponse)

viewModel.fetchTaxonomies(testSite)
advanceUntilIdle()

val responseList: List<TaxonomyTypeDetailsWithEditContext> = response.data.taxonomyTypes.map { it.value }
assertEquals(responseList, taxonomies)
}

@Test
fun `fetch taxonomies with error response do nothing`() = runTest {
// Use a concrete error type that we can create - UnknownError requires statusCode and response
val errorResponse = WpRequestResult.UnknownError<Any>(
statusCode = 500u,
response = "Internal Server Error"
)

whenever(wpApiClient.request<Any>(any())).thenReturn(errorResponse)

viewModel.fetchTaxonomies(testSite)

verify(appLogWrapper).e(any(), any())
assertTrue(taxonomies.isEmpty())
}

private fun createTestTaxonomyTypesResponseWithEditContext(): TaxonomyTypesResponseWithEditContext {
val visibility = TaxonomyTypeVisibility(
public = true,
publiclyQueryable = true,
showUi = true,
showAdminColumn = true,
showInNavMenus = true,
showInQuickEdit = true
)

val categoryDetails = TaxonomyTypeDetailsWithEditContext(
name = "Categories",
slug = "category",
description = "Test categories",
visibility = visibility,
restBase = "categories",
restNamespace = "wp/v2",
types = listOf("post"),
hierarchical = true,
showCloud = true,
capabilities = mock(),
labels = mock()
)

val tagDetails = TaxonomyTypeDetailsWithEditContext(
name = "Tags",
slug = "post_tag",
description = "Test tags",
visibility = visibility,
restBase = "tags",
restNamespace = "wp/v2",
types = listOf("post"),
hierarchical = false,
showCloud = true,
capabilities = mock(),
labels = mock()
)

return TaxonomyTypesResponseWithEditContext(
mapOf(
TaxonomyType.Category to categoryDetails,
TaxonomyType.PostTag to tagDetails
)
)
}
}