All files util.ts

100% Statements 87/87
100% Branches 42/42
100% Functions 8/8
100% Lines 87/87

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134  2x   2x 1133x 1133x 1132x 1130x 1130x 11x 1119x 1130x   1133x   2x 11461x 11461x 8167x   11461x               2x 2284x 2284x 2284x 2253x 2253x   31x 31x   2x 260x 254x   2x 252x 260x 237x 237x   2x 7619x 7619x 7619x 7619x 6600x 5811x 5796x   7619x   2x   528x 249x 249x     508x 1x 1x     278x 278x   278x 278x 278x 278x                         2x 990x 990x 990x 990x 990x   625x 867x   363x   990x   166x   990x   95x   990x 1x 990x 990x   2x 2232x 2232x 2232x   2232x 1321x 1321x 3x 9x 3x 1321x 1318x 1318x 1321x   2232x 2232x  
import type { Route, RouteConfirmHookResult, RouteMatchType } from './types';
export const isBrowser = typeof window === 'object';
 
export function isNotNullish(value: unknown): boolean {
    return (
        value !== undefined &&
        value !== null &&
        !Number.isNaN(
            value instanceof Number
                ? value.valueOf() // For new Number() cases
                : value
        )
    );
}
 
export function isPlainObject(o: unknown): o is Record<string, any> {
    return (
        o?.constructor === Object ||
        Object.prototype.toString.call(o) === '[object Object]'
    );
}
 
/**
 * Check if value is a valid non-empty plain object
 * Only check enumerable string keys to ensure it's a proper plain object
 * @param value Value to check
 * @returns true if it's a valid non-empty plain object
 */
export function isNonEmptyPlainObject(
    value: unknown
): value is Record<string, any> {
    if (!isPlainObject(value)) {
        return false;
    }
    // Only check enumerable string keys to ensure valid properties of plain object
    return Object.keys(value as Record<string, any>).length > 0;
}
 
export const removeFromArray = <T>(arr: T[], ele: T) => {
    if (!Array.isArray(arr) || arr.length === 0) return;
    const i = Number.isNaN(ele)
        ? // If ele is NaN, use findIndex to search for NaN, because NaN !== NaN, so we can't use indexOf directly
          arr.findIndex((item) => Number.isNaN(item))
        : arr.indexOf(ele);
    if (i === -1) return;
    arr.splice(i, 1);
};
 
export function isValidConfirmHookResult(
    result: unknown
): result is Exclude<RouteConfirmHookResult, void> {
    return (
        result === false ||
        typeof result === 'function' ||
        typeof result === 'string' ||
        isPlainObject(result)
    );
}
 
export function isUrlEqual(url1: URL, url2?: URL | null): boolean {
    // If url2 doesn't exist, return false
    if (!url2) {
        return false;
    }
 
    // If it's the same object reference, return true directly
    if (url1 === url2) {
        return true;
    }
 
    // Copy and sort query parameters
    (url1 = new URL(url1)).searchParams.sort();
    (url2 = new URL(url2)).searchParams.sort();
    // Avoid trailing hash symbol impact from empty hash
    url1.hash = url1.hash;
    url2.hash = url2.hash;
    return url1.href === url2.href;
}
 
/**
 * Compare if two routes match
 *
 * @param fromRoute First route object
 * @param toRoute Second route object, may be null
 * @param matchType Match type
 * - 'route': Route-level matching, compare if route configurations are the same
 * - 'exact': Exact matching, compare if full paths are the same
 * - 'include': Include matching, check if route1 path starts with route2 path
 * @returns Whether they match
 */
export function isRouteMatched(
    fromRoute: Route,
    toRoute: Route | null,
    matchType: RouteMatchType
): boolean {
    if (!toRoute) return false;
 
    switch (matchType) {
        case 'route':
            // Route-level matching - compare route configurations
            return fromRoute.config === toRoute.config;
 
        case 'exact':
            // Exact matching - full paths are identical
            return fromRoute.fullPath === toRoute.fullPath;
 
        case 'include':
            // Include matching - route1 path contains route2 path
            return fromRoute.fullPath.startsWith(toRoute.fullPath);
 
        default:
            return false;
    }
}
 
export function decodeParams<T extends Record<string, string | string[]>>(
    params: T
): T {
    const result = {} as T;
 
    for (const key in params) {
        const value = params[key];
        if (Array.isArray(value)) {
            result[key] = value.map((item) =>
                decodeURIComponent(item)
            ) as T[typeof key];
        } else {
            result[key] = decodeURIComponent(value) as T[typeof key];
        }
    }
 
    return result;
}