diff --git a/components/src/index.js b/components/src/index.js index 89534fc6..08d3550e 100644 --- a/components/src/index.js +++ b/components/src/index.js @@ -9,6 +9,7 @@ export { default as Icon } from '~widgets/icon/widget.vue'; export { default as View } from '~widgets/view/widget.vue'; export { default as Navigation } from '~widgets/navigation/widget.vue'; export { default as Status } from '~widgets/status/widget.vue'; +export { default as Select } from '~widgets/select/widget.vue'; export { default as Textfield } from '~widgets/textfield/widget.vue'; export { default as Table } from '~widgets/table/widget.vue'; export { default as ComplexTable } from './widgets/complexTable/widget.vue'; diff --git a/components/src/stories/Select.stories.js b/components/src/stories/Select.stories.js new file mode 100644 index 00000000..077940b1 --- /dev/null +++ b/components/src/stories/Select.stories.js @@ -0,0 +1,80 @@ +import { ref } from 'vue'; + +import Select from '~widgets/select/widget.vue'; +import registerWidget from '~core/registerWidget'; + +registerWidget('ui-select', Select); + +export const Basic = { + name: 'Basic options', + render: (args) => ({ + setup() { + return { args }; + }, + template: '', + }), + + args: { + label: 'Label text', + modelValue: '', + hint: 'Some hint text', + options: ['foo', 'bar', 'baz'], + }, +}; + +export const Object = { + name: 'Array of objects in options', + render: Basic.render, + args: { + ...Basic.args, + propValue: 'id', + propText: 'name', + options: [ + { id: 'OBJ-123', name: 'The first object' }, + { id: 'OBJ-456', name: 'The second object' }, + { id: 'OBJ-789', name: 'The third object' }, + ], + }, +}; + +export const Events = { + name: 'Using v-model', + render: (args) => ({ + setup() { + const selectedItem = ref(''); + const setSelectedItem = (event) => { + selectedItem.value = event.detail[0]; + }; + + return { args, selectedItem, setSelectedItem }; + }, + template: ` +
+

The current selected value is: {{ selectedItem }}

+ +
+ `, + }), + args: Basic.args, +}; + +export default { + title: 'Components/Select', + component: Select, + parameters: { + layout: 'centered', + }, + argTypes: { + label: 'text', + modelValue: 'text', + hint: 'text', + propValue: 'text', + propText: 'text', + options: { control: 'array' }, + }, +}; diff --git a/components/src/widgets/select/widget.spec.js b/components/src/widgets/select/widget.spec.js new file mode 100644 index 00000000..652e08b4 --- /dev/null +++ b/components/src/widgets/select/widget.spec.js @@ -0,0 +1,74 @@ +import { mount } from '@vue/test-utils'; +import Select from './widget.vue'; + +describe('Select', () => { + let wrapper; + + beforeEach(() => { + wrapper = mount(Select, { + props: { + modelValue: '', + options: ['foo', 'bar', 'baz'], + label: 'My select', + hint: 'Some random hint', + }, + }); + }); + + describe('render', () => { + it('renders the base component', () => { + expect(wrapper.get('.select-input__label').text()).toEqual('My select'); + expect(wrapper.get('.select-input__hint').text()).toEqual('Some random hint'); + expect(wrapper.get('.select-input__no-selection').text()).toEqual('—'); + }); + + it('renders a simple array of text elements', () => { + const menuOptions = wrapper.findAll('.select-input__option'); + + expect(menuOptions.length).toEqual(3); + expect(menuOptions[0].text()).toEqual('foo'); + expect(menuOptions[1].text()).toEqual('bar'); + expect(menuOptions[2].text()).toEqual('baz'); + }); + + it('renders a complex array of objects', async () => { + await wrapper.setProps({ + options: [ + { id: '123', external_id: 'ext-123', name: 'Foo' }, + { id: '456', external_id: 'ext-456', name: 'Bar' }, + { id: '789', external_id: 'ext-789', name: 'Baz' }, + ], + propValue: 'external_id', + propText: 'name', + }); + + const menuOptions = wrapper.findAll('.select-input__option'); + + expect(menuOptions.length).toEqual(3); + expect(menuOptions[0].text()).toEqual('Foo'); + expect(menuOptions[1].text()).toEqual('Bar'); + expect(menuOptions[2].text()).toEqual('Baz'); + }); + }); + + describe('events', () => { + describe('when an item is selected', () => { + beforeEach(async () => { + await wrapper.findAll('.select-input__option')[1].trigger('click'); + }); + + it('renders the selected item', () => { + expect(wrapper.get('.select-input__option_selected').text()).toEqual('bar'); + expect(wrapper.get('.select-input__selected').text()).toEqual('bar'); + }); + + it('emits the update:modelValue event with the selected value', () => { + expect(wrapper.emitted('update:modelValue')[0]).toEqual(['bar']); + }); + + it('emits the valueChange event with the selected value', () => { + expect(wrapper.emitted('valueChange')[0]).toEqual(['bar']); + }); + }); + }); +}); diff --git a/components/src/widgets/select/widget.vue b/components/src/widgets/select/widget.vue new file mode 100644 index 00000000..b6ef65bd --- /dev/null +++ b/components/src/widgets/select/widget.vue @@ -0,0 +1,175 @@ + + + + +