@moni-labs/moni-ui 0.4.14 → 0.4.17

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.
@@ -432,4 +432,397 @@ describe('moni-bottom-sheet', () => {
432
432
  expect(closeSpy).not.toHaveBeenCalled();
433
433
  expect(el.open).toBe(true);
434
434
  });
435
+
436
+ it('detecta arrastre fuera de la pantalla y marca como cerrado', async () => {
437
+ el.handle = true;
438
+ el.open = true;
439
+ await el.updateComplete;
440
+
441
+ const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
442
+ const handle = el.shadowRoot?.querySelector('.handle') as HTMLElement;
443
+
444
+ handle.setPointerCapture = vi.fn();
445
+ handle.releasePointerCapture = vi.fn();
446
+
447
+ vi.spyOn(dialog, 'getBoundingClientRect').mockReturnValue({
448
+ height: 300,
449
+ width: 375,
450
+ x: 0,
451
+ y: 300,
452
+ top: 300,
453
+ bottom: 600,
454
+ left: 0,
455
+ right: 375,
456
+ toJSON: () => {}
457
+ });
458
+
459
+ const closeSpy = vi.fn();
460
+ el.addEventListener('close', closeSpy);
461
+
462
+ handle.dispatchEvent(
463
+ new PointerEvent('pointerdown', {
464
+ bubbles: true,
465
+ clientX: 100,
466
+ clientY: 400,
467
+ pointerId: 1
468
+ })
469
+ );
470
+
471
+ // Arrastrar hacia el borde inferior/fuera de pantalla
472
+ dialog.dispatchEvent(
473
+ new PointerEvent('pointermove', {
474
+ bubbles: true,
475
+ clientX: 100,
476
+ clientY: window.innerHeight || 800,
477
+ pointerId: 1
478
+ })
479
+ );
480
+
481
+ // Soltar fuera de la pantalla
482
+ handle.dispatchEvent(
483
+ new PointerEvent('pointerup', {
484
+ bubbles: true,
485
+ clientX: 100,
486
+ clientY: (window.innerHeight || 800) + 50,
487
+ pointerId: 1
488
+ })
489
+ );
490
+
491
+ dialog.dispatchEvent(new Event('transitionend'));
492
+ await el.updateComplete;
493
+
494
+ expect(closeSpy).toHaveBeenCalled();
495
+ expect(el.open).toBe(false);
496
+ });
497
+
498
+ it('cierra y marca como open=false cuando ocurre pointercancel tras arrastrar hacia abajo', async () => {
499
+ el.handle = true;
500
+ el.open = true;
501
+ await el.updateComplete;
502
+
503
+ const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
504
+ const handle = el.shadowRoot?.querySelector('.handle') as HTMLElement;
505
+
506
+ handle.setPointerCapture = vi.fn();
507
+ handle.releasePointerCapture = vi.fn();
508
+
509
+ vi.spyOn(dialog, 'getBoundingClientRect').mockReturnValue({
510
+ height: 300,
511
+ width: 375,
512
+ x: 0,
513
+ y: 300,
514
+ top: 300,
515
+ bottom: 600,
516
+ left: 0,
517
+ right: 375,
518
+ toJSON: () => {}
519
+ });
520
+
521
+ const closeSpy = vi.fn();
522
+ el.addEventListener('close', closeSpy);
523
+
524
+ handle.dispatchEvent(
525
+ new PointerEvent('pointerdown', {
526
+ bubbles: true,
527
+ clientX: 100,
528
+ clientY: 400,
529
+ pointerId: 1
530
+ })
531
+ );
532
+
533
+ // Arrastre hacia abajo significativo
534
+ dialog.dispatchEvent(
535
+ new PointerEvent('pointermove', {
536
+ bubbles: true,
537
+ clientX: 100,
538
+ clientY: 550, // 150px
539
+ pointerId: 1
540
+ })
541
+ );
542
+
543
+ // El navegador cancela el puntero (por ejemplo, salida de viewport en móvil/pantalla táctil)
544
+ dialog.dispatchEvent(
545
+ new PointerEvent('pointercancel', {
546
+ bubbles: true,
547
+ clientX: 0,
548
+ clientY: 0,
549
+ pointerId: 1
550
+ })
551
+ );
552
+
553
+ dialog.dispatchEvent(new Event('transitionend'));
554
+ await el.updateComplete;
555
+
556
+ expect(closeSpy).toHaveBeenCalled();
557
+ expect(el.open).toBe(false);
558
+ });
559
+
560
+ it('cierra y marca como open=false cuando ocurre lostpointercapture tras arrastrar hacia abajo', async () => {
561
+ el.handle = true;
562
+ el.open = true;
563
+ await el.updateComplete;
564
+
565
+ const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
566
+ const handle = el.shadowRoot?.querySelector('.handle') as HTMLElement;
567
+
568
+ handle.setPointerCapture = vi.fn();
569
+ handle.releasePointerCapture = vi.fn();
570
+
571
+ vi.spyOn(dialog, 'getBoundingClientRect').mockReturnValue({
572
+ height: 300,
573
+ width: 375,
574
+ x: 0,
575
+ y: 300,
576
+ top: 300,
577
+ bottom: 600,
578
+ left: 0,
579
+ right: 375,
580
+ toJSON: () => {}
581
+ });
582
+
583
+ const closeSpy = vi.fn();
584
+ el.addEventListener('close', closeSpy);
585
+
586
+ handle.dispatchEvent(
587
+ new PointerEvent('pointerdown', {
588
+ bubbles: true,
589
+ clientX: 100,
590
+ clientY: 400,
591
+ pointerId: 1
592
+ })
593
+ );
594
+
595
+ dialog.dispatchEvent(
596
+ new PointerEvent('pointermove', {
597
+ bubbles: true,
598
+ clientX: 100,
599
+ clientY: 520, // 120px
600
+ pointerId: 1
601
+ })
602
+ );
603
+
604
+ dialog.dispatchEvent(
605
+ new PointerEvent('lostpointercapture', {
606
+ bubbles: true,
607
+ clientX: 0,
608
+ clientY: 0,
609
+ pointerId: 1
610
+ })
611
+ );
612
+
613
+ dialog.dispatchEvent(new Event('transitionend'));
614
+ await el.updateComplete;
615
+
616
+ expect(closeSpy).toHaveBeenCalled();
617
+ expect(el.open).toBe(false);
618
+ });
619
+
620
+ it('cierra y marca como open=false cuando se arrastra un bottom sheet expandido hacia abajo', async () => {
621
+ el.handle = true;
622
+ el.open = true;
623
+ await el.updateComplete;
624
+
625
+ const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
626
+ const handle = el.shadowRoot?.querySelector('.handle') as HTMLElement;
627
+ dialog.classList.add('expanded');
628
+
629
+ handle.setPointerCapture = vi.fn();
630
+ handle.releasePointerCapture = vi.fn();
631
+
632
+ vi.spyOn(dialog, 'getBoundingClientRect').mockReturnValue({
633
+ height: 600,
634
+ width: 375,
635
+ x: 0,
636
+ y: 100,
637
+ top: 100,
638
+ bottom: 700,
639
+ left: 0,
640
+ right: 375,
641
+ toJSON: () => {}
642
+ });
643
+
644
+ const closeSpy = vi.fn();
645
+ el.addEventListener('close', closeSpy);
646
+
647
+ handle.dispatchEvent(
648
+ new PointerEvent('pointerdown', {
649
+ bubbles: true,
650
+ clientX: 100,
651
+ clientY: 100,
652
+ pointerId: 1
653
+ })
654
+ );
655
+
656
+ dialog.dispatchEvent(
657
+ new PointerEvent('pointermove', {
658
+ bubbles: true,
659
+ clientX: 100,
660
+ clientY: 550, // Delta de 450px hacia abajo (arrastrado fuera / muy abajo)
661
+ pointerId: 1
662
+ })
663
+ );
664
+
665
+ handle.dispatchEvent(
666
+ new PointerEvent('pointerup', {
667
+ bubbles: true,
668
+ clientX: 100,
669
+ clientY: 550,
670
+ pointerId: 1
671
+ })
672
+ );
673
+
674
+ dialog.dispatchEvent(new Event('transitionend'));
675
+ await el.updateComplete;
676
+
677
+ expect(closeSpy).toHaveBeenCalled();
678
+ expect(el.open).toBe(false);
679
+ });
680
+
681
+ it('no cierra el bottom sheet si el usuario arrastra hacia abajo pero lo sube de nuevo antes de soltar', async () => {
682
+ el.handle = true;
683
+ el.open = true;
684
+ await el.updateComplete;
685
+
686
+ const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
687
+ const handle = el.shadowRoot?.querySelector('.handle') as HTMLElement;
688
+
689
+ handle.setPointerCapture = vi.fn();
690
+ handle.releasePointerCapture = vi.fn();
691
+
692
+ vi.spyOn(dialog, 'getBoundingClientRect').mockReturnValue({
693
+ height: 300,
694
+ width: 375,
695
+ x: 0,
696
+ y: 300,
697
+ top: 300,
698
+ bottom: 600,
699
+ left: 0,
700
+ right: 375,
701
+ toJSON: () => {}
702
+ });
703
+
704
+ const closeSpy = vi.fn();
705
+ el.addEventListener('close', closeSpy);
706
+
707
+ // Inicia arrastre en Y = 400
708
+ handle.dispatchEvent(
709
+ new PointerEvent('pointerdown', {
710
+ bubbles: true,
711
+ clientX: 100,
712
+ clientY: 400,
713
+ pointerId: 1
714
+ })
715
+ );
716
+
717
+ // Arrastra hacia abajo (Y = 600, delta = 200px)
718
+ dialog.dispatchEvent(
719
+ new PointerEvent('pointermove', {
720
+ bubbles: true,
721
+ clientX: 100,
722
+ clientY: 600,
723
+ pointerId: 1
724
+ })
725
+ );
726
+
727
+ // Cambia de opinión y lo sube de nuevo cerca del origen (Y = 410, delta = 10px)
728
+ dialog.dispatchEvent(
729
+ new PointerEvent('pointermove', {
730
+ bubbles: true,
731
+ clientX: 100,
732
+ clientY: 410,
733
+ pointerId: 1
734
+ })
735
+ );
736
+
737
+ // Suelta en Y = 410
738
+ handle.dispatchEvent(
739
+ new PointerEvent('pointerup', {
740
+ bubbles: true,
741
+ clientX: 100,
742
+ clientY: 410,
743
+ pointerId: 1
744
+ })
745
+ );
746
+
747
+ await el.updateComplete;
748
+
749
+ expect(closeSpy).not.toHaveBeenCalled();
750
+ expect(el.open).toBe(true);
751
+ });
752
+
753
+ it('permite abrir y cerrar mediante los métodos show() y close()', async () => {
754
+ expect(el.open).toBe(false);
755
+ el.show();
756
+ await el.updateComplete;
757
+ expect(el.open).toBe(true);
758
+
759
+ const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
760
+ const closePromise = el.close();
761
+ dialog.dispatchEvent(new Event('transitionend'));
762
+ await closePromise;
763
+ await el.updateComplete;
764
+ expect(el.open).toBe(false);
765
+ });
766
+
767
+ it('abre el diálogo nativo como modal con showModal cuando modal=true', async () => {
768
+ await el.updateComplete;
769
+ const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
770
+ const showModalSpy = vi.fn();
771
+ const showSpy = vi.fn();
772
+ (dialog as any).showModal = showModalSpy;
773
+ (dialog as any).show = showSpy;
774
+
775
+ expect(el.modal).toBe(true);
776
+ el.open = true;
777
+ await el.updateComplete;
778
+
779
+ expect(showModalSpy).toHaveBeenCalled();
780
+ expect(showSpy).not.toHaveBeenCalled();
781
+ });
782
+
783
+ it('abre el diálogo nativo con show() en vez de showModal cuando modal=false', async () => {
784
+ el.modal = false;
785
+ await el.updateComplete;
786
+ const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
787
+ const showModalSpy = vi.fn();
788
+ const showSpy = vi.fn();
789
+ (dialog as any).showModal = showModalSpy;
790
+ (dialog as any).show = showSpy;
791
+
792
+ el.open = true;
793
+ await el.updateComplete;
794
+
795
+ expect(showSpy).toHaveBeenCalled();
796
+ expect(showModalSpy).not.toHaveBeenCalled();
797
+ });
798
+
799
+ it('cierra el diálogo nativo con close() al poner open=false', async () => {
800
+ await el.updateComplete;
801
+ const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
802
+ (dialog as any).showModal = vi.fn(() => {
803
+ (dialog as any).open = true;
804
+ });
805
+ const closeSpy = vi.fn();
806
+ (dialog as any).close = closeSpy;
807
+
808
+ el.open = true;
809
+ await el.updateComplete;
810
+ el.open = false;
811
+ await el.updateComplete;
812
+
813
+ expect(closeSpy).toHaveBeenCalled();
814
+ });
815
+
816
+ it('aplica fallback al atributo open cuando el entorno no implementa showModal', async () => {
817
+ await el.updateComplete;
818
+ const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
819
+ (dialog as any).showModal = undefined;
820
+ (dialog as any).show = undefined;
821
+
822
+ el.open = true;
823
+ await el.updateComplete;
824
+
825
+ expect(dialog.hasAttribute('open')).toBe(true);
826
+ expect((dialog as any).open).toBe(true);
827
+ });
435
828
  });