S
SolidJS12mo ago
oneiro

Testing portals

Hey, I am currently trying to test a datepicker created with https://github.com/rnwonder/solid-date-picker with testing-libary. However I am not quite sure how to select the actual picker. It seems to get rendered into a div with the id portal-island, which also shows up in my test, but without its contents. What would be the general way to test this? I also tried to jsut use querySelector but am not sure, how to get the correct container element to attach the querySelector to. Thanks in advance
GitHub
GitHub - rnwonder/solid-date-picker: A simple and reusable Datepick...
A simple and reusable Datepicker component for SolidJS - GitHub - rnwonder/solid-date-picker: A simple and reusable Datepicker component for SolidJS
9 Replies
Nathan
Nathan12mo ago
Do you have some example test code?
oneiro
oneiro12mo ago
a very minimal example of the test would probably look something like this:
render(() => <App />, { location: '/' })
if (useFakeTimers) {
vi.useFakeTimers()
}
const fakeSystemTime = new Date(2023, 6, 2)
const day = 28

const pickerInput = screen.getByTestId(/Date picker selectedDate-editTimeEntry/i)
expect(pickerInput).toBeVisible()

const targetDate = setDate(fakeSystemTime, day)

// NOTE:
// We avoid 'format()' here on purpose, because the date-picker implementation we use
// uses the javascript date als value for their data attribute. This means that the month
// is zero based instead of 1 based (e.g. meaning July === 6 instead of 7).
const selector = `div[data-value="${getYear(targetDate)}-${getMonth(targetDate)}-${day}"]`

// not sure if there is a way to use queries instead of the query selector here
const picker = ?.querySelector(selector)
render(() => <App />, { location: '/' })
if (useFakeTimers) {
vi.useFakeTimers()
}
const fakeSystemTime = new Date(2023, 6, 2)
const day = 28

const pickerInput = screen.getByTestId(/Date picker selectedDate-editTimeEntry/i)
expect(pickerInput).toBeVisible()

const targetDate = setDate(fakeSystemTime, day)

// NOTE:
// We avoid 'format()' here on purpose, because the date-picker implementation we use
// uses the javascript date als value for their data attribute. This means that the month
// is zero based instead of 1 based (e.g. meaning July === 6 instead of 7).
const selector = `div[data-value="${getYear(targetDate)}-${getMonth(targetDate)}-${day}"]`

// not sure if there is a way to use queries instead of the query selector here
const picker = ?.querySelector(selector)
oh wait, I completely forgot to click the pickerinput... that's probably the first issue here 😅 I would now be able to find one of the picker buttons by calling screen.getByText(<some-day-number>) - however looking for text by a number would probably be rather unreliable in the context of a picker.
Nathan
Nathan12mo ago
Hmm, why is that?
oneiro
oneiro12mo ago
ah yeah sorry, some more context: I am actually writing a small helper to make the interaction with the picker repeatable (a small layer of abstraction over our application UI). The actual wrapper would look something like this:
{
...
editDateWithPicker: ({
day,
useFakeTimers = true,
fakeSystemTime = new Date(2023, 6, 2),
}: {
day: number
useFakeTimers?: boolean
fakeSystemTime?: Date
}) => {
if (useFakeTimers) {
vi.useFakeTimers()
}

const pickerInput = screen.getByTestId(/Date picker selectedDate-editTimeEntry/i)
expect(pickerInput).toBeVisible()
fireEvent.click(pickerInput)

const targetDate = setDate(fakeSystemTime, day)

const test = screen.getByText(day)
console.log({ test })

// NOTE:
// We avoid 'format()' here on purpose, because the date-picker implementation we use
// uses the javascript date als value for their data attribute. This means that the month
// is zero based instead of 1 based (e.g. meaning July === 6 instead of 7).
const selector = `div[data-value="${getYear(targetDate)}-${getMonth(targetDate)}-${day}"]`

},
...
}
{
...
editDateWithPicker: ({
day,
useFakeTimers = true,
fakeSystemTime = new Date(2023, 6, 2),
}: {
day: number
useFakeTimers?: boolean
fakeSystemTime?: Date
}) => {
if (useFakeTimers) {
vi.useFakeTimers()
}

const pickerInput = screen.getByTestId(/Date picker selectedDate-editTimeEntry/i)
expect(pickerInput).toBeVisible()
fireEvent.click(pickerInput)

const targetDate = setDate(fakeSystemTime, day)

const test = screen.getByText(day)
console.log({ test })

// NOTE:
// We avoid 'format()' here on purpose, because the date-picker implementation we use
// uses the javascript date als value for their data attribute. This means that the month
// is zero based instead of 1 based (e.g. meaning July === 6 instead of 7).
const selector = `div[data-value="${getYear(targetDate)}-${getMonth(targetDate)}-${day}"]`

},
...
}
So the day is configurable. Therefore it could technically happen, that a day is being used, that is not unique on the screen. But maybe I am overthinking this...
Nathan
Nathan12mo ago
I see. Would it be overboard to aria-label them with the month? Or maybe you could pick them out by style, if you want to avoid just going "I pick the first one because I know that's the right one"
oneiro
oneiro12mo ago
yeah, unfortunately I don't have access to the underlying day element, so I can't put an arial-label onto it (which would otherwise have been my first cause of action). Maybe picking by style might work - thanks for the suggestion 👍 oh wait, I am just seeing that these days might already have an aria-label I could select at 🤩
Nathan
Nathan12mo ago
hooray for accessibility
oneiro
oneiro12mo ago
yes, that works nicely, for reference, here's my updated code:
editDateWithPicker: ({
day,
useFakeTimers = true,
fakeSystemTime = new Date(2023, 6, 2),
}: {
day: number
useFakeTimers?: boolean
fakeSystemTime?: Date
}) => {
if (useFakeTimers) {
vi.useFakeTimers()
}

const pickerInput = screen.getByTestId(/Date picker selectedDate-editTimeEntry/i)
expect(pickerInput).toBeVisible()

fireEvent.click(pickerInput)

const targetDate = setDate(fakeSystemTime, day)

// NOTE:
// We avoid 'format()' here on purpose, because the date-picker implementation we use
// uses the javascript date als value for their data attribute. This means that the month
// is zero based instead of 1 based (e.g. meaning July === 6 instead of 7).
const targetDateString = `${getYear(targetDate)}-${getMonth(targetDate)}-${day}`

const dayButton = screen.getByRole('button', { name: `Choose ${targetDateString}` })
fireEvent.click(dayButton)
}
editDateWithPicker: ({
day,
useFakeTimers = true,
fakeSystemTime = new Date(2023, 6, 2),
}: {
day: number
useFakeTimers?: boolean
fakeSystemTime?: Date
}) => {
if (useFakeTimers) {
vi.useFakeTimers()
}

const pickerInput = screen.getByTestId(/Date picker selectedDate-editTimeEntry/i)
expect(pickerInput).toBeVisible()

fireEvent.click(pickerInput)

const targetDate = setDate(fakeSystemTime, day)

// NOTE:
// We avoid 'format()' here on purpose, because the date-picker implementation we use
// uses the javascript date als value for their data attribute. This means that the month
// is zero based instead of 1 based (e.g. meaning July === 6 instead of 7).
const targetDateString = `${getYear(targetDate)}-${getMonth(targetDate)}-${day}`

const dayButton = screen.getByRole('button', { name: `Choose ${targetDateString}` })
fireEvent.click(dayButton)
}
Thanks @hypersoar for thinkig this through with me 🙂
Nathan
Nathan12mo ago
Happy to kinda help!