All files / router/src helpers.ts

97.82% Statements 90/92
93.54% Branches 29/31
100% Functions 10/10
97.82% Lines 90/92

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 135 136 137 138 139 140 141 142 143 144 145                1x   1x 435x   435x 435x   1x   1x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x   1x 63x 63x   63x 63x 63x   63x 63x 63x     63x 63x   63x 63x   1x   69x 2x 2x     67x 67x     69x 22x 22x     45x     45x     45x 45x   1x 7x 22x 22x 7x 7x   1x 19x   19x 55x 15x 15x 55x 19x 19x   1x 22x     22x 22x     22x 3x 3x     22x 55x     55x   19x   19x     19x 19x 55x   19x 19x   1x 1x   1x       1x 1x   1x 2x   2x 1x   1x 1x
import type { 
    NavigationResult, 
    NavigationState, 
    NavigationHistoryEntry, 
    QueryParams
} from '#src/types';
 
// Utility functions
const generateRandomId = () => Math.random().toString(36).substring(2, 9);
 
export const getHash = (url: string): string => {
    const urlObject = new URL(url);
 
    return urlObject.hash.slice(1).replace('/', '') || '/';
};
 
export const createHash = (hash: string) => `/${hash}`;
 
export const createHistoryEntry = (
    url: string, 
    state: NavigationState = {}, 
    index: number = 0
): NavigationHistoryEntry => {
    return {
        url,
        key         : generateRandomId(),
        id          : generateRandomId(),
        index,
        sameDocument: true,
        state,
        hash        : getHash(url),
    };
};
 
export const createNavigationResult = (destination: NavigationHistoryEntry): NavigationResult => {
    let commitResolve: (value: NavigationHistoryEntry) => void;
    let finishResolve: (value: NavigationHistoryEntry) => void;
    
    const committed = new Promise<NavigationHistoryEntry>((resolve) => {
        commitResolve = resolve;
    });
    
    const finished = new Promise<NavigationHistoryEntry>((resolve) => {
        finishResolve = resolve;
    });
    
    // Use setTimeout to simulate the async nature of navigation
    setTimeout(() => commitResolve(destination), 0);
    setTimeout(() => finishResolve(destination), 10);
    
    return { committed, finished, };
};
 
export const isRouteMatch = (pattern: string, hash: string) => {
    // Handle null or undefined hash
    if(hash === null || hash === undefined) {
        return false;
    }
 
    // Split the pattern and URL into segments to ensure they have the same length
    const patternSegments = pattern.split('/');
    const hashSegments = hash.split('/');
    
    // If the segments don't match in length, return false
    if(patternSegments.length !== hashSegments.length) {
        return false;
    }
    
    // Replace route parameters with a regex pattern that matches any characters
    const patternRegex = pattern.replace(/:\w+/g, '([^/]+)');
  
    // Create a regular expression for matching the URL
    const regex = new RegExp(`^${patternRegex}$`);
    
    // Check if the hash matches the pattern
    return regex.test(hash);
};
 
export const getRouteMap = (routeNames: string[]) => {
    return routeNames.reduce((acc, name) => {
        acc[name] = name;
        return acc;
    }, {} as Record<string, string>);
};
 
export const getRouteItem = <T>(map: Record<string, T>, hash: string) => {
    let route: T | undefined;
 
    return Object.keys(map).reduce((acc, key) => {
        if(isRouteMatch(key, hash)) {
            acc = map[key];
        }
        return acc;
    }, route);
};
 
export const getParamsFromUrl = (pattern: string, hash: string): Record<string, string> => {
    const params: Record<string, string> = {};
    
    // Split the pattern and URL into segments
    const patternSegments = pattern.split('/');
    const urlSegments = hash.split('/');
    
    // If the segments don't match in length, return empty params
    if(patternSegments.length !== urlSegments.length) {
        return params;
    }
    
    // Iterate through the pattern segments
    for(let i = 0; i < patternSegments.length; i++) {
        const patternSegment = patternSegments[i];
        
        // Check if the segment is a parameter (starts with ':')
        if(patternSegment.startsWith(':')) {
            // Extract the parameter name (remove the ':')
            const paramName = patternSegment.substring(1);
            // Get the corresponding value from the URL
            const paramValue = urlSegments[i];
 
            // Add to the params object
            params[paramName] = paramValue;
        }
    }
    
    return params;
};
 
export const parseQueryParams = <T extends QueryParams = QueryParams>(urlPart: string): T => {
    const queryString = urlPart.split('?')[1];
 
    if(!queryString) {
        return {} as T;
    }
  
    const params = queryString.split('&');
    const queryParams: { [key: string]: string } = {};
  
    params.forEach((param) => {
        const [key, value] = param.split('=');
 
        queryParams[key] = value ? decodeURIComponent(value) : '';
    });
  
    return queryParams as T;
};