@panoramax/web-viewer 3.0.2-develop-a8ea8e60

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.
Files changed (125) hide show
  1. package/.dockerignore +6 -0
  2. package/.gitlab-ci.yml +71 -0
  3. package/CHANGELOG.md +428 -0
  4. package/CODE_OF_CONDUCT.md +134 -0
  5. package/Dockerfile +14 -0
  6. package/LICENSE +21 -0
  7. package/README.md +39 -0
  8. package/build/editor.html +1 -0
  9. package/build/index.css +36 -0
  10. package/build/index.css.map +1 -0
  11. package/build/index.html +1 -0
  12. package/build/index.js +25 -0
  13. package/build/index.js.map +1 -0
  14. package/build/map.html +1 -0
  15. package/build/viewer.html +1 -0
  16. package/config/env.js +104 -0
  17. package/config/getHttpsConfig.js +66 -0
  18. package/config/getPackageJson.js +25 -0
  19. package/config/jest/babelTransform.js +29 -0
  20. package/config/jest/cssTransform.js +14 -0
  21. package/config/jest/fileTransform.js +40 -0
  22. package/config/modules.js +134 -0
  23. package/config/paths.js +72 -0
  24. package/config/pnpTs.js +35 -0
  25. package/config/webpack/persistentCache/createEnvironmentHash.js +9 -0
  26. package/config/webpack.config.js +885 -0
  27. package/config/webpackDevServer.config.js +127 -0
  28. package/docs/01_Start.md +149 -0
  29. package/docs/02_Usage.md +828 -0
  30. package/docs/03_URL_settings.md +140 -0
  31. package/docs/04_Advanced_examples.md +214 -0
  32. package/docs/05_Compatibility.md +85 -0
  33. package/docs/09_Develop.md +62 -0
  34. package/docs/90_Releases.md +27 -0
  35. package/docs/images/class_diagram.drawio +129 -0
  36. package/docs/images/class_diagram.jpg +0 -0
  37. package/docs/images/screenshot.jpg +0 -0
  38. package/mkdocs.yml +45 -0
  39. package/package.json +254 -0
  40. package/public/editor.html +54 -0
  41. package/public/favicon.ico +0 -0
  42. package/public/index.html +59 -0
  43. package/public/map.html +53 -0
  44. package/public/viewer.html +67 -0
  45. package/scripts/build.js +217 -0
  46. package/scripts/start.js +176 -0
  47. package/scripts/test.js +52 -0
  48. package/src/Editor.css +37 -0
  49. package/src/Editor.js +359 -0
  50. package/src/StandaloneMap.js +114 -0
  51. package/src/Viewer.css +203 -0
  52. package/src/Viewer.js +1186 -0
  53. package/src/components/CoreView.css +64 -0
  54. package/src/components/CoreView.js +159 -0
  55. package/src/components/Loader.css +56 -0
  56. package/src/components/Loader.js +111 -0
  57. package/src/components/Map.css +65 -0
  58. package/src/components/Map.js +841 -0
  59. package/src/components/Photo.css +36 -0
  60. package/src/components/Photo.js +687 -0
  61. package/src/img/arrow_360.svg +14 -0
  62. package/src/img/arrow_flat.svg +11 -0
  63. package/src/img/arrow_triangle.svg +10 -0
  64. package/src/img/arrow_turn.svg +9 -0
  65. package/src/img/bg_aerial.jpg +0 -0
  66. package/src/img/bg_streets.jpg +0 -0
  67. package/src/img/loader_base.jpg +0 -0
  68. package/src/img/loader_hd.jpg +0 -0
  69. package/src/img/logo_dead.svg +91 -0
  70. package/src/img/marker.svg +17 -0
  71. package/src/img/marker_blue.svg +20 -0
  72. package/src/img/switch_big.svg +44 -0
  73. package/src/img/switch_mini.svg +48 -0
  74. package/src/index.js +10 -0
  75. package/src/translations/de.json +163 -0
  76. package/src/translations/en.json +164 -0
  77. package/src/translations/eo.json +6 -0
  78. package/src/translations/es.json +164 -0
  79. package/src/translations/fi.json +1 -0
  80. package/src/translations/fr.json +164 -0
  81. package/src/translations/hu.json +133 -0
  82. package/src/translations/nl.json +1 -0
  83. package/src/translations/zh_Hant.json +136 -0
  84. package/src/utils/API.js +709 -0
  85. package/src/utils/Exif.js +198 -0
  86. package/src/utils/I18n.js +75 -0
  87. package/src/utils/Map.js +382 -0
  88. package/src/utils/PhotoAdapter.js +45 -0
  89. package/src/utils/Utils.js +568 -0
  90. package/src/utils/Widgets.js +477 -0
  91. package/src/viewer/URLHash.js +334 -0
  92. package/src/viewer/Widgets.css +711 -0
  93. package/src/viewer/Widgets.js +1196 -0
  94. package/tests/Editor.test.js +125 -0
  95. package/tests/StandaloneMap.test.js +44 -0
  96. package/tests/Viewer.test.js +363 -0
  97. package/tests/__snapshots__/Editor.test.js.snap +300 -0
  98. package/tests/__snapshots__/StandaloneMap.test.js.snap +30 -0
  99. package/tests/__snapshots__/Viewer.test.js.snap +195 -0
  100. package/tests/components/CoreView.test.js +91 -0
  101. package/tests/components/Loader.test.js +38 -0
  102. package/tests/components/Map.test.js +230 -0
  103. package/tests/components/Photo.test.js +335 -0
  104. package/tests/components/__snapshots__/Loader.test.js.snap +15 -0
  105. package/tests/components/__snapshots__/Map.test.js.snap +767 -0
  106. package/tests/components/__snapshots__/Photo.test.js.snap +205 -0
  107. package/tests/data/Map_geocoder_ban.json +36 -0
  108. package/tests/data/Map_geocoder_nominatim.json +56 -0
  109. package/tests/data/Viewer_pictures_1.json +148 -0
  110. package/tests/setupTests.js +5 -0
  111. package/tests/utils/API.test.js +906 -0
  112. package/tests/utils/Exif.test.js +124 -0
  113. package/tests/utils/I18n.test.js +28 -0
  114. package/tests/utils/Map.test.js +105 -0
  115. package/tests/utils/Utils.test.js +300 -0
  116. package/tests/utils/Widgets.test.js +107 -0
  117. package/tests/utils/__snapshots__/API.test.js.snap +132 -0
  118. package/tests/utils/__snapshots__/Exif.test.js.snap +43 -0
  119. package/tests/utils/__snapshots__/Map.test.js.snap +48 -0
  120. package/tests/utils/__snapshots__/Utils.test.js.snap +41 -0
  121. package/tests/utils/__snapshots__/Widgets.test.js.snap +44 -0
  122. package/tests/viewer/URLHash.test.js +537 -0
  123. package/tests/viewer/Widgets.test.js +127 -0
  124. package/tests/viewer/__snapshots__/URLHash.test.js.snap +98 -0
  125. package/tests/viewer/__snapshots__/Widgets.test.js.snap +393 -0
@@ -0,0 +1,124 @@
1
+ import * as Exif from "../../src/utils/Exif";
2
+
3
+ describe("getExifFloat", () => {
4
+ it.each([
5
+ [undefined, undefined],
6
+ [null, undefined],
7
+ ["", undefined],
8
+ [" ", undefined],
9
+ [1, 1],
10
+ [1.4, 1.4],
11
+ ["1", 1],
12
+ ["-1.4", -1.4],
13
+ ["-104/10", -10.4],
14
+ ["-104.12/-12.12", 8.590759075907592],
15
+ ])("works with %s", (input, expected) => {
16
+ expect(Exif.getExifFloat(input)).toBe(expected);
17
+ });
18
+ });
19
+
20
+ describe("getGPSPrecision", () => {
21
+ it.each([
22
+ [undefined, "unknown"],
23
+ [0.4, "ideal"],
24
+ [0.9, "excellent"],
25
+ [2.9, "good"],
26
+ [6.9, "moderate"],
27
+ [9.9, "fair"],
28
+ [20, "poor"],
29
+ ["9/10", "excellent"],
30
+ ["99/10", "fair"],
31
+ ])("handles GPSHPos %s > %s", (input, expected) => {
32
+ const p = { properties: { exif: { "Exif.GPSInfo.GPSHPositioningError": input, "Exif.GPSInfo.GPSDOP": input } } };
33
+ expect(Exif.getGPSPrecision(p)).toBe(expected);
34
+ });
35
+
36
+ it.each([
37
+ [undefined, "unknown"],
38
+ [0.9, "ideal"],
39
+ [1.9, "excellent"],
40
+ [4.9, "good"],
41
+ [9.9, "moderate"],
42
+ [19.9, "fair"],
43
+ [20, "poor"],
44
+ ["1/1", "excellent"],
45
+ ["15/10", "excellent"],
46
+ ])("handles GPSDOP %s > %s", (input, expected) => {
47
+ const p = { properties: { exif: { "Exif.GPSInfo.GPSDOP": input } } };
48
+ expect(Exif.getGPSPrecision(p)).toBe(expected);
49
+ });
50
+ });
51
+
52
+ describe("getSphereCorrection", () => {
53
+ it("works with API props", () => {
54
+ const p = { properties: {
55
+ "view:azimuth": 50,
56
+ "pers:yaw": 42,
57
+ "pers:pitch": -30,
58
+ "pers:roll": 72.2
59
+ } };
60
+ expect(Exif.getSphereCorrection(p)).toMatchSnapshot();
61
+ });
62
+
63
+ it("works with exif tags", () => {
64
+ const p = { properties: {
65
+ exif: {
66
+ "Xmp.Camera.Pitch": "-30/1",
67
+ "Xmp.GPano.PoseRollDegrees": 72.2,
68
+ "Exif.MpfInfo.MPFYawAngle": 42,
69
+ },
70
+ "view:azimuth": 50,
71
+ } };
72
+ expect(Exif.getSphereCorrection(p)).toMatchSnapshot();
73
+ });
74
+ });
75
+
76
+ describe("getCroppedPanoData", () => {
77
+ it("works with API props", () => {
78
+ const p = { properties: {
79
+ "pers:interior_orientation": {
80
+ "sensor_array_dimensions": [15872,7936],
81
+ "visible_area": [0,2538,0,2792]
82
+ }
83
+ } };
84
+ expect(Exif.getCroppedPanoData(p)).toMatchSnapshot();
85
+ });
86
+
87
+ it("works with exif tags", () => {
88
+ const p = { properties: {
89
+ exif: {
90
+ "Xmp.GPano.CroppedAreaImageHeightPixels": "2606",
91
+ "Xmp.GPano.CroppedAreaImageWidthPixels": "15872",
92
+ "Xmp.GPano.CroppedAreaLeftPixels": "0",
93
+ "Xmp.GPano.CroppedAreaTopPixels": "2538",
94
+ "Xmp.GPano.FullPanoHeightPixels": "7936",
95
+ "Xmp.GPano.FullPanoWidthPixels": "15872",
96
+ },
97
+ } };
98
+ expect(Exif.getCroppedPanoData(p)).toMatchSnapshot();
99
+ });
100
+
101
+ it("works with API props and unneeded", () => {
102
+ const p = { properties: {
103
+ "pers:interior_orientation": {
104
+ "sensor_array_dimensions": [15872,7936],
105
+ "visible_area": [0,0,0,0]
106
+ }
107
+ } };
108
+ expect(Exif.getCroppedPanoData(p)).toMatchSnapshot();
109
+ });
110
+
111
+ it("works with exif tags and unneeded", () => {
112
+ const p = { properties: {
113
+ exif: {
114
+ "Xmp.GPano.CroppedAreaImageHeightPixels": "7936",
115
+ "Xmp.GPano.CroppedAreaImageWidthPixels": "15872",
116
+ "Xmp.GPano.CroppedAreaLeftPixels": "0",
117
+ "Xmp.GPano.CroppedAreaTopPixels": "0",
118
+ "Xmp.GPano.FullPanoHeightPixels": "7936",
119
+ "Xmp.GPano.FullPanoWidthPixels": "15872",
120
+ },
121
+ } };
122
+ expect(Exif.getCroppedPanoData(p)).toMatchSnapshot();
123
+ });
124
+ });
@@ -0,0 +1,28 @@
1
+ import { getTranslations } from "../../src/utils/I18n";
2
+
3
+ describe("getTranslations", () => {
4
+ it("works with default lang", () => {
5
+ const res = getTranslations("en");
6
+ expect(res.map.loading).toBe("Loading…");
7
+ });
8
+
9
+ it("works with existing lang", () => {
10
+ const res = getTranslations("fr");
11
+ expect(res.map.loading).toBe("Chargement…");
12
+ });
13
+
14
+ it("fallbacks to English if lang not found", () => {
15
+ const res = getTranslations("xn");
16
+ expect(res.map.loading).toBe("Loading…");
17
+ });
18
+
19
+ it("fallbacks to English if lang is undefined", () => {
20
+ const res = getTranslations();
21
+ expect(res.map.loading).toBe("Loading…");
22
+ });
23
+
24
+ it("works when primary lang code exists", () => {
25
+ const res = getTranslations("fr_FR");
26
+ expect(res.map.loading).toBe("Chargement…");
27
+ });
28
+ });
@@ -0,0 +1,105 @@
1
+ import * as Map from "../../src/utils/Map";
2
+ import GEOCODER_NOMINATIM from "../data/Map_geocoder_nominatim.json";
3
+ import GEOCODER_BAN from "../data/Map_geocoder_ban.json";
4
+
5
+
6
+ jest.mock("maplibre-gl", () => ({
7
+ addProtocol: jest.fn(),
8
+ Popup: function() {
9
+ return {
10
+ on: jest.fn(),
11
+ };
12
+ },
13
+ LngLat: function() {
14
+ return { lng: -1.7, lat: 47.8 };
15
+ },
16
+ LngLatBounds: function() {
17
+ return { sw: { lng: -1.7, lat: 47.8 }, ne: { lng: -1.7, lat: 47.8 } };
18
+ },
19
+ }));
20
+
21
+
22
+ describe("getThumbGif", () => {
23
+ it("works", () => {
24
+ const lang = { loading: "Loading..." };
25
+ const res = Map.getThumbGif(lang);
26
+ expect(res).toBeDefined();
27
+ expect(res.tagName).toBe("IMG");
28
+ expect(res.alt).toBe(lang.loading);
29
+ expect(res.title).toBe(lang.loading);
30
+ });
31
+ });
32
+
33
+ describe("getUserLayerId", () => {
34
+ it("works with default tiles", () => {
35
+ expect(Map.getUserLayerId("geovisio", "pictures")).toBe("geovisio_pictures");
36
+ });
37
+
38
+ it("works with specific user tiles", () => {
39
+ expect(Map.getUserLayerId("toto", "pictures")).toBe("geovisio_toto_pictures");
40
+ });
41
+ });
42
+
43
+ describe("getUserSourceId", () => {
44
+ it("works with default tiles", () => {
45
+ expect(Map.getUserSourceId("geovisio")).toBe("geovisio");
46
+ });
47
+
48
+ it("works with specific user tiles", () => {
49
+ expect(Map.getUserSourceId("toto")).toBe("geovisio_toto");
50
+ });
51
+ });
52
+
53
+ describe("geocoderParamsToURLString", () => {
54
+ it("works", () => {
55
+ const p = { bla: "blorg", you: 1 };
56
+ const r = Map.geocoderParamsToURLString(p);
57
+ expect(r).toEqual("bla=blorg&you=1");
58
+ });
59
+
60
+ it("handles special characters", () => {
61
+ const p = { bbox: "12,14,-45,78" };
62
+ const r = Map.geocoderParamsToURLString(p);
63
+ expect(r).toEqual("bbox=12%2C14%2C-45%2C78");
64
+ });
65
+
66
+ it("filters nulls", () => {
67
+ const p = { val1: undefined, val2: null, val3: 0 };
68
+ const r = Map.geocoderParamsToURLString(p);
69
+ expect(r).toEqual("val3=0");
70
+ });
71
+ });
72
+
73
+ describe("forwardGeocodingNominatim", () => {
74
+ it("works", () => {
75
+ // Mock API search
76
+ global.fetch = jest.fn(() => Promise.resolve({
77
+ json: () => GEOCODER_NOMINATIM
78
+ }));
79
+
80
+ // Search config
81
+ const cfg = { query: "bla", limit: 5, bbox: "17.7,-45.2,17.8,-45.1" };
82
+
83
+ return Map.forwardGeocodingNominatim(cfg).then(res => {
84
+ 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=geojson&polygon_geojson=1&addressdetails=1"]]);
85
+ expect(res).toMatchSnapshot();
86
+ });
87
+ });
88
+ });
89
+
90
+ describe("forwardGeocodingBAN", () => {
91
+ it("works", () => {
92
+ // Mock API search
93
+ global.fetch = jest.fn(() => Promise.resolve({
94
+ json: () => GEOCODER_BAN
95
+ }));
96
+
97
+ // Search config
98
+ const cfg = { query: "bla", limit: 5, proximity: "17.7,-45.2" };
99
+
100
+ return Map.forwardGeocodingBAN(cfg).then(res => {
101
+ expect(global.fetch.mock.calls).toEqual([["https://api-adresse.data.gouv.fr/search/?q=bla&limit=5&lat=17.7&lon=-45.2"]]);
102
+ expect(res).toMatchSnapshot();
103
+ });
104
+ });
105
+ });
@@ -0,0 +1,300 @@
1
+ import * as Utils from "../../src/utils/Utils";
2
+
3
+ jest.mock("../../src/utils/Map", () => ({
4
+ COLORS_HEX: {
5
+ SELECTED: 0x0000ff,
6
+ }
7
+ }));
8
+
9
+ describe("getDistance", () => {
10
+ it("works", () => {
11
+ const p1 = [1,1];
12
+ const p2 = [2,2];
13
+ const res = Utils.getDistance(p1, p2);
14
+ expect(res).toBe(Math.sqrt(2));
15
+ });
16
+ });
17
+
18
+ describe("sortPicturesInDirection", () => {
19
+ it("works with next/prev links", () => {
20
+ const sort = Utils.sortPicturesInDirection([0,0]);
21
+ let res = sort({rel: "prev"}, {rel: "next"});
22
+ expect(res).toBe(0);
23
+ res = sort({rel: "prev"}, {rel: "related"});
24
+ expect(res).toBe(-1);
25
+ res = sort({rel: "related"}, {rel: "next"});
26
+ expect(res).toBe(1);
27
+ });
28
+
29
+ it("works with related at different dates", () => {
30
+ const sort = Utils.sortPicturesInDirection([0,0]);
31
+ let res = sort(
32
+ {rel: "related", date: "2023-01-01"},
33
+ {rel: "related", date: "2022-01-01"}
34
+ );
35
+ expect(res).toBe(-1); // Most recent goes first
36
+ res = sort(
37
+ {rel: "related", date: "2022-01-01"},
38
+ {rel: "related", date: "2023-01-01"}
39
+ );
40
+ expect(res).toBe(1);
41
+ });
42
+
43
+ it("works with related at same date", () => {
44
+ const sort = Utils.sortPicturesInDirection([0,0]);
45
+ let res = sort(
46
+ {rel: "related", date: "2023-01-01", geometry: {coordinates: [0.1,0.1]}},
47
+ {rel: "related", date: "2023-01-01", geometry: {coordinates: [0.5,0.5]}}
48
+ );
49
+ expect(res).toBeLessThan(0); // Nearest goes first
50
+ res = sort(
51
+ {rel: "related", date: "2023-01-01", geometry: {coordinates: [0.5,0.5]}},
52
+ {rel: "related", date: "2023-01-01", geometry: {coordinates: [0.1,0.1]}}
53
+ );
54
+ expect(res).toBeGreaterThan(0);
55
+ res = sort(
56
+ {rel: "related", date: "2023-01-01", geometry: {coordinates: [0.1,0.1]}},
57
+ {rel: "related", date: "2023-01-01", geometry: {coordinates: [0.1,0.1]}}
58
+ );
59
+ expect(res).toBe(0);
60
+ });
61
+ });
62
+
63
+ describe("getAzimuth", () => {
64
+ it("works with 0 + NE", () => {
65
+ const pointDepart = [0, 0];
66
+ const pointArrivee = [1, 1];
67
+ const azimuth = Utils.getAzimuth(pointDepart, pointArrivee);
68
+ expect(azimuth).toBe(45);
69
+ });
70
+
71
+ it("works with 0 + SE", () => {
72
+ const pointDepart = [0, 0];
73
+ const pointArrivee = [1, -1];
74
+ const azimuth = Utils.getAzimuth(pointDepart, pointArrivee);
75
+ expect(azimuth).toBe(135);
76
+ });
77
+
78
+ it("works with 0 + N", () => {
79
+ const pointDepart = [0, 0];
80
+ const pointArrivee = [0, 1];
81
+ const azimuth = Utils.getAzimuth(pointDepart, pointArrivee);
82
+ expect(azimuth).toBe(0);
83
+ });
84
+
85
+ it("works with 0 + S", () => {
86
+ const pointDepart = [0, 0];
87
+ const pointArrivee = [0, -1];
88
+ const azimuth = Utils.getAzimuth(pointDepart, pointArrivee);
89
+ expect(azimuth).toBe(180);
90
+ });
91
+ });
92
+
93
+ describe("getRelativeHeading", () => {
94
+ it("should throw an error if no picture selected", () => {
95
+ expect(() => Utils.getRelativeHeading()).toThrow("No picture selected");
96
+ });
97
+
98
+ it("should calculate relative heading correctly", () => {
99
+ const pictureMetadata = {
100
+ properties: { "view:azimuth": 30 },
101
+ sequence: { prevPic: "prevPictureId", nextPic: "nextPictureId" },
102
+ links: [
103
+ { nodeId: "prevPictureId", gps: [0, 0] },
104
+ { nodeId: "nextPictureId", gps: [2, 2] }
105
+ ],
106
+ gps: [1, 1]
107
+ };
108
+
109
+ expect(Utils.getRelativeHeading(pictureMetadata)).toBe(-15);
110
+ });
111
+
112
+ it("works looking behind", () => {
113
+ const pictureMetadata = {
114
+ properties: { "view:azimuth": 226 },
115
+ sequence: { prevPic: "prevPictureId", nextPic: "nextPictureId" },
116
+ links: [
117
+ { nodeId: "prevPictureId", gps: [0, 0] },
118
+ { nodeId: "nextPictureId", gps: [2, 2] }
119
+ ],
120
+ gps: [1, 1]
121
+ };
122
+
123
+ expect(Utils.getRelativeHeading(pictureMetadata)).toBe(-179);
124
+ });
125
+
126
+ it("works with distorted path", () => {
127
+ const pictureMetadata = {
128
+ properties: { "view:azimuth": 100 },
129
+ sequence: { prevPic: "prevPictureId", nextPic: "nextPictureId" },
130
+ links: [
131
+ { nodeId: "prevPictureId", gps: [0, 0] },
132
+ { nodeId: "nextPictureId", gps: [2, 1] }
133
+ ],
134
+ gps: [1, 0]
135
+ };
136
+
137
+ expect(Utils.getRelativeHeading(pictureMetadata)).toBe(10);
138
+ });
139
+
140
+ it("works without previous link", () => {
141
+ const pictureMetadata = {
142
+ properties: { "view:azimuth": 100 },
143
+ sequence: { nextPic: "nextPictureId" },
144
+ links: [
145
+ { nodeId: "nextPictureId", gps: [2, 1] }
146
+ ],
147
+ gps: [1, 1]
148
+ };
149
+
150
+ expect(Utils.getRelativeHeading(pictureMetadata)).toBe(10);
151
+ });
152
+
153
+ it("should handle missing prevPic or nextPic", () => {
154
+ const metadataWithoutPrevNext = {
155
+ properties: { "view:azimuth": 30 },
156
+ gps: [0, 0]
157
+ };
158
+ expect(Utils.getRelativeHeading(metadataWithoutPrevNext)).toBe(0);
159
+ });
160
+ });
161
+
162
+ describe("getSimplifiedAngle", () => {
163
+ it("returns \"N\"", () => {
164
+ expect(Utils.getSimplifiedAngle([0, 0], [0, 1])).toBe("N");
165
+ });
166
+
167
+ it("returns \"ENE\"", () => {
168
+ expect(Utils.getSimplifiedAngle([0, 0], [1, 1])).toBe("ENE");
169
+ });
170
+
171
+ it("returns \"ESE\"", () => {
172
+ expect(Utils.getSimplifiedAngle([0, 0], [1, -1])).toBe("ESE");
173
+ });
174
+
175
+ it("returns \"S\"", () => {
176
+ expect(Utils.getSimplifiedAngle([0, 0], [0, -1])).toBe("S");
177
+ });
178
+
179
+ it("returns \"WNW\"", () => {
180
+ expect(Utils.getSimplifiedAngle([0, 0], [-1, 1])).toBe("WNW");
181
+ });
182
+
183
+ it("returns \"WSW\"", () => {
184
+ expect(Utils.getSimplifiedAngle([0, 0], [-1, -1])).toBe("WSW");
185
+ });
186
+ });
187
+
188
+ describe("positionToXYZ", () => {
189
+ it("works with xy", () => {
190
+ const r = Utils.positionToXYZ({ pitch: 10, yaw: -5 });
191
+ expect(r).toEqual({ x: -286.4788975654116, y: 572.9577951308232 });
192
+ });
193
+
194
+ it("works with xyz", () => {
195
+ const r = Utils.positionToXYZ({ pitch: 10, yaw: -5 }, 15);
196
+ expect(r).toEqual({ x: -286.4788975654116, y: 572.9577951308232, z: 15 });
197
+ });
198
+ });
199
+
200
+ describe("xyzToPosition", () => {
201
+ it("works with xyz", () => {
202
+ const r = Utils.xyzToPosition(-286.4788975654116, 572.9577951308232, 15);
203
+ expect(r).toEqual({ pitch: 10, yaw: -5, zoom: 15 });
204
+ });
205
+ });
206
+
207
+ describe("getNodeCaption", () => {
208
+ it("works with date", () => {
209
+ const m = { properties: { datetime: "2022-02-01T12:15:36Z" } };
210
+ global.Date = jest.fn(() => ({ toLocaleDateString: () => "February 2 2022" }));
211
+ const res = Utils.getNodeCaption(m, { gvs: { metadata_general_license_link: "" }});
212
+ expect(res).toMatchSnapshot();
213
+ });
214
+
215
+ it("works with date+tz", () => {
216
+ const m = { properties: { datetimetz: "2022-02-01T12:15:36-03:00" } };
217
+ global.Date = jest.fn(() => ({ toLocaleDateString: () => "February 2 2022" }));
218
+ const res = Utils.getNodeCaption(m, { gvs: { metadata_general_license_link: "" }});
219
+ expect(res).toMatchSnapshot();
220
+ });
221
+
222
+ it("works with producer", () => {
223
+ const m = { providers: [ { name: "PanierAvide", roles: ["producer", "licensor"] } ] };
224
+ const res = Utils.getNodeCaption(m, { gvs: { metadata_general_license_link: "" }});
225
+ expect(res).toMatchSnapshot();
226
+ });
227
+
228
+ it("works with date + producer", () => {
229
+ const m = { properties: { datetime: "2022-02-01T12:15:36Z" }, providers: [ { name: "PanierAvide", roles: ["producer", "licensor"] } ] };
230
+ global.Date = jest.fn(() => ({ toLocaleDateString: () => "February 2 2022" }));
231
+ const res = Utils.getNodeCaption(m, { gvs: { metadata_general_license_link: "" }});
232
+ expect(res).toMatchSnapshot();
233
+ });
234
+
235
+ it("works with date + 2 producers", () => {
236
+ const m = {
237
+ properties: { datetime: "2022-02-01T12:15:36Z" },
238
+ providers: [
239
+ { name: "GeoVisio Corp.", roles: ["producer", "licensor"] },
240
+ { name: "PanierAvide", roles: ["producer"] },
241
+ ]
242
+ };
243
+ global.Date = jest.fn(() => ({ toLocaleDateString: () => "February 2 2022" }));
244
+ const res = Utils.getNodeCaption(m, { gvs: { metadata_general_license_link: "" }});
245
+ expect(res).toMatchSnapshot();
246
+ });
247
+ });
248
+
249
+ describe("getCookie", () => {
250
+ it("should return the value of the specified cookie", () => {
251
+ jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=abc123");
252
+ expect(Utils.getCookie("session")).toBe("abc123");
253
+ });
254
+
255
+ it("should return null if the cookie is not found", () => {
256
+ jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=abc123");
257
+ expect(Utils.getCookie("user_id")).toBeUndefined();
258
+ });
259
+
260
+ it("should return the correct value when multiple cookies are set", () => {
261
+ jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=abc123; user_id=789; user_name=John");
262
+ expect(Utils.getCookie("user_id")).toBe("789");
263
+ });
264
+
265
+ it("should return null if cookie with the specified name has no value", () => {
266
+ jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=; user_id=789");
267
+ expect(Utils.getCookie("session")).toBe("");
268
+ });
269
+
270
+ it("should return the correct value when the cookie contains =", () => {
271
+ jest.spyOn(document, "cookie", "get").mockReturnValueOnce("custom_cookie=abc=123");
272
+ expect(Utils.getCookie("custom_cookie")).toBe("abc=123");
273
+ });
274
+ });
275
+
276
+ describe("getUserAccount", () => {
277
+ it("should return an object with user id and name when all cookies are present", () => {
278
+ jest.spyOn(document, "cookie", "get").mockReturnValue("session=abc123; user_id=789; user_name=John");
279
+ expect(Utils.getUserAccount()).toEqual({ id: "789", name: "John" });
280
+ });
281
+
282
+ it("should return null if session cookie is missing", () => {
283
+ jest.spyOn(document, "cookie", "get").mockReturnValue("user_id=789; user_name=John");
284
+ expect(Utils.getUserAccount()).toBeNull();
285
+ });
286
+
287
+ it("should return null if user_id cookie is missing", () => {
288
+ jest.spyOn(document, "cookie", "get").mockReturnValue("session=abc123; user_name=John");
289
+ expect(Utils.getUserAccount()).toBeNull();
290
+ });
291
+
292
+ it("should return null if user_name cookie is missing", () => {
293
+ jest.spyOn(document, "cookie", "get").mockReturnValue("session=abc123; user_id=789");
294
+ expect(Utils.getUserAccount()).toBeNull();
295
+ });
296
+
297
+ it("should return null if all cookies are missing", () => {
298
+ expect(Utils.getUserAccount()).toBeNull();
299
+ });
300
+ });
@@ -0,0 +1,107 @@
1
+ import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
2
+ import * as Widgets from "../../src/utils/Widgets";
3
+
4
+ describe("createButton", () => {
5
+ it("works", () => {
6
+ const child = document.createElement("span");
7
+ const btn = Widgets.createButton("mybtn", child, "blabla", ["gvs-blabla"]);
8
+ expect(btn.className).toBe("gvs-btn gvs-widget-bg gvs-blabla");
9
+ expect(btn.id).toBe("mybtn");
10
+ expect(btn.children[0]).toBe(child);
11
+ expect(btn.title).toBe("blabla");
12
+ });
13
+
14
+ it("works without options", () => {
15
+ const child = document.createElement("span");
16
+ const btn = Widgets.createButton("mybtn");
17
+ expect(btn.className).toBe("gvs-btn gvs-widget-bg");
18
+ expect(btn.id).toBe("mybtn");
19
+ expect(btn.children.length).toBe(0);
20
+ expect(btn.title).toBe("");
21
+ });
22
+ });
23
+
24
+ describe("createExpandableButton", () => {
25
+ it("works with large container", () => {
26
+ const container = { _viewer: { isWidthSmall: () => false } };
27
+ const btn = Widgets.createExpandableButton("mybtn", faChevronDown, "blabla", container, ["gvs-blabla"]);
28
+ expect(btn.className).toBe("gvs-btn gvs-widget-bg gvs-btn-expandable gvs-blabla");
29
+ expect(btn.innerHTML).toMatchSnapshot();
30
+ expect(btn.id).toBe("mybtn");
31
+ });
32
+
33
+ it("works with small container", () => {
34
+ const container = { _viewer: { isWidthSmall: () => true } };
35
+ const btn = Widgets.createExpandableButton("mybtn", faChevronDown, "blabla", container, ["gvs-blabla"]);
36
+ expect(btn.className).toBe("gvs-btn gvs-widget-bg gvs-btn-expandable gvs-blabla");
37
+ expect(btn.innerHTML).toMatchSnapshot();
38
+ expect(btn.id).toBe("mybtn");
39
+ });
40
+ });
41
+
42
+ describe("createSearchBar", () => {
43
+ it("works", () => {
44
+ const container = { _t: {} };
45
+ const btn = Widgets.createSearchBar("mysrch", "no res", jest.fn(), jest.fn(), container);
46
+ expect(btn.className).toBe("gvs-widget-bg gvs-search-bar");
47
+ expect(btn.innerHTML).toMatchSnapshot();
48
+ expect(btn.id).toBe("mysrch");
49
+ });
50
+ });
51
+
52
+ describe("createPanel", () => {
53
+ it("works", () => {
54
+ const btn = document.createElement("button");
55
+ btn.id = "btngvs";
56
+ btn.addEventListener = jest.fn();
57
+ const child = document.createElement("span");
58
+ const pnl = Widgets.createPanel({}, btn, [child], ["gvs-blabla"]);
59
+ expect(pnl.id).toBe("btngvs-panel");
60
+ expect(pnl.className).toBe("gvs-panel gvs-widget-bg gvs-hidden gvs-blabla");
61
+ expect(pnl.innerHTML).toMatchSnapshot();
62
+ expect(btn.addEventListener.mock.calls).toMatchSnapshot();
63
+ });
64
+ });
65
+
66
+ describe("createGroup", () => {
67
+ it("works", () => {
68
+ const container = { _corners: { "main-top-left": document.createElement("div") } };
69
+ const child = document.createElement("span");
70
+ const grp = Widgets.createGroup("grp", "main-top-left", container, [child], ["gvs-blabla"]);
71
+ expect(grp.className).toBe("gvs-group gvs-blabla");
72
+ expect(grp.innerHTML).toMatchSnapshot();
73
+ expect(grp.id).toBe("grp");
74
+ expect(container._corners["main-top-left"].children[0]).toBe(grp);
75
+ });
76
+ });
77
+
78
+ describe("enableButton", () => {
79
+ it("works", () => {
80
+ const btn = document.createElement("button");
81
+ btn.setAttribute("disabled", "");
82
+ Widgets.enableButton(btn);
83
+ expect(btn.getAttribute("disabled")).toBeNull();
84
+ });
85
+ });
86
+
87
+ describe("disableButton", () => {
88
+ it("works", () => {
89
+ const btn = document.createElement("button");
90
+ Widgets.disableButton(btn);
91
+ expect(btn.getAttribute("disabled")).toBe("");
92
+ });
93
+ });
94
+
95
+ describe("fa", () => {
96
+ it("works", () => {
97
+ const res = Widgets.fa(faChevronDown);
98
+ expect(res).toMatchSnapshot();
99
+ });
100
+ });
101
+
102
+ describe("fat", () => {
103
+ it("works", () => {
104
+ const res = Widgets.fat(faChevronDown);
105
+ expect(res).toMatchSnapshot();
106
+ });
107
+ });