Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
import { useResourceCardRow } from '@shell/components/Resource/Detail/Card/StateCard/composables';
import { effectScope, ref } from 'vue';
import { useResourceCardRow, useResourceCardRowFromSummary, useResourceCardRowFromRelationships, useResourceSummary } from '@shell/components/Resource/Detail/Card/StateCard/composables';
import type { SummaryResult } from '@shell/components/Resource/Detail/Card/StateCard/composables';

const generation = ref(0);
const mockStore: any = {
getters: {
'cluster/normalizeType': (type: string) => type,
'cluster/generation': () => generation.value,
},
dispatch: jest.fn(),
};

jest.mock('vuex', () => ({ useStore: () => mockStore }));

const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0));

describe('useResourceCardRow', () => {
describe('with default keys', () => {
Expand Down Expand Up @@ -140,3 +155,276 @@ describe('useResourceCardRow', () => {
});
});
});

describe('useResourceCardRowFromSummary', () => {
it('should return empty props when summaryResult is null', () => {
const result = useResourceCardRowFromSummary('Services', null);

expect(result.label).toBe('Services');
expect(result.color).toBeUndefined();
expect(result.counts).toBeUndefined();
});

it('should return empty props when summary array is empty', () => {
const result = useResourceCardRowFromSummary('Services', { count: 0, summary: [] });

expect(result.color).toBeUndefined();
expect(result.counts).toBeUndefined();
});

it('should return empty props when summary is null', () => {
const result = useResourceCardRowFromSummary('Services', { count: 5, summary: null });

expect(result.color).toBeUndefined();
expect(result.counts).toBeUndefined();
});

it('should return a plain count when no metadata.state.name entry exists', () => {
const summary: SummaryResult = {
count: 3,
summary: [{ property: 'some.other.field', counts: { foo: 3 } }]
};

const result = useResourceCardRowFromSummary('Pods', summary);

expect(result.counts).toStrictEqual([{ label: '', count: 3 }]);
});

it('should map state names to colors and display labels', () => {
const summary: SummaryResult = {
count: 5,
summary: [{ property: 'metadata.state.name', counts: { running: 3, error: 2 } }]
};

const result = useResourceCardRowFromSummary('Pods', summary);

expect(result.counts).toHaveLength(2);
expect(result.counts).toContainEqual(expect.objectContaining({
label: 'running', count: 3, color: 'success'
}));
expect(result.counts).toContainEqual(expect.objectContaining({
label: 'error', count: 2, color: 'error'
}));
});

it('should set the highest alert color as main color', () => {
const summary: SummaryResult = {
count: 4,
summary: [{ property: 'metadata.state.name', counts: { active: 3, error: 1 } }]
};

const result = useResourceCardRowFromSummary('Services', summary);

expect(result.color).toBe('error');
});

it('should sort by alert level then by count', () => {
const summary: SummaryResult = {
count: 6,
summary: [{
property: 'metadata.state.name',
counts: {
active: 3, error: 1, warning: 2
}
}]
};

const result = useResourceCardRowFromSummary('Pods', summary);

expect(result.counts![0].color).toBe('error');
expect(result.counts![1].color).toBe('warning');
expect(result.counts![2].color).toBe('success');
});

it('should pass the to parameter through', () => {
const to = { hash: '#pods' };
const result = useResourceCardRowFromSummary('Pods', null, to);

expect(result.to).toStrictEqual(to);
});

it('should handle a single state', () => {
const summary: SummaryResult = {
count: 2,
summary: [{ property: 'metadata.state.name', counts: { active: 2 } }]
};

const result = useResourceCardRowFromSummary('Services', summary);

expect(result.counts).toHaveLength(1);
expect(result.counts![0]).toStrictEqual(expect.objectContaining({
label: 'active', count: 2, color: 'success'
}));
expect(result.color).toBe('success');
});
});

describe('useResourceCardRowFromRelationships', () => {
it('should return empty props for empty relationships', () => {
const result = useResourceCardRowFromRelationships('Refers to', []);

expect(result.label).toBe('Refers to');
expect(result.color).toBeUndefined();
expect(result.counts).toBeUndefined();
});

it('should aggregate relationship states', () => {
const rels = [
{ toType: 'configmap', state: 'active' },
{ toType: 'secret', state: 'active' },
{ toType: 'serviceaccount', state: 'error' }
];

const result = useResourceCardRowFromRelationships('Refers to', rels);

expect(result.counts).toHaveLength(2);
expect(result.counts).toContainEqual(expect.objectContaining({ label: 'active', count: 2 }));
expect(result.counts).toContainEqual(expect.objectContaining({ label: 'error', count: 1 }));
});

it('should default missing state to "missing"', () => {
const rels = [
{ toType: 'configmap' },
{ toType: 'secret', state: 'active' }
];

const result = useResourceCardRowFromRelationships('Refers to', rels);

expect(result.counts).toContainEqual(expect.objectContaining({
label: 'missing', count: 1, color: 'warning'
}));
expect(result.counts).toContainEqual(expect.objectContaining({
label: 'active', count: 1, color: 'success'
}));
});

it('should set the highest alert color as main color', () => {
const rels = [
{ toType: 'configmap', state: 'active' },
{ toType: 'secret', state: 'error' }
];

const result = useResourceCardRowFromRelationships('Refers to', rels);

expect(result.color).toBe('error');
});

it('should sort by alert level then by count', () => {
const rels = [
{ toType: 'a', state: 'active' },
{ toType: 'b', state: 'active' },
{ toType: 'c', state: 'active' },
{ toType: 'd', state: 'error' },
{ toType: 'e', state: 'warning' },
{ toType: 'f', state: 'warning' }
];

const result = useResourceCardRowFromRelationships('Refers to', rels);

expect(result.counts![0].color).toBe('error');
expect(result.counts![1].color).toBe('warning');
expect(result.counts![2].color).toBe('success');
});

it('should pass the to parameter through', () => {
const to = { hash: '#related' };
const result = useResourceCardRowFromRelationships('Refers to', [], to);

expect(result.to).toStrictEqual(to);
});

it('should handle all relationships having no state', () => {
const rels = [
{ toType: 'configmap' },
{ toType: 'secret' }
];

const result = useResourceCardRowFromRelationships('Refers to', rels);

expect(result.counts).toHaveLength(1);
expect(result.counts![0]).toStrictEqual(expect.objectContaining({
label: 'missing', count: 2, color: 'warning'
}));
});
});

describe('useResourceSummary', () => {
beforeEach(() => {
jest.clearAllMocks();
generation.value = 0;
});

it('should fetch initial summary data', async() => {
mockStore.dispatch.mockResolvedValue({
count: 5,
summary: [{ property: 'metadata.state.name', counts: { active: 5 } }]
});

const scope = effectScope();
const { count, summary } = scope.run(() => useResourceSummary('pod', { summaryField: 'metadata.state.name' }))!;

await flushPromises();

expect(mockStore.dispatch).toHaveBeenCalledWith('cluster/fetchResourceSummary', {
type: 'pod',
opt: { summaryField: 'metadata.state.name' }
});
expect(count.value).toBe(5);
expect(summary.value).toStrictEqual([{ property: 'metadata.state.name', counts: { active: 5 } }]);

scope.stop();
});

it('should not update refs when fetch returns undefined', async() => {
mockStore.dispatch.mockResolvedValue(undefined);

const scope = effectScope();
const { count, summary } = scope.run(() => useResourceSummary('pod', { summaryField: 'metadata.state.name' }))!;

await flushPromises();

expect(count.value).toBe(0);
expect(summary.value).toBeNull();

scope.stop();
});

it('should refetch when generation changes', async() => {
mockStore.dispatch
.mockResolvedValueOnce({ count: 2, summary: [{ property: 'metadata.state.name', counts: { active: 2 } }] })
.mockResolvedValueOnce({ count: 3, summary: [{ property: 'metadata.state.name', counts: { active: 3 } }] });

const scope = effectScope();
const { count, summary } = scope.run(() => useResourceSummary('pod', { summaryField: 'metadata.state.name' }))!;

await flushPromises();
expect(count.value).toBe(2);

generation.value++;
await flushPromises();

expect(mockStore.dispatch).toHaveBeenCalledTimes(2);
expect(count.value).toBe(3);
expect(summary.value).toStrictEqual([{ property: 'metadata.state.name', counts: { active: 3 } }]);

scope.stop();
});

it('should stop watching when scope is disposed', async() => {
mockStore.dispatch.mockResolvedValue({ count: 1, summary: null });

const scope = effectScope();

scope.run(() => useResourceSummary('pod', { summaryField: 'metadata.state.name' }));
await flushPromises();

expect(mockStore.dispatch).toHaveBeenCalledTimes(1);

scope.stop();

generation.value++;
await flushPromises();

expect(mockStore.dispatch).toHaveBeenCalledTimes(1);
});
});
Loading
Loading