slim-select 3.2.0 → 3.4.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.
@@ -1658,4 +1658,220 @@ describe('render module', () => {
1658
1658
  expect(render.content.main.classList.contains(render.classes.dirBelow)).toBe(true)
1659
1659
  })
1660
1660
  })
1661
+
1662
+ describe('setContentPosition (via moveContentBelow)', () => {
1663
+ // Helper: mock getBoundingClientRect on an element to return specific values
1664
+ function mockRect(el: HTMLElement, rect: Partial<DOMRect>) {
1665
+ el.getBoundingClientRect = vi.fn(
1666
+ () =>
1667
+ ({
1668
+ top: 0,
1669
+ left: 0,
1670
+ right: 0,
1671
+ bottom: 0,
1672
+ width: 0,
1673
+ height: 0,
1674
+ x: 0,
1675
+ y: 0,
1676
+ toJSON: () => {},
1677
+ ...rect
1678
+ }) as DOMRect
1679
+ )
1680
+ }
1681
+
1682
+ test('absolute (default): content aligns below trigger using document coords', () => {
1683
+ // Simulate trigger at viewport (100, 50) with height 40 and width 200
1684
+ mockRect(render.main.main, { top: 100, left: 50, height: 40, width: 200 })
1685
+
1686
+ // No scroll
1687
+ Object.defineProperty(window, 'scrollY', { value: 0, writable: true })
1688
+ Object.defineProperty(window, 'scrollX', { value: 0, writable: true })
1689
+
1690
+ render.settings.contentPosition = 'absolute'
1691
+ render.moveContentBelow()
1692
+
1693
+ // top = viewportTop(100) + scrollY(0) + height(40) = 140
1694
+ // left = viewportLeft(50) + scrollX(0) = 50
1695
+ expect(render.content.main.style.top).toBe('140px')
1696
+ expect(render.content.main.style.left).toBe('50px')
1697
+ expect(render.content.main.style.width).toBe('200px')
1698
+ })
1699
+
1700
+ test('absolute: content accounts for page scroll', () => {
1701
+ // Trigger at viewport (100, 50) but page is scrolled down 300px and right 20px
1702
+ mockRect(render.main.main, { top: 100, left: 50, height: 40, width: 200 })
1703
+
1704
+ Object.defineProperty(window, 'scrollY', { value: 300, writable: true })
1705
+ Object.defineProperty(window, 'scrollX', { value: 20, writable: true })
1706
+
1707
+ render.settings.contentPosition = 'absolute'
1708
+ render.moveContentBelow()
1709
+
1710
+ // top = viewportTop(100) + scrollY(300) + height(40) = 440
1711
+ // left = viewportLeft(50) + scrollX(20) = 70
1712
+ expect(render.content.main.style.top).toBe('440px')
1713
+ expect(render.content.main.style.left).toBe('70px')
1714
+ expect(render.content.main.style.width).toBe('200px')
1715
+ })
1716
+
1717
+ test('absolute: content aligns correctly when trigger is near top of page', () => {
1718
+ // Trigger is at very top, no scroll
1719
+ mockRect(render.main.main, { top: 0, left: 10, height: 30, width: 150 })
1720
+
1721
+ Object.defineProperty(window, 'scrollY', { value: 0, writable: true })
1722
+ Object.defineProperty(window, 'scrollX', { value: 0, writable: true })
1723
+
1724
+ render.settings.contentPosition = 'absolute'
1725
+ render.moveContentBelow()
1726
+
1727
+ // top = 0 + 0 + 30 = 30
1728
+ // left = 10 + 0 = 10
1729
+ expect(render.content.main.style.top).toBe('30px')
1730
+ expect(render.content.main.style.left).toBe('10px')
1731
+ expect(render.content.main.style.width).toBe('150px')
1732
+ })
1733
+
1734
+ test('fixed: content uses viewport coords without scroll offset', () => {
1735
+ // Same trigger position and scroll as the absolute+scroll test
1736
+ mockRect(render.main.main, { top: 100, left: 50, height: 40, width: 200 })
1737
+
1738
+ Object.defineProperty(window, 'scrollY', { value: 300, writable: true })
1739
+ Object.defineProperty(window, 'scrollX', { value: 20, writable: true })
1740
+
1741
+ render.settings.contentPosition = 'fixed'
1742
+ render.moveContentBelow()
1743
+
1744
+ // Fixed ignores scroll: top = viewportTop(100) + height(40) = 140
1745
+ // left = viewportLeft(50) = 50
1746
+ expect(render.content.main.style.top).toBe('140px')
1747
+ expect(render.content.main.style.left).toBe('50px')
1748
+ expect(render.content.main.style.width).toBe('200px')
1749
+ })
1750
+
1751
+ test('relative: content position styles are not set', () => {
1752
+ mockRect(render.main.main, { top: 100, left: 50, height: 40, width: 200 })
1753
+
1754
+ render.settings.contentPosition = 'relative'
1755
+
1756
+ // Reset styles to confirm they aren't changed
1757
+ render.content.main.style.top = ''
1758
+ render.content.main.style.left = ''
1759
+ render.content.main.style.width = ''
1760
+
1761
+ render.moveContentBelow()
1762
+
1763
+ // setContentPosition returns early for relative, so no inline positioning
1764
+ expect(render.content.main.style.top).toBe('')
1765
+ expect(render.content.main.style.left).toBe('')
1766
+ expect(render.content.main.style.width).toBe('')
1767
+ })
1768
+
1769
+ test('absolute vs fixed diverge only by scroll offset', () => {
1770
+ const triggerRect = { top: 200, left: 80, height: 36, width: 250 }
1771
+
1772
+ Object.defineProperty(window, 'scrollY', { value: 500, writable: true })
1773
+ Object.defineProperty(window, 'scrollX', { value: 0, writable: true })
1774
+
1775
+ // Absolute
1776
+ mockRect(render.main.main, triggerRect)
1777
+ render.settings.contentPosition = 'absolute'
1778
+ render.moveContentBelow()
1779
+ const absTop = render.content.main.style.top
1780
+ const absLeft = render.content.main.style.left
1781
+
1782
+ // Fixed
1783
+ mockRect(render.main.main, triggerRect)
1784
+ render.settings.contentPosition = 'fixed'
1785
+ render.moveContentBelow()
1786
+ const fixedTop = render.content.main.style.top
1787
+ const fixedLeft = render.content.main.style.left
1788
+
1789
+ // Absolute top should be exactly scrollY more than fixed top
1790
+ expect(parseFloat(absTop)).toBe(parseFloat(fixedTop) + 500)
1791
+ // Left should differ by scrollX (which is 0 here, so equal)
1792
+ expect(parseFloat(absLeft)).toBe(parseFloat(fixedLeft))
1793
+ })
1794
+
1795
+ describe('contentWidth setting', () => {
1796
+ beforeEach(() => {
1797
+ mockRect(render.main.main, { top: 100, left: 50, height: 40, width: 200 })
1798
+ Object.defineProperty(window, 'scrollY', { value: 0, writable: true })
1799
+ Object.defineProperty(window, 'scrollX', { value: 0, writable: true })
1800
+ render.settings.contentPosition = 'absolute'
1801
+ })
1802
+
1803
+ test('default (empty): width matches trigger', () => {
1804
+ render.settings.contentWidth = ''
1805
+ render.moveContentBelow()
1806
+
1807
+ expect(render.content.main.style.width).toBe('200px')
1808
+ expect(render.content.main.style.minWidth).toBe('')
1809
+ expect(render.content.main.style.maxWidth).toBe('')
1810
+ })
1811
+
1812
+ test('exact value: sets width to given value', () => {
1813
+ render.settings.contentWidth = '500px'
1814
+ render.moveContentBelow()
1815
+
1816
+ expect(render.content.main.style.width).toBe('500px')
1817
+ expect(render.content.main.style.minWidth).toBe('')
1818
+ expect(render.content.main.style.maxWidth).toBe('')
1819
+ })
1820
+
1821
+ test('> prefix: sets min-width, no fixed width', () => {
1822
+ render.settings.contentWidth = '>300px'
1823
+ render.moveContentBelow()
1824
+
1825
+ expect(render.content.main.style.minWidth).toBe('300px')
1826
+ expect(render.content.main.style.width).toBe('')
1827
+ expect(render.content.main.style.maxWidth).toBe('')
1828
+ })
1829
+
1830
+ test('< prefix: sets max-width, no fixed width', () => {
1831
+ render.settings.contentWidth = '<600px'
1832
+ render.moveContentBelow()
1833
+
1834
+ expect(render.content.main.style.maxWidth).toBe('600px')
1835
+ expect(render.content.main.style.width).toBe('')
1836
+ expect(render.content.main.style.minWidth).toBe('')
1837
+ })
1838
+
1839
+ test('exact value with percentage', () => {
1840
+ render.settings.contentWidth = '100%'
1841
+ render.moveContentBelow()
1842
+
1843
+ expect(render.content.main.style.width).toBe('100%')
1844
+ expect(render.content.main.style.minWidth).toBe('')
1845
+ expect(render.content.main.style.maxWidth).toBe('')
1846
+ })
1847
+
1848
+ test('clears previous width styles when switching modes', () => {
1849
+ // First set min-width
1850
+ render.settings.contentWidth = '>300px'
1851
+ render.moveContentBelow()
1852
+ expect(render.content.main.style.minWidth).toBe('300px')
1853
+
1854
+ // Now switch to exact width — min-width should be cleared
1855
+ render.settings.contentWidth = '500px'
1856
+ render.moveContentBelow()
1857
+ expect(render.content.main.style.width).toBe('500px')
1858
+ expect(render.content.main.style.minWidth).toBe('')
1859
+ expect(render.content.main.style.maxWidth).toBe('')
1860
+ })
1861
+
1862
+ test('clears previous width styles when switching to default', () => {
1863
+ // First set max-width
1864
+ render.settings.contentWidth = '<600px'
1865
+ render.moveContentBelow()
1866
+ expect(render.content.main.style.maxWidth).toBe('600px')
1867
+
1868
+ // Now switch to default — max-width should be cleared, width matches trigger
1869
+ render.settings.contentWidth = ''
1870
+ render.moveContentBelow()
1871
+ expect(render.content.main.style.width).toBe('200px')
1872
+ expect(render.content.main.style.minWidth).toBe('')
1873
+ expect(render.content.main.style.maxWidth).toBe('')
1874
+ })
1875
+ })
1876
+ })
1661
1877
  })