refactor plugin installation query state

This commit is contained in:
yyh 2025-12-26 16:08:21 +08:00
parent 4c0fc56dd5
commit 1785dce04e
No known key found for this signature in database
3 changed files with 80 additions and 45 deletions

View File

@ -86,15 +86,13 @@ const PluginPage = ({
return
}
if (bundleInfo) {
// bundleInfo is a JSON string from URL, needs parsing
try {
const parsedBundleInfo = typeof bundleInfo === 'string' ? JSON.parse(bundleInfo) : bundleInfo
const { data } = await fetchBundleInfoFromMarketPlace(parsedBundleInfo)
const { data } = await fetchBundleInfoFromMarketPlace(bundleInfo)
setDependencies(data.version.dependencies)
showInstallFromMarketplace()
}
catch (e) {
console.error('Failed to parse bundle info:', e)
catch (error) {
console.error('Failed to load bundle info:', error)
}
}
})()

View File

@ -474,9 +474,10 @@ describe('useQueryParams hooks', () => {
describe('usePluginInstallation', () => {
it('should parse package ids from JSON arrays', () => {
// Arrange
const bundleInfo = { org: 'org', name: 'bundle', version: '1.0.0' }
const { result } = renderWithAdapter(
() => usePluginInstallation(),
'?package-ids=%5B%22org%2Fplugin%22%5D&bundle-info=bundle',
`?package-ids=%5B%22org%2Fplugin%22%5D&bundle-info=${encodeURIComponent(JSON.stringify(bundleInfo))}`,
)
// Act
@ -484,7 +485,7 @@ describe('useQueryParams hooks', () => {
// Assert
expect(state.packageId).toBe('org/plugin')
expect(state.bundleInfo).toBe('bundle')
expect(state.bundleInfo).toEqual(bundleInfo)
})
it('should return raw package id when JSON parsing fails', () => {
@ -532,24 +533,26 @@ describe('useQueryParams hooks', () => {
it('should set bundle info when provided', async () => {
// Arrange
const bundleInfo = { org: 'org', name: 'bundle', version: '1.0.0' }
const { result, onUrlUpdate } = renderWithAdapter(() => usePluginInstallation())
// Act
act(() => {
result.current[1]({ bundleInfo: 'bundle' })
result.current[1]({ bundleInfo })
})
// Assert
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
expect(update.searchParams.get('bundle-info')).toBe('bundle')
expect(update.searchParams.get('bundle-info')).toBe(JSON.stringify(bundleInfo))
})
it('should clear installation params when state is null', async () => {
// Arrange
const bundleInfo = { org: 'org', name: 'bundle', version: '1.0.0' }
const { result, onUrlUpdate } = renderWithAdapter(
() => usePluginInstallation(),
'?package-ids=%5B%22org%2Fplugin%22%5D&bundle-info=bundle',
`?package-ids=%5B%22org%2Fplugin%22%5D&bundle-info=${encodeURIComponent(JSON.stringify(bundleInfo))}`,
)
// Act
@ -566,9 +569,10 @@ describe('useQueryParams hooks', () => {
it('should preserve bundle info when only packageId is updated', async () => {
// Arrange
const bundleInfo = { org: 'org', name: 'bundle', version: '1.0.0' }
const { result, onUrlUpdate } = renderWithAdapter(
() => usePluginInstallation(),
'?bundle-info=bundle',
`?bundle-info=${encodeURIComponent(JSON.stringify(bundleInfo))}`,
)
// Act
@ -579,7 +583,7 @@ describe('useQueryParams hooks', () => {
// Assert
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
expect(update.searchParams.get('bundle-info')).toBe('bundle')
expect(update.searchParams.get('bundle-info')).toBe(JSON.stringify(bundleInfo))
})
})
})

View File

@ -141,6 +141,47 @@ export function useMarketplaceFilters() {
*/
const PACKAGE_IDS_PARAM = 'package-ids'
const BUNDLE_INFO_PARAM = 'bundle-info'
type BundleInfoQuery = {
org: string
name: string
version: string
}
const parseAsPackageId = createParser<string>({
parse: (value) => {
try {
const parsed = JSON.parse(value)
if (Array.isArray(parsed)) {
const first = parsed[0]
return typeof first === 'string' ? first : null
}
return value
}
catch {
return value
}
},
serialize: value => JSON.stringify([value]),
})
const parseAsBundleInfo = createParser<BundleInfoQuery>({
parse: (value) => {
try {
const parsed = JSON.parse(value) as Partial<BundleInfoQuery>
if (parsed
&& typeof parsed.org === 'string'
&& typeof parsed.name === 'string'
&& typeof parsed.version === 'string') {
return { org: parsed.org, name: parsed.name, version: parsed.version }
}
}
catch {
return null
}
return null
},
serialize: value => JSON.stringify(value),
})
/**
* Hook to manage plugin installation state via URL
@ -149,53 +190,45 @@ const BUNDLE_INFO_PARAM = 'bundle-info'
* @example
* const [installState, setInstallState] = usePluginInstallation()
* setInstallState({ packageId: 'org/plugin' }) // Sets ?package-ids=["org/plugin"]
* setInstallState({ bundleInfo: { org: 'org', name: 'bundle', version: '1.0.0' } }) // Sets ?bundle-info=...
* setInstallState(null) // Clears installation params
*/
export function usePluginInstallation() {
const [packageIds, setPackageIds] = useQueryState(
PACKAGE_IDS_PARAM,
parseAsString,
)
const [bundleInfo, setBundleInfo] = useQueryState(
BUNDLE_INFO_PARAM,
parseAsString,
const [installState, setInstallStateState] = useQueryStates(
{
packageId: parseAsPackageId,
bundleInfo: parseAsBundleInfo,
},
{
urlKeys: {
packageId: PACKAGE_IDS_PARAM,
bundleInfo: BUNDLE_INFO_PARAM,
},
},
)
const setInstallState = useCallback(
(state: { packageId?: string, bundleInfo?: string } | null) => {
(state: { packageId?: string, bundleInfo?: BundleInfoQuery } | null) => {
if (!state) {
setPackageIds(null)
setBundleInfo(null)
setInstallStateState(null)
return
}
if (state.packageId) {
// Store as JSON array for consistency with existing code
setPackageIds(JSON.stringify([state.packageId]))
}
if (state.bundleInfo) {
setBundleInfo(state.bundleInfo)
}
const patch: { packageId?: string, bundleInfo?: BundleInfoQuery } = {}
if (state.packageId)
patch.packageId = state.packageId
if (state.bundleInfo)
patch.bundleInfo = state.bundleInfo
if (Object.keys(patch).length === 0)
return
setInstallStateState(patch)
},
[setBundleInfo, setPackageIds],
[setInstallStateState],
)
// Parse packageIds from JSON array
const currentPackageId = packageIds
? (() => {
try {
const parsed = JSON.parse(packageIds)
return Array.isArray(parsed) ? parsed[0] : packageIds
}
catch {
return packageIds
}
})()
: null
return [
{
packageId: currentPackageId,
bundleInfo,
packageId: installState.packageId,
bundleInfo: installState.bundleInfo,
},
setInstallState,
] as const