|
1 | 1 | import { describe, test, expect } from 'vitest' |
2 | 2 | import { Parser } from './parse' |
3 | | -import { NODE_DECLARATION, NODE_STYLE_RULE, NODE_AT_RULE } from './arena' |
| 3 | +import { parse_selector } from './parse-selector' |
| 4 | +import { |
| 5 | + NODE_DECLARATION, |
| 6 | + NODE_STYLE_RULE, |
| 7 | + NODE_AT_RULE, |
| 8 | + NODE_SELECTOR_NTH, |
| 9 | + NODE_SELECTOR_NTH_OF, |
| 10 | + NODE_SELECTOR_LIST, |
| 11 | + NODE_SELECTOR_PSEUDO_CLASS, |
| 12 | +} from './arena' |
4 | 13 |
|
5 | 14 | describe('CSSNode', () => { |
6 | 15 | describe('iteration', () => { |
@@ -618,4 +627,147 @@ describe('CSSNode', () => { |
618 | 627 | expect(prelude.type_name).toBe('media-query') |
619 | 628 | }) |
620 | 629 | }) |
| 630 | + |
| 631 | + describe('Pseudo-class convenience properties', () => { |
| 632 | + describe('nth_of helpers (NODE_SELECTOR_NTH_OF)', () => { |
| 633 | + test('nth property returns An+B formula node', () => { |
| 634 | + const result = parse_selector(':nth-child(2n+1 of .foo)') |
| 635 | + const selector = result.first_child |
| 636 | + const pseudo = selector?.first_child // Get pseudo-class |
| 637 | + const nthOf = pseudo?.first_child // NODE_SELECTOR_NTH_OF |
| 638 | + |
| 639 | + expect(nthOf?.nth).not.toBeNull() |
| 640 | + expect(nthOf?.nth?.type).toBe(NODE_SELECTOR_NTH) |
| 641 | + expect(nthOf?.nth?.nth_a).toBe('2n') |
| 642 | + expect(nthOf?.nth?.nth_b).toBe('+1') |
| 643 | + }) |
| 644 | + |
| 645 | + test('selector property returns selector list', () => { |
| 646 | + const result = parse_selector(':nth-child(2n of .foo, #bar)') |
| 647 | + const selector = result.first_child |
| 648 | + const pseudo = selector?.first_child |
| 649 | + const nthOf = pseudo?.first_child |
| 650 | + |
| 651 | + expect(nthOf?.selector).not.toBeNull() |
| 652 | + expect(nthOf?.selector?.type).toBe(NODE_SELECTOR_LIST) |
| 653 | + expect(nthOf?.selector?.text).toBe('.foo, #bar') |
| 654 | + }) |
| 655 | + |
| 656 | + test('returns null for wrong node types', () => { |
| 657 | + const result = parse_selector('.foo') |
| 658 | + const selector = result.first_child |
| 659 | + const classNode = selector?.first_child |
| 660 | + |
| 661 | + expect(classNode?.nth).toBeNull() |
| 662 | + expect(classNode?.selector).toBeNull() |
| 663 | + }) |
| 664 | + |
| 665 | + test('works with :nth-last-child', () => { |
| 666 | + const result = parse_selector(':nth-last-child(odd of .item)') |
| 667 | + const selector = result.first_child |
| 668 | + const pseudo = selector?.first_child |
| 669 | + const nthOf = pseudo?.first_child |
| 670 | + |
| 671 | + expect(nthOf?.nth).not.toBeNull() |
| 672 | + expect(nthOf?.nth?.nth_a).toBe('odd') |
| 673 | + expect(nthOf?.selector).not.toBeNull() |
| 674 | + expect(nthOf?.selector?.text).toBe('.item') |
| 675 | + }) |
| 676 | + |
| 677 | + test('works with :nth-of-type', () => { |
| 678 | + const result = parse_selector(':nth-of-type(3n of .special)') |
| 679 | + const selector = result.first_child |
| 680 | + const pseudo = selector?.first_child |
| 681 | + const nthOf = pseudo?.first_child |
| 682 | + |
| 683 | + expect(nthOf?.nth).not.toBeNull() |
| 684 | + expect(nthOf?.nth?.nth_a).toBe('3n') |
| 685 | + expect(nthOf?.selector?.text).toBe('.special') |
| 686 | + }) |
| 687 | + |
| 688 | + test('works with :nth-last-of-type', () => { |
| 689 | + const result = parse_selector(':nth-last-of-type(even of div)') |
| 690 | + const selector = result.first_child |
| 691 | + const pseudo = selector?.first_child |
| 692 | + const nthOf = pseudo?.first_child |
| 693 | + |
| 694 | + expect(nthOf?.nth?.nth_a).toBe('even') |
| 695 | + expect(nthOf?.selector?.text).toBe('div') |
| 696 | + }) |
| 697 | + }) |
| 698 | + |
| 699 | + describe('selector_list helper (NODE_SELECTOR_PSEUDO_CLASS)', () => { |
| 700 | + test('returns selector list for :is()', () => { |
| 701 | + const result = parse_selector(':is(.foo, #bar)') |
| 702 | + const selector = result.first_child |
| 703 | + const pseudo = selector?.first_child |
| 704 | + |
| 705 | + expect(pseudo?.type).toBe(NODE_SELECTOR_PSEUDO_CLASS) |
| 706 | + expect(pseudo?.selector_list).not.toBeNull() |
| 707 | + expect(pseudo?.selector_list?.type).toBe(NODE_SELECTOR_LIST) |
| 708 | + expect(pseudo?.selector_list?.text).toBe('.foo, #bar') |
| 709 | + }) |
| 710 | + |
| 711 | + test('returns selector list for :nth-child(of)', () => { |
| 712 | + const result = parse_selector(':nth-child(2n of .foo)') |
| 713 | + const selector = result.first_child |
| 714 | + const pseudo = selector?.first_child |
| 715 | + |
| 716 | + expect(pseudo?.selector_list).not.toBeNull() |
| 717 | + expect(pseudo?.selector_list?.text).toBe('.foo') |
| 718 | + }) |
| 719 | + |
| 720 | + test('returns null for pseudo-classes without selectors', () => { |
| 721 | + const result = parse_selector(':hover') |
| 722 | + const selector = result.first_child |
| 723 | + const pseudo = selector?.first_child |
| 724 | + |
| 725 | + expect(pseudo?.selector_list).toBeNull() |
| 726 | + }) |
| 727 | + |
| 728 | + test('returns null for :nth-child without "of"', () => { |
| 729 | + const result = parse_selector(':nth-child(2n)') |
| 730 | + const selector = result.first_child |
| 731 | + const pseudo = selector?.first_child |
| 732 | + |
| 733 | + expect(pseudo?.selector_list).toBeNull() |
| 734 | + }) |
| 735 | + |
| 736 | + test('works with :not()', () => { |
| 737 | + const result = parse_selector(':not(.excluded)') |
| 738 | + const selector = result.first_child |
| 739 | + const pseudo = selector?.first_child |
| 740 | + |
| 741 | + expect(pseudo?.selector_list).not.toBeNull() |
| 742 | + expect(pseudo?.selector_list?.text).toBe('.excluded') |
| 743 | + }) |
| 744 | + |
| 745 | + test('works with :has()', () => { |
| 746 | + const result = parse_selector(':has(> .child)') |
| 747 | + const selector = result.first_child |
| 748 | + const pseudo = selector?.first_child |
| 749 | + |
| 750 | + expect(pseudo?.selector_list).not.toBeNull() |
| 751 | + expect(pseudo?.selector_list?.text).toBe('> .child') |
| 752 | + }) |
| 753 | + |
| 754 | + test('works with :where()', () => { |
| 755 | + const result = parse_selector(':where(article, section)') |
| 756 | + const selector = result.first_child |
| 757 | + const pseudo = selector?.first_child |
| 758 | + |
| 759 | + expect(pseudo?.selector_list).not.toBeNull() |
| 760 | + expect(pseudo?.selector_list?.text).toBe('article, section') |
| 761 | + }) |
| 762 | + |
| 763 | + test('complex :nth-child with multiple selectors', () => { |
| 764 | + const result = parse_selector(':nth-child(3n+2 of .item, .element, #special)') |
| 765 | + const selector = result.first_child |
| 766 | + const pseudo = selector?.first_child |
| 767 | + |
| 768 | + expect(pseudo?.selector_list).not.toBeNull() |
| 769 | + expect(pseudo?.selector_list?.text).toBe('.item, .element, #special') |
| 770 | + }) |
| 771 | + }) |
| 772 | + }) |
621 | 773 | }) |
0 commit comments