mirror of
https://github.com/apache/superset.git
synced 2026-09-08 16:25:03 +00:00
Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Code
parent
a501fed560
commit
a0d7ec9faf
+46
@@ -989,6 +989,52 @@ test('shows all options when filterOption is false', async () => {
|
||||
expect(options[0]).toHaveTextContent('Server 0');
|
||||
});
|
||||
|
||||
test('renders a server-matched option whose label diverges from the search term when filterOption is false (regression for #42041)', async () => {
|
||||
// Mirrors the real permissions-search bug: the remote fetch legitimately
|
||||
// matches the raw, underscore-containing value (e.g. a schema name like
|
||||
// "stg_silver"), but the returned option's displayed label has had
|
||||
// underscores replaced with spaces (see formatPermissionLabel in
|
||||
// features/roles/utils.ts). filterOption defaults to true, which
|
||||
// re-filters already-matched options against that same relabeled text
|
||||
// client-side, so the underscore search term never matches and the
|
||||
// legitimately fetched option gets hidden -- this is why
|
||||
// PermissionsField (features/roles/RoleFormItems.tsx) sets
|
||||
// filterOption={false}: the loader is already the authoritative filter,
|
||||
// and its match doesn't depend on the label used to render the option.
|
||||
const searchData = [{ label: 'stg silver', value: 100 }];
|
||||
const loadOptions = jest.fn(async (search: string) =>
|
||||
// totalCount must exceed the empty initial page here, otherwise
|
||||
// AsyncSelect marks allValuesLoaded and short-circuits every later
|
||||
// fetch, including the search request this test depends on.
|
||||
search === ''
|
||||
? { data: [], totalCount: 1 }
|
||||
: { data: searchData, totalCount: 1 },
|
||||
);
|
||||
|
||||
render(
|
||||
<AsyncSelect
|
||||
{...defaultProps}
|
||||
options={loadOptions}
|
||||
filterOption={false}
|
||||
/>,
|
||||
);
|
||||
await open();
|
||||
|
||||
await type('stg_silver');
|
||||
await waitFor(() =>
|
||||
expect(loadOptions).toHaveBeenCalledWith(
|
||||
'stg_silver',
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
),
|
||||
);
|
||||
|
||||
// The backend legitimately matched and returned this option (asserted
|
||||
// above); it should render in the dropdown despite the search term using
|
||||
// underscores while the label uses spaces.
|
||||
expect(await findSelectOption('stg silver')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('preserves new option entry across search fetch when allowNewOptions is on', async () => {
|
||||
const page0Data = Array.from({ length: 10 }, (_, i) => ({
|
||||
label: `Option ${i}`,
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import { render, screen, waitFor, within } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import {
|
||||
RoleNameField,
|
||||
PermissionsField,
|
||||
UsersField,
|
||||
GroupsField,
|
||||
} from './RoleFormItems';
|
||||
import { fetchPermissionOptions } from './utils';
|
||||
|
||||
jest.mock('./utils', () => ({
|
||||
fetchPermissionOptions: jest.fn(),
|
||||
@@ -53,6 +55,45 @@ test('PermissionsField renders loading state', () => {
|
||||
expect(screen.getByTestId('permissions-select')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('PermissionsField shows a permission matched by its raw name even though the label uses spaces (regression for #42041)', async () => {
|
||||
// fetchPermissionOptions matches the raw, underscore-containing name
|
||||
// server-side; the returned label has already gone through
|
||||
// formatPermissionLabel (underscores replaced with spaces for display).
|
||||
// PermissionsField's normalizing filterOption must match the raw search
|
||||
// term against that space-formatted label, or the option the server
|
||||
// legitimately returned gets hidden by client-side re-filtering.
|
||||
jest
|
||||
.mocked(fetchPermissionOptions)
|
||||
.mockImplementation(async (filterValue: string) =>
|
||||
filterValue === 'stg_silver'
|
||||
? { data: [{ value: 1, label: 'stg silver' }], totalCount: 1 }
|
||||
: // totalCount must exceed the empty initial page here, otherwise
|
||||
// AsyncSelect marks allValuesLoaded and short-circuits every
|
||||
// later fetch, including the search request this test depends on.
|
||||
{ data: [], totalCount: 1 },
|
||||
);
|
||||
|
||||
render(<PermissionsField addDangerToast={addDangerToast} />);
|
||||
const combobox = screen.getByRole('combobox');
|
||||
await waitFor(() => userEvent.click(combobox));
|
||||
await userEvent.clear(combobox);
|
||||
await userEvent.type(combobox, 'stg_silver', { delay: 10 });
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fetchPermissionOptions).toHaveBeenCalledWith(
|
||||
'stg_silver',
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
addDangerToast,
|
||||
),
|
||||
);
|
||||
expect(
|
||||
await within(document.querySelector('.rc-virtual-list')!).findByText(
|
||||
'stg silver',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('UsersField renders label and select', () => {
|
||||
render(<UsersField addDangerToast={addDangerToast} loading={false} />);
|
||||
expect(screen.getByText('Users')).toBeInTheDocument();
|
||||
|
||||
@@ -60,6 +60,18 @@ export const PermissionsField = ({
|
||||
placeholder={t('Select permissions')}
|
||||
options={options}
|
||||
loading={loading}
|
||||
// formatPermissionLabel renders the raw permission/view_menu name with
|
||||
// underscores replaced by spaces, so AsyncSelect's default client-side
|
||||
// re-filter never matches a raw-name search term (e.g. "stg_silver")
|
||||
// against the displayed label ("stg silver") and hides the
|
||||
// server-matched option. Normalize both sides so client-side narrowing
|
||||
// still works without hiding valid matches. See #42041.
|
||||
filterOption={(input, option) =>
|
||||
String(option?.label ?? '')
|
||||
.toLowerCase()
|
||||
.replace(/_/g, ' ')
|
||||
.includes(input.toLowerCase().replace(/_/g, ' '))
|
||||
}
|
||||
getPopupContainer={trigger => trigger.closest('.ant-modal-container')}
|
||||
data-test="permissions-select"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user