Add Electron test harness with example plugin
This commit is contained in:
237
test/electron/plugins/example-plugin/index.js
Normal file
237
test/electron/plugins/example-plugin/index.js
Normal file
@@ -0,0 +1,237 @@
|
||||
/**
|
||||
* Example plugin — demonstrates every PluginContext API surface.
|
||||
* Loaded as CommonJS by the test harness (eval'd via new Function).
|
||||
*/
|
||||
module.exports = {
|
||||
name: 'Example Plugin',
|
||||
version: '1.0.0',
|
||||
|
||||
/** @param {import('@paarrot/plugin-manager').PluginContext} ctx */
|
||||
onLoad(ctx) {
|
||||
// -------------------------------------------------------------------------
|
||||
// Settings — persisted to storage automatically
|
||||
// -------------------------------------------------------------------------
|
||||
ctx.settings.define({
|
||||
greeting: {
|
||||
type: 'string',
|
||||
label: 'Greeting word',
|
||||
default: 'Hello',
|
||||
},
|
||||
autoExpand: {
|
||||
type: 'boolean',
|
||||
label: 'Auto-expand abbreviations (brb, omw, afk)',
|
||||
default: true,
|
||||
},
|
||||
logLevel: {
|
||||
type: 'select',
|
||||
label: 'Log verbosity',
|
||||
default: 'normal',
|
||||
options: [
|
||||
{ value: 'quiet', label: 'Quiet' },
|
||||
{ value: 'normal', label: 'Normal' },
|
||||
{ value: 'verbose', label: 'Verbose' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Commands
|
||||
// -------------------------------------------------------------------------
|
||||
ctx.commands.register({
|
||||
name: 'hello',
|
||||
description: 'Greet someone',
|
||||
args: [{ name: 'name', type: 'string', required: false, description: 'Who to greet' }],
|
||||
run(args) {
|
||||
const greeting = ctx.settings.get('greeting') ?? 'Hello';
|
||||
return `${greeting}, ${args.name || 'World'}!`;
|
||||
},
|
||||
});
|
||||
|
||||
ctx.commands.register({
|
||||
name: 'echo',
|
||||
description: 'Echo text back',
|
||||
args: [{ name: 'text', type: 'string', description: 'Text to echo' }],
|
||||
run: (args) => args.text || '(nothing)',
|
||||
});
|
||||
|
||||
ctx.commands.register({
|
||||
name: 'roll',
|
||||
description: 'Roll a dice — /roll <sides>',
|
||||
args: [{ name: 'sides', type: 'number', description: 'Number of sides (default 6)' }],
|
||||
run(args) {
|
||||
const sides = parseInt(args.sides, 10) || 6;
|
||||
const result = Math.floor(Math.random() * sides) + 1;
|
||||
return `Rolled a d${sides}: ${result}`;
|
||||
},
|
||||
});
|
||||
|
||||
ctx.commands.register({
|
||||
name: 'setting',
|
||||
description: 'Read a plugin setting — /setting <key>',
|
||||
args: [{ name: 'key', type: 'string' }],
|
||||
run(args) {
|
||||
const val = ctx.settings.get(args.key);
|
||||
return val !== undefined ? `${args.key} = ${JSON.stringify(val)}` : `Setting "${args.key}" not found`;
|
||||
},
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Message interceptors
|
||||
// -------------------------------------------------------------------------
|
||||
ctx.messages.onBeforeSend((msg) => {
|
||||
if (!ctx.settings.get('autoExpand')) return;
|
||||
msg.content = msg.content
|
||||
.replace(/\bbrb\b/gi, 'be right back')
|
||||
.replace(/\bomw\b/gi, 'on my way')
|
||||
.replace(/\bafk\b/gi, 'away from keyboard');
|
||||
|
||||
if (ctx.settings.get('logLevel') === 'verbose') {
|
||||
ctx.log('beforeSend interceptor ran');
|
||||
}
|
||||
});
|
||||
|
||||
ctx.messages.onReceive((msg) => {
|
||||
if (ctx.settings.get('logLevel') !== 'quiet') {
|
||||
ctx.log(`Received message in ${msg.roomId}: "${msg.content.slice(0, 40)}"`);
|
||||
}
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Theme — Ocean Blue dark palette
|
||||
// -------------------------------------------------------------------------
|
||||
ctx.themes.register({
|
||||
id: 'ocean',
|
||||
name: 'Ocean Blue',
|
||||
kind: 'dark',
|
||||
colors: {
|
||||
background: {
|
||||
container: '#0d1b2a',
|
||||
containerHover: '#1b2d42',
|
||||
containerActive: '#243856',
|
||||
containerLine: '#1e3a5f',
|
||||
onContainer: '#c9d6e3',
|
||||
},
|
||||
surface: {
|
||||
container: '#112233',
|
||||
containerHover: '#1a3344',
|
||||
containerActive: '#224455',
|
||||
containerLine: '#1e3a5f',
|
||||
onContainer: '#d0dde8',
|
||||
},
|
||||
surfaceVariant: {
|
||||
container: '#162840',
|
||||
containerHover: '#1f3555',
|
||||
containerActive: '#28426a',
|
||||
containerLine: '#2a4a70',
|
||||
onContainer: '#b8cfe0',
|
||||
},
|
||||
primary: {
|
||||
main: '#0077b6',
|
||||
mainHover: '#0096c7',
|
||||
mainActive: '#00b4d8',
|
||||
mainLine: '#0077b6',
|
||||
onMain: '#ffffff',
|
||||
container: '#023e8a',
|
||||
containerHover: '#0d47a1',
|
||||
containerActive: '#1565c0',
|
||||
containerLine: '#0d47a1',
|
||||
onContainer: '#90e0ef',
|
||||
},
|
||||
secondary: {
|
||||
main: '#48cae4',
|
||||
mainHover: '#00b4d8',
|
||||
mainActive: '#0096c7',
|
||||
mainLine: '#48cae4',
|
||||
onMain: '#000000',
|
||||
container: '#0d6986',
|
||||
containerHover: '#0e7fa0',
|
||||
containerActive: '#0f95bb',
|
||||
containerLine: '#0d6986',
|
||||
onContainer: '#caf0f8',
|
||||
},
|
||||
success: {
|
||||
main: '#00b894',
|
||||
mainHover: '#00cca3',
|
||||
mainActive: '#00dfb2',
|
||||
mainLine: '#00b894',
|
||||
onMain: '#000000',
|
||||
container: '#007a65',
|
||||
containerHover: '#009478',
|
||||
containerActive: '#00ae8b',
|
||||
containerLine: '#007a65',
|
||||
onContainer: '#b2f0e8',
|
||||
},
|
||||
warning: {
|
||||
main: '#f39c12',
|
||||
mainHover: '#f5a623',
|
||||
mainActive: '#f7b733',
|
||||
mainLine: '#f39c12',
|
||||
onMain: '#000000',
|
||||
container: '#a66c00',
|
||||
containerHover: '#bf7c00',
|
||||
containerActive: '#d98c00',
|
||||
containerLine: '#a66c00',
|
||||
onContainer: '#fde9b2',
|
||||
},
|
||||
critical: {
|
||||
main: '#e74c3c',
|
||||
mainHover: '#ec5f50',
|
||||
mainActive: '#f17264',
|
||||
mainLine: '#e74c3c',
|
||||
onMain: '#ffffff',
|
||||
container: '#9c1e12',
|
||||
containerHover: '#b52316',
|
||||
containerActive: '#ce281a',
|
||||
containerLine: '#9c1e12',
|
||||
onContainer: '#fcd4d0',
|
||||
},
|
||||
other: {
|
||||
focusRing: '#0096c7',
|
||||
shadow: 'rgba(0,0,0,0.5)',
|
||||
overlay: 'rgba(0,0,0,0.6)',
|
||||
},
|
||||
},
|
||||
extraCss: `
|
||||
body.plugin-example-plugin-ocean {
|
||||
background: #0d1b2a;
|
||||
color: #c9d6e3;
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Events — listen on the injected event client
|
||||
// -------------------------------------------------------------------------
|
||||
ctx.events.on('test-event', (data) => {
|
||||
ctx.log(`test-event received: ${JSON.stringify(data)}`);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Timers — heartbeat every 30 s
|
||||
// -------------------------------------------------------------------------
|
||||
ctx.timers.setInterval(() => {
|
||||
if (ctx.settings.get('logLevel') === 'verbose') {
|
||||
ctx.log('Heartbeat tick');
|
||||
}
|
||||
}, 30_000);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Notify on load
|
||||
// -------------------------------------------------------------------------
|
||||
ctx.notify({
|
||||
title: 'Example Plugin',
|
||||
body: 'Loaded successfully! Try a /command below.',
|
||||
type: 'success',
|
||||
});
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
// Timers, event handlers, and themes are cleaned up automatically by the registry.
|
||||
},
|
||||
|
||||
/** Public API — accessible by other plugins via ctx.require('example-plugin') */
|
||||
exports: {
|
||||
version: '1.0.0',
|
||||
greet: (name) => `Hi from Example Plugin, ${name}!`,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user