Skip to content

Commit cdd3214

Browse files
xingzhang-suserushk014
authored andcommitted
Github #357: Bug: Do not display minute and second on scan interval column in registry list
1 parent d5e2c4a commit cdd3214

File tree

8 files changed

+136
-114
lines changed

8 files changed

+136
-114
lines changed

babel.config.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ module.exports = {
77
{
88
root: ['.'],
99
alias: {
10-
'@': '.',
11-
'~': '.',
12-
'@benchmark-compliance': './pkg/benchmark-compliance',
13-
'@sbombastic': './pkg/sbombastic',
14-
'@network': './pkg/network',
15-
'@runtime-process-profile': './pkg/runtime-process-profile',
10+
'@': '.',
11+
'~': '.',
12+
'@benchmark-compliance': './pkg/benchmark-compliance',
13+
'@sbombastic': './pkg/sbombastic',
14+
'@network': './pkg/network',
15+
'@runtime-process-profile': './pkg/runtime-process-profile',
1616
},
1717
},
1818
],

pkg/sbomscanner/components/__tests__/InstallView.spec.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@ jest.mock('@sbomscanner/utils/chart', () => ({
1818
let modules: Vuex.Modules<any, any> = {
1919
'resource-fetch': {
2020
namespaced: true,
21-
getters: {
22-
refreshFlag: () => false,
23-
},
21+
getters: { refreshFlag: () => false },
2422
},
2523
catalog: {
2624
namespaced: true,
27-
getters: {
25+
getters: {
2826
charts: () => [],
2927
repos: () => []
3028
},
@@ -78,7 +76,7 @@ describe('InstallView.vue', () => {
7876

7977
dispatch = jest.fn(() => ({ save: jest.fn() }));
8078

81-
store = createStore({
79+
store = createStore({
8280
getters,
8381
modules
8482
});
@@ -440,7 +438,7 @@ describe('InstallView.vue', () => {
440438
currentStore: () => (id: string) => {
441439
return { id, name: 'mocked-store' };
442440
},
443-
currentCluster: () => ({ id: 'cluster-1' }),
441+
currentCluster: () => ({ id: 'cluster-1' }),
444442
'catalog/chart': () => jest.fn().mockReturnValue({ chartName: 'cnpg-controller' }),
445443
'management/byId': () => (id: string) => {
446444
return { id, name: 'mocked-management' };
@@ -461,6 +459,7 @@ describe('InstallView.vue', () => {
461459
},
462460
},
463461
});
462+
464463
await Object.defineProperty(wrapper.vm, 'controllerChart4Cnpg' , {
465464
get: () => ({
466465
repoType: 'cluster', repoName: 'repo1', chartName: 'chart1', versions: ['1.0.0']
@@ -489,8 +488,8 @@ describe('InstallView.vue', () => {
489488
},
490489
currentCluster: () => ({ id: 'cluster-1' }),
491490
'cluster/schemaFor': () => jest.fn().mockReturnValue(mockSchema),
492-
'catalog/chart': () => jest.fn().mockReturnValue({ chartName: 'cnpg-controller' }),
493-
'management/byId': () => (id: string) => {
491+
'catalog/chart': () => jest.fn().mockReturnValue({ chartName: 'cnpg-controller' }),
492+
'management/byId': () => (id: string) => {
494493
return { id, name: 'mocked-management' };
495494
},
496495
'i18n/t': () => jest.fn((key) => key),
@@ -509,8 +508,9 @@ describe('InstallView.vue', () => {
509508
},
510509
},
511510
});
511+
512512
require('@sbomscanner/utils/chart').getLatestVersion.mockReturnValue(null);
513-
513+
514514
await wrapper.vm.chartRoute();
515515
expect(handleGrowl).toHaveBeenCalled();
516516
});
@@ -613,11 +613,11 @@ describe('InstallView.vue - schema computed tests', () => {
613613
currentStore: () => (id: string) => {
614614
return { id, name: 'mocked-store' };
615615
},
616-
'catalog/chart': () => jest.fn().mockReturnValue({ chartName: 'cnpg-controller' }),
616+
'catalog/chart': () => jest.fn().mockReturnValue({ chartName: 'cnpg-controller' }),
617617
'cluster/schemaFor': () => jest.fn().mockReturnValue(mockSchema),
618618
'cluster/canList': () => jest.fn().mockReturnValue(true),
619-
620-
'management/byId': () => (id: string) => {
619+
620+
'management/byId': () => (id: string) => {
621621
return { id, name: 'mocked-management' };
622622
},
623623
'i18n/t': () => jest.fn((key) => key),
@@ -648,7 +648,7 @@ describe('InstallView.vue - schema computed tests', () => {
648648
currentStore: () => (id: string) => {
649649
return { id, name: 'mocked-store' };
650650
},
651-
'catalog/chart': () => jest.fn().mockReturnValue({ chartName: 'cnpg-controller' }),
651+
'catalog/chart': () => jest.fn().mockReturnValue({ chartName: 'cnpg-controller' }),
652652
'cluster/schemaFor': () => jest.fn().mockReturnValue(mockSchema),
653653
'cluster/canList': () => jest.fn().mockReturnValue(true),
654654
'management/byId': () => (id: string) => {
@@ -807,6 +807,7 @@ describe('InstallView.vue - schema computed tests', () => {
807807

808808
mockStore.dispatch = jest.fn().mockResolvedValue(mockRepo);
809809
const wrapper = factory(mockStore);
810+
810811
Object.defineProperty(wrapper.vm.$, 'refs', {
811812
value: { wizard: { goToStep: jest.fn() } },
812813
configurable: true,
@@ -1076,9 +1077,7 @@ describe('chartRoute - InstallView router push', () => {
10761077
});
10771078

10781079
it('hasSbomscannerSchema is false - calls $router.push normally', async() => {
1079-
const routerMock = {
1080-
push: jest.fn()
1081-
};
1080+
const routerMock = { push: jest.fn() };
10821081
const wrapper = factory({ routerMock });
10831082

10841083
await Object.defineProperty(wrapper.vm, 'controllerChart4Sbomscanner' , {

pkg/sbomscanner/components/__tests__/RegistryDetails.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe('RegistryDetails.vue', () => {
5151
mocks: {
5252
$store: storeMock,
5353
$fetchState: { pending: false },
54-
$route: {
54+
$route: {
5555
params: {
5656
cluster: 'local', id: 'my-reg', ns: 'ns1'
5757
}

pkg/sbomscanner/edit/__tests__/sbomscanner.kubewarden.io.registry.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ const LabeledSelectStub = {
2626
props: ['value', 'options', 'optionKey', 'optionLabel', 'required', 'dataTestid'],
2727
};
2828

29-
const { REGISTRY_TYPE } = require('@sbomscanner/constants');
30-
3129
const stubs = {
3230
CruResource: { name: 'CruResource', template: '<div><slot /></div>' },
3331
NameNsDescription: true,

pkg/sbomscanner/formatters/ScanInterval.vue

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<span
44
v-if="value"
55
class="scan-interval-text"
6-
>{{ t('imageScanner.general.every') }}&nbsp;{{ value }}</span>
6+
>{{ t('imageScanner.general.every') }}&nbsp;{{ scanInterval }}</span>
77
<span
88
v-else
99
class="scan-interval-text scan-interval-none"
@@ -18,6 +18,33 @@ export default {
1818
required: true
1919
}
2020
},
21+
computed: {
22+
scanInterval() {
23+
const regex = /(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/;
24+
const matches = this.value.match(regex);
25+
26+
if (!matches) {
27+
return this.value;
28+
}
29+
30+
const [, hours, minutes, seconds] = matches;
31+
let result = '';
32+
33+
if (hours && hours !== '0') {
34+
result += `${hours}h`;
35+
}
36+
37+
if (minutes && minutes !== '0') {
38+
result += `${minutes}m`;
39+
}
40+
41+
if (seconds && seconds !== '0') {
42+
result += `${seconds}s`;
43+
}
44+
45+
return result || this.value;
46+
}
47+
}
2148
};
2249
</script>
2350

pkg/sbomscanner/formatters/__tests__/ScanHistorySinceCell.spec.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,15 @@ import ScanHistorySinceCell from '../ScanHistorySinceCell.vue';
33
// import Date from '@shell/components/formatter/Date';
44

55
jest.mock('@shell/components/formatter/Date', () => ({
6-
name: 'Date',
7-
props: {
8-
value: {},
9-
},
6+
name: 'Date',
7+
props: { value: {} },
108
}));
119

1210
describe('ScanHistorySinceCell.vue', () => {
1311
it('should render the Date component and pass the value prop to it', () => {
1412
const mockDateString = '2025-10-20T18:00:00Z';
1513

16-
const wrapper = shallowMount(ScanHistorySinceCell, {
17-
props: { value: mockDateString },
18-
});
14+
const wrapper = shallowMount(ScanHistorySinceCell, { props: { value: mockDateString } });
1915

2016
const dateComponent = wrapper.findComponent(Date);
2117

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,68 @@
1-
import { jest } from '@jest/globals';
21
import { shallowMount } from '@vue/test-utils';
32
import ScanInterval from '../ScanInterval.vue';
43

5-
describe('ScanInterval.vue', () => {
6-
const mockT = jest.fn((key) => key);
4+
describe('ScanIntervalCell.vue', () => {
5+
const tMock = (key: any) => key; // simple mock translation
76

8-
const mountComponent = (value) => {
7+
function factory(propsData: any) {
98
return shallowMount(ScanInterval, {
10-
props: { value },
11-
global: { mocks: { t: mockT } },
9+
propsData,
10+
mocks: { t: tMock }
1211
});
13-
};
12+
}
1413

15-
beforeEach(() => {
16-
mockT.mockClear();
14+
test('renders n/a when value is empty string', () => {
15+
const wrapper = factory({ value: '' });
16+
17+
expect(wrapper.find('.scan-interval-none').exists()).toBe(true);
18+
expect(wrapper.find('.scan-interval-none').text()).toBe('n/a');
19+
});
20+
21+
test('parses full h/m/s (e.g., "1h30m20s")', () => {
22+
const wrapper = factory({ value: '1h30m20s' });
23+
24+
expect(wrapper.vm.scanInterval).toBe('1h30m20s');
25+
expect(wrapper.find('.scan-interval-text').text()).toContain('1h30m20s');
26+
});
27+
28+
test('parses only hours (e.g., "2h")', () => {
29+
const wrapper = factory({ value: '2h' });
30+
31+
expect(wrapper.vm.scanInterval).toBe('2h');
1732
});
1833

19-
it('should render the interval when value is provided', () => {
20-
const mockValue = '1h 30m';
21-
const wrapper = mountComponent(mockValue);
34+
test('parses only minutes (e.g., "45m")', () => {
35+
const wrapper = factory({ value: '45m' });
36+
37+
expect(wrapper.vm.scanInterval).toBe('45m');
38+
});
2239

23-
const span = wrapper.find('.scan-interval-text');
40+
test('parses only seconds (e.g., "10s")', () => {
41+
const wrapper = factory({ value: '10s' });
2442

25-
expect(span.exists()).toBe(true);
26-
expect(span.classes()).not.toContain('text-muted');
43+
expect(wrapper.vm.scanInterval).toBe('10s');
44+
});
2745

28-
expect(mockT).toHaveBeenCalledTimes(1);
29-
expect(mockT).toHaveBeenCalledWith('imageScanner.general.every');
46+
test('filters out zero values (e.g., "0h10m0s")', () => {
47+
const wrapper = factory({ value: '0h10m0s' });
3048

31-
expect(span.text()).toBe(`imageScanner.general.every\u00A0${ mockValue }`);
49+
// Should remove hours and seconds
50+
expect(wrapper.vm.scanInterval).toBe('10m');
3251
});
3352

34-
it('should render "n/a" when value is an empty string', () => {
35-
const mockValue = '';
36-
const wrapper = mountComponent(mockValue);
53+
test('returns raw string when regex does not match', () => {
54+
const wrapper = factory({ value: 'invalid' });
55+
56+
// regex still matches empty groups, but value="invalid" → returns original string
57+
expect(wrapper.vm.scanInterval).toBe('invalid');
58+
});
3759

38-
const span = wrapper.find('.scan-interval-text');
60+
test('renders translated prefix + parsed value', () => {
61+
const wrapper = factory({ value: '1h' });
3962

40-
expect(span.exists()).toBe(true);
41-
expect(span.classes()).toContain('scan-interval-none');
42-
expect(span.text()).toBe('n/a');
63+
const text = wrapper.find('.scan-interval-text').text();
4364

44-
expect(mockT).not.toHaveBeenCalled();
65+
expect(text).toContain('imageScanner.general.every');
66+
expect(text).toContain('1h');
4567
});
4668
});

0 commit comments

Comments
 (0)