Skip to content

Commit d8c27bf

Browse files
task state filtering: require waiting to be selected for selected modifiers
* Don't allow the waiting state filter to be de-selected if a waiting state modifier IS selected. * This replaces the disabling of the dropdown which previously encouraged the correct behaviour.
1 parent bf351f7 commit d8c27bf

File tree

2 files changed

+108
-15
lines changed

2 files changed

+108
-15
lines changed

src/components/cylc/ViewToolbar.vue

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
8282
>
8383
<Task :task="item.props" />
8484
</template>
85-
<!-- disable expansion until parent active (for task state filters) -->
86-
<template
87-
v-slot:toggle="{ props: toggleProps, isActive, isOpen }"
88-
v-if="iControl.props['task-state-icons']"
89-
>
90-
<v-icon
91-
:icon="isOpen ? $options.icons.mdiChevronUp : $options.icons.mdiChevronDown"
92-
:disabled="!isActive && !isOpen"
93-
v-bind="toggleProps"
94-
/>
95-
</template>
9685
</v-treeview>
9786
</v-menu>
9887
</div>
@@ -109,6 +98,7 @@ import {
10998
mdiFilter,
11099
mdiMagnify,
111100
} from '@mdi/js'
101+
import { TaskState, WaitingStateModifierNames } from '@/model/TaskState.model'
112102
113103
export default {
114104
name: 'ViewToolbar',
@@ -202,6 +192,7 @@ export default {
202192
let callback // callback to fire when control is activated
203193
let disabled // true if control should not be enabled
204194
let props
195+
let action
205196
const values = this.getValues()
206197
for (const group of this.groups) {
207198
iGroup = {
@@ -212,9 +203,10 @@ export default {
212203
callback = null
213204
disabled = false
214205
props = control.props || {}
206+
action = control.action
215207
216208
// set callback
217-
switch (control.action) {
209+
switch (action) {
218210
case 'toggle': // toggle button
219211
callback = (e) => this.toggle(control, e)
220212
break
@@ -224,7 +216,7 @@ export default {
224216
case 'taskIDFilter': // specialised "input" for filtering tasks
225217
callback = (value) => this.set(control, value)
226218
control.icon = mdiMagnify
227-
control.action = 'input'
219+
action = 'input'
228220
props = {
229221
placeholder: 'Search',
230222
...props,
@@ -234,7 +226,7 @@ export default {
234226
callback = (value) => this.set(control, value)
235227
break
236228
case 'taskStateFilter': // specialised "menu" for filtering tasks
237-
control.action = 'menu'
229+
action = 'menu'
238230
control.icon = mdiFilter
239231
props = {
240232
items: taskStateItems,
@@ -266,6 +258,7 @@ export default {
266258
267259
iControl = {
268260
...control,
261+
action,
269262
props,
270263
callback,
271264
disabled
@@ -308,6 +301,17 @@ export default {
308301
},
309302
set (control, value) {
310303
// update the value
304+
if ( // special logic for the taskStateFilter
305+
control.action === 'taskStateFilter' &&
306+
// if a waiting state modifier is selected
307+
value.some((modifier) => WaitingStateModifierNames.includes(modifier)) &&
308+
// but the waiting state is not
309+
!value.includes(TaskState.WAITING.name)
310+
) {
311+
// then add the waiting state (i.e, don't allow the user to de-select
312+
// waiting whilst a modifier is in play)
313+
value.push(TaskState.WAITING.name)
314+
}
311315
this.$emit('setOption', control.key, value)
312316
},
313317
autoResizeInput (e) {

tests/component/specs/viewToolbar.cy.js

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('View Toolbar Component', () => {
2727
cy.vmount(
2828
ViewToolbar,
2929
{
30-
props: { groups }
30+
props: { groups },
3131
}
3232
).as('wrapper')
3333
// add the classes Vuetify requires
@@ -224,4 +224,93 @@ describe('View Toolbar Component', () => {
224224
// TODO: visual regression test
225225
// https://github.com/cylc/cylc-ui/issues/178
226226
})
227+
228+
it('filters task states', () => {
229+
mountToolbar([
230+
{
231+
title: 'Group 1',
232+
controls: [
233+
{
234+
title: 'Filter',
235+
action: 'taskStateFilter',
236+
value: [],
237+
key: 'filter'
238+
}
239+
]
240+
},
241+
])
242+
cy.get('button').click()
243+
cy.get('.v-list-item').as('menuItems')
244+
.filter(':visible')
245+
.should('have.length', 10)
246+
.contains('.v-list-item', 'waiting').as('waitingItem')
247+
248+
// 1) select the waiting state and expand the waiting submenu
249+
cy.get('@waitingItem')
250+
.click()
251+
.find('.v-icon--clickable')
252+
.click({ force: true })
253+
254+
// the setOption event should be fired
255+
.get('@wrapper').then(({ wrapper }) => {
256+
expect(
257+
wrapper.emitted().setOption.at(-1)[1]
258+
).to.deep.equal(['waiting'])
259+
})
260+
261+
// the waiting state should be selected
262+
.get('@waitingItem')
263+
.should('have.class', 'v-list-item--active')
264+
265+
// the waiting submenu should be open
266+
.get('.v-list-item').as('menuItems')
267+
.filter(':visible')
268+
.should('have.length', 15)
269+
270+
// 2) select the Retry state
271+
cy.get('@menuItems')
272+
.contains('Retry')
273+
.click({ force: true })
274+
275+
// the setOption event should be fired
276+
.get('@wrapper').then(({ wrapper }) => {
277+
expect(
278+
wrapper.emitted().setOption.at(-1)[1]
279+
).to.deep.equal(['waiting', 'isRetry'])
280+
})
281+
282+
// 3) unselect the waiting state
283+
cy.get('@waitingItem')
284+
.click()
285+
286+
// implementation detail: the setOption event is fired again
287+
// but the waiting state is not removed
288+
.get('@wrapper').then(({ wrapper }) => {
289+
expect(
290+
wrapper.emitted().setOption.at(-1)[1]
291+
).to.deep.equal(['isRetry', 'waiting'])
292+
})
293+
294+
// the waiting state should still be selected
295+
.get('@waitingItem')
296+
.should('have.class', 'v-list-item--active')
297+
298+
// 3) unselect the retry state THEN the waiting state
299+
cy.get('@menuItems')
300+
.contains('Retry')
301+
.click({ force: true })
302+
.get('@waitingItem')
303+
.click()
304+
305+
// the waiting state should NOT be selected
306+
.get('@waitingItem')
307+
.should('not.have.class', 'v-list-item--active')
308+
309+
// the setOption event should be fired
310+
.get('@wrapper').then(({ wrapper }) => {
311+
expect(
312+
wrapper.emitted().setOption.at(-1)[1]
313+
).to.deep.equal([])
314+
})
315+
})
227316
})

0 commit comments

Comments
 (0)