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:
parent
ff3a8d2096
commit
c761ea7c88
@ -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
|
||||
|
@ -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: [],
|
||||
}]})
|
||||
})
|
||||
})
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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[] = []
|
||||
|
||||
|
@ -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}
|
||||
|
Loading…
Reference in New Issue
Block a user