diff --git a/src/components/UserPanel.vue b/src/components/UserPanel.vue
index a0ef73a1f..5f3266eb2 100644
--- a/src/components/UserPanel.vue
+++ b/src/components/UserPanel.vue
@@ -1,6 +1,9 @@
diff --git a/src/components/common/VnSelectWorker.vue b/src/components/common/VnSelectWorker.vue
index 9a8151a3d..2762d6c02 100644
--- a/src/components/common/VnSelectWorker.vue
+++ b/src/components/common/VnSelectWorker.vue
@@ -55,7 +55,7 @@ const url = computed(() => {
sort-by="nickname ASC"
>
-
+
@@ -72,7 +72,8 @@ const url = computed(() => {
{{ scope.opt.nickname }}
- #{{ scope.opt.id }}, {{ scope.opt.nickname }}, {{ scope.opt.code }}
+ #{{ scope.opt.id }}, {{ scope.opt.nickname }},
+ {{ scope.opt.code }}
diff --git a/src/components/common/__tests__/VnInput.spec.js b/src/components/common/__tests__/VnInput.spec.js
new file mode 100644
index 000000000..13f9ed804
--- /dev/null
+++ b/src/components/common/__tests__/VnInput.spec.js
@@ -0,0 +1,91 @@
+import { createWrapper } from 'app/test/vitest/helper';
+import { vi, describe, expect, it } from 'vitest';
+import VnInput from 'src/components/common/VnInput.vue';
+
+
+describe('VnInput', () => {
+ let vm;
+ let wrapper;
+ let input;
+
+ function generateWrapper(value, isOutlined, emptyToNull, insertable) {
+ wrapper = createWrapper(VnInput, {
+ props: {
+ modelValue: value,
+ isOutlined, emptyToNull, insertable,
+ maxlength: 101
+ },
+ attrs: {
+ label: 'test',
+ required: true,
+ maxlength: 101,
+ maxLength: 10,
+ 'max-length':20
+ },
+ });
+ wrapper = wrapper.wrapper;
+ vm = wrapper.vm;
+ input = wrapper.find('[data-cy="test_input"]');
+ };
+
+ describe('value', () => {
+ it('should emit update:modelValue when value changes', async () => {
+ generateWrapper('12345', false, false, true)
+ await input.setValue('123');
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual(['123']);
+ });
+
+ it('should emit update:modelValue with null when input is empty', async () => {
+ generateWrapper('12345', false, true, true);
+ await input.setValue('');
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([null]);
+ });
+ });
+
+ describe('styleAttrs', () => {
+ it('should return empty styleAttrs when isOutlined is false', async () => {
+ generateWrapper('123', false, false, false);
+ expect(vm.styleAttrs).toEqual({});
+ });
+
+ it('should set styleAttrs when isOutlined is true', async () => {
+ generateWrapper('123', true, false, false);
+ expect(vm.styleAttrs.outlined).toBe(true);
+ });
+ });
+
+ describe('handleKeydown', () => {
+ it('should do nothing when "Backspace" key is pressed', async () => {
+ generateWrapper('12345', false, false, true);
+ await input.trigger('keydown', { key: 'Backspace' });
+ expect(wrapper.emitted('update:modelValue')).toBeUndefined();
+ const spyhandler = vi.spyOn(vm, 'handleInsertMode');
+ expect(spyhandler).not.toHaveBeenCalled();
+
+ });
+
+ /*
+ TODO: #8399 REDMINE
+ */
+ it.skip('handleKeydown respects insertable behavior', async () => {
+ const expectedValue = '12345';
+ generateWrapper('1234', false, false, true);
+ vm.focus()
+ await input.trigger('keydown', { key: '5' });
+ await vm.$nextTick();
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([expectedValue ]);
+ expect(vm.value).toBe( expectedValue);
+ });
+ });
+
+ describe('focus', () => {
+ it('should call focus method when input is focused', async () => {
+ generateWrapper('123', false, false, true);
+ const focusSpy = vi.spyOn(input.element, 'focus');
+ vm.focus();
+ expect(focusSpy).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/components/ui/__tests__/VnImg.spec.js b/src/components/ui/__tests__/VnImg.spec.js
new file mode 100644
index 000000000..39dd10775
--- /dev/null
+++ b/src/components/ui/__tests__/VnImg.spec.js
@@ -0,0 +1,89 @@
+import { vi, describe, expect, it, beforeEach, afterEach } from 'vitest';
+import { createWrapper } from 'app/test/vitest/helper';
+import VnImg from 'src/components/ui/VnImg.vue';
+
+let wrapper;
+let vm;
+const isEmployeeMock = vi.fn();
+
+function generateWrapper(storage = 'images') {
+ wrapper = createWrapper(VnImg, {
+ props: {
+ id: 123,
+ zoomResolution: '400x400',
+ storage,
+ }
+ });
+ wrapper = wrapper.wrapper;
+ vm = wrapper.vm;
+ vm.timeStamp = 'timestamp';
+};
+
+vi.mock('src/composables/useSession', () => ({
+ useSession: () => ({
+ getTokenMultimedia: () => 'token',
+ }),
+}));
+
+vi.mock('src/composables/useRole', () => ({
+ useRole: () => ({
+ isEmployee: isEmployeeMock,
+ }),
+}));
+
+
+describe('VnImg', () => {
+ beforeEach(() => {
+ isEmployeeMock.mockReset();
+ });
+
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe('getUrl', () => {
+ it('should return /api/{storage}/{id}/downloadFile?access_token={token} when storage is dms', async () => {
+ isEmployeeMock.mockReturnValue(false);
+ generateWrapper('dms');
+ await vm.$nextTick();
+ const url = vm.getUrl();
+ expect(url).toBe('/api/dms/123/downloadFile?access_token=token');
+ });
+
+ it('should return /no-user.png when role is not employee and storage is not dms', async () => {
+ isEmployeeMock.mockReturnValue(false);
+ generateWrapper();
+ await vm.$nextTick();
+ const url = vm.getUrl();
+ expect(url).toBe('/no-user.png');
+ });
+
+ it('should return /api/{storage}/{collection}/{curResolution}/{id}/download?access_token={token}&{timeStamp} when zoom is false and role is employee and storage is not dms', async () => {
+ isEmployeeMock.mockReturnValue(true);
+ generateWrapper();
+ await vm.$nextTick();
+ const url = vm.getUrl();
+ expect(url).toBe('/api/images/catalog/200x200/123/download?access_token=token×tamp');
+ });
+
+ it('should return /api/{storage}/{collection}/{curResolution}/{id}/download?access_token={token}&{timeStamp} when zoom is true and role is employee and storage is not dms', async () => {
+ isEmployeeMock.mockReturnValue(true);
+ generateWrapper();
+ await vm.$nextTick();
+ const url = vm.getUrl(true);
+ expect(url).toBe('/api/images/catalog/400x400/123/download?access_token=token×tamp');
+ });
+ });
+
+ describe('reload', () => {
+ it('should update the timestamp', async () => {
+ generateWrapper();
+ const initialTimestamp = wrapper.vm.timeStamp;
+
+ wrapper.vm.reload();
+ const newTimestamp = wrapper.vm.timeStamp;
+
+ expect(initialTimestamp).not.toEqual(newTimestamp);
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/layouts/OutLayout.vue b/src/layouts/OutLayout.vue
index 0eb1329a4..4ccc6bf9e 100644
--- a/src/layouts/OutLayout.vue
+++ b/src/layouts/OutLayout.vue
@@ -2,6 +2,8 @@
import { Dark, Quasar } from 'quasar';
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
+import { localeEquivalence } from 'src/i18n/index';
+import quasarLang from 'src/utils/quasarLang';
const { t, locale } = useI18n();
@@ -12,18 +14,9 @@ const userLocale = computed({
set(value) {
locale.value = value;
- if (value === 'en') value = 'en-GB';
+ value = localeEquivalence[value] ?? value;
- // FIXME: Dynamic imports from absolute paths are not compatible with vite:
- // https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations
- try {
- const langList = import.meta.glob('../../node_modules/quasar/lang/*.mjs');
- langList[`../../node_modules/quasar/lang/${value}.mjs`]().then((lang) => {
- Quasar.lang.set(lang.default);
- });
- } catch (error) {
- //
- }
+ quasarLang(value);
},
});
diff --git a/src/utils/quasarLang.js b/src/utils/quasarLang.js
new file mode 100644
index 000000000..ebd590c05
--- /dev/null
+++ b/src/utils/quasarLang.js
@@ -0,0 +1,12 @@
+const langList = import.meta.glob('../../node_modules/quasar/lang/*.js');
+import { Quasar } from 'quasar';
+
+export default function (value) {
+ try {
+ langList[`../../node_modules/quasar/lang/${value}.js`]().then((lang) => {
+ Quasar.lang.set(lang.default);
+ });
+ } catch (error) {
+ //
+ }
+}