@automattic/charts 0.59.0 → 1.0.1

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.
@@ -716,6 +716,21 @@ describe( 'normalizeColorToHex', () => {
716
716
  } );
717
717
  } );
718
718
 
719
+ describe( 'HSLA strings', () => {
720
+ it( 'converts hsla(0, 100%, 50%, 1) to #ff0000', () => {
721
+ expect( normalizeColorToHex( 'hsla(0, 100%, 50%, 1)' ) ).toBe( '#ff0000' );
722
+ } );
723
+
724
+ it( 'converts hsla(120, 100%, 50%, 0.5) to #00ff00', () => {
725
+ expect( normalizeColorToHex( 'hsla(120, 100%, 50%, 0.5)' ) ).toBe( '#00ff00' );
726
+ } );
727
+
728
+ it( 'converts fully transparent hsla to #000000', () => {
729
+ // d3-color converts fully transparent colors (alpha=0) to black
730
+ expect( normalizeColorToHex( 'hsla(240, 100%, 50%, 0)' ) ).toBe( '#000000' );
731
+ } );
732
+ } );
733
+
719
734
  describe( 'RGB strings', () => {
720
735
  it( 'converts rgb(255, 0, 0) to #ff0000', () => {
721
736
  expect( normalizeColorToHex( 'rgb(255, 0, 0)' ) ).toBe( '#ff0000' );
@@ -726,6 +741,36 @@ describe( 'normalizeColorToHex', () => {
726
741
  } );
727
742
  } );
728
743
 
744
+ describe( 'RGBA strings', () => {
745
+ it( 'converts rgba(255, 0, 0, 1) to #ff0000', () => {
746
+ expect( normalizeColorToHex( 'rgba(255, 0, 0, 1)' ) ).toBe( '#ff0000' );
747
+ } );
748
+
749
+ it( 'converts rgba(0, 0, 255, 0.5) to #0000ff', () => {
750
+ // Alpha channel is stripped in hex conversion
751
+ expect( normalizeColorToHex( 'rgba(0, 0, 255, 0.5)' ) ).toBe( '#0000ff' );
752
+ } );
753
+
754
+ it( 'converts fully transparent rgba to #000000', () => {
755
+ // d3-color converts fully transparent colors (alpha=0) to black
756
+ expect( normalizeColorToHex( 'rgba(128, 128, 128, 0)' ) ).toBe( '#000000' );
757
+ } );
758
+ } );
759
+
760
+ describe( 'Named CSS colors', () => {
761
+ it( 'converts steelblue to hex', () => {
762
+ expect( normalizeColorToHex( 'steelblue' ) ).toBe( '#4682b4' );
763
+ } );
764
+
765
+ it( 'converts red to hex', () => {
766
+ expect( normalizeColorToHex( 'red' ) ).toBe( '#ff0000' );
767
+ } );
768
+
769
+ it( 'returns unknown strings as-is', () => {
770
+ expect( normalizeColorToHex( 'notacolor' ) ).toBe( 'notacolor' );
771
+ } );
772
+ } );
773
+
729
774
  describe( 'CSS variables', () => {
730
775
  it( 'returns original if no resolveCss function provided', () => {
731
776
  expect( normalizeColorToHex( '--my-color' ) ).toBe( '--my-color' );
@@ -755,6 +800,71 @@ describe( 'normalizeColorToHex', () => {
755
800
  const mockResolve = jest.fn().mockReturnValue( null );
756
801
  expect( normalizeColorToHex( '--my-color', null, mockResolve ) ).toBe( '--my-color' );
757
802
  } );
803
+
804
+ it( 'returns original when CSS variable resolves to itself', () => {
805
+ const mockResolve = jest.fn().mockImplementation( ( v: string ) => v );
806
+ expect( normalizeColorToHex( '--loop', null, mockResolve ) ).toBe( '--loop' );
807
+ } );
808
+
809
+ it( 'returns original when CSS variable resolves to empty string', () => {
810
+ const mockResolve = jest.fn().mockReturnValue( '' );
811
+ expect( normalizeColorToHex( '--empty', null, mockResolve ) ).toBe( '--empty' );
812
+ } );
813
+
814
+ it( 'does not infinite loop on indirect CSS variable cycle', () => {
815
+ const mockResolve = jest.fn().mockImplementation( ( v: string ) => {
816
+ if ( v === '--a' ) return 'var(--b)';
817
+ if ( v === 'var(--b)' ) return '--a';
818
+
819
+ return null;
820
+ } );
821
+
822
+ expect( () => normalizeColorToHex( '--a', null, mockResolve ) ).not.toThrow();
823
+ } );
824
+
825
+ it( 'resolves multi-hop CSS variable chain', () => {
826
+ const mockResolve = jest.fn().mockImplementation( ( v: string ) => {
827
+ if ( v === '--a' ) return 'var(--b)';
828
+ if ( v === 'var(--b)' ) return 'hsl(0, 100%, 50%)';
829
+
830
+ return null;
831
+ } );
832
+
833
+ expect( normalizeColorToHex( '--a', null, mockResolve ) ).toBe( '#ff0000' );
834
+ } );
835
+ } );
836
+
837
+ describe( 'Whitespace handling', () => {
838
+ it( 'trims leading and trailing spaces from hex', () => {
839
+ expect( normalizeColorToHex( ' #ff0000 ' ) ).toBe( '#ff0000' );
840
+ } );
841
+
842
+ it( 'trims spaces from HSL string', () => {
843
+ expect( normalizeColorToHex( ' hsl(0, 100%, 50%) ' ) ).toBe( '#ff0000' );
844
+ } );
845
+
846
+ it( 'trims spaces from named color', () => {
847
+ expect( normalizeColorToHex( ' red ' ) ).toBe( '#ff0000' );
848
+ } );
849
+ } );
850
+
851
+ describe( 'Case insensitivity', () => {
852
+ it( 'converts uppercase HSL', () => {
853
+ expect( normalizeColorToHex( 'HSL(0, 100%, 50%)' ) ).toBe( '#ff0000' );
854
+ } );
855
+
856
+ it( 'converts uppercase RGB', () => {
857
+ expect( normalizeColorToHex( 'RGB(255, 0, 0)' ) ).toBe( '#ff0000' );
858
+ } );
859
+
860
+ it( 'converts uppercase RGBA', () => {
861
+ expect( normalizeColorToHex( 'RGBA(0, 0, 255, 1)' ) ).toBe( '#0000ff' );
862
+ } );
863
+
864
+ it( 'handles uppercase VAR() syntax', () => {
865
+ const mockResolve = jest.fn().mockReturnValue( '#ff0000' );
866
+ expect( normalizeColorToHex( 'VAR(--my-color)', null, mockResolve ) ).toBe( '#ff0000' );
867
+ } );
758
868
  } );
759
869
 
760
870
  describe( 'Invalid inputs', () => {
@@ -0,0 +1,101 @@
1
+ import { sanitizeHtml } from '../sanitize-html';
2
+
3
+ describe( 'sanitizeHtml', () => {
4
+ test( 'preserves safe formatting tags', () => {
5
+ const input = '<b>Bold</b> <em>italic</em> <strong>strong</strong>';
6
+ expect( sanitizeHtml( input ) ).toBe( '<b>Bold</b> <em>italic</em> <strong>strong</strong>' );
7
+ } );
8
+
9
+ test( 'strips style attributes', () => {
10
+ const input = '<div style="padding: 12px;"><span style="color: red;">text</span></div>';
11
+ expect( sanitizeHtml( input ) ).toBe( '<div><span>text</span></div>' );
12
+ } );
13
+
14
+ test( 'preserves br tags', () => {
15
+ const input = 'line one<br>line two';
16
+ expect( sanitizeHtml( input ) ).toBe( 'line one<br>line two' );
17
+ } );
18
+
19
+ test( 'removes script tags', () => {
20
+ const input = '<b>Safe</b><script>alert("xss")</script>';
21
+ expect( sanitizeHtml( input ) ).toBe( '<b>Safe</b>' );
22
+ } );
23
+
24
+ test( 'removes img tags with onerror handlers', () => {
25
+ const input = '<b>Safe</b><img src=x onerror="alert(document.cookie)">';
26
+ expect( sanitizeHtml( input ) ).toBe( '<b>Safe</b>' );
27
+ } );
28
+
29
+ test( 'removes iframe tags', () => {
30
+ const input = '<div>text</div><iframe src="https://evil.com"></iframe>';
31
+ expect( sanitizeHtml( input ) ).toBe( '<div>text</div>' );
32
+ } );
33
+
34
+ test( 'removes event handler attributes from allowed tags', () => {
35
+ const input = '<div onclick="alert(1)" onmouseover="steal()">text</div>';
36
+ expect( sanitizeHtml( input ) ).toBe( '<div>text</div>' );
37
+ } );
38
+
39
+ test( 'removes javascript: URLs from href', () => {
40
+ const input = '<a href="javascript:alert(1)">click</a>';
41
+ expect( sanitizeHtml( input ) ).toBe( '<a>click</a>' );
42
+ } );
43
+
44
+ test( 'preserves safe href values', () => {
45
+ const input = '<a href="https://example.com" target="_blank" rel="noopener">link</a>';
46
+ expect( sanitizeHtml( input ) ).toBe(
47
+ '<a href="https://example.com" target="_blank" rel="noopener noreferrer">link</a>'
48
+ );
49
+ } );
50
+
51
+ test( 'removes object and embed tags', () => {
52
+ const input = '<div>safe</div><object data="x"></object><embed src="y">';
53
+ expect( sanitizeHtml( input ) ).toBe( '<div>safe</div>' );
54
+ } );
55
+
56
+ test( 'removes form tags but preserves safe child content', () => {
57
+ const input = '<form action="https://evil.com"><div>trap</div></form>';
58
+ const result = sanitizeHtml( input );
59
+ expect( result ).not.toContain( '<form' );
60
+ expect( result ).not.toContain( 'action' );
61
+ expect( result ).toContain( '<div>trap</div>' );
62
+ } );
63
+
64
+ test( 'handles complex tooltip HTML and strips style attributes', () => {
65
+ const input = `<div style="padding: 12px; font-family: sans-serif;">
66
+ <div style="font-weight: bold;">United States</div>
67
+ <div style="color: #666;">Orders: <strong>1,000</strong></div>
68
+ </div>`;
69
+ const result = sanitizeHtml( input );
70
+ expect( result ).toContain( 'United States' );
71
+ expect( result ).toContain( '<strong>1,000</strong>' );
72
+ expect( result ).not.toContain( 'style' );
73
+ } );
74
+
75
+ test( 'handles empty string', () => {
76
+ expect( sanitizeHtml( '' ) ).toBe( '' );
77
+ } );
78
+
79
+ test( 'handles plain text', () => {
80
+ expect( sanitizeHtml( 'just text' ) ).toBe( 'just text' );
81
+ } );
82
+
83
+ test( 'removes disallowed attributes like id and style', () => {
84
+ const input = '<div id="evil" style="color: red;">text</div>';
85
+ expect( sanitizeHtml( input ) ).toBe( '<div>text</div>' );
86
+ } );
87
+
88
+ test( 'enforces rel="noopener noreferrer" on target="_blank" links', () => {
89
+ const input = '<a href="https://example.com" target="_blank">link</a>';
90
+ expect( sanitizeHtml( input ) ).toBe(
91
+ '<a href="https://example.com" target="_blank" rel="noopener noreferrer">link</a>'
92
+ );
93
+ } );
94
+
95
+ test( 'overrides insufficient rel on target="_blank" links', () => {
96
+ const input = '<a href="https://example.com" target="_blank" rel="noopener">link</a>';
97
+ expect( sanitizeHtml( input ) ).toBe(
98
+ '<a href="https://example.com" target="_blank" rel="noopener noreferrer">link</a>'
99
+ );
100
+ } );
101
+ } );