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
|
||||
// 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: {
|
||||
environment: 'node',
|
||||
passWithNoTests: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
Reference in new issue
Block a user