diff --git a/src/main/Outlet.tsx b/src/main/Outlet.tsx index c58e9dc..87f8e98 100644 --- a/src/main/Outlet.tsx +++ b/src/main/Outlet.tsx @@ -3,6 +3,8 @@ import { ChildSlotControllerContext, Slot } from './Slot'; /** * Props of the {@link Outlet} component. + * + * @group Components */ export interface OutletProps { /** @@ -13,6 +15,8 @@ export interface OutletProps { /** * Renders a route provided by an enclosing {@link Router}. + * + * @group Components */ export function Outlet(props: OutletProps): ReactNode { const controller = useContext(ChildSlotControllerContext); diff --git a/src/main/PathnameTemplate.ts b/src/main/PathnameTemplate.ts index 48078f1..33a73ae 100644 --- a/src/main/PathnameTemplate.ts +++ b/src/main/PathnameTemplate.ts @@ -2,6 +2,8 @@ import { Dict } from './types'; /** * A result returned by {@link PathnameTemplate.match} on a successful pathname match. + * + * @group Routing */ export interface PathnameMatch { /** @@ -22,6 +24,8 @@ export interface PathnameMatch { /** * A template of a pathname pattern. + * + * @group Routing */ export class PathnameTemplate { /** diff --git a/src/main/Route.ts b/src/main/Route.ts index bbd7056..59a6025 100644 --- a/src/main/Route.ts +++ b/src/main/Route.ts @@ -26,6 +26,7 @@ type PartialToVoid = Partial extends T ? T | void : T; * @template Params Route params. * @template Data Data loaded by a route. * @template Context A context required by a data loader. + * @group Routing */ export class Route< Parent extends Route | null = any, diff --git a/src/main/Router.tsx b/src/main/Router.tsx index 4ed05ba..c0b7d78 100644 --- a/src/main/Router.tsx +++ b/src/main/Router.tsx @@ -7,6 +7,7 @@ import { InternalRouter } from './InternalRouter'; * Props of the {@link Router} component. * * @template Context A context provided by a {@link Router} for a {@link RouteOptions.loader}. + * @group Components */ export interface RouterProps { /** @@ -92,6 +93,8 @@ export interface RouterProps { /** * A router that renders a route that matches the provided location. + * + * @group Components */ export function Router(props: Omit, 'context'>): ReactElement; @@ -99,6 +102,7 @@ export function Router(props: Omit, 'context'>): ReactElement; * A router that renders a route that matches the provided location. * * @template Context A context provided by a {@link Router} for a {@link RouteOptions.loader}. + * @group Components */ export function Router(props: RouterProps): ReactElement; diff --git a/src/main/createNavigation.ts b/src/main/createNavigation.ts index fdf2e52..7c3a9e3 100644 --- a/src/main/createNavigation.ts +++ b/src/main/createNavigation.ts @@ -6,6 +6,8 @@ import { toLocation } from './utils'; /** * Provides components a way to trigger router navigation. + * + * @group Routing */ export interface Navigation { /** diff --git a/src/main/createRoute.ts b/src/main/createRoute.ts index 24ece97..7c01469 100644 --- a/src/main/createRoute.ts +++ b/src/main/createRoute.ts @@ -9,6 +9,7 @@ import { RouteOptions } from './types'; * @template Params Route params. * @template Data Data loaded by a route. * @template Context A context provided by a {@link Router} for a {@link RouteOptions.loader}. + * @group Routing */ export function createRoute( options?: RouteOptions @@ -23,6 +24,7 @@ export function createRoute( parent: Parent, @@ -35,6 +37,7 @@ export function createRoute( pathname: string, @@ -49,6 +52,7 @@ export function createRoute( * @param component A component that is rendered by a route. * @template Parent A parent route. * @template Params Route params. + * @group Routing */ export function createRoute( parent: Parent, diff --git a/src/main/history/Link.tsx b/src/main/history/Link.tsx index 4527549..dd262ea 100644 --- a/src/main/history/Link.tsx +++ b/src/main/history/Link.tsx @@ -6,6 +6,8 @@ import { HistoryContext } from './useHistory'; /** * Props of the {@link Link} component. + * + * @group Components */ export interface LinkProps extends Omit, 'href'> { /** @@ -33,6 +35,8 @@ export interface LinkProps extends Omit, 'href * * If there's no enclosing {@link HistoryProvider} the {@link Link} component renders `#` * as an {@link !HTMLAnchorElement.href a.href}. + * + * @group Components */ export const Link = forwardRef((props, ref) => { const { to, prefetch, replace, onClick, ...anchorProps } = props; diff --git a/src/main/history/createBrowserHistory.ts b/src/main/history/createBrowserHistory.ts index f0d42cc..a9ddded 100644 --- a/src/main/history/createBrowserHistory.ts +++ b/src/main/history/createBrowserHistory.ts @@ -3,46 +3,51 @@ import { Location } from '../types'; import { toLocation } from '../utils'; import { History, HistoryOptions } from './types'; import { urlSearchParamsAdapter } from './urlSearchParamsAdapter'; -import { parseURL, toURL } from './utils'; +import { debasePathname, parseLocation, rebasePathname, stringifyLocation } from './utils'; /** * Create the history adapter that reads and writes location to a browser's session history. * * @param options History options. + * @group History */ export function createBrowserHistory(options: HistoryOptions = {}): History { - const { base, searchParamsAdapter = urlSearchParamsAdapter } = options; + const { basePathname, searchParamsAdapter = urlSearchParamsAdapter } = options; const pubSub = new PubSub(); const handlePopstate = () => pubSub.publish(); - const baseURL = base === undefined ? undefined : new URL(base); let prevHref: string; let location: Location; return { get location() { - const { href } = window.location; + const { pathname, search, hash } = window.location; + const href = pathname + search + hash; if (prevHref !== href) { prevHref = href; - location = parseURL(href, searchParamsAdapter, baseURL); + location = parseLocation(debasePathname(basePathname, href), searchParamsAdapter); } return location; }, toURL(to) { - return toURL(toLocation(to), searchParamsAdapter, baseURL); + return rebasePathname(basePathname, typeof to === 'string' ? to : stringifyLocation(to, searchParamsAdapter)); }, push(to) { - location = toLocation(to); - window.history.pushState(location.state, '', toURL(location, searchParamsAdapter, baseURL)); + location = typeof to === 'string' ? parseLocation(to, searchParamsAdapter) : toLocation(to); + + to = rebasePathname(basePathname, stringifyLocation(location, searchParamsAdapter)); + window.history.pushState(location.state, '', to); pubSub.publish(); }, replace(to) { - location = toLocation(to); - window.history.replaceState(location.state, '', toURL(location, searchParamsAdapter, baseURL)); + location = typeof to === 'string' ? parseLocation(to, searchParamsAdapter) : toLocation(to); + + to = rebasePathname(basePathname, stringifyLocation(location, searchParamsAdapter)); + window.history.replaceState(location.state, '', to); pubSub.publish(); }, diff --git a/src/main/history/createHashHistory.ts b/src/main/history/createHashHistory.ts index 76a1f5d..0ceb6e5 100644 --- a/src/main/history/createHashHistory.ts +++ b/src/main/history/createHashHistory.ts @@ -3,18 +3,18 @@ import { Location } from '../types'; import { toLocation } from '../utils'; import { History, HistoryOptions } from './types'; import { urlSearchParamsAdapter } from './urlSearchParamsAdapter'; -import { parseURL, toURL } from './utils'; +import { parseLocation, rebasePathname, stringifyLocation } from './utils'; /** * Create the history adapter that reads and writes location to a browser's session history using only URL hash. * * @param options History options. + * @group History */ export function createHashHistory(options: HistoryOptions = {}): History { - const { base, searchParamsAdapter = urlSearchParamsAdapter } = options; + const { basePathname, searchParamsAdapter = urlSearchParamsAdapter } = options; const pubSub = new PubSub(); const handlePopstate = () => pubSub.publish(); - const baseURL = base === undefined ? undefined : new URL(base); let prevHref: string; let location: Location; @@ -25,26 +25,31 @@ export function createHashHistory(options: HistoryOptions = {}): History { if (prevHref !== href) { prevHref = href; - location = parseURL(href, searchParamsAdapter); + location = parseLocation(href, searchParamsAdapter); } return location; }, toURL(to) { - const url = '#' + encodeURIComponent(toURL(toLocation(to), searchParamsAdapter)); - - return baseURL === undefined ? url : new URL(url, baseURL).toString(); + return rebasePathname( + basePathname, + '#' + encodeURIComponent(typeof to === 'string' ? to : stringifyLocation(to, searchParamsAdapter)) + ); }, push(to) { - location = toLocation(to); - window.history.pushState(location.state, '', '#' + encodeURIComponent(toURL(location, searchParamsAdapter))); + location = typeof to === 'string' ? parseLocation(to, searchParamsAdapter) : toLocation(to); + + to = stringifyLocation(location, searchParamsAdapter); + window.history.pushState(location.state, '', '#' + encodeURIComponent(to)); pubSub.publish(); }, replace(to) { - location = toLocation(to); - window.history.replaceState(location.state, '', '#' + encodeURIComponent(toURL(location, searchParamsAdapter))); + location = typeof to === 'string' ? parseLocation(to, searchParamsAdapter) : toLocation(to); + + to = stringifyLocation(location, searchParamsAdapter); + window.history.replaceState(location.state, '', '#' + encodeURIComponent(to)); pubSub.publish(); }, diff --git a/src/main/history/createMemoryHistory.ts b/src/main/history/createMemoryHistory.ts index c5f9dda..1ce6253 100644 --- a/src/main/history/createMemoryHistory.ts +++ b/src/main/history/createMemoryHistory.ts @@ -3,10 +3,12 @@ import { Location } from '../types'; import { toLocation } from '../utils'; import { History, HistoryOptions } from './types'; import { urlSearchParamsAdapter } from './urlSearchParamsAdapter'; -import { toURL } from './utils'; +import { parseLocation, rebasePathname, stringifyLocation } from './utils'; /** * Options of {@link createMemoryHistory}. + * + * @group History */ export interface MemoryHistoryOptions extends HistoryOptions { /** @@ -19,12 +21,12 @@ export interface MemoryHistoryOptions extends HistoryOptions { * Create the history adapter that reads and writes location to an in-memory stack. * * @param options History options. + * @group History */ export function createMemoryHistory(options: MemoryHistoryOptions): History { - const { base, searchParamsAdapter = urlSearchParamsAdapter } = options; + const { basePathname, searchParamsAdapter = urlSearchParamsAdapter } = options; const pubSub = new PubSub(); const entries = options.initialEntries.slice(0); - const baseURL = base === undefined ? undefined : new URL(base); if (entries.length === 0) { throw new Error('Expected at least one initial entry'); @@ -38,17 +40,20 @@ export function createMemoryHistory(options: MemoryHistoryOptions): History { }, toURL(to) { - return toURL(toLocation(to), searchParamsAdapter, baseURL); + return rebasePathname(basePathname, typeof to === 'string' ? to : stringifyLocation(to, searchParamsAdapter)); }, push(to) { cursor++; - entries.splice(cursor, entries.length, toLocation(to)); + + to = typeof to === 'string' ? parseLocation(to, searchParamsAdapter) : toLocation(to); + entries.splice(cursor, entries.length, to); pubSub.publish(); }, replace(to) { - entries.splice(cursor, entries.length, toLocation(to)); + to = typeof to === 'string' ? parseLocation(to, searchParamsAdapter) : toLocation(to); + entries.splice(cursor, entries.length, to); pubSub.publish(); }, diff --git a/src/main/history/types.ts b/src/main/history/types.ts index b5853ca..f09e73c 100644 --- a/src/main/history/types.ts +++ b/src/main/history/types.ts @@ -1,10 +1,13 @@ import { Dict, Location, To } from '../types'; +/** + * @group History + */ export interface HistoryOptions { /** - * A base URL. + * A base pathname. */ - base?: URL | string; + basePathname?: string; /** * An adapter that extracts params from a URL search string and stringifies them back. By default, an adapter that @@ -15,6 +18,8 @@ export interface HistoryOptions { /** * A history abstraction. + * + * @group History */ export interface History { /** @@ -23,25 +28,38 @@ export interface History { readonly location: Location; /** - * Creates a URL for a given location. + * Creates a pathname-search-hash string for a given location. + * + * If history was initialized with a {@link HistoryOptions.basePathname basePathname} then it is prepended to the + * returned URL. * * @param to A location to create a URL for. */ - toURL(to: To): string; + toURL(to: To | string): string; /** * Adds an entry to the history stack. * * @param to A location to navigate to. + * @example + * const userRoute = createRoute('/users/:userId'); + * history.push(userRoute.getLocation({ userId: 42 })); + * // or + * history.push('/users/42'); */ - push(to: To): void; + push(to: To | string): void; /** * Modifies the current history entry, replacing it with the state object and URL passed in the method parameters. * * @param to A location to navigate to. + * @example + * const userRoute = createRoute('/users/:userId'); + * history.replace(userRoute.getLocation({ userId: 42 })); + * // or + * history.replace('/users/42'); */ - replace(to: To): void; + replace(to: To | string): void; /** * Move back to the previous history entry. @@ -59,6 +77,8 @@ export interface History { /** * Extracts params from a URL search string and stringifies them back. + * + * @group History */ export interface SearchParamsAdapter { /** diff --git a/src/main/history/urlSearchParamsAdapter.ts b/src/main/history/urlSearchParamsAdapter.ts index 1865b60..a9b9891 100644 --- a/src/main/history/urlSearchParamsAdapter.ts +++ b/src/main/history/urlSearchParamsAdapter.ts @@ -3,6 +3,8 @@ import { SearchParamsAdapter } from './types'; /** * Parses URL search params using {@link !URLSearchParams}. + * + * @group History */ export const urlSearchParamsAdapter: SearchParamsAdapter = { parse(search) { diff --git a/src/main/history/useHistory.ts b/src/main/history/useHistory.ts index a7321ce..0229e9f 100644 --- a/src/main/history/useHistory.ts +++ b/src/main/history/useHistory.ts @@ -8,11 +8,15 @@ HistoryContext.displayName = 'HistoryContext'; /** * Provides {@link History} instance to nested elements. + * + * @group Hooks */ export const HistoryProvider = HistoryContext.Provider; /** * Returns a history provided by an enclosing {@link HistoryProvider}. + * + * @group Hooks */ export function useHistory(): History { const history = useContext(HistoryContext); diff --git a/src/main/history/useHistorySubscription.ts b/src/main/history/useHistorySubscription.ts index d30c6d1..e359623 100644 --- a/src/main/history/useHistorySubscription.ts +++ b/src/main/history/useHistorySubscription.ts @@ -5,6 +5,7 @@ import { History } from './types'; * Subscribes component to updates of a history adapter and triggers re-render when history location is changed. * * @param history The history to subscribe to. + * @group Hooks */ export function useHistorySubscription(history: History): void { const getLocation = () => history.location; diff --git a/src/main/history/utils.ts b/src/main/history/utils.ts index 6a93584..6253e7c 100644 --- a/src/main/history/utils.ts +++ b/src/main/history/utils.ts @@ -1,50 +1,86 @@ -import { Location } from '../types'; +import { Location, To } from '../types'; +import { toLocation } from '../utils'; import { urlSearchParamsAdapter } from './urlSearchParamsAdapter'; /** - * Composes a URL from a location. + * Parses a pathname-search-hash string as a location. * - * @param location A location to compose a URL from. - * @param searchParamsAdapter An adapter that creates a search string. - * @param base A base URL. + * @param to A pathname-search-hash string to parse. + * @param searchParamsAdapter An adapter that parses a search string. + * @group History */ -export function toURL(location: Location, searchParamsAdapter = urlSearchParamsAdapter, base?: string | URL): string { - const { pathname, searchParams, hash } = location; +export function parseLocation(to: string, searchParamsAdapter = urlSearchParamsAdapter): Location { + const hashIndex = to.indexOf('#'); - const search = searchParamsAdapter.stringify(searchParams); + let searchIndex = to.indexOf('?'); + if (hashIndex !== -1 && searchIndex > hashIndex) { + searchIndex = -1; + } - const url = - pathname + - (search === '' || search === '?' ? '' : search.charAt(0) === '?' ? search : '?' + search) + - (hash === '' ? '' : '#' + encodeURIComponent(hash)); + let pathname = + searchIndex === -1 && hashIndex === -1 ? to : to.substring(0, searchIndex === -1 ? hashIndex : searchIndex); - return base === undefined ? url : new URL(url, base).toString(); + return { + pathname: pathname === '' || pathname.charCodeAt(0) !== 47 ? '/' + pathname : pathname, + searchParams: searchParamsAdapter.parse( + searchIndex === -1 ? '' : to.substring(searchIndex + 1, hashIndex === -1 ? undefined : hashIndex) + ), + hash: hashIndex === -1 ? '' : decodeURIComponent(to.substring(hashIndex + 1)), + state: undefined, + }; } /** - * Parses a URL string as a location. + * Stringifies a location as pathname-search-hash string. * - * @param url A URL to parse. - * @param searchParamsAdapter An adapter that parses a search string. - * @param base A base URL. + * @param to A location to stringify. + * @param searchParamsAdapter An adapter that stringifies a search string. + * @group History */ -export function parseURL(url: string, searchParamsAdapter = urlSearchParamsAdapter, base?: string | URL): Location { - const { pathname, search, hash } = new URL(url, 'https://0'); +export function stringifyLocation(to: To, searchParamsAdapter = urlSearchParamsAdapter): string { + const { pathname, searchParams, hash } = toLocation(to); + + const search = searchParamsAdapter.stringify(searchParams); - let basePathname; + return ( + pathname + + (search === '' || search === '?' ? '' : search.charCodeAt(0) === 63 ? search : '?' + search) + + (hash === '' ? '' : '#' + encodeURIComponent(hash)) + ); +} - if (base !== undefined) { - base = typeof base === 'string' ? new URL(base) : base; - basePathname = base.pathname.endsWith('/') ? base.pathname.slice(0, -1) : base.pathname; +export function rebasePathname(basePathname: string | undefined, pathname: string): string { + if (basePathname === undefined || basePathname === '') { + return pathname; } + return ( + (basePathname.endsWith('/') ? basePathname.slice(0, -1) : basePathname) + + (pathname.charCodeAt(0) === 47 ? pathname : '/' + pathname) + ); +} - return { - pathname: - basePathname !== undefined && pathname.startsWith(basePathname) - ? pathname.substring(basePathname.length) - : pathname, - searchParams: searchParamsAdapter.parse(search), - hash: decodeURIComponent(hash.substring(1)), - state: undefined, - }; +export function debasePathname(basePathname: string | undefined, pathname: string): string { + if (basePathname === undefined || basePathname === '') { + return pathname; + } + if (pathname === basePathname) { + return '/'; + } + + let charCode; + + if ( + pathname.length > basePathname.length && + pathname.startsWith(basePathname) && + (basePathname.endsWith('/') || + (charCode = pathname.charCodeAt(basePathname.length)) === 47 || + charCode === 63 || + charCode === 35) + ) { + pathname = pathname.substring(basePathname.length); + + return pathname === '' || pathname.charCodeAt(0) !== 47 ? '/' + pathname : pathname; + } + + throw new Error("Pathname doesn't match base pathname: " + basePathname); } diff --git a/src/main/index.ts b/src/main/index.ts index 3156e8a..8ab6117 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,6 +5,7 @@ export { Link } from './history/Link'; export { urlSearchParamsAdapter } from './history/urlSearchParamsAdapter'; export { useHistory, HistoryProvider } from './history/useHistory'; export { useHistorySubscription } from './history/useHistorySubscription'; +export { parseLocation, stringifyLocation } from './history/utils'; export { createRoute } from './createRoute'; export { notFound, NotFoundError } from './notFound'; diff --git a/src/main/notFound.ts b/src/main/notFound.ts index 1e002cc..36aa76f 100644 --- a/src/main/notFound.ts +++ b/src/main/notFound.ts @@ -3,6 +3,7 @@ * {@link RouteOptions.notFoundComponent}. * * @param message An optional message of a {@link NotFoundError}. + * @group Routing */ export function notFound(message?: string): never { throw new NotFoundError(message); @@ -13,6 +14,7 @@ export function notFound(message?: string): never { * instead of the matched route. * * Use {@link notFound} to create a {@link NotFoundError} instance. + * @group Routing */ export class NotFoundError extends Error {} diff --git a/src/main/redirect.ts b/src/main/redirect.ts index 1ec47aa..f72d625 100644 --- a/src/main/redirect.ts +++ b/src/main/redirect.ts @@ -3,6 +3,8 @@ import { toLocation } from './utils'; /** * Options of the {@link redirect} function. + * + * @group Routing */ export interface RedirectOptions { /** @@ -17,6 +19,8 @@ export interface RedirectOptions { * Throws a {@link Redirect} instance that redirects router to a location. * * During SSR, redirects abort rendering. On the client, redirects trigger {@link RouterProps.onReplace}. + * + * @group Routing */ export function redirect(to: To, options?: RedirectOptions): never { throw new Redirect(toLocation(to), options?.isPermanent); @@ -26,6 +30,8 @@ export function redirect(to: To, options?: RedirectOptions): never { * A redirect to a location. * * Use {@link redirect} to create a {@link Redirect} instance. + * + * @group Routing */ export class Redirect { /** diff --git a/src/main/types.ts b/src/main/types.ts index 6f6d5fa..981a2bb 100644 --- a/src/main/types.ts +++ b/src/main/types.ts @@ -6,11 +6,15 @@ export interface Dict { /** * A location or route that doesn't have required search params. + * + * @group Routing */ export type To = Location | { getLocation(): Location }; /** * A location contains information about the URL path and history state. + * + * @group Routing */ export interface Location { /** @@ -36,6 +40,8 @@ export interface Location { /** * Non-essential {@link Location} options. + * + * @group Routing */ export interface LocationOptions { /** @@ -56,6 +62,7 @@ export interface LocationOptions { * * @template Params Route params. * @see {@link urlSearchParamsAdapter} + * @group Routing */ export interface ParamsAdapter { /** @@ -94,6 +101,8 @@ export interface ParamsAdapter { *
If another route is currently rendered then it would be preserved until component and loader of a newly matched * route are being loaded. Otherwise, a {@link RouteOptions.loadingComponent} is rendered.
* + * + * @group Routing */ export type LoadingAppearance = 'loading' | 'auto'; @@ -106,6 +115,8 @@ export type LoadingAppearance = 'loading' | 'auto'; *
"client"
*
Route is rendered exclusively on the client. During SSR loading state is rendered.
* + * + * @group Routing */ export type RenderingDisposition = 'server' | 'client'; @@ -115,6 +126,7 @@ export type RenderingDisposition = 'server' | 'client'; * @template Params Route params. * @template Data Data loaded by a route. * @template Context A context provided by a {@link Router} for a {@link loader}. + * @group Routing */ export interface RouteOptions { /** @@ -219,6 +231,8 @@ export interface RouteOptions { /** * A state rendered by a route component. + * + * @group Routing */ export interface RouteState { /** diff --git a/src/main/useNavigation.ts b/src/main/useNavigation.ts index 5028581..1ecc4f8 100644 --- a/src/main/useNavigation.ts +++ b/src/main/useNavigation.ts @@ -3,6 +3,8 @@ import { useInternalRouter } from './useInternalRouter'; /** * Returns a {@link Navigation} that controls an enclosing router. + * + * @group Hooks */ export function useNavigation(): Navigation { return useInternalRouter().navigation; diff --git a/src/main/useRouteData.ts b/src/main/useRouteData.ts index 35a17f6..000c6c3 100644 --- a/src/main/useRouteData.ts +++ b/src/main/useRouteData.ts @@ -7,6 +7,7 @@ import { useSlotController } from './useSlotController'; * * @param route The route to retrieve data for. * @template Data Data loaded by a route. + * @group Hooks */ export function useRouteData(route: Route): Data { const controller = useSlotController(); diff --git a/src/main/useRouteError.ts b/src/main/useRouteError.ts index e2191be..f922ee6 100644 --- a/src/main/useRouteError.ts +++ b/src/main/useRouteError.ts @@ -5,6 +5,8 @@ import { useSlotController } from './useSlotController'; * Returns an error that occurred during route content loading, or during rendering. * * If there's no then `undefined` is returned. + * + * @group Hooks */ export function useRouteError(): unknown { const controller = useSlotController(); diff --git a/src/main/useRouteParams.ts b/src/main/useRouteParams.ts index 41b7bbe..52c753f 100644 --- a/src/main/useRouteParams.ts +++ b/src/main/useRouteParams.ts @@ -7,6 +7,7 @@ import { useSlotController } from './useSlotController'; * * @param route The route to retrieve params for. * @template Params Route params. + * @group Hooks */ export function useRouteParams(route: Route): Params { const controller = useSlotController(); diff --git a/src/test/history/createBrowserHistory.test.ts b/src/test/history/createBrowserHistory.test.ts index 543b1c7..2b94f30 100644 --- a/src/test/history/createBrowserHistory.test.ts +++ b/src/test/history/createBrowserHistory.test.ts @@ -14,17 +14,12 @@ describe('createBrowserHistory', () => { expect(window.location.href).toBe('http://localhost/aaa/bbb'); - expect(createBrowserHistory({ base: 'http://localhost/aaa' }).location).toEqual({ + expect(createBrowserHistory({ basePathname: '/aaa' }).location).toEqual({ pathname: '/bbb', searchParams: {}, hash: '', }); - expect(createBrowserHistory({ base: 'http://localhost/aaa/' }).location).toEqual({ - pathname: '/bbb', - searchParams: {}, - hash: '', - }); - expect(createBrowserHistory({ base: 'http://xxx.zzz/aaa' }).location).toEqual({ + expect(createBrowserHistory({ basePathname: '/aaa/' }).location).toEqual({ pathname: '/bbb', searchParams: {}, hash: '', @@ -162,7 +157,7 @@ describe('createBrowserHistory', () => { expect(searchParamsAdapterMock.stringify).toHaveBeenCalledTimes(1); expect(searchParamsAdapterMock.parse).toHaveBeenCalledTimes(1); - expect(searchParamsAdapterMock.parse).toHaveBeenNthCalledWith(1, '?xxx=111'); + expect(searchParamsAdapterMock.parse).toHaveBeenNthCalledWith(1, 'xxx=111'); }); test('creates a URL', async () => { @@ -173,7 +168,11 @@ describe('createBrowserHistory', () => { test('creates a URL with a default base', async () => { expect( - createBrowserHistory({ base: 'http://bbb.ccc' }).toURL({ pathname: '/aaa', searchParams: { xxx: 111 }, hash: '' }) + createBrowserHistory({ basePathname: 'http://bbb.ccc' }).toURL({ + pathname: '/aaa', + searchParams: { xxx: 111 }, + hash: '', + }) ).toBe('http://bbb.ccc/aaa?xxx=111'); }); }); diff --git a/src/test/history/createHashHistory.test.ts b/src/test/history/createHashHistory.test.ts index 4ef7772..b4f7ceb 100644 --- a/src/test/history/createHashHistory.test.ts +++ b/src/test/history/createHashHistory.test.ts @@ -140,7 +140,7 @@ describe('createHashHistory', () => { expect(searchParamsAdapterMock.stringify).toHaveBeenCalledTimes(1); expect(searchParamsAdapterMock.parse).toHaveBeenCalledTimes(1); - expect(searchParamsAdapterMock.parse).toHaveBeenNthCalledWith(1, '?xxx=111'); + expect(searchParamsAdapterMock.parse).toHaveBeenNthCalledWith(1, 'xxx=111'); }); test('creates a URL', async () => { @@ -151,7 +151,11 @@ describe('createHashHistory', () => { test('creates a URL with a default base', async () => { expect( - createHashHistory({ base: 'http://bbb.ccc' }).toURL({ pathname: '/aaa', searchParams: { xxx: 111 }, hash: '' }) + createHashHistory({ basePathname: 'http://bbb.ccc' }).toURL({ + pathname: '/aaa', + searchParams: { xxx: 111 }, + hash: '', + }) ).toBe('http://bbb.ccc/#%2Faaa%3Fxxx%3D111'); }); }); diff --git a/src/test/history/utils.test.ts b/src/test/history/utils.test.ts index 6a21979..80eca0d 100644 --- a/src/test/history/utils.test.ts +++ b/src/test/history/utils.test.ts @@ -1,46 +1,32 @@ -import { urlSearchParamsAdapter } from '../../main'; -import { parseURL, toURL } from '../../main/history/utils'; +import { parseLocation, stringifyLocation } from '../../main/history/utils'; describe('toURL', () => { test('returns a URL', () => { - expect(toURL({ pathname: '/aaa', searchParams: {}, hash: '' })).toBe('/aaa'); - expect(toURL({ pathname: '/aaa', searchParams: {}, hash: '#$%' })).toBe('/aaa#%23%24%25'); - expect(toURL({ pathname: '/aaa', searchParams: { xxx: 111, yyy: 222 }, hash: '' })).toBe('/aaa?xxx=111&yyy=222'); - expect( - toURL({ pathname: '/aaa', searchParams: { xxx: 111, yyy: 222 }, hash: '' }, urlSearchParamsAdapter, 'http://zzz') - ).toBe('http://zzz/aaa?xxx=111&yyy=222'); + expect(stringifyLocation({ pathname: '/aaa', searchParams: {}, hash: '' })).toBe('/aaa'); + expect(stringifyLocation({ pathname: '/aaa', searchParams: {}, hash: '#$%' })).toBe('/aaa#%23%24%25'); + expect(stringifyLocation({ pathname: '/aaa', searchParams: { xxx: 111, yyy: 222 }, hash: '' })).toBe( + '/aaa?xxx=111&yyy=222' + ); }); }); -describe('parseURL', () => { +describe('parseLocation', () => { test('parses a URL', () => { - expect(parseURL('/aaa')).toEqual({ pathname: '/aaa', searchParams: {}, hash: '' }); - expect(parseURL('/aaa#')).toEqual({ pathname: '/aaa', searchParams: {}, hash: '' }); - expect(parseURL('/aaa?')).toEqual({ pathname: '/aaa', searchParams: {}, hash: '' }); - expect(parseURL('/aaa?#')).toEqual({ pathname: '/aaa', searchParams: {}, hash: '' }); + expect(parseLocation('/aaa')).toEqual({ pathname: '/aaa', searchParams: {}, hash: '' }); + expect(parseLocation('/aaa#')).toEqual({ pathname: '/aaa', searchParams: {}, hash: '' }); + expect(parseLocation('/aaa?')).toEqual({ pathname: '/aaa', searchParams: {}, hash: '' }); + expect(parseLocation('/aaa?#')).toEqual({ pathname: '/aaa', searchParams: {}, hash: '' }); - expect(parseURL('/aaa?xxx=111')).toEqual({ + expect(parseLocation('/aaa?xxx=111')).toEqual({ pathname: '/aaa', searchParams: { xxx: '111' }, hash: '', }); - expect(parseURL('/aaa#%23%24%25')).toEqual({ + expect(parseLocation('/aaa#%23%24%25')).toEqual({ pathname: '/aaa', searchParams: {}, hash: '#$%', }); - - expect(parseURL('https://example.com/aaa')).toEqual({ - pathname: '/aaa', - searchParams: {}, - hash: '', - }); - - expect(parseURL('https://example.com/aaa/bbb', urlSearchParamsAdapter, 'http://xxx.yyy/aaa')).toEqual({ - pathname: '/bbb', - searchParams: {}, - hash: '', - }); }); });