Merge pull request #9709 from s0nea/string-list-bulk-addition

Add bulk addition to string list component
This commit is contained in:
Phillip Rak 2023-10-25 14:26:57 -07:00 committed by GitHub
commit 65a5ad308d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 331 additions and 21 deletions

View File

@ -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": [

View File

@ -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'];

View File

@ -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()"
>
<div
v-for="item in items"
v-for="(item, index) in items"
:key="item"
:ref="item"
:class="{
@ -421,7 +460,7 @@ export default Vue.extend({
:data-testid="`item-edit-${item}`"
class="edit-input static"
:value="value != null ? value : item"
@input="onChange($event)"
@input="onChange($event, index)"
@blur.prevent="updateItem(item)"
@keydown.native.enter="updateItem(item, !errors.duplicate)"
/>

View File

@ -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';
<Meta
title="Components/StringList"
<Meta
title="Components/StringList"
component={StringList}
argTypes={{
actionsPosition: {
@ -57,6 +57,7 @@ It can be used to handle a list of strings.
- An item can be edited by double click on it.
- In case of the item name is already in the list, an error message is displayed on the bottom of the box.
- Select an item and then click on minus button to remove it from the list.
- If a bulk addition delimiter is set, the input value will be split into individual entries.
<br/>