Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
fix(settings): improve forced locale/language validation and testability
- Clarified the naming in the validate functions
- Removed unnecessary mocks, and added stubs in tests.
- Testing for existence of element with data-test attributes, and without relying on translated text.
- Removed overly complex component mocking

Signed-off-by: Annabel Church <[email protected]>
  • Loading branch information
arc64 committed Jun 24, 2025
commit 262c9f89441f9efbceb774614679fac1d9b10704
31 changes: 20 additions & 11 deletions apps/settings/lib/Settings/Personal/PersonalInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,20 @@ function (IAccountProperty $property) {
* Validates a forced language setting against available languages
*/
private function validateForcedLanguage(string $forcedLanguage, array $languages): ?array {
$allLanguages = array_merge($languages['commonLanguages'], $languages['otherLanguages']);
$forcedLang = array_filter($allLanguages, fn($lang) => $lang['code'] === $forcedLanguage);
$forcedLang = reset($forcedLang);

if ($forcedLang && isset($forcedLang['name'])) {
$allLanguages = array_merge(
$languages['commonLanguages'] ?? [],
$languages['otherLanguages'] ?? []
);
$matchingLanguages = array_filter(
$allLanguages,
fn($lang) => $lang['code'] === $forcedLanguage
);
$matchingLanguage = reset($matchingLanguages);

if ($matchingLanguage && isset($matchingLanguage['name'])) {
return [
'code' => $forcedLanguage,
'name' => $forcedLang['name']
'name' => $matchingLanguage['name']
];
}

Expand Down Expand Up @@ -288,13 +294,16 @@ private function getLanguageMap(IUser $user): array {
* Validates a forced locale setting against available locales
*/
private function validateForcedLocale(string $forcedLocale, array $localeCodes): ?array {
$forcedLocaleObj = array_filter($localeCodes, fn($locale) => $locale['code'] === $forcedLocale);
$forcedLocaleObj = reset($forcedLocaleObj);

if ($forcedLocaleObj && isset($forcedLocaleObj['name'])) {
$matchingLocales = array_filter(
$localeCodes,
fn($locale) => $locale['code'] === $forcedLocale
);
$matchingLocale = reset($matchingLocales);

if ($matchingLocale && isset($matchingLocale['name'])) {
return [
'code' => $forcedLocale,
'name' => $forcedLocaleObj['name']
'name' => $matchingLocale['name']
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { loadState } from '@nextcloud/initial-state'
import LanguageSection from './LanguageSection.vue'

/**
* Mock Nextcloud modules
*/
vi.mock('@nextcloud/initial-state', () => ({
loadState: vi.fn(() => ({
languageMap: {
activeLanguage: { code: 'en', name: 'English' },
commonLanguages: [],
otherLanguages: [],
},
})),
}))

describe('LanguageSection', () => {
let wrapper

const mountComponent = () => {
return mount(LanguageSection, {
stubs: {
Language: {
template: '<div data-test="language-select" />',
},
HeaderBar: {
template: '<div data-test="header-bar" />',
},
},
})
}

describe('when the language is user-configurable', () => {
beforeEach(async () => {
const userConfigurableData = {
languageMap: {
activeLanguage: { code: 'en', name: 'English' },
commonLanguages: [{ code: 'en', name: 'English' }],
otherLanguages: [{ code: 'de', name: 'German' }],
},
}
vi.mocked(loadState).mockReturnValueOnce(userConfigurableData)
wrapper = mountComponent()
await wrapper.vm.$nextTick()
})

it('shows the language select component', () => {
expect(wrapper.find('[data-test="language-select"]').exists()).toBe(true)
})

})

describe('when there is no language data', () => {
beforeEach(async () => {
const noLanguageData = { languageMap: {} }
vi.mocked(loadState).mockReturnValueOnce(noLanguageData)
wrapper = mountComponent()
await wrapper.vm.$nextTick()
})

it('shows no language component', () => {
expect(wrapper.find('[data-test="no-language-message"]').exists()).toBe(true)
})
})

describe('when the language is forced by the administrator', () => {
beforeEach(async () => {
const forcedLanguageData = {
languageMap: {
forcedLanguage: { code: 'uk', name: 'Ukrainian' },
},
}
vi.mocked(loadState).mockReturnValueOnce(forcedLanguageData)
wrapper = mountComponent()
await wrapper.vm.$nextTick()
})

it('shows forced language component', () => {
expect(wrapper.find('[data-test="forced-language-message"]').exists()).toBe(true)
})

})

afterEach(() => {
if (wrapper) {
wrapper.destroy()
wrapper = null
}
vi.resetAllMocks()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@
:readable="propertyReadable" />

<Language v-if="isEditable"
data-test="language-select"
:input-id="inputId"
:common-languages="commonLanguages"
:other-languages="otherLanguages"
:language.sync="language" />

<span v-else-if="forcedLanguage && forcedLanguage.name">
<span v-else-if="forcedLanguage && forcedLanguage.name"
data-test="forced-language-message">
{{ t('settings', 'Language is forced to {language} by the administrator', { language: forcedLanguage.name }) }}
</span>
<span v-else>
<span v-else
data-test="no-language-message">
{{ t('settings', 'No language set') }}
</span>
</section>
Expand Down Expand Up @@ -76,8 +79,7 @@ export default {

methods: {
t,
}

},
}
</script>

Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that there is a test but for this its probably also good to add a cypress e2e test.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't committed the cypress tests yet, as I'm struggling to get them running with my current setup (https://github.com/juliusknorr/nextcloud-docker-dev).

This file was deleted.

Loading