test(ext): failing-test scaffold for org read UI (Plan B Tasks 1-5)
Failing tests written against Dev-A's published org SW message contract (org_list_configs / org_switch / org_list_items / org_get_item / org_list_collections), plus compile-clean stubs. vitest: 6 fail / 3 pass — the 3 passes are A-independent (currentContext default + org reflect, personal "+ new" kept); the 6 fails are exactly the org behaviors HELD until Dev-A merges to main. Integration (flip-on at Dev-A merge): - org-context messageForList/messageForGet org branches (org_list_items/org_get_item) - org-switcher org_list_configs fetch + org_switch dispatch + list reload + offline banner - item-list org initial-load-on-render (org_list_items) + hide "+ new" in org context - collection facet populate from org_list_collections + click-to-filter Scaffold contents: - shared/org-context.ts (+__tests__): currentContext complete; messageFor* personal-only (HOLD) - shared/popup-state.ts: optional orgContext / orgOffline fields (non-breaking; A-typed orgConfigs/orgCollections join at integration) - popup/components/org-switcher.ts (+__tests__): select shell only (HOLD switch + Task 5 banner) - popup/components/__tests__/item-list.test.ts: org load + read-only (+ new hidden) assertions - vault/vault-sidebar.ts renderCollectionFacet (+__tests__): empty container (HOLD) Read-only this plan: write affordances stay hidden (Dev-C adds them). No org-message-type references in src — branch stays compile-clean; build:all type-check is the integration gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3b8DA2mXmLWkd4zDbHXHg
This commit is contained in:
37
extension/src/shared/__tests__/org-context.test.ts
Normal file
37
extension/src/shared/__tests__/org-context.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// Drive getState() so we can flip the current context per assertion. The
|
||||
// `vi.mock` idiom (vs vi.spyOn on an ESM namespace) is the established
|
||||
// component-test pattern in this codebase and is reliable across modules.
|
||||
vi.mock('../state', () => ({
|
||||
getState: vi.fn(),
|
||||
}));
|
||||
|
||||
import { currentContext, messageForList, messageForGet } from '../org-context';
|
||||
import { getState } from '../state';
|
||||
|
||||
const mockGetState = getState as ReturnType<typeof vi.fn>;
|
||||
|
||||
describe('shared/org-context: context-aware SW message selection', () => {
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
it('currentContext defaults to personal when orgContext is unset', () => {
|
||||
mockGetState.mockReturnValue({});
|
||||
expect(currentContext()).toBe('personal');
|
||||
});
|
||||
|
||||
it('currentContext reflects the active org', () => {
|
||||
mockGetState.mockReturnValue({ orgContext: 'org-1' });
|
||||
expect(currentContext()).toBe('org-1');
|
||||
});
|
||||
|
||||
it('messageForList/Get pick personal vs org by current context', () => {
|
||||
mockGetState.mockReturnValue({ orgContext: 'personal' });
|
||||
expect(messageForList()).toEqual({ type: 'list_items' });
|
||||
expect(messageForGet('x')).toEqual({ type: 'get_item', id: 'x' });
|
||||
|
||||
mockGetState.mockReturnValue({ orgContext: 'org-1' });
|
||||
expect(messageForList()).toEqual({ type: 'org_list_items' });
|
||||
expect(messageForGet('x')).toEqual({ type: 'org_get_item', id: 'x' });
|
||||
});
|
||||
});
|
||||
28
extension/src/shared/org-context.ts
Normal file
28
extension/src/shared/org-context.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// Context-aware SW message selection (Plan B Task 1).
|
||||
//
|
||||
// Single source of truth for "which SW message does the current context use":
|
||||
// the personal vault sends `list_items`/`get_item`; an org context sends the
|
||||
// org-scoped equivalents. The list + detail views consume these so neither has
|
||||
// to branch on context itself.
|
||||
//
|
||||
// SCAFFOLD STATE (HOLD until Dev-A merges): `currentContext()` is complete.
|
||||
// `messageForList`/`messageForGet` return the PERSONAL message only; the org
|
||||
// branch is held until Dev-A lands `org_list_items` / `org_get_item` in the
|
||||
// `shared/messages.ts` Request union. Flip the two HOLD lines at integration.
|
||||
|
||||
import { getState } from './state';
|
||||
import type { Request } from './messages';
|
||||
|
||||
export function currentContext(): 'personal' | string {
|
||||
return getState().orgContext ?? 'personal';
|
||||
}
|
||||
|
||||
export function messageForList(): Request {
|
||||
// HOLD(dev-a): in org context return { type: 'org_list_items' }.
|
||||
return { type: 'list_items' };
|
||||
}
|
||||
|
||||
export function messageForGet(id: string): Request {
|
||||
// HOLD(dev-a): in org context return { type: 'org_get_item', id }.
|
||||
return { type: 'get_item', id };
|
||||
}
|
||||
@@ -47,6 +47,13 @@ export interface PopupState {
|
||||
vaultSettings: VaultSettings | null;
|
||||
generatorDefaults: GeneratorRequest | null;
|
||||
historyItemId: ItemId | null;
|
||||
// Org (enterprise) context — Plan B (v0.9.0). `orgContext` is 'personal' or an
|
||||
// orgId; `orgOffline` flags a degraded read-only org (set by org_switch). Optional
|
||||
// so existing personal-only state constructions need no change; currentContext()
|
||||
// (shared/org-context.ts) falls back to 'personal'. orgConfigs/orgCollections
|
||||
// (Dev-A's OrgConfigSummary[]/Collection[]) join at org-track integration.
|
||||
orgContext?: 'personal' | string;
|
||||
orgOffline?: boolean;
|
||||
// Vault-tab-only fields. The popup surface leaves these at their defaults
|
||||
// (unlocked=false implicit via separate lock-screen view, drawer/panel false).
|
||||
// Kept on the shared shape so VaultState satisfies StateHost.getState()
|
||||
|
||||
Reference in New Issue
Block a user