feat: Add a unit test target (jest) to the UI extension and run it in CI (#1192)

- Introduce test targets.
- Adding additional tests.

Related to: https://github.com/harvester/harvester/issues/11892

Signed-off-by: Volker Theile <vtheile@suse.com>
This commit is contained in:
Volker Theile
2026-10-13 18:00:38 +02:00
committed by GitHub
parent 02862bbeea
commit 71141b257a
18 changed files with 924 additions and 108 deletions
@@ -2,9 +2,23 @@ import { mount } from '@vue/test-utils';
import { _EDIT } from '@shell/config/query-params';
import HarvesterEditContainer from '../container.vue';
const mountOptions = () => ({
props: { mode: _EDIT, value: {} },
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'i18n/t': jest.fn(),
}
}
}
}
});
describe('component: HarvesterEditContainer', () => {
it('should display all the inputs', () => {
const wrapper = mount(HarvesterEditContainer, { propsData: { mode: _EDIT, value: {} } });
const wrapper = mount(HarvesterEditContainer, mountOptions());
const inputWraps = wrapper.findAll('[data-testid^=input-hec-]');
@@ -15,7 +29,7 @@ describe('component: HarvesterEditContainer', () => {
'name',
'container',
])('should emit an update on %p input', (field) => {
const wrapper = mount(HarvesterEditContainer, { propsData: { mode: _EDIT, value: {} } });
const wrapper = mount(HarvesterEditContainer, mountOptions());
const input = wrapper.find(`[data-testid="input-hec-${ field }"]`).find('input');
const newValue = 123;
@@ -28,7 +42,7 @@ describe('component: HarvesterEditContainer', () => {
'type',
'bus',
])('should emit an update on %p selection change', async(field) => {
const wrapper = mount(HarvesterEditContainer, { propsData: { mode: _EDIT, value: {} } });
const wrapper = mount(HarvesterEditContainer, mountOptions());
const select = wrapper.find(`[data-testid="input-hec-${ field }"]`);
select.find('button').trigger('click');
@@ -5,14 +5,17 @@ import HarvesterEditExisting from '../existing.vue';
describe('component: HarvesterEditExisting', () => {
it('should display all the inputs', () => {
const wrapper = mount(HarvesterEditExisting, {
propsData: {
props: {
mode: _EDIT, value: {}, rows: []
},
mocks: {
$store: {
getters: {
'harvester/all': () => [],
'i18n/t': jest.fn()
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': () => [],
'i18n/t': jest.fn()
}
}
}
}
@@ -27,14 +30,17 @@ describe('component: HarvesterEditExisting', () => {
'name',
])('should emit an update on %p input', (field) => {
const wrapper = mount(HarvesterEditExisting, {
propsData: {
props: {
mode: _EDIT, value: {}, rows: []
},
mocks: {
$store: {
getters: {
'harvester/all': () => [],
'i18n/t': jest.fn()
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': () => [],
'i18n/t': jest.fn()
}
}
}
}
@@ -53,14 +59,17 @@ describe('component: HarvesterEditExisting', () => {
'size',
])('should emit an update on %p input and blur', (field) => {
const wrapper = mount(HarvesterEditExisting, {
propsData: {
props: {
mode: _EDIT, value: {}, rows: []
},
mocks: {
$store: {
getters: {
'harvester/all': () => [],
'i18n/t': jest.fn()
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': () => [],
'i18n/t': jest.fn()
}
}
}
}
@@ -80,14 +89,17 @@ describe('component: HarvesterEditExisting', () => {
// 'bus', // disabled
])('should emit an update on %p selection change', async(field) => {
const wrapper = mount(HarvesterEditExisting, {
propsData: {
props: {
mode: _EDIT, value: {}, rows: []
},
mocks: {
$store: {
getters: {
'harvester/all': () => [],
'i18n/t': jest.fn()
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': () => [],
'i18n/t': jest.fn()
}
}
}
}
@@ -2,23 +2,29 @@ import { mount } from '@vue/test-utils';
import { _CREATE } from '@shell/config/query-params';
import HarvesterEditVMImage from '../vmImage.vue';
// The form emits an update when it is created, so count only the updates that an interaction adds
const updates = (wrapper) => wrapper.emitted('update')?.length ?? 0;
describe('component: HarvesterEditVMImage', () => {
it('should display all the inputs', () => {
const wrapper = mount(HarvesterEditVMImage, {
propsData: {
props: {
mode: _CREATE,
isVirtualType: false,
newCreateId: '123',
validateRequired: true,
idx: 1
},
mocks: {
$store: {
getters: {
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
}
},
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
}
},
}
}
});
@@ -31,20 +37,23 @@ describe('component: HarvesterEditVMImage', () => {
'name',
])('should emit an update on %p input', (field) => {
const wrapper = mount(HarvesterEditVMImage, {
propsData: {
props: {
mode: _CREATE,
isVirtualType: false,
newCreateId: '123',
validateRequired: true,
idx: 1
},
mocks: {
$store: {
getters: {
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
}
},
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
}
},
}
}
});
const input = wrapper.find(`[data-testid="input-hevi-${ field }"]`).find('input');
@@ -57,31 +66,38 @@ describe('component: HarvesterEditVMImage', () => {
it.each([
'size',
])('should emit an update on %p input blur', (field) => {
])('should emit an update on %p input blur', async(field) => {
const wrapper = mount(HarvesterEditVMImage, {
propsData: {
props: {
mode: _CREATE,
isVirtualType: false,
newCreateId: '123',
validateRequired: true,
idx: 1
},
mocks: {
$store: {
getters: {
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
}
},
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
}
},
}
}
});
const input = wrapper.find(`[data-testid="input-hevi-${ field }"]`).find('input');
const newValue = 123;
input.setValue(newValue);
input.trigger('blur');
// setting the value already emits an update, so the blur has to add one on top of that
await input.setValue(newValue);
expect(wrapper.emitted('update')).toHaveLength(1);
const afterSetValue = updates(wrapper);
await input.trigger('blur');
expect(updates(wrapper)).toBeGreaterThan(afterSetValue);
});
it.each([
@@ -90,20 +106,23 @@ describe('component: HarvesterEditVMImage', () => {
'bus',
])('should emit an update on %p selection change', async(field) => {
const wrapper = mount(HarvesterEditVMImage, {
propsData: {
props: {
mode: _CREATE,
isVirtualType: false,
newCreateId: '123',
validateRequired: true,
idx: 1
},
mocks: {
$store: {
getters: {
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
}
},
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
}
},
}
}
});
const select = wrapper.find(`[data-testid="input-hevi-${ field }"]`);
@@ -2,18 +2,24 @@ import { mount } from '@vue/test-utils';
import { _EDIT } from '@shell/config/query-params';
import HarvesterEditVolume from '../volume.vue';
// The form emits an update when it is created, so count only the updates that an interaction adds
const updates = (wrapper) => wrapper.emitted('update')?.length ?? 0;
describe('component: HarvesterEditVolume', () => {
it('should display all the inputs', () => {
const wrapper = mount(HarvesterEditVolume, {
propsData: { validateRequired: true },
mocks: {
$store: {
getters: {
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
'i18n/exists': jest.fn(),
}
},
props: { validateRequired: true },
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
'i18n/exists': jest.fn(),
}
},
}
}
});
@@ -22,29 +28,56 @@ describe('component: HarvesterEditVolume', () => {
expect(inputWraps).toHaveLength(4);
});
it.each([
'name',
'size',
])('should emit an update on %p input blur', (field) => {
it('should emit an update on name input', async() => {
const wrapper = mount(HarvesterEditVolume, {
propsData: { mode: _EDIT, validateRequired: true },
mocks: {
$store: {
getters: {
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
'i18n/exists': jest.fn(),
}
},
props: { mode: _EDIT, validateRequired: true },
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
'i18n/exists': jest.fn(),
}
},
}
}
});
const input = wrapper.find(`[data-testid="input-hev-${ field }"]`).find('input');
const newValue = 123;
const before = updates(wrapper);
const input = wrapper.find('[data-testid="input-hev-name"]').find('input');
input.setValue(newValue);
input.trigger('blur');
await input.setValue(123);
expect(wrapper.emitted('update')).toHaveLength(1);
expect(updates(wrapper)).toBeGreaterThan(before);
});
it('should emit an update on size input blur', async() => {
const wrapper = mount(HarvesterEditVolume, {
props: { mode: _EDIT, validateRequired: true },
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
'i18n/exists': jest.fn(),
}
},
}
}
});
const input = wrapper.find('[data-testid="input-hev-size"]').find('input');
// setting the value already emits an update, so the blur has to add one on top of that
await input.setValue(123);
const afterSetValue = updates(wrapper);
await input.trigger('blur');
expect(updates(wrapper)).toBeGreaterThan(afterSetValue);
});
it.each([
@@ -52,23 +85,27 @@ describe('component: HarvesterEditVolume', () => {
'bus'
])('should emit an update on %p selection change', async(field) => {
const wrapper = mount(HarvesterEditVolume, {
propsData: { mode: _EDIT, validateRequired: true },
mocks: {
$store: {
getters: {
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
'i18n/exists': jest.fn(),
}
},
props: { mode: _EDIT, validateRequired: true },
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => true,
'harvester/all': jest.fn(),
'i18n/t': jest.fn(),
'i18n/exists': jest.fn(),
}
},
}
}
});
const before = updates(wrapper);
const select = wrapper.find(`[data-testid="input-hev-${ field }"]`);
select.find('button').trigger('click');
await wrapper.trigger('keydown.down');
await wrapper.trigger('keydown.enter');
expect(wrapper.emitted('update')).toHaveLength(1);
expect(updates(wrapper)).toBeGreaterThan(before);
});
});