plugin-manager.spec.ts 4.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import { readFileSync } from 'node:fs'
  2. import { runInContext } from 'node:vm'
  3. import { JSDOM } from 'jsdom'
  4. import { expect, it, vi } from 'vitest'
  5. import { resolveDesktopLocale } from '../src/locale.ts'
  6. it('keeps disabled packages visible and offers recovery without a running backend', async () => {
  7. const dom = new JSDOM(readFileSync(new URL('../renderer/plugin-manager.html', import.meta.url), 'utf8'), { runScripts: 'outside-only' })
  8. let enabled = true
  9. let ready = false
  10. const disableAll = vi.fn(async () => { enabled = false; ready = true })
  11. const toggle = vi.fn(async (_name: string, active: boolean) => { enabled = active })
  12. const api = {
  13. locale: async () => resolveDesktopLocale('en'),
  14. backend: { status: async () => ready ? { phase: 'ready' } : { phase: 'error', message: 'plugin requires Cordis ^2.0.0' }, retry: vi.fn() },
  15. plugins: { list: async () => [{ name: 'example-plugin', version: '1.0.0', enabled }], disableAll, toggle },
  16. }
  17. Object.defineProperty(dom.window, 'dshDesktop', { value: api })
  18. try {
  19. runInContext(readFileSync(new URL('../renderer/plugin-manager.js', import.meta.url), 'utf8'), dom.getInternalVMContext())
  20. const document = dom.window.document
  21. await expect.poll(() => document.querySelector('#plugins li')?.textContent).toContain('example-plugin')
  22. expect(document.querySelector<HTMLElement>('#recovery')?.hidden).toBe(false)
  23. expect(document.querySelector('#startup-error')?.textContent).toBe('plugin requires Cordis ^2.0.0')
  24. document.querySelector<HTMLButtonElement>('#disable-all')?.click()
  25. await expect.poll(() => document.querySelector<HTMLElement>('#recovery')?.hidden).toBe(true)
  26. expect(disableAll).toHaveBeenCalledOnce()
  27. expect(document.querySelector('#plugins li')?.textContent).toMatchInlineSnapshot('"example-plugin1.0.0 · DisabledEnableUpdateRemove"')
  28. document.querySelector<HTMLButtonElement>('#plugins li button')?.click()
  29. await expect.poll(() => toggle.mock.calls).toEqual([['example-plugin', true]])
  30. await expect.poll(() => document.querySelector('#plugins li')?.textContent).toBe('example-plugin1.0.0DisableUpdateRemove')
  31. } finally { dom.window.close() }
  32. })
  33. it.each(['en', 'zh-CN'])('updates through the inline form, including the installed version (%s)', async (language) => {
  34. const dom = new JSDOM(readFileSync(new URL('../renderer/plugin-manager.html', import.meta.url), 'utf8'), { runScripts: 'outside-only' })
  35. const locale = resolveDesktopLocale(language)
  36. const update = vi.fn(async () => {})
  37. Object.defineProperty(dom.window, 'dshDesktop', { value: {
  38. locale: async () => locale,
  39. backend: { status: async () => ({ phase: 'ready' }) },
  40. plugins: { list: async () => [{ name: 'example-plugin', version: '1.0.0', enabled: true }], update },
  41. } })
  42. try {
  43. runInContext(readFileSync(new URL('../renderer/plugin-manager.js', import.meta.url), 'utf8'), dom.getInternalVMContext())
  44. const document = dom.window.document
  45. await expect.poll(() => document.querySelector('#plugins li')?.textContent).toContain('example-plugin')
  46. const updateButton = () => document.querySelectorAll<HTMLButtonElement>('#plugins li button')[1]!
  47. const form = document.querySelector<HTMLFormElement>('#update-form')!
  48. const version = document.querySelector<HTMLInputElement>('#update-version')!
  49. updateButton().click()
  50. expect(form.hidden).toBe(false)
  51. expect(version.value).toBe('1.0.0')
  52. expect(document.activeElement).toBe(version)
  53. expect(document.querySelector('#update-label')?.textContent).toBe(locale.messages.targetVersion.replace('{name}', 'example-plugin'))
  54. expect(document.querySelector('#update-cancel')?.textContent).toBe(locale.messages.cancel)
  55. if (language === 'en') {
  56. expect({ label: document.querySelector('#update-label')?.textContent,
  57. actions: [...form.querySelectorAll('button')].map(button => button.textContent) }).toMatchInlineSnapshot(`
  58. {
  59. "actions": [
  60. "Update",
  61. "Cancel",
  62. ],
  63. "label": "Enter the target version for example-plugin",
  64. }
  65. `)
  66. }
  67. document.querySelector<HTMLButtonElement>('#update-cancel')!.click()
  68. expect(form.hidden).toBe(true)
  69. expect(document.activeElement).toBe(updateButton())
  70. expect(update).not.toHaveBeenCalled()
  71. for (const target of ['1.0.0', '^2.0.0']) {
  72. updateButton().click()
  73. version.value = target
  74. form.requestSubmit()
  75. await expect.poll(() => document.querySelector('#status')?.textContent).toBe(locale.messages.operationComplete)
  76. expect(update).toHaveBeenLastCalledWith('example-plugin', target)
  77. expect(form.hidden).toBe(true)
  78. }
  79. expect(update).toHaveBeenCalledTimes(2)
  80. } finally { dom.window.close() }
  81. })