import { render } from '@dynatrace/strato-components-testing/jest';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { buildMockedFetchResponse } from './test-utils';
import SettingNameSettingsWidget from './setting-name.widget';
const LIST_URL = '/platform/app-settings/v2/objects';
const OBJECT_ID = 'setting-name-connection-object-id';
const LOADED_VALUE = {
name: 'My Setting',
enabled: true,
environment: 'production',
retentionDays: 30,
};
describe('SettingNameSettingsWidget', () => {
let fetchMock: jest.Mock;
beforeEach(() => {
fetchMock = jest.fn();
globalThis.fetch = fetchMock;
});
afterEach(() => {
// Clean up the mock to prevent interference with other tests
fetchMock.mockClear();
});
it('should render a loading indicator before the settings have been fetched', () => {
// A promise that never resolves keeps the widget in its loading state.
fetchMock.mockImplementation(() => new Promise(() => {}));
render();
expect(screen.getByRole('progressbar')).toBeTruthy();
});
it('should render the loaded values once the settings have been fetched', async () => {
fetchMock.mockImplementation((url: string) => {
if (url.startsWith(`${LIST_URL}?`)) {
// The default response fields are only "objectId" and "version" — a regression that
// drops "addFields" would silently return an empty form (EDGE-8).
expect(url).toContain('add-fields=value%2C');
expect(url).toContain('resourceContext');
return buildMockedFetchResponse({
items: [
{
objectId: OBJECT_ID,
version: '1',
value: LOADED_VALUE,
resourceContext: { operations: ['read', 'write'] },
},
],
totalCount: 1,
pageSize: 500,
});
}
return Promise.reject(new Error(`Unexpected fetch URL: ${url}`));
});
render();
expect(await screen.findByDisplayValue('My Setting')).toBeTruthy();
});
it('should create a new settings object when saving for the first time', async () => {
fetchMock.mockImplementation((url: string, init?: RequestInit) => {
if (url.startsWith(`${LIST_URL}?`)) {
return buildMockedFetchResponse({
items: [],
totalCount: 0,
pageSize: 500,
});
} else if (url === LIST_URL && init?.method === 'POST') {
return buildMockedFetchResponse({ objectId: OBJECT_ID, version: '1' });
}
return Promise.reject(new Error(`Unexpected fetch URL: ${url}`));
});
render();
const nameInput = await screen.findByPlaceholderText('Enter a name');
await userEvent.type(nameInput, 'My Setting');
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
await screen.findByText('All changes saved');
expect(
fetchMock.mock.calls.some(
([url, init]: [string, RequestInit?]) =>
url === LIST_URL && init?.method === 'POST',
),
).toBe(true);
});
it('should update the existing settings object with the loaded version', async () => {
fetchMock.mockImplementation((url: string, init?: RequestInit) => {
if (url.startsWith(`${LIST_URL}?`)) {
return buildMockedFetchResponse({
items: [
{
objectId: OBJECT_ID,
version: '1',
value: LOADED_VALUE,
resourceContext: { operations: ['read', 'write'] },
},
],
totalCount: 1,
pageSize: 500,
});
} else if (
url.startsWith(`${LIST_URL}/${OBJECT_ID}?`) &&
init?.method === 'PUT'
) {
return buildMockedFetchResponse({ version: '2' });
}
return Promise.reject(new Error(`Unexpected fetch URL: ${url}`));
});
render();
const nameInput = await screen.findByDisplayValue('My Setting');
await userEvent.clear(nameInput);
await userEvent.type(nameInput, 'Updated Setting');
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
await screen.findByText('All changes saved');
expect(
fetchMock.mock.calls.some(
([url, init]: [string, RequestInit?]) =>
url.startsWith(`${LIST_URL}/${OBJECT_ID}?`) &&
url.includes('optimistic-locking-version=1') &&
init?.method === 'PUT',
),
).toBe(true);
});
it('should disable the form and show a read-only banner when the user lacks write access', async () => {
fetchMock.mockImplementation((url: string) => {
if (url.startsWith(`${LIST_URL}?`)) {
return buildMockedFetchResponse({
items: [
{
objectId: OBJECT_ID,
version: '1',
value: LOADED_VALUE,
resourceContext: { operations: ['read'] },
},
],
totalCount: 1,
pageSize: 500,
});
}
return Promise.reject(new Error(`Unexpected fetch URL: ${url}`));
});
render();
expect(await screen.findByText('Read-only')).toBeTruthy();
const nameInput = await screen.findByDisplayValue('My Setting');
expect(nameInput).toBeDisabled();
expect(screen.queryByRole('button', { name: 'Save' })).toBeNull();
});
it('should show a critical message instead of an empty form when loading fails', async () => {
fetchMock.mockImplementation((url: string) => {
if (url.startsWith(`${LIST_URL}?`)) {
return Promise.reject(new Error('Network error'));
}
return Promise.reject(new Error(`Unexpected fetch URL: ${url}`));
});
render();
expect(await screen.findByText('Unable to load settings')).toBeTruthy();
expect(screen.queryByPlaceholderText('Enter a name')).toBeNull();
});
it('should show the form with defaults when the schema is not yet registered (404)', async () => {
fetchMock.mockImplementation((url: string) => {
if (url.startsWith(`${LIST_URL}?`)) {
const body = { error: { code: 404, message: 'Schema not found' } };
const response = {
json: () => body,
text: () => JSON.stringify(body),
ok: false,
status: 404,
headers: new Headers({ 'content-type': 'application/json' }),
};
return Promise.resolve({ clone: () => ({ ...response }), ...response });
}
return Promise.reject(new Error(`Unexpected fetch URL: ${url}`));
});
render();
expect(await screen.findByPlaceholderText('Enter a name')).toBeTruthy();
expect(screen.queryByText('Unable to load settings')).toBeNull();
});
it('should show a critical toast and keep the typed values when saving fails', async () => {
fetchMock.mockImplementation((url: string, init?: RequestInit) => {
if (url.startsWith(`${LIST_URL}?`)) {
return buildMockedFetchResponse({
items: [],
totalCount: 0,
pageSize: 500,
});
} else if (url === LIST_URL && init?.method === 'POST') {
return Promise.reject(new Error('Network error'));
}
return Promise.reject(new Error(`Unexpected fetch URL: ${url}`));
});
render();
const nameInput = await screen.findByPlaceholderText('Enter a name');
await userEvent.type(nameInput, 'My Setting');
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
expect(await screen.findByText('Failed to save settings')).toBeTruthy();
expect(screen.getByDisplayValue('My Setting')).toBeTruthy();
});
it('should restore the last saved values when discarding unsaved changes', async () => {
fetchMock.mockImplementation((url: string) => {
if (url.startsWith(`${LIST_URL}?`)) {
return buildMockedFetchResponse({
items: [
{
objectId: OBJECT_ID,
version: '1',
value: LOADED_VALUE,
resourceContext: { operations: ['read', 'write'] },
},
],
totalCount: 1,
pageSize: 500,
});
}
return Promise.reject(new Error(`Unexpected fetch URL: ${url}`));
});
render();
const nameInput = await screen.findByDisplayValue('My Setting');
await userEvent.clear(nameInput);
await userEvent.type(nameInput, 'Updated Setting');
const discardButton = await screen.findByRole('button', {
name: 'Discard',
});
await userEvent.click(discardButton);
expect(await screen.findByDisplayValue('My Setting')).toBeTruthy();
expect(screen.queryByRole('button', { name: 'Discard' })).toBeNull();
});
});