fix(ui): where builder issues (#6478)
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
|
||||
export type RenderCustomComponentProps = {
|
||||
CustomComponent?: React.ComponentType<any>
|
||||
DefaultComponent: React.ComponentType<any>
|
||||
componentProps?: Record<string, any>
|
||||
}
|
||||
|
||||
export const RenderCustomClientComponent: React.FC<RenderCustomComponentProps> = (props) => {
|
||||
const { CustomComponent, DefaultComponent, componentProps = {} } = props
|
||||
|
||||
if (CustomComponent) {
|
||||
return <CustomComponent {...componentProps} />
|
||||
}
|
||||
|
||||
if (DefaultComponent) {
|
||||
return <DefaultComponent {...componentProps} />
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -14,6 +14,11 @@ export type RenderCustomComponentProps = {
|
||||
serverOnlyProps?: ServerProps
|
||||
}
|
||||
|
||||
/**
|
||||
* If you are passing dynamic props or function props to this component,
|
||||
* you should instead use the <RenderCustomClientComponent/>
|
||||
*/
|
||||
|
||||
export const RenderCustomComponent: React.FC<RenderCustomComponentProps> = (props) => {
|
||||
const { CustomComponent, DefaultComponent, componentProps, serverOnlyProps } = props
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export type Props = {
|
||||
}) => void
|
||||
}
|
||||
|
||||
import { RenderCustomComponent } from '../../../elements/RenderCustomComponent/index.js'
|
||||
import { RenderCustomClientComponent } from '../../../elements/RenderCustomClientComponent/index.js'
|
||||
import { useDebounce } from '../../../hooks/useDebounce.js'
|
||||
import { Button } from '../../Button/index.js'
|
||||
import { ReactSelect } from '../../ReactSelect/index.js'
|
||||
@@ -148,7 +148,7 @@ export const Condition: React.FC<Props> = (props) => {
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__value`}>
|
||||
<RenderCustomComponent
|
||||
<RenderCustomClientComponent
|
||||
CustomComponent={internalField?.props?.admin?.components?.Filter}
|
||||
DefaultComponent={ValueComponent}
|
||||
componentProps={{
|
||||
@@ -190,8 +190,8 @@ export const Condition: React.FC<Props> = (props) => {
|
||||
iconStyle="with-border"
|
||||
onClick={() =>
|
||||
addCondition({
|
||||
andIndex,
|
||||
fieldName,
|
||||
andIndex: andIndex + 1,
|
||||
fieldName: fields[0].value,
|
||||
orIndex,
|
||||
relation: 'and',
|
||||
})
|
||||
|
||||
@@ -131,7 +131,6 @@ export const WhereBuilder: React.FC<WhereBuilderProps> = (props) => {
|
||||
setConditions((prevConditions) => {
|
||||
const newConditions = [...prevConditions]
|
||||
newConditions[orIndex].and.splice(andIndex, 1)
|
||||
|
||||
if (newConditions[orIndex].and.length === 0) {
|
||||
newConditions.splice(orIndex, 1)
|
||||
}
|
||||
@@ -156,44 +155,48 @@ export const WhereBuilder: React.FC<WhereBuilderProps> = (props) => {
|
||||
{t('general:filterWhere', { label: getTranslation(collectionPluralLabel, i18n) })}
|
||||
</div>
|
||||
<ul className={`${baseClass}__or-filters`}>
|
||||
{conditions.map((or, orIndex) => (
|
||||
<li key={orIndex}>
|
||||
{orIndex !== 0 && <div className={`${baseClass}__label`}>{t('general:or')}</div>}
|
||||
<ul className={`${baseClass}__and-filters`}>
|
||||
{Array.isArray(or?.and) &&
|
||||
or.and.map((_, andIndex) => {
|
||||
const initialFieldName = Object.keys(conditions[orIndex].and[andIndex])[0]
|
||||
const initialOperator =
|
||||
Object.keys(
|
||||
conditions[orIndex].and[andIndex]?.[initialFieldName] || {},
|
||||
)?.[0] || undefined
|
||||
const initialValue =
|
||||
conditions[orIndex].and[andIndex]?.[initialFieldName]?.[initialOperator] ||
|
||||
''
|
||||
{conditions.map((or, orIndex) => {
|
||||
const compoundOrKey = `${orIndex}_${Array.isArray(or?.and) ? or.and.length : ''}`
|
||||
|
||||
return (
|
||||
<li key={andIndex}>
|
||||
{andIndex !== 0 && (
|
||||
<div className={`${baseClass}__label`}>{t('general:and')}</div>
|
||||
)}
|
||||
<Condition
|
||||
addCondition={addCondition}
|
||||
andIndex={andIndex}
|
||||
fieldName={initialFieldName}
|
||||
fields={reducedFields}
|
||||
initialValue={initialValue}
|
||||
key={andIndex}
|
||||
operator={initialOperator}
|
||||
orIndex={orIndex}
|
||||
removeCondition={removeCondition}
|
||||
updateCondition={updateCondition}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
return (
|
||||
<li key={compoundOrKey}>
|
||||
{orIndex !== 0 && <div className={`${baseClass}__label`}>{t('general:or')}</div>}
|
||||
<ul className={`${baseClass}__and-filters`}>
|
||||
{Array.isArray(or?.and) &&
|
||||
or.and.map((_, andIndex) => {
|
||||
const initialFieldName = Object.keys(conditions[orIndex].and[andIndex])[0]
|
||||
const initialOperator =
|
||||
Object.keys(
|
||||
conditions[orIndex].and[andIndex]?.[initialFieldName] || {},
|
||||
)?.[0] || undefined
|
||||
const initialValue =
|
||||
conditions[orIndex].and[andIndex]?.[initialFieldName]?.[
|
||||
initialOperator
|
||||
] || ''
|
||||
|
||||
return (
|
||||
<li key={andIndex}>
|
||||
{andIndex !== 0 && (
|
||||
<div className={`${baseClass}__label`}>{t('general:and')}</div>
|
||||
)}
|
||||
<Condition
|
||||
addCondition={addCondition}
|
||||
andIndex={andIndex}
|
||||
fieldName={initialFieldName}
|
||||
fields={reducedFields}
|
||||
initialValue={initialValue}
|
||||
operator={initialOperator}
|
||||
orIndex={orIndex}
|
||||
removeCondition={removeCondition}
|
||||
updateCondition={updateCondition}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
<Button
|
||||
buttonStyle="icon-label"
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import path from 'path'
|
||||
import { wait } from 'payload/utilities'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { PayloadTestSDK } from '../../../helpers/sdk/index.js'
|
||||
@@ -205,4 +206,139 @@ describe('Text', () => {
|
||||
await expect(field.locator('.rs__value-container')).toContainText(input)
|
||||
await expect(field.locator('.rs__value-container')).toContainText(furtherInput)
|
||||
})
|
||||
|
||||
test('should reset filter conditions when adding additional filters', async () => {
|
||||
await page.goto(url.list)
|
||||
|
||||
// open the first filter options
|
||||
await page.locator('.list-controls__toggle-where').click()
|
||||
await expect(page.locator('.list-controls__where.rah-static--height-auto')).toBeVisible()
|
||||
await page.locator('.where-builder__add-first-filter').click()
|
||||
|
||||
const firstInitialField = page.locator('.condition__field')
|
||||
const firstOperatorField = page.locator('.condition__operator')
|
||||
const firstValueField = page.locator('.condition__value >> input')
|
||||
|
||||
await firstInitialField.click()
|
||||
const firstInitialFieldOptions = firstInitialField.locator('.rs__option')
|
||||
await firstInitialFieldOptions.locator('text=text').first().click()
|
||||
await expect(firstInitialField.locator('.rs__single-value')).toContainText('Text')
|
||||
|
||||
await firstOperatorField.click()
|
||||
await firstOperatorField.locator('.rs__option').locator('text=equals').click()
|
||||
|
||||
await firstValueField.fill('hello')
|
||||
|
||||
await wait(500)
|
||||
|
||||
await expect(firstValueField).toHaveValue('hello')
|
||||
|
||||
// open the second filter options
|
||||
await page.locator('.condition__actions-add').click()
|
||||
|
||||
const secondLi = page.locator('.where-builder__and-filters li:nth-child(2)')
|
||||
|
||||
await expect(secondLi).toBeVisible()
|
||||
|
||||
const secondInitialField = secondLi.locator('.condition__field')
|
||||
const secondOperatorField = secondLi.locator('.condition__operator >> input')
|
||||
const secondValueField = secondLi.locator('.condition__value >> input')
|
||||
|
||||
await expect(secondInitialField.locator('.rs__single-value')).toContainText('Text')
|
||||
await expect(secondOperatorField).toHaveValue('')
|
||||
await expect(secondValueField).toHaveValue('')
|
||||
})
|
||||
|
||||
test('should not re-render page upon typing in a value in the filter value field', async () => {
|
||||
await page.goto(url.list)
|
||||
|
||||
// open the first filter options
|
||||
await page.locator('.list-controls__toggle-where').click()
|
||||
await expect(page.locator('.list-controls__where.rah-static--height-auto')).toBeVisible()
|
||||
await page.locator('.where-builder__add-first-filter').click()
|
||||
|
||||
const firstInitialField = page.locator('.condition__field')
|
||||
const firstOperatorField = page.locator('.condition__operator')
|
||||
const firstValueField = page.locator('.condition__value >> input')
|
||||
|
||||
await firstInitialField.click()
|
||||
const firstInitialFieldOptions = firstInitialField.locator('.rs__option')
|
||||
await firstInitialFieldOptions.locator('text=text').first().click()
|
||||
await expect(firstInitialField.locator('.rs__single-value')).toContainText('Text')
|
||||
|
||||
await firstOperatorField.click()
|
||||
await firstOperatorField.locator('.rs__option').locator('text=equals').click()
|
||||
|
||||
// Type into the input field instead of filling it
|
||||
await firstValueField.click()
|
||||
await firstValueField.type('hello', { delay: 100 }) // Add a delay to simulate typing speed
|
||||
|
||||
// Wait for a short period to see if the input loses focus
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// Check if the input still has the correct value
|
||||
await expect(firstValueField).toHaveValue('hello')
|
||||
})
|
||||
|
||||
test('should still show second filter if two filters exist and first filter is removed', async () => {
|
||||
await page.goto(url.list)
|
||||
|
||||
// open the first filter options
|
||||
await page.locator('.list-controls__toggle-where').click()
|
||||
await expect(page.locator('.list-controls__where.rah-static--height-auto')).toBeVisible()
|
||||
await page.locator('.where-builder__add-first-filter').click()
|
||||
|
||||
const firstInitialField = page.locator('.condition__field')
|
||||
const firstOperatorField = page.locator('.condition__operator')
|
||||
const firstValueField = page.locator('.condition__value >> input')
|
||||
|
||||
await firstInitialField.click()
|
||||
const firstInitialFieldOptions = firstInitialField.locator('.rs__option')
|
||||
await firstInitialFieldOptions.locator('text=text').first().click()
|
||||
await expect(firstInitialField.locator('.rs__single-value')).toContainText('Text')
|
||||
|
||||
await firstOperatorField.click()
|
||||
await firstOperatorField.locator('.rs__option').locator('text=equals').click()
|
||||
|
||||
await firstValueField.fill('hello')
|
||||
|
||||
await wait(500)
|
||||
|
||||
await expect(firstValueField).toHaveValue('hello')
|
||||
|
||||
// open the second filter options
|
||||
await page.locator('.condition__actions-add').click()
|
||||
|
||||
const secondLi = page.locator('.where-builder__and-filters li:nth-child(2)')
|
||||
|
||||
await expect(secondLi).toBeVisible()
|
||||
|
||||
const secondInitialField = secondLi.locator('.condition__field')
|
||||
const secondOperatorField = secondLi.locator('.condition__operator')
|
||||
const secondValueField = secondLi.locator('.condition__value >> input')
|
||||
|
||||
await secondInitialField.click()
|
||||
const secondInitialFieldOptions = secondInitialField.locator('.rs__option')
|
||||
await secondInitialFieldOptions.locator('text=text').first().click()
|
||||
await expect(secondInitialField.locator('.rs__single-value')).toContainText('Text')
|
||||
|
||||
await secondOperatorField.click()
|
||||
await secondOperatorField.locator('.rs__option').locator('text=equals').click()
|
||||
|
||||
await secondValueField.fill('world')
|
||||
await expect(secondValueField).toHaveValue('world')
|
||||
|
||||
await wait(500)
|
||||
|
||||
const firstLi = page.locator('.where-builder__and-filters li:nth-child(1)')
|
||||
const removeButton = firstLi.locator('.condition__actions-remove')
|
||||
|
||||
// remove first filter
|
||||
await removeButton.click()
|
||||
|
||||
const filterListItems = page.locator('.where-builder__and-filters li')
|
||||
await expect(filterListItems).toHaveCount(1)
|
||||
|
||||
await expect(firstValueField).toHaveValue('world')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user