feat(web): combine plugin settings into tabs

This commit is contained in:
ZiyaZhang
2026-08-12 00:38:10 -07:00
parent b1c13b5ca3
commit 4f5c730032
50 changed files with 512 additions and 217 deletions

View File

@@ -38,7 +38,7 @@ async function bench() {
function declare(slots: SlotsService): () => void {
return slots.register({
name: 'root',
children: { 'settings.section': { kind: 'list', scope: 'root' } },
children: { 'settings.plugins.tab': { kind: 'list', scope: 'root' } },
} as never, () => null)
}
@@ -47,16 +47,16 @@ describe('ui-plugins browser plugin', () => {
expect(inject).toEqual(['slots', 'locale', 'remote', 'remote.pluginInventory'])
})
it('registers a localized section without reading the Remote eagerly', async () => {
it('registers a localized tab without reading the Remote eagerly', async () => {
const b = await bench()
declare(b.slots)
await b.ctx.plugin({ inject: [...inject], apply }).await()
const entry = b.slots.entries('settings.section')[0]!
const entry = b.slots.entries('settings.plugins.tab')[0]!
expect(entry.component).toBe(PluginSettingsSection)
expect(entry.options).toMatchObject({ id: 'plugin-inventory', order: 15 })
expect(entry.options).toMatchObject({ id: 'all', order: 10 })
expect(entry.locale).toBe(NS)
expect(resolveSlotLabel(entry.options.label)).toBe('插件')
expect(resolveSlotLabel(entry.options.label)).toBe('插件列表')
expect(b.list).not.toHaveBeenCalled()
const injected = (entry.inject as unknown as () => PluginSettingsSectionInjected)()
@@ -71,22 +71,22 @@ describe('ui-plugins browser plugin', () => {
const b = await bench()
const fiber = b.ctx.plugin({ inject: [...inject], apply })
await fiber.await()
expect(b.slots.entries('settings.section')).toHaveLength(0)
expect(b.slots.entries('settings.plugins.tab')).toHaveLength(0)
const stop = declare(b.slots)
await vi.waitFor(() => { expect(b.slots.entries('settings.section')).toHaveLength(1) })
await vi.waitFor(() => { expect(b.slots.entries('settings.plugins.tab')).toHaveLength(1) })
b.locale.setLocale('en')
expect(resolveSlotLabel(b.slots.entries('settings.section')[0]!.options.label)).toBe('Plugins')
expect(resolveSlotLabel(b.slots.entries('settings.plugins.tab')[0]!.options.label)).toBe('Plugin list')
stop()
expect(b.slots.entries('settings.section')).toHaveLength(0)
expect(b.slots.entries('settings.plugins.tab')).toHaveLength(0)
declare(b.slots)
await vi.waitFor(() => {
expect(b.slots.entries('settings.section')[0]?.component).toBe(PluginSettingsSection)
expect(b.slots.entries('settings.plugins.tab')[0]?.component).toBe(PluginSettingsSection)
})
await fiber.dispose()
expect(b.slots.entries('settings.section')).toHaveLength(0)
expect(b.slots.entries('settings.plugins.tab')).toHaveLength(0)
expect(() => b.locale.register(NS, 'zh', {})).not.toThrow()
await b.ctx.fiber.dispose()
})

View File

@@ -12,16 +12,12 @@ afterEach(cleanup)
type Snapshot = Awaited<ReturnType<PluginSettingsSectionInjected['list']>>
const t = ((key: PluginsKey): string => en[key]) as PluginSettingsSectionProps['t']
const unusedHook = (() => { throw new Error('unused by plugin inventory') }) as never
function props(list: PluginSettingsSectionInjected['list']): PluginSettingsSectionProps {
return {
close: vi.fn(),
useSessions: unusedHook,
useWorkspaces: unusedHook,
t,
list,
}
} as PluginSettingsSectionProps
}
const SNAPSHOT = {
@@ -36,7 +32,7 @@ const SNAPSHOT = {
} as unknown as Snapshot
describe('PluginSettingsSection', () => {
it('renders searchable two-column-card semantics with dots and tags', async () => {
it('renders runtime status only for enabled plugins', async () => {
const deferred = Promise.withResolvers<Snapshot>()
const list = vi.fn(() => deferred.promise)
const view = render(<PluginSettingsSection {...props(list)} />)
@@ -56,10 +52,10 @@ describe('PluginSettingsSection', () => {
'Loading',
'Mount failed',
'Unloading',
'Not mounted',
]) {
expect(screen.getByRole('img', { name: value })).toBeTruthy()
}
expect(screen.queryByRole('img', { name: 'Not mounted' })).toBeNull()
const active = screen.getByRole('button', { name: 'hmr, Mounted, Enabled' })
expect(active.getAttribute('aria-expanded')).toBe('false')
fireEvent.click(active)
@@ -75,8 +71,10 @@ describe('PluginSettingsSection', () => {
target: { value: 'disabled-entry' },
})
expect(view.container.querySelector('[data-loader-entry]')).toBeNull()
fireEvent.click(screen.getByRole('button', { name: 'directory-picker-native, Not mounted, Disabled' }))
fireEvent.click(screen.getByRole('button', { name: 'directory-picker-native, Disabled' }))
expect(screen.getAllByText(en.disabledTag)).toHaveLength(2)
expect(screen.queryByText(en.cordis)).toBeNull()
expect(screen.queryByText(en.unobserved)).toBeNull()
})
it('filters by module name or Loader entry id', async () => {