* hackathon: Refactor Jan into an Electron app * chore: correct NextJS export output path * chore: build electron app for all production targets * fix: correct assetPrefix for production build * chore: preferences shortcut * chore: refactor * chore: refactor into ts * feature/#52-compile-plugin-with-webpack * chore: introduce renderer <=> plugins <=> main invocation * chore: suppress errors - deprecate graphql & next-auth * chore: data plugin functions * add llm support Signed-off-by: James <james@jan.ai> * chore: update plugin * chore: introduce data-plugin * chore: plugin invokes main with args and synchronously * chore: install db plugin should setup db * feature: Data Driver Plugin - Load conversations and messages from data plugin * chore: store text message sent * chore: shared core services * feature: inference service * chore: conversations ordering * adding model management service Signed-off-by: James <james@jan.ai> * chore: strict type * feature: abstract plugin preferences * chore: abstract plugin preference * Revert "chore: strict type" This reverts commit 9be188d827a0b2e081e9e04b192c323799de5bb5. * chore: base-plugin styling * feature: create and delete conversation * chore: fix plugin search & clean messages * chore: typing indicator * chore: refactor useSendChatMessage * chore: persists inserted id to in-memory messages * chore: search conversation history * add delete and download model (#189) Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai> * chore: add empty state for conversation list * chore: prompt missing extension function & fix app crashes * chore: prompt user to install required plugins * chore: add launch background * chore: relaunch app on model downloaded * Jan app add installation instruction (#191) Co-authored-by: Hien To <> * Chore: rename folder web-client to app (#192) * Chore: rename folder web-client to app --------- Co-authored-by: Hien To <> * revert: add pre-install package * add progress for downloading model Signed-off-by: James <james@jan.ai> * feature: production bundle * add download progress Signed-off-by: James <james@jan.ai> * chore: add new chat function * fix: electron asar unpack modules & dynamic import * chore: fix unpack * chore: fix dev pack * Add instruction to build dmg file to README.md * init model dynamically Signed-off-by: James <james@jan.ai> --------- Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai> Co-authored-by: NamH <NamNh0122@gmail.com> Co-authored-by: hiento09 <136591877+hiento09@users.noreply.github.com> Co-authored-by: Hien To <>
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,
|
|
})
|
|
})
|
|
})
|