Merge pull request #28945 from Nikunj0601/feat/ui-disabled-picker

feature: Allow passing `disabled` attribute to all pickers
This commit is contained in:
Ben Lambert
2025-02-25 11:41:36 +01:00
committed by GitHub
35 changed files with 676 additions and 26 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': minor
---
Allowed passing `ui:disabled` for disabling the input field of all the pickers.
@@ -261,6 +261,73 @@ describe('<EntityPicker />', () => {
});
});
describe('ui:disabled EntityPicker', () => {
beforeEach(() => {
uiSchema = {
'ui:options': {
catalogFilter: [
{
kind: ['Group'],
'metadata.name': 'test-entity',
},
{
kind: ['User'],
'metadata.name': 'test-entity',
},
],
},
};
props = {
onChange,
schema,
required: true,
uiSchema,
rawErrors,
formData,
} as unknown as FieldProps<any>;
catalogApi.getEntities.mockResolvedValue({ items: entities });
});
it('Prevents user from modifying input when ui:disabled is true', async () => {
props.uiSchema = { 'ui:disabled': true };
props.formData = 'component:default/myentity';
await renderInTestApp(
<Wrapper>
<EntityPicker {...props} />
</Wrapper>,
);
const input = screen.getByRole('textbox');
// Expect input to be disabled
expect(input).toBeDisabled();
expect(input).toHaveValue('component:default/myentity');
});
it('Allows user to edit when ui:disabled is false', async () => {
props.uiSchema = { 'ui:disabled': false };
props.formData = 'component:default/myentity';
await renderInTestApp(
<Wrapper>
<EntityPicker {...props} />
</Wrapper>,
);
const input = screen.getByRole('textbox');
expect(input).not.toBeDisabled();
fireEvent.change(input, {
target: { value: 'component:default/mynewentity' },
});
fireEvent.blur(input);
expect(input).toHaveValue('component:default/mynewentity');
expect(onChange).toHaveBeenCalledWith('component:default/mynewentity');
});
});
describe('catalogFilter should take precedence over allowedKinds', () => {
beforeEach(() => {
uiSchema = {
@@ -73,6 +73,7 @@ export const EntityPicker = (props: EntityPickerProps) => {
const defaultKind = uiSchema['ui:options']?.defaultKind;
const defaultNamespace =
uiSchema['ui:options']?.defaultNamespace || undefined;
const isDisabled = uiSchema?.['ui:disabled'] ?? false;
const catalogApi = useApi(catalogApiRef);
const entityPresentationApi = useApi(entityPresentationApiRef);
@@ -185,9 +186,10 @@ export const EntityPicker = (props: EntityPickerProps) => {
>
<Autocomplete
disabled={
required &&
!allowArbitraryValues &&
entities?.catalogEntities.length === 1
isDisabled ||
(required &&
!allowArbitraryValues &&
entities?.catalogEntities.length === 1)
}
id={idSchema?.$id}
value={selectedEntity}
@@ -212,6 +214,7 @@ export const EntityPicker = (props: EntityPickerProps) => {
FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }}
variant="outlined"
required={required}
disabled={isDisabled}
InputProps={params.InputProps}
/>
)}
@@ -45,6 +45,8 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => {
const kinds = uiSchema['ui:options']?.kinds;
const showCounts = uiSchema['ui:options']?.showCounts;
const helperText = uiSchema['ui:options']?.helperText;
const isDisabled = uiSchema?.['ui:disabled'] ?? false;
const { t } = useTranslationRef(scaffolderTranslationRef);
const { loading, value: existingTags } = useAsync(async () => {
@@ -101,6 +103,7 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => {
freeSolo
filterSelectedOptions
onChange={setTags}
disabled={isDisabled}
value={formData || []}
inputValue={inputValue}
loading={loading}
@@ -113,6 +116,7 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => {
<TextField
{...params}
label={t('fields.entityTagsPicker.title')}
disabled={isDisabled}
onChange={e => setInputValue(e.target.value)}
error={inputError}
helperText={helperText ?? t('fields.entityTagsPicker.description')}
@@ -407,6 +407,38 @@ describe('<MultiEntityPicker />', () => {
});
});
describe('ui:disabled MultiEntityPicker', () => {
beforeEach(() => {
uiSchema = {
'ui:options': {
allowArbitraryValues: true,
},
'ui:disabled': true,
};
props = {
onChange,
schema,
required: true,
uiSchema,
rawErrors,
formData,
} as unknown as FieldProps<any>;
catalogApi.getEntities.mockResolvedValue({ items: entities });
});
it('Prevents user from modifying input when ui:disabled is true', async () => {
props.formData = ['component/default:myentity'];
await renderInTestApp(
<Wrapper>
<MultiEntityPicker {...props} />
</Wrapper>,
);
const input = screen.getByRole('textbox');
expect(input).toBeDisabled();
});
});
describe('Optional MultiEntityPicker', () => {
beforeEach(() => {
uiSchema = {
@@ -61,10 +61,12 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => {
formData,
idSchema,
} = props;
const catalogFilter = buildCatalogFilter(uiSchema);
const defaultKind = uiSchema['ui:options']?.defaultKind;
const defaultNamespace =
uiSchema['ui:options']?.defaultNamespace || undefined;
const isDisabled = uiSchema?.['ui:disabled'] ?? false;
const [noOfItemsSelected, setNoOfItemsSelected] = useState(0);
const catalogApi = useApi(catalogApiRef);
@@ -151,7 +153,10 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => {
multiple
filterSelectedOptions
disabled={
required && !allowArbitraryValues && entities?.entities?.length === 1
isDisabled ||
(required &&
!allowArbitraryValues &&
entities?.entities?.length === 1)
}
id={idSchema?.$id}
defaultValue={formData}
@@ -175,6 +180,7 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => {
<TextField
{...params}
label={title}
disabled={isDisabled}
margin="dense"
helperText={description}
FormHelperTextProps={{
@@ -26,6 +26,7 @@ import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffo
import React from 'react';
import { OwnerPicker } from './OwnerPicker';
import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog';
import { fireEvent, screen } from '@testing-library/react';
const makeEntity = (kind: string, namespace: string, name: string): Entity => ({
apiVersion: 'backstage.io/v1beta1',
@@ -114,6 +115,73 @@ describe('<OwnerPicker />', () => {
});
});
describe('ui:disabled OwnerPicker', () => {
beforeEach(() => {
uiSchema = {
'ui:options': {
catalogFilter: [
{
kind: ['Group'],
'metadata.name': 'test-entity',
},
{
kind: ['User'],
'metadata.name': 'test-entity',
},
],
},
};
props = {
onChange,
schema,
required: true,
uiSchema,
rawErrors,
formData,
} as unknown as FieldProps<any>;
catalogApi.getEntities.mockResolvedValue({ items: entities });
});
it('Prevents user from modifying input when ui:disabled is true', async () => {
props.uiSchema = { 'ui:disabled': true };
props.formData = 'group:default/myentity';
await renderInTestApp(
<Wrapper>
<OwnerPicker {...props} />
</Wrapper>,
);
const input = screen.getByRole('textbox');
// Expect input to be disabled
expect(input).toBeDisabled();
expect(input).toHaveValue('group:default/myentity');
});
it('Allows user to edit when ui:disabled is false', async () => {
props.uiSchema = { 'ui:disabled': false };
props.formData = 'group:default/myentity';
await renderInTestApp(
<Wrapper>
<OwnerPicker {...props} />
</Wrapper>,
);
const input = screen.getByRole('textbox');
expect(input).not.toBeDisabled();
fireEvent.change(input, {
target: { value: 'group:default/mynewentity' },
});
fireEvent.blur(input);
expect(input).toHaveValue('group:default/mynewentity');
expect(onChange).toHaveBeenCalledWith('group:default/mynewentity');
});
});
describe('with allowedKinds', () => {
beforeEach(() => {
uiSchema = { 'ui:options': { allowedKinds: ['User'] } };
@@ -20,7 +20,13 @@ import {
scaffolderApiRef,
} from '@backstage/plugin-scaffolder-react';
import { BitbucketRepoBranchPicker } from './BitbucketRepoBranchPicker';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import {
act,
fireEvent,
render,
waitFor,
screen,
} from '@testing-library/react';
import { TestApiProvider } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
@@ -44,6 +50,25 @@ describe('BitbucketRepoBranchPicker', () => {
expect(getByRole('textbox')).toHaveValue('main');
});
it('input field disabled', () => {
render(
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
<BitbucketRepoBranchPicker
onChange={jest.fn()}
isDisabled
state={{ branch: 'main' }}
rawErrors={[]}
/>
</TestApiProvider>,
);
const input = screen.getByRole('textbox');
// Expect input to be disabled
expect(input).toBeDisabled();
expect(input).toHaveValue('main');
});
it('calls onChange when the input field changes', () => {
const onChange = jest.fn();
@@ -36,6 +36,7 @@ export const BitbucketRepoBranchPicker = ({
state,
rawErrors,
accessToken,
isDisabled,
required,
}: BaseRepoBranchPickerProps<{
accessToken?: string;
@@ -86,9 +87,15 @@ export const BitbucketRepoBranchPicker = ({
onChange={(_, newValue) => {
onChange({ branch: newValue || '' });
}}
disabled={isDisabled}
options={availableBranches}
renderInput={params => (
<TextField {...params} label="Branch" required={required} />
<TextField
{...params}
label="Branch"
disabled={isDisabled}
required={required}
/>
)}
freeSolo
autoSelect
@@ -15,7 +15,7 @@
*/
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import { DefaultRepoBranchPicker } from './DefaultRepoBranchPicker';
@@ -33,6 +33,23 @@ describe('DefaultRepoBranchPicker', () => {
expect(getByRole('textbox')).toHaveValue('main');
});
it('input field disabled', () => {
render(
<DefaultRepoBranchPicker
onChange={jest.fn()}
isDisabled
state={{ branch: 'main' }}
rawErrors={[]}
/>,
);
const input = screen.getByRole('textbox');
// Expect input to be disabled
expect(input).toBeDisabled();
expect(input).toHaveValue('main');
});
it('calls onChange when the input field changes', () => {
const onChange = jest.fn();
@@ -32,6 +32,7 @@ export const DefaultRepoBranchPicker = ({
onChange,
state,
rawErrors,
isDisabled,
required,
}: BaseRepoBranchPickerProps) => {
const { branch } = state;
@@ -45,6 +46,7 @@ export const DefaultRepoBranchPicker = ({
<TextField
id="branchInput"
label="Branch"
disabled={isDisabled}
onChange={e => onChange({ branch: e.target.value })}
value={branch}
/>
@@ -20,7 +20,13 @@ import {
scaffolderApiRef,
} from '@backstage/plugin-scaffolder-react';
import { GitHubRepoBranchPicker } from './GitHubRepoBranchPicker';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import {
act,
fireEvent,
render,
waitFor,
screen,
} from '@testing-library/react';
import { TestApiProvider } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
@@ -44,6 +50,25 @@ describe('GitHubRepoBranchPicker', () => {
expect(getByRole('textbox')).toHaveValue('main');
});
it('input field disabled', () => {
render(
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
<GitHubRepoBranchPicker
onChange={jest.fn()}
isDisabled
state={{ branch: 'main' }}
rawErrors={[]}
/>
</TestApiProvider>,
);
const input = screen.getByRole('textbox');
// Expect input to be disabled
expect(input).toBeDisabled();
expect(input).toHaveValue('main');
});
it('calls onChange when the input field changes', () => {
const onChange = jest.fn();
@@ -36,6 +36,7 @@ export const GitHubRepoBranchPicker = ({
state,
rawErrors,
accessToken,
isDisabled,
required,
}: BaseRepoBranchPickerProps<{
accessToken?: string;
@@ -86,9 +87,15 @@ export const GitHubRepoBranchPicker = ({
onChange={(_, newValue) => {
onChange({ branch: newValue || '' });
}}
disabled={isDisabled}
options={availableBranches}
renderInput={params => (
<TextField {...params} label="Branch" required={required} />
<TextField
{...params}
label="Branch"
disabled={isDisabled}
required={required}
/>
)}
freeSolo
autoSelect
@@ -31,7 +31,7 @@ import {
useTemplateSecrets,
ScaffolderRJSFField,
} from '@backstage/plugin-scaffolder-react';
import { act, fireEvent } from '@testing-library/react';
import { act, fireEvent, screen } from '@testing-library/react';
import { RepoBranchPicker } from './RepoBranchPicker';
describe('RepoBranchPicker', () => {
@@ -92,6 +92,40 @@ describe('RepoBranchPicker', () => {
);
});
it('should disable the picker when ui:disabled', async () => {
const onSubmit = jest.fn();
await renderInTestApp(
<TestApiProvider
apis={[
[scmIntegrationsApiRef, mockIntegrationsApi],
[scmAuthApiRef, {}],
[scaffolderApiRef, {}],
]}
>
<SecretsContextProvider>
<Form
validator={validator}
schema={{ type: 'string' }}
uiSchema={{ 'ui:field': 'RepoBranchPicker', 'ui:disabled': true }}
fields={{
RepoBranchPicker:
RepoBranchPicker as ScaffolderRJSFField<string>,
}}
onSubmit={onSubmit}
formContext={{
formData: { repoUrl: 'github.com' },
}}
/>
</SecretsContextProvider>
</TestApiProvider>,
);
const input = screen.getByRole('textbox');
expect(input).toBeDisabled();
});
it('should render properly with title and description', async () => {
const { getByText } = await renderInTestApp(
<TestApiProvider
@@ -133,6 +133,7 @@ export const RepoBranchPicker = (props: RepoBranchPickerProps) => {
uiSchema?.['ui:options']?.requestUserCredentials?.secretsKey &&
secrets[uiSchema['ui:options'].requestUserCredentials.secretsKey]
}
isDisabled={uiSchema?.['ui:disabled'] ?? false}
required={required}
/>
);
@@ -146,6 +147,7 @@ export const RepoBranchPicker = (props: RepoBranchPickerProps) => {
uiSchema?.['ui:options']?.requestUserCredentials?.secretsKey &&
secrets[uiSchema['ui:options'].requestUserCredentials.secretsKey]
}
isDisabled={uiSchema?.['ui:disabled'] ?? false}
required={required}
/>
);
@@ -155,6 +157,7 @@ export const RepoBranchPicker = (props: RepoBranchPickerProps) => {
onChange={updateLocalState}
state={state}
rawErrors={rawErrors}
isDisabled={uiSchema?.['ui:disabled'] ?? false}
required={required}
/>
);
@@ -26,5 +26,6 @@ export type BaseRepoBranchPickerProps<T extends {} = {}> = T & {
onChange: (state: RepoBranchPickerState) => void;
state: RepoBranchPickerState;
rawErrors: string[];
isDisabled?: boolean;
required?: boolean;
};
@@ -30,6 +30,23 @@ describe('AzureRepoPicker', () => {
expect(allInputs).toHaveLength(2);
});
it('disables input fields when isDisabled is true', async () => {
const { getAllByRole } = await renderInTestApp(
<AzureRepoPicker
onChange={jest.fn()}
rawErrors={[]}
state={{}}
isDisabled
/>,
);
const allInputs = getAllByRole('textbox');
allInputs.forEach(input => {
expect(input).toBeDisabled();
});
});
describe('org field', () => {
it('calls onChange when the organisation changes', async () => {
const onChange = jest.fn();
@@ -35,6 +35,7 @@ export const AzureRepoPicker = (
rawErrors,
state,
onChange,
isDisabled,
} = props;
const { t } = useTranslationRef(scaffolderTranslationRef);
@@ -63,7 +64,7 @@ export const AzureRepoPicker = (
onChange={s =>
onChange({ organization: String(Array.isArray(s) ? s[0] : s) })
}
disabled={allowedOrganizations.length === 1}
disabled={isDisabled || allowedOrganizations.length === 1}
selected={organization}
items={organizationItems}
/>
@@ -77,6 +78,7 @@ export const AzureRepoPicker = (
label={t('fields.azureRepoPicker.organization.title')}
onChange={e => onChange({ organization: e.target.value })}
helperText={t('fields.azureRepoPicker.organization.description')}
disabled={isDisabled}
value={organization}
/>
)}
@@ -94,7 +96,7 @@ export const AzureRepoPicker = (
onChange={s =>
onChange({ project: String(Array.isArray(s) ? s[0] : s) })
}
disabled={allowedProject.length === 1}
disabled={isDisabled || allowedProject.length === 1}
selected={project}
items={projectItems}
/>
@@ -108,6 +110,7 @@ export const AzureRepoPicker = (
label={t('fields.azureRepoPicker.project.title')}
onChange={e => onChange({ project: e.target.value })}
value={project}
disabled={isDisabled}
helperText={t('fields.azureRepoPicker.project.description')}
/>
)}
@@ -271,4 +271,50 @@ describe('BitbucketRepoPicker', () => {
);
});
});
describe('BitbucketRepoPicker - isDisabled', () => {
it('disables workspace and project inputs when isDisabled is true', async () => {
const { getAllByRole } = await renderInTestApp(
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
<BitbucketRepoPicker
onChange={jest.fn()}
rawErrors={[]}
state={{
host: 'bitbucket.org',
workspace: 'testWorkspace',
project: 'testProject',
}}
isDisabled
/>
</TestApiProvider>,
);
const inputs = getAllByRole('textbox');
expect(inputs).toHaveLength(2);
expect(inputs[0]).toBeDisabled();
expect(inputs[1]).toBeDisabled();
});
it('does not disable workspace and project inputs when isDisabled is false', async () => {
const { getAllByRole } = await renderInTestApp(
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
<BitbucketRepoPicker
onChange={jest.fn()}
rawErrors={[]}
state={{
host: 'bitbucket.org',
workspace: 'testWorkspace',
project: 'testProject',
}}
isDisabled={false}
/>
</TestApiProvider>,
);
const inputs = getAllByRole('textbox');
expect(inputs).toHaveLength(2);
expect(inputs[0]).not.toBeDisabled();
expect(inputs[1]).not.toBeDisabled();
});
});
});
@@ -49,6 +49,7 @@ export const BitbucketRepoPicker = (
rawErrors,
state,
accessToken,
isDisabled,
} = props;
const { t } = useTranslationRef(scaffolderTranslationRef);
@@ -176,7 +177,7 @@ export const BitbucketRepoPicker = (
onChange={s =>
onChange({ workspace: String(Array.isArray(s) ? s[0] : s) })
}
disabled={allowedOwners.length === 1}
disabled={isDisabled || allowedOwners.length === 1}
selected={workspace}
items={ownerItems}
/>
@@ -191,9 +192,11 @@ export const BitbucketRepoPicker = (
<TextField
{...params}
label={t('fields.bitbucketRepoPicker.workspaces.inputTitle')}
disabled={isDisabled}
required
/>
)}
disabled={isDisabled}
freeSolo
autoSelect
/>
@@ -215,7 +218,7 @@ export const BitbucketRepoPicker = (
onChange={s =>
onChange({ project: String(Array.isArray(s) ? s[0] : s) })
}
disabled={allowedProjects.length === 1}
disabled={isDisabled || allowedProjects.length === 1}
selected={project}
items={projectItems}
/>
@@ -226,10 +229,12 @@ export const BitbucketRepoPicker = (
onChange({ project: newValue || '' });
}}
options={availableProjects}
disabled={isDisabled}
renderInput={params => (
<TextField
{...params}
label={t('fields.bitbucketRepoPicker.project.inputTitle')}
disabled={isDisabled}
required
/>
)}
@@ -20,6 +20,23 @@ import { fireEvent } from '@testing-library/react';
import { renderInTestApp } from '@backstage/test-utils';
describe('GerritRepoPicker', () => {
it('disables input fields when isDisabled is true', async () => {
const { getAllByRole } = await renderInTestApp(
<GerritRepoPicker
onChange={jest.fn()}
rawErrors={[]}
state={{}}
isDisabled
/>,
);
const allInputs = getAllByRole('textbox');
allInputs.forEach(input => {
expect(input).toBeDisabled();
});
});
describe('owner input field', () => {
it('calls onChange when the owner input changes', async () => {
const onChange = jest.fn();
@@ -21,7 +21,7 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { scaffolderTranslationRef } from '../../../translation';
export const GerritRepoPicker = (props: BaseRepoUrlPickerProps) => {
const { onChange, rawErrors, state } = props;
const { onChange, rawErrors, state, isDisabled } = props;
const { t } = useTranslationRef(scaffolderTranslationRef);
const { workspace, owner } = state;
return (
@@ -32,6 +32,7 @@ export const GerritRepoPicker = (props: BaseRepoUrlPickerProps) => {
label={t('fields.gerritRepoPicker.owner.title')}
onChange={e => onChange({ owner: e.target.value })}
helperText={t('fields.gerritRepoPicker.owner.description')}
disabled={isDisabled}
value={owner}
/>
</FormControl>
@@ -44,6 +45,7 @@ export const GerritRepoPicker = (props: BaseRepoUrlPickerProps) => {
id="parentInput"
label={t('fields.gerritRepoPicker.parent.title')}
onChange={e => onChange({ workspace: e.target.value })}
disabled={isDisabled}
value={workspace}
helperText={t('fields.gerritRepoPicker.parent.description')}
/>
@@ -20,6 +20,22 @@ import { fireEvent } from '@testing-library/react';
import { renderInTestApp } from '@backstage/test-utils';
describe('GiteaRepoPicker', () => {
it('disables input fields when isDisabled is true', async () => {
const { getAllByRole } = await renderInTestApp(
<GiteaRepoPicker
onChange={jest.fn()}
rawErrors={[]}
state={{}}
isDisabled
/>,
);
const allInputs = getAllByRole('textbox');
allInputs.forEach(input => {
expect(input).toBeDisabled();
});
});
describe('owner input field', () => {
it('calls onChange when the owner input changes', async () => {
const onChange = jest.fn();
@@ -28,7 +28,7 @@ export const GiteaRepoPicker = (
allowedRepos?: string[];
}>,
) => {
const { allowedOwners = [], state, onChange, rawErrors } = props;
const { allowedOwners = [], state, onChange, rawErrors, isDisabled } = props;
const { t } = useTranslationRef(scaffolderTranslationRef);
const ownerItems: SelectItem[] = allowedOwners
? allowedOwners.map(i => ({ label: i, value: i }))
@@ -55,7 +55,7 @@ export const GiteaRepoPicker = (
),
})
}
disabled={allowedOwners.length === 1}
disabled={isDisabled || allowedOwners.length === 1}
selected={owner}
items={ownerItems}
/>
@@ -70,6 +70,7 @@ export const GiteaRepoPicker = (
label={t('fields.giteaRepoPicker.owner.inputTitle')}
onChange={e => onChange({ owner: e.target.value })}
helperText={t('fields.giteaRepoPicker.owner.description')}
disabled={isDisabled}
value={owner}
/>
</>
@@ -170,4 +170,38 @@ describe('GithubRepoPicker', () => {
);
});
});
describe('GithubRepoPicker - isDisabled', () => {
it('disables all inputs when isDisabled is true', async () => {
const { getByLabelText } = await renderInTestApp(
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
<GithubRepoPicker
onChange={jest.fn()}
rawErrors={[]}
state={{ repoName: 'repo' }}
isDisabled
/>
</TestApiProvider>,
);
const ownerInput = getByLabelText(/owner/i);
expect(ownerInput).toBeDisabled();
});
it('does not disable inputs when isDisabled is false', async () => {
const { getByLabelText } = await renderInTestApp(
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
<GithubRepoPicker
onChange={jest.fn()}
rawErrors={[]}
state={{ repoName: 'repo' }}
isDisabled={false}
/>
</TestApiProvider>,
);
const ownerInput = getByLabelText(/owner/i);
expect(ownerInput).not.toBeDisabled();
});
});
});
@@ -34,7 +34,14 @@ export const GithubRepoPicker = (
accessToken?: string;
}>,
) => {
const { allowedOwners = [], rawErrors, state, onChange, accessToken } = props;
const {
allowedOwners = [],
rawErrors,
state,
onChange,
accessToken,
isDisabled,
} = props;
const { t } = useTranslationRef(scaffolderTranslationRef);
const ownerItems: SelectItem[] = allowedOwners
? allowedOwners.map(i => ({ label: i, value: i }))
@@ -110,7 +117,7 @@ export const GithubRepoPicker = (
onChange={s =>
onChange({ owner: String(Array.isArray(s) ? s[0] : s) })
}
disabled={allowedOwners.length === 1}
disabled={isDisabled || allowedOwners.length === 1}
selected={owner}
items={ownerItems}
/>
@@ -126,10 +133,12 @@ export const GithubRepoPicker = (
<TextField
{...params}
label={t('fields.githubRepoPicker.owner.inputTitle')}
disabled={isDisabled}
required
/>
)}
freeSolo
disabled={isDisabled}
autoSelect
/>
)}
@@ -31,6 +31,73 @@ describe('GitlabRepoPicker', () => {
}),
),
};
describe('GitlabRepoPicker - isDisabled', () => {
it('disables owner input when isDisabled is true', async () => {
const { getByRole } = await renderInTestApp(
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
<GitlabRepoPicker
onChange={jest.fn()}
rawErrors={[]}
state={{ repoName: 'repo' }}
isDisabled
/>
</TestApiProvider>,
);
expect(getByRole('textbox')).toBeDisabled();
});
it('does not disable owner input when isDisabled is false', async () => {
const { getByRole } = await renderInTestApp(
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
<GitlabRepoPicker
onChange={jest.fn()}
rawErrors={[]}
state={{ repoName: 'repo' }}
isDisabled={false}
/>
</TestApiProvider>,
);
expect(getByRole('textbox')).not.toBeDisabled();
});
it('disables select input when allowedOwners are provided and isDisabled is true', async () => {
const allowedOwners = ['owner1', 'owner2'];
const { getByRole } = await renderInTestApp(
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
<GitlabRepoPicker
onChange={jest.fn()}
rawErrors={[]}
state={{ repoName: 'repo' }}
allowedOwners={allowedOwners}
isDisabled
/>
</TestApiProvider>,
);
expect(getByRole('combobox')).toBeDisabled();
});
it('does not disable select input when allowedOwners are provided and isDisabled is false', async () => {
const allowedOwners = ['owner1', 'owner2'];
const { getByRole } = await renderInTestApp(
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
<GitlabRepoPicker
onChange={jest.fn()}
rawErrors={[]}
state={{ repoName: 'repo' }}
allowedOwners={allowedOwners}
isDisabled={false}
/>
</TestApiProvider>,
);
expect(getByRole('combobox')).not.toBeDisabled();
});
});
describe('owner field', () => {
it('renders a select if there is a list of allowed owners', async () => {
const allowedOwners = ['owner1', 'owner2'];
@@ -33,7 +33,14 @@ export const GitlabRepoPicker = (
accessToken?: string;
}>,
) => {
const { allowedOwners = [], state, onChange, rawErrors, accessToken } = props;
const {
allowedOwners = [],
state,
onChange,
rawErrors,
accessToken,
isDisabled,
} = props;
const [availableGroups, setAvailableGroups] = useState<
{ title: string; id: string }[]
>([]);
@@ -128,7 +135,7 @@ export const GitlabRepoPicker = (
),
})
}
disabled={allowedOwners.length === 1}
disabled={isDisabled || allowedOwners.length === 1}
selected={owner}
items={ownerItems}
/>
@@ -147,10 +154,12 @@ export const GitlabRepoPicker = (
<TextField
{...params}
label={t('fields.gitlabRepoPicker.owner.title')}
disabled={isDisabled}
required
/>
)}
freeSolo
disabled={isDisabled}
autoSelect
/>
)}
@@ -119,6 +119,36 @@ describe('RepoUrlPicker', () => {
);
});
it('should disable the picker when ui:disabled', async () => {
const onSubmit = jest.fn();
const { getAllByRole } = await renderInTestApp(
<TestApiProvider
apis={[
[scmIntegrationsApiRef, mockIntegrationsApi],
[scmAuthApiRef, {}],
[scaffolderApiRef, mockScaffolderApi],
]}
>
<SecretsContextProvider>
<Form
validator={validator}
schema={{ type: 'string' }}
uiSchema={{ 'ui:field': 'RepoUrlPicker', 'ui:disabled': true }}
fields={{
RepoUrlPicker: RepoUrlPicker as ScaffolderRJSFField<string>,
}}
onSubmit={onSubmit}
/>
</SecretsContextProvider>
</TestApiProvider>,
);
const [ownerInput, repoInput] = getAllByRole('textbox');
expect(ownerInput).toBeDisabled();
expect(repoInput).toBeDisabled();
});
it('should render properly with allowedHosts', async () => {
const { getByRole } = await renderInTestApp(
<TestApiProvider
@@ -77,7 +77,10 @@ export const RepoUrlPicker = (
() => uiSchema?.['ui:options']?.allowedRepos ?? [],
[uiSchema],
);
const isDisabled = useMemo(
() => uiSchema?.['ui:disabled'] ?? false,
[uiSchema],
);
const { owner, organization, project, repoName } = state;
useEffect(() => {
@@ -179,6 +182,7 @@ export const RepoUrlPicker = (
hosts={allowedHosts}
onChange={host => setState(prevState => ({ ...prevState, host }))}
rawErrors={rawErrors}
isDisabled={isDisabled}
/>
{hostType === 'github' && (
<GithubRepoPicker
@@ -186,6 +190,7 @@ export const RepoUrlPicker = (
onChange={updateLocalState}
rawErrors={rawErrors}
state={state}
isDisabled={isDisabled}
accessToken={
uiSchema?.['ui:options']?.requestUserCredentials?.secretsKey &&
secrets[uiSchema['ui:options'].requestUserCredentials.secretsKey]
@@ -198,6 +203,7 @@ export const RepoUrlPicker = (
allowedRepos={allowedRepos}
rawErrors={rawErrors}
state={state}
isDisabled={isDisabled}
onChange={updateLocalState}
/>
)}
@@ -207,6 +213,7 @@ export const RepoUrlPicker = (
rawErrors={rawErrors}
state={state}
onChange={updateLocalState}
isDisabled={isDisabled}
accessToken={
uiSchema?.['ui:options']?.requestUserCredentials?.secretsKey &&
secrets[uiSchema['ui:options'].requestUserCredentials.secretsKey]
@@ -220,6 +227,7 @@ export const RepoUrlPicker = (
rawErrors={rawErrors}
state={state}
onChange={updateLocalState}
isDisabled={isDisabled}
accessToken={
uiSchema?.['ui:options']?.requestUserCredentials?.secretsKey &&
secrets[uiSchema['ui:options'].requestUserCredentials.secretsKey]
@@ -232,6 +240,7 @@ export const RepoUrlPicker = (
allowedProject={allowedProjects}
rawErrors={rawErrors}
state={state}
isDisabled={isDisabled}
onChange={updateLocalState}
/>
)}
@@ -240,6 +249,7 @@ export const RepoUrlPicker = (
rawErrors={rawErrors}
state={state}
onChange={updateLocalState}
isDisabled={isDisabled}
/>
)}
<RepoUrlPickerRepoName
@@ -251,6 +261,7 @@ export const RepoUrlPicker = (
repoName: repo.id || repo.name,
}))
}
isDisabled={isDisabled}
rawErrors={rawErrors}
availableRepos={state.availableRepos}
/>
@@ -100,4 +100,31 @@ describe('RepoUrlPickerHostField', () => {
expect(listbox.getAllByRole('option')).toHaveLength(2);
});
it('disables the host select when isDisabled is true', async () => {
const mockOnChange = jest.fn();
const mockScaffolderApi = {
getIntegrationsList: jest.fn().mockResolvedValue({
integrations: [
{ host: 'github.com', title: 'github.com', type: 'github' },
{ host: 'gitlab.com', title: 'gitlab.com', type: 'gitlab' },
],
}),
};
const { getByTestId } = await renderInTestApp(
<TestApiProvider apis={[[scaffolderApiRef, mockScaffolderApi]]}>
<RepoUrlPickerHost
hosts={['github.com', 'gitlab.com']}
onChange={mockOnChange}
rawErrors={[]}
isDisabled
/>
</TestApiProvider>,
);
const selectElement = getByTestId('host-select').querySelector('select');
expect(selectElement).toBeDisabled();
});
});
@@ -28,8 +28,9 @@ export const RepoUrlPickerHost = (props: {
hosts?: string[];
onChange: (host: string) => void;
rawErrors: string[];
isDisabled?: boolean;
}) => {
const { host, hosts, onChange, rawErrors } = props;
const { host, hosts, onChange, rawErrors, isDisabled } = props;
const { t } = useTranslationRef(scaffolderTranslationRef);
const scaffolderApi = useApi(scaffolderApiRef);
@@ -75,7 +76,7 @@ export const RepoUrlPickerHost = (props: {
>
<Select
native
disabled={hosts?.length === 1}
disabled={isDisabled || hosts?.length === 1}
label={t('fields.repoUrlPicker.host.title')}
onChange={s => onChange(String(Array.isArray(s) ? s[0] : s))}
selected={host}
@@ -107,4 +107,43 @@ describe('RepoUrlPickerRepoName', () => {
await userEvent.click(getByText(availableRepos[0].name));
expect(onChange).toHaveBeenCalledWith(availableRepos[0]);
});
it('should disable the repo selection when isDisabled is true', async () => {
const allowedRepos = ['foo', 'bar'];
const onChange = jest.fn();
const { getByRole } = await renderInTestApp(
<RepoUrlPickerRepoName
onChange={onChange}
allowedRepos={allowedRepos}
rawErrors={[]}
isDisabled
/>,
);
// Find the select element
const selectElement = getByRole('combobox');
// Ensure it's disabled
expect(selectElement).toBeDisabled();
});
it('should disable the text input when no options are passed and isDisabled is true', async () => {
const onChange = jest.fn();
const { getByRole } = await renderInTestApp(
<RepoUrlPickerRepoName
onChange={onChange}
allowedRepos={[]}
rawErrors={[]}
isDisabled
/>,
);
// Find the text input (autocomplete)
const textInput = getByRole('textbox');
// Ensure it's disabled
expect(textInput).toBeDisabled();
});
});
@@ -29,8 +29,16 @@ export const RepoUrlPickerRepoName = (props: {
onChange: (chosenRepo: AvailableRepositories) => void;
rawErrors: string[];
availableRepos?: AvailableRepositories[];
isDisabled?: boolean;
}) => {
const { repoName, allowedRepos, onChange, rawErrors, availableRepos } = props;
const {
repoName,
allowedRepos,
onChange,
rawErrors,
availableRepos,
isDisabled,
} = props;
const { t } = useTranslationRef(scaffolderTranslationRef);
useEffect(() => {
@@ -63,7 +71,7 @@ export const RepoUrlPickerRepoName = (props: {
name: String(Array.isArray(selected) ? selected[0] : selected),
})
}
disabled={allowedRepos.length === 1}
disabled={isDisabled || allowedRepos.length === 1}
selected={repoName}
items={repoItems}
/>
@@ -86,6 +94,7 @@ export const RepoUrlPickerRepoName = (props: {
)}
freeSolo
autoSelect
disabled={isDisabled}
/>
)}
<FormHelperText>
@@ -32,4 +32,5 @@ export type BaseRepoUrlPickerProps<T extends {} = {}> = T & {
onChange: (state: RepoUrlPickerState) => void;
state: RepoUrlPickerState;
rawErrors: string[];
isDisabled?: boolean;
};