Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ This is a **Platform.Bible extension** for interlinear Bible text alignment. Pla

`src/main.ts` — called by Platform.Bible on activation. Exports two lifecycle functions:

- `activate(context)` — registers the `interlinearizer.mainWebView` WebView provider, the `interlinearizer.openForWebView` command, and `onDidOpenWebView` / `onDidCloseWebView` subscriptions. All registrations are added to `context.registrations` so the platform disposes them on deactivation.
- `activate(context)` — registers the `interlinearizer.mainWebView` WebView provider, the `interlinearizer.openForWebView` command, the `interlinearizer.continuousScroll` project settings validator, and `onDidOpenWebView` / `onDidCloseWebView` subscriptions. All registrations are added to `context.registrations` so the platform disposes them on deactivation.
- `deactivate()` — clears `openWebViewsByProject` and returns `true`.

`openWebViewsByProject` (`Map<string, string>`) tracks one open WebView ID per project to prevent duplicates; reopening an already-open project brings that tab to front via the `existingId` option.
Expand Down
5 changes: 5 additions & 0 deletions __mocks__/papi-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const mockSelectProject = jest.fn();
const mockGetOpenWebViewDefinition = jest.fn();
const mockOnDidOpenWebView = jest.fn();
const mockOnDidCloseWebView = jest.fn();
const mockRegisterValidator = jest.fn();
const mockLogger = {
debug: jest.fn(),
error: jest.fn(),
Expand All @@ -24,6 +25,9 @@ const papi = {
dialogs: {
selectProject: mockSelectProject,
},
projectSettings: {
registerValidator: mockRegisterValidator,
},
webViewProviders: {
registerWebViewProvider: mockRegisterWebViewProvider,
},
Expand All @@ -44,6 +48,7 @@ const defaultExport = {
__mockGetOpenWebViewDefinition: mockGetOpenWebViewDefinition,
__mockOnDidOpenWebView: mockOnDidOpenWebView,
__mockOnDidCloseWebView: mockOnDidCloseWebView,
__mockRegisterValidator: mockRegisterValidator,
__mockLogger: mockLogger,
};

Expand Down
8 changes: 6 additions & 2 deletions src/__tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface PapiBackendTestMock {
__mockGetOpenWebViewDefinition: jest.Mock;
__mockOnDidOpenWebView: jest.Mock;
__mockOnDidCloseWebView: jest.Mock;
__mockRegisterValidator: jest.Mock;
__mockLogger: { debug: jest.Mock; error: jest.Mock; info: jest.Mock; warn: jest.Mock };
}

Expand All @@ -34,6 +35,7 @@ function isPapiBackendTestMock(m: unknown): m is PapiBackendTestMock {
'__mockGetOpenWebViewDefinition' in m &&
'__mockOnDidOpenWebView' in m &&
'__mockOnDidCloseWebView' in m &&
'__mockRegisterValidator' in m &&
'__mockLogger' in m
);
}
Expand All @@ -47,6 +49,7 @@ const {
__mockGetOpenWebViewDefinition,
__mockOnDidOpenWebView,
__mockOnDidCloseWebView,
__mockRegisterValidator,
__mockLogger,
} = papiBackendMock;

Expand Down Expand Up @@ -117,6 +120,7 @@ describe('main', () => {
beforeEach(() => {
__mockRegisterWebViewProvider.mockResolvedValue({ dispose: jest.fn() });
__mockRegisterCommand.mockResolvedValue({ dispose: jest.fn() });
__mockRegisterValidator.mockResolvedValue({ dispose: jest.fn() });
__mockOpenWebView.mockResolvedValue('mock-webview-id');
__mockSelectProject.mockResolvedValue(undefined);
__mockGetOpenWebViewDefinition.mockResolvedValue(undefined);
Expand Down Expand Up @@ -151,12 +155,12 @@ describe('main', () => {
);
});

it('adds all four registrations to the activation context', async () => {
it('adds all five registrations to the activation context', async () => {
const context = createTestActivationContext();

await activate(context);

expect(context.registrations.unsubscribers.size).toBe(4);
expect(context.registrations.unsubscribers.size).toBe(5);
});

it('logs activation start and finish', async () => {
Expand Down
11 changes: 9 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ async function openInterlinearizerForWebView(webViewId?: string): Promise<string
}

/**
* Extension entry point. Registers the Interlinearizer WebView provider and the open command.
* Called by the platform when the extension is loaded.
* Extension entry point. Registers the Interlinearizer WebView provider, the open command, the
* `continuousScroll` project settings validator, and `onDidOpenWebView` / `onDidCloseWebView`
* lifecycle subscriptions. Called by the platform when the extension is loaded.
*
* @param context - Activation context; used to register disposables so the platform can clean them
* up on deactivation.
Expand Down Expand Up @@ -141,6 +142,11 @@ export async function activate(context: ExecutionActivationContext): Promise<voi
},
);

const continuousScrollValidatorRegistration = await papi.projectSettings.registerValidator(
'interlinearizer.continuousScroll',
async (newValue) => typeof newValue === 'boolean',
);

const webViewOpenUnsubscriber = papi.webViews.onDidOpenWebView(({ webView }) => {
if (webView.webViewType !== mainWebViewType || !webView.projectId) return;
openWebViewsByProject.set(webView.projectId, webView.id);
Expand All @@ -155,6 +161,7 @@ export async function activate(context: ExecutionActivationContext): Promise<voi
context.registrations.add(
mainWebViewProviderRegistration,
openForWebViewCommandRegistration,
continuousScrollValidatorRegistration,
webViewOpenUnsubscriber,
webViewCloseUnsubscriber,
);
Expand Down
Loading