Added tests for web/src/lib
This commit is contained in:
1 parent
67e2c96ca7
commit
60643546f6
5 files changed
+172
-2
No files matched your search
@@ -0,0 +1,29 @@
|
|||||||
|
import { expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import type { ClassificationCode } from '@/types/Opportunities';
|
||||||
|
import { applyClassificationChanges } from './classifications';
|
||||||
|
|
||||||
|
const code = (id: number, value: string): ClassificationCode => ({
|
||||||
|
id,
|
||||||
|
is_cpv: true,
|
||||||
|
code: value,
|
||||||
|
classification: `Code ${value}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removes ids, adds codes and orders by code then id', () => {
|
||||||
|
const before = [code(1, '03000000'), code(2, '72000000'), code(3, '45000000')];
|
||||||
|
const after = applyClassificationChanges(before, {
|
||||||
|
added: [code(4, '09000000'), code(5, '03000000')],
|
||||||
|
removed: [2],
|
||||||
|
});
|
||||||
|
expect(after.map((c) => c.id)).toEqual([1, 5, 4, 3]);
|
||||||
|
expect(before).toHaveLength(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('an added code replaces one with the same id', () => {
|
||||||
|
const after = applyClassificationChanges([code(1, '03000000')], {
|
||||||
|
added: [code(1, '03000001')],
|
||||||
|
removed: [],
|
||||||
|
});
|
||||||
|
expect(after).toEqual([code(1, '03000001')]);
|
||||||
|
});
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import type { Annotation } from '@/types/Annotations';
|
||||||
|
import type { OpportunityFilters } from './filterOpportunities';
|
||||||
|
import {
|
||||||
|
activeFilterCount,
|
||||||
|
emptyFilters,
|
||||||
|
filterOpportunities,
|
||||||
|
NOT_MARKED,
|
||||||
|
statusLabel,
|
||||||
|
statusOptions,
|
||||||
|
} from './filterOpportunities';
|
||||||
|
import { testOpportunity } from './testOpportunity';
|
||||||
|
|
||||||
|
const opportunities = [
|
||||||
|
testOpportunity('a', {
|
||||||
|
title: 'Cloud hosting',
|
||||||
|
status: 'active',
|
||||||
|
sme_suitable: true,
|
||||||
|
classifications: [{ id: 1, is_cpv: true, code: '72000000', classification: 'IT' }],
|
||||||
|
latest_release_date: '2026-10-01T12:00:00Z',
|
||||||
|
}),
|
||||||
|
testOpportunity('b', {
|
||||||
|
title: 'Road repairs',
|
||||||
|
status: 'complete',
|
||||||
|
latest_release_date: '2026-10-03T12:00:00Z',
|
||||||
|
}),
|
||||||
|
testOpportunity('c', {
|
||||||
|
title: 'Cloud migration',
|
||||||
|
status: 'planned',
|
||||||
|
latest_release_date: '2026-10-02T12:00:00Z',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const annotations: Record<string, Annotation> = {
|
||||||
|
a: { uuid: 'a', marks: ['interested'], hidden: false },
|
||||||
|
b: { uuid: 'b', marks: [], hidden: true },
|
||||||
|
c: { uuid: 'c', marks: [], hidden: false },
|
||||||
|
};
|
||||||
|
const annotationFor = (uuid: string): Annotation =>
|
||||||
|
annotations[uuid] ?? { uuid, marks: [], hidden: false };
|
||||||
|
|
||||||
|
const filtered = (filters: Partial<OpportunityFilters>): string[] =>
|
||||||
|
filterOpportunities(opportunities, { ...emptyFilters(), ...filters }, annotationFor).map(
|
||||||
|
(opportunity) => opportunity.uuid,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('filterOpportunities', () => {
|
||||||
|
test('hides hidden opportunities unless asked, newest release first', () => {
|
||||||
|
expect(filtered({})).toEqual(['c', 'a']);
|
||||||
|
expect(filtered({ showHidden: true })).toEqual(['b', 'c', 'a']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each<{ name: string; filters: Partial<OpportunityFilters>; want: string[] }>([
|
||||||
|
{ name: 'title, ignoring case and spaces', filters: { title: ' CLOUD ' }, want: ['c', 'a'] },
|
||||||
|
{ name: 'statuses', filters: { statuses: new Set(['active', 'planned']) }, want: ['c', 'a'] },
|
||||||
|
{ name: 'a mark', filters: { marks: new Set(['interested']) }, want: ['a'] },
|
||||||
|
{ name: 'no marks', filters: { marks: new Set([NOT_MARKED]) }, want: ['c'] },
|
||||||
|
{ name: 'classification', filters: { classificationIds: new Set([1]) }, want: ['a'] },
|
||||||
|
{ name: 'SME suitable', filters: { smeSuitable: true }, want: ['a'] },
|
||||||
|
])('filters by $name', ({ filters, want }) => {
|
||||||
|
expect(filtered(filters)).toEqual(want);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('combines filters', () => {
|
||||||
|
expect(filtered({ title: 'cloud', smeSuitable: true })).toEqual(['a']);
|
||||||
|
expect(filtered({ title: 'road', showHidden: true, smeSuitable: true })).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('activeFilterCount counts the filters in use', () => {
|
||||||
|
expect(activeFilterCount(emptyFilters())).toBe(0);
|
||||||
|
expect(
|
||||||
|
activeFilterCount({ ...emptyFilters(), title: ' ', smeSuitable: true, showHidden: true }),
|
||||||
|
).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('statusOptions lists the known statuses, then others sorted', () => {
|
||||||
|
const options = statusOptions([
|
||||||
|
testOpportunity('x', { status: 'zzz' }),
|
||||||
|
testOpportunity('y', { status: 'aaa' }),
|
||||||
|
testOpportunity('z', { status: 'active' }),
|
||||||
|
]);
|
||||||
|
expect(options.slice(-2)).toEqual(['aaa', 'zzz']);
|
||||||
|
expect(options[0]).toBe('planning');
|
||||||
|
expect(statusLabel('active')).toBe('Active');
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import { mergeOpportunities } from './opportunities';
|
||||||
|
import { testOpportunity } from './testOpportunity';
|
||||||
|
|
||||||
|
test('replaces opportunities in place and adds new ones at the end', () => {
|
||||||
|
const list = [testOpportunity('a'), testOpportunity('b')];
|
||||||
|
const merged = mergeOpportunities(list, [
|
||||||
|
testOpportunity('c'),
|
||||||
|
testOpportunity('a', { title: 'Changed' }),
|
||||||
|
]);
|
||||||
|
expect(merged.map((o) => [o.uuid, o.title])).toEqual([
|
||||||
|
['a', 'Changed'],
|
||||||
|
['b', 'Opportunity b'],
|
||||||
|
['c', 'Opportunity c'],
|
||||||
|
]);
|
||||||
|
expect(list[0]?.title).toBe('Opportunity a');
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import type { Opportunity } from '@/types/Opportunities';
|
||||||
|
|
||||||
|
/** An opportunity for tests, with overrides for the fields a test is about. */
|
||||||
|
export function testOpportunity(uuid: string, overrides: Partial<Opportunity> = {}): Opportunity {
|
||||||
|
return {
|
||||||
|
uuid,
|
||||||
|
uuid_type: 'ocid',
|
||||||
|
title: `Opportunity ${uuid}`,
|
||||||
|
status: 'active',
|
||||||
|
description: '',
|
||||||
|
buyer_name: 'Example Council',
|
||||||
|
buyer_id: 'GB-EXAMPLE',
|
||||||
|
classifications: [],
|
||||||
|
main_category: 'services',
|
||||||
|
procurement_method: 'open',
|
||||||
|
multi_lot: false,
|
||||||
|
sme_suitable: false,
|
||||||
|
vcse_suitable: false,
|
||||||
|
start_date: null,
|
||||||
|
end_date: null,
|
||||||
|
gross_value: 0,
|
||||||
|
has_documents: false,
|
||||||
|
source: 'FindATender',
|
||||||
|
latest_release_date: '2026-10-01T12:00:00Z',
|
||||||
|
releases: null,
|
||||||
|
marks: {
|
||||||
|
uuid,
|
||||||
|
hidden: false,
|
||||||
|
skipped: false,
|
||||||
|
interested: false,
|
||||||
|
bid_submitted: false,
|
||||||
|
won: false,
|
||||||
|
lost: false,
|
||||||
|
},
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
+1
-2
@@ -26,10 +26,9 @@ export default defineConfig(({ mode }) => {
|
|||||||
},
|
},
|
||||||
// Vitest. vite-plugin-solid would run tests in jsdom, which isn't
|
// Vitest. vite-plugin-solid would run tests in jsdom, which isn't
|
||||||
// installed: nothing tested yet renders, and Solid's reactivity works
|
// installed: nothing tested yet renders, and Solid's reactivity works
|
||||||
// in Node as it is. passWithNoTests until the first tests are added.
|
// in Node as it is.
|
||||||
test: {
|
test: {
|
||||||
environment: 'node',
|
environment: 'node',
|
||||||
passWithNoTests: true,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user