|
1 | 1 | import { expect, test } from '@playwright/test'
|
2 |
| -import { foods } from '../src/site/options' |
| 2 | +import { |
| 3 | + colors as demo_colors, |
| 4 | + languages as demo_languages, |
| 5 | + foods, |
| 6 | +} from '../src/site/options' |
3 | 7 |
|
4 | 8 | // to run tests in this file, use `npm run test:e2e`
|
5 | 9 |
|
@@ -618,65 +622,167 @@ test(`dragging selected options across each other changes their order`, async ({
|
618 | 622 | })
|
619 | 623 |
|
620 | 624 | test.describe(`portal feature`, () => {
|
621 |
| - test(`dropdown renders in document.body when portal is active in modal`, async ({ |
| 625 | + test(`foods dropdown in modal 1 renders in body when portal is active`, async ({ |
622 | 626 | page,
|
623 | 627 | }) => {
|
624 | 628 | await page.goto(`/modal`, { waitUntil: `networkidle` })
|
625 | 629 |
|
626 |
| - // Open the first modal |
627 | 630 | await page
|
628 | 631 | .getByRole(`button`, { name: `Open Modal 1 (Vertical Selects)` })
|
629 | 632 | .click()
|
630 | 633 |
|
631 |
| - // Locate the MultiSelect input for foods within the first modal |
632 |
| - const foods_multiselect_input = page.locator( |
633 |
| - `div.modal-content.modal-1 div.multiselect input[placeholder='Choose foods...']`, |
| 634 | + const modal_1_content = page.locator(`div.modal-content.modal-1`) |
| 635 | + const foods_input = modal_1_content.locator( |
| 636 | + `div.multiselect input[placeholder='Choose foods...']`, |
634 | 637 | )
|
635 |
| - // Wait for the modal to be fully visible and input to be available |
636 |
| - await foods_multiselect_input.waitFor({ state: `visible` }) |
| 638 | + await foods_input.click() // Open dropdown |
637 | 639 |
|
638 |
| - // Click the input to open the dropdown |
639 |
| - await foods_multiselect_input.click() |
| 640 | + // Options list should be portalled to body and visible |
| 641 | + const portalled_foods_options = page.locator( |
| 642 | + `body > ul.options[aria-expanded="true"]:has(li:has-text("${foods[0]}"))`, |
| 643 | + ) |
| 644 | + await expect(portalled_foods_options).toBeVisible() |
640 | 645 |
|
641 |
| - // Identify the specific portalled options list for foods |
642 |
| - // It should be in the body and contain the first food item |
643 |
| - const foods_options_list_in_body = page.locator( |
644 |
| - `body > ul.options:has(li:has-text("${foods[0]}"))`, |
| 646 | + // Options list should not be a direct child of the multiselect wrapper in the modal |
| 647 | + const foods_multiselect_wrapper = modal_1_content.locator( |
| 648 | + `div.multiselect:has(input[placeholder='Choose foods...'])`, |
645 | 649 | )
|
646 |
| - await foods_options_list_in_body.waitFor({ state: `visible` }) |
647 |
| - await expect(foods_options_list_in_body).toHaveAttribute( |
| 650 | + await expect( |
| 651 | + foods_multiselect_wrapper.locator(`> ul.options`), |
| 652 | + ).not.toBeAttached() |
| 653 | + |
| 654 | + // Select an option |
| 655 | + await portalled_foods_options.locator(`li:has-text("${foods[0]}")`).click() |
| 656 | + await expect(portalled_foods_options).toBeHidden() // Dropdown should close |
| 657 | + |
| 658 | + await expect( |
| 659 | + modal_1_content.getByRole(`button`, { name: `Remove ${foods[0]}` }), |
| 660 | + ).toBeVisible() |
| 661 | + |
| 662 | + await page.keyboard.press(`Escape`) // Close any remaining popups/dropdowns |
| 663 | + await page.getByRole(`button`, { name: `Close Modal 1` }).click() |
| 664 | + await expect(modal_1_content).toBeHidden() |
| 665 | + }) |
| 666 | + |
| 667 | + test(`dropdown renders within component when portal is inactive (/ui page)`, async ({ |
| 668 | + page, |
| 669 | + }) => { |
| 670 | + await page.goto(`/ui`, { waitUntil: `networkidle` }) |
| 671 | + |
| 672 | + const foods_multiselect = page.locator(`#foods`) |
| 673 | + await foods_multiselect.locator(`input[autocomplete]`).click() // Open dropdown |
| 674 | + |
| 675 | + // Options list should be a child of the multiselect wrapper and visible |
| 676 | + const foods_options_in_component = foods_multiselect.locator(`ul.options`) |
| 677 | + await expect(foods_options_in_component).toBeVisible() |
| 678 | + await expect(foods_options_in_component).toHaveAttribute( |
648 | 679 | `aria-expanded`,
|
649 | 680 | `true`,
|
650 |
| - ) // Confirm it's open |
| 681 | + ) |
| 682 | + |
| 683 | + // Options list should NOT be portalled to the body |
| 684 | + const portalled_foods_options = page.locator( |
| 685 | + // More specific selector to avoid accidental matches if body > ul.options exists for other reasons |
| 686 | + `body > ul.options[aria-expanded="true"]:has(li:has-text("${foods[0]}"))`, |
| 687 | + ) |
| 688 | + await expect(portalled_foods_options).not.toBeAttached() |
| 689 | + }) |
| 690 | + |
| 691 | + test(`colors dropdown in modal 1 renders in body when portal is active`, async ({ |
| 692 | + page, |
| 693 | + }) => { |
| 694 | + await page.goto(`/modal`, { waitUntil: `networkidle` }) |
| 695 | + |
| 696 | + await page |
| 697 | + .getByRole(`button`, { name: `Open Modal 1 (Vertical Selects)` }) |
| 698 | + .click() |
| 699 | + |
| 700 | + const modal_1_content = page.locator(`div.modal-content.modal-1`) |
| 701 | + const colors_input = modal_1_content.locator( |
| 702 | + `div.multiselect input[placeholder='Choose colors...']`, |
| 703 | + ) |
| 704 | + await colors_input.click() // Open dropdown |
651 | 705 |
|
652 |
| - // Assert that the options list is NOT a direct child of the multiselect wrapper within the modal |
653 |
| - const multiselect_wrapper_in_modal = page.locator( |
654 |
| - `div.modal-content.modal-1 div.multiselect:has(input[placeholder='Choose foods...'])`, |
| 706 | + // Options list should be portalled to body and visible |
| 707 | + const portalled_colors_options = page.locator( |
| 708 | + `body > ul.options[aria-expanded="true"]:has(li:has-text("${demo_colors[0]}"))`, |
| 709 | + ) |
| 710 | + await expect(portalled_colors_options).toBeVisible() |
| 711 | + |
| 712 | + // Options list should not be a direct child of the multiselect wrapper in the modal |
| 713 | + const colors_multiselect_wrapper = modal_1_content.locator( |
| 714 | + `div.multiselect:has(input[placeholder='Choose colors...'])`, |
655 | 715 | )
|
656 | 716 | await expect(
|
657 |
| - multiselect_wrapper_in_modal.locator(`> ul.options`), |
| 717 | + colors_multiselect_wrapper.locator(`> ul.options`), |
658 | 718 | ).not.toBeAttached()
|
659 | 719 |
|
660 |
| - // Select an option from this specific portalled dropdown |
661 |
| - await foods_options_list_in_body |
662 |
| - .locator(`li:has-text("${foods[0]}")`) |
| 720 | + // Select an option |
| 721 | + await portalled_colors_options |
| 722 | + .locator(`li:has-text("${demo_colors[0]}")`) |
663 | 723 | .click()
|
| 724 | + await expect(portalled_colors_options).toBeHidden() // Dropdown should close |
| 725 | + |
| 726 | + await expect( |
| 727 | + modal_1_content.getByRole(`button`, { name: `Remove ${demo_colors[0]}` }), |
| 728 | + ).toBeVisible() |
664 | 729 |
|
665 |
| - // Assert this specific dropdown is now hidden |
666 |
| - await expect(foods_options_list_in_body).toBeHidden({ timeout: 3000 }) |
| 730 | + await page.keyboard.press(`Escape`) // Close any remaining popups/dropdowns |
| 731 | + await page.getByRole(`button`, { name: `Close Modal 1` }).click() |
| 732 | + await expect(modal_1_content).toBeHidden() |
| 733 | + }) |
667 | 734 |
|
668 |
| - // Explicitly wait for the selected item to appear before asserting visibility |
| 735 | + test(`languages dropdown in modal 2 renders in body when portal is active`, async ({ |
| 736 | + page, |
| 737 | + }) => { |
| 738 | + await page.goto(`/modal`, { waitUntil: `networkidle` }) |
| 739 | + |
| 740 | + await page |
| 741 | + .getByRole(`button`, { name: `Open Modal 2 (Horizontal Selects)` }) |
| 742 | + .click() |
| 743 | + |
| 744 | + const modal_2_content = page.locator(`div.modal-content.modal-2`) |
| 745 | + const languages_input = modal_2_content.locator( |
| 746 | + `div.multiselect input[placeholder='Choose languages...']`, |
| 747 | + ) |
| 748 | + await languages_input.click() // Open dropdown |
| 749 | + |
| 750 | + // Options list should be portalled to body and visible |
| 751 | + const portalled_languages_options = page.locator( |
| 752 | + `body > ul.options[aria-expanded="true"]:has(li:has-text("${demo_languages[0]}"))`, |
| 753 | + ) |
| 754 | + await expect(portalled_languages_options).toBeVisible() |
| 755 | + |
| 756 | + // Options list should not be a direct child of the multiselect wrapper in the modal |
| 757 | + const languages_multiselect_wrapper = modal_2_content.locator( |
| 758 | + `div.multiselect:has(input[placeholder='Choose languages...'])`, |
| 759 | + ) |
| 760 | + await expect( |
| 761 | + languages_multiselect_wrapper.locator(`> ul.options`), |
| 762 | + ).not.toBeAttached() |
| 763 | + |
| 764 | + // Select an option, ensuring exact match |
| 765 | + await portalled_languages_options |
| 766 | + .getByRole(`option`, { name: demo_languages[0], exact: true }) |
| 767 | + .click() |
| 768 | + // Dropdown should remain visible on desktop by default |
| 769 | + await expect(portalled_languages_options).toBeVisible() |
| 770 | + // And the selected option should no longer be in the options list (if duplicates=false) |
669 | 771 | await expect(
|
670 |
| - page.getByRole(`button`, { |
671 |
| - name: `Remove 🍇 Grapes`, |
| 772 | + portalled_languages_options.getByRole(`option`, { |
| 773 | + name: demo_languages[0], |
| 774 | + exact: true, |
672 | 775 | }),
|
673 |
| - ).toBeVisible() |
| 776 | + ).not.toBeAttached() |
674 | 777 |
|
675 |
| - // click escape to close the dropdown |
676 |
| - await page.keyboard.press(`Escape`) |
| 778 | + await expect( |
| 779 | + modal_2_content.getByRole(`button`, { |
| 780 | + name: `Remove ${demo_languages[0]}`, |
| 781 | + }), |
| 782 | + ).toBeVisible() |
677 | 783 |
|
678 |
| - // Close the modal |
679 |
| - await page.getByRole(`button`, { name: `Close Modal 1` }).click() |
680 |
| - await expect(page.locator(`div.modal-content.modal-1`)).toBeHidden() |
| 784 | + await page.keyboard.press(`Escape`) // Close any remaining popups/dropdowns |
| 785 | + await page.getByRole(`button`, { name: `Close Modal 2` }).click() |
| 786 | + await expect(modal_2_content).toBeHidden() |
681 | 787 | })
|
682 | 788 | })
|
0 commit comments