1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-01-11 18:13:52 +02:00

update condition to default if not valid for property (#4455)

This commit is contained in:
Scott Bishel 2023-01-17 12:03:22 -07:00 committed by GitHub
parent ff3a8d2096
commit c761ea7c88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 604 additions and 1 deletions

View File

@ -455,6 +455,474 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on delet
</div>
`;
exports[`components/viewHeader/filterEntry return filterEntry and click on different property type 1`] = `
<div>
<div
class="FilterEntry"
>
<div
aria-label="menuwrapper"
class="MenuWrapper override menuOpened"
role="button"
>
<button
class="Button"
type="button"
>
<span>
Status
</span>
</button>
<div
class="Menu noselect bottom "
>
<div
class="menu-contents"
>
<div
class="menu-options"
>
<div>
<div
aria-label="Title"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Title
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
<div>
<div
aria-label="Status"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Status
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
<div>
<div
aria-label="Property 1"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Property 1
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
<div>
<div
aria-label="Property 2"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Property 2
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
<div>
<div
aria-label="Property 3"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Property 3
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
</div>
<div
class="menu-spacer hideOnWidescreen"
/>
<div
class="menu-options hideOnWidescreen"
>
<div
aria-label="Cancel"
class="MenuOption TextOption menu-option menu-cancel"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Cancel
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
</div>
</div>
</div>
<div
aria-label="menuwrapper"
class="MenuWrapper"
role="button"
>
<button
class="Button"
type="button"
>
<span>
includes
</span>
</button>
</div>
<div
aria-label="menuwrapper"
class="MenuWrapper filterValue"
role="button"
>
<button
class="Button"
type="button"
>
<span>
Status
</span>
</button>
</div>
<div
class="octo-spacer"
/>
<button
class="Button"
type="button"
>
<span>
Delete
</span>
</button>
</div>
</div>
`;
exports[`components/viewHeader/filterEntry return filterEntry and click on different property type, but same filterOperation 1`] = `
<div>
<div
class="FilterEntry"
>
<div
aria-label="menuwrapper"
class="MenuWrapper override menuOpened"
role="button"
>
<button
class="Button"
type="button"
>
<span>
Property 1
</span>
</button>
<div
class="Menu noselect bottom "
>
<div
class="menu-contents"
>
<div
class="menu-options"
>
<div>
<div
aria-label="Title"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Title
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
<div>
<div
aria-label="Status"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Status
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
<div>
<div
aria-label="Property 1"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Property 1
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
<div>
<div
aria-label="Property 2"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Property 2
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
<div>
<div
aria-label="Property 3"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Property 3
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
</div>
<div
class="menu-spacer hideOnWidescreen"
/>
<div
class="menu-options hideOnWidescreen"
>
<div
aria-label="Cancel"
class="MenuOption TextOption menu-option menu-cancel"
role="button"
>
<div
class="d-flex"
>
<div
class="noicon"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Cancel
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
</div>
</div>
</div>
<div
aria-label="menuwrapper"
class="MenuWrapper"
role="button"
>
<button
class="Button"
type="button"
>
<span>
is set
</span>
</button>
</div>
<div
class="octo-spacer"
/>
<button
class="Button"
type="button"
>
<span>
Delete
</span>
</button>
</div>
</div>
`;
exports[`components/viewHeader/filterEntry return filterEntry and click on doesn't include 1`] = `
<div>
<div

View File

@ -270,4 +270,57 @@ describe('components/viewHeader/filterEntry', () => {
userEvent.click(allButton[allButton.length - 1])
expect(mockedMutator.changeViewFilter).toBeCalledTimes(1)
})
test('return filterEntry and click on different property type', () => {
activeView.fields.filter.filters = [statusFilter]
const {container} = render(
wrapIntl(
<ReduxProvider store={store}>
<FilterEntry
board={board}
view={activeView}
conditionClicked={mockedConditionClicked}
filter={statusFilter}
/>
</ReduxProvider>,
),
)
const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
userEvent.click(buttonElement)
expect(container).toMatchSnapshot()
const buttonDate = screen.getByRole('button', {name: 'Property 3'})
userEvent.click(buttonDate)
expect(mockedMutator.changeViewFilter).toBeCalledWith(
board.id, activeView.id,
{operation: 'and', filters: [statusFilter]},
{operation: 'and', filters: [dateFilter]})
})
test('return filterEntry and click on different property type, but same filterOperation', () => {
activeView.fields.filter.filters = [booleanFilter]
const {container} = render(
wrapIntl(
<ReduxProvider store={store}>
<FilterEntry
board={board}
view={activeView}
conditionClicked={mockedConditionClicked}
filter={booleanFilter}
/>
</ReduxProvider>,
),
)
const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
userEvent.click(buttonElement)
expect(container).toMatchSnapshot()
const buttonDate = screen.getByRole('button', {name: 'Property 3'})
userEvent.click(buttonDate)
expect(mockedMutator.changeViewFilter).toBeCalledWith(
board.id, activeView.id,
{operation: 'and', filters: [booleanFilter]},
{operation: 'and',
filters: [{
propertyId: board.cardProperties[3].id,
condition: 'isSet',
values: [],
}]})
})
})

View File

@ -76,6 +76,7 @@ const FilterEntry = (props: Props): JSX.Element => {
Utils.assert(newFilter, `No filter at index ${filterIndex}`)
if (newFilter.propertyId !== optionId) {
newFilter.propertyId = optionId
newFilter.condition = OctoUtils.filterConditionValidOrDefault(propsRegistry.get(o.type).filterValueType, newFilter.condition)
newFilter.values = []
mutator.changeViewFilter(props.board.id, view.id, view.fields.filter, filterGroup)
}

View File

@ -28,6 +28,35 @@ test('duplicateBlockTree: Card', async () => {
}
})
test('filterConditionValidOrDefault', async () => {
// Test 'options'
expect(OctoUtils.filterConditionValidOrDefault('options', 'includes')).toBe('includes')
expect(OctoUtils.filterConditionValidOrDefault('options', 'notIncludes')).toBe('notIncludes')
expect(OctoUtils.filterConditionValidOrDefault('options', 'isEmpty')).toBe('isEmpty')
expect(OctoUtils.filterConditionValidOrDefault('options', 'isNotEmpty')).toBe('isNotEmpty')
expect(OctoUtils.filterConditionValidOrDefault('options', 'is')).toBe('includes')
expect(OctoUtils.filterConditionValidOrDefault('boolean', 'isSet')).toBe('isSet')
expect(OctoUtils.filterConditionValidOrDefault('boolean', 'isNotSet')).toBe('isNotSet')
expect(OctoUtils.filterConditionValidOrDefault('boolean', 'includes')).toBe('isSet')
expect(OctoUtils.filterConditionValidOrDefault('text', 'is')).toBe('is')
expect(OctoUtils.filterConditionValidOrDefault('text', 'contains')).toBe('contains')
expect(OctoUtils.filterConditionValidOrDefault('text', 'notContains')).toBe('notContains')
expect(OctoUtils.filterConditionValidOrDefault('text', 'startsWith')).toBe('startsWith')
expect(OctoUtils.filterConditionValidOrDefault('text', 'notStartsWith')).toBe('notStartsWith')
expect(OctoUtils.filterConditionValidOrDefault('text', 'endsWith')).toBe('endsWith')
expect(OctoUtils.filterConditionValidOrDefault('text', 'notEndsWith')).toBe('notEndsWith')
expect(OctoUtils.filterConditionValidOrDefault('text', 'isEmpty')).toBe('is')
expect(OctoUtils.filterConditionValidOrDefault('date', 'is')).toBe('is')
expect(OctoUtils.filterConditionValidOrDefault('date', 'isBefore')).toBe('isBefore')
expect(OctoUtils.filterConditionValidOrDefault('date', 'isAfter')).toBe('isAfter')
expect(OctoUtils.filterConditionValidOrDefault('date', 'isSet')).toBe('isSet')
expect(OctoUtils.filterConditionValidOrDefault('date', 'isNotSet')).toBe('isNotSet')
expect(OctoUtils.filterConditionValidOrDefault('date', 'isEmpty')).toBe('is')
})
function createCardTree(): [Block[], Block] {
const blocks: Block[] = []

View File

@ -3,6 +3,7 @@
import {IntlShape} from 'react-intl'
import {FilterValueType} from './properties/types'
import {Block, createBlock} from './blocks/block'
import {BoardView, createBoardView} from './blocks/boardView'
import {Card, createCard} from './blocks/card'
@ -152,6 +153,57 @@ class OctoUtils {
return '(unknown)'
}
}
}
static filterConditionValidOrDefault(filterValueType: FilterValueType, currentFilterCondition: FilterCondition): FilterCondition {
if (filterValueType === 'options') {
switch (currentFilterCondition) {
case 'includes':
case 'notIncludes':
case 'isEmpty':
case 'isNotEmpty':
return currentFilterCondition
default: {
return 'includes'
}
}
} else if (filterValueType === 'boolean') {
switch (currentFilterCondition) {
case 'isSet':
case 'isNotSet':
return currentFilterCondition
default: {
return 'isSet'
}
}
} else if (filterValueType === 'text') {
switch (currentFilterCondition) {
case 'is':
case 'contains':
case 'notContains':
case 'startsWith':
case 'notStartsWith':
case 'endsWith':
case 'notEndsWith':
return currentFilterCondition
default: {
return 'is'
}
}
} else if (filterValueType === 'date') {
switch (currentFilterCondition) {
case 'is':
case 'isBefore':
case 'isAfter':
case 'isSet':
case 'isNotSet':
return currentFilterCondition
default: {
return 'is'
}
}
}
Utils.assertFailure()
return 'includes'
}
}
export {OctoUtils}