Newer
Older
navi-1 / webclient / tests / unit / components / ui / notificationPromptBanner.test.js
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'

import NotificationPromptBanner from '@/components/ui/NotificationPromptBanner.vue'

function mountBanner(props = {}) {
  return mount(NotificationPromptBanner, {
    props: {
      open: true,
      title: 'Turn on notifications',
      text: 'Navi can let you know when a reply is ready.',
      acceptText: 'Turn on',
      dismissText: 'Not now',
      ...props,
    },
  })
}

describe('NotificationPromptBanner', () => {
  it('renders nothing while it is closed', () => {
    expect(mountBanner({ open: false }).find('.notification-prompt').exists()).toBe(false)
  })

  it('shows what it was given, and both answers', () => {
    const wrapper = mountBanner()

    expect(wrapper.find('.notification-prompt-title').text()).toBe('Turn on notifications')
    expect(wrapper.find('.notification-prompt-text').text())
      .toBe('Navi can let you know when a reply is ready.')
    expect(wrapper.findAll('button').map(button => button.text()))
      .toEqual(['Turn on', 'Not now'])
    expect(wrapper.find('i.ph-bell').exists()).toBe(true)
  })

  it('reports the answer from the click itself, with nothing in between', () => {
    // The whole point of the button: the browser only shows its own prompt if
    // the gesture is still live when the handler runs, so the emit must not
    // wait on anything.
    const wrapper = mountBanner()
    const [accept] = wrapper.findAll('button')

    accept.element.click()

    expect(wrapper.emitted('accept')).toHaveLength(1)
    expect(wrapper.emitted('dismiss')).toBeUndefined()
  })

  it('reports the second answer too', async () => {
    const wrapper = mountBanner()
    const [, dismiss] = wrapper.findAll('button')

    await dismiss.trigger('click')

    expect(wrapper.emitted('dismiss')).toHaveLength(1)
    expect(wrapper.emitted('accept')).toBeUndefined()
  })

  it('closes both answers while an answer is in flight', () => {
    const wrapper = mountBanner({ busy: true })

    for (const button of wrapper.findAll('button')) {
      expect(button.attributes('disabled')).toBeDefined()
    }
  })

  it('announces itself by the title it was given', () => {
    const wrapper = mountBanner()

    const region = wrapper.find('[role="region"]')
    expect(region.attributes('aria-label')).toBe('Turn on notifications')
    expect(region.attributes('aria-live')).toBe('polite')
  })

  it('lets the caller replace the body and the actions', () => {
    const wrapper = mount(NotificationPromptBanner, {
      props: { open: true, title: 'Turn on notifications' },
      slots: { default: '<b>your own words</b>', actions: '<button>only one</button>' },
    })

    expect(wrapper.find('.notification-prompt-text').html()).toContain('your own words')
    expect(wrapper.findAll('button').map(button => button.text())).toEqual(['only one'])
  })
})