• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jumpinjackie / mapguide-react-layout / 15160437878

21 May 2025 11:00AM UTC coverage: 21.631% (-42.6%) from 64.24%
15160437878

Pull #1552

github

web-flow
Merge 8b7153d9e into 236e2ea07
Pull Request #1552: Feature/package updates 2505

839 of 1165 branches covered (72.02%)

11 of 151 new or added lines in 25 files covered. (7.28%)

1332 existing lines in 50 files now uncovered.

4794 of 22163 relevant lines covered (21.63%)

6.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.25
/src/utils/url.ts
1
import queryString from "qs";
1✔
2
import type { IInvokeUrlCommandParameter } from "../api/common";
3
import { strIsNullOrEmpty } from './string';
1✔
4
const parse = require("url-parse");
1✔
5

6
const DEFAULT_PARSE_OPTIONS = { ignoreQueryPrefix: true };
1✔
7

8
/**
9
 * Indicates if the given arrays are equal
10
 *
11
 * @param {(string[]|null)} a
12
 * @param {(string[]|null)} b
13
 * @returns {boolean}
14
 */
15
function arraysEqual(a: string[] | null, b: string[] | null): boolean {
3✔
16
    if (a === b) return true;
3!
17
    if (a == null || b == null) return false;
3!
18
    if (a.length != b.length) return false;
3!
19

20
    // If you don't care about the order of the elements inside
21
    // the array, you should sort both arrays here.
22

23
    for (let i = 0; i < a.length; ++i) {
3✔
24
        if (a[i] !== b[i]) {
4!
25
            return false;
×
UNCOV
26
        }
×
27
    }
4✔
28
    return true;
3✔
29
}
3✔
30

31
/**
32
 * Indicates if the given sets of parameterse are the same
33
 *
34
 * @param {*} params1
35
 * @param {*} params2
36
 * @returns {boolean}
37
 */
38
function areParamsEqual(params1: any, params2: any): boolean {
3✔
39
    //HACK: locale is an optional part of the mapname/session/locale triplet
40
    //For the purpose of the same url test, the presence (or lack thereof) of
41
    //this parameter should not break the url equality test
42
    const keys1 = Object.keys(params1).filter(k => k.toLowerCase() != "locale").sort();
3✔
43
    const keys2 = Object.keys(params2).filter(k => k.toLowerCase() != "locale").sort();
3✔
44
    if (arraysEqual(keys1, keys2)) {
3✔
45
        for (const key of keys1) {
3✔
46
            if (params1[key] != params2[key]) {
3✔
47
                return false;
1✔
48
            }
1✔
49
        }
3✔
50
        return true;
2✔
51
    }
2!
52
    return false;
×
UNCOV
53
}
×
54

55
/**
56
 * Indicates if the given URLs are the same
57
 *
58
 * @export
59
 * @param {string} url1
60
 * @param {string} url2
61
 * @returns {boolean}
62
 */
63
export function areUrlsSame(url1: string, url2: string): boolean {
1✔
64
    const parsed1 = parse(url1);
5✔
65
    const parsed2 = parse(url2);
5✔
66
    const params1 = queryString.parse(parsed1.query, DEFAULT_PARSE_OPTIONS);
5✔
67
    const params2 = queryString.parse(parsed2.query, DEFAULT_PARSE_OPTIONS);
5✔
68

69
    const same = parsed1.protocol == parsed2.protocol
5✔
70
        && parsed1.slashes == parsed2.slashes
4✔
71
        && parsed1.auth == parsed2.auth
4✔
72
        && parsed1.username == parsed2.username
4✔
73
        && parsed1.password == parsed2.password
4✔
74
        && parsed1.host == parsed2.host
4✔
75
        && parsed1.hostname == parsed2.hostname
3✔
76
        && parsed1.port == parsed2.port
3✔
77
        && parsed1.pathname == parsed2.pathname
3✔
78
        && parsed1.hash == parsed2.hash
3✔
79
        && parsed1.host == parsed2.host
3✔
80
        && areParamsEqual(params1, params2);
3✔
81

82
    return same;
5✔
83
}
5✔
84

85
/**
86
 * A parsed component URI
87
 * 
88
 * @export
89
 * @interface ParsedComponentUri
90
 */
91
export interface ParsedComponentUri {
92
    name: string;
93
    props: any;
94
}
95

96
/**
97
 * Indicates if the given URI is a component URI
98
 * 
99
 * @export
100
 * @param {string} uri 
101
 * @returns {boolean} 
102
 */
103
export function isComponentUri(uri: string): boolean {
1✔
104
    return uri.indexOf("component://") >= 0;
16✔
105
}
16✔
106

107
/**
108
 * Parses the given component URI. If it not a valid component URI returns undefined
109
 * 
110
 * @export
111
 * @param {string} uri 
112
 * @returns {(ParsedComponentUri | undefined)} 
113
 */
114
export function parseComponentUri(uri: string): ParsedComponentUri | undefined {
1✔
115
    if (isComponentUri(uri)) {
4✔
116
        const qi = uri.lastIndexOf("?");
3✔
117
        const name = qi < 0 ? uri.substring(12) : uri.substring(12, qi);
3✔
118
        const props = qi < 0 ? {} : queryString.parse(uri.substring(qi), DEFAULT_PARSE_OPTIONS);
3✔
119
        return {
3✔
120
            name,
3✔
121
            props
3✔
122
        };
3✔
123
    }
3✔
124
}
4✔
125

126
/**
127
 * Normalizes the given URL to ensure it has the baseline set of required parameters for invoking any server-side script that uses the MapGuide Web API
128
 * 
129
 * @export
130
 * @param {string} url The url to normalize
131
 * @param {(string | undefined)} mapName The name of the current runtime map
132
 * @param {(string | undefined)} session The current session id
133
 * @param {string} [locale] An optional locale
134
 * @param {boolean} [uppercase=true] If true, will uppercase all parameter names
135
 * @param {IInvokeUrlCommandParameter[]} [extraParameters=[]] Any extra parameters to append to the URL
136
 * @returns {string} 
137
 */
138
export function ensureParameters(url: string, mapName: string | undefined, session: string | undefined, locale?: string, uppercase = true, extraParameters: IInvokeUrlCommandParameter[] = []): string {
1✔
139
    //If this is a component URL, let it be
140
    if (isComponentUri(url)) {
9✔
141
        return url;
1✔
142
    }
1✔
143
    const parsed = parseUrl(url);
8✔
144
    const params: any = parsed.query != null ? queryString.parse(parsed.query, DEFAULT_PARSE_OPTIONS) : {};
9!
145
    let bNeedMapName = true;
9✔
146
    let bNeedSession = true;
9✔
147
    let bNeedLocale = true;
9✔
148
    for (const key in params) {
9✔
149
        const name = key.toLowerCase();
12✔
150
        switch (name) {
12✔
151
            case "session":
12✔
152
                bNeedSession = false;
4✔
153
                break;
4✔
154
            case "mapname":
12✔
155
                bNeedMapName = false;
6✔
156
                break;
6✔
157
            case "locale":
12✔
158
                bNeedLocale = false;
2✔
159
                break;
2✔
160
        }
12✔
161
    }
12✔
162
    if (bNeedMapName && !strIsNullOrEmpty(mapName)) {
9✔
163
        if (uppercase) {
2✔
164
            params.MAPNAME = mapName;
2✔
165
        } else {
2!
166
            params.mapname = mapName;
×
UNCOV
167
        }
×
168
    }
2✔
169
    if (bNeedSession && !strIsNullOrEmpty(session)) {
9✔
170
        if (uppercase) {
4✔
171
            params.SESSION = session;
4✔
172
        } else {
4!
173
            params.session = session;
×
UNCOV
174
        }
×
175
    }
4✔
176
    if (bNeedLocale) {
9✔
177
        if (uppercase) {
6✔
178
            params.LOCALE = locale;
6✔
179
        } else {
6!
180
            params.locale = locale;
×
UNCOV
181
        }
×
182
    }
6✔
183

184
    for (const p of extraParameters) {
9!
185
        params[p.name] = p.value;
×
UNCOV
186
    }
✔
187

188
    /*
189
    parsed.query = queryString.stringify(params);
190
    const result = parsed.toString();
191

192
    if (url.indexOf(parsed.protocol) >= 0 || url.indexOf("/") == 0) {
193
        return result;
194
    }
195

196
    return result;
197
    */
198
    //Don't uppercase the parameters here if true, uppercasing is only for filling in
199
    //missing parameters
200
    return appendParameters(url, params, true, false /*uppercase*/);
8✔
201
}
8✔
202

203
/**
204
 * Represents a parsed URL with query string separated
205
 *
206
 * @export
207
 * @interface IParsedUrl
208
 * @since 0.12
209
 */
210
export interface IParsedUrl {
211
    url: string;
212
    query: any;
213
}
214

215
/**
216
 * Parses the given URL and separates out the query string parameters
217
 *
218
 * @export
219
 * @param {string} url The URL to parse
220
 * @returns {IParsedUrl}
221
 * @since 0.12
222
 */
223
export function parseUrl(url: string): IParsedUrl {
1✔
224
    //return queryString.parseUrl(url);
225
    const qi = url.lastIndexOf("?");
11✔
226
    const parsedUrl = qi < 0 ? url : url.substring(0, qi);
11✔
227
    const query = qi < 0 ? {} : queryString.parse(url.substring(qi), DEFAULT_PARSE_OPTIONS);
11✔
228
    return {
11✔
229
        url: parsedUrl,
11✔
230
        query
11✔
231
    };
11✔
232
}
11✔
233

234
/**
235
 * Converts the given object to a query string fragment
236
 *
237
 * @export
238
 * @param {*} parameters The object to stringify
239
 * @returns {string} The query string fragment
240
 */
241
export function stringifyQuery(parameters: any): string {
1✔
242
    return queryString.stringify(parameters);
×
UNCOV
243
}
×
244

245
/**
246
 * Appends the specified parameters to the given URL
247
 *
248
 * @export
249
 * @param {string} url The URL to append parameters to
250
 * @param {*} parameters The parameters to append
251
 * @param {boolean} [bOverwriteExisting=true] If true, will overwrite any existing parameters if the URL already has them
252
 * @param {boolean} [bConvertToUppercase=false] If true, will ensure all parameter names are uppercase
253
 * @param {boolean} [bDiscardExistingParams=false] If true, will discard existing query string params before appending
254
 * @since 0.12
255
 */
256
export function appendParameters(url: string, parameters: any, bOverwriteExisting: boolean = true, bConvertToUppercase: boolean = false, bDiscardExistingParams: boolean = false) {
1✔
257
    const parsed = parse(url);
22✔
258
    let currentParams: any;
22✔
259
    if (!bDiscardExistingParams) {
22✔
260
        currentParams = parsed.query != null ? queryString.parse(parsed.query, DEFAULT_PARSE_OPTIONS) : {};
22!
261
    } else {
22!
262
        currentParams = {};
×
UNCOV
263
    }
×
264

265
    const paramNames: any = {};
22✔
266
    for (const key in currentParams) {
22✔
267
        paramNames[key.toUpperCase()] = key;
20✔
268
    }
20✔
269

270
    for (const name in parameters) {
22✔
271
        //See if this parameter name was normalized
272
        const key = paramNames[name.toUpperCase()] || name;
38✔
273
        //If it was and we've got an existing value there, skip if not overwriting
274
        if (key && currentParams[key] && !bOverwriteExisting) {
38✔
275
            continue;
4✔
276
        }
4✔
277
        //Put the parameter value
278
        currentParams[key] = parameters[name];
34✔
279
    }
34✔
280

281
    if (bConvertToUppercase) {
22✔
282
        let params2: any = {};
6✔
283
        for (const name in currentParams) {
6✔
284
            params2[name.toUpperCase()] = currentParams[name];
6✔
285
        }
6✔
286
        currentParams = params2;
6✔
287
    }
6✔
288

289
    parsed.query = queryString.stringify(currentParams);
22✔
290
    const result = parsed.toString();
22✔
291

292
    if (url.indexOf(parsed.protocol) >= 0 || url.indexOf("/") == 0) {
22!
293
        return result;
22✔
294
    }
22!
295

296
    return result;
×
UNCOV
297
}
×
298

299
/**
300
 * Parses the query string section of the given URL and returns the parsed
301
 * parameters as an object
302
 * @param url The URL to parse
303
 * @since 0.13
304
 */
305
export function parseUrlParameters(url: string): any {
1✔
306
    const parsed = parse(url);
×
307
    const currentParams: any = parsed.query != null ? queryString.parse(parsed.query, DEFAULT_PARSE_OPTIONS) : {};
×
308
    return currentParams;
×
UNCOV
309
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc