diff --git a/extension/src/popup/components/item-form.ts b/extension/src/popup/components/item-form.ts index 6f513c2..5773e6e 100644 --- a/extension/src/popup/components/item-form.ts +++ b/extension/src/popup/components/item-form.ts @@ -1,7 +1,7 @@ /// Typed-item add/edit form dispatcher. Each type's renderForm lives in /// its own module under ./types/. Document stays "coming soon" until γ. -import { navigate, getState, setState, escapeHtml, popOutToTab } from '../../shared/state'; +import { navigate, getState, setState, escapeHtml, popOutToTab, isInTab } from '../../shared/state'; import type { Item, ItemType } from '../../shared/types'; const TYPE_OPTIONS: Array<{ type: ItemType; icon: string; label: string }> = [ @@ -58,7 +58,7 @@ function renderTypeSelection(app: HTMLElement): void {

new item

- + ${isInTab() ? '' : ''}
${TYPE_OPTIONS.map((opt) => ` diff --git a/extension/src/popup/components/types/__tests__/popout-button-fullscreen.test.ts b/extension/src/popup/components/types/__tests__/popout-button-fullscreen.test.ts new file mode 100644 index 0000000..b9ccfb3 --- /dev/null +++ b/extension/src/popup/components/types/__tests__/popout-button-fullscreen.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('../../../../shared/state', () => ({ + sendMessage: vi.fn(), + getState: () => ({ newType: 'login', generatorDefaults: null, error: null, loading: false, vaultSettings: null, entries: [] }), + setState: vi.fn(), + navigate: vi.fn(), + escapeHtml: (s: string) => s, + popOutToTab: vi.fn(), + isInTab: () => true, // FULLSCREEN context + openVaultTab: vi.fn(), + registerHost: vi.fn(), +})); + +vi.mock('../../generator-panel', () => ({ + openGeneratorPanel: vi.fn(), + closeGeneratorPanel: vi.fn(), + isGeneratorPanelOpen: () => false, +})); + +import * as login from '../login'; +import * as secureNote from '../secure-note'; +import * as identity from '../identity'; +import * as card from '../card'; +import * as key from '../key'; +import * as totp from '../totp'; +import * as documentType from '../document'; + +const forms: Array<[string, (app: HTMLElement, mode: 'add' | 'edit', existing: null) => void]> = [ + ['login', login.renderForm], + ['secure-note', secureNote.renderForm], + ['identity', identity.renderForm], + ['card', card.renderForm], + ['key', key.renderForm], + ['totp', totp.renderForm], + ['document', documentType.renderForm], +]; + +describe('popout-to-tab button (fullscreen context)', () => { + beforeEach(() => { document.body.innerHTML = '
'; }); + + it.each(forms)('%s form does NOT render the popout button', (_name, render) => { + render(document.getElementById('app')!, 'add', null); + expect(document.getElementById('popout-btn')).toBeNull(); + }); +}); diff --git a/extension/src/popup/components/types/__tests__/popout-button.test.ts b/extension/src/popup/components/types/__tests__/popout-button.test.ts new file mode 100644 index 0000000..3232f02 --- /dev/null +++ b/extension/src/popup/components/types/__tests__/popout-button.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('../../../../shared/state', () => ({ + sendMessage: vi.fn(), + getState: () => ({ newType: 'login', generatorDefaults: null, error: null, loading: false, vaultSettings: null, entries: [] }), + setState: vi.fn(), + navigate: vi.fn(), + escapeHtml: (s: string) => s, + popOutToTab: vi.fn(), + isInTab: () => false, // POPUP context + openVaultTab: vi.fn(), + registerHost: vi.fn(), +})); + +vi.mock('../../generator-panel', () => ({ + openGeneratorPanel: vi.fn(), + closeGeneratorPanel: vi.fn(), + isGeneratorPanelOpen: () => false, +})); + +import * as login from '../login'; +import * as secureNote from '../secure-note'; +import * as identity from '../identity'; +import * as card from '../card'; +import * as key from '../key'; +import * as totp from '../totp'; +import * as documentType from '../document'; + +const forms: Array<[string, (app: HTMLElement, mode: 'add' | 'edit', existing: null) => void]> = [ + ['login', login.renderForm], + ['secure-note', secureNote.renderForm], + ['identity', identity.renderForm], + ['card', card.renderForm], + ['key', key.renderForm], + ['totp', totp.renderForm], + ['document', documentType.renderForm], +]; + +describe('popout-to-tab button (popup context)', () => { + beforeEach(() => { document.body.innerHTML = '
'; }); + + it.each(forms)('%s form renders the popout button', (_name, render) => { + render(document.getElementById('app')!, 'add', null); + expect(document.getElementById('popout-btn')).not.toBeNull(); + }); +}); diff --git a/extension/src/popup/components/types/card.ts b/extension/src/popup/components/types/card.ts index 481fa02..6c7725b 100644 --- a/extension/src/popup/components/types/card.ts +++ b/extension/src/popup/components/types/card.ts @@ -1,7 +1,7 @@ /// Card: number / holder / expiry MonthYear / cvv / pin / kind. /// Detail view has a styled card-silhouette signature block. -import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state'; +import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../../shared/state'; import { REQUIRED_PILL_HTML } from '../../../shared/glyphs'; import type { Item, ItemId, ManifestEntry, CardKind, Section, AttachmentRef } from '../../../shared/types'; import { @@ -177,7 +177,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
${mode === 'add' ? 'new card' : 'edit card'}
- + ${isInTab() ? '' : ''}
${state.error ? `
${escapeHtml(state.error)}
` : ''}
diff --git a/extension/src/popup/components/types/document.ts b/extension/src/popup/components/types/document.ts index 62260df..cccdcf8 100644 --- a/extension/src/popup/components/types/document.ts +++ b/extension/src/popup/components/types/document.ts @@ -2,7 +2,7 @@ /// notes/tags + optional supplementary attachments. /// Primary attachment is referenced by ID from the item's attachments array. -import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state'; +import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../../shared/state'; import { REQUIRED_PILL_HTML } from '../../../shared/glyphs'; import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types'; import { @@ -88,7 +88,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
${isEdit ? 'edit document' : 'new document'}
- + ${isInTab() ? '' : ''}
${state.error ? `
${escapeHtml(state.error)}
` : ''}
diff --git a/extension/src/popup/components/types/identity.ts b/extension/src/popup/components/types/identity.ts index 1fb09d6..90fffc3 100644 --- a/extension/src/popup/components/types/identity.ts +++ b/extension/src/popup/components/types/identity.ts @@ -1,7 +1,7 @@ /// Identity: full_name, address (multiline), phone, email, date_of_birth. /// Detail view shows a "profile card" signature block + plain rows. -import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state'; +import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../../shared/state'; import { REQUIRED_PILL_HTML } from '../../../shared/glyphs'; import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types'; import { @@ -137,7 +137,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
${mode === 'add' ? 'new identity' : 'edit identity'}
- + ${isInTab() ? '' : ''}
${state.error ? `
${escapeHtml(state.error)}
` : ''}
diff --git a/extension/src/popup/components/types/key.ts b/extension/src/popup/components/types/key.ts index ec5ce03..e227870 100644 --- a/extension/src/popup/components/types/key.ts +++ b/extension/src/popup/components/types/key.ts @@ -2,7 +2,7 @@ /// Form's key_material textarea uses CSS text-security to mask characters /// since