Newer
Older
navi-1 / webclient / tests / unit / composables / usePush.test.js
import { describe, it, expect, vi, beforeEach } from 'vitest'

// usePush touches Notification/navigator.serviceWorker at module load —
// stub the browser surface before importing it.
class FakeNotification {
  static permission = 'default'
  static requestPermission = vi.fn(async () => 'granted')
}

vi.stubGlobal('Notification', FakeNotification)

import { urlBase64ToUint8Array, usePush } from '@/composables/usePush.js'
import * as api from '@/api'

describe('urlBase64ToUint8Array', () => {
  it('round-trips URL-safe base64 with padding', () => {
    // VAPID keys are 65 bytes; this is a known 16-byte sample.
    const b64 = 'BQEhHkLb1xqWzM3vFVdUfA'
    const bytes = urlBase64ToUint8Array(b64)
    expect(bytes).toBeInstanceOf(Uint8Array)
    expect(bytes.length).toBe(16)
  })

  it('decodes standard base64 payloads unchanged', () => {
    // 'AQID' → [1, 2, 3]
    expect(Array.from(urlBase64ToUint8Array('AQID'))).toEqual([1, 2, 3])
  })
})

describe('usePush', () => {
  beforeEach(() => {
    vi.restoreAllMocks()
  })

  it('exposes enable/disable/syncState and refs', () => {
    const push = usePush()
    expect(typeof push.enable).toBe('function')
    expect(typeof push.disable).toBe('function')
    expect(typeof push.syncState).toBe('function')
    expect(typeof push.permission.value).toBe('string')
  })

  it('enable() subscribes and posts the endpoint payload', async ({ }) => {
    const fakeSub = {
      endpoint: 'https://push.example/endpoint/1',
      toJSON: () => ({
        endpoint: 'https://push.example/endpoint/1',
        keys: { p256dh: 'P256DH', auth: 'AUTH' },
      }),
      unsubscribe: async () => true,
    }
    const fakeReg = {
      pushManager: {
        getSubscription: vi.fn(async () => null),
        subscribe: vi.fn(async () => fakeSub),
      },
    }
    vi.stubGlobal('navigator', {
      userAgent: 'vitest',
      serviceWorker: {
        getRegistration: async () => fakeReg,
        ready: Promise.resolve(fakeReg),
      },
    })
    vi.stubGlobal('PushManager', function PushManager() {})
    vi.spyOn(api, 'getVapidKey').mockResolvedValue({ public_key: 'AQIDBAUGBw' })
    const subscribeSpy = vi.spyOn(api, 'subscribePush').mockResolvedValue({ id: '1' })

    const push = usePush()
    const ok = await push.enable()

    expect(ok).toBe(true)
    expect(fakeReg.pushManager.subscribe).toHaveBeenCalledWith(
      expect.objectContaining({ userVisibleOnly: true })
    )
    expect(subscribeSpy).toHaveBeenCalledWith({
      endpoint: 'https://push.example/endpoint/1',
      keys: { p256dh: 'P256DH', auth: 'AUTH' },
      user_agent: 'vitest',
    })
    expect(push.subscribed.value).toBe(true)
  })

  it('enable() returns false when the server has no VAPID key', async () => {
    vi.stubGlobal('navigator', {
      userAgent: 'vitest',
      serviceWorker: {
        getRegistration: async () => null,
        ready: Promise.resolve({ pushManager: { getSubscription: async () => null, subscribe: async () => {} } }),
      },
    })
    vi.stubGlobal('PushManager', function PushManager() {})
    vi.spyOn(api, 'getVapidKey').mockResolvedValue({ public_key: null })

    const push = usePush()
    expect(await push.enable()).toBe(false)
  })
})