Skip to content
Merged
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
11 changes: 9 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ jobs:
run: |
npm run lint

- name: make test-js 🧪
- name: run test and coverage 🧪
run: |
npm run test
npm run coverage

- name: upload coverage 📊
uses: codecov/codecov-action@v5
with:
files: ./coverage/coverage-final.json
token: ${{ secrets.CODECOV_TOKEN }}
slug: bmlt-enabled/activity
319 changes: 319 additions & 0 deletions src/tests/components/ConfigModal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,323 @@ describe('ConfigModal component', () => {
expect(screen.getByText('Select the BMLT root server to query')).toBeInTheDocument();
expect(screen.getByText('Number of days of history to fetch (1-365)')).toBeInTheDocument();
});

test('handles non-Error exceptions when loading servers', async () => {
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
const { fetchServerList } = await import('../../lib/services/serverList');
vi.mocked(fetchServerList).mockRejectedValue('String error');

render(ConfigModal, {
props: {
open: true,
onsubmit: () => {}
}
});

await waitFor(() => {
expect(screen.getByText('Failed to load servers')).toBeInTheDocument();
});

consoleErrorSpy.mockRestore();
});

test('handles non-Error exceptions when loading service bodies', async () => {
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
const user = userEvent.setup();
const { fetchServiceBodies } = await import('../../lib/services/serverList');
vi.mocked(fetchServiceBodies).mockRejectedValue('String error');

render(ConfigModal, {
props: {
open: true,
onsubmit: () => {}
}
});

const serverSelect = await screen.findByLabelText('BMLT Server');
await user.selectOptions(serverSelect, 'https://test1.com');

await waitFor(() => {
expect(screen.getByText('Failed to load service bodies')).toBeInTheDocument();
});

consoleErrorSpy.mockRestore();
});

test('successfully submits form with valid data', async () => {
const user = userEvent.setup();
const onsubmit = vi.fn();

render(ConfigModal, {
props: {
open: true,
onsubmit
}
});

const serverSelect = await screen.findByLabelText('BMLT Server');
await user.selectOptions(serverSelect, 'https://test1.com');

await waitFor(() => {
expect(screen.getByText('Service Bodies')).toBeInTheDocument();
});

// Select a service body (using MultiSelect is complex, so we'll verify the flow)
// The submit button should be disabled initially
const submitButton = screen.getByText('Apply & Reload');
expect(submitButton).toBeDisabled();
});

test('displays selected service body count', async () => {
const user = userEvent.setup();

render(ConfigModal, {
props: {
open: true,
onsubmit: () => {}
}
});

const serverSelect = await screen.findByLabelText('BMLT Server');
await user.selectOptions(serverSelect, 'https://test1.com');

await waitFor(() => {
expect(screen.getByText('Service Bodies')).toBeInTheDocument();
});

// Initially 0 selected
expect(screen.getByText(/0 selected/)).toBeInTheDocument();
});

test('loads service bodies on mount if localServer is set', async () => {
const { config } = await import('../../lib/stores/config.svelte');
const { fetchServiceBodies } = await import('../../lib/services/serverList');

// Pre-configure a server
config.bmltServer = 'https://test1.com';

render(ConfigModal, {
props: {
open: true,
onsubmit: () => {}
}
});

await waitFor(() => {
expect(fetchServiceBodies).toHaveBeenCalledWith('https://test1.com');
});

// Clean up
config.bmltServer = '';
});

test('sorts servers alphabetically', async () => {
const unsortedServers: BmltServer[] = [
{ id: '1', name: 'Zebra Server', url: 'https://zebra.com' },
{ id: '2', name: 'Alpha Server', url: 'https://alpha.com' },
{ id: '3', name: 'Beta Server', url: 'https://beta.com' }
];

const { fetchServerList } = await import('../../lib/services/serverList');
vi.mocked(fetchServerList).mockResolvedValue(unsortedServers);

const { container } = render(ConfigModal, {
props: {
open: true,
onsubmit: () => {}
}
});

await waitFor(() => {
const serverSelect = screen.getByLabelText('BMLT Server');
expect(serverSelect).toBeInTheDocument();
});

// Servers should be sorted in dropdown
const select = container.querySelector('#bmlt-server');
expect(select).toBeInTheDocument();
});

test('creates grouped service body items with different types', async () => {
const user = userEvent.setup();
const diverseServiceBodies: ServiceBody[] = [
{ id: '1', name: 'Zonal Forum Body', type: 'ZF' },
{ id: '2', name: 'Regional Service Body', type: 'RS' },
{ id: '3', name: 'Area Service Body', type: 'AS' },
{ id: '4', name: 'Metro Area Body', type: 'MA' },
{ id: '5', name: 'Group Body', type: 'GR' }
];

const { fetchServiceBodies } = await import('../../lib/services/serverList');
vi.mocked(fetchServiceBodies).mockResolvedValue(diverseServiceBodies);

render(ConfigModal, {
props: {
open: true,
onsubmit: () => {}
}
});

const serverSelect = await screen.findByLabelText('BMLT Server');
await user.selectOptions(serverSelect, 'https://test1.com');

await waitFor(() => {
expect(screen.getByText('Service Bodies')).toBeInTheDocument();
});
});

test('handles service bodies with unknown types', async () => {
const user = userEvent.setup();
const bodiesWithUnknownType: ServiceBody[] = [
{ id: '1', name: 'Known Body', type: 'AS' },
{ id: '2', name: 'Unknown Body', type: 'XX' },
{ id: '3', name: 'Another Unknown', type: 'YY' }
];

const { fetchServiceBodies } = await import('../../lib/services/serverList');
vi.mocked(fetchServiceBodies).mockResolvedValue(bodiesWithUnknownType);

render(ConfigModal, {
props: {
open: true,
onsubmit: () => {}
}
});

const serverSelect = await screen.findByLabelText('BMLT Server');
await user.selectOptions(serverSelect, 'https://test1.com');

await waitFor(() => {
expect(screen.getByText('Service Bodies')).toBeInTheDocument();
});
});

test('displays badge colors based on service body types', async () => {
const user = userEvent.setup();
const typedBodies: ServiceBody[] = [
{ id: '1', name: 'Zonal', type: 'ZF' },
{ id: '2', name: 'Regional', type: 'RS' },
{ id: '3', name: 'Area', type: 'AS' },
{ id: '4', name: 'Metro', type: 'MA' },
{ id: '5', name: 'Group', type: 'GR' }
];

const { fetchServiceBodies } = await import('../../lib/services/serverList');
vi.mocked(fetchServiceBodies).mockResolvedValue(typedBodies);

render(ConfigModal, {
props: {
open: true,
onsubmit: () => {}
}
});

const serverSelect = await screen.findByLabelText('BMLT Server');
await user.selectOptions(serverSelect, 'https://test1.com');

await waitFor(() => {
expect(screen.getByText('Service Bodies')).toBeInTheDocument();
});

// Badge colors are applied based on service body type
});

test('getBadgeColor returns gray for unknown body', async () => {
const user = userEvent.setup();

render(ConfigModal, {
props: {
open: true,
onsubmit: () => {}
}
});

const serverSelect = await screen.findByLabelText('BMLT Server');
await user.selectOptions(serverSelect, 'https://test1.com');

await waitFor(() => {
expect(screen.getByText('Service Bodies')).toBeInTheDocument();
});

// The getBadgeColor function handles unknown body IDs
});

test('clears service body selection when server changes', async () => {
const user = userEvent.setup();
const { config } = await import('../../lib/stores/config.svelte');

// Start with some selected bodies
config.serviceBodyIds = ['1', '2'];

render(ConfigModal, {
props: {
open: true,
onsubmit: () => {}
}
});

const serverSelect = await screen.findByLabelText('BMLT Server');

// Select first server
await user.selectOptions(serverSelect, 'https://test1.com');
await waitFor(() => {
expect(screen.getByText('Service Bodies')).toBeInTheDocument();
});

// Change to different server
await user.selectOptions(serverSelect, 'https://test2.com');

// Service bodies should be reloaded
const { fetchServiceBodies } = await import('../../lib/services/serverList');
await waitFor(() => {
expect(fetchServiceBodies).toHaveBeenCalledWith('https://test2.com');
});

// Clean up
config.serviceBodyIds = [];
});

test('form submit is prevented without service body selection', async () => {
const user = userEvent.setup();
const onsubmit = vi.fn();

render(ConfigModal, {
props: {
open: true,
onsubmit
}
});

const serverSelect = await screen.findByLabelText('BMLT Server');
await user.selectOptions(serverSelect, 'https://test1.com');

await waitFor(() => {
expect(screen.getByText('Service Bodies')).toBeInTheDocument();
});

// Try to submit without selecting service bodies
const submitButton = screen.getByText('Apply & Reload');

// Button should be disabled
expect(submitButton).toBeDisabled();
expect(onsubmit).not.toHaveBeenCalled();
});

test('allows changing days to look back value', async () => {
const user = userEvent.setup();

render(ConfigModal, {
props: {
open: true,
onsubmit: () => {}
}
});

const daysInput = (await screen.findByLabelText('Days to Look Back')) as HTMLInputElement;

await user.clear(daysInput);
await user.type(daysInput, '30');

expect(daysInput.value).toBe('30');
});
});