onairos 2.2.1 → 2.3.0

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.
@@ -0,0 +1,647 @@
1
+ /**
2
+ * Laravel Integration Test Suite
3
+ *
4
+ * Tests the Laravel-specific functionality of the Onairos package
5
+ * including Blade helpers, Vue components, and Vite plugins.
6
+ */
7
+
8
+ import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
9
+ import { JSDOM } from 'jsdom';
10
+ import { createApp } from 'vue';
11
+ import { mount } from '@vue/test-utils';
12
+
13
+ // Import Laravel-specific modules
14
+ import {
15
+ initializeOnairosForBlade,
16
+ createOnairosButton,
17
+ renderOnairosDirective
18
+ } from '../../src/laravel/blade-helpers.js';
19
+
20
+ import OnairosVue from '../../src/laravel/OnairosVue.vue';
21
+
22
+ import {
23
+ onairosLaravelPlugin,
24
+ onairosVuePlugin,
25
+ onairosReactPlugin
26
+ } from '../../src/laravel/vite-plugin.js';
27
+
28
+ // Mock DOM environment
29
+ const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
30
+ global.window = dom.window;
31
+ global.document = dom.window.document;
32
+ global.navigator = dom.window.navigator;
33
+
34
+ describe('Laravel Blade Integration', () => {
35
+ beforeEach(() => {
36
+ // Reset DOM
37
+ document.body.innerHTML = '';
38
+
39
+ // Reset global objects
40
+ delete window.OnairosConfig;
41
+ delete window.OnairosUtils;
42
+ delete window.createOnairosButton;
43
+ delete window.initializeOnairosForBlade;
44
+
45
+ // Mock console methods
46
+ vi.spyOn(console, 'log').mockImplementation(() => {});
47
+ vi.spyOn(console, 'error').mockImplementation(() => {});
48
+ });
49
+
50
+ afterEach(() => {
51
+ vi.restoreAllMocks();
52
+ });
53
+
54
+ describe('initializeOnairosForBlade', () => {
55
+ test('should initialize with default configuration', () => {
56
+ initializeOnairosForBlade();
57
+
58
+ expect(window.OnairosConfig).toBeDefined();
59
+ expect(window.OnairosConfig.baseUrl).toBe('https://api2.onairos.uk');
60
+ expect(window.OnairosConfig.testMode).toBe(false);
61
+ expect(window.OnairosConfig.autoDetectMobile).toBe(true);
62
+ });
63
+
64
+ test('should merge custom configuration', () => {
65
+ const customConfig = {
66
+ apiKey: 'test-api-key',
67
+ testMode: true,
68
+ baseUrl: 'https://custom.api.com'
69
+ };
70
+
71
+ initializeOnairosForBlade(customConfig);
72
+
73
+ expect(window.OnairosConfig.apiKey).toBe('test-api-key');
74
+ expect(window.OnairosConfig.testMode).toBe(true);
75
+ expect(window.OnairosConfig.baseUrl).toBe('https://custom.api.com');
76
+ expect(window.OnairosConfig.autoDetectMobile).toBe(true); // Default preserved
77
+ });
78
+
79
+ test('should inject global styles when enabled', () => {
80
+ initializeOnairosForBlade({ globalStyles: true });
81
+
82
+ const styleElement = document.getElementById('onairos-styles');
83
+ expect(styleElement).not.toBeNull();
84
+ expect(styleElement.textContent).toContain('.onairos-btn');
85
+ });
86
+
87
+ test('should not inject styles when disabled', () => {
88
+ initializeOnairosForBlade({ globalStyles: false });
89
+
90
+ const styleElement = document.getElementById('onairos-styles');
91
+ expect(styleElement).toBeNull();
92
+ });
93
+
94
+ test('should setup mobile detection utilities', () => {
95
+ initializeOnairosForBlade();
96
+
97
+ expect(window.OnairosUtils).toBeDefined();
98
+ expect(typeof window.OnairosUtils.detectMobile).toBe('function');
99
+ expect(typeof window.OnairosUtils.isMobile).toBe('boolean');
100
+ });
101
+ });
102
+
103
+ describe('createOnairosButton', () => {
104
+ beforeEach(() => {
105
+ initializeOnairosForBlade();
106
+ });
107
+
108
+ test('should create button in target element', () => {
109
+ // Create target element
110
+ const targetDiv = document.createElement('div');
111
+ targetDiv.id = 'test-button';
112
+ document.body.appendChild(targetDiv);
113
+
114
+ createOnairosButton('test-button', {
115
+ requestData: ['email', 'profile'],
116
+ webpageName: 'Test App'
117
+ });
118
+
119
+ expect(targetDiv.innerHTML).toContain('onairos-button-container');
120
+ expect(targetDiv.innerHTML).toContain('Connect with Onairos');
121
+ });
122
+
123
+ test('should handle missing target element gracefully', () => {
124
+ const consoleSpy = vi.spyOn(console, 'error');
125
+
126
+ createOnairosButton('non-existent-element', {});
127
+
128
+ expect(consoleSpy).toHaveBeenCalledWith(
129
+ expect.stringContaining('Element with ID "non-existent-element" not found')
130
+ );
131
+ });
132
+
133
+ test('should apply custom configuration', () => {
134
+ const targetDiv = document.createElement('div');
135
+ targetDiv.id = 'custom-button';
136
+ document.body.appendChild(targetDiv);
137
+
138
+ createOnairosButton('custom-button', {
139
+ requestData: ['email'],
140
+ webpageName: 'Custom App',
141
+ buttonType: 'icon',
142
+ textColor: 'blue'
143
+ });
144
+
145
+ const button = document.getElementById('custom-button-btn');
146
+ expect(button).not.toBeNull();
147
+ expect(button.className).toContain('onairos-btn-icon');
148
+
149
+ const textSpan = button.querySelector('.onairos-btn-text');
150
+ expect(textSpan.style.color).toBe('blue');
151
+ });
152
+
153
+ test('should add click event listener', () => {
154
+ const targetDiv = document.createElement('div');
155
+ targetDiv.id = 'clickable-button';
156
+ document.body.appendChild(targetDiv);
157
+
158
+ createOnairosButton('clickable-button', {
159
+ requestData: ['email'],
160
+ webpageName: 'Clickable App'
161
+ });
162
+
163
+ const button = document.getElementById('clickable-button-btn');
164
+ expect(button).not.toBeNull();
165
+
166
+ // Verify button has click event (we can't easily test the actual click without complex mocking)
167
+ expect(button.onclick).not.toBeNull();
168
+ });
169
+ });
170
+
171
+ describe('renderOnairosDirective', () => {
172
+ test('should generate HTML with unique ID', () => {
173
+ const html1 = renderOnairosDirective({ requestData: ['email'] });
174
+ const html2 = renderOnairosDirective({ requestData: ['profile'] });
175
+
176
+ expect(html1).toContain('<div id="onairos-');
177
+ expect(html2).toContain('<div id="onairos-');
178
+
179
+ // Extract IDs to ensure they're unique
180
+ const id1 = html1.match(/id="(onairos-[^"]+)"/)[1];
181
+ const id2 = html2.match(/id="(onairos-[^"]+)"/)[1];
182
+
183
+ expect(id1).not.toBe(id2);
184
+ });
185
+
186
+ test('should include configuration in script', () => {
187
+ const options = {
188
+ requestData: ['email', 'profile'],
189
+ webpageName: 'Directive Test'
190
+ };
191
+
192
+ const html = renderOnairosDirective(options);
193
+
194
+ expect(html).toContain('createOnairosButton');
195
+ expect(html).toContain(JSON.stringify(options));
196
+ expect(html).toContain('DOMContentLoaded');
197
+ });
198
+ });
199
+
200
+ describe('Mobile Detection', () => {
201
+ beforeEach(() => {
202
+ initializeOnairosForBlade();
203
+ });
204
+
205
+ test('should detect mobile user agents', () => {
206
+ // Mock mobile user agent
207
+ Object.defineProperty(window.navigator, 'userAgent', {
208
+ writable: true,
209
+ value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)'
210
+ });
211
+
212
+ const isMobile = window.OnairosUtils.detectMobile();
213
+ expect(isMobile).toBe(true);
214
+ });
215
+
216
+ test('should detect desktop user agents', () => {
217
+ // Mock desktop user agent
218
+ Object.defineProperty(window.navigator, 'userAgent', {
219
+ writable: true,
220
+ value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
221
+ });
222
+
223
+ // Mock window dimensions for desktop
224
+ Object.defineProperty(window, 'innerWidth', {
225
+ writable: true,
226
+ value: 1920
227
+ });
228
+
229
+ const isMobile = window.OnairosUtils.detectMobile();
230
+ expect(isMobile).toBe(false);
231
+ });
232
+
233
+ test('should detect mobile by screen width', () => {
234
+ // Mock narrow screen
235
+ Object.defineProperty(window, 'innerWidth', {
236
+ writable: true,
237
+ value: 500
238
+ });
239
+
240
+ const isMobile = window.OnairosUtils.detectMobile();
241
+ expect(isMobile).toBe(true);
242
+ });
243
+ });
244
+ });
245
+
246
+ describe('Laravel Vue Integration', () => {
247
+ describe('OnairosVue Component', () => {
248
+ test('should render with default props', () => {
249
+ const wrapper = mount(OnairosVue);
250
+
251
+ expect(wrapper.find('.onairos-vue-wrapper').exists()).toBe(true);
252
+ expect(wrapper.find('.onairos-vue-btn').exists()).toBe(true);
253
+ expect(wrapper.text()).toContain('Connect with Onairos');
254
+ });
255
+
256
+ test('should accept custom props', () => {
257
+ const wrapper = mount(OnairosVue, {
258
+ props: {
259
+ requestData: ['email', 'profile', 'preferences'],
260
+ webpageName: 'Vue Test App',
261
+ buttonType: 'rounded',
262
+ size: 'large',
263
+ textColor: 'black'
264
+ }
265
+ });
266
+
267
+ expect(wrapper.vm.requestData).toEqual(['email', 'profile', 'preferences']);
268
+ expect(wrapper.vm.webpageName).toBe('Vue Test App');
269
+ expect(wrapper.vm.buttonType).toBe('rounded');
270
+ expect(wrapper.vm.size).toBe('large');
271
+ });
272
+
273
+ test('should validate prop types', () => {
274
+ // Test invalid buttonType
275
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
276
+
277
+ mount(OnairosVue, {
278
+ props: {
279
+ buttonType: 'invalid-type'
280
+ }
281
+ });
282
+
283
+ // Vue should warn about invalid prop value
284
+ expect(consoleSpy).toHaveBeenCalled();
285
+ });
286
+
287
+ test('should apply correct CSS classes', () => {
288
+ const wrapper = mount(OnairosVue, {
289
+ props: {
290
+ buttonType: 'pill',
291
+ size: 'large',
292
+ customClass: 'my-custom-class'
293
+ }
294
+ });
295
+
296
+ const button = wrapper.find('.onairos-vue-btn');
297
+ expect(button.classes()).toContain('onairos-btn-pill');
298
+ expect(button.classes()).toContain('onairos-btn-large');
299
+ expect(button.classes()).toContain('my-custom-class');
300
+ });
301
+
302
+ test('should handle loading state', async () => {
303
+ const wrapper = mount(OnairosVue);
304
+
305
+ // Initially not loading
306
+ expect(wrapper.find('.onairos-loading').exists()).toBe(false);
307
+ expect(wrapper.find('.onairos-vue-btn').exists()).toBe(true);
308
+
309
+ // Set loading state
310
+ await wrapper.setData({ isLoading: true });
311
+
312
+ expect(wrapper.find('.onairos-loading').exists()).toBe(true);
313
+ expect(wrapper.find('.onairos-vue-btn').exists()).toBe(false);
314
+ });
315
+
316
+ test('should emit events on click', async () => {
317
+ const wrapper = mount(OnairosVue);
318
+
319
+ await wrapper.find('.onairos-vue-btn').trigger('click');
320
+
321
+ expect(wrapper.emitted()).toHaveProperty('click');
322
+ expect(wrapper.emitted()).toHaveProperty('loading');
323
+ });
324
+
325
+ test('should show success message', async () => {
326
+ const wrapper = mount(OnairosVue, {
327
+ props: {
328
+ successMessage: 'Custom success message'
329
+ }
330
+ });
331
+
332
+ await wrapper.setData({ showSuccess: true });
333
+
334
+ const successDiv = wrapper.find('.onairos-success');
335
+ expect(successDiv.exists()).toBe(true);
336
+ expect(successDiv.text()).toContain('Custom success message');
337
+ });
338
+
339
+ test('should show error message', async () => {
340
+ const wrapper = mount(OnairosVue);
341
+
342
+ await wrapper.setData({ error: 'Connection failed' });
343
+
344
+ const errorDiv = wrapper.find('.onairos-error');
345
+ expect(errorDiv.exists()).toBe(true);
346
+ expect(errorDiv.text()).toContain('Connection failed');
347
+ });
348
+
349
+ test('should disable button when disabled prop is true', () => {
350
+ const wrapper = mount(OnairosVue, {
351
+ props: {
352
+ disabled: true
353
+ }
354
+ });
355
+
356
+ const button = wrapper.find('.onairos-vue-btn');
357
+ expect(button.attributes('disabled')).toBeDefined();
358
+ expect(button.classes()).toContain('onairos-btn-disabled');
359
+ });
360
+ });
361
+ });
362
+
363
+ describe('Laravel Vite Plugin Integration', () => {
364
+ describe('onairosLaravelPlugin', () => {
365
+ test('should create plugin with default options', () => {
366
+ const plugin = onairosLaravelPlugin();
367
+
368
+ expect(plugin).toBeDefined();
369
+ expect(plugin.name).toBe('onairos-laravel');
370
+ expect(typeof plugin.config).toBe('function');
371
+ });
372
+
373
+ test('should accept custom options', () => {
374
+ const customOptions = {
375
+ autoImport: false,
376
+ bladeSupport: false,
377
+ enableHMR: false
378
+ };
379
+
380
+ const plugin = onairosLaravelPlugin(customOptions);
381
+ expect(plugin).toBeDefined();
382
+ expect(plugin.name).toBe('onairos-laravel');
383
+ });
384
+
385
+ test('should configure Vite properly', () => {
386
+ const plugin = onairosLaravelPlugin({
387
+ optimizeDeps: true,
388
+ bladeSupport: true
389
+ });
390
+
391
+ const mockViteConfig = {
392
+ optimizeDeps: { include: [] },
393
+ resolve: { alias: {} },
394
+ server: { watch: { include: [] } }
395
+ };
396
+
397
+ // Simulate Vite calling the config function
398
+ plugin.config(mockViteConfig, { command: 'serve' });
399
+
400
+ expect(mockViteConfig.optimizeDeps.include).toContain('onairos');
401
+ expect(mockViteConfig.resolve.alias['@onairos']).toBeDefined();
402
+ expect(mockViteConfig.server.watch.include).toContain('resources/views/**/*.blade.php');
403
+ });
404
+ });
405
+
406
+ describe('onairosVuePlugin', () => {
407
+ test('should create Vue-specific plugin', () => {
408
+ const plugin = onairosVuePlugin();
409
+
410
+ expect(plugin).toBeDefined();
411
+ expect(plugin.name).toBe('onairos-vue-laravel');
412
+ expect(typeof plugin.config).toBe('function');
413
+ });
414
+
415
+ test('should configure Vue dependencies', () => {
416
+ const plugin = onairosVuePlugin();
417
+ const mockViteConfig = {
418
+ optimizeDeps: { include: [] }
419
+ };
420
+
421
+ plugin.config(mockViteConfig);
422
+
423
+ expect(mockViteConfig.optimizeDeps.include).toContain('onairos');
424
+ expect(mockViteConfig.optimizeDeps.include).toContain('vue');
425
+ });
426
+
427
+ test('should handle auto-import for Vue files', () => {
428
+ const plugin = onairosVuePlugin({ autoImport: true });
429
+
430
+ const codeWithOnairosButton = '<template><OnairosButton /></template>';
431
+ const codeWithoutImport = 'export default {}';
432
+
433
+ // Mock transform function
434
+ const result1 = plugin.transform(codeWithOnairosButton, 'Component.vue');
435
+ const result2 = plugin.transform(codeWithoutImport, 'Other.vue');
436
+
437
+ expect(result1).toContain("import { OnairosButton } from 'onairos'");
438
+ expect(result2).toBeNull(); // No transform needed
439
+ });
440
+ });
441
+
442
+ describe('onairosReactPlugin', () => {
443
+ test('should create React-specific plugin', () => {
444
+ const plugin = onairosReactPlugin();
445
+
446
+ expect(plugin).toBeDefined();
447
+ expect(plugin.name).toBe('onairos-react-laravel');
448
+ expect(typeof plugin.config).toBe('function');
449
+ });
450
+
451
+ test('should configure React dependencies', () => {
452
+ const plugin = onairosReactPlugin();
453
+ const mockViteConfig = {
454
+ optimizeDeps: { include: [] }
455
+ };
456
+
457
+ plugin.config(mockViteConfig);
458
+
459
+ expect(mockViteConfig.optimizeDeps.include).toContain('onairos');
460
+ expect(mockViteConfig.optimizeDeps.include).toContain('react');
461
+ expect(mockViteConfig.optimizeDeps.include).toContain('react-dom');
462
+ });
463
+
464
+ test('should handle auto-import for React files', () => {
465
+ const plugin = onairosReactPlugin({ autoImport: true });
466
+
467
+ const jsxCode = 'function App() { return <OnairosButton />; }';
468
+ const tsxCode = 'const App: React.FC = () => <OnairosButton />;';
469
+ const regularJs = 'console.log("hello");';
470
+
471
+ const result1 = plugin.transform(jsxCode, 'App.jsx');
472
+ const result2 = plugin.transform(tsxCode, 'App.tsx');
473
+ const result3 = plugin.transform(regularJs, 'utils.js');
474
+
475
+ expect(result1).toContain("import { OnairosButton } from 'onairos'");
476
+ expect(result2).toContain("import { OnairosButton } from 'onairos'");
477
+ expect(result3).toBeNull();
478
+ });
479
+ });
480
+ });
481
+
482
+ describe('Laravel Integration - End-to-End Scenarios', () => {
483
+ beforeEach(() => {
484
+ // Reset environment
485
+ document.body.innerHTML = '';
486
+ delete window.OnairosConfig;
487
+ delete window.OnairosUtils;
488
+ });
489
+
490
+ test('should support complete Blade integration workflow', () => {
491
+ // 1. Initialize Onairos for Blade
492
+ initializeOnairosForBlade({
493
+ testMode: true,
494
+ webpageName: 'E2E Test App'
495
+ });
496
+
497
+ // 2. Create button element
498
+ const buttonContainer = document.createElement('div');
499
+ buttonContainer.id = 'e2e-button';
500
+ document.body.appendChild(buttonContainer);
501
+
502
+ // 3. Create Onairos button
503
+ createOnairosButton('e2e-button', {
504
+ requestData: ['email', 'profile'],
505
+ webpageName: 'E2E Test',
506
+ buttonType: 'pill'
507
+ });
508
+
509
+ // 4. Verify complete setup
510
+ expect(window.OnairosConfig).toBeDefined();
511
+ expect(window.OnairosUtils).toBeDefined();
512
+ expect(document.getElementById('e2e-button-btn')).not.toBeNull();
513
+ expect(document.getElementById('onairos-styles')).not.toBeNull();
514
+ });
515
+
516
+ test('should handle multiple buttons on same page', () => {
517
+ initializeOnairosForBlade();
518
+
519
+ // Create multiple button containers
520
+ const container1 = document.createElement('div');
521
+ container1.id = 'button-1';
522
+ const container2 = document.createElement('div');
523
+ container2.id = 'button-2';
524
+
525
+ document.body.appendChild(container1);
526
+ document.body.appendChild(container2);
527
+
528
+ // Create multiple buttons
529
+ createOnairosButton('button-1', {
530
+ requestData: ['email'],
531
+ webpageName: 'App 1'
532
+ });
533
+
534
+ createOnairosButton('button-2', {
535
+ requestData: ['profile'],
536
+ webpageName: 'App 2'
537
+ });
538
+
539
+ // Verify both buttons exist
540
+ expect(document.getElementById('button-1-btn')).not.toBeNull();
541
+ expect(document.getElementById('button-2-btn')).not.toBeNull();
542
+
543
+ // Verify they have different configurations
544
+ const btn1 = document.getElementById('button-1-btn');
545
+ const btn2 = document.getElementById('button-2-btn');
546
+
547
+ const config1 = JSON.parse(btn1.getAttribute('data-onairos-config'));
548
+ const config2 = JSON.parse(btn2.getAttribute('data-onairos-config'));
549
+
550
+ expect(config1.webpageName).toBe('App 1');
551
+ expect(config2.webpageName).toBe('App 2');
552
+ });
553
+
554
+ test('should handle initialization errors gracefully', () => {
555
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
556
+
557
+ // Try to create button without initialization
558
+ createOnairosButton('non-existent', {});
559
+
560
+ expect(consoleSpy).toHaveBeenCalled();
561
+ });
562
+ });
563
+
564
+ describe('Laravel Environment Integration', () => {
565
+ test('should work with Laravel .env variables', () => {
566
+ // Mock import.meta.env for Laravel Vite
567
+ global.import = {
568
+ meta: {
569
+ env: {
570
+ DEV: true,
571
+ VITE_ONAIROS_API_KEY: 'test-key',
572
+ VITE_ONAIROS_TEST_MODE: 'true',
573
+ VITE_ONAIROS_BASE_URL: 'https://test.api.com'
574
+ }
575
+ }
576
+ };
577
+
578
+ initializeOnairosForBlade({
579
+ testMode: global.import.meta.env.VITE_ONAIROS_TEST_MODE === 'true',
580
+ apiKey: global.import.meta.env.VITE_ONAIROS_API_KEY,
581
+ baseUrl: global.import.meta.env.VITE_ONAIROS_BASE_URL
582
+ });
583
+
584
+ expect(window.OnairosConfig.testMode).toBe(true);
585
+ expect(window.OnairosConfig.apiKey).toBe('test-key');
586
+ expect(window.OnairosConfig.baseUrl).toBe('https://test.api.com');
587
+ });
588
+ });
589
+
590
+ describe('Laravel Performance Tests', () => {
591
+ test('should initialize quickly', () => {
592
+ const startTime = performance.now();
593
+
594
+ initializeOnairosForBlade();
595
+
596
+ const endTime = performance.now();
597
+ const duration = endTime - startTime;
598
+
599
+ // Should initialize in less than 50ms
600
+ expect(duration).toBeLessThan(50);
601
+ });
602
+
603
+ test('should create buttons efficiently', () => {
604
+ initializeOnairosForBlade();
605
+
606
+ const container = document.createElement('div');
607
+ container.id = 'perf-test';
608
+ document.body.appendChild(container);
609
+
610
+ const startTime = performance.now();
611
+
612
+ createOnairosButton('perf-test', {
613
+ requestData: ['email'],
614
+ webpageName: 'Performance Test'
615
+ });
616
+
617
+ const endTime = performance.now();
618
+ const duration = endTime - startTime;
619
+
620
+ // Should create button in less than 20ms
621
+ expect(duration).toBeLessThan(20);
622
+ });
623
+
624
+ test('should handle multiple buttons efficiently', () => {
625
+ initializeOnairosForBlade();
626
+
627
+ const startTime = performance.now();
628
+
629
+ // Create 10 buttons
630
+ for (let i = 0; i < 10; i++) {
631
+ const container = document.createElement('div');
632
+ container.id = `perf-button-${i}`;
633
+ document.body.appendChild(container);
634
+
635
+ createOnairosButton(`perf-button-${i}`, {
636
+ requestData: ['email'],
637
+ webpageName: `Perf Test ${i}`
638
+ });
639
+ }
640
+
641
+ const endTime = performance.now();
642
+ const duration = endTime - startTime;
643
+
644
+ // Should create 10 buttons in less than 100ms
645
+ expect(duration).toBeLessThan(100);
646
+ });
647
+ });