feat(ext): shared state host — decouple components from popup.ts
Introduce shared/state.ts as a service-locator so popup components (item-detail, item-form, trash, devices, settings, etc.) work in both the popup and vault tab bundles. Both entry points register themselves as the host; components import from shared/state instead of popup.ts. Vault.ts now delegates to the real popup components, removing ~300 lines of placeholder renderers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../../popup', async () => {
|
||||
vi.mock('../../../shared/state', async () => {
|
||||
const sendMessage = vi.fn();
|
||||
const escapeHtml = (s: string): string => s.replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]!));
|
||||
return { sendMessage, escapeHtml };
|
||||
return { sendMessage, escapeHtml, popOutToTab: vi.fn(), isInTab: vi.fn(() => false), openVaultTab: vi.fn() };
|
||||
});
|
||||
|
||||
import { renderAttachmentsDisclosure, wireAttachmentsDisclosure } from '../attachments-disclosure';
|
||||
import { sendMessage } from '../../popup';
|
||||
import { sendMessage } from '../../../shared/state';
|
||||
import type { AttachmentRef } from '../../../shared/types';
|
||||
|
||||
const REF1: AttachmentRef = { id: 'a1', filename: 'doc.pdf', mime_type: 'application/pdf', size: 12345, created: 1700000000 };
|
||||
|
||||
@@ -12,14 +12,17 @@ globalThis.chrome = {
|
||||
};
|
||||
|
||||
// Mock popup module
|
||||
vi.mock('../../popup', () => ({
|
||||
vi.mock('../../../shared/state', () => ({
|
||||
setState: vi.fn(),
|
||||
sendMessage: vi.fn(),
|
||||
navigate: vi.fn(),
|
||||
escapeHtml: (s: string) => s,
|
||||
popOutToTab: vi.fn(),
|
||||
isInTab: vi.fn(() => false),
|
||||
openVaultTab: vi.fn(),
|
||||
}));
|
||||
|
||||
import { sendMessage, navigate } from '../../popup';
|
||||
import { sendMessage, navigate } from '../../../shared/state';
|
||||
|
||||
describe('devices view', () => {
|
||||
let app: HTMLElement;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { renderFieldHistory, teardown } from '../field-history';
|
||||
|
||||
// Mock popup module
|
||||
vi.mock('../../popup', () => ({
|
||||
vi.mock('../../../shared/state', () => ({
|
||||
getState: vi.fn(() => ({
|
||||
historyItemId: 'item123',
|
||||
selectedItem: { id: 'item123', title: 'Test Item', modified: 1000 },
|
||||
@@ -11,9 +11,12 @@ vi.mock('../../popup', () => ({
|
||||
sendMessage: vi.fn(),
|
||||
navigate: vi.fn(),
|
||||
escapeHtml: (s: string) => s,
|
||||
popOutToTab: vi.fn(),
|
||||
isInTab: vi.fn(() => false),
|
||||
openVaultTab: vi.fn(),
|
||||
}));
|
||||
|
||||
import { sendMessage, navigate } from '../../popup';
|
||||
import { sendMessage, navigate } from '../../../shared/state';
|
||||
|
||||
describe('field-history view', () => {
|
||||
let app: HTMLElement;
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../../../shared/state', async () => {
|
||||
const escapeHtml = (s: string) => s
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, ''');
|
||||
return { escapeHtml, popOutToTab: vi.fn(), isInTab: vi.fn(() => false), openVaultTab: vi.fn() };
|
||||
});
|
||||
|
||||
import {
|
||||
renderRow,
|
||||
renderConcealedRow,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../../popup', async () => {
|
||||
vi.mock('../../../shared/state', async () => {
|
||||
const sendMessage = vi.fn();
|
||||
return { sendMessage };
|
||||
return { sendMessage, popOutToTab: vi.fn(), isInTab: vi.fn(() => false), openVaultTab: vi.fn() };
|
||||
});
|
||||
|
||||
import { openGeneratorPanel, closeGeneratorPanel, isGeneratorPanelOpen } from '../generator-panel';
|
||||
import { sendMessage } from '../../popup';
|
||||
import { sendMessage } from '../../../shared/state';
|
||||
import type { GeneratorRequest } from '../../../shared/types';
|
||||
|
||||
const DEFAULT_REQ: GeneratorRequest = {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../../../shared/state', async () => {
|
||||
const escapeHtml = (s: string) => s
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, ''');
|
||||
return { escapeHtml, popOutToTab: vi.fn(), isInTab: vi.fn(() => false), openVaultTab: vi.fn() };
|
||||
});
|
||||
|
||||
import { renderSectionsEditor, generateFieldId, wireSectionsEditor } from '../fields';
|
||||
import type { Section } from '../../../shared/types';
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../../../shared/state', async () => {
|
||||
const escapeHtml = (s: string) => s
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, ''');
|
||||
return { escapeHtml, popOutToTab: vi.fn(), isInTab: vi.fn(() => false), openVaultTab: vi.fn() };
|
||||
});
|
||||
|
||||
import { renderSections } from '../fields';
|
||||
import type { Item } from '../../../shared/types';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../../popup', async () => {
|
||||
vi.mock('../../../shared/state', async () => {
|
||||
const navigate = vi.fn();
|
||||
const setState = vi.fn();
|
||||
const sendMessage = vi.fn();
|
||||
@@ -25,7 +25,7 @@ vi.mock('../../popup', async () => {
|
||||
const escapeHtml = (s: string) => s
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, ''');
|
||||
return { navigate, setState, sendMessage, getState, escapeHtml };
|
||||
return { navigate, setState, sendMessage, getState, escapeHtml, popOutToTab: vi.fn(), isInTab: vi.fn(() => false), openVaultTab: vi.fn() };
|
||||
});
|
||||
|
||||
vi.mock('../generator-panel', () => ({
|
||||
@@ -34,7 +34,7 @@ vi.mock('../generator-panel', () => ({
|
||||
}));
|
||||
|
||||
import { renderVaultSettings } from '../settings-vault';
|
||||
import { sendMessage } from '../../popup';
|
||||
import { sendMessage } from '../../../shared/state';
|
||||
|
||||
describe('settings-vault', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { renderTrash } from '../trash';
|
||||
|
||||
// Mock popup module
|
||||
vi.mock('../../popup', () => ({
|
||||
vi.mock('../../../shared/state', () => ({
|
||||
getState: vi.fn(() => ({
|
||||
vaultSettings: { trash_retention: { kind: 'days', value: 30 } },
|
||||
})),
|
||||
@@ -10,9 +10,12 @@ vi.mock('../../popup', () => ({
|
||||
sendMessage: vi.fn(),
|
||||
navigate: vi.fn(),
|
||||
escapeHtml: (s: string) => s,
|
||||
popOutToTab: vi.fn(),
|
||||
isInTab: vi.fn(() => false),
|
||||
openVaultTab: vi.fn(),
|
||||
}));
|
||||
|
||||
import { sendMessage, navigate } from '../../popup';
|
||||
import { sendMessage, navigate } from '../../../shared/state';
|
||||
|
||||
describe('trash view', () => {
|
||||
let app: HTMLElement;
|
||||
|
||||
Reference in New Issue
Block a user