diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue
index 7fdb54bc4..940b72ff0 100644
--- a/src/components/CrudModel.vue
+++ b/src/components/CrudModel.vue
@@ -127,7 +127,7 @@ function resetData(data) {
originalData.value = JSON.parse(JSON.stringify(data));
formData.value = JSON.parse(JSON.stringify(data));
- if (watchChanges.value) watchChanges.value(); //destoy watcher
+ if (watchChanges.value) watchChanges.value(); //destroy watcher
watchChanges.value = watch(formData, () => (hasChanges.value = true), { deep: true });
}
@@ -270,10 +270,8 @@ function getChanges() {
function isEmpty(obj) {
if (obj == null) return true;
- if (obj === undefined) return true;
- if (Object.keys(obj).length === 0) return true;
-
- if (obj.length > 0) return false;
+ if (Array.isArray(obj)) return !obj.length;
+ return !Object.keys(obj).length;
}
async function reload(params) {
diff --git a/src/components/__tests__/CrudModel.spec.js b/src/components/__tests__/CrudModel.spec.js
index 6ce93e59c..e0afd30ad 100644
--- a/src/components/__tests__/CrudModel.spec.js
+++ b/src/components/__tests__/CrudModel.spec.js
@@ -1,11 +1,13 @@
-import { createWrapper } from 'app/test/vitest/helper';
+import { createWrapper, axios } from 'app/test/vitest/helper';
import CrudModel from 'components/CrudModel.vue';
import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest';
describe('CrudModel', () => {
+ let wrapper;
let vm;
+ let data;
beforeAll(() => {
- vm = createWrapper(CrudModel, {
+ wrapper = createWrapper(CrudModel, {
global: {
stubs: [
'vnPaginate',
@@ -25,12 +27,16 @@ describe('CrudModel', () => {
dataKey: 'crudModelKey',
model: 'crudModel',
url: 'crudModelUrl',
+ saveFn: '',
},
- }).vm;
+ });
+ wrapper=wrapper.wrapper;
+ vm=wrapper.vm;
});
beforeEach(() => {
vm.fetch([]);
+ vm.watchChanges = null;
});
afterEach(() => {
@@ -117,4 +123,126 @@ describe('CrudModel', () => {
});
});
});
+
+ describe('isEmpty()', () => {
+ let dummyObj;
+ let dummyArray;
+ let result;
+ it('should return true if object si null', async () => {
+ dummyObj = null;
+ result = vm.isEmpty(dummyObj);
+
+ expect(result).toBe(true);
+ });
+
+ it('should return true if object si undefined', async () => {
+ dummyObj = undefined;
+ result = vm.isEmpty(dummyObj);
+
+ expect(result).toBe(true);
+ });
+
+ it('should return true if object is empty', async () => {
+ dummyObj ={};
+ result = vm.isEmpty(dummyObj);
+
+ expect(result).toBe(true);
+ });
+
+ it('should return false if object is not empty', async () => {
+ dummyObj = {a:1, b:2, c:3};
+ result = vm.isEmpty(dummyObj);
+
+ expect(result).toBe(false);
+ });
+
+ it('should return true if array is empty', async () => {
+ dummyArray = [];
+ result = vm.isEmpty(dummyArray);
+
+ expect(result).toBe(true);
+ });
+
+ it('should return false if array is not empty', async () => {
+ dummyArray = [1,2,3];
+ result = vm.isEmpty(dummyArray);
+
+ expect(result).toBe(false);
+ })
+ });
+
+ describe('resetData()', () => {
+ it('should add $index to elements in data[] and sets originalData and formData with data', async () => {
+ data = [{
+ name: 'Tony',
+ lastName: 'Stark',
+ age: 42,
+ }];
+
+ vm.resetData(data);
+
+ expect(vm.originalData).toEqual(data);
+ expect(vm.originalData[0].$index).toEqual(0);
+ expect(vm.formData).toEqual(data);
+ expect(vm.formData[0].$index).toEqual(0);
+ expect(vm.watchChanges).not.toBeNull();
+ });
+
+ it('should dont do nothing if data is null', async () => {
+ vm.resetData(null);
+
+ expect(vm.watchChanges).toBeNull();
+ });
+
+ it('should set originalData and formatData with data and generate watchChanges', async () => {
+ data = {
+ name: 'Tony',
+ lastName: 'Stark',
+ age: 42,
+ };
+
+ vm.resetData(data);
+
+ expect(vm.originalData).toEqual(data);
+ expect(vm.formData).toEqual(data);
+ expect(vm.watchChanges).not.toBeNull();
+ });
+ });
+
+ describe('saveChanges()', () => {
+ data = [{
+ name: 'Tony',
+ lastName: 'Stark',
+ age: 42,
+ }];
+
+ it('should call saveFn if exists', async () => {
+ await wrapper.setProps({ saveFn: vi.fn() });
+
+ vm.saveChanges(data);
+
+ expect(vm.saveFn).toHaveBeenCalledOnce();
+ expect(vm.isLoading).toBe(false);
+ expect(vm.hasChanges).toBe(false);
+
+ await wrapper.setProps({ saveFn: '' });
+ });
+
+ it("should use default url if there's not saveFn", async () => {
+ const postMock =vi.spyOn(axios, 'post');
+
+ vm.formData = [{
+ name: 'Bruce',
+ lastName: 'Wayne',
+ age: 45,
+ }]
+
+ await vm.saveChanges(data);
+
+ expect(postMock).toHaveBeenCalledWith(vm.url + '/crud', data);
+ expect(vm.isLoading).toBe(false);
+ expect(vm.hasChanges).toBe(false);
+ expect(vm.originalData).toEqual(JSON.parse(JSON.stringify(vm.formData)));
+ });
+ });
});
diff --git a/src/components/__tests__/FormModel.spec.js b/src/components/__tests__/FormModel.spec.js
new file mode 100644
index 000000000..e35684bc3
--- /dev/null
+++ b/src/components/__tests__/FormModel.spec.js
@@ -0,0 +1,149 @@
+import { describe, expect, it, beforeAll, vi, afterAll } from 'vitest';
+import { createWrapper, axios } from 'app/test/vitest/helper';
+import FormModel from 'src/components/FormModel.vue';
+
+describe('FormModel', () => {
+ const model = 'mockModel';
+ const url = 'mockUrl';
+ const formInitialData = { mockKey: 'mockVal' };
+
+ describe('modelValue', () => {
+ it('should use the provided model', () => {
+ const { vm } = mount({ propsData: { model } });
+ expect(vm.modelValue).toBe(model);
+ });
+
+ it('should use the route meta title when model is not provided', () => {
+ const { vm } = mount({});
+ expect(vm.modelValue).toBe('formModel_mockTitle');
+ });
+ });
+
+ describe('onMounted()', () => {
+ let mockGet;
+
+ beforeAll(() => {
+ mockGet = vi.spyOn(axios, 'get').mockResolvedValue({ data: {} });
+ });
+
+ afterAll(() => {
+ mockGet.mockRestore();
+ });
+
+ it('should not fetch when has formInitialData', () => {
+ mount({ propsData: { url, model, autoLoad: true, formInitialData } });
+ expect(mockGet).not.toHaveBeenCalled();
+ });
+
+ it('should fetch when there is url and auto-load', () => {
+ mount({ propsData: { url, model, autoLoad: true } });
+ expect(mockGet).toHaveBeenCalled();
+ });
+
+ it('should not observe changes', () => {
+ const { vm } = mount({
+ propsData: { url, model, observeFormChanges: false, formInitialData },
+ });
+
+ expect(vm.hasChanges).toBe(true);
+ vm.reset();
+ expect(vm.hasChanges).toBe(true);
+ });
+
+ it('should observe changes', async () => {
+ const { vm } = mount({
+ propsData: { url, model, formInitialData },
+ });
+ vm.state.set(model, formInitialData);
+ expect(vm.hasChanges).toBe(false);
+
+ vm.formData.mockKey = 'newVal';
+ await vm.$nextTick();
+ expect(vm.hasChanges).toBe(true);
+ vm.formData.mockKey = 'mockVal';
+ });
+ });
+
+ describe('trimData()', () => {
+ let vm;
+ beforeAll(() => {
+ vm = mount({}).vm;
+ });
+
+ it('should trim whitespace from string values', () => {
+ const data = { key1: ' value1 ', key2: ' value2 ' };
+ const trimmedData = vm.trimData(data);
+ expect(trimmedData).toEqual({ key1: 'value1', key2: 'value2' });
+ });
+
+ it('should not modify non-string values', () => {
+ const data = { key1: 123, key2: true, key3: null, key4: undefined };
+ const trimmedData = vm.trimData(data);
+ expect(trimmedData).toEqual(data);
+ });
+ });
+
+ describe('save()', async () => {
+ it('should not call if there are not changes', async () => {
+ const { vm } = mount({ propsData: { url, model } });
+
+ await vm.save();
+ expect(vm.hasChanges).toBe(false);
+ });
+
+ it('should call axios.patch with the right data', async () => {
+ const spy = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} });
+ const { vm } = mount({ propsData: { url, model, formInitialData } });
+ vm.formData.mockKey = 'newVal';
+ await vm.$nextTick();
+ await vm.save();
+ expect(spy).toHaveBeenCalled();
+ vm.formData.mockKey = 'mockVal';
+ });
+
+ it('should call axios.post with the right data', async () => {
+ const spy = vi.spyOn(axios, 'post').mockResolvedValue({ data: {} });
+ const { vm } = mount({
+ propsData: { url, model, formInitialData, urlCreate: 'mockUrlCreate' },
+ });
+ vm.formData.mockKey = 'newVal';
+ await vm.$nextTick();
+ await vm.save();
+ expect(spy).toHaveBeenCalled();
+ vm.formData.mockKey = 'mockVal';
+ });
+
+ it('should use the saveFn', async () => {
+ const { vm } = mount({
+ propsData: { url, model, formInitialData, saveFn: () => {} },
+ });
+ const spyPatch = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} });
+ const spySaveFn = vi.spyOn(vm.$props, 'saveFn');
+
+ vm.formData.mockKey = 'newVal';
+ await vm.$nextTick();
+ await vm.save();
+ expect(spyPatch).not.toHaveBeenCalled();
+ expect(spySaveFn).toHaveBeenCalled();
+ vm.formData.mockKey = 'mockVal';
+ });
+
+ it('should reload the data after save', async () => {
+ const { vm } = mount({
+ propsData: { url, model, formInitialData, reload: true },
+ });
+ vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} });
+
+ vm.formData.mockKey = 'newVal';
+ await vm.$nextTick();
+ await vm.save();
+ vm.formData.mockKey = 'mockVal';
+ });
+ });
+});
+
+function mount({ propsData = {} }) {
+ return createWrapper(FormModel, {
+ propsData,
+ });
+}
diff --git a/src/pages/Account/Card/AccountDescriptor.vue b/src/pages/Account/Card/AccountDescriptor.vue
index 4e10e1366..8af817fcc 100644
--- a/src/pages/Account/Card/AccountDescriptor.vue
+++ b/src/pages/Account/Card/AccountDescriptor.vue
@@ -53,7 +53,6 @@ const hasAccount = ref(false);
-
-
+
{{ t('account.card.deactivated') }}
@@ -91,10 +90,10 @@ const hasAccount = ref(false);
{{ t('account.card.enabled') }}
diff --git a/src/pages/Claim/Card/ClaimDevelopment.vue b/src/pages/Claim/Card/ClaimDevelopment.vue
index e288d8614..d17c6b4e6 100644
--- a/src/pages/Claim/Card/ClaimDevelopment.vue
+++ b/src/pages/Claim/Card/ClaimDevelopment.vue
@@ -164,19 +164,7 @@ const columns = computed(() => [
:autofocus="col.tabIndex == 1"
input-debounce="0"
hide-selected
- >
-
-
-
- {{ scope.opt?.name }}
-
- {{ scope.opt?.nickname }}
- {{ scope.opt?.code }}
-
-
-
-
-
+ />
-
+
{{ value }}
-import { ref, computed } from 'vue';
+import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
@@ -14,7 +14,12 @@ import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue';
import { useState } from 'src/composables/useState';
const state = useState();
-const customer = computed(() => state.get('customer'));
+const customer = ref();
+
+onMounted(async () => {
+ customer.value = state.get('customer');
+ if (customer.value) customer.value.webAccess = data.value?.account?.isActive;
+});
const $props = defineProps({
id: {
@@ -38,7 +43,6 @@ const entityId = computed(() => {
const data = ref(useCardDescription());
const setData = (entity) => {
data.value = useCardDescription(entity?.name, entity?.id);
- if (customer.value) customer.value.webAccess = data.value?.account?.isActive;
};
const debtWarning = computed(() => {
return customer.value?.debt > customer.value?.credit ? 'negative' : 'primary';
diff --git a/src/pages/Department/Card/DepartmentBasicData.vue b/src/pages/Department/Card/DepartmentBasicData.vue
index 22ce06821..b13aed2d3 100644
--- a/src/pages/Department/Card/DepartmentBasicData.vue
+++ b/src/pages/Department/Card/DepartmentBasicData.vue
@@ -52,7 +52,7 @@ const { t } = useI18n();
[
},
format: (row) => row.code,
},
- {
- name: 'companyFk',
- label: t('globals.company'),
- columnFilter: {
- component: 'select',
- attrs: {
- url: 'Companies',
- fields: ['id', 'code'],
- optionLabel: 'code',
- },
- },
- format: (row) => row.code,
- },
{
align: 'right',
name: 'tableActions',
diff --git a/src/pages/Supplier/Card/SupplierBasicData.vue b/src/pages/Supplier/Card/SupplierBasicData.vue
index 842109656..22a6deaab 100644
--- a/src/pages/Supplier/Card/SupplierBasicData.vue
+++ b/src/pages/Supplier/Card/SupplierBasicData.vue
@@ -6,9 +6,11 @@ import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
+import { useArrayData } from 'src/composables/useArrayData';
const route = useRoute();
const { t } = useI18n();
+const arrayData = useArrayData();
const companySizes = [
{ id: 'small', name: t('globals.small'), size: '1-5' },
{ id: 'medium', name: t('globals.medium'), size: '6-50' },
@@ -22,6 +24,7 @@ const companySizes = [
model="supplier"
auto-load
:clear-store-on-unmount="false"
+ @on-data-saved="arrayData.fetch({})"
>
diff --git a/src/pages/Supplier/Card/SupplierDescriptor.vue b/src/pages/Supplier/Card/SupplierDescriptor.vue
index 28cfe49ce..a1a2a0991 100644
--- a/src/pages/Supplier/Card/SupplierDescriptor.vue
+++ b/src/pages/Supplier/Card/SupplierDescriptor.vue
@@ -9,7 +9,7 @@ import VnLv from 'src/components/ui/VnLv.vue';
import { toDateString } from 'src/filters';
import useCardDescription from 'src/composables/useCardDescription';
import { getUrl } from 'src/composables/getUrl';
-import { useState } from 'src/composables/useState';
+import { useArrayData } from 'src/composables/useArrayData';
const $props = defineProps({
id: {
@@ -26,7 +26,7 @@ const $props = defineProps({
const route = useRoute();
const { t } = useI18n();
const url = ref();
-const state = useState();
+const arrayData = useArrayData();
const filter = {
fields: [
@@ -77,7 +77,7 @@ const setData = (entity) => {
data.value = useCardDescription(entity.ref, entity.id);
};
-const supplier = computed(() => state.get('supplier'));
+const supplier = computed(() => arrayData.store.data);
const getEntryQueryParams = (supplier) => {
if (!supplier) return null;
diff --git a/test/vitest/helper.js b/test/vitest/helper.js
index ce057c7c3..1e693ab63 100644
--- a/test/vitest/helper.js
+++ b/test/vitest/helper.js
@@ -26,7 +26,7 @@ vi.mock('vue-router', () => ({
params: {
id: 1,
},
- meta: { moduleName: 'mockName' },
+ meta: { moduleName: 'mockModuleName' },
matched: [{ path: 'mockName/list' }],
},
},
@@ -35,7 +35,7 @@ vi.mock('vue-router', () => ({
matched: [],
query: {},
params: {},
- meta: { moduleName: 'mockName' },
+ meta: { moduleName: 'mockModuleName', title: 'mockTitle', name: 'mockName' },
path: 'mockSection/list',
}),
onBeforeRouteLeave: () => {},