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(); }); });