diff --git a/pkg/rancher-components/package.json b/pkg/rancher-components/package.json index d31e73d2955..9e27659dc7c 100644 --- a/pkg/rancher-components/package.json +++ b/pkg/rancher-components/package.json @@ -2,7 +2,7 @@ "name": "@rancher/components", "repository": "git://github.com:rancher/dashboard.git", "license": "Apache-2.0", - "version": "0.1.3", + "version": "0.1.4", "module": "./main.js", "main": "./dist/@rancher/components.umd.min.js", "files": [ diff --git a/pkg/rancher-components/src/components/StringList/StringList.test.ts b/pkg/rancher-components/src/components/StringList/StringList.test.ts index 70b341bd41c..61d8008fcc8 100644 --- a/pkg/rancher-components/src/components/StringList/StringList.test.ts +++ b/pkg/rancher-components/src/components/StringList/StringList.test.ts @@ -398,6 +398,276 @@ describe('stringList.vue', () => { }); }); + describe('bulk delimiter', () => { + const delimiter = /;/; + + describe('add', () => { + const items: string[] = []; + + beforeEach(() => { + wrapper = mount(StringList, { + propsData: { + items, + bulkAdditionDelimiter: delimiter, + errorMessages: { duplicate: 'error, item is duplicate.' }, + } + }); + }); + + it('should split values if delimiter set', async() => { + const value = 'test;test1;test2'; + const result = ['test', 'test1', 'test2']; + + // activate create mode + await wrapper.setData({ isCreateItem: true }); + const inputField = wrapper.find('[data-testid="item-create"]'); + + await inputField.setValue(value); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const itemsResult = (wrapper.emitted('change') || [])[0][0]; + + expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result)); + }); + + it('should show warning if one of the values is a duplicate', async() => { + const value = 'test;test1;test2'; + + await wrapper.setProps({ items: ['test1'] }); + + // activate create mode + await wrapper.setData({ isCreateItem: true }); + const inputField = wrapper.find('[data-testid="item-create"]'); + + await inputField.setValue(value); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate; + + expect(isDuplicate).toBe(true); + }); + + it('should show a warning if the new values are all duplicates', async() => { + const value = 'test;test'; + + // activate create mode + await wrapper.setData({ isCreateItem: true }); + const inputField = wrapper.find('[data-testid="item-create"]'); + + await inputField.setValue(value); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate; + + expect(isDuplicate).toBe(true); + }); + + it('should not consider empty strings at the beginning', async() => { + const value = ';test;test1;test2'; + const result = ['test', 'test1', 'test2']; + + // activate create mode + await wrapper.setData({ isCreateItem: true }); + const inputField = wrapper.find('[data-testid="item-create"]'); + + await inputField.setValue(value); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const itemsResult = (wrapper.emitted('change') || [])[0][0]; + + expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result)); + }); + + it('should not consider empty strings in the middle', async() => { + const value = 'test;test1;;test2'; + const result = ['test', 'test1', 'test2']; + + // activate create mode + await wrapper.setData({ isCreateItem: true }); + const inputField = wrapper.find('[data-testid="item-create"]'); + + await inputField.setValue(value); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const itemsResult = (wrapper.emitted('change') || [])[0][0]; + + expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result)); + }); + + it('should not consider empty strings at the end', async() => { + const value = 'test;test1;test2;'; + const result = ['test', 'test1', 'test2']; + + // activate create mode + await wrapper.setData({ isCreateItem: true }); + const inputField = wrapper.find('[data-testid="item-create"]'); + + await inputField.setValue(value); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const itemsResult = (wrapper.emitted('change') || [])[0][0]; + + expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result)); + }); + }); + + describe('edit', () => { + const items = ['test1', 'test2']; + + beforeEach(() => { + wrapper = mount(StringList, { + propsData: { + items, + bulkAdditionDelimiter: delimiter, + errorMessages: { duplicate: 'error, item is duplicate.' }, + } + }); + }); + + it('should split values if delimiter set', async() => { + const newValue = 'test1;new;values'; + const result = ['test1', 'new', 'values', 'test2']; + + await wrapper.setData({ editedItem: items[0] }); + const inputField = wrapper.find('[data-testid^="item-edit"]'); + + await inputField.setValue(newValue); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const itemsResult = (wrapper.emitted('change') || [])[0][0]; + + expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result)); + }); + + it('should show warning if one of the values is a duplicate', async() => { + const newValue = 'test1;test2'; + + await wrapper.setData({ editedItem: items[0] }); + const inputField = wrapper.find('[data-testid^="item-edit"]'); + + await inputField.setValue(newValue); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate; + + expect(isDuplicate).toBe(true); + }); + + it('should show a warning if the new values are all duplicates', async() => { + const newValue = 'test;test'; + + await wrapper.setData({ editedItem: items[0] }); + const inputField = wrapper.find('[data-testid^="item-edit"]'); + + await inputField.setValue(newValue); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate; + + expect(isDuplicate).toBe(true); + }); + + it('should not consider empty strings at the beginning', async() => { + const newValue = ';test1;new;value'; + const result = ['test1', 'new', 'value', 'test2']; + + await wrapper.setData({ editedItem: items[0] }); + const inputField = wrapper.find('[data-testid^="item-edit"]'); + + await inputField.setValue(newValue); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const itemsResult = (wrapper.emitted('change') || [])[0][0]; + + expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result)); + }); + + it('should not consider empty strings in the middle 1', async() => { + const newValue = 'test1; ;new;value'; + const result = ['test1', 'new', 'value', 'test2']; + + await wrapper.setData({ editedItem: items[0] }); + const inputField = wrapper.find('[data-testid^="item-edit"]'); + + await inputField.setValue(newValue); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const itemsResult = (wrapper.emitted('change') || [])[0][0]; + + expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result)); + }); + + it('should not consider empty strings in the middle 2', async() => { + const newValue = 'test1;;new;value'; + const result = ['test1', 'new', 'value', 'test2']; + + await wrapper.setData({ editedItem: items[0] }); + const inputField = wrapper.find('[data-testid^="item-edit"]'); + + await inputField.setValue(newValue); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const itemsResult = (wrapper.emitted('change') || [])[0][0]; + + expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result)); + }); + + it('should not consider empty strings at the end', async() => { + const newValue = 'test1;new;value;'; + const result = ['test1', 'new', 'value', 'test2']; + + await wrapper.setData({ editedItem: items[0] }); + const inputField = wrapper.find('[data-testid^="item-edit"]'); + + await inputField.setValue(newValue); + + // press enter + await inputField.trigger('keydown.enter'); + await wrapper.vm.$nextTick(); + + const itemsResult = (wrapper.emitted('change') || [])[0][0]; + + expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result)); + }); + }); + }); + describe('errors handling', () => { it('show duplicate warning icon when errorMessages is defined', async() => { const items = ['test']; diff --git a/pkg/rancher-components/src/components/StringList/StringList.vue b/pkg/rancher-components/src/components/StringList/StringList.vue index b915d59875c..a83c56d1730 100644 --- a/pkg/rancher-components/src/components/StringList/StringList.vue +++ b/pkg/rancher-components/src/components/StringList/StringList.vue @@ -82,6 +82,13 @@ export default Vue.extend({ return {} as ErrorMessages; }, }, + /** + * Enables bulk addition and defines the delimiter to split the input string. + */ + bulkAdditionDelimiter: { + type: RegExp, + default: null, + } }, data() { return { @@ -125,13 +132,9 @@ export default Vue.extend({ }, methods: { - onChange(value: string) { + onChange(value: string, index?: number) { this.value = value; - - const items = [ - ...this.items, - this.value - ]; + const items = this.addValueToItems(this.items, value, index); this.toggleError( 'duplicate', @@ -321,10 +324,7 @@ export default Vue.extend({ const value = this.value?.trim(); if (value) { - const items = [ - ...this.items, - value, - ]; + const items = this.addValueToItems(this.items, value); if (!hasDuplicatedStrings(items, this.caseSensitive)) { this.updateItems(items); @@ -343,12 +343,8 @@ export default Vue.extend({ const value = this.value?.trim(); if (value) { - const items = [...this.items]; - const index = findStringIndex(items, item, false); - - if (index !== -1) { - items[index] = value; - } + const index = findStringIndex(this.items, item, false); + const items = index !== -1 ? this.addValueToItems(this.items, value, index) : this.items; if (!hasDuplicatedStrings(items, this.caseSensitive)) { this.updateItems(items); @@ -360,6 +356,49 @@ export default Vue.extend({ } }, + /** + * Add a new or update an exiting item in the items list. + * + * @param items The current items list. + * @param value The new value to be added. + * @param index The list index of the item to be updated (optional). + * @returns Updated items list. + */ + addValueToItems(items: string[], value: string, index?: number): string[] { + const updatedItems = [...items]; + + // Add new item + if (index === undefined) { + if (this.bulkAdditionDelimiter && value.search(this.bulkAdditionDelimiter) >= 0) { + updatedItems.push(...this.splitBulkValue(value)); + } else { + updatedItems.push(value); + } + } else { // Update existing item + if (this.bulkAdditionDelimiter && value.search(this.bulkAdditionDelimiter) >= 0) { + updatedItems.splice(index, 1, ...this.splitBulkValue(value)); + } else { + updatedItems[index] = value; + } + } + + return updatedItems; + }, + + /** + * Split the value by the defined delimiter and remove empty strings. + * + * @param value The value to be split. + * @returns Array containing split values. + */ + splitBulkValue(value: string): string[] { + return value + .split(this.bulkAdditionDelimiter) + .filter((item) => { + return item.trim().length > 0; + }); + }, + /** * Remove an item from items list */ @@ -393,7 +432,7 @@ export default Vue.extend({ @dblclick="onClickEmptyBody()" >
diff --git a/storybook/stories/components/StringList.stories.mdx b/storybook/stories/components/StringList.stories.mdx index bed4a083e35..c7940f3401e 100644 --- a/storybook/stories/components/StringList.stories.mdx +++ b/storybook/stories/components/StringList.stories.mdx @@ -3,8 +3,8 @@ import { Canvas, Meta, Story, ArgsTable, Source } from '@storybook/addon-docs'; import StringList from '@/pkg/rancher-components/src/components/StringList/StringList'; import { useArgs } from '@storybook/client-api'; -