@atlassian/atlassian-connect-js 5.3.190

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 (190) hide show
  1. package/.depcheckrc.json +16 -0
  2. package/.editorconfig +9 -0
  3. package/.envrc +5 -0
  4. package/.eslintignore +12 -0
  5. package/.eslintrc +31 -0
  6. package/.husky/pre-commit +11 -0
  7. package/.lintstagedrc.js +6 -0
  8. package/.netrc +1 -0
  9. package/.npmrc-public +4 -0
  10. package/.nvmrc +1 -0
  11. package/LICENSE +3 -0
  12. package/README.md +281 -0
  13. package/bitbucket-pipelines.yml +33 -0
  14. package/build/bin/bin-helper.js +29 -0
  15. package/build/bin/npm-postinstall.js +9 -0
  16. package/build/configs/append-sourcemapping.js +13 -0
  17. package/build/configs/availabletasks.js +15 -0
  18. package/build/configs/clean.js +12 -0
  19. package/build/configs/concat.js +44 -0
  20. package/build/configs/copy.js +32 -0
  21. package/build/configs/index.js +19 -0
  22. package/build/configs/jshint.js +9 -0
  23. package/build/configs/karma.js +11 -0
  24. package/build/configs/replace.js +10 -0
  25. package/build/configs/requirejs.js +187 -0
  26. package/build/configs/saucelabs-launchers.js +30 -0
  27. package/build/configs/shell.js +24 -0
  28. package/build/configs/uglify.js +15 -0
  29. package/build/configs/watch.js +45 -0
  30. package/build/end.frag +13 -0
  31. package/build/start.frag +18 -0
  32. package/bundlesize.config.json +14 -0
  33. package/dist/connect-host.css +225 -0
  34. package/dist/connect-host.js +13908 -0
  35. package/dist/connect-host.min.css +1 -0
  36. package/dist/iframe-fedramp.js +13663 -0
  37. package/dist/iframe.js +13663 -0
  38. package/dist/legacy-text-colors.css +245 -0
  39. package/dist/surfaces.css +11 -0
  40. package/dist/themes/atlaskit-tokens_dark-future.css +6 -0
  41. package/dist/themes/atlaskit-tokens_dark.css +394 -0
  42. package/dist/themes/atlaskit-tokens_legacy-dark.css +394 -0
  43. package/dist/themes/atlaskit-tokens_legacy-light.css +394 -0
  44. package/dist/themes/atlaskit-tokens_light-future.css +6 -0
  45. package/dist/themes/atlaskit-tokens_light.css +394 -0
  46. package/dist/themes/atlaskit-tokens_shape.css +14 -0
  47. package/dist/themes/atlaskit-tokens_spacing.css +26 -0
  48. package/dist/themes/atlaskit-tokens_typography-adg3.css +25 -0
  49. package/dist/themes/atlaskit-tokens_typography-modernized.css +25 -0
  50. package/dist/themes/atlaskit-tokens_typography-refreshed.css +25 -0
  51. package/gulpfile.js +204 -0
  52. package/memleak-check/app.html +10 -0
  53. package/memleak-check/index.html +31 -0
  54. package/memleak-check/index.js +33 -0
  55. package/package.json +102 -0
  56. package/renovate.json +11 -0
  57. package/security-assistant.yml +2 -0
  58. package/spec/.eslintrc +10 -0
  59. package/spec/config/karma.base.conf.js +200 -0
  60. package/spec/config/karma.conf.js +20 -0
  61. package/spec/fixtures/base_dialog_component_tests.js +180 -0
  62. package/spec/mocks/mock_feature_flag.js +23 -0
  63. package/spec/tests/amd_spec.js +122 -0
  64. package/spec/tests/analytics_dispatcher_spec.js +429 -0
  65. package/spec/tests/analytics_performance_spec.js +41 -0
  66. package/spec/tests/button_spec.js +143 -0
  67. package/spec/tests/dialog_extension_spec.js +59 -0
  68. package/spec/tests/dialog_module_provider_spec.js +499 -0
  69. package/spec/tests/dialog_module_spec.js +395 -0
  70. package/spec/tests/dialog_spec.js +296 -0
  71. package/spec/tests/dialog_webitem_spec.js +183 -0
  72. package/spec/tests/dropdown_module_spec.js +77 -0
  73. package/spec/tests/env_module_spec.js +153 -0
  74. package/spec/tests/events_spec.js +77 -0
  75. package/spec/tests/extension_configuration_options_store_spec.js +25 -0
  76. package/spec/tests/flag_module_provider_spec.js +79 -0
  77. package/spec/tests/flag_module_spec.js +118 -0
  78. package/spec/tests/flag_spec.js +243 -0
  79. package/spec/tests/host-api_spec.js +220 -0
  80. package/spec/tests/iframe-create_spec.js +43 -0
  81. package/spec/tests/iframe_container_spec.js +19 -0
  82. package/spec/tests/iframe_spec.js +265 -0
  83. package/spec/tests/inline_dialog_spec.js +41 -0
  84. package/spec/tests/inline_dialog_webitem_spec.js +331 -0
  85. package/spec/tests/loading_indicator_spec.js +48 -0
  86. package/spec/tests/messages_module_spec.js +108 -0
  87. package/spec/tests/meta_spec.js +71 -0
  88. package/spec/tests/module_args_spec.js +52 -0
  89. package/spec/tests/observe_spec.js +73 -0
  90. package/spec/tests/public_events.spec.js +102 -0
  91. package/spec/tests/scroll_position_spec.js +109 -0
  92. package/spec/tests/theming_spec.js +594 -0
  93. package/spec/tests/util_spec.js +55 -0
  94. package/spec/tests/utils/base64_spec.js +20 -0
  95. package/spec/tests/utils/button_spec.js +11 -0
  96. package/spec/tests/utils/cookie_spec.js +19 -0
  97. package/spec/tests/utils/deprecate_spec.js +22 -0
  98. package/spec/tests/utils/dialog_spec.js +297 -0
  99. package/spec/tests/utils/host_util_spec.js +45 -0
  100. package/spec/tests/utils/iframe_spec.js +41 -0
  101. package/spec/tests/utils/jwt_spec.js +135 -0
  102. package/spec/tests/utils/url_spec.js +37 -0
  103. package/spec/tests/utils/waitUntilReadyNextTick.js +5 -0
  104. package/spec/tests/utils/webitem.js +219 -0
  105. package/spec/tests/webitem_spec.js +131 -0
  106. package/src/css/host/dialog.css +144 -0
  107. package/src/css/host/flags.css +5 -0
  108. package/src/css/host/host.css +64 -0
  109. package/src/css/host/messages.css +12 -0
  110. package/src/css/plugin/legacy-text-colors.less +55 -0
  111. package/src/css/plugin/surfaces.css +11 -0
  112. package/src/host/ACJSFrameworkAdaptor.js +44 -0
  113. package/src/host/actions/analytics_action.js +35 -0
  114. package/src/host/actions/button_actions.js +21 -0
  115. package/src/host/actions/dialog_actions.js +27 -0
  116. package/src/host/actions/dialog_extension_actions.js +25 -0
  117. package/src/host/actions/dom_event_actions.js +32 -0
  118. package/src/host/actions/dropdown_actions.js +12 -0
  119. package/src/host/actions/env_actions.js +57 -0
  120. package/src/host/actions/event_actions.js +33 -0
  121. package/src/host/actions/flag_actions.js +22 -0
  122. package/src/host/actions/iframe_actions.js +27 -0
  123. package/src/host/actions/inline_dialog_actions.js +26 -0
  124. package/src/host/actions/inline_dialog_webitem_actions.js +10 -0
  125. package/src/host/actions/jwt_actions.js +51 -0
  126. package/src/host/actions/loading_indicator_actions.js +10 -0
  127. package/src/host/actions/module_actions.js +14 -0
  128. package/src/host/actions/webitem_actions.js +29 -0
  129. package/src/host/components/button.js +112 -0
  130. package/src/host/components/dialog.js +447 -0
  131. package/src/host/components/dialog_extension.js +106 -0
  132. package/src/host/components/dialog_webitem.js +69 -0
  133. package/src/host/components/flag.js +102 -0
  134. package/src/host/components/iframe.js +130 -0
  135. package/src/host/components/iframe_container.js +38 -0
  136. package/src/host/components/inline_dialog.js +108 -0
  137. package/src/host/components/inline_dialog_webitem.js +157 -0
  138. package/src/host/components/loading_indicator.js +110 -0
  139. package/src/host/components/webitem.js +227 -0
  140. package/src/host/deprecate.js +20 -0
  141. package/src/host/dispatchers/analytics_dispatcher.js +512 -0
  142. package/src/host/dispatchers/event_dispatcher.js +46 -0
  143. package/src/host/dollar.js +8 -0
  144. package/src/host/host-api.js +325 -0
  145. package/src/host/iframe-create.js +7 -0
  146. package/src/host/index.js +108 -0
  147. package/src/host/module-providers.js +13 -0
  148. package/src/host/modules/_featureFlag.js +42 -0
  149. package/src/host/modules/_performance.js +54 -0
  150. package/src/host/modules/analytics.js +17 -0
  151. package/src/host/modules/dialog.js +496 -0
  152. package/src/host/modules/dropdown.js +255 -0
  153. package/src/host/modules/env.js +156 -0
  154. package/src/host/modules/events.js +28 -0
  155. package/src/host/modules/flag.js +170 -0
  156. package/src/host/modules/host.js +19 -0
  157. package/src/host/modules/inline-dialog.js +33 -0
  158. package/src/host/modules/messages.js +309 -0
  159. package/src/host/modules/page.js +29 -0
  160. package/src/host/modules/scroll-position.js +102 -0
  161. package/src/host/modules/theming.js +114 -0
  162. package/src/host/stores/extension_configuration_options_store.js +24 -0
  163. package/src/host/util.js +98 -0
  164. package/src/host/utils/access-narrowing-context.js +18 -0
  165. package/src/host/utils/base64.js +22 -0
  166. package/src/host/utils/button.js +10 -0
  167. package/src/host/utils/cookie.js +14 -0
  168. package/src/host/utils/dialog.js +239 -0
  169. package/src/host/utils/feature-flag.js +27 -0
  170. package/src/host/utils/iframe.js +23 -0
  171. package/src/host/utils/jwt.js +60 -0
  172. package/src/host/utils/observe.js +34 -0
  173. package/src/host/utils/removal-observer.js +30 -0
  174. package/src/host/utils/simplexdm.js +58 -0
  175. package/src/host/utils/url.js +23 -0
  176. package/src/host/utils/webitem.js +214 -0
  177. package/src/plugin/amd.js +115 -0
  178. package/src/plugin/analytics.js +84 -0
  179. package/src/plugin/deprecate.js +16 -0
  180. package/src/plugin/dialog.js +197 -0
  181. package/src/plugin/dollar.js +77 -0
  182. package/src/plugin/events-instance.js +2 -0
  183. package/src/plugin/events.js +246 -0
  184. package/src/plugin/extension_configuration_options_store.js +27 -0
  185. package/src/plugin/featureFlag.js +8 -0
  186. package/src/plugin/index.js +118 -0
  187. package/src/plugin/meta.js +14 -0
  188. package/src/plugin/public-events.js +73 -0
  189. package/src/plugin/theming.js +211 -0
  190. package/src/plugin/util.js +104 -0
@@ -0,0 +1,429 @@
1
+ import AnalyticsDispatcher from 'src/host/dispatchers/analytics_dispatcher';
2
+ import IframeActions from 'src/host/actions/iframe_actions';
3
+ import LoadingIndicatorActions from 'src/host/actions/loading_indicator_actions';
4
+ import HostApi from '../../src/host/host-api';
5
+ import { observeIframeRemoval } from 'src/host/utils/removal-observer';
6
+ import getBooleanFeatureFlag from '../../src/host/utils/feature-flag';
7
+
8
+ const extension = {
9
+ id: 'xxxewjkd',
10
+ addon_key: 'some-addon-key',
11
+ key: 'some-module-key'
12
+ };
13
+
14
+
15
+
16
+ describe('Analytics Dispatcher', () => {
17
+ beforeEach(() => {
18
+ window.requestIdleCallback = (callback) => setTimeout(callback);
19
+ });
20
+
21
+ it('trackLoadingStarted stores the time', () => {
22
+ expect(AnalyticsDispatcher._addons).toEqual({});
23
+ AnalyticsDispatcher.trackLoadingStarted(extension);
24
+ expect(AnalyticsDispatcher._addons[extension.id].startLoading).toEqual(jasmine.any(Number));
25
+ });
26
+
27
+ it('should consistently sample load events', () => {
28
+ spyOn(AnalyticsDispatcher, '_shouldSampleEvent').and.returnValue(true);
29
+ expect(AnalyticsDispatcher._shouldSampleLoadEvent({ id: '1234' })).toBe(true);
30
+
31
+ AnalyticsDispatcher._shouldSampleEvent.and.returnValue(false);
32
+ // Remember the result for the same ID
33
+ expect(AnalyticsDispatcher._shouldSampleLoadEvent({ id: '1234' })).toBe(true);
34
+ // A new ID should return false
35
+ expect(AnalyticsDispatcher._shouldSampleLoadEvent({ id: '5678' })).toBe(false);
36
+ })
37
+
38
+ it('trackUseOfDeprecatedMethod triggers a gasV3 analytics call', () => {
39
+ spyOn(AnalyticsDispatcher, '_trackAndForwardGasV3');
40
+ var methodUsed = 'someDeprecatedMethodName';
41
+ AnalyticsDispatcher.trackGasV3UseOfDeprecatedMethod(methodUsed, extension);
42
+ expect(AnalyticsDispatcher._trackAndForwardGasV3).toHaveBeenCalledWith('operational', {
43
+ action: 'deprecatedMethod',
44
+ actionSubject: 'connectAddon',
45
+ actionSubjectId: extension.addon_key,
46
+ attributes: {
47
+ methodUsed: methodUsed,
48
+ moduleKey: extension.key
49
+ },
50
+ source: extension.addon_key
51
+ });
52
+ });
53
+
54
+ it('dispatch/trackExternal triggers a gasV3 analytics call', () => {
55
+ spyOn(AnalyticsDispatcher, '_trackAndForwardGasV3');
56
+ var eventName = 'event.name';
57
+ var eventValue = {
58
+ addonKey: extension.addon_key,
59
+ moduleKey: extension.key,
60
+ value: 'something'
61
+ };
62
+ AnalyticsDispatcher.dispatch(eventName, eventValue);
63
+ expect(AnalyticsDispatcher._trackAndForwardGasV3).toHaveBeenCalledWith('operational', {
64
+ action: 'externalEvent',
65
+ actionSubject: 'connectAddon',
66
+ actionSubjectId: eventName,
67
+ attributes: {
68
+ eventName,
69
+ addonKey: extension.addon_key,
70
+ moduleKey: extension.key,
71
+ value: 'something'
72
+ },
73
+ source: 'connectAddon'
74
+ });
75
+
76
+ });
77
+
78
+ it('removes the timeout when the entry is removed from addon store', () => {
79
+ AnalyticsDispatcher.trackLoadingStarted(extension);
80
+ expect(AnalyticsDispatcher._addons[extension.id]).toBeDefined();
81
+ let TimeoutId = AnalyticsDispatcher._addons[extension.id];
82
+ spyOn(window, 'clearTimeout');
83
+
84
+ AnalyticsDispatcher._resetAnalyticsDueToUnreliable(extension.id);
85
+ expect(AnalyticsDispatcher._addons[extension.id]).toBeUndefined();
86
+ expect(window.clearTimeout).toHaveBeenCalledWith(TimeoutId);
87
+ });
88
+
89
+ it('trackIframePerformanceMetrics triggers _analytics.trackIframePerformanceMetrics', () => {
90
+ spyOn(AnalyticsDispatcher, '_shouldSampleEvent').and.returnValue(true);
91
+ spyOn(AnalyticsDispatcher, '_trackGasV3');
92
+ var metrics = {
93
+ domainLookupTime: 111,
94
+ connectionTime: 2222,
95
+ decodedBodySize: 333,
96
+ domContentLoadedTime: 444,
97
+ fetchTime: 555
98
+ };
99
+ AnalyticsDispatcher.trackIframePerformance(metrics, Object.assign({}, extension, {options: {
100
+ pearApp: 'true',
101
+ moduleLocation: 'some-module-location',
102
+ moduleType: 'some-module-type'
103
+ }}));
104
+ expect(AnalyticsDispatcher._trackGasV3).toHaveBeenCalled();
105
+ expect(AnalyticsDispatcher._trackGasV3).toHaveBeenCalledWith('operational', {
106
+ source: extension['addon_key'],
107
+ action: 'iframeRendered',
108
+ actionSubject: 'connectAddon',
109
+ actionSubjectId: extension['addon_key'],
110
+ attributes: {
111
+ key: extension['key'],
112
+ pearApp: true,
113
+ domainLookupTime: metrics.domainLookupTime,
114
+ connectionTime: metrics.connectionTime,
115
+ decodedBodySize: metrics.decodedBodySize,
116
+ domContentLoadedTime: metrics.domContentLoadedTime,
117
+ fetchTime: metrics.fetchTime,
118
+ moduleLocation: 'some-module-location',
119
+ moduleType: 'some-module-type',
120
+ iframeIsCacheable: false
121
+ }
122
+ });
123
+ });
124
+
125
+ it('on iframe viewed trigger a gasv3 analytics call', () => {
126
+ spyOn(AnalyticsDispatcher, '_shouldSampleEvent').and.returnValue(true);
127
+ spyOn(AnalyticsDispatcher, '_trackGasV3');
128
+ const extension = {
129
+ addon_key: 'some-addon-key',
130
+ key: 'some-module-key',
131
+ options: {
132
+ moduleType: 'some-module-type',
133
+ pearApp: 'true',
134
+ moduleLocation: 'some-module-location'
135
+ },
136
+ id: 'some-addon-key__some-module-key_1y28nd',
137
+ startLoading: Date.now()
138
+ };
139
+ AnalyticsDispatcher.trackGasV3Visible(extension);
140
+
141
+ expect(AnalyticsDispatcher._trackGasV3).toHaveBeenCalledWith('operational', {
142
+ source: 'some-addon-key',
143
+ action: 'iframeViewed',
144
+ actionSubject: 'connectAddon',
145
+ actionSubjectId: 'some-addon-key',
146
+ attributes: {
147
+ iframeIsCacheable: false,
148
+ moduleType: 'some-module-type',
149
+ moduleKey: 'some-module-key',
150
+ moduleLocation: 'some-module-location',
151
+ pearApp: true
152
+ }
153
+ });
154
+ });
155
+
156
+ it('on method invoked trigger a gasv3 analytics call', () => {
157
+ spyOn(AnalyticsDispatcher, '_shouldSampleEvent').and.returnValue(true);
158
+ spyOn(AnalyticsDispatcher, '_trackAndForwardGasV3');
159
+ const extension = {
160
+ addon_key: 'some-addon-key',
161
+ key: 'some-module-key',
162
+ options: {
163
+ moduleType: 'some-module-type',
164
+ moduleLocation: 'some-module-location',
165
+ pearApp: 'false',
166
+ },
167
+ id: 'some-addon-key__some-module-key_1y28nd',
168
+ };
169
+ const module = 'foo';
170
+ const fn = 'bar';
171
+
172
+ const contextFn = () => {};
173
+ contextFn._context = {};
174
+
175
+ const args = [
176
+ undefined, null, 'some string', 1234, {}, [], () => {},
177
+ // Undefined and the context function should be ignored, only at the end
178
+ undefined, contextFn
179
+ ]
180
+ AnalyticsDispatcher.trackGasV3InvokeMethod(extension, module, fn, args);
181
+
182
+ expect(AnalyticsDispatcher._trackAndForwardGasV3).toHaveBeenCalledWith(
183
+ 'operational',
184
+ {
185
+ action: 'invokeMethod',
186
+ actionSubject: 'connectAddon',
187
+ actionSubjectId: 'some-addon-key',
188
+ attributes: {
189
+ argCount: 7,
190
+ argTypes: ['undefined', 'null', 'string', 'number', 'object', 'array', 'function'],
191
+ method: 'bar',
192
+ module: 'foo',
193
+ moduleKey: 'some-module-key',
194
+ moduleType: 'some-module-type',
195
+ moduleLocation: 'some-module-location',
196
+ pearApp: false,
197
+ },
198
+ source: 'some-addon-key',
199
+ }
200
+ );
201
+ });
202
+
203
+ it('should always trigger a gasv3 analytics call for allowlisted methods', () => {
204
+ // Should still be called even with sampling returning false
205
+ spyOn(AnalyticsDispatcher, '_shouldSampleEvent').and.returnValue(false);
206
+ spyOn(AnalyticsDispatcher, '_trackAndForwardGasV3');
207
+ const extension = {
208
+ addon_key: 'some-addon-key',
209
+ key: 'some-module-key',
210
+ options: {
211
+ moduleType: 'some-module-type',
212
+ moduleLocation: 'some-module-location',
213
+ pearApp: 'false',
214
+ },
215
+ id: 'some-addon-key__some-module-key_1y28nd',
216
+ };
217
+ const module = 'foo';
218
+ const fn = 'saveMacro';
219
+
220
+ AnalyticsDispatcher.trackGasV3InvokeMethod(extension, module, fn);
221
+
222
+ expect(AnalyticsDispatcher._trackAndForwardGasV3).toHaveBeenCalledWith(
223
+ 'operational',
224
+ {
225
+ action: 'invokeMethod',
226
+ actionSubject: 'connectAddon',
227
+ actionSubjectId: 'some-addon-key',
228
+ attributes: {
229
+ argCount: 0,
230
+ argTypes: [],
231
+ method: 'saveMacro',
232
+ module: 'foo',
233
+ moduleKey: 'some-module-key',
234
+ moduleType: 'some-module-type',
235
+ moduleLocation: 'some-module-location',
236
+ pearApp: false,
237
+ },
238
+ source: 'some-addon-key',
239
+ }
240
+ );
241
+ });
242
+
243
+ it('on iframe-bridge-established trigger a gasv3 analytics call', () => {
244
+ spyOn(AnalyticsDispatcher, '_shouldSampleEvent').and.returnValue(true);
245
+ spyOn(AnalyticsDispatcher, '_trackGasV3');
246
+ const extension = {
247
+ addon_key: 'some-addon-key',
248
+ key: 'some-module-key',
249
+ options: {
250
+ moduleType: 'some-module-type',
251
+ pearApp: 'true',
252
+ moduleLocation: 'some-module-location',
253
+ noDom: true,
254
+ },
255
+ id: 'some-addon-key__some-module-key_1y28nd',
256
+ startLoading: Date.now()
257
+ };
258
+ AnalyticsDispatcher._addons[extension.id] = extension;
259
+
260
+ IframeActions.notifyBridgeEstablished(document.createElement('div'), extension);
261
+
262
+ expect(AnalyticsDispatcher._trackGasV3).toHaveBeenCalledWith('operational', {
263
+ source: 'some-addon-key',
264
+ action: 'iframeLoaded',
265
+ actionSubject: 'connectAddon',
266
+ actionSubjectId: 'some-addon-key',
267
+ attributes: {
268
+ iframeIsCacheable: false,
269
+ iframeLoadMillis: jasmine.any(Number),
270
+ iframeLoadBucket: jasmine.any(Number),
271
+ moduleType: 'some-module-type',
272
+ moduleKey: 'some-module-key',
273
+ moduleLocation: 'some-module-location',
274
+ pearApp: true
275
+ }
276
+ });
277
+ });
278
+
279
+ it('on iframe-bridge-timeout trigger a gasv3 analytics call', () => {
280
+ spyOn(AnalyticsDispatcher, '_shouldSampleEvent').and.returnValue(true);
281
+ spyOn(AnalyticsDispatcher, '_trackGasV3');
282
+ const extension = {
283
+ addon_key: 'some-addon-key',
284
+ key: 'some-module-key',
285
+ options: {
286
+ moduleType: 'some-module-type',
287
+ pearApp: 'true',
288
+ moduleLocation: 'some-module-location',
289
+ noDom: true
290
+ },
291
+ id: 'some-addon-key__some-module-key_1d28nd'
292
+ };
293
+
294
+ LoadingIndicatorActions.timeout(document.createElement('div'), extension);
295
+
296
+ expect(AnalyticsDispatcher._trackGasV3).toHaveBeenCalledWith('operational', {
297
+ source: 'some-addon-key',
298
+ action: 'iframeTimeout',
299
+ actionSubject: 'connectAddon',
300
+ actionSubjectId: 'some-addon-key',
301
+ attributes: {
302
+ iframeIsCacheable: false,
303
+ moduleType: 'some-module-type',
304
+ moduleKey: 'some-module-key',
305
+ moduleLocation: 'some-module-location',
306
+ pearApp: true,
307
+ connectedStatus: 'true'
308
+ }
309
+ });
310
+ });
311
+
312
+ it('web-vitals analytics call should be triggered', () => {
313
+ spyOn(AnalyticsDispatcher, '_shouldSampleEvent').and.returnValue(true);
314
+ spyOn(AnalyticsDispatcher, '_trackAndForwardGasV3');
315
+ const extension = {
316
+ addon_key: 'some-addon-key',
317
+ key: 'some-module-key',
318
+ options: {
319
+ moduleType: 'some-module-type',
320
+ pearApp: 'true',
321
+ moduleLocation: 'some-module-location'
322
+ },
323
+ }
324
+
325
+ AnalyticsDispatcher.trackGasV3WebVitals({
326
+ 'metric:cls': 0.1,
327
+ 'metric:fcp': 1000,
328
+ 'metric:lcp': 2000,
329
+ 'metric:ttfb': 3000,
330
+ }, extension);
331
+
332
+ expect(AnalyticsDispatcher._trackAndForwardGasV3).toHaveBeenCalledWith('operational', {
333
+ source: 'some-addon-key',
334
+ action: 'webVitals',
335
+ actionSubject: 'connectAddon',
336
+ actionSubjectId: 'some-addon-key',
337
+ attributes: {
338
+ 'metric:cls': 0.1,
339
+ 'metric:fcp': 1000,
340
+ 'metric:lcp': 2000,
341
+ 'metric:ttfb': 3000,
342
+ iframeIsCacheable: false,
343
+ moduleType: 'some-module-type',
344
+ key: 'some-module-key',
345
+ moduleLocation: 'some-module-location',
346
+ pearApp: true
347
+ }});
348
+ })
349
+
350
+ it('should trigger analytics when statsig client is not initialized', () => {
351
+ spyOn(AnalyticsDispatcher, '_trackAndForwardGasV3');
352
+ getBooleanFeatureFlag('some-flag', true);
353
+ expect(AnalyticsDispatcher._trackAndForwardGasV3).toHaveBeenCalledWith('operational', {
354
+ action: 'returned',
355
+ actionSubject: 'defaultFalseFeatureFlag',
356
+ actionSubjectId: 'some-flag',
357
+ attributes: {
358
+ featureFlag: 'some-flag',
359
+ },
360
+ source: 'Host',
361
+ });
362
+ });
363
+ });
364
+
365
+ describe('observeIframes', () => {
366
+ let observer;
367
+ beforeEach(() => {
368
+ // Mocking HostApi.destroy method
369
+ spyOn(HostApi, 'destroy');
370
+
371
+ // Pass the mockHostApi to the observeIframes function
372
+ observer = observeIframeRemoval();
373
+ });
374
+
375
+ afterEach(() => {
376
+ // Clean up after each test
377
+ observer.disconnect();
378
+ });
379
+
380
+ it('should call HostApi.destroy when an iframe is removed', (done) => {
381
+ // Create a test iframe with an ID and append it to the document
382
+ const iframe = document.createElement('iframe');
383
+ iframe.id = 'test-iframe';
384
+ document.body.appendChild(iframe);
385
+
386
+ // Remove the iframe and check if HostApi.destroy is called
387
+ document.body.removeChild(iframe);
388
+
389
+ // MutationObserver works asynchronously, so we need to wait for it
390
+ setTimeout(() => {
391
+ expect(HostApi.destroy).toHaveBeenCalledWith('test-iframe');
392
+ done();
393
+ }, 50);
394
+ });
395
+
396
+ it('should call HostApi.destroy for iframes within a removed node', (done) => {
397
+ // Create a div containing an iframe
398
+ const container = document.createElement('div');
399
+ const nestedIframe = document.createElement('iframe');
400
+ nestedIframe.id = 'nested-iframe';
401
+ container.appendChild(nestedIframe);
402
+ document.body.appendChild(container);
403
+
404
+ // Remove the container div
405
+ document.body.removeChild(container);
406
+
407
+ // MutationObserver works asynchronously, so we need to wait for it
408
+ setTimeout(() => {
409
+ expect(HostApi.destroy).toHaveBeenCalledWith('nested-iframe');
410
+ done();
411
+ }, 50);
412
+ });
413
+
414
+ it('should not call HostApi.destroy if a non-iframe node is removed', (done) => {
415
+ // Create a non-iframe element and append it to the document
416
+ const div = document.createElement('div');
417
+ div.id = 'test-div';
418
+ document.body.appendChild(div);
419
+
420
+ // Remove the non-iframe element
421
+ document.body.removeChild(div);
422
+
423
+ // MutationObserver works asynchronously, so we need to wait to ensure no calls were made
424
+ setTimeout(() => {
425
+ expect(HostApi.destroy).not.toHaveBeenCalled();
426
+ done();
427
+ }, 50);
428
+ });
429
+ });
@@ -0,0 +1,41 @@
1
+ import AP from 'simple-xdm/combined';
2
+ import analytics from 'src/plugin/analytics';
3
+
4
+ describe('Plugin Performance Analytics', () => {
5
+ beforeEach(() => {
6
+ AP._analytics = {
7
+ trackIframePerformanceMetrics: jasmine.createSpy('testFunction'),
8
+ trackWebVitals: jasmine.createSpy('trackWebVitals')
9
+ };
10
+ });
11
+
12
+ afterEach(() => {
13
+ window.AP = null;
14
+ });
15
+
16
+ it('sends performance metrics', () => {
17
+ analytics.sendMetrics();
18
+ expect(AP._analytics.trackIframePerformanceMetrics).toHaveBeenCalled();
19
+ const firstCallArg = AP._analytics.trackIframePerformanceMetrics.calls.first().args[0];
20
+ // We skip these assertions for the p2 plugin integration test version of chrome used in webdriver tests because the API does not exist in that version.
21
+ // We also skip these in Safari because Safari does not support this API.
22
+ if(navigator.appVersion.indexOf('Chrome/48.') === -1 && navigator.appVersion.indexOf('Safari') === -1) {
23
+ expect(firstCallArg.connectionTime).toEqual(jasmine.any(Number));
24
+ expect(firstCallArg.decodedBodySize).toEqual(jasmine.any(Number));
25
+ expect(firstCallArg.domContentLoadedTime).toEqual(jasmine.any(Number));
26
+ expect(firstCallArg.domainLookupTime).toEqual(jasmine.any(Number));
27
+ expect(firstCallArg.fetchTime).toEqual(jasmine.any(Number));
28
+ }
29
+ });
30
+
31
+ it('sends web vitals', (done) => {
32
+ analytics.setupWebVitals(1000);
33
+ setTimeout(() => {
34
+ expect(AP._analytics.trackWebVitals).toHaveBeenCalled();
35
+ const firstCallArg = AP._analytics.trackWebVitals.calls.first().args[0];
36
+ expect(Object.keys(firstCallArg).length).toBeGreaterThan(0);
37
+ expect(Object.values(firstCallArg).every(value => typeof value === 'number')).toBe(true);
38
+ done()
39
+ }, 1001);
40
+ })
41
+ });
@@ -0,0 +1,143 @@
1
+ import ButtonComponent from 'src/host/components/button';
2
+ import EventDispatcher from 'src/host/dispatchers/event_dispatcher';
3
+
4
+ describe('Button component', () => {
5
+ afterEach(() => {
6
+ $('.aui-button').remove();
7
+ });
8
+ describe('render', () => {
9
+
10
+ it('renders a button', () => {
11
+ var $button = ButtonComponent.render();
12
+ expect($button[0].nodeName).toEqual('BUTTON');
13
+ expect($button.hasClass(ButtonComponent.AP_BUTTON_CLASS)).toBe(true);
14
+ expect($button.attr('id')).toMatch(/ap\-button\-[0-9]{1}/);
15
+ expect($button.attr('aria-disabled')).toEqual('false');
16
+ });
17
+
18
+ it('with an id', () => {
19
+ var id = 'someid';
20
+ var $button = ButtonComponent.render({
21
+ id: id
22
+ });
23
+ expect($button.attr('id')).toEqual(id);
24
+ });
25
+
26
+ it('with text', () => {
27
+ var text = 'button text';
28
+ var $button = ButtonComponent.render({text: text});
29
+ expect($button.text()).toEqual(text);
30
+ });
31
+
32
+ it('with additional class', () => {
33
+ var className = 'aclass';
34
+ var $button = ButtonComponent.render({additionalClasses: className});
35
+ expect($button.hasClass(className)).toBe(true);
36
+ });
37
+
38
+ it('with additional classes', () => {
39
+ var classNames = ['classa','classb'];
40
+ var $button = ButtonComponent.render({additionalClasses: classNames});
41
+ for(var i = 0; i < classNames.length; i++){
42
+ expect($button.hasClass(classNames[i])).toBe(true);
43
+ }
44
+ });
45
+
46
+ it('with type', () => {
47
+ var type = 'secondary';
48
+ var $button = ButtonComponent.render({type: type});
49
+ expect($button.hasClass('aui-button-' + type)).toBe(true);
50
+ });
51
+
52
+ it('with disabled', () => {
53
+ var $button = ButtonComponent.render({disabled: true});
54
+ expect($button.attr('aria-disabled')).toEqual('true');
55
+ });
56
+
57
+ });
58
+
59
+ describe('button click', () => {
60
+ it('does not dispatch an event when disabled', (done) => {
61
+ var $button = ButtonComponent.render({disabled: true});
62
+ document.body.appendChild($button[0]);
63
+ var spy = jasmine.createSpy('spy');
64
+ EventDispatcher.registerOnce('button-clicked', spy);
65
+
66
+ // jquery bug, need to wait 1 tick before triggering.
67
+ setTimeout(function(){
68
+ $button.trigger('click');
69
+ }, 0);
70
+
71
+ // wait longer than the 1 tick to make sure it's not triggered.
72
+ setTimeout(function(){
73
+ expect(spy).not.toHaveBeenCalled();
74
+ done();
75
+ }, 10);
76
+ });
77
+
78
+ it('disaptches an event', (done) => {
79
+ var $button = ButtonComponent.render({disabled: false});
80
+ document.body.appendChild($button[0]);
81
+ EventDispatcher.registerOnce('button-clicked', (data) => {
82
+ expect(data.$el.attr('id')).toEqual($button.attr('id'));
83
+ done();
84
+ });
85
+ // jquery bug, need to wait 1 tick before triggering.
86
+ setTimeout(function(){
87
+ $button.trigger('click');
88
+ }, 0);
89
+ });
90
+
91
+ });
92
+
93
+ describe('button toggle', () => {
94
+ it('state to disabled', () => {
95
+ var $button = ButtonComponent.render();
96
+ expect(ButtonComponent.isEnabled($button)).toBe(true);
97
+ ButtonComponent.setDisabled($button, true);
98
+ expect(ButtonComponent.isEnabled($button)).toBe(false);
99
+ });
100
+
101
+ it('state to enabled', () => {
102
+ var $button = ButtonComponent.render({disabled: true});
103
+ expect(ButtonComponent.isEnabled($button)).toBe(false);
104
+ ButtonComponent.setDisabled($button, false);
105
+ expect(ButtonComponent.isEnabled($button)).toBe(true);
106
+ });
107
+
108
+ it('visibility to hidden', () => {
109
+ var $button = ButtonComponent.render();
110
+ document.body.appendChild($button[0]);
111
+ expect($button.is(':visible')).toBe(true);
112
+ ButtonComponent.setHidden($button, true);
113
+ expect($button.is(':visible')).toBe(false);
114
+ });
115
+
116
+ it('visibility to visible', () => {
117
+ var $button = ButtonComponent.render();
118
+ document.body.appendChild($button[0]);
119
+ $button.hide();
120
+ expect($button.is(':visible')).toBe(false);
121
+ ButtonComponent.setHidden($button, false);
122
+ expect($button.is(':visible')).toBe(true);
123
+ });
124
+ });
125
+
126
+ describe('immutable button toggle', () => {
127
+ it('state to disabled', () => {
128
+ var $button = ButtonComponent.render({immutable: true});
129
+ expect(ButtonComponent.isEnabled($button)).toBe(true);
130
+ ButtonComponent.setDisabled($button, true);
131
+ expect(ButtonComponent.isEnabled($button)).toBe(true);
132
+ });
133
+
134
+ it('visibility to hidden', () => {
135
+ var $button = ButtonComponent.render({immutable: true});
136
+ document.body.appendChild($button[0]);
137
+ expect($button.is(':visible')).toBe(true);
138
+ ButtonComponent.setHidden($button, true);
139
+ expect($button.is(':visible')).toBe(true);
140
+ });
141
+ });
142
+
143
+ });
@@ -0,0 +1,59 @@
1
+ import DialogExtensionComponent from 'src/host/components/dialog_extension';
2
+ import baseDialogComponentTests from 'fixtures/base_dialog_component_tests';
3
+
4
+ describe('dialog extension component', () => {
5
+ afterEach(() => {
6
+ $('.aui-dialog2').remove();
7
+ $('.aui-blanket').remove();
8
+ });
9
+ describe('render', () => {
10
+ var extension = {
11
+ addon_key: 'some-key',
12
+ key: 'module-key',
13
+ url: 'https://www.example.com'
14
+ };
15
+
16
+ it('renders a chromeless dialog', () => {
17
+ var options = baseDialogComponentTests.getChromelessOptions();
18
+ expect($('.aui-dialog2').length).toEqual(0);
19
+ DialogExtensionComponent.render(extension, options);
20
+ expect($('.aui-dialog2').length).toEqual(1);
21
+ baseDialogComponentTests.testChromeless(options);
22
+ });
23
+
24
+ it('renders a dialog with chrome', () => {
25
+ var options = baseDialogComponentTests.getChromeOptions();
26
+ expect($('.aui-dialog2').length).toEqual(0);
27
+ DialogExtensionComponent.render(extension, options);
28
+ expect($('.aui-dialog2').length).toEqual(1);
29
+ baseDialogComponentTests.testChrome(options);
30
+ });
31
+
32
+ it('renders a dialog with chrome in fullscreen', () => {
33
+ var options = baseDialogComponentTests.getFullscreenOptions();
34
+ expect($('.aui-dialog2').length).toEqual(0);
35
+ DialogExtensionComponent.render(extension, options);
36
+ expect($('.aui-dialog2').length).toEqual(1);
37
+ baseDialogComponentTests.testFullScreen(options);
38
+ });
39
+
40
+
41
+ it('contains an iframe', () => {
42
+ DialogExtensionComponent.render(extension);
43
+ expect($('.aui-dialog2 iframe').length).toEqual(1);
44
+ });
45
+
46
+ it('correct dimensions', () => {
47
+ var dimensions = {
48
+ width: 456,
49
+ height: 321
50
+ };
51
+ DialogExtensionComponent.render(extension, dimensions);
52
+ var $iframe = $('.aui-dialog2 iframe');
53
+ expect($iframe.length).toEqual(1);
54
+ expect($iframe.width()).toEqual(dimensions.width);
55
+ expect($iframe.height()).toEqual(dimensions.height);
56
+ });
57
+
58
+ });
59
+ });