Revision 1b215f65
Added by Maria Agaphontzev about 2 months ago
webpack/assets/javascripts/bundle.js | ||
---|---|---|
import * as configReportsModalDiff from './foreman_config_reports_modal_diff';
|
||
import * as dashboard from './dashboard';
|
||
import * as spice from './spice';
|
||
import * as typeAheadSelect from './foreman_type_ahead_select';
|
||
import * as lookupKeys from './foreman_lookup_keys';
|
||
import './react_app/common/MountingService';
|
||
import './foreman_overrides';
|
||
... | ... | |
document,
|
||
componentRegistry,
|
||
store,
|
||
typeAheadSelect,
|
||
lookupKeys,
|
||
});
|
webpack/assets/javascripts/foreman_tools.js | ||
---|---|---|
});
|
||
}
|
||
|
||
export function initTypeAheadSelect(input) {
|
||
input.select2({
|
||
formatNoMatches: __('No matches found'),
|
||
ajax: {
|
||
url: input.data('url'),
|
||
dataType: 'json',
|
||
quietMillis: 250,
|
||
data: (term, page) => ({
|
||
q: term,
|
||
scope: input.data('scope'),
|
||
}),
|
||
results: data => ({
|
||
results: data.map(({ id, name }) => ({ id, text: name })),
|
||
}),
|
||
cache: true,
|
||
},
|
||
initSelection(element, callback) {
|
||
$.ajax(input.data('url'), {
|
||
data: {
|
||
scope: input.data('scope'),
|
||
},
|
||
dataType: 'json',
|
||
}).done(data => {
|
||
if (data.length > 0) {
|
||
// eslint-disable-next-line standard/no-callback-literal
|
||
callback({ id: data[0].id, text: data[0].name });
|
||
}
|
||
});
|
||
},
|
||
width: '400px',
|
||
});
|
||
}
|
||
|
||
// generates an absolute, needed in case of running Foreman from a subpath
|
||
export { foremanUrl } from './react_app/common/helpers';
|
||
|
webpack/assets/javascripts/foreman_tools.test.js | ||
---|---|---|
);
|
||
});
|
||
});
|
||
|
||
/* eslint-disable max-statements */
|
||
describe('initTypeAheadSelect', () => {
|
||
it('initializes select2 on given input field', () => {
|
||
document.body.innerHTML =
|
||
'<input type="text" id="typeahead" data-url="testurl" data-scope="testscope">';
|
||
|
||
const field = $('#typeahead');
|
||
|
||
$.ajax = jest.fn(url => {
|
||
const ajaxMock = $.Deferred();
|
||
|
||
ajaxMock.resolve([
|
||
{ id: 1, name: 'testoption' },
|
||
{ id: 2, name: 'anotheroption' },
|
||
]);
|
||
return ajaxMock.promise();
|
||
});
|
||
|
||
tools.initTypeAheadSelect(field);
|
||
$('.select2-choice').trigger('mousedown');
|
||
$('.select2-choice').trigger('mouseup');
|
||
expect(document.body.innerHTML).toContain('select2-container');
|
||
expect($('.select2-chosen').text()).toEqual('testoption');
|
||
});
|
||
});
|
webpack/assets/javascripts/foreman_type_ahead_select.js | ||
---|---|---|
import {
|
||
updateOptions as updateTypeAheadSelectOptions,
|
||
updateSelected as updateTypeAheadSelectSelected,
|
||
} from './react_app/components/common/TypeAheadSelect/TypeAheadSelectActions';
|
||
import store from './react_app/redux';
|
||
|
||
export const updateOptions = (options, id) => {
|
||
store.dispatch(updateTypeAheadSelectOptions(options, id));
|
||
};
|
||
|
||
export const updateSelected = (selected, id) => {
|
||
store.dispatch(updateTypeAheadSelectSelected(selected, id));
|
||
};
|
webpack/assets/javascripts/react_app/common/testHelpers.js | ||
---|---|---|
factChart: {
|
||
modalToDisplay: {},
|
||
},
|
||
typeAheadSelect: {},
|
||
settingRecords: {
|
||
settings: {},
|
||
editing: null,
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/TypeAheadSelect.fixtures.js | ||
---|---|---|
import Immutable from 'seamless-immutable';
|
||
|
||
export const id = 'test_type_ahead_select';
|
||
export const options = ['option1', 'option2'];
|
||
export const selected = ['option2'];
|
||
export const initialState = Immutable({ typeAheadSelect: {} });
|
||
export const populatedState = Immutable({
|
||
typeAheadSelect: {
|
||
test_type_ahead_select: {
|
||
options,
|
||
selected,
|
||
},
|
||
},
|
||
});
|
||
|
||
export const props = {
|
||
id,
|
||
options,
|
||
selected,
|
||
};
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/TypeAheadSelectActions.js | ||
---|---|---|
import {
|
||
INIT,
|
||
UPDATE_OPTIONS,
|
||
UPDATE_SELECTED,
|
||
} from './TypeAheadSelectConstants';
|
||
import { mapSelected } from './TypeAheadSelectSelectors';
|
||
|
||
export const initialUpdate = (options, selected, id) => ({
|
||
type: INIT,
|
||
payload: {
|
||
id,
|
||
options,
|
||
selected,
|
||
},
|
||
});
|
||
|
||
export const updateOptions = (options, id) => ({
|
||
type: UPDATE_OPTIONS,
|
||
payload: {
|
||
id,
|
||
options,
|
||
},
|
||
});
|
||
|
||
export const updateSelected = (selected, id) => ({
|
||
type: UPDATE_SELECTED,
|
||
payload: {
|
||
id,
|
||
selected: mapSelected(selected),
|
||
},
|
||
});
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/TypeAheadSelectConstants.js | ||
---|---|---|
export const INIT = 'TYPEAHEAD_INIT';
|
||
export const UPDATE_OPTIONS = 'TYPEAHEAD_UPDATE_OPTIONS';
|
||
export const UPDATE_SELECTED = 'TYPEAHEAD_UPDATE_SELECTED';
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/TypeAheadSelectReducer.js | ||
---|---|---|
import Immutable from 'seamless-immutable';
|
||
import {
|
||
INIT,
|
||
UPDATE_OPTIONS,
|
||
UPDATE_SELECTED,
|
||
} from './TypeAheadSelectConstants';
|
||
|
||
const initialState = Immutable({});
|
||
|
||
export default (
|
||
state = initialState,
|
||
{ type, payload: { id, options, selected } = {} }
|
||
) => {
|
||
switch (type) {
|
||
case INIT:
|
||
return state.setIn([id], {
|
||
...state[id],
|
||
options,
|
||
selected,
|
||
});
|
||
case UPDATE_OPTIONS:
|
||
return state.setIn([id], {
|
||
...state[id],
|
||
options,
|
||
});
|
||
case UPDATE_SELECTED:
|
||
return state.setIn([id], {
|
||
...state[id],
|
||
selected,
|
||
});
|
||
default:
|
||
return state;
|
||
}
|
||
};
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/TypeAheadSelectSelectors.js | ||
---|---|---|
import Immutable from 'seamless-immutable';
|
||
|
||
export const mapSelected = selected => selected.map(item => item.label || item);
|
||
|
||
const selectTypeAheadSelect = ({ typeAheadSelect }, id) =>
|
||
typeAheadSelect[id] || {};
|
||
|
||
export const selectTypeAheadSelectExists = ({ typeAheadSelect }, id) =>
|
||
!!typeAheadSelect[id];
|
||
|
||
export const selectOptions = (state, id) => {
|
||
const typeAhead = selectTypeAheadSelect(state, id);
|
||
const options = typeAhead.options || [];
|
||
return Immutable.isImmutable(options) ? options.asMutable() : options;
|
||
};
|
||
|
||
export const selectSelected = (state, id) =>
|
||
selectTypeAheadSelect(state, id).selected;
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/__snapshots__/integration.test.js.snap | ||
---|---|---|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||
|
||
exports[`TypeAheadSelect integration test flows: after initialUpdate 1`] = `
|
||
Object {
|
||
"typeAheadSelect": Object {
|
||
"test_type_ahead_select": Object {
|
||
"options": Array [
|
||
"option1",
|
||
"option2",
|
||
],
|
||
"selected": Array [
|
||
"option2",
|
||
],
|
||
},
|
||
},
|
||
}
|
||
`;
|
||
|
||
exports[`TypeAheadSelect integration test flows: initial state 1`] = `
|
||
Object {
|
||
"typeAheadSelect": Object {},
|
||
}
|
||
`;
|
||
|
||
exports[`TypeAheadSelect integration test flows: updated options and selections 1`] = `
|
||
Object {
|
||
"typeAheadSelect": Object {
|
||
"test_type_ahead_select": Object {
|
||
"options": Array [
|
||
"Walrus",
|
||
"Bear",
|
||
],
|
||
"selected": Array [
|
||
"Bear",
|
||
],
|
||
},
|
||
},
|
||
}
|
||
`;
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/__tests__/TypeAheadSelectActions.test.js | ||
---|---|---|
import { testActionSnapshotWithFixtures } from '../../../../common/testHelpers';
|
||
import {
|
||
initialUpdate,
|
||
updateOptions,
|
||
updateSelected,
|
||
} from '../TypeAheadSelectActions';
|
||
import { id, options, selected } from '../TypeAheadSelect.fixtures';
|
||
|
||
const fixtures = {
|
||
'initializes defaults': () => initialUpdate(options, selected, id),
|
||
'updates options': () => updateOptions(options, id),
|
||
'updates selections': () => updateSelected(selected, id),
|
||
};
|
||
|
||
describe('TypeAheadSelectActions', () =>
|
||
testActionSnapshotWithFixtures(fixtures));
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/__tests__/TypeAheadSelectReducer.test.js | ||
---|---|---|
import { testReducerSnapshotWithFixtures } from '../../../../common/testHelpers';
|
||
import reducer from '../TypeAheadSelectReducer';
|
||
import {
|
||
INIT,
|
||
UPDATE_OPTIONS,
|
||
UPDATE_SELECTED,
|
||
} from '../TypeAheadSelectConstants';
|
||
import { id, options, selected } from '../TypeAheadSelect.fixtures';
|
||
|
||
const fixtures = {
|
||
'initial state': {},
|
||
'initiates defaults': {
|
||
action: {
|
||
type: INIT,
|
||
payload: {
|
||
id,
|
||
options,
|
||
selected,
|
||
},
|
||
},
|
||
},
|
||
'updates options': {
|
||
action: {
|
||
type: UPDATE_OPTIONS,
|
||
payload: {
|
||
id,
|
||
options,
|
||
},
|
||
},
|
||
},
|
||
'updates selections': {
|
||
action: {
|
||
type: UPDATE_SELECTED,
|
||
payload: {
|
||
id,
|
||
selected,
|
||
},
|
||
},
|
||
},
|
||
};
|
||
|
||
describe('TypeAheadSelectReducer', () =>
|
||
testReducerSnapshotWithFixtures(reducer, fixtures));
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/__tests__/TypeAheadSelectSelectors.test.js | ||
---|---|---|
import {
|
||
selectSelected,
|
||
selectOptions,
|
||
selectTypeAheadSelectExists,
|
||
} from '../TypeAheadSelectSelectors';
|
||
import { id, initialState, populatedState } from '../TypeAheadSelect.fixtures';
|
||
import { testSelectorsSnapshotWithFixtures } from '../../../../common/testHelpers';
|
||
|
||
describe('TypeAheadSelectSelectors', () => {
|
||
describe('with empty state', () => {
|
||
const fixtures = {
|
||
'returns an nothing': () => selectSelected(initialState, id),
|
||
'returns an empty array of options': () =>
|
||
selectOptions(initialState, id),
|
||
'returns false': () => selectTypeAheadSelectExists(initialState, id),
|
||
};
|
||
|
||
testSelectorsSnapshotWithFixtures(fixtures);
|
||
});
|
||
|
||
describe('with state', () => {
|
||
const fixtures = {
|
||
'returns selections array from state': () =>
|
||
selectSelected(populatedState, id),
|
||
'returns options array from state': () =>
|
||
selectOptions(populatedState, id),
|
||
'returns true': () => selectTypeAheadSelectExists(populatedState, id),
|
||
};
|
||
|
||
testSelectorsSnapshotWithFixtures(fixtures);
|
||
});
|
||
});
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/__tests__/__snapshots__/TypeAheadSelectActions.test.js.snap | ||
---|---|---|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||
|
||
exports[`TypeAheadSelectActions initializes defaults 1`] = `
|
||
Object {
|
||
"payload": Object {
|
||
"id": "test_type_ahead_select",
|
||
"options": Array [
|
||
"option1",
|
||
"option2",
|
||
],
|
||
"selected": Array [
|
||
"option2",
|
||
],
|
||
},
|
||
"type": "TYPEAHEAD_INIT",
|
||
}
|
||
`;
|
||
|
||
exports[`TypeAheadSelectActions updates options 1`] = `
|
||
Object {
|
||
"payload": Object {
|
||
"id": "test_type_ahead_select",
|
||
"options": Array [
|
||
"option1",
|
||
"option2",
|
||
],
|
||
},
|
||
"type": "TYPEAHEAD_UPDATE_OPTIONS",
|
||
}
|
||
`;
|
||
|
||
exports[`TypeAheadSelectActions updates selections 1`] = `
|
||
Object {
|
||
"payload": Object {
|
||
"id": "test_type_ahead_select",
|
||
"selected": Array [
|
||
"option2",
|
||
],
|
||
},
|
||
"type": "TYPEAHEAD_UPDATE_SELECTED",
|
||
}
|
||
`;
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/__tests__/__snapshots__/TypeAheadSelectReducer.test.js.snap | ||
---|---|---|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||
|
||
exports[`TypeAheadSelectReducer initial state 1`] = `Object {}`;
|
||
|
||
exports[`TypeAheadSelectReducer initiates defaults 1`] = `
|
||
Object {
|
||
"test_type_ahead_select": Object {
|
||
"options": Array [
|
||
"option1",
|
||
"option2",
|
||
],
|
||
"selected": Array [
|
||
"option2",
|
||
],
|
||
},
|
||
}
|
||
`;
|
||
|
||
exports[`TypeAheadSelectReducer updates options 1`] = `
|
||
Object {
|
||
"test_type_ahead_select": Object {
|
||
"options": Array [
|
||
"option1",
|
||
"option2",
|
||
],
|
||
},
|
||
}
|
||
`;
|
||
|
||
exports[`TypeAheadSelectReducer updates selections 1`] = `
|
||
Object {
|
||
"test_type_ahead_select": Object {
|
||
"selected": Array [
|
||
"option2",
|
||
],
|
||
},
|
||
}
|
||
`;
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/__tests__/__snapshots__/TypeAheadSelectSelectors.test.js.snap | ||
---|---|---|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||
|
||
exports[`TypeAheadSelectSelectors with empty state returns an empty array of options 1`] = `Array []`;
|
||
|
||
exports[`TypeAheadSelectSelectors with empty state returns an nothing 1`] = `undefined`;
|
||
|
||
exports[`TypeAheadSelectSelectors with empty state returns false 1`] = `false`;
|
||
|
||
exports[`TypeAheadSelectSelectors with state returns options array from state 1`] = `
|
||
Array [
|
||
"option1",
|
||
"option2",
|
||
]
|
||
`;
|
||
|
||
exports[`TypeAheadSelectSelectors with state returns selections array from state 1`] = `
|
||
Array [
|
||
"option2",
|
||
]
|
||
`;
|
||
|
||
exports[`TypeAheadSelectSelectors with state returns true 1`] = `true`;
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/index.js | ||
---|---|---|
import React, { useEffect } from 'react';
|
||
import { useSelector, useDispatch } from 'react-redux';
|
||
import PropTypes from 'prop-types';
|
||
import { TypeAheadSelect } from 'patternfly-react';
|
||
import { initialUpdate, updateSelected } from './TypeAheadSelectActions';
|
||
import {
|
||
selectTypeAheadSelectExists,
|
||
selectOptions,
|
||
selectSelected,
|
||
} from './TypeAheadSelectSelectors';
|
||
import reducer from './TypeAheadSelectReducer';
|
||
|
||
const ConnectedTypeAheadSelect = ({
|
||
id,
|
||
options,
|
||
selected,
|
||
allowNew,
|
||
multiple,
|
||
placeholder,
|
||
defaultInputValue,
|
||
clearButton,
|
||
inputProps,
|
||
}) => {
|
||
const dispatch = useDispatch();
|
||
const exists = useSelector(state => selectTypeAheadSelectExists(state, id));
|
||
|
||
useEffect(() => {
|
||
if (!exists) {
|
||
dispatch(initialUpdate(options, selected, id));
|
||
}
|
||
}, [dispatch, exists, options, selected, id]);
|
||
|
||
const _selected = useSelector(state => selectSelected(state, id));
|
||
const _options = useSelector(state => selectOptions(state, id));
|
||
const onChange = items => dispatch(updateSelected(items, id));
|
||
|
||
return (
|
||
<TypeAheadSelect
|
||
id={id}
|
||
options={_options}
|
||
selected={_selected}
|
||
allowNew={allowNew}
|
||
multiple={multiple}
|
||
placeholder={placeholder}
|
||
defaultInputValue={defaultInputValue}
|
||
clearButton={clearButton}
|
||
inputProps={inputProps}
|
||
onChange={onChange}
|
||
/>
|
||
);
|
||
};
|
||
|
||
ConnectedTypeAheadSelect.propTypes = {
|
||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||
options: PropTypes.array,
|
||
selected: PropTypes.array,
|
||
allowNew: PropTypes.bool,
|
||
multiple: PropTypes.bool,
|
||
placeholder: PropTypes.string,
|
||
defaultInputValue: PropTypes.string,
|
||
clearButton: PropTypes.bool,
|
||
inputProps: PropTypes.object,
|
||
};
|
||
|
||
ConnectedTypeAheadSelect.defaultProps = {
|
||
options: [],
|
||
selected: [],
|
||
allowNew: false,
|
||
multiple: false,
|
||
placeholder: '',
|
||
defaultInputValue: '',
|
||
clearButton: false,
|
||
inputProps: {},
|
||
};
|
||
|
||
export default ConnectedTypeAheadSelect;
|
||
|
||
export const reducers = { typeAheadSelect: reducer };
|
webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/integration.test.js | ||
---|---|---|
import React from 'react';
|
||
import IntegrationTestHelper from '../../../common/IntegrationTestHelper';
|
||
import TypeAheadSelect, { reducers } from './';
|
||
import { id, props, options, selected } from './TypeAheadSelect.fixtures';
|
||
import { updateOptions, updateSelected } from './TypeAheadSelectActions';
|
||
|
||
describe('TypeAheadSelect integration test', () => {
|
||
it('flows', async () => {
|
||
const integrationTestHelper = new IntegrationTestHelper(reducers);
|
||
|
||
integrationTestHelper.takeStoreSnapshot('initial state');
|
||
|
||
const component = integrationTestHelper.mount(
|
||
<TypeAheadSelect {...props} />
|
||
);
|
||
|
||
const getProps = wrapper =>
|
||
wrapper
|
||
.find('Typeahead')
|
||
.first()
|
||
.props();
|
||
|
||
component.update();
|
||
|
||
expect(getProps(component).selected).toEqual(selected);
|
||
expect(getProps(component).options).toEqual(options);
|
||
|
||
integrationTestHelper.takeStoreSnapshot('after initialUpdate');
|
||
|
||
const newOptions = ['Walrus', 'Bear'];
|
||
const newSelections = ['Bear'];
|
||
|
||
integrationTestHelper.store.dispatch(updateOptions(newOptions, id));
|
||
integrationTestHelper.store.dispatch(updateSelected(newSelections, id));
|
||
component.update();
|
||
|
||
expect(getProps(component).selected).toEqual(newSelections);
|
||
expect(getProps(component).options).toEqual(newOptions);
|
||
|
||
integrationTestHelper.takeStoreSnapshot('updated options and selections');
|
||
});
|
||
});
|
webpack/assets/javascripts/react_app/components/componentRegistry.js | ||
---|---|---|
import LoginPage from './LoginPage';
|
||
import ExternalLogout from './ExternalLogout';
|
||
import Slot from './common/Slot';
|
||
import TypeAheadSelect from './common/TypeAheadSelect';
|
||
import DatePicker from './common/DateTimePicker/DatePicker';
|
||
import RedirectCancelButton from './common/RedirectCancelButton';
|
||
import SettingRecords from './SettingRecords';
|
||
... | ... | |
{ name: 'DiffModal', type: DiffModal },
|
||
{ name: 'ExternalLogout', type: ExternalLogout },
|
||
{ name: 'Slot', type: Slot },
|
||
{ name: 'TypeAheadSelect', type: TypeAheadSelect },
|
||
{ name: 'DatePicker', type: DatePicker },
|
||
{ name: 'RedirectCancelButton', type: RedirectCancelButton },
|
||
{ name: 'SettingRecords', type: SettingRecords },
|
webpack/assets/javascripts/react_app/redux/reducers/index.js | ||
---|---|---|
import { reducers as editorReducers } from '../../components/Editor';
|
||
import { reducers as templateGenerationReducers } from '../../components/TemplateGenerator';
|
||
import { reducers as fillReducers } from '../../components/common/Fill';
|
||
import { reducers as typeAheadSelectReducers } from '../../components/common/TypeAheadSelect';
|
||
import { reducers as auditsPageReducers } from '../../routes/Audits/AuditsPage';
|
||
import { reducers as intervalReducers } from '../middlewares/IntervalMiddleware';
|
||
import { reducers as bookmarksPF4Reducers } from '../../components/PF4/Bookmarks';
|
||
... | ... | |
...diffModalReducers,
|
||
...editorReducers,
|
||
...templateGenerationReducers,
|
||
...typeAheadSelectReducers,
|
||
...settingRecordsReducers,
|
||
...personalAccessTokensReducers,
|
||
...confirmModalReducers,
|
Also available in: Unified diff
Fixes #37280 - remove typeAheadSelect