Newer
Older
navi-1 / webclient / tests / unit / composables / nativeBridge.test.js
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import {
  bridgeAvailable,
  nativeBridge,
  nativePushFlag,
  notifyNativeTurn,
  requestPermissionViaBridge,
} from '@/composables/nativeBridge.js'

function makeBridge(overrides = {}) {
  return {
    notify: vi.fn(),
    enableBackgroundMode: vi.fn(),
    disableBackgroundMode: vi.fn(),
    hasNotificationPermission: vi.fn(() => true),
    requestNotificationPermission: vi.fn(),
    isIgnoringBatteryOptimizations: vi.fn(() => true),
    requestIgnoreBatteryOptimizations: vi.fn(),
    ...overrides,
  }
}

describe('nativeBridge', () => {
  // node env here may have no localStorage global — provide a minimal shim
  // so the flag path is testable.
  let mem
  beforeEach(() => {
    mem = {}
    if (!globalThis.localStorage) {
      vi.stubGlobal('localStorage', {
        getItem: (k) => (k in mem ? mem[k] : null),
        setItem: (k, v) => { mem[k] = String(v) },
        removeItem: (k) => { delete mem[k] },
        clear: () => { for (const key of Object.keys(mem)) delete mem[key] },
      })
    } else {
      localStorage.clear()
    }
    if (window.NaviAndroid) delete window.NaviAndroid
  })
  afterEach(() => {
    if (window.NaviAndroid) delete window.NaviAndroid
  })

  it('without the bridge everything is a safe no-op', () => {
    expect(nativeBridge()).toBe(null)
    expect(bridgeAvailable()).toBe(false)
    expect(nativePushFlag()).toBe(false)
    expect(() => notifyNativeTurn('t', 'b', '/x')).not.toThrow()
  })

  it('detects the bridge surface', () => {
    window.NaviAndroid = makeBridge()
    expect(bridgeAvailable()).toBe(true)
  })

  it('notifyNativeTurn skips unless the user enabled native push', () => {
    window.NaviAndroid = makeBridge()
    Object.defineProperty(document, 'hidden', { value: true, configurable: true })
    notifyNativeTurn('Navi', 'body', '/#s1')
    expect(window.NaviAndroid.notify).not.toHaveBeenCalled()

    localStorage?.setItem?.("navi_push_android", "1")
    notifyNativeTurn('Navi', 'body', '/#s1')
    expect(window.NaviAndroid.notify).toHaveBeenCalledWith('Navi', 'body', '/#s1')
  })

  it('notifyNativeTurn skips when the window is visible', () => {
    Object.defineProperty(document, 'hidden', { value: false, configurable: true })
    window.NaviAndroid = makeBridge()
    localStorage?.setItem?.("navi_push_android", "1")
    notifyNativeTurn('Navi', 'body', '/#s1')
    expect(window.NaviAndroid.notify).not.toHaveBeenCalled()
  })

  it('requestPermissionViaBridge resolves from the navi-perm-result event', async () => {
    const bridge = makeBridge({ hasNotificationPermission: vi.fn(() => false) })
    window.NaviAndroid = bridge
    const p = requestPermissionViaBridge()
    expect(bridge.requestNotificationPermission).toHaveBeenCalled()
    window.dispatchEvent(new CustomEvent('navi-perm-result', { detail: true }))
    await expect(p).resolves.toBe(true)
  })

  it('requestPermissionViaBridge fails closed when already-declined is echoed', async () => {
    const bridge = makeBridge({ hasNotificationPermission: vi.fn(() => false) })
    window.NaviAndroid = bridge
    const p = requestPermissionViaBridge()
    window.dispatchEvent(new CustomEvent('navi-perm-result', { detail: false }))
    await expect(p).resolves.toBe(false)
  })
})