* refactor: move Electron app to main directory and enforce ts strict mode * chore: add pre-install plugins * remove duplicated initModel function Signed-off-by: James <james@jan.ai> * chore: correct module path * fix: dynamic import does not work with ts * chore: web should be able to run on target host browser * fix: history panel, should display conversations rather just blank state * chore: init default model * chore: pluggin in ts * fix: pre-pack model management * fix: compiled core should not include plugins * chore: refactor - invoke plugin function * refactor download/delete file Signed-off-by: James <james@jan.ai> * update prebuild lib Signed-off-by: James <james@jan.ai> * chore: yarn workspace * chore: update yarn workspace * chore: yarn workspace with nohoist * fix: llama-cpp-import * chore: fix data-plugin wrong module path * chore: correct build step * chore: - separate inference service (#212) - remove base-plugin Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai> * chore: update core plugins * chore: hide installation prompt and fix model load - management plugin * chore: remove legacy files; update readme * fix: refresh page lost the download state Signed-off-by: James <james@jan.ai> * fix: ai prompt not passed to plugin Signed-off-by: James <james@jan.ai> * chore: module import fix for production * chore: auto updater * chore: package is public * chore: fix yarn workspace config * update: model management uses Q4_K_M * chore: fix yarn scripts for publishing * chore: app updater - progress update message * chore: user confirms update action * adding some state for changing page store downloaded model to database Signed-off-by: James <james@jan.ai> * chore: refactor plugins into yarn workspace - a single command to publish all base plugins * chore update readme (#218) Co-authored-by: Hien To <tominhhien97@gmail.com> * change app name and app icon Signed-off-by: James <james@jan.ai> * remove: go-to-nowhere actions * chore: bundle core plugins from root and scan default plugins * fix: app crashes on different field name lookup * chore: css fix * chore: bind download progress to app ui * chore: bind active model * chore: simplify app splash-screen only centered jan icon * feature: system monitoring plugin (#196) * feat: Add function for system monitoring * chore: register plugin functions * chore: move to corresponding directory * chore: bind system monitoring data to UI --------- Co-authored-by: Louis <louis@jan.ai> * chore: add build:plugins step to README * chore: model searching and fix model name * fix: plugin file selected appearance * fix: create new conversation does not work * fix: delete conversation not update state - messages still exist * chore: fix asset path prefix * Add CICD for macos (#221) Co-authored-by: Hien To <tominhhien97@gmail.com> * chore: fix production plugin path * chore: add shell open url in external browser --------- Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai> Co-authored-by: NamH <NamNh0122@gmail.com> Co-authored-by: 0xSage <n@pragmatic.vc> Co-authored-by: hiento09 <136591877+hiento09@users.noreply.github.com> Co-authored-by: Hien To <tominhhien97@gmail.com> Co-authored-by: namvuong <22463238+vuonghoainam@users.noreply.github.com>
197 lines
5.9 KiB
JavaScript
197 lines
5.9 KiB
JavaScript
jest.mock('electron', () => {
|
|
const handlers = {}
|
|
|
|
return {
|
|
ipcMain: {
|
|
handle(channel, callback) {
|
|
handlers[channel] = callback
|
|
}
|
|
},
|
|
ipcRenderer: {
|
|
invoke(channel, ...args) {
|
|
return Promise.resolve(handlers[channel].call(undefined, 'event', ...args))
|
|
}
|
|
},
|
|
webContents: {
|
|
getAllWebContents: jest.fn(() => [])
|
|
},
|
|
contextBridge: {
|
|
exposeInMainWorld(key, val) {
|
|
global.window = { [key]: val }
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
jest.mock('../pluginMgr/store', () => {
|
|
const setActive = jest.fn(() => true)
|
|
const uninstall = jest.fn()
|
|
const update = jest.fn(() => true)
|
|
const isUpdateAvailable = jest.fn(() => false)
|
|
|
|
class Plugin {
|
|
constructor(name) {
|
|
this.name = name
|
|
this.activationPoints = ['test']
|
|
}
|
|
setActive = setActive
|
|
uninstall = uninstall
|
|
update = update
|
|
isUpdateAvailable = isUpdateAvailable
|
|
}
|
|
|
|
return {
|
|
getPlugin: jest.fn(name => new Plugin(name)),
|
|
getActivePlugins: jest.fn(() => [new Plugin('test')]),
|
|
installPlugins: jest.fn(async plugins => plugins.map(name => new Plugin(name))),
|
|
removePlugin: jest.fn()
|
|
}
|
|
})
|
|
|
|
const { rmSync } = require('fs')
|
|
const { webContents } = require('electron')
|
|
const useFacade = require('./index')
|
|
const { getActive, install, toggleActive, uninstall, update, updatesAvailable, registerActive } = require('../execution/facade')
|
|
const { setPluginsPath, setConfirmInstall } = require('../pluginMgr/globals')
|
|
const router = require('../pluginMgr/router')
|
|
const { getPlugin, getActivePlugins, removePlugin } = require('../pluginMgr/store')
|
|
const { get: getActivations } = require('../execution/activation-manager')
|
|
|
|
const pluginsPath = './testPlugins'
|
|
const confirmInstall = jest.fn(() => true)
|
|
|
|
beforeAll(async () => {
|
|
setPluginsPath(pluginsPath)
|
|
router()
|
|
useFacade()
|
|
})
|
|
|
|
afterAll(() => {
|
|
rmSync(pluginsPath, { recursive: true })
|
|
})
|
|
|
|
describe('install', () => {
|
|
it('should return cancelled state if the confirmPlugin callback returns falsy', async () => {
|
|
setConfirmInstall(() => false)
|
|
const plugins = await install(['test-install'])
|
|
expect(plugins).toEqual(false)
|
|
})
|
|
|
|
it('should perform a security check of the install using confirmInstall if facade is used', async () => {
|
|
setConfirmInstall(confirmInstall)
|
|
await install(['test-install'])
|
|
expect(confirmInstall.mock.calls.length).toBeTruthy()
|
|
})
|
|
|
|
it('should register all installed plugins', async () => {
|
|
const pluginName = 'test-install'
|
|
await install([pluginName])
|
|
expect(getActivations()).toContainEqual(expect.objectContaining({
|
|
plugin: pluginName
|
|
}))
|
|
})
|
|
|
|
it('should return a list of plugins', async () => {
|
|
setConfirmInstall(confirmInstall)
|
|
const pluginName = 'test-install'
|
|
const plugins = await install([pluginName])
|
|
expect(plugins).toEqual([expect.objectContaining({ name: pluginName })])
|
|
})
|
|
})
|
|
|
|
describe('uninstall', () => {
|
|
it('should uninstall all plugins with the provided name, remove it from the store and refresh all renderers', async () => {
|
|
// Reset mock functions
|
|
const mockUninstall = getPlugin().uninstall
|
|
mockUninstall.mockClear()
|
|
removePlugin.mockClear()
|
|
webContents.getAllWebContents.mockClear()
|
|
getPlugin.mockClear()
|
|
|
|
// Uninstall plugins
|
|
const specs = ['test-uninstall-1', 'test-uninstall-2']
|
|
await uninstall(specs)
|
|
|
|
// Test result
|
|
expect(getPlugin.mock.calls).toEqual(specs.map(spec => [spec]))
|
|
expect(mockUninstall.mock.calls.length).toBeTruthy()
|
|
expect(removePlugin.mock.calls.length).toBeTruthy()
|
|
expect(webContents.getAllWebContents.mock.calls.length).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
describe('getActive', () => {
|
|
it('should return all active plugins', async () => {
|
|
getActivePlugins.mockClear()
|
|
await getActive()
|
|
expect(getActivePlugins.mock.calls.length).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
describe('registerActive', () => {
|
|
it('should register all active plugins', async () => {
|
|
await registerActive()
|
|
expect(getActivations()).toContainEqual(expect.objectContaining({
|
|
plugin: 'test'
|
|
}))
|
|
})
|
|
})
|
|
|
|
describe('update', () => {
|
|
const specs = ['test-uninstall-1', 'test-uninstall-2']
|
|
const mockUpdate = getPlugin().update
|
|
|
|
beforeAll(async () => {
|
|
// Reset mock functions
|
|
mockUpdate.mockClear()
|
|
webContents.getAllWebContents.mockClear()
|
|
getPlugin.mockClear()
|
|
|
|
// Update plugins
|
|
await update(specs)
|
|
})
|
|
|
|
it('should call the update function on all provided plugins', async () => {
|
|
// Check result
|
|
expect(getPlugin.mock.calls).toEqual(specs.map(spec => [spec]))
|
|
expect(mockUpdate.mock.calls.length).toBe(2)
|
|
})
|
|
|
|
it('should reload the renderers if reload is true', () => {
|
|
expect(webContents.getAllWebContents.mock.calls.length).toBeTruthy()
|
|
})
|
|
|
|
it('should not reload the renderer if reload is false', async () => {
|
|
webContents.getAllWebContents.mockClear()
|
|
await update(['test-uninstall'], false)
|
|
expect(webContents.getAllWebContents.mock.calls.length).toBeFalsy()
|
|
})
|
|
})
|
|
|
|
describe('toggleActive', () => {
|
|
it('call the setActive function on the plugin with the provided name, with the provided active state', async () => {
|
|
await toggleActive('test-toggleActive', true)
|
|
expect(getPlugin.mock.lastCall).toEqual(['test-toggleActive'])
|
|
const mockSetActive = getPlugin().setActive
|
|
expect(mockSetActive.mock.lastCall).toEqual([true])
|
|
})
|
|
})
|
|
|
|
describe('updatesAvailable', () => {
|
|
it('should return the new versions for the provided plugins if provided', async () => {
|
|
// Reset mock functions
|
|
const mockIsUpdAvailable = getPlugin().isUpdateAvailable
|
|
mockIsUpdAvailable.mockClear()
|
|
getPlugin.mockClear()
|
|
|
|
// Get available updates
|
|
const testPlugin1 = 'test-plugin-1'
|
|
const testPlugin2 = 'test-update-2'
|
|
const updates = await updatesAvailable([testPlugin1, testPlugin2])
|
|
expect(updates).toEqual({
|
|
[testPlugin1]: false,
|
|
[testPlugin2]: false,
|
|
})
|
|
})
|
|
})
|