mirror of
https://git.shipoclu.com/moon/plspace.git
synced 2026-08-13 02:42:30 +00:00
improved component composition and added tests.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Runtime dependencies used by Svelte components.
|
||||
*
|
||||
* Components read these through context instead of importing backend functions
|
||||
* and global stores directly. Production gets the real implementations by
|
||||
* default; tests can replace only the pieces they exercise.
|
||||
*/
|
||||
import { getContext } from 'svelte'
|
||||
import type { ApiClient } from './api/client'
|
||||
import type { CredentialAccount, InstanceInfo } from './api/types'
|
||||
import * as endpointImplementations from './api/endpoints'
|
||||
import { router as defaultRouter, type RouteMatch } from './router.svelte'
|
||||
import { session as defaultSession } from './stores/session.svelte'
|
||||
import { theme as defaultTheme } from './stores/theme.svelte'
|
||||
|
||||
export interface SessionService {
|
||||
host: string
|
||||
token: string | null
|
||||
me: CredentialAccount | null
|
||||
instance: InstanceInfo | null
|
||||
loading: boolean
|
||||
error: string | null
|
||||
readonly api: ApiClient
|
||||
readonly signedIn: boolean
|
||||
readonly connected: boolean
|
||||
restore(): Promise<string | null>
|
||||
connect(host: string): Promise<void>
|
||||
login(host: string, returnTo?: string): Promise<void>
|
||||
logout(): Promise<void>
|
||||
disconnect(): void
|
||||
}
|
||||
|
||||
export interface RouterService {
|
||||
current: RouteMatch
|
||||
go(to: string): void
|
||||
replace(to: string): void
|
||||
}
|
||||
|
||||
export interface ThemeService {
|
||||
viewerCss: string
|
||||
allowProfileCss: boolean
|
||||
setViewerCss(css: string): void
|
||||
setAllowProfileCss(allow: boolean): void
|
||||
applyProfileCss(css: string | null | undefined): void
|
||||
clearProfileCss(): void
|
||||
}
|
||||
|
||||
export interface AppServices {
|
||||
session: SessionService
|
||||
router: RouterService
|
||||
theme: ThemeService
|
||||
endpoints: typeof endpointImplementations
|
||||
}
|
||||
|
||||
export const APP_SERVICES = Symbol('plspace.app-services')
|
||||
|
||||
export const defaultAppServices: AppServices = {
|
||||
session: defaultSession,
|
||||
router: defaultRouter,
|
||||
theme: defaultTheme,
|
||||
endpoints: endpointImplementations,
|
||||
}
|
||||
|
||||
/** Read dependencies supplied by a parent/test, falling back to production. */
|
||||
export function useAppServices(): AppServices {
|
||||
return getContext<AppServices | undefined>(APP_SERVICES) ?? defaultAppServices
|
||||
}
|
||||
|
||||
export interface AppServiceOverrides {
|
||||
session?: SessionService
|
||||
router?: RouterService
|
||||
theme?: ThemeService
|
||||
endpoints?: Partial<typeof endpointImplementations>
|
||||
}
|
||||
|
||||
/** Build a complete service object from small test doubles. */
|
||||
export function createAppServices(overrides: AppServiceOverrides = {}): AppServices {
|
||||
return {
|
||||
session: overrides.session ?? defaultAppServices.session,
|
||||
router: overrides.router ?? defaultAppServices.router,
|
||||
theme: overrides.theme ?? defaultAppServices.theme,
|
||||
endpoints: { ...defaultAppServices.endpoints, ...overrides.endpoints },
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,12 @@ function matchPattern(pattern: string, path: string): Record<string, string> | n
|
||||
if (prefixed) {
|
||||
const [, prefix, name] = prefixed
|
||||
if (prefix && !actual.startsWith(prefix)) return null
|
||||
const value = decodeURIComponent(actual.slice(prefix.length))
|
||||
let value: string
|
||||
try {
|
||||
value = decodeURIComponent(actual.slice(prefix.length))
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
if (!value) return null
|
||||
params[name.slice(1)] = value
|
||||
continue
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { parseHash } from './router.svelte'
|
||||
|
||||
describe('parseHash', () => {
|
||||
it('decodes valid route parameters', () => {
|
||||
expect(parseHash('#/@alice%40remote.test').params.acct).toBe('alice@remote.test')
|
||||
})
|
||||
|
||||
it('treats malformed percent escapes as not found', () => {
|
||||
expect(() => parseHash('#/@broken%ZZ')).not.toThrow()
|
||||
expect(parseHash('#/@broken%ZZ').name).toBe('notfound')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user