guidepup / virtual-screen-reader

Virtual Screen Reader is a screen reader simulator for unit tests.
https://guidepup.dev
MIT License
97 stars 3 forks source link

Virtual screen reader interactivity? #44

Closed DarrenXu94 closed 6 months ago

DarrenXu94 commented 7 months ago

Hi there,

Great initiative and great library! I am trying to use virtual-screen-reader with my Vue components and testing-library. Is it possible to have events from JS or interactive elements captured? I've noticed fireEvent from '@testing-library/vue' doesn't change anything and neither does virtual.press('Enter'). Not sure if it's possible but I don't see any examples.

Thanks!

cmorten commented 7 months ago

In theory both should work 😅 sounds like have missed a trick somewhere!

Can you provide a minimal repro? (Though admittedly sounds like a hello world would suffice… just not so familiar with Vue these days)

jlp-craigmorten commented 7 months ago

Hi @DarrenXu94 👋

Please see https://github.com/guidepup/virtual-screen-reader/blob/main/examples/vue/src/__tests__/IncrementCounter.spec.ts as an example of writing a Virtual Screen Reader test with an interactive Vue component.

As far as I could tell everything was working as expected.

If you have specific issues, or an example that isn't working, happy to take a look and potentially reopen the issue.

DarrenXu94 commented 6 months ago

Hi there, I have created a playground with the component to test here

I am running this test against it

import { describe, it } from 'vitest'
import { render, screen, waitFor } from '@testing-library/vue'
import TextareaCounter from '../TextareaCounter.vue'

import { virtual } from '@guidepup/virtual-screen-reader'

describe('Textarea accessibility', () => {
  it('announces chars remaining', async () => {
    const { container } = render(TextareaCounter)

    virtual.start({ container })

    // Checking we are within textarea
    console.log(virtual.activeNode)

    await virtual.type('test')

    await waitFor(() => screen.getByDisplayValue('test'))

    console.log(await virtual.spokenPhraseLog());
    await virtual.stop();  
  })
})

Basically what should happen is it debounces the announcement for chars remaining.

When using NVDA I get the result

Screenshot 2024-01-31 134732

When I log out the spokenPhraseLog it just returns [ 'textbox' ]

Any ideas?

jlp-craigmorten commented 6 months ago

Hi @DarrenXu94

I've mirrored your example with working tests as a new example, see https://github.com/guidepup/virtual-screen-reader/blob/main/examples/vue/src/__tests__/TextAreaCounter.spec.ts

You test logic was flawed in that it doesn't take into account the 1 second delay of the debounce you have put in place - you are getting the spoken phrase log before the time ellapses to actually update your hidden live region.

DarrenXu94 commented 6 months ago

Thanks so much for all your help! One final thing, I copied the component and test locally, ran the test and got this error

image

Seems to be a different output despite running the same test, not sure if this is expected behaviour.

This is not stopping me or anything, I'd just like to let you know the test was failing, not sure if there are different results based on different systems (I'm using Node v18.19.0 on Windows) or if the test didn't work in the first place

Thanks again

cmorten commented 6 months ago

Ah I believe that is a small bug that was fixed in 0.16.2 where element being typed into was incorrectly being announced again after live region announcements.

If you upgrade to the latest the snapshots should hopefully match the examples.

DarrenXu94 commented 6 months ago

Hi there, sorry another issue with a slightly more complex example here

In this case I'm opening a modal using tab and enter keys. The modal has the role dialog and focuses on the exit button when it's open.

Screenshot 2024-02-07 085427

The expected behaviour is when the focus is within a dialog it announces 'dialog' and the title.

This is the test I'm running against it

import { afterEach, describe, expect, it } from 'vitest'
import { render, screen, waitFor } from '@testing-library/vue'
import ModalExample from '../ModalExample.vue'

/**
 * Replace with:
 *
 * import { virtual } from '@guidepup/virtual-screen-reader'
 *
 * in your own code.
 */
import { virtual } from '@guidepup/virtual-screen-reader'

describe('Text Area Counter', () => {
  afterEach(async () => {
    await virtual.stop()
  })

  it('announces the modal as a dialog', async () => {
    const { container } = render(ModalExample)

    await virtual.start({ container: document.body })
    await virtual.next()
    await virtual.act()

    // Adding timeout to counteract the setTimeout in Modal.vue
    await new Promise((resolve) => setTimeout(resolve, 1000))

    await waitFor(() =>screen.getByText('Modal content'))

    console.log(await virtual.spokenPhraseLog())
  })  
})

and the result is [ 'document', 'button, Open modal', 'button, Exit' ]

so I see the modal is open and the button has focus because 'button, Exit' is announced, but no announcement for the dialog.

Sorry for being annoying and asking so many questions but I am very excited for using this library

jlp-craigmorten commented 6 months ago

Hey @DarrenXu94

For this particular example see #12 where haven't completed all the modal behaviours yet #47 where haven't completed all the dialog behaviours yet, will take a look at it.

jlp-craigmorten commented 6 months ago

Ok, see 0.17.0 which should have the feature along with the example https://github.com/guidepup/virtual-screen-reader/blob/main/examples/vue/src/__tests__/OpenModal.spec.ts demonstrating the dialog being announced when it is entered.