Files
accounted/lib/browser/__tests__/deferred-tab.test.ts
T
Jakob Wennberg f07a34c51b fix(invoices): stop popup blockers from silently eating the PDF preview tab (#1191)
* fix(invoices): stop popup blockers from silently eating the PDF preview tab

A window.open() after an await runs outside the click's transient user
activation (~5s, less in Safari), so the preview tab was popup-blocked
exactly when generation was slow (cold start + logo re-encode). The
request succeeded, nothing opened, no error: the button looked locked
(support: carina@cbysea.se).

- lib/browser/deferred-tab.ts: open the tab synchronously in the click,
  navigate it when the result arrives, close it on failure (the pattern
  AGIPanel already used for its signing tab), with unit tests.
- InvoiceEditor: preview uses the deferred tab + popup-blocked toast;
  revoke the blob URL instead of leaking it; guard the review dialog
  against an unresolved customer (silent no-op click); 5s timeout on the
  pre-review next-number fetch; spinner + disable while the submit
  handler is in flight in create mode.
- Same pre-open fix in TransactionAttachmentIndicator,
  JournalEntryAttachments (failures now toast instead of vanishing),
  DocumentViewButton, and the Arcim reconnect OAuth popup (its 'trusted
  gesture' comment was wrong after the await).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(invoices): review triage: close blocked preview tab, precise popup hint, test convention

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 13:43:04 +02:00

97 lines
3.0 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { openDeferredTab } from '@/lib/browser/deferred-tab'
function makeFakeTab() {
return {
closed: false,
opener: {} as unknown,
close: vi.fn(),
location: { href: '' },
document: {
title: '',
body: { appendChild: vi.fn() },
createElement: vi.fn(() => ({ textContent: '', style: { cssText: '' } })),
},
}
}
beforeEach(() => {
vi.clearAllMocks()
vi.unstubAllGlobals()
})
afterEach(() => {
vi.unstubAllGlobals()
})
describe('openDeferredTab', () => {
it('is SSR-safe: reports blocked when window is undefined', () => {
const tab = openDeferredTab()
expect(tab.blocked).toBe(true)
expect(tab.navigate('https://example.com')).toBe(false)
expect(() => tab.close()).not.toThrow()
})
it('reports blocked and refuses navigation when window.open returns null', () => {
vi.stubGlobal('window', { open: vi.fn(() => null) })
const tab = openDeferredTab('Laddar...')
expect(tab.blocked).toBe(true)
expect(tab.navigate('https://example.com')).toBe(false)
expect(() => tab.close()).not.toThrow()
})
it('opens synchronously, severs opener, and navigates the pending tab', () => {
const fake = makeFakeTab()
const open = vi.fn(() => fake)
vi.stubGlobal('window', { open })
const tab = openDeferredTab('Genererar...')
expect(open).toHaveBeenCalledWith('', '_blank')
expect(tab.blocked).toBe(false)
expect(fake.opener).toBeNull()
expect(fake.document.title).toBe('Genererar...')
expect(fake.document.body.appendChild).toHaveBeenCalledTimes(1)
expect(tab.navigate('blob:https://app/abc')).toBe(true)
expect(fake.location.href).toBe('blob:https://app/abc')
})
it('skips the placeholder when none is given', () => {
const fake = makeFakeTab()
vi.stubGlobal('window', { open: vi.fn(() => fake) })
openDeferredTab()
expect(fake.document.body.appendChild).not.toHaveBeenCalled()
expect(fake.document.title).toBe('')
})
it('fails navigation once the user closed the pending tab', () => {
const fake = makeFakeTab()
vi.stubGlobal('window', { open: vi.fn(() => fake) })
const tab = openDeferredTab()
fake.closed = true
expect(tab.navigate('https://example.com')).toBe(false)
})
it('close() closes an open tab and is a no-op afterwards', () => {
const fake = makeFakeTab()
vi.stubGlobal('window', { open: vi.fn(() => fake) })
const tab = openDeferredTab()
tab.close()
expect(fake.close).toHaveBeenCalledTimes(1)
fake.closed = true
tab.close()
expect(fake.close).toHaveBeenCalledTimes(1)
})
it('survives a placeholder document that throws', () => {
const fake = makeFakeTab()
fake.document.createElement = vi.fn(() => {
throw new Error('detached')
})
vi.stubGlobal('window', { open: vi.fn(() => fake) })
const tab = openDeferredTab('Laddar...')
expect(tab.blocked).toBe(false)
expect(tab.navigate('https://example.com')).toBe(true)
})
})