Fluent UI TagPicker How add the resultsFooter in the pickersuggestionprops - fluent-ui

Trying to get the Footer option in the TagPicker, but it does not seems to work, how to get the Footer in TagPicker
const pickerSuggestionsProps = {
suggestionsHeaderText: getLocale(props.pageactions, "suggestedResults"),
noResultsFoundText: getLocale(props.pageactions, "resultsNotfound"),
isResultsFooterVisible : true,
resultsFooter: () => <div>Hello world</div>
}
<TagPicker
removeButtonAriaLabel="Remove"
selectionAriaLabel="Selected Lookups"
onResolveSuggestions={filterSuggestedTags}
getTextFromItem={getTextFromItem}
pickerSuggestionsProps={pickerSuggestionsProps}
itemLimit={1}
inputProps={{
id: "id",
}}
onEmptyInputFocus={(tagList) => onEmptyInputFocus('', tagList, testTags)}
defaultSelectedItems={selectedItems}
onChange={onTagsChange}
selectedItems = {selectedItems}
onItemSelected={onItemSelected}
onRenderSuggestionsItem={onRenderSuggestionsItem}
/>

Related

Using vitest and testing-library is there a way to segregate component renders on a test by test basis?

I have a simple list component written in Vue3 that I am using to learn how to write automated test with Vitest and testing-library. However every test method seems to be rendered together, causing my getByText calls to throw the error TestingLibraryElementError: Found multiple elements with the text: foo.
This is the test I have written:
import { describe, it, expect, test } from 'vitest'
import { render, screen, fireEvent } from '#testing-library/vue'
import TmpList from '../ui/TmpList.vue'
const listItems = ['foo', 'bar']
describe('TmpList', () => {
// Test item-content slot rendering
test('renders item-content slot', () => {
const slotTemplate = `
<template v-slot:item-content="{ item }">
<div> {{ item }} </div>
</template>`;
render(TmpList, { props: { listItems }, slots: { 'item-content': slotTemplate } });
listItems.forEach(li => {
expect(screen.getByText(li)).toBeTruthy();
})
})
// Test list item interaction
test('should select item when clicked and is selectable', async () => {
const slotTemplate = `
<template v-slot:item-content="{ item }">
<div> {{ item }} </div>
</template>`;
render(TmpList, { props: { listItems, selectable: true }, slots: { 'item-content': slotTemplate } });
const firstItem = screen.getByText(listItems[0]);
await fireEvent.click(firstItem);
expect(firstItem.classList).toContain('selected-item')
})
})
The component:
<template>
<ul>
<li v-for="(item, index) in listItems" :key="`list-item-${index}`" #click="onItemClick(index)"
class="rounded mx-2" :class="{
'selected-item bg-secondary-600/20 text-secondary':
selectedIndex == index,
'hover:bg-zinc-200/30': selectable,
}">
<slot name="item-content" :item="item"></slot>
</li>
</ul>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
export interface Props {
listItems: any[];
selectable?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
selectable: false,
});
const selectedIndex = ref<number>(-1);
const onItemClick = (index: number) => {
if (props.selectable) {
selectedIndex.value = index;
}
};
</script>
This is the full error I get in the terminal:
TestingLibraryElementError: Found multiple elements with the text: foo
Here are the matching elements:
Ignored nodes: comments, script, style
<div>
foo
</div>
Ignored nodes: comments, script, style
<div>
foo
</div>
(If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)).
Ignored nodes: comments, script, style
<body>
<div>
<ul
data-v-96593be0=""
>
<li
class="rounded mx-2"
data-v-96593be0=""
>
<div>
foo
</div>
</li>
<li
class="rounded mx-2"
data-v-96593be0=""
>
<div>
bar
</div>
</li>
</ul>
</div>
<div>
<ul
data-v-96593be0=""
>
<li
class="rounded mx-2 hover:bg-zinc-200/30"
data-v-96593be0=""
>
<div>
foo
</div>
</li>
<li
class="rounded mx-2 hover:bg-zinc-200/30"
data-v-96593be0=""
>
<div>
bar
</div>
</li>
</ul>
</div>
</body>
❯ Object.getElementError node_modules/#testing-library/dom/dist/config.js:37:19
❯ getElementError node_modules/#testing-library/dom/dist/query-helpers.js:20:35
❯ getMultipleElementsFoundError node_modules/#testing-library/dom/dist/query-helpers.js:23:10
❯ node_modules/#testing-library/dom/dist/query-helpers.js:55:13
❯ node_modules/#testing-library/dom/dist/query-helpers.js:95:19
❯ src/components/__tests__/SUList.spec.ts:54:33
52|
53| render(TmpList, { props: { listItems, selectable: true }, slots: { 'item-content': slotTemplate } });
54| const firstItem = screen.getByText(listItems[0]);
| ^
55| await fireEvent.click(firstItem);
56| expect(firstItem.classList).toContain('selected-item')
I know I could use the getAllByText method to query multiple items, but in this test I am expecting only one element to be found. The duplication is related to the rendering in the test, not an issue with the actual component.
Am I doing something wrong when writing the tests? Is there a way to ensure that each render will be executend independetly of renders from other tests?
Every render() returns #testing-library's methods (query* /get* /find* ) scoped to the template being rendered.
In other words, they normally require a container parameter, but when returned by render, the container is already set to that particular render's DOM:
it('should select on click', async () => {
const { getByText } = render(TmpList, {
props: { listItems, selectable: true },
slots: { 'item-content': slotTemplate },
})
const firstItem = getByText(listItems[0])
expect(firstItem).not.toHaveClass('selected-item')
await fireEvent.click(firstItem)
expect(firstItem).toHaveClass('selected-item')
})
Notes:
fireEvent is no longer returning a promise in latest versions of #testing-library. If, in the version you're using, still returns a promise, keep the async - only true for #testing-library/react.
you want to get to a point where you no longer need to import screen in your test suite
If you find yourself writing the same selector or the same render parameters multiple times, it might make sense to write a renderComponent helper at the top of your test suite:
describe(`<ListItems />`, () => {
// define TmpList, listItems, slotTemplate
const defaults = {
props: { listItems, selectable: true },
slots: { 'item-content': slotTemplate },
}
const renderComponent = (overrides = {}) => {
// rendered test layout
const rtl = render(TmpList, {
...defaults,
...overrides
})
return {
...rtl,
getFirstItem: () => rtl.getByText(listItems[0]),
}
}
it('should select on click', async () => {
const { getFirstItem } = renderComponent()
expect(getFirstItem()).not.toHaveClass('selected-item')
await fireEvent.click(getFirstItem())
expect(getFirstItem()).toHaveClass('selected-item')
})
it('does something else with different props', () => {
const { getFirstItem } = renderComponent({
props: /* override defaults.props */
})
// expect(getFirstItem()).toBeOhSoSpecial('sigh...')
})
})
Note I'm spreading rtl in the returned value of renderComponent(), so all the get*/find*/query* methods are still available, for the one-off usage, not worth writing a getter for.

How to create a search filter on Vue.js when you use map

I tried to do a search in vue but it didn't work.
I tried to make a computed function and every time if I have a word in the search to filter by that word of the product, but I don't understand why it doesn't work
My products
async getProducts() {
this.$store.dispatch("actions/setLoading", true);
const products = (await ProductService.getProducts()).data.products;
this.dataSource = products.map((product, key) => {
return {
key: key + 1,
image_url: (
<div class="record-img align-center-v">
<img
src={product.image_url}
alt={product.product_name}
width="100"
/>
</div>
),
product_name: (<a href={product.product_url}>{product.product_name}</a>),
price: product.price,
price_with_discount: product.price_with_discount,
categories: product.categories,
sku: product.sku,
quantity: product.quantity,
currency: product.currency,
action: (
<div class="table-actions">
asds
<sdFeatherIcons type="trash-2" size={14} /> Test
</div>
)
}
});
this.$store.dispatch("actions/setLoading", false);
}
my filter function
watch: {
search() {
return this.dataSource.filter(product => product.product_name.toLowerCase().includes(searchText.value.toLowerCase()))
}
}
<a-table
:rowSelection="rowSelection"
:pagination="{ pageSize: 10, showSizeChanger: true }"
:dataSource="dataSource"
:columns="columns"
/>
functions that are watchers behave like methods, not like computed functions.
you can write a computed property -
computed:{
computedDataSource(){
return this.dataSource.filter(product => product.product_name.toLowerCase()
.includes(searchText.value.toLowerCase()))
}
html -
<a-table :rowSelection="rowSelection"
:pagination="{ pageSize: 10, showSizeChanger: true }"
:dataSource="computedDataSource"
:columns="columns"/>

How to update input defaultValue in React child component when props change?

I can pass props to the child component, but the input value doesn't change, when I update props (e.g. I send down "second" instead of "first", I can console.log "second" but the input value remains "first".) What could be the problem?
Code example:
// in parent component
const ParentComp = () => {
const [showEdit, setShowEdit] = useState(false);
const [currentElement, setCurrentElement] = useState('');
const myList = [{ label: 'first'}, {label: 'second'}]
const editElement = (el) => {
setShowEdit(true);
setCurrentElement(el);
}
return (
<div>
{myList.map((el, i) => (
<span key={i} onClick={() => editElement(el)}>
{el.label}
</span>
))}
{showEdit && (
<ChildComponent elData={currentElement} />
)}
</div>
)}
// in child component
const ChildComponent = ({ elData }) => {
const [testInput, setTestInput] = useState(elData.label)
return (
<input
onChange={(e) => setTestInput(e.target?.value)}
defaultValue={elData.label}
/>
)
}
I found a working solution but I'm not quite sure how it works.
// old code
<input
onChange={(e) => setTestInput(e.target?.value)}
defaultValue={elData.label}
/>
// new code
<input
onChange={(e) => setTestInput(e.target?.value)}
value={testInput || ''}
/>
Can anyone explain the difference?

Change view by using computed property

I have component of ViewControls, where have switchControls with which I can get the index of the clicked button. For example, i clicked first btn, and snippetValue will be 0, and so on.
<template>
<SwitchControls v-model="snippetValue">
<button>
<i class="icon list" />
</button>
<button>
<i class="icon grid" />
</button>
</SwitchControls>
</template>
Question, how can i bind to a snippetValue (0 - 'list', 1 - 'grid') and emit params
emits: [ "update:modelValue" ],
setup (props, { emit }) {
const snippetValue = ref(0)
const currentValue = computed({
get: () => props.modelValue,
set: (val: number) => {
if (val === 0) {
emit("update:modelValue", "list")
}
else {
emit("update:modelValue", "grid")
}
},
})
},
parent.vue
<ViewControls v-model="snippetVariant"/>
Try to bind the v-model directive from SwitchControls directly with computed property currentValue :
<template>
<SwitchControls v-model="currentValue">
<button>
<i class="icon list" />
</button>
<button>
<i class="icon grid" />
</button>
</SwitchControls>
</template>
emits: [ "update:modelValue" ],
setup (props, { emit }) {
const currentValue = computed({
get: () => props.modelValue,
set: (val: number) => {
emit("update:modelValue", val === 0 ? "list" : "grid")
},
})
},

Why the text isn’t updated in Vue3?

I’m trying to display a name dynamically, but I get the same name forEach element. What I’m trying to do is:
<template>
<div class="app__projects">
<div
class="app__projects__container"
v-for="project in visibleProjects"
:key="project.id"
:id="project.id"
>
<div class="app__projects__image">
<img
:src="project.imgUrl"
alt="Project Image"
width="570"
height="320"
loading="lazy"
/>
</div>
<div class="app__projects__content">
<h3>{{ project.name }}</h3>
<p>
{{ project.description }}
</p>
<a
:href="project.link"
target="_blank"
class="app__projects__content-btn"
>
{{ displayNameButton }}
</a>
<hr class="app__projects__content--spacer" />
</div>
</div>
<button
v-if="showMoreProjectsButton"
class="app__projects__showMoreButton"
#click="loadMoreProjects"
>
show more projects
</button>
</div>
</template>
On the I'm trying to display a name dynamically, and all the time the same name is displayed, but I want to display the name based on the computed property that I wrote below.
Here is the visibleProjects:
const visibleProjects = computed(() => {
return storeProjects.projects.slice(0, maxProjectsShown.value);
});
I’m trying to iterate through an array of objects from the store like:
const displayNameButton = computed(() => {
const isObjPresent = storeProjects.projects.find((o => o.wordpress === 'yes')).wordpress;
console.log(isObjPresent);
if (isObjPresent === 'yes') return 'See Website';
else if (!isObjPresent) return 'See code';
})
The array of objects from the store is:
import { defineStore } from 'pinia';
import { v4 as uuidv4 } from 'uuid';
export const useProjectsStore = defineStore({
id: 'projects',
state: () => {
return {
projects: [
{
id: uuidv4(),
imgUrl: lightImg,
name: 'use this',
description:
'track of this',
wordpress: false,
},
{
id: uuidv4(),
imgUrl: recogn,
name: 'deep lear',
description:
'I tried my best',
wordpress: ‘yes’,
},
...
{},
{},
],
};
},
});
So the problem is with your computed property. It will always return the same value because there is no input based on which the function can determine which string should it returns. Based on the code you already have I think you should write a method that will return desired string.
const displayNameButton = (project) => {
return (project.wordpress === 'yes') ? 'See Website' : 'See code';
})
and in the template
<a
:href="project.link"
target="_blank"
class="app__projects__content-btn"
>
{{ displayNameButton(project) }}
</a>
OR you can modify your visibleProjects:
const visibleProjects = computed(() => {
return storeProjects.projects.slice(0, maxProjectsShown.value).map((e) => {
const project = {...e};
project.wordpress = (project.wordpress === 'yes') ? 'See Website' : 'See code';
return project;
});
});
and in the template
<a
:href="project.link"
target="_blank"
class="app__projects__content-btn"
>
{{ project.wordpress }}
</a>