@panoramax/web-viewer 4.0.2 → 4.0.3-develop-d59993ce
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -1
- package/build/index.css +2 -2
- package/build/index.css.map +1 -1
- package/build/index.js +287 -72
- package/build/index.js.map +1 -1
- package/config/jest/mocks.js +5 -0
- package/docs/09_Develop.md +6 -0
- package/docs/reference/components/core/PhotoViewer.md +1 -0
- package/docs/reference/components/core/Viewer.md +2 -1
- package/docs/reference/components/menus/AnnotationsList.md +16 -0
- package/docs/reference/components/ui/HashTags.md +15 -0
- package/docs/reference/components/ui/ListItem.md +38 -0
- package/docs/reference/components/ui/Photo.md +78 -1
- package/docs/reference/components/ui/SemanticsTable.md +32 -0
- package/docs/reference/components/ui/widgets/GeoSearch.md +5 -1
- package/docs/reference/utils/PresetsManager.md +35 -0
- package/docs/reference.md +4 -0
- package/mkdocs.yml +4 -0
- package/package.json +2 -1
- package/src/components/core/Basic.css +2 -0
- package/src/components/core/PhotoViewer.js +11 -0
- package/src/components/core/Viewer.js +8 -1
- package/src/components/layout/Tabs.js +1 -1
- package/src/components/menus/AnnotationsList.js +151 -0
- package/src/components/menus/PictureLegend.js +6 -5
- package/src/components/menus/PictureMetadata.js +80 -8
- package/src/components/menus/index.js +1 -0
- package/src/components/styles.js +34 -0
- package/src/components/ui/HashTags.js +98 -0
- package/src/components/ui/ListItem.js +83 -0
- package/src/components/ui/Photo.js +188 -0
- package/src/components/ui/SemanticsTable.js +87 -0
- package/src/components/ui/index.js +3 -0
- package/src/components/ui/widgets/GeoSearch.js +13 -5
- package/src/img/osm.svg +49 -0
- package/src/img/wd.svg +1 -0
- package/src/translations/en.json +23 -0
- package/src/translations/fr.json +21 -0
- package/src/translations/it.json +28 -1
- package/src/translations/nl.json +14 -4
- package/src/translations/zh_Hant.json +6 -1
- package/src/utils/PresetsManager.js +137 -0
- package/src/utils/URLHandler.js +1 -1
- package/src/utils/geocoder.js +135 -83
- package/src/utils/index.js +3 -1
- package/src/utils/picture.js +28 -0
- package/src/utils/semantics.js +162 -0
- package/src/utils/services.js +39 -1
- package/src/utils/widgets.js +18 -1
- package/tests/components/core/__snapshots__/PhotoViewer.test.js.snap +10 -0
- package/tests/components/core/__snapshots__/Viewer.test.js.snap +10 -0
- package/tests/data/Map_geocoder_nominatim.json +25 -40
- package/tests/utils/PresetsManager.test.js +123 -0
- package/tests/utils/URLHandler.test.js +42 -0
- package/tests/utils/__snapshots__/geocoder.test.js.snap +5 -16
- package/tests/utils/geocoder.test.js +1 -1
- package/tests/utils/semantics.test.js +125 -0
package/src/utils/widgets.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// Every single icon imported separately to reduce bundle size
|
|
2
1
|
import { icon } from "@fortawesome/fontawesome-svg-core";
|
|
2
|
+
import { setCustomIconLoader } from "iconify-icon";
|
|
3
|
+
import { TemakiIconURL } from "./services";
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -13,6 +14,22 @@ export function fa(i, o) {
|
|
|
13
14
|
return icon(i, o).node[0];
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Register more icons providers for Iconify
|
|
19
|
+
* @private
|
|
20
|
+
*/
|
|
21
|
+
export function moreIcons() {
|
|
22
|
+
setCustomIconLoader(async (name) => {
|
|
23
|
+
const response = await fetch(TemakiIconURL(name));
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
body: await response.text()
|
|
29
|
+
};
|
|
30
|
+
}, "temaki");
|
|
31
|
+
}
|
|
32
|
+
|
|
16
33
|
/**
|
|
17
34
|
* Create a web component with its initial properties
|
|
18
35
|
* @private
|
|
@@ -23,6 +23,12 @@ exports[`_initWidgets should handle widgets if width is not small 1`] = `
|
|
|
23
23
|
slot="top-left"
|
|
24
24
|
/>,
|
|
25
25
|
],
|
|
26
|
+
Array [
|
|
27
|
+
<pnx-hashtags
|
|
28
|
+
class="pnx-only-psv pnx-print-hidden"
|
|
29
|
+
slot="top-right"
|
|
30
|
+
/>,
|
|
31
|
+
],
|
|
26
32
|
],
|
|
27
33
|
"results": Array [
|
|
28
34
|
Object {
|
|
@@ -37,6 +43,10 @@ exports[`_initWidgets should handle widgets if width is not small 1`] = `
|
|
|
37
43
|
"type": "return",
|
|
38
44
|
"value": undefined,
|
|
39
45
|
},
|
|
46
|
+
Object {
|
|
47
|
+
"type": "return",
|
|
48
|
+
"value": undefined,
|
|
49
|
+
},
|
|
40
50
|
],
|
|
41
51
|
}
|
|
42
52
|
`;
|
|
@@ -16,6 +16,12 @@ exports[`_initWidgets should handle widgets if width is not small 1`] = `
|
|
|
16
16
|
slot="top-left"
|
|
17
17
|
/>,
|
|
18
18
|
],
|
|
19
|
+
Array [
|
|
20
|
+
<pnx-hashtags
|
|
21
|
+
class="pnx-only-psv pnx-print-hidden"
|
|
22
|
+
slot="top-right"
|
|
23
|
+
/>,
|
|
24
|
+
],
|
|
19
25
|
Array [
|
|
20
26
|
<pnx-widget-player
|
|
21
27
|
class="pnx-only-psv pnx-print-hidden"
|
|
@@ -68,6 +74,10 @@ exports[`_initWidgets should handle widgets if width is not small 1`] = `
|
|
|
68
74
|
"type": "return",
|
|
69
75
|
"value": undefined,
|
|
70
76
|
},
|
|
77
|
+
Object {
|
|
78
|
+
"type": "return",
|
|
79
|
+
"value": undefined,
|
|
80
|
+
},
|
|
71
81
|
],
|
|
72
82
|
}
|
|
73
83
|
`;
|
|
@@ -1,56 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "FeatureCollection",
|
|
3
|
-
"
|
|
3
|
+
"geocoding": {
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"attribution": "Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright",
|
|
6
|
+
"licence": "ODbL",
|
|
7
|
+
"query": "paris"
|
|
8
|
+
},
|
|
4
9
|
"features": [
|
|
5
10
|
{
|
|
6
11
|
"type": "Feature",
|
|
7
12
|
"properties": {
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"address": {
|
|
18
|
-
"suburb": "Paris",
|
|
19
|
-
"city_district": "Paris",
|
|
20
|
-
"city": "Paris",
|
|
21
|
-
"ISO3166-2-lvl6": "FR-75",
|
|
13
|
+
"geocoding": {
|
|
14
|
+
"place_id": 88066702,
|
|
15
|
+
"osm_type": "relation",
|
|
16
|
+
"osm_id": 71525,
|
|
17
|
+
"osm_key": "boundary",
|
|
18
|
+
"osm_value": "administrative",
|
|
19
|
+
"type": "city",
|
|
20
|
+
"label": "Paris, Île-de-France, France métropolitaine, France",
|
|
21
|
+
"name": "Paris",
|
|
22
22
|
"state": "Île-de-France",
|
|
23
|
-
"ISO3166-2-lvl4": "FR-IDF",
|
|
24
|
-
"region": "France métropolitaine",
|
|
25
23
|
"country": "France",
|
|
26
|
-
"country_code": "fr"
|
|
24
|
+
"country_code": "fr",
|
|
25
|
+
"admin": {
|
|
26
|
+
"level6": "Paris",
|
|
27
|
+
"level4": "Île-de-France",
|
|
28
|
+
"level3": "France métropolitaine"
|
|
29
|
+
}
|
|
27
30
|
}
|
|
28
31
|
},
|
|
29
|
-
"bbox": [
|
|
30
|
-
2.224122,
|
|
31
|
-
48.8155755,
|
|
32
|
-
2.4697602,
|
|
33
|
-
48.902156
|
|
34
|
-
],
|
|
35
32
|
"geometry": {
|
|
36
|
-
"type": "
|
|
33
|
+
"type": "Point",
|
|
37
34
|
"coordinates": [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
2.224122,
|
|
41
|
-
48.854199
|
|
42
|
-
],
|
|
43
|
-
[
|
|
44
|
-
2.224125,
|
|
45
|
-
48.85402
|
|
46
|
-
],
|
|
47
|
-
[
|
|
48
|
-
2.224125,
|
|
49
|
-
48.853869
|
|
50
|
-
]
|
|
51
|
-
]
|
|
35
|
+
2.3483915,
|
|
36
|
+
48.8534951
|
|
52
37
|
]
|
|
53
38
|
}
|
|
54
39
|
}
|
|
55
40
|
]
|
|
56
|
-
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import PresetsManager from "../../src/utils/PresetsManager";
|
|
2
|
+
|
|
3
|
+
const mockPresets = {
|
|
4
|
+
"osm_traffic_sign": {
|
|
5
|
+
"icon": "fas-directions",
|
|
6
|
+
"tags": {
|
|
7
|
+
"osm|traffic_sign": "*"
|
|
8
|
+
},
|
|
9
|
+
"geometry": [
|
|
10
|
+
"point",
|
|
11
|
+
"vertex"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"osm_traffic_calming": {
|
|
15
|
+
"icon": "temaki-diamond",
|
|
16
|
+
"tags": {
|
|
17
|
+
"osm|traffic_calming": "*"
|
|
18
|
+
},
|
|
19
|
+
"geometry": [
|
|
20
|
+
"vertex"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"osm_shop": {
|
|
24
|
+
"icon": "maki-shop",
|
|
25
|
+
"tags": {
|
|
26
|
+
"osm|shop": "*"
|
|
27
|
+
},
|
|
28
|
+
"geometry": [
|
|
29
|
+
"point",
|
|
30
|
+
"area"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Mock des appels fetch
|
|
36
|
+
global.fetch = jest.fn(() =>
|
|
37
|
+
Promise.resolve({
|
|
38
|
+
ok: true,
|
|
39
|
+
json: () => Promise.resolve({}),
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
fetch.mockClear();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should initialize with default values", () => {
|
|
48
|
+
const presetsManager = new PresetsManager(null, true);
|
|
49
|
+
expect(presetsManager._ready).toBe(false);
|
|
50
|
+
expect(presetsManager._presets).toBe(null);
|
|
51
|
+
expect(presetsManager._translations).toEqual({});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should load translations and presets", async () => {
|
|
55
|
+
const mockTranslationsEN = { en: { presets: { preset1: { name: "Preset 1" } } } };
|
|
56
|
+
const mockTranslationsFR = { fr: { presets: { preset1: { name: "Préréglage 1" } } } };
|
|
57
|
+
|
|
58
|
+
fetch.mockImplementationOnce(() =>
|
|
59
|
+
Promise.resolve({
|
|
60
|
+
ok: true,
|
|
61
|
+
json: () => Promise.resolve(mockTranslationsEN),
|
|
62
|
+
})
|
|
63
|
+
).mockImplementationOnce(() =>
|
|
64
|
+
Promise.resolve({
|
|
65
|
+
ok: true,
|
|
66
|
+
json: () => Promise.resolve(mockTranslationsFR),
|
|
67
|
+
})
|
|
68
|
+
).mockImplementationOnce(() =>
|
|
69
|
+
Promise.resolve({
|
|
70
|
+
ok: true,
|
|
71
|
+
json: () => Promise.resolve(mockPresets),
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const presetsManager = new PresetsManager("fr");
|
|
76
|
+
await presetsManager.onceReady();
|
|
77
|
+
|
|
78
|
+
expect(presetsManager._translations.en).toEqual(mockTranslationsEN.en.presets);
|
|
79
|
+
expect(presetsManager._translations.fr).toEqual(mockTranslationsFR.fr.presets);
|
|
80
|
+
expect(presetsManager._presets).toEqual(mockPresets);
|
|
81
|
+
expect(presetsManager._ready).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should handle errors during load", async () => {
|
|
85
|
+
global.console = { error: jest.fn() };
|
|
86
|
+
|
|
87
|
+
fetch.mockImplementation(() =>
|
|
88
|
+
Promise.resolve({
|
|
89
|
+
ok: false,
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const presetsManager = new PresetsManager();
|
|
94
|
+
await expect(presetsManager.onceReady()).rejects.toBeUndefined();
|
|
95
|
+
expect(presetsManager._ready).toBe(-1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should find the best matching preset", async () => {
|
|
99
|
+
fetch.mockImplementationOnce(() =>
|
|
100
|
+
Promise.resolve({
|
|
101
|
+
ok: true,
|
|
102
|
+
json: () => Promise.resolve({}),
|
|
103
|
+
})
|
|
104
|
+
).mockImplementationOnce(() =>
|
|
105
|
+
Promise.resolve({
|
|
106
|
+
ok: true,
|
|
107
|
+
json: () => Promise.resolve({}),
|
|
108
|
+
})
|
|
109
|
+
).mockImplementationOnce(() =>
|
|
110
|
+
Promise.resolve({
|
|
111
|
+
ok: true,
|
|
112
|
+
json: () => Promise.resolve(mockPresets),
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const presetsManager = new PresetsManager();
|
|
117
|
+
await presetsManager.onceReady();
|
|
118
|
+
|
|
119
|
+
const feature = { semantics: [{ key: "key1", value: "value1" }, { key: "key2", value: "value2" }] };
|
|
120
|
+
const bestPreset = await presetsManager.getPreset(feature);
|
|
121
|
+
|
|
122
|
+
expect(bestPreset).toEqual(mockPresets.preset2);
|
|
123
|
+
});
|
|
@@ -243,6 +243,48 @@ describe("currentURLParams", () => {
|
|
|
243
243
|
const uh = new URLHandler(v);
|
|
244
244
|
expect(uh.currentURLParams()).toStrictEqual({});
|
|
245
245
|
});
|
|
246
|
+
|
|
247
|
+
it("works with shortlinks", () => {
|
|
248
|
+
delete window.location;
|
|
249
|
+
window.location = { search: "?s=fp;s2;pa0270208-d79d-49a9-85c7-a352bb96962e;c282.09/9.34/30;m17/43.107492/5.868459;va;bs" };
|
|
250
|
+
const v = { addEventListener: jest.fn() };
|
|
251
|
+
const uh = new URLHandler(v);
|
|
252
|
+
expect(uh.currentURLParams()).toStrictEqual({
|
|
253
|
+
"focus": "pic",
|
|
254
|
+
"speed": 200,
|
|
255
|
+
"pic": "a0270208-d79d-49a9-85c7-a352bb96962e",
|
|
256
|
+
"xyz": "282.09/9.34/30",
|
|
257
|
+
"map": "17/43.107492/5.868459",
|
|
258
|
+
"theme": "age",
|
|
259
|
+
"background": "streets",
|
|
260
|
+
"camera": undefined,
|
|
261
|
+
"date_from": undefined,
|
|
262
|
+
"date_to": undefined,
|
|
263
|
+
"pic_score": undefined,
|
|
264
|
+
"users": undefined
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("works with shortlinks urlEncoded", () => {
|
|
269
|
+
delete window.location;
|
|
270
|
+
window.location = { search: "?s=fp%3Bs2%3Bpa0270208-d79d-49a9-85c7-a352bb96962e%3Bc282.09/9.34/30%3Bm17/43.107492/5.868459%3Bva%3Bbs" };
|
|
271
|
+
const v = { addEventListener: jest.fn() };
|
|
272
|
+
const uh = new URLHandler(v);
|
|
273
|
+
expect(uh.currentURLParams()).toStrictEqual({
|
|
274
|
+
"focus": "pic",
|
|
275
|
+
"speed": 200,
|
|
276
|
+
"pic": "a0270208-d79d-49a9-85c7-a352bb96962e",
|
|
277
|
+
"xyz": "282.09/9.34/30",
|
|
278
|
+
"map": "17/43.107492/5.868459",
|
|
279
|
+
"theme": "age",
|
|
280
|
+
"background": "streets",
|
|
281
|
+
"camera": undefined,
|
|
282
|
+
"date_from": undefined,
|
|
283
|
+
"date_to": undefined,
|
|
284
|
+
"pic_score": undefined,
|
|
285
|
+
"users": undefined
|
|
286
|
+
});
|
|
287
|
+
});
|
|
246
288
|
});
|
|
247
289
|
|
|
248
290
|
describe("currentMapString", () => {
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`forwardGeocodingBAN works 1`] = `
|
|
4
4
|
Object {
|
|
5
|
-
"attribution": "BAN",
|
|
6
5
|
"features": Array [
|
|
7
6
|
Object {
|
|
8
7
|
"center": Object {
|
|
@@ -16,11 +15,6 @@ Object {
|
|
|
16
15
|
"zoom": 20,
|
|
17
16
|
},
|
|
18
17
|
],
|
|
19
|
-
"licence": "ODbL 1.0",
|
|
20
|
-
"limit": 1,
|
|
21
|
-
"query": "8 bd du port",
|
|
22
|
-
"type": "FeatureCollection",
|
|
23
|
-
"version": "draft",
|
|
24
18
|
}
|
|
25
19
|
`;
|
|
26
20
|
|
|
@@ -28,20 +22,15 @@ exports[`forwardGeocodingNominatim works 1`] = `
|
|
|
28
22
|
Object {
|
|
29
23
|
"features": Array [
|
|
30
24
|
Object {
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
"lng": -1.7,
|
|
35
|
-
},
|
|
36
|
-
"sw": Object {
|
|
37
|
-
"lat": 47.8,
|
|
38
|
-
"lng": -1.7,
|
|
39
|
-
},
|
|
25
|
+
"center": Object {
|
|
26
|
+
"lat": 47.8,
|
|
27
|
+
"lng": -1.7,
|
|
40
28
|
},
|
|
41
|
-
"place_name": "Paris",
|
|
29
|
+
"place_name": "Paris, Île-de-France",
|
|
42
30
|
"place_type": Array [
|
|
43
31
|
"place",
|
|
44
32
|
],
|
|
33
|
+
"zoom": 12,
|
|
45
34
|
},
|
|
46
35
|
],
|
|
47
36
|
}
|
|
@@ -13,7 +13,7 @@ describe("forwardGeocodingNominatim", () => {
|
|
|
13
13
|
const cfg = { query: "bla", limit: 5, bbox: "17.7,-45.2,17.8,-45.1" };
|
|
14
14
|
|
|
15
15
|
return geocoder.forwardGeocodingNominatim(cfg).then(res => {
|
|
16
|
-
expect(global.fetch.mock.calls).toEqual([["https://nominatim.openstreetmap.org/search?q=bla&limit=5&viewbox=17.7%2C-45.2%2C17.8%2C-45.1&format=
|
|
16
|
+
expect(global.fetch.mock.calls).toEqual([["https://nominatim.openstreetmap.org/search?q=bla&limit=5&viewbox=17.7%2C-45.2%2C17.8%2C-45.1&format=geocodejson&addressdetails=1"]]);
|
|
17
17
|
expect(res).toMatchSnapshot();
|
|
18
18
|
});
|
|
19
19
|
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { decodeKey, decodeBasicTag, groupByPrefix } from "../../src/utils/semantics";
|
|
2
|
+
|
|
3
|
+
describe("decodeBasicTag", () => {
|
|
4
|
+
test("should return null if no equal sign is present", () => {
|
|
5
|
+
expect(decodeBasicTag("key")).toBeNull();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test("should correctly decode a tag with an equal sign", () => {
|
|
9
|
+
expect(decodeBasicTag("key=value")).toEqual({
|
|
10
|
+
key: { prefix: "", subkey: "key", qualifies: null },
|
|
11
|
+
value: "value"
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("should correctly decode a tag with a prefix", () => {
|
|
16
|
+
expect(decodeBasicTag("osm|key=value")).toEqual({
|
|
17
|
+
key: { prefix: "osm", subkey: "key", qualifies: null },
|
|
18
|
+
value: "value"
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("decodeKey", () => {
|
|
24
|
+
test("should return default structure if key does not match regex", () => {
|
|
25
|
+
expect(decodeKey("panoKey")).toEqual({
|
|
26
|
+
prefix: "",
|
|
27
|
+
subkey: "panoKey",
|
|
28
|
+
qualifies: null
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("should correctly decode a key with a prefix", () => {
|
|
33
|
+
expect(decodeKey("osm|traffic_sign")).toEqual({
|
|
34
|
+
prefix: "osm",
|
|
35
|
+
subkey: "traffic_sign",
|
|
36
|
+
qualifies: null
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("should correctly decode a key with qualifiers", () => {
|
|
41
|
+
expect(decodeKey("detection_model[camera_mount=pole]")).toEqual({
|
|
42
|
+
prefix: "",
|
|
43
|
+
subkey: "detection_model",
|
|
44
|
+
qualifies: { key: { prefix: "", subkey: "camera_mount", qualifies: null }, value: "pole" }
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("should correctly decode a key with a prefix and qualifiers", () => {
|
|
49
|
+
expect(decodeKey("osm|source[osm|traffic_sign=stop]")).toEqual({
|
|
50
|
+
prefix: "osm",
|
|
51
|
+
subkey: "source",
|
|
52
|
+
qualifies: { key: { prefix: "osm", subkey: "traffic_sign", qualifies: null }, value: "stop" }
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("groupByPrefix", () => {
|
|
58
|
+
test("should group tags by prefix", () => {
|
|
59
|
+
const tags = [
|
|
60
|
+
{ key: "osm|highway", value: "residential" },
|
|
61
|
+
{ key: "wd|P31", value: "Q5" },
|
|
62
|
+
{ key: "exif|model", value: "CameraModel" }
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const expected = [
|
|
66
|
+
{
|
|
67
|
+
prefix: "osm",
|
|
68
|
+
title: "OpenStreetMap",
|
|
69
|
+
tags: [{ key: "highway", value: "residential" }],
|
|
70
|
+
key_transform: expect.any(Function),
|
|
71
|
+
logo: "osm.svg",
|
|
72
|
+
value_transform: expect.any(Function)
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
prefix: "wd",
|
|
76
|
+
title: "Wikidata",
|
|
77
|
+
tags: [{ key: "P31", value: "Q5" }],
|
|
78
|
+
key_transform: expect.any(Function),
|
|
79
|
+
logo: "wd.svg",
|
|
80
|
+
value_transform: expect.any(Function)
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
prefix: "exif",
|
|
84
|
+
title: "EXIF",
|
|
85
|
+
tags: [{ key: "model", value: "CameraModel" }]
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
expect(groupByPrefix(tags)).toEqual(expected);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("should handle qualifiers correctly", () => {
|
|
93
|
+
const tags = [
|
|
94
|
+
{ key: "osm|highway", value: "residential" },
|
|
95
|
+
{ key: "osm|source[osm|highway=residential]", value: "opendata" }
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const expected = [{
|
|
99
|
+
prefix: "osm",
|
|
100
|
+
title: "OpenStreetMap",
|
|
101
|
+
tags: [{
|
|
102
|
+
key: "highway",
|
|
103
|
+
value: "residential",
|
|
104
|
+
qualifiers: [{ key: "osm|source[osm|highway=residential]", prefix: "osm", subkey: "source", value: "opendata" }]
|
|
105
|
+
}],
|
|
106
|
+
key_transform: expect.any(Function),
|
|
107
|
+
logo: "osm.svg",
|
|
108
|
+
value_transform: expect.any(Function)
|
|
109
|
+
}];
|
|
110
|
+
|
|
111
|
+
expect(groupByPrefix(tags)).toEqual(expected);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("should handle unknown prefixes", () => {
|
|
115
|
+
const tags = [ { key: "unknown|key", value: "value" } ];
|
|
116
|
+
|
|
117
|
+
const expected = [{
|
|
118
|
+
prefix: "unknown",
|
|
119
|
+
title: "unknown",
|
|
120
|
+
tags: [{ key: "key", value: "value" }]
|
|
121
|
+
}];
|
|
122
|
+
|
|
123
|
+
expect(groupByPrefix(tags)).toEqual(expected);
|
|
124
|
+
});
|
|
125
|
+
});
|