Adding filters
Our datepicker example from before contains a calendar which can be either open or closed:
<div id="birthday-picker" class="datepicker" title="Birthday">
<label for="birthday-field">Birthday</label>
<input id="birthday-field" type="text"/>
<div class="calendar open">
...
</div>
</div>
Let's start by creating an interactor for this calendar:
- JavaScript
- TypeScript
import { HTML } from '@interactors/html';
const Calendar = HTML.extend('calendar')
.selector('.calendar');
import { HTML } from '@interactors/html';
const Calendar = HTML.extend<HTMLDivElement>('calendar')
.selector('.calendar');
We now want to define a filter on this interactor, which tells us whether the calendar is open or closed:
- JavaScript
- TypeScript
import { HTML } from '@interactors/html';
const Calendar = HTML.extend('calendar')
.selector('.calendar')
.filters({
open: (element) => element.classList.has('open')
});
import { HTML } from '@interactors/html';
const Calendar = HTML.extend<HTMLDivElement>('calendar')
.selector('.calendar')
.filters({
open: (element) => element.classList.has('open')
});
The filter is named open
and it returns whether or not the element has the
open
class applied to it. Just like with the locator function, our filter
returns the value for the given element.
Now that we've defined a filter, we can use it to both locate calendars, and to make assertions about them. For example, we could check if our calendar is open like this:
await Calendar().is({ open: true });
Default filters
As we saw when talking about filters, filters can have a
default value. In the case of our calendar it might make sense to give the
open
filter a default value of true
. In most cases it should be required
that the calendar is open for us to interact with it. Let's add a default
filter like this:
- JavaScript
- TypeScript
import { HTML } from '@interactors/html';
const Calendar = HTML.extend('calendar')
.selector('.calendar')
.filters({
open: {
apply: (element) => element.classList.has('open'),
default: true
}
});
import { HTML } from '@interactors/html';
const Calendar = HTML.extend<HTMLDivElement>('calendar')
.selector('.calendar')
.filters({
open: {
apply: (element) => element.classList.has('open'),
default: true
}
});
As you can see, the open
filter now need to take an object, where the filter
function is specified in apply
and the default value can be provided as well.
Filter delegation
We have seen how to define a filter on our calendar to specify whether it is open or not. But we want to also apply this filter to the entire datepicker. With filter delegation, it is easy to reuse the filters of another interactor:
- JavaScript
- TypeScript
import { HTML } from '@interactors/html';
const DatePicker = HTML.extend('datepicker')
.selector('.datepicker')
.locator(TextField());
.filters({
open: Calendar().open()
});
import { HTML } from '@interactors/html';
const DatePicker = HTML.extend<HTMLDivElement>('datepicker')
.selector('.datepicker')
.locator(TextField());
.filters({
open: Calendar().open()
});
We can now use our open
filter on the datepicker as normal:
await DatePicker('Birthday').is({ open: true });
Our datepicker interactor will locate the calendar within itself, and apply the
filter from the calendar interactor. We can also use exists
and absent
in the
same way:
- JavaScript
- TypeScript
import { HTML } from '@interactors/html';
const DatePicker = HTML.extend('datepicker')
.selector('.datepicker')
.filters({
hasCalendar: Calendar().exists()
});
import { HTML } from '@interactors/html';
const DatePicker = HTML.extend<HTMLDivElement>('datepicker')
.selector('.datepicker')
.filters({
hasCalendar: Calendar().exists()
});
If you're observant, this might have you worried: doesn't exists()
throw an
error when it fails, and aren't we creating an unhandled rejected promise here?
The trick to making this work is that all actions and assertions are lazy
promises, and they won't actually run or do anything until we await them for
the first time. This allows us to create what we call an "interaction" without
actually performing any actual work:
// create the interaction, this doesn't do anything yet!
let interaction = Calendar().exists();
// only when we actually await the interaction do we start
// to check whether the calendar exists.
await interaction;