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

jumpinjackie / mapguide-react-layout / 26033267383

18 May 2026 12:23PM UTC coverage: 60.531% (+0.9%) from 59.676%
26033267383

push

github

web-flow
Dispatchable init action (#1648)

* Update instructions

* Refactor initialization logic and remove viewer command dependency

- Replaced the `initLayout` command with `fetchInitDocument` and `initAppFromDocument` in the App component.
- Updated the `IInitAppLayout` interface to accommodate `WebLayout` alongside `ApplicationDefinition`.
- Enhanced error handling by exporting `processAndDispatchInitError`.
- Removed the `IViewerInitCommand` dependency from various components and tests.
- Adjusted tests to reflect changes in initialization logic and ensure proper mocking of new functions.
- Cleaned up imports and unused code related to the old initialization command.

* Add integration tests for init dispatchable action and snapshot for redux state

- Implemented a new test file `init-dispatchable.spec.ts` to test the `initAppFromDocument` action.
- Created a mock client to simulate API interactions.
- Added a snapshot file `init-dispatchable.spec.ts.snap` to capture the expected redux baseline state after dispatching the action.
- Ensured the legacy app definition is correctly de-arrayified and integrated into the redux store.

3235 of 3962 branches covered (81.65%)

447 of 723 new or added lines in 4 files covered. (61.83%)

9 existing lines in 1 file now uncovered.

15990 of 26416 relevant lines covered (60.53%)

14.06 hits per line

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

96.67
/src/actions/init-command.ts
1
import type { IMapSwipePair } from '../api/common';
2
import { IGenericSubjectMapLayer } from './defs';
3
import { makeUnique } from '../utils/array';
1✔
4
import { ApplicationDefinition, MapConfiguration, MapSetGroup } from '../api/contracts/fusion';
5
import { MgError } from '../api/error';
1✔
6
import { strStartsWith } from '../utils/string';
1✔
7
import { IClusterSettings } from '../api/ol-style-contracts';
8

9
/**
10
 * Parses swipe pair declarations from the application definition's MapSet.
11
 *
12
 * A swipe pair is declared by adding Extension.SwipePairWith (the paired map group id)
13
 * and Extension.SwipePrimary ("true" or "false") to a MapGroup element.
14
 *
15
 * @since 0.15
16
 */
17
export function parseSwipePairs(appDef: ApplicationDefinition): IMapSwipePair[] {
1✔
18
    const pairs: IMapSwipePair[] = [];
7✔
19
    const seen = new Set<string>();
7✔
20
    if (!appDef.MapSet?.MapGroup) {
7✔
21
        return pairs;
1✔
22
    }
1✔
23
    for (const mg of appDef.MapSet.MapGroup) {
7✔
24
        const ext = mg.Extension;
13✔
25
        if (!ext) {
13✔
26
            continue;
8✔
27
        }
8✔
28
        const swipePairWith = ext.SwipePairWith as string | undefined;
5✔
29
        const swipePrimary = ext.SwipePrimary as string | undefined;
5✔
30
        if (swipePairWith && swipePrimary?.toLowerCase() === "true") {
13✔
31
            const primaryId = mg["@id"];
4✔
32
            const pairKey = [primaryId, swipePairWith].sort().join("|");
4✔
33
            if (!seen.has(pairKey)) {
4✔
34
                seen.add(pairKey);
3✔
35
                const primaryLabel = ext.SwipePrimaryLabel as string | undefined;
3✔
36
                const secondaryLabel = ext.SwipeSecondaryLabel as string | undefined;
3✔
37
                pairs.push({
3✔
38
                    primaryMapName: primaryId,
3✔
39
                    secondaryMapName: swipePairWith,
3✔
40
                    ...(primaryLabel ? { primaryLabel } : {}),
3!
41
                    ...(secondaryLabel ? { secondaryLabel } : {})
3!
42
                });
3✔
43
            }
3✔
44
        }
4✔
45
    }
13✔
46
    return pairs;
6✔
47
}
6✔
48

49
/**
50
 * Parses a map-level mouse coordinate format override from the first map
51
 * configuration inside a MapGroup.
52
 *
53
 * Supported extension key on the first map's Extension object:
54
 * - MouseCoordinatesFormat
55
 *
56
 * @hidden
57
 * @since 0.15
58
 */
59
export function parseMapGroupCoordinateFormat(mapGroup: MapSetGroup): string | undefined {
1✔
60
    const ext = mapGroup.Map?.[0]?.Extension;
14✔
61
    if (!ext) {
14✔
62
        return undefined;
2✔
63
    }
2✔
64
    const candidate = ext.MouseCoordinatesFormat;
12✔
65
    if (typeof candidate === "string" && candidate.trim().length > 0) {
14✔
66
        return candidate;
3✔
67
    }
3✔
68
    return undefined;
9✔
69
}
9✔
70

71
const TYPE_SUBJECT = "SubjectLayer";
1✔
72
const TYPE_EXTERNAL = "External";
1✔
73

74
export type SessionInit = {
75
    session: string;
76
    sessionWasReused: boolean;
77
}
78

79
function getMapGuideConfiguration(appDef: ApplicationDefinition): [string, MapConfiguration][] {
23✔
80
    const configs = [] as [string, MapConfiguration][];
23✔
81
    if (appDef.MapSet) {
23✔
82
        for (const mg of appDef.MapSet.MapGroup) {
23✔
83
            for (const map of mg.Map) {
87✔
84
                if (map.Type == "MapGuide") {
135✔
85
                    configs.push([mg["@id"], map]);
86✔
86
                }
86✔
87
            }
135✔
88
        }
87✔
89
    }
23✔
90
    return configs;
23✔
91
}
23✔
92

93
function tryExtractMapMetadata(extension: any) {
86✔
94
    const ext: any = {};
86✔
95
    for (const k in extension) {
86✔
96
        if (strStartsWith(k, "Meta_")) {
214✔
97
            const sk = k.substring("Meta_".length);
2✔
98
            ext[sk] = extension[k];
2✔
99
        }
2✔
100
    }
214✔
101
    return ext;
86✔
102
}
86✔
103

104
export function buildSubjectLayerDefn(name: string, map: MapConfiguration): IGenericSubjectMapLayer {
1✔
105
    const st = map.Extension.source_type;
2✔
106
    const initiallyVisible = map.Extension.initially_visible ?? true;
2!
107
    const sp: any = {};
2✔
108
    const lo: any = {};
2✔
109
    const meta: any = {};
2✔
110
    const keys = Object.keys(map.Extension);
2✔
111
    let popupTemplate = map.Extension.popup_template;
2✔
112
    let selectable: boolean | undefined = map.Extension.is_selectable ?? true;
2✔
113
    let disableHover: boolean | undefined = map.Extension.disable_hover ?? false;
2✔
114
    for (const k of keys) {
2✔
115
        const spidx = k.indexOf("source_param_");
18✔
116
        const loidx = k.indexOf("layer_opt_");
18✔
117
        const midx = k.indexOf("meta_");
18✔
118
        if (spidx == 0) {
18✔
119
            const kn = k.substring("source_param_".length);
2✔
120
            sp[kn] = map.Extension[k];
2✔
121
        } else if (loidx == 0) {
18✔
122
            const kn = k.substring("layer_opt_".length);
1✔
123
            lo[kn] = map.Extension[k];
1✔
124
        } else if (midx == 0) {
16✔
125
            const kn = k.substring("meta_".length);
1✔
126
            meta[kn] = map.Extension[k];
1✔
127
        }
1✔
128
    }
18✔
129
    const sl = {
2✔
130
        name: name,
2✔
131
        description: map.Extension.layer_description,
2✔
132
        displayName: map.Extension.display_name,
2✔
133
        driverName: map.Extension.driver_name,
2✔
134
        type: st,
2✔
135
        layerOptions: lo,
2✔
136
        sourceParams: sp,
2✔
137
        meta: (Object.keys(meta).length > 0 ? meta : undefined),
2✔
138
        initiallyVisible,
2✔
139
        selectable,
2✔
140
        disableHover,
2✔
141
        popupTemplate,
2✔
142
        vectorStyle: map.Extension.vector_layer_style
2✔
143
    } as IGenericSubjectMapLayer;
2✔
144

145
    if (map.Extension.cluster) {
2✔
146
        sl.cluster = {
1✔
147
            ...map.Extension.cluster
1✔
148
        } as IClusterSettings;
1✔
149
    }
1✔
150
    return sl;
2✔
151
}
2✔
152

153
export function getMapDefinitionsFromFlexLayout(appDef: ApplicationDefinition): (MapToLoad | IGenericSubjectMapLayer)[] {
1✔
154
    const maps = [] as (MapToLoad | IGenericSubjectMapLayer)[];
23✔
155
    const configs = getMapGuideConfiguration(appDef);
23✔
156
    if (configs.length > 0) {
23✔
157
        for (const c of configs) {
22✔
158
            maps.push({ 
86✔
159
                name: c[0],
86✔
160
                mapDef: c[1].Extension.ResourceId,
86✔
161
                metadata: tryExtractMapMetadata(c[1].Extension)
86✔
162
            });
86✔
163
        }
86✔
164
    }
22✔
165
    if (appDef.MapSet?.MapGroup) {
23✔
166
        for (const mGroup of appDef.MapSet.MapGroup) {
23✔
167
            for (const map of mGroup.Map) {
87✔
168
                if (map.Type == TYPE_SUBJECT) {
135✔
169
                    const name = mGroup["@id"];
1✔
170
                    maps.push(buildSubjectLayerDefn(name, map));
1✔
171
                }
1✔
172
            }
135✔
173
        }
87✔
174
    }
23✔
175
    if (maps.length == 0)
23✔
176
        throw new MgError("No Map Definition or subject layer found in Application Definition");
23✔
177

178
    return maps;
22✔
179
}
22✔
180

181
export type MapToLoad = { name: string, mapDef: string, metadata: any };
182

183
export function isMapDefinition(arg: MapToLoad | IGenericSubjectMapLayer): arg is MapToLoad {
1✔
184
    return (arg as any).mapDef != null;
32✔
185
}
32✔
186

187
export function isStateless(appDef: ApplicationDefinition) {
1✔
188
    // This appdef is stateless if:
189
    //
190
    //  1. It has a Stateless extension property set to "true" (ie. The author has opted-in to this feature)
191
    //  2. No MapGuide Map Definitions were found in the appdef
192
    if (appDef.Extension?.Stateless == "true")
30✔
193
        return true;
30✔
194

195
    try {
20✔
196
        const maps = getMapDefinitionsFromFlexLayout(appDef);
20✔
197
        for (const m of maps) {
20✔
198
            if (isMapDefinition(m)) {
20✔
199
                return false;
20✔
200
            }
20✔
201
        }
20!
202
        return true;
×
203
    } catch (e) {
×
204
        return true;
×
205
    }
×
206
}
30✔
207

208
/**
209
 * @hidden
210
 * @since 0.15
211
 */
212
export function getExtraProjectionsFromFlexLayout(appDef: ApplicationDefinition): string[] {
1✔
213
    // The only widgets we care about are coordinate-related widgets.
214
    const epsgs: string[] = [];
1✔
215
    for (const ws of appDef.WidgetSet) {
1✔
216
        for (const w of ws.Widget) {
1✔
217
            if (w.Type == "CoordinateTracker") {
70✔
218
                const ps = w.Extension.Projection || [];
1!
219
                for (const p of ps) {
1✔
220
                    epsgs.push(p.split(':')[1]);
2✔
221
                }
2✔
222
            } else if (w.Type == "CursorPosition") {
70✔
223
                const dp = w.Extension.DisplayProjection;
1✔
224
                if (dp) {
1!
NEW
225
                    epsgs.push(dp.split(':')[1]);
×
226
                }
×
227
            }
1✔
228
        }
70✔
229
    }
1✔
230
    return makeUnique(epsgs);
1✔
231
}
1✔
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