mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-24 07:20:16 +05:30
feat(plugins): add export-format registration hook to plugin API
Add context.formats.registerExportFormat(id, opts) to PluginContext,
backed by a new FormatRegistry (mirrors PluginRegistry's Map-based
shape). Plugins register namespaced (${pluginId}:${id}) export
formats with a label/extension/handler; the writing-studio built-in
plugin registers a trivial "sprint-summary" .txt export as a
worked example.
The plugin system lives entirely in the renderer process while the
Export menu is built in main.js, so wiring formats into the menu
required a small IPC round-trip: renderer sends format metadata to
main after plugin load (main rebuilds the menu via the already-
idempotent createMenu()), and a menu click sends the resolved save
path back to the renderer, which is the only process holding the
plugin's handler function.
Amit Haridas
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
const { FormatRegistry } = require('../src/plugins/format-registry');
|
||||
|
||||
describe('FormatRegistry', () => {
|
||||
let registry;
|
||||
|
||||
beforeEach(() => {
|
||||
registry = new FormatRegistry();
|
||||
});
|
||||
|
||||
test('register — stores an entry retrievable by id', () => {
|
||||
const handler = jest.fn();
|
||||
registry.register('plugin-a:fmt', { label: 'Format A', extension: 'txt', handler });
|
||||
const entry = registry.get('plugin-a:fmt');
|
||||
expect(entry).toEqual({ label: 'Format A', extension: 'txt', handler });
|
||||
});
|
||||
|
||||
test('get — returns undefined for unknown id', () => {
|
||||
expect(registry.get('nope')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('register — overwrites an existing id', () => {
|
||||
registry.register('plugin-a:fmt', { label: 'First' });
|
||||
registry.register('plugin-a:fmt', { label: 'Second' });
|
||||
expect(registry.get('plugin-a:fmt').label).toBe('Second');
|
||||
});
|
||||
|
||||
test('getAll — returns all entries with id merged in', () => {
|
||||
registry.register('plugin-a:fmt', { label: 'Format A', extension: 'txt' });
|
||||
registry.register('plugin-b:fmt', { label: 'Format B', extension: 'csv' });
|
||||
const all = registry.getAll();
|
||||
expect(all).toHaveLength(2);
|
||||
expect(all).toContainEqual({ id: 'plugin-a:fmt', label: 'Format A', extension: 'txt' });
|
||||
expect(all).toContainEqual({ id: 'plugin-b:fmt', label: 'Format B', extension: 'csv' });
|
||||
});
|
||||
|
||||
test('getAll — returns empty array when nothing registered', () => {
|
||||
expect(registry.getAll()).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -21,6 +21,7 @@ describe('PluginContext', () => {
|
||||
},
|
||||
ipc: { invoke: jest.fn(), on: jest.fn() },
|
||||
exportHooks: { preHooks: [], postHooks: [] },
|
||||
formatRegistry: { register: jest.fn(), get: jest.fn(), getAll: jest.fn() },
|
||||
};
|
||||
context = new PluginContext(mockDeps);
|
||||
});
|
||||
@@ -87,4 +88,18 @@ describe('PluginContext', () => {
|
||||
context.exports.registerPostHook(handler);
|
||||
expect(mockDeps.exportHooks.postHooks).toContain(handler);
|
||||
});
|
||||
|
||||
test('exposes formats.registerExportFormat with namespaced id', () => {
|
||||
const handler = jest.fn();
|
||||
const opts = { label: 'My Format', extension: 'txt', handler };
|
||||
context.formats.registerExportFormat('my-format', opts);
|
||||
expect(mockDeps.formatRegistry.register).toHaveBeenCalledWith('test-plugin:my-format', opts);
|
||||
});
|
||||
|
||||
test('formats.registerExportFormat is a no-op when no formatRegistry is injected', () => {
|
||||
const noRegistryContext = new PluginContext({ ...mockDeps, formatRegistry: undefined });
|
||||
expect(() =>
|
||||
noRegistryContext.formats.registerExportFormat('x', { handler: jest.fn() })
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { PluginRegistry } = require('../src/plugins/plugin-registry');
|
||||
const { PluginAPI } = require('../src/plugins/plugin-api');
|
||||
const { EventBus } = require('../src/plugins/event-bus');
|
||||
const { FormatRegistry } = require('../src/plugins/format-registry');
|
||||
|
||||
class TestPlugin extends PluginAPI {
|
||||
init(context) {
|
||||
@@ -153,4 +154,40 @@ describe('PluginRegistry', () => {
|
||||
registry.getPlugin('test').instance.ctx.exports.registerPreHook(handler);
|
||||
expect(registry.exportHooks.preHooks).toContain(handler);
|
||||
});
|
||||
|
||||
test('a plugin calling context.formats.registerExportFormat populates the injected FormatRegistry with a namespaced entry', () => {
|
||||
const formatRegistry = new FormatRegistry();
|
||||
const registryWithFormats = new PluginRegistry({ ...mockDeps, formatRegistry });
|
||||
|
||||
class ExportingPlugin extends PluginAPI {
|
||||
init(context) {
|
||||
context.formats.registerExportFormat('sprint-summary', {
|
||||
label: 'Writing Studio Summary (.txt)',
|
||||
extension: 'txt',
|
||||
handler: async () => {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registryWithFormats.register({
|
||||
id: 'writing-studio',
|
||||
name: 'Writing Studio',
|
||||
version: '1.0.0',
|
||||
description: 'desc',
|
||||
manifest: {},
|
||||
PluginClass: ExportingPlugin,
|
||||
dir: '/tmp/test',
|
||||
});
|
||||
|
||||
const entry = formatRegistry.get('writing-studio:sprint-summary');
|
||||
expect(entry).toBeDefined();
|
||||
expect(entry.label).toBe('Writing Studio Summary (.txt)');
|
||||
expect(entry.extension).toBe('txt');
|
||||
expect(typeof entry.handler).toBe('function');
|
||||
|
||||
const all = formatRegistry.getAll();
|
||||
expect(all).toContainEqual(
|
||||
expect.objectContaining({ id: 'writing-studio:sprint-summary', extension: 'txt' })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user