diff --git a/src/web/components/form/radio.jsx b/src/web/components/form/radio.jsx
index 01f998469b..508fde0ea4 100644
--- a/src/web/components/form/radio.jsx
+++ b/src/web/components/form/radio.jsx
@@ -30,7 +30,7 @@ const Radio = ({
{...props}
checked={checked}
disabled={disabled}
- label={title}
+ label={String(title)}
name={name}
value={value}
onChange={handleChange}
diff --git a/src/web/pages/nvts/__tests__/nvtpreference.jsx b/src/web/pages/nvts/__tests__/nvtpreference.jsx
new file mode 100644
index 0000000000..33627f0b33
--- /dev/null
+++ b/src/web/pages/nvts/__tests__/nvtpreference.jsx
@@ -0,0 +1,199 @@
+/* SPDX-FileCopyrightText: 2025 Greenbone AG
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import {describe, test, expect, testing} from '@gsa/testing';
+import NvtPreference from 'web/pages/nvts/nvtpreference';
+import {render, fireEvent, screen} from 'web/utils/testing';
+
+describe('NvtPreference', () => {
+ const mockOnChange = testing.fn();
+
+ const renderComponent = (preference, value) => {
+ render(
+ ,
+ );
+ };
+
+ test('renders checkbox input', () => {
+ const preference = {
+ type: 'checkbox',
+ hr_name: 'Checkbox Preference',
+ name: 'checkbox_preference',
+ };
+ renderComponent(preference, 'yes');
+
+ expect(screen.getByText('Checkbox Preference')).toBeVisible();
+ expect(screen.getByRole('radio', {name: /yes/i})).toBeVisible();
+ expect(screen.getByRole('radio', {name: /no/i})).toBeVisible();
+ });
+
+ test('renders password input', () => {
+ const preference = {
+ type: 'password',
+ hr_name: 'Password Preference',
+ name: 'password_preference',
+ };
+ renderComponent(preference, '');
+
+ expect(screen.getByText('Password Preference')).toBeVisible();
+ expect(
+ screen.getByRole('checkbox', {name: /Replace existing password with/i}),
+ ).toBeVisible();
+ expect(screen.getByTestId('password-input')).toBeDisabled();
+ });
+
+ test('renders file input', () => {
+ const preference = {
+ type: 'file',
+ hr_name: 'File Preference',
+ name: 'file_preference',
+ value: '',
+ };
+ renderComponent(preference, '');
+
+ expect(screen.getByText('File Preference')).toBeVisible();
+ expect(screen.getByRole('checkbox', {name: /Upload file/i})).toBeVisible();
+ expect(screen.getByTestId('file-input')).toBeDisabled();
+ });
+
+ test('renders radio input', () => {
+ const preference = {
+ type: 'radio',
+ hr_name: 'Radio Preference',
+ name: 'radio_preference',
+ value: 'option1',
+ alt: ['option2', 'option3'],
+ };
+ renderComponent(preference, 'option1');
+
+ expect(screen.getByText('Radio Preference')).toBeVisible();
+ expect(screen.getByRole('radio', {name: /option1/i})).toBeChecked();
+ expect(screen.getByRole('radio', {name: /option2/i})).toBeVisible();
+ expect(screen.getByRole('radio', {name: /option3/i})).toBeVisible();
+ });
+
+ test('renders radio input with numeric value 0', () => {
+ const preference = {
+ type: 'radio',
+ hr_name: 'Radio Preference',
+ name: 'radio_preference',
+ value: 1,
+ alt: [0, 2],
+ };
+ renderComponent(preference, 2);
+
+ expect(screen.getByText('Radio Preference')).toBeVisible();
+ expect(screen.getByRole('radio', {name: '0'})).toBeVisible();
+ expect(screen.getByRole('radio', {name: '1'})).toBeVisible();
+ expect(screen.getByRole('radio', {name: '2'})).toBeChecked();
+
+ fireEvent.click(screen.getByRole('radio', {name: '0'}));
+ expect(mockOnChange).toHaveBeenCalledWith({
+ type: 'setValue',
+ newState: {name: 'radio_preference', value: 0},
+ });
+ });
+
+ test('renders text input', () => {
+ const preference = {
+ type: 'text',
+ hr_name: 'Text Preference',
+ name: 'text_preference',
+ };
+ renderComponent(preference, 'some text');
+
+ expect(screen.getByText('Text Preference')).toBeVisible();
+ expect(screen.getByRole('textbox')).toHaveValue('some text');
+ });
+
+ test('calls onChange when checkbox is toggled', () => {
+ const preference = {
+ type: 'checkbox',
+ hr_name: 'Checkbox Preference',
+ name: 'checkbox_preference',
+ };
+ renderComponent(preference, 'yes');
+
+ fireEvent.click(screen.getByRole('radio', {name: /no/i}));
+ expect(mockOnChange).toHaveBeenCalledWith({
+ type: 'setValue',
+ newState: {name: 'checkbox_preference', value: 'no'},
+ });
+ });
+
+ test('calls onChange when password checkbox is toggled', () => {
+ const preference = {
+ type: 'password',
+ hr_name: 'Password Preference',
+ name: 'password_preference',
+ };
+ renderComponent(preference, '');
+
+ fireEvent.click(
+ screen.getByRole('checkbox', {name: /Replace existing password with/i}),
+ );
+
+ expect(screen.getByTestId('password-input')).not.toBeDisabled();
+ expect(mockOnChange).toHaveBeenCalledWith({
+ type: 'setValue',
+ newState: {name: 'password_preference', value: ''},
+ });
+ });
+
+ test('calls onChange when file checkbox is toggled', () => {
+ const preference = {
+ type: 'file',
+ hr_name: 'File Preference',
+ name: 'file_preference',
+ value: '',
+ };
+ renderComponent(preference, '');
+
+ fireEvent.click(screen.getByRole('checkbox', {name: /Upload file/i}));
+ expect(mockOnChange).toHaveBeenCalledWith({
+ type: 'setValue',
+ newState: {name: 'file_preference', value: ''},
+ });
+ });
+
+ test('calls onChange when radio is selected', () => {
+ const preference = {
+ type: 'radio',
+ hr_name: 'Radio Preference',
+ name: 'radio_preference',
+ value: 'option1',
+ alt: ['option2', 'option3'],
+ };
+ renderComponent(preference, 'option1');
+
+ fireEvent.click(screen.getByRole('radio', {name: /option2/i}));
+ expect(mockOnChange).toHaveBeenCalledWith({
+ type: 'setValue',
+ newState: {name: 'radio_preference', value: 'option2'},
+ });
+ });
+
+ test('calls onChange when text input is changed', () => {
+ const preference = {
+ type: 'text',
+ hr_name: 'Text Preference',
+ name: 'text_preference',
+ };
+ renderComponent(preference, 'some text');
+
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: {value: 'new text'},
+ });
+ expect(mockOnChange).toHaveBeenCalledWith({
+ type: 'setValue',
+ newState: {name: 'text_preference', value: 'new text'},
+ });
+ });
+});
diff --git a/src/web/pages/nvts/nvtpreference.jsx b/src/web/pages/nvts/nvtpreference.jsx
index 51c94eafe5..e3e194274d 100644
--- a/src/web/pages/nvts/nvtpreference.jsx
+++ b/src/web/pages/nvts/nvtpreference.jsx
@@ -6,7 +6,8 @@
import _ from 'gmp/locale';
import {map} from 'gmp/utils/array';
import {isEmpty} from 'gmp/utils/string';
-import React from 'react';
+import {useState} from 'react';
+import styled from 'styled-components';
import Checkbox from 'web/components/form/checkbox';
import FileField from 'web/components/form/filefield';
import PasswordField from 'web/components/form/passwordfield';
@@ -15,127 +16,116 @@ import TextField from 'web/components/form/textfield';
import YesNoRadio from 'web/components/form/yesnoradio';
import Column from 'web/components/layout/column';
import Divider from 'web/components/layout/divider';
-import Layout from 'web/components/layout/layout';
import TableData from 'web/components/table/data';
import TableRow from 'web/components/table/row';
import PropTypes from 'web/utils/proptypes';
-const noop_convert = value => value;
+const StyledTableData = styled(TableData)`
+ overflow-wrap: break-word;
+ white-space: normal;
+ word-break: break-word;
+`;
-class NvtPreference extends React.Component {
- constructor(...args) {
- super(...args);
+const noopConvert = value => value;
- this.state = {
- checked: false,
- };
+const NvtPreference = ({preference, value = '', onChange}) => {
+ const [checked, setChecked] = useState(false);
- this.onCheckedChange = this.onCheckedChange.bind(this);
- this.onPreferenceChange = this.onPreferenceChange.bind(this);
- }
-
- onPreferenceChange(value) {
- const {onChange, preference} = this.props;
+ const onPreferenceChange = value => {
onChange({type: 'setValue', newState: {name: preference.name, value}});
- }
+ };
- onCheckedChange(value) {
+ const onCheckedChange = value => {
if (value) {
- this.onPreferenceChange('');
+ onPreferenceChange('');
} else {
- this.onPreferenceChange(undefined);
+ onPreferenceChange(undefined);
}
- this.setState({checked: value});
- }
+ setChecked(value);
+ };
+ const {type} = preference;
- render() {
- const {preference, value = ''} = this.props;
+ let input;
- const {checked} = this.state;
- const {type} = preference;
-
- let input;
-
- if (type === 'checkbox') {
- input = (
-
+ );
+ } else if (type === 'password') {
+ input = (
+
+
- );
- } else if (type === 'password') {
- input = (
-
-
-
-
- );
- } else if (type === 'file') {
- input = (
-
-
-
-
- );
- } else if (type === 'radio') {
- input = (
-
-
- {map(preference.alt, alt => {
- return (
-
- );
- })}
-
- );
- } else {
- input = (
-
- );
- }
- return (
-
- {preference.hr_name}
- {input}
- {preference.default}
-
+
+ );
+ } else if (type === 'file') {
+ input = (
+
+
+
+
+ );
+ } else if (type === 'radio') {
+ input = (
+
+ onPreferenceChange(preference.value)}
+ />
+ {map(preference.alt, alt => {
+ return (
+ onPreferenceChange(alt)}
+ />
+ );
+ })}
+
+ );
+ } else {
+ input = (
+
);
}
-}
+ return (
+
+ {preference.hr_name}
+ {input}
+ {preference.default}
+
+ );
+};
NvtPreference.propTypes = {
preference: PropTypes.shape({
diff --git a/src/web/pages/scanconfigs/editnvtdetailsdialog.jsx b/src/web/pages/scanconfigs/editnvtdetailsdialog.jsx
index 07cf4e7151..16b030f276 100644
--- a/src/web/pages/scanconfigs/editnvtdetailsdialog.jsx
+++ b/src/web/pages/scanconfigs/editnvtdetailsdialog.jsx
@@ -10,6 +10,7 @@ import DateTime from 'web/components/date/datetime';
import SaveDialog from 'web/components/dialog/savedialog';
import Radio from 'web/components/form/radio';
import TextField from 'web/components/form/textfield';
+import Column from 'web/components/layout/column';
import Divider from 'web/components/layout/divider';
import Layout from 'web/components/layout/layout';
import DetailsLink from 'web/components/link/detailslink';
@@ -227,7 +228,7 @@ const EditNvtDetailsDialog = ({
{_('Timeout')}
-
+
-
- setDefaultTimeout(value)}
- />
-
-
-
+ setDefaultTimeout(value)}
+ />
+
+
{isDefined(defaultTimeout) ? defaultTimeout : ''}