├── .babelrc ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── coverage ├── clover.xml ├── coverage-final.json ├── lcov-report │ ├── base.css │ ├── block-navigation.js │ ├── favicon.png │ ├── index.html │ ├── prettify.css │ ├── prettify.js │ ├── sort-arrow-sprite.png │ ├── sorter.js │ └── src │ │ ├── components │ │ ├── GA4RComponents.tsx.html │ │ ├── index.html │ │ └── withTracker.tsx.html │ │ ├── hooks │ │ ├── index.html │ │ └── useGA4React.tsx.html │ │ ├── index.html │ │ ├── index.tsx.html │ │ └── lib │ │ ├── ga4manager.tsx.html │ │ └── index.html └── lcov.info ├── example ├── GA4R-Component │ └── GA4R-Component.tsx └── withTracker │ └── React-Router-v4-withTracker.tsx ├── jest.config.js ├── package.json ├── src ├── components │ ├── GA4RComponents.tsx │ └── withTracker.tsx ├── hooks │ └── useGA4React.tsx ├── index.tsx ├── lib │ └── ga4manager.tsx └── models │ └── gtagModels.tsx ├── test ├── GA4RComponents.test.tsx ├── GA4RComponents.timeout.test.tsx ├── __snapshots__ │ ├── GA4RComponents.test.tsx.snap │ ├── GA4RComponents.timeout.test.tsx.snap │ ├── ga4react.test.ts.snap │ ├── ga4react2.test.ts.snap │ ├── useGA4React.test.tsx.snap │ ├── useGA4React.timeout.test.tsx.snap │ ├── useGA4React.withoutGaCode.test.tsx.snap │ ├── withTracker.test.tsx.snap │ └── withTracker.timeout.test.tsx.snap ├── ga4react.test.ts ├── ga4react.timeout.test.ts ├── ga4react2.test.ts ├── useGA4React.test.tsx ├── useGA4React.timeout.test.tsx ├── useGA4React.withoutGaCode.test.tsx ├── withTracker.test.tsx └── withTracker.timeout.test.tsx ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", { "targets": { "esmodules": true } }]] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Release 0.1.271 2 | 3 | - FIX: pass nonce to ga script 4 | - ADD: changelog 5 | 6 | ## Release 0.1.269 7 | 8 | - REF: test 9 | - ADD: options for future implementations 10 | 11 | ## Release 0.1.267 12 | 13 | -REF: interface 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 manueltrebbi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GA4React - Google Analytics 4 React 2 | 3 | Simple wrapper of ga4 scripts for React: 4 | https://developers.google.com/analytics/devguides/collection/ga4 5 | 6 | ## Google Analytics 4 React 7 | 8 | Example without components 9 | 10 | ```javascript 11 | 12 | const ga4react = new GA4React( 13 | 'YOUR GA CODE', 14 | { /* ga custom config, optional */ }, 15 | [ /* additional code, optional */ ], 16 | 5000 /* timeout, optional, defaults is 5000 */, 17 | options /* { nonce: ['first-script-is-async','second-script'] } */ 18 | ); 19 | 20 | ga4react.initialize().then((ga4) => { 21 | ga4.pageview('path') 22 | ga4.gtag('event','pageview','path') // or your custom gtag event 23 | },(err) => { 24 | console.error(err) 25 | }) 26 | 27 | ``` 28 | 29 | --- 30 | 31 | ## Inject GA4React function in props of childrens 32 | 33 | Example with custom components 'GA4R' 34 | 35 | ```typescript 36 | const Test: React.FC = ({ ga4 }) => { 37 | return <>{ga4 && console.log(ga4)}; 38 | }; 39 | 40 | 41 | 42 | 43 | 44 | ``` 45 | 46 | ## RENDER: 47 | 48 | console.log results: 49 | 50 | `{pageview: ƒ, gtag: ƒ, event: ƒ}` 51 | 52 | --- 53 | 54 | ## Components withTracker 55 | 56 | Pass pageview data to the `path` prop: 57 | 58 | ```javascript 59 | const Tracker = withTracker(props => <>{JSON.stringify(props)}); 60 | ... 61 | 62 | 63 | ``` 64 | 65 | --- 66 | 67 | ## useGA4React Hook 68 | 69 | ```javascript 70 | const Example = () => { 71 | const ga4React = useGA4React(); // GA CODE, optional, if empty try to get from globals 72 | return <>{JSON.stringify(ga4React)}; 73 | }; 74 | 75 | ``` 76 | 77 | ___ 78 | ## "Manual start" 79 | 80 | ```javascript 81 | import React from "react"; 82 | import ReactDOM from "react-dom"; 83 | import GA4React, { useGA4React } from "ga-4-react"; 84 | 85 | const ga4react = new GA4React("G-1JXXXXX"); 86 | 87 | function MyApp() { 88 | const ga = useGA4React(); 89 | console.log(ga); 90 | 91 | return
hi!
; 92 | } 93 | 94 | (async () => { 95 | await ga4react.initialize(); 96 | 97 | ReactDOM.render( 98 | 99 | 100 | , 101 | document.getElementById("root") 102 | ); 103 | })(); 104 | ``` 105 | 106 | --- 107 | 108 | Buy Me A Coffee 109 | -------------------------------------------------------------------------------- /coverage/clover.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /coverage/coverage-final.json: -------------------------------------------------------------------------------- 1 | {"/Users/unrealmanu/PROGETTI/manuel/ga-4-react/src/index.tsx": {"path":"/Users/unrealmanu/PROGETTI/manuel/ga-4-react/src/index.tsx","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":63}},"1":{"start":{"line":3,"column":0},"end":{"line":3,"column":69}},"2":{"start":{"line":5,"column":0},"end":{"line":5,"column":69}},"3":{"start":{"line":7,"column":0},"end":{"line":7,"column":78}},"4":{"start":{"line":9,"column":13},"end":{"line":9,"column":40}},"5":{"start":{"line":11,"column":13},"end":{"line":11,"column":35}},"6":{"start":{"line":13,"column":13},"end":{"line":13,"column":43}},"7":{"start":{"line":15,"column":13},"end":{"line":15,"column":47}},"8":{"start":{"line":17,"column":0},"end":{"line":17,"column":31}}},"fnMap":{},"branchMap":{},"s":{"0":7,"1":7,"2":7,"3":7,"4":7,"5":7,"6":7,"7":7,"8":7},"f":{},"b":{}} 2 | ,"/Users/unrealmanu/PROGETTI/manuel/ga-4-react/src/components/GA4RComponents.tsx": {"path":"/Users/unrealmanu/PROGETTI/manuel/ga-4-react/src/components/GA4RComponents.tsx","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":51}},"1":{"start":{"line":2,"column":0},"end":{"line":2,"column":43}},"2":{"start":{"line":18,"column":18},"end":{"line":41,"column":1}},"3":{"start":{"line":23,"column":2},"end":{"line":40,"column":4}},"4":{"start":{"line":25,"column":6},"end":{"line":27,"column":null}},"5":{"start":{"line":26,"column":8},"end":{"line":26,"column":56}},"6":{"start":{"line":30,"column":6},"end":{"line":38,"column":null}},"7":{"start":{"line":31,"column":8},"end":{"line":35,"column":11}},"8":{"start":{"line":37,"column":8},"end":{"line":37,"column":21}},"9":{"start":{"line":43,"column":37},"end":{"line":77,"column":1}},"10":{"start":{"line":44,"column":6},"end":{"line":44,"column":null}},"11":{"start":{"line":45,"column":9},"end":{"line":45,"column":null}},"12":{"start":{"line":46,"column":8},"end":{"line":46,"column":null}},"13":{"start":{"line":47,"column":16},"end":{"line":47,"column":null}},"14":{"start":{"line":48,"column":10},"end":{"line":48,"column":null}},"15":{"start":{"line":50,"column":38},"end":{"line":50,"column":57}},"16":{"start":{"line":50,"column":19},"end":{"line":50,"column":21}},"17":{"start":{"line":50,"column":34},"end":{"line":50,"column":38}},"18":{"start":{"line":52,"column":2},"end":{"line":74,"column":9}},"19":{"start":{"line":53,"column":4},"end":{"line":73,"column":null}},"20":{"start":{"line":54,"column":25},"end":{"line":58,"column":null}},"21":{"start":{"line":60,"column":6},"end":{"line":67,"column":8}},"22":{"start":{"line":62,"column":10},"end":{"line":62,"column":50}},"23":{"start":{"line":65,"column":10},"end":{"line":65,"column":29}},"24":{"start":{"line":69,"column":18},"end":{"line":69,"column":40}},"25":{"start":{"line":70,"column":6},"end":{"line":72,"column":null}},"26":{"start":{"line":71,"column":8},"end":{"line":71,"column":48}},"27":{"start":{"line":76,"column":2},"end":{"line":76,"column":27}},"28":{"start":{"line":43,"column":13},"end":{"line":43,"column":37}},"29":{"start":{"line":79,"column":0},"end":{"line":79,"column":20}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":18,"column":18},"end":{"line":18,"column":null}},"loc":{"start":{"line":21,"column":31},"end":{"line":41,"column":1}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":24,"column":33},"end":{"line":24,"column":34}},"loc":{"start":{"line":24,"column":67},"end":{"line":39,"column":5}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":43,"column":37},"end":{"line":43,"column":38}},"loc":{"start":{"line":49,"column":1},"end":{"line":77,"column":1}}},"3":{"name":"(anonymous_3)","decl":{"start":{"line":52,"column":12},"end":{"line":52,"column":null}},"loc":{"start":{"line":52,"column":12},"end":{"line":74,"column":3}}},"4":{"name":"(anonymous_4)","decl":{"start":{"line":61,"column":8},"end":{"line":61,"column":9}},"loc":{"start":{"line":61,"column":38},"end":{"line":63,"column":9}}},"5":{"name":"(anonymous_5)","decl":{"start":{"line":64,"column":8},"end":{"line":64,"column":11}},"loc":{"start":{"line":64,"column":11},"end":{"line":66,"column":9}}}},"branchMap":{"0":{"loc":{"start":{"line":25,"column":6},"end":{"line":27,"column":null}},"type":"if","locations":[{"start":{"line":25,"column":6},"end":{"line":27,"column":null}},{"start":{"line":25,"column":6},"end":{"line":27,"column":null}}]},"1":{"loc":{"start":{"line":30,"column":6},"end":{"line":38,"column":null}},"type":"if","locations":[{"start":{"line":30,"column":6},"end":{"line":38,"column":null}},{"start":{"line":30,"column":6},"end":{"line":38,"column":null}}]},"2":{"loc":{"start":{"line":30,"column":10},"end":{"line":30,"column":20}},"type":"binary-expr","locations":[{"start":{"line":30,"column":10},"end":{"line":30,"column":20}},{"start":{"line":30,"column":24},"end":{"line":30,"column":62}}]},"3":{"loc":{"start":{"line":53,"column":4},"end":{"line":73,"column":null}},"type":"if","locations":[{"start":{"line":53,"column":4},"end":{"line":73,"column":null}},{"start":{"line":53,"column":4},"end":{"line":73,"column":null}}]},"4":{"loc":{"start":{"line":70,"column":6},"end":{"line":72,"column":null}},"type":"if","locations":[{"start":{"line":70,"column":6},"end":{"line":72,"column":null}},{"start":{"line":70,"column":6},"end":{"line":72,"column":null}}]}},"s":{"0":7,"1":7,"2":7,"3":3,"4":3,"5":1,"6":2,"7":1,"8":1,"9":7,"10":8,"11":7,"12":7,"13":7,"14":7,"15":7,"16":7,"17":7,"18":7,"19":4,"20":2,"21":2,"22":1,"23":1,"24":2,"25":2,"26":2,"27":7,"28":7,"29":7},"f":{"0":3,"1":3,"2":8,"3":4,"4":1,"5":1},"b":{"0":[1,2],"1":[1,1],"2":[2,2],"3":[2,2],"4":[2,0]}} 3 | ,"/Users/unrealmanu/PROGETTI/manuel/ga-4-react/src/components/withTracker.tsx": {"path":"/Users/unrealmanu/PROGETTI/manuel/ga-4-react/src/components/withTracker.tsx","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":41}},"1":{"start":{"line":2,"column":0},"end":{"line":2,"column":26}},"2":{"start":{"line":18,"column":2},"end":{"line":56,"column":4}},"3":{"start":{"line":27,"column":8},"end":{"line":27,"column":14}},"4":{"start":{"line":28,"column":4},"end":{"line":54,"column":7}},"5":{"start":{"line":29,"column":6},"end":{"line":53,"column":null}},"6":{"start":{"line":31,"column":22},"end":{"line":31,"column":44}},"7":{"start":{"line":32,"column":10},"end":{"line":34,"column":null}},"8":{"start":{"line":33,"column":12},"end":{"line":33,"column":48}},"9":{"start":{"line":35,"column":10},"end":{"line":35,"column":16}},"10":{"start":{"line":38,"column":27},"end":{"line":42,"column":null}},"11":{"start":{"line":44,"column":10},"end":{"line":51,"column":12}},"12":{"start":{"line":46,"column":14},"end":{"line":46,"column":50}},"13":{"start":{"line":49,"column":14},"end":{"line":49,"column":33}},"14":{"start":{"line":52,"column":10},"end":{"line":52,"column":16}},"15":{"start":{"line":55,"column":4},"end":{"line":55,"column":38}},"16":{"start":{"line":15,"column":0},"end":{"line":15,"column":16}},"17":{"start":{"line":59,"column":0},"end":{"line":59,"column":27}}},"fnMap":{"0":{"name":"withTracker","decl":{"start":{"line":15,"column":16},"end":{"line":15,"column":27}},"loc":{"start":{"line":16,"column":28},"end":{"line":57,"column":1}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":18,"column":9},"end":{"line":18,"column":10}},"loc":{"start":{"line":18,"column":55},"end":{"line":56,"column":3}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":28,"column":14},"end":{"line":28,"column":null}},"loc":{"start":{"line":28,"column":14},"end":{"line":54,"column":5}}},"3":{"name":"(anonymous_3)","decl":{"start":{"line":45,"column":12},"end":{"line":45,"column":13}},"loc":{"start":{"line":45,"column":42},"end":{"line":47,"column":13}}},"4":{"name":"(anonymous_4)","decl":{"start":{"line":48,"column":12},"end":{"line":48,"column":13}},"loc":{"start":{"line":48,"column":23},"end":{"line":50,"column":13}}}},"branchMap":{"0":{"loc":{"start":{"line":30,"column":8},"end":{"line":35,"column":16}},"type":"switch","locations":[{"start":{"line":30,"column":8},"end":{"line":35,"column":16}},{"start":{"line":36,"column":8},"end":{"line":36,"column":16}},{"start":{"line":37,"column":8},"end":{"line":52,"column":16}}]},"1":{"loc":{"start":{"line":32,"column":10},"end":{"line":34,"column":null}},"type":"if","locations":[{"start":{"line":32,"column":10},"end":{"line":34,"column":null}},{"start":{"line":32,"column":10},"end":{"line":34,"column":null}}]}},"s":{"0":7,"1":7,"2":2,"3":21,"4":3,"5":3,"6":1,"7":1,"8":1,"9":1,"10":2,"11":2,"12":2,"13":0,"14":2,"15":3,"16":7,"17":7},"f":{"0":2,"1":3,"2":3,"3":2,"4":0},"b":{"0":[1,0,2],"1":[1,0]}} 4 | ,"/Users/unrealmanu/PROGETTI/manuel/ga-4-react/src/hooks/useGA4React.tsx": {"path":"/Users/unrealmanu/PROGETTI/manuel/ga-4-react/src/hooks/useGA4React.tsx","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":44}},"1":{"start":{"line":2,"column":0},"end":{"line":2,"column":26}},"2":{"start":{"line":5,"column":27},"end":{"line":40,"column":1}},"3":{"start":{"line":11,"column":24},"end":{"line":11,"column":76}},"4":{"start":{"line":11,"column":12},"end":{"line":11,"column":14}},"5":{"start":{"line":11,"column":20},"end":{"line":11,"column":24}},"6":{"start":{"line":12,"column":2},"end":{"line":38,"column":15}},"7":{"start":{"line":13,"column":4},"end":{"line":37,"column":null}},"8":{"start":{"line":14,"column":6},"end":{"line":34,"column":null}},"9":{"start":{"line":16,"column":27},"end":{"line":20,"column":null}},"10":{"start":{"line":22,"column":10},"end":{"line":29,"column":12}},"11":{"start":{"line":24,"column":14},"end":{"line":24,"column":26}},"12":{"start":{"line":27,"column":14},"end":{"line":27,"column":33}},"13":{"start":{"line":30,"column":10},"end":{"line":30,"column":16}},"14":{"start":{"line":32,"column":10},"end":{"line":32,"column":41}},"15":{"start":{"line":33,"column":10},"end":{"line":33,"column":16}},"16":{"start":{"line":36,"column":6},"end":{"line":36,"column":37}},"17":{"start":{"line":39,"column":2},"end":{"line":39,"column":13}},"18":{"start":{"line":5,"column":13},"end":{"line":5,"column":27}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":27},"end":{"line":5,"column":null}},"loc":{"start":{"line":9,"column":20},"end":{"line":40,"column":1}}},"1":{"name":"(anonymous_1)","decl":{"start":{"line":12,"column":12},"end":{"line":12,"column":null}},"loc":{"start":{"line":12,"column":12},"end":{"line":38,"column":3}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":23,"column":12},"end":{"line":23,"column":13}},"loc":{"start":{"line":23,"column":42},"end":{"line":25,"column":13}}},"3":{"name":"(anonymous_3)","decl":{"start":{"line":26,"column":12},"end":{"line":26,"column":13}},"loc":{"start":{"line":26,"column":23},"end":{"line":28,"column":13}}}},"branchMap":{"0":{"loc":{"start":{"line":13,"column":4},"end":{"line":37,"column":null}},"type":"if","locations":[{"start":{"line":13,"column":4},"end":{"line":37,"column":null}},{"start":{"line":13,"column":4},"end":{"line":37,"column":null}}]},"1":{"loc":{"start":{"line":15,"column":8},"end":{"line":30,"column":16}},"type":"switch","locations":[{"start":{"line":15,"column":8},"end":{"line":30,"column":16}},{"start":{"line":31,"column":8},"end":{"line":33,"column":16}}]}},"s":{"0":7,"1":7,"2":7,"3":7,"4":6,"5":6,"6":6,"7":4,"8":3,"9":2,"10":2,"11":1,"12":1,"13":2,"14":1,"15":1,"16":1,"17":6,"18":7},"f":{"0":7,"1":4,"2":1,"3":1},"b":{"0":[3,1],"1":[2,1]}} 5 | ,"/Users/unrealmanu/PROGETTI/manuel/ga-4-react/src/lib/ga4manager.tsx": {"path":"/Users/unrealmanu/PROGETTI/manuel/ga-4-react/src/lib/ga4manager.tsx","statementMap":{"0":{"start":{"line":11,"column":13},"end":{"line":11,"column":50}},"1":{"start":{"line":25,"column":0},"end":{"line":25,"column":13}},"2":{"start":{"line":28,"column":12},"end":{"line":28,"column":26}},"3":{"start":{"line":29,"column":12},"end":{"line":29,"column":39}},"4":{"start":{"line":30,"column":12},"end":{"line":30,"column":44}},"5":{"start":{"line":31,"column":12},"end":{"line":31,"column":28}},"6":{"start":{"line":26,"column":10},"end":{"line":26,"column":54}},"7":{"start":{"line":33,"column":4},"end":{"line":33,"column":31}},"8":{"start":{"line":34,"column":4},"end":{"line":34,"column":25}},"9":{"start":{"line":35,"column":4},"end":{"line":35,"column":35}},"10":{"start":{"line":36,"column":4},"end":{"line":36,"column":45}},"11":{"start":{"line":42,"column":10},"end":{"line":48,"column":null}},"12":{"start":{"line":43,"column":4},"end":{"line":47,"column":6}},"13":{"start":{"line":54,"column":9},"end":{"line":132,"column":null}},"14":{"start":{"line":54,"column":2},"end":{"line":54,"column":9}},"15":{"start":{"line":55,"column":4},"end":{"line":131,"column":7}},"16":{"start":{"line":56,"column":6},"end":{"line":58,"column":null}},"17":{"start":{"line":57,"column":8},"end":{"line":57,"column":59}},"18":{"start":{"line":60,"column":36},"end":{"line":60,"column":76}},"19":{"start":{"line":61,"column":45},"end":{"line":61,"column":77}},"20":{"start":{"line":62,"column":6},"end":{"line":62,"column":44}},"21":{"start":{"line":63,"column":6},"end":{"line":66,"column":8}},"22":{"start":{"line":67,"column":6},"end":{"line":105,"column":8}},"23":{"start":{"line":68,"column":43},"end":{"line":69,"column":null}},"24":{"start":{"line":71,"column":8},"end":{"line":73,"column":null}},"25":{"start":{"line":72,"column":10},"end":{"line":72,"column":26}},"26":{"start":{"line":75,"column":46},"end":{"line":75,"column":78}},"27":{"start":{"line":77,"column":8},"end":{"line":77,"column":57}},"28":{"start":{"line":79,"column":33},"end":{"line":82,"column":74}},"29":{"start":{"line":84,"column":8},"end":{"line":90,"column":null}},"30":{"start":{"line":85,"column":10},"end":{"line":89,"column":13}},"31":{"start":{"line":86,"column":12},"end":{"line":88,"column":18}},"32":{"start":{"line":92,"column":8},"end":{"line":92,"column":42}},"33":{"start":{"line":94,"column":8},"end":{"line":94,"column":37}},"34":{"start":{"line":96,"column":51},"end":{"line":96,"column":73}},"35":{"start":{"line":98,"column":8},"end":{"line":100,"column":null}},"36":{"start":{"line":99,"column":10},"end":{"line":99,"column":51}},"37":{"start":{"line":102,"column":8},"end":{"line":102,"column":67}},"38":{"start":{"line":104,"column":8},"end":{"line":104,"column":26}},"39":{"start":{"line":107,"column":6},"end":{"line":109,"column":8}},"40":{"start":{"line":108,"column":8},"end":{"line":108,"column":60}},"41":{"start":{"line":111,"column":33},"end":{"line":124,"column":7}},"42":{"start":{"line":112,"column":8},"end":{"line":123,"column":null}},"43":{"start":{"line":115,"column":12},"end":{"line":121,"column":null}},"44":{"start":{"line":116,"column":14},"end":{"line":116,"column":44}},"45":{"start":{"line":117,"column":14},"end":{"line":120,"column":16}},"46":{"start":{"line":122,"column":12},"end":{"line":122,"column":18}},"47":{"start":{"line":126,"column":6},"end":{"line":126,"column":72}},"48":{"start":{"line":128,"column":6},"end":{"line":130,"column":23}},"49":{"start":{"line":129,"column":8},"end":{"line":129,"column":46}},"50":{"start":{"line":138,"column":9},"end":{"line":148,"column":null}},"51":{"start":{"line":143,"column":4},"end":{"line":147,"column":7}},"52":{"start":{"line":157,"column":9},"end":{"line":168,"column":null}},"53":{"start":{"line":161,"column":4},"end":{"line":161,"column":null}},"54":{"start":{"line":163,"column":4},"end":{"line":167,"column":7}},"55":{"start":{"line":174,"column":9},"end":{"line":177,"column":null}},"56":{"start":{"line":174,"column":12},"end":{"line":174,"column":24}},"57":{"start":{"line":176,"column":4},"end":{"line":176,"column":30}},"58":{"start":{"line":183,"column":9},"end":{"line":186,"column":null}},"59":{"start":{"line":183,"column":14},"end":{"line":183,"column":26}},"60":{"start":{"line":185,"column":4},"end":{"line":185,"column":32}},"61":{"start":{"line":191,"column":9},"end":{"line":198,"column":null}},"62":{"start":{"line":192,"column":4},"end":{"line":197,"column":null}},"63":{"start":{"line":194,"column":8},"end":{"line":194,"column":20}},"64":{"start":{"line":196,"column":8},"end":{"line":196,"column":21}},"65":{"start":{"line":203,"column":9},"end":{"line":209,"column":null}},"66":{"start":{"line":204,"column":4},"end":{"line":208,"column":null}},"67":{"start":{"line":205,"column":6},"end":{"line":205,"column":41}},"68":{"start":{"line":207,"column":6},"end":{"line":207,"column":62}},"69":{"start":{"line":210,"column":0},"end":{"line":210,"column":null}},"70":{"start":{"line":25,"column":13},"end":{"line":25,"column":21}},"71":{"start":{"line":212,"column":0},"end":{"line":212,"column":24}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":25,"column":0},"end":{"line":25,"column":13}},"loc":{"start":{"line":25,"column":0},"end":{"line":210,"column":null}}},"1":{"name":"GA4React","decl":{"start":{"line":27,"column":2},"end":{"line":27,"column":null}},"loc":{"start":{"line":31,"column":28},"end":{"line":37,"column":3}}},"2":{"name":"(anonymous_2)","decl":{"start":{"line":42,"column":2},"end":{"line":42,"column":10}},"loc":{"start":{"line":42,"column":2},"end":{"line":48,"column":3}}},"3":{"name":"(anonymous_3)","decl":{"start":{"line":54,"column":2},"end":{"line":54,"column":9}},"loc":{"start":{"line":54,"column":2},"end":{"line":132,"column":3}}},"4":{"name":"(anonymous_4)","decl":{"start":{"line":55,"column":23},"end":{"line":55,"column":24}},"loc":{"start":{"line":55,"column":39},"end":{"line":131,"column":5}}},"5":{"name":"(anonymous_5)","decl":{"start":{"line":67,"column":27},"end":{"line":67,"column":null}},"loc":{"start":{"line":67,"column":27},"end":{"line":105,"column":7}}},"6":{"name":"(anonymous_6)","decl":{"start":{"line":85,"column":40},"end":{"line":85,"column":41}},"loc":{"start":{"line":85,"column":53},"end":{"line":89,"column":11}}},"7":{"name":"(anonymous_7)","decl":{"start":{"line":107,"column":28},"end":{"line":107,"column":null}},"loc":{"start":{"line":107,"column":28},"end":{"line":109,"column":7}}},"8":{"name":"(anonymous_8)","decl":{"start":{"line":111,"column":33},"end":{"line":111,"column":null}},"loc":{"start":{"line":111,"column":33},"end":{"line":124,"column":7}}},"9":{"name":"(anonymous_9)","decl":{"start":{"line":128,"column":17},"end":{"line":128,"column":null}},"loc":{"start":{"line":128,"column":17},"end":{"line":130,"column":7}}},"10":{"name":"(anonymous_10)","decl":{"start":{"line":138,"column":2},"end":{"line":138,"column":9}},"loc":{"start":{"line":141,"column":18},"end":{"line":148,"column":3}}},"11":{"name":"(anonymous_11)","decl":{"start":{"line":157,"column":2},"end":{"line":157,"column":9}},"loc":{"start":{"line":161,"column":35},"end":{"line":168,"column":3}}},"12":{"name":"(anonymous_12)","decl":{"start":{"line":174,"column":2},"end":{"line":174,"column":9}},"loc":{"start":{"line":174,"column":2},"end":{"line":177,"column":3}}},"13":{"name":"(anonymous_13)","decl":{"start":{"line":183,"column":2},"end":{"line":183,"column":9}},"loc":{"start":{"line":183,"column":2},"end":{"line":186,"column":3}}},"14":{"name":"(anonymous_14)","decl":{"start":{"line":191,"column":2},"end":{"line":191,"column":9}},"loc":{"start":{"line":191,"column":2},"end":{"line":198,"column":3}}},"15":{"name":"(anonymous_15)","decl":{"start":{"line":203,"column":2},"end":{"line":203,"column":9}},"loc":{"start":{"line":203,"column":2},"end":{"line":209,"column":3}}}},"branchMap":{"0":{"loc":{"start":{"line":33,"column":18},"end":{"line":33,"column":24}},"type":"binary-expr","locations":[{"start":{"line":33,"column":18},"end":{"line":33,"column":24}},{"start":{"line":33,"column":28},"end":{"line":33,"column":30}}]},"1":{"loc":{"start":{"line":35,"column":19},"end":{"line":35,"column":26}},"type":"binary-expr","locations":[{"start":{"line":35,"column":19},"end":{"line":35,"column":26}},{"start":{"line":35,"column":30},"end":{"line":35,"column":34}}]},"2":{"loc":{"start":{"line":56,"column":6},"end":{"line":58,"column":null}},"type":"if","locations":[{"start":{"line":56,"column":6},"end":{"line":58,"column":null}},{"start":{"line":56,"column":6},"end":{"line":58,"column":null}}]},"3":{"loc":{"start":{"line":71,"column":8},"end":{"line":73,"column":null}},"type":"if","locations":[{"start":{"line":71,"column":8},"end":{"line":73,"column":null}},{"start":{"line":71,"column":8},"end":{"line":73,"column":null}}]},"4":{"loc":{"start":{"line":84,"column":8},"end":{"line":90,"column":null}},"type":"if","locations":[{"start":{"line":84,"column":8},"end":{"line":90,"column":null}},{"start":{"line":84,"column":8},"end":{"line":90,"column":null}}]},"5":{"loc":{"start":{"line":98,"column":8},"end":{"line":100,"column":null}},"type":"if","locations":[{"start":{"line":98,"column":8},"end":{"line":100,"column":null}},{"start":{"line":98,"column":8},"end":{"line":100,"column":null}}]},"6":{"loc":{"start":{"line":113,"column":10},"end":{"line":113,"column":29}},"type":"switch","locations":[{"start":{"line":113,"column":10},"end":{"line":113,"column":29}},{"start":{"line":114,"column":10},"end":{"line":122,"column":18}}]},"7":{"loc":{"start":{"line":115,"column":12},"end":{"line":121,"column":null}},"type":"if","locations":[{"start":{"line":115,"column":12},"end":{"line":121,"column":null}},{"start":{"line":115,"column":12},"end":{"line":121,"column":null}}]},"8":{"loc":{"start":{"line":145,"column":21},"end":{"line":145,"column":29}},"type":"binary-expr","locations":[{"start":{"line":145,"column":21},"end":{"line":145,"column":29}},{"start":{"line":145,"column":33},"end":{"line":145,"column":48}}]},"9":{"loc":{"start":{"line":146,"column":18},"end":{"line":146,"column":23}},"type":"binary-expr","locations":[{"start":{"line":146,"column":18},"end":{"line":146,"column":23}},{"start":{"line":146,"column":27},"end":{"line":146,"column":41}}]},"10":{"loc":{"start":{"line":161,"column":4},"end":{"line":161,"column":null}},"type":"if","locations":[{"start":{"line":161,"column":4},"end":{"line":161,"column":null}},{"start":{"line":161,"column":4},"end":{"line":161,"column":null}}]},"11":{"loc":{"start":{"line":193,"column":6},"end":{"line":194,"column":20}},"type":"switch","locations":[{"start":{"line":193,"column":6},"end":{"line":194,"column":20}},{"start":{"line":195,"column":6},"end":{"line":196,"column":21}}]},"12":{"loc":{"start":{"line":204,"column":4},"end":{"line":208,"column":null}},"type":"if","locations":[{"start":{"line":204,"column":4},"end":{"line":208,"column":null}},{"start":{"line":204,"column":4},"end":{"line":208,"column":null}}]}},"s":{"0":7,"1":7,"2":7,"3":7,"4":7,"5":7,"6":7,"7":7,"8":7,"9":7,"10":7,"11":7,"12":11,"13":7,"14":7,"15":7,"16":7,"17":0,"18":7,"19":7,"20":7,"21":7,"22":7,"23":11,"24":11,"25":4,"26":11,"27":11,"28":11,"29":11,"30":1,"31":0,"32":11,"33":11,"34":11,"35":11,"36":0,"37":11,"38":11,"39":7,"40":0,"41":7,"42":7,"43":7,"44":7,"45":7,"46":7,"47":7,"48":7,"49":3,"50":7,"51":3,"52":7,"53":0,"54":0,"55":7,"56":0,"57":0,"58":7,"59":15,"60":3,"61":7,"62":29,"63":8,"64":21,"65":7,"66":5,"67":4,"68":1,"69":7,"70":7,"71":7},"f":{"0":7,"1":7,"2":11,"3":7,"4":7,"5":11,"6":0,"7":0,"8":7,"9":3,"10":3,"11":0,"12":0,"13":3,"14":29,"15":5},"b":{"0":[7,6],"1":[7,5],"2":[0,7],"3":[4,7],"4":[1,10],"5":[0,11],"6":[7,7],"7":[7,0],"8":[3,3],"9":[3,3],"10":[0,0],"11":[8,21],"12":[4,1]}} 6 | } 7 | -------------------------------------------------------------------------------- /coverage/lcov-report/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* yellow */ 156 | .cbranch-no { background: yellow !important; color: #111; } 157 | /* dark red */ 158 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 159 | .low .chart { border:1px solid #C21F39 } 160 | .highlighted, 161 | .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ 162 | background: #C21F39 !important; 163 | } 164 | /* medium red */ 165 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 166 | /* light red */ 167 | .low, .cline-no { background:#FCE1E5 } 168 | /* light green */ 169 | .high, .cline-yes { background:rgb(230,245,208) } 170 | /* medium green */ 171 | .cstat-yes { background:rgb(161,215,106) } 172 | /* dark green */ 173 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 174 | .high .chart { border:1px solid rgb(77,146,33) } 175 | /* dark yellow (gold) */ 176 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 177 | .medium .chart { border:1px solid #f9cd0b; } 178 | /* light yellow */ 179 | .medium { background: #fff4c2; } 180 | 181 | .cstat-skip { background: #ddd; color: #111; } 182 | .fstat-skip { background: #ddd; color: #111 !important; } 183 | .cbranch-skip { background: #ddd !important; color: #111; } 184 | 185 | span.cline-neutral { background: #eaeaea; } 186 | 187 | .coverage-summary td.empty { 188 | opacity: .5; 189 | padding-top: 4px; 190 | padding-bottom: 4px; 191 | line-height: 1; 192 | color: #888; 193 | } 194 | 195 | .cover-fill, .cover-empty { 196 | display:inline-block; 197 | height: 12px; 198 | } 199 | .chart { 200 | line-height: 0; 201 | } 202 | .cover-empty { 203 | background: white; 204 | } 205 | .cover-full { 206 | border-right: none !important; 207 | } 208 | pre.prettyprint { 209 | border: none !important; 210 | padding: 0 !important; 211 | margin: 0 !important; 212 | } 213 | .com { color: #999 !important; } 214 | .ignore-none { color: #999; font-weight: normal; } 215 | 216 | .wrapper { 217 | min-height: 100%; 218 | height: auto !important; 219 | height: 100%; 220 | margin: 0 auto -48px; 221 | } 222 | .footer, .push { 223 | height: 48px; 224 | } 225 | -------------------------------------------------------------------------------- /coverage/lcov-report/block-navigation.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var jumpToCode = (function init() { 3 | // Classes of code we would like to highlight in the file view 4 | var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; 5 | 6 | // Elements to highlight in the file listing view 7 | var fileListingElements = ['td.pct.low']; 8 | 9 | // We don't want to select elements that are direct descendants of another match 10 | var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` 11 | 12 | // Selecter that finds elements on the page to which we can jump 13 | var selector = 14 | fileListingElements.join(', ') + 15 | ', ' + 16 | notSelector + 17 | missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` 18 | 19 | // The NodeList of matching elements 20 | var missingCoverageElements = document.querySelectorAll(selector); 21 | 22 | var currentIndex; 23 | 24 | function toggleClass(index) { 25 | missingCoverageElements 26 | .item(currentIndex) 27 | .classList.remove('highlighted'); 28 | missingCoverageElements.item(index).classList.add('highlighted'); 29 | } 30 | 31 | function makeCurrent(index) { 32 | toggleClass(index); 33 | currentIndex = index; 34 | missingCoverageElements.item(index).scrollIntoView({ 35 | behavior: 'smooth', 36 | block: 'center', 37 | inline: 'center' 38 | }); 39 | } 40 | 41 | function goToPrevious() { 42 | var nextIndex = 0; 43 | if (typeof currentIndex !== 'number' || currentIndex === 0) { 44 | nextIndex = missingCoverageElements.length - 1; 45 | } else if (missingCoverageElements.length > 1) { 46 | nextIndex = currentIndex - 1; 47 | } 48 | 49 | makeCurrent(nextIndex); 50 | } 51 | 52 | function goToNext() { 53 | var nextIndex = 0; 54 | 55 | if ( 56 | typeof currentIndex === 'number' && 57 | currentIndex < missingCoverageElements.length - 1 58 | ) { 59 | nextIndex = currentIndex + 1; 60 | } 61 | 62 | makeCurrent(nextIndex); 63 | } 64 | 65 | return function jump(event) { 66 | switch (event.which) { 67 | case 78: // n 68 | case 74: // j 69 | goToNext(); 70 | break; 71 | case 66: // b 72 | case 75: // k 73 | case 80: // p 74 | goToPrevious(); 75 | break; 76 | } 77 | }; 78 | })(); 79 | window.addEventListener('keydown', jumpToCode); 80 | -------------------------------------------------------------------------------- /coverage/lcov-report/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unrealmanu/ga-4-react/ec99a94fdf391f925424d9717adc0310674d4a65/coverage/lcov-report/favicon.png -------------------------------------------------------------------------------- /coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for All files 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files

23 |
24 | 25 |
26 | 93.92% 27 | Statements 28 | 139/148 29 |
30 | 31 | 32 |
33 | 82.22% 34 | Branches 35 | 37/45 36 |
37 | 38 | 39 |
40 | 83.87% 41 | Functions 42 | 26/31 43 |
44 | 45 | 46 |
47 | 94.2% 48 | Lines 49 | 130/138 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 |
58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 |
FileStatementsBranchesFunctionsLines
src 78 |
79 |
100%9/9100%0/0100%0/0100%9/9
src/components 93 |
94 |
97.92%47/4880%12/1590.91%10/1197.78%44/45
src/hooks 108 |
109 |
100%19/19100%4/4100%4/4100%16/16
src/lib 123 |
124 |
88.89%64/7280.77%21/2675%12/1689.71%61/68
137 |
138 |
139 |
140 | 145 | 146 | 147 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unrealmanu/ga-4-react/ec99a94fdf391f925424d9717adc0310674d4a65/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var addSorting = (function() { 3 | 'use strict'; 4 | var cols, 5 | currentSort = { 6 | index: 0, 7 | desc: false 8 | }; 9 | 10 | // returns the summary table element 11 | function getTable() { 12 | return document.querySelector('.coverage-summary'); 13 | } 14 | // returns the thead element of the summary table 15 | function getTableHeader() { 16 | return getTable().querySelector('thead tr'); 17 | } 18 | // returns the tbody element of the summary table 19 | function getTableBody() { 20 | return getTable().querySelector('tbody'); 21 | } 22 | // returns the th element for nth column 23 | function getNthColumn(n) { 24 | return getTableHeader().querySelectorAll('th')[n]; 25 | } 26 | 27 | // loads all columns 28 | function loadColumns() { 29 | var colNodes = getTableHeader().querySelectorAll('th'), 30 | colNode, 31 | cols = [], 32 | col, 33 | i; 34 | 35 | for (i = 0; i < colNodes.length; i += 1) { 36 | colNode = colNodes[i]; 37 | col = { 38 | key: colNode.getAttribute('data-col'), 39 | sortable: !colNode.getAttribute('data-nosort'), 40 | type: colNode.getAttribute('data-type') || 'string' 41 | }; 42 | cols.push(col); 43 | if (col.sortable) { 44 | col.defaultDescSort = col.type === 'number'; 45 | colNode.innerHTML = 46 | colNode.innerHTML + ''; 47 | } 48 | } 49 | return cols; 50 | } 51 | // attaches a data attribute to every tr element with an object 52 | // of data values keyed by column name 53 | function loadRowData(tableRow) { 54 | var tableCols = tableRow.querySelectorAll('td'), 55 | colNode, 56 | col, 57 | data = {}, 58 | i, 59 | val; 60 | for (i = 0; i < tableCols.length; i += 1) { 61 | colNode = tableCols[i]; 62 | col = cols[i]; 63 | val = colNode.getAttribute('data-value'); 64 | if (col.type === 'number') { 65 | val = Number(val); 66 | } 67 | data[col.key] = val; 68 | } 69 | return data; 70 | } 71 | // loads all row data 72 | function loadData() { 73 | var rows = getTableBody().querySelectorAll('tr'), 74 | i; 75 | 76 | for (i = 0; i < rows.length; i += 1) { 77 | rows[i].data = loadRowData(rows[i]); 78 | } 79 | } 80 | // sorts the table using the data for the ith column 81 | function sortByIndex(index, desc) { 82 | var key = cols[index].key, 83 | sorter = function(a, b) { 84 | a = a.data[key]; 85 | b = b.data[key]; 86 | return a < b ? -1 : a > b ? 1 : 0; 87 | }, 88 | finalSorter = sorter, 89 | tableBody = document.querySelector('.coverage-summary tbody'), 90 | rowNodes = tableBody.querySelectorAll('tr'), 91 | rows = [], 92 | i; 93 | 94 | if (desc) { 95 | finalSorter = function(a, b) { 96 | return -1 * sorter(a, b); 97 | }; 98 | } 99 | 100 | for (i = 0; i < rowNodes.length; i += 1) { 101 | rows.push(rowNodes[i]); 102 | tableBody.removeChild(rowNodes[i]); 103 | } 104 | 105 | rows.sort(finalSorter); 106 | 107 | for (i = 0; i < rows.length; i += 1) { 108 | tableBody.appendChild(rows[i]); 109 | } 110 | } 111 | // removes sort indicators for current column being sorted 112 | function removeSortIndicators() { 113 | var col = getNthColumn(currentSort.index), 114 | cls = col.className; 115 | 116 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 117 | col.className = cls; 118 | } 119 | // adds sort indicators for current column being sorted 120 | function addSortIndicators() { 121 | getNthColumn(currentSort.index).className += currentSort.desc 122 | ? ' sorted-desc' 123 | : ' sorted'; 124 | } 125 | // adds event listeners for all sorter widgets 126 | function enableUI() { 127 | var i, 128 | el, 129 | ithSorter = function ithSorter(i) { 130 | var col = cols[i]; 131 | 132 | return function() { 133 | var desc = col.defaultDescSort; 134 | 135 | if (currentSort.index === i) { 136 | desc = !currentSort.desc; 137 | } 138 | sortByIndex(i, desc); 139 | removeSortIndicators(); 140 | currentSort.index = i; 141 | currentSort.desc = desc; 142 | addSortIndicators(); 143 | }; 144 | }; 145 | for (i = 0; i < cols.length; i += 1) { 146 | if (cols[i].sortable) { 147 | // add the click event handler on the th so users 148 | // dont have to click on those tiny arrows 149 | el = getNthColumn(i).querySelector('.sorter').parentElement; 150 | if (el.addEventListener) { 151 | el.addEventListener('click', ithSorter(i)); 152 | } else { 153 | el.attachEvent('onclick', ithSorter(i)); 154 | } 155 | } 156 | } 157 | } 158 | // adds sorting functionality to the UI 159 | return function() { 160 | if (!getTable()) { 161 | return; 162 | } 163 | cols = loadColumns(); 164 | loadData(); 165 | addSortIndicators(); 166 | enableUI(); 167 | }; 168 | })(); 169 | 170 | window.addEventListener('load', addSorting); 171 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/components/GA4RComponents.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/components/GA4RComponents.tsx 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/components GA4RComponents.tsx

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 30/30 29 |
30 | 31 | 32 |
33 | 90% 34 | Branches 35 | 9/10 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 6/6 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 27/27 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 |
58 |
59 |

 60 | 
1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | 9 69 | 10 70 | 11 71 | 12 72 | 13 73 | 14 74 | 15 75 | 16 76 | 17 77 | 18 78 | 19 79 | 20 80 | 21 81 | 22 82 | 23 83 | 24 84 | 25 85 | 26 86 | 27 87 | 28 88 | 29 89 | 30 90 | 31 91 | 32 92 | 33 93 | 34 94 | 35 95 | 36 96 | 37 97 | 38 98 | 39 99 | 40 100 | 41 101 | 42 102 | 43 103 | 44 104 | 45 105 | 46 106 | 47 107 | 48 108 | 49 109 | 50 110 | 51 111 | 52 112 | 53 113 | 54 114 | 55 115 | 56 116 | 57 117 | 58 118 | 59 119 | 60 120 | 61 121 | 62 122 | 63 123 | 64 124 | 65 125 | 66 126 | 67 127 | 68 128 | 69 129 | 70 130 | 71 131 | 72 132 | 73 133 | 74 134 | 75 135 | 76 136 | 77 137 | 78 138 | 79 139 | 807x 140 | 7x 141 |   142 |   143 |   144 |   145 |   146 |   147 |   148 |   149 |   150 |   151 |   152 |   153 |   154 |   155 |   156 | 7x 157 |   158 |   159 |   160 |   161 | 3x 162 |   163 | 3x 164 | 1x 165 |   166 |   167 |   168 | 2x 169 | 1x 170 |   171 |   172 |   173 |   174 |   175 | 1x 176 |   177 |   178 |   179 |   180 |   181 | 7x 182 | 8x 183 | 7x 184 | 7x 185 | 7x 186 | 7x 187 |   188 | 7x 189 |   190 | 7x 191 | 4x 192 | 2x 193 |   194 |   195 |   196 |   197 |   198 | 2x 199 |   200 | 1x 201 |   202 |   203 | 1x 204 |   205 |   206 |   207 | 2x 208 | 2x 209 | 2x 210 |   211 |   212 |   213 |   214 | 7x 215 |   216 |   217 | 7x 218 |  
import React, { useState, useEffect } from 'react';
219 | import GA4React from './../lib/ga4manager';
220 | import { GA4ReactResolveInterface } from '../lib/gtagModels';
221 |  
222 | export interface IGAReactConfig {
223 |   send_page_view: boolean;
224 |   groups: string;
225 | }
226 |  
227 | export interface IGA4R {
228 |   code: string;
229 |   timeout?: number;
230 |   config?: IGAReactConfig;
231 |   additionalCode?: Array<string>;
232 |   children?: any;
233 | }
234 |  
235 | const outputGA4 = (
236 |   children: any,
237 |   setComponents: Function,
238 |   ga4: GA4ReactResolveInterface
239 | ) => {
240 |   setComponents(
241 |     React.Children.map(children, (child: React.ReactChildren, index) => {
242 |       if (!React.isValidElement(child)) {
243 |         return <React.Fragment>{child}</React.Fragment>;
244 |       }
245 |  
246 |       //@ts-ignore
247 |       if (child.type && typeof child.type.name !== 'undefined') {
248 |         return React.cloneElement(child, {
249 |           //@ts-ignore
250 |           ga4: ga4,
251 |           index,
252 |         });
253 |       } else {
254 |         return child;
255 |       }
256 |     })
257 |   );
258 | };
259 |  
260 | export const GA4R: React.FC<IGA4R> = ({
261 |   code,
262 |   timeout,
263 |   config,
264 |   additionalCode,
265 |   children,
266 | }) => {
267 |   const [components, setComponents] = useState<any>(null);
268 |  
269 |   useEffect(() => {
270 |     if (!GA4React.isInitialized()) {
271 |       const ga4manager = new GA4React(
272 |         `${code}`,
273 |         config,
274 |         additionalCode,
275 |         timeout
276 |       );
277 |       ga4manager.initialize().then(
278 |         (ga4: GA4ReactResolveInterface) => {
279 |           outputGA4(children, setComponents, ga4);
280 |         },
281 |         err => {
282 |           console.error(err);
283 |         }
284 |       );
285 |     } else {
286 |       const ga4 = GA4React.getGA4React();
287 |       Eif (ga4) {
288 |         outputGA4(children, setComponents, ga4);
289 |       }
290 |     }
291 |   }, []);
292 |  
293 |   return <>{components}</>;
294 | };
295 |  
296 | export default GA4R;
297 |  
298 | 299 |
300 |
301 | 306 | 307 | 308 | 313 | 314 | 315 | 316 | 317 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/components/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/components 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src/components

23 |
24 | 25 |
26 | 97.92% 27 | Statements 28 | 47/48 29 |
30 | 31 | 32 |
33 | 80% 34 | Branches 35 | 12/15 36 |
37 | 38 | 39 |
40 | 90.91% 41 | Functions 42 | 10/11 43 |
44 | 45 | 46 |
47 | 97.78% 48 | Lines 49 | 44/45 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 |
58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 |
FileStatementsBranchesFunctionsLines
GA4RComponents.tsx 78 |
79 |
100%30/3090%9/10100%6/6100%27/27
withTracker.tsx 93 |
94 |
94.44%17/1860%3/580%4/594.44%17/18
107 |
108 |
109 |
110 | 115 | 116 | 117 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/components/withTracker.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/components/withTracker.tsx 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/components withTracker.tsx

23 |
24 | 25 |
26 | 94.44% 27 | Statements 28 | 17/18 29 |
30 | 31 | 32 |
33 | 60% 34 | Branches 35 | 3/5 36 |
37 | 38 | 39 |
40 | 80% 41 | Functions 42 | 4/5 43 |
44 | 45 | 46 |
47 | 94.44% 48 | Lines 49 | 17/18 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 |
58 |
59 |

 60 | 
1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | 9 69 | 10 70 | 11 71 | 12 72 | 13 73 | 14 74 | 15 75 | 16 76 | 17 77 | 18 78 | 19 79 | 20 80 | 21 81 | 22 82 | 23 83 | 24 84 | 25 85 | 26 86 | 27 87 | 28 88 | 29 89 | 30 90 | 31 91 | 32 92 | 33 93 | 34 94 | 35 95 | 36 96 | 37 97 | 38 98 | 39 99 | 40 100 | 41 101 | 42 102 | 43 103 | 44 104 | 45 105 | 46 106 | 47 107 | 48 108 | 49 109 | 50 110 | 51 111 | 52 112 | 53 113 | 54 114 | 55 115 | 56 116 | 57 117 | 58 118 | 59 119 | 607x 120 | 7x 121 |   122 |   123 |   124 |   125 |   126 |   127 |   128 |   129 |   130 |   131 |   132 |   133 | 7x 134 |   135 |   136 | 2x 137 |   138 |   139 |   140 |   141 |   142 |   143 |   144 |   145 | 21x 146 | 3x 147 | 3x 148 |   149 | 1x 150 | 1x 151 | 1x 152 |   153 | 1x 154 |   155 |   156 | 2x 157 |   158 |   159 |   160 |   161 |   162 | 2x 163 |   164 | 2x 165 |   166 |   167 |   168 |   169 |   170 | 2x 171 |   172 |   173 | 3x 174 |   175 |   176 |   177 | 7x 178 |  
import React, { useEffect } from 'react';
179 | import GA4React from '..';
180 | import { ga4Config, GA4ReactResolveInterface } from '../lib/gtagModels';
181 |  
182 | export interface GA4WithTrackerComponentInterface {
183 |   path: string | Location;
184 |   location?: string | Location;
185 |   title?: string;
186 |   gaCode?: string;
187 |   gaConfig?: ga4Config | object;
188 |   additionalCode?: Array<string>;
189 |   timeout?: number;
190 | }
191 |  
192 | export function withTracker(
193 |   MyComponent: React.FC<any>
194 | ): React.FC<GA4WithTrackerComponentInterface> {
195 |   return (props: GA4WithTrackerComponentInterface & any) => {
196 |     const {
197 |       path,
198 |       location,
199 |       title,
200 |       gaCode,
201 |       gaTimeout,
202 |       gaConfig,
203 |       gaAdditionalCode,
204 |     } = props;
205 |     useEffect(() => {
206 |       switch (GA4React.isInitialized()) {
207 |         case true:
208 |           const ga4 = GA4React.getGA4React();
209 |           Eif (ga4) {
210 |             ga4.pageview(path, location, title);
211 |           }
212 |           break;
213 |         default:
214 |         case false:
215 |           const ga4react = new GA4React(
216 |             `${gaCode}`,
217 |             gaConfig,
218 |             gaAdditionalCode,
219 |             gaTimeout
220 |           );
221 |           ga4react.initialize().then(
222 |             (ga4: GA4ReactResolveInterface) => {
223 |               ga4.pageview(path, location, title);
224 |             },
225 |             (err: Error) => {
226 |               console.error(err);
227 |             }
228 |           );
229 |           break;
230 |       }
231 |     });
232 |     return <MyComponent {...props} />;
233 |   };
234 | }
235 |  
236 | export default withTracker;
237 |  
238 | 239 |
240 |
241 | 246 | 247 | 248 | 253 | 254 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/hooks/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/hooks 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src/hooks

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 19/19 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 4/4 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 4/4 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 16/16 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 |
58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
FileStatementsBranchesFunctionsLines
useGA4React.tsx 78 |
79 |
100%19/19100%4/4100%4/4100%16/16
92 |
93 |
94 |
95 | 100 | 101 | 102 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/hooks/useGA4React.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/hooks/useGA4React.tsx 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/hooks useGA4React.tsx

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 19/19 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 4/4 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 4/4 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 16/16 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 |
58 |
59 |

 60 | 
1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | 9 69 | 10 70 | 11 71 | 12 72 | 13 73 | 14 74 | 15 75 | 16 76 | 17 77 | 18 78 | 19 79 | 20 80 | 21 81 | 22 82 | 23 83 | 24 84 | 25 85 | 26 86 | 27 87 | 28 88 | 29 89 | 30 90 | 31 91 | 32 92 | 33 93 | 34 94 | 35 95 | 36 96 | 37 97 | 38 98 | 39 99 | 40 100 | 417x 101 | 7x 102 |   103 |   104 | 7x 105 |   106 |   107 |   108 |   109 |   110 | 7x 111 | 6x 112 | 4x 113 | 3x 114 |   115 | 2x 116 |   117 |   118 |   119 |   120 |   121 | 2x 122 |   123 | 1x 124 |   125 |   126 | 1x 127 |   128 |   129 | 2x 130 |   131 | 1x 132 | 1x 133 |   134 |   135 | 1x 136 |   137 |   138 | 6x 139 |   140 |  
import { useState, useEffect } from 'react';
141 | import GA4React from '..';
142 | import { ga4Config, GA4ReactResolveInterface } from '../lib/gtagModels';
143 |  
144 | export const useGA4React = (
145 |   gaCode?: string,
146 |   gaConfig?: ga4Config | object,
147 |   gaAdditionalCode?: Array<string>,
148 |   gaTimeout?: number
149 | ): GA4ReactResolveInterface | void => {
150 |   const [ga4, setGA4] = useState<GA4ReactResolveInterface | void>(undefined);
151 |   useEffect(() => {
152 |     if (gaCode) {
153 |       switch (GA4React.isInitialized()) {
154 |         case false:
155 |           const ga4react = new GA4React(
156 |             `${gaCode}`,
157 |             gaConfig,
158 |             gaAdditionalCode,
159 |             gaTimeout
160 |           );
161 |           ga4react.initialize().then(
162 |             (ga4: GA4ReactResolveInterface) => {
163 |               setGA4(ga4);
164 |             },
165 |             (err: Error) => {
166 |               console.error(err);
167 |             }
168 |           );
169 |           break;
170 |         case true:
171 |           setGA4(GA4React.getGA4React());
172 |           break;
173 |       }
174 |     } else {
175 |       setGA4(GA4React.getGA4React());
176 |     }
177 |   }, [gaCode]);
178 |   return ga4;
179 | };
180 |  
181 | 182 |
183 |
184 | 189 | 190 | 191 | 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 9/9 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 9/9 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 |
58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
FileStatementsBranchesFunctionsLines
index.tsx 78 |
79 |
100%9/9100%0/0100%0/0100%9/9
92 |
93 |
94 |
95 | 100 | 101 | 102 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/index.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/index.tsx 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src index.tsx

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 9/9 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 9/9 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 |
58 |
59 |

 60 | 
1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | 9 69 | 10 70 | 11 71 | 12 72 | 13 73 | 14 74 | 15 75 | 16 76 | 17 77 | 187x 78 |   79 | 7x 80 |   81 | 7x 82 |   83 | 7x 84 |   85 | 7x 86 |   87 | 7x 88 |   89 | 7x 90 |   91 | 7x 92 |   93 | 7x 94 |  
import { GA4React as GA4ReactManager } from './lib/ga4manager';
 95 |  
 96 | import { GA4R as GA4RComponents } from './components/GA4RComponents';
 97 |  
 98 | import { useGA4React as useGA4ReactHook } from './hooks/useGA4React';
 99 |  
100 | import { withTracker as withTrackerFunction } from './components/withTracker';
101 |  
102 | export const GA4React = GA4ReactManager;
103 |  
104 | export const GA4R = GA4RComponents;
105 |  
106 | export const useGA4React = useGA4ReactHook;
107 |  
108 | export const withTracker = withTrackerFunction;
109 |  
110 | export default GA4ReactManager;
111 |  
112 | 113 |
114 |
115 | 120 | 121 | 122 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/lib/ga4manager.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/lib/ga4manager.tsx 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/lib ga4manager.tsx

23 |
24 | 25 |
26 | 88.89% 27 | Statements 28 | 64/72 29 |
30 | 31 | 32 |
33 | 80.77% 34 | Branches 35 | 21/26 36 |
37 | 38 | 39 |
40 | 75% 41 | Functions 42 | 12/16 43 |
44 | 45 | 46 |
47 | 89.71% 48 | Lines 49 | 61/68 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 |
58 |
59 |

 60 | 
1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | 9 69 | 10 70 | 11 71 | 12 72 | 13 73 | 14 74 | 15 75 | 16 76 | 17 77 | 18 78 | 19 79 | 20 80 | 21 81 | 22 82 | 23 83 | 24 84 | 25 85 | 26 86 | 27 87 | 28 88 | 29 89 | 30 90 | 31 91 | 32 92 | 33 93 | 34 94 | 35 95 | 36 96 | 37 97 | 38 98 | 39 99 | 40 100 | 41 101 | 42 102 | 43 103 | 44 104 | 45 105 | 46 106 | 47 107 | 48 108 | 49 109 | 50 110 | 51 111 | 52 112 | 53 113 | 54 114 | 55 115 | 56 116 | 57 117 | 58 118 | 59 119 | 60 120 | 61 121 | 62 122 | 63 123 | 64 124 | 65 125 | 66 126 | 67 127 | 68 128 | 69 129 | 70 130 | 71 131 | 72 132 | 73 133 | 74 134 | 75 135 | 76 136 | 77 137 | 78 138 | 79 139 | 80 140 | 81 141 | 82 142 | 83 143 | 84 144 | 85 145 | 86 146 | 87 147 | 88 148 | 89 149 | 90 150 | 91 151 | 92 152 | 93 153 | 94 154 | 95 155 | 96 156 | 97 157 | 98 158 | 99 159 | 100 160 | 101 161 | 102 162 | 103 163 | 104 164 | 105 165 | 106 166 | 107 167 | 108 168 | 109 169 | 110 170 | 111 171 | 112 172 | 113 173 | 114 174 | 115 175 | 116 176 | 117 177 | 118 178 | 119 179 | 120 180 | 121 181 | 122 182 | 123 183 | 124 184 | 125 185 | 126 186 | 127 187 | 128 188 | 129 189 | 130 190 | 131 191 | 132 192 | 133 193 | 134 194 | 135 195 | 136 196 | 137 197 | 138 198 | 139 199 | 140 200 | 141 201 | 142 202 | 143 203 | 144 204 | 145 205 | 146 206 | 147 207 | 148 208 | 149 209 | 150 210 | 151 211 | 152 212 | 153 213 | 154 214 | 155 215 | 156 216 | 157 217 | 158 218 | 159 219 | 160 220 | 161 221 | 162 222 | 163 223 | 164 224 | 165 225 | 166 226 | 167 227 | 168 228 | 169 229 | 170 230 | 171 231 | 172 232 | 173 233 | 174 234 | 175 235 | 176 236 | 177 237 | 178 238 | 179 239 | 180 240 | 181 241 | 182 242 | 183 243 | 184 244 | 185 245 | 186 246 | 187 247 | 188 248 | 189 249 | 190 250 | 191 251 | 192 252 | 193 253 | 194 254 | 195 255 | 196 256 | 197 257 | 198 258 | 199 259 | 200 260 | 201 261 | 202 262 | 203 263 | 204 264 | 205 265 | 206 266 | 207 267 | 208 268 | 209 269 | 210 270 | 211 271 | 212 272 | 213  273 |   274 |   275 |   276 |   277 |   278 |   279 |   280 |   281 |   282 | 7x 283 |   284 |   285 |   286 |   287 |   288 |   289 |   290 |   291 |   292 |   293 |   294 |   295 |   296 | 7x 297 | 7x 298 |   299 | 7x 300 | 7x 301 | 7x 302 | 7x 303 |   304 | 7x 305 | 7x 306 | 7x 307 | 7x 308 |   309 |   310 |   311 |   312 |   313 | 7x 314 | 11x 315 |   316 |   317 |   318 |   319 |   320 |   321 |   322 |   323 |   324 |   325 | 7x 326 | 7x 327 | 7x 328 |   329 |   330 |   331 | 7x 332 | 7x 333 | 7x 334 | 7x 335 |   336 |   337 |   338 | 7x 339 | 11x 340 |   341 |   342 | 11x 343 | 4x 344 |   345 |   346 | 11x 347 |   348 | 11x 349 |   350 | 11x 351 |   352 |   353 |   354 |   355 | 11x 356 | 1x 357 |   358 |   359 |   360 |   361 |   362 |   363 | 11x 364 |   365 | 11x 366 |   367 | 11x 368 |   369 | 11x 370 |   371 |   372 |   373 | 11x 374 |   375 | 11x 376 |   377 |   378 | 7x 379 |   380 |   381 |   382 | 7x 383 | 7x 384 |   385 |   386 | 7x 387 | 7x 388 | 7x 389 |   390 |   391 |   392 |   393 | 7x 394 |   395 |   396 |   397 | 7x 398 |   399 | 7x 400 | 3x 401 |   402 |   403 |   404 |   405 |   406 |   407 |   408 |   409 | 7x 410 |   411 |   412 |   413 |   414 | 3x 415 |   416 |   417 |   418 |   419 |   420 |   421 |   422 |   423 |   424 |   425 |   426 |   427 |   428 | 7x 429 |   430 |   431 |   432 |   433 |   434 |   435 |   436 |   437 |   438 |   439 |   440 |   441 |   442 |   443 |   444 |   445 | 7x 446 |   447 |   448 |   449 |   450 |   451 |   452 |   453 |   454 | 15x 455 |   456 | 3x 457 |   458 |   459 |   460 |   461 |   462 | 7x 463 | 29x 464 |   465 | 8x 466 |   467 | 21x 468 |   469 |   470 |   471 |   472 |   473 |   474 | 7x 475 | 5x 476 | 4x 477 |   478 | 1x 479 |   480 |   481 | 7x 482 |   483 | 7x 484 |  
import {
485 |   ga4Config,
486 |   GA4ReactInterface,
487 |   GA4ReactResolveInterface,
488 |   gtagAction,
489 |   gtagCategory,
490 |   gtagFunction,
491 |   gtagLabel,
492 | } from './gtagModels';
493 |  
494 | export const GA4ReactGlobalIndex = '__ga4React__';
495 |  
496 | declare global {
497 |   interface Window {
498 |     gtag: gtagFunction | Function;
499 |     ga?: Function;
500 |     __ga4React__: GA4ReactResolveInterface;
501 |   }
502 | }
503 |  
504 | /**
505 |  * @desc class required to manage google analitycs 4
506 |  * @class GA4React
507 |  *  */
508 | export class GA4React implements GA4ReactInterface {
509 |   private scriptSyncId: string = 'ga4ReactScriptSync';
510 |   constructor(
511 |     private gaCode: string,
512 |     private config?: ga4Config | object,
513 |     private additionalGaCode?: Array<string>,
514 |     private timeout?: number
515 |   ) {
516 |     this.config = config || {};
517 |     this.gaCode = gaCode;
518 |     this.timeout = timeout || 5000;
519 |     this.additionalGaCode = additionalGaCode;
520 |   }
521 |  
522 |   /**
523 |    * @desc output on resolve initialization
524 |    */
525 |   private outputOnResolve(): GA4ReactResolveInterface {
526 |     return {
527 |       pageview: this.pageview,
528 |       event: this.event,
529 |       gtag: this.gtag,
530 |     };
531 |   }
532 |  
533 |   /**
534 |    * @desc Return main function for send ga4 events, pageview etc
535 |    * @returns {Promise<GA4ReactResolveInterface>}
536 |    */
537 |   public initialize(): Promise<GA4ReactResolveInterface> {
538 |     return new Promise((resolve, reject) => {
539 |       Iif (GA4React.isInitialized()) {
540 |         reject(new Error('GA4React is being initialized'));
541 |       }
542 |  
543 |       const head: HTMLHeadElement = document.getElementsByTagName('head')[0];
544 |       const scriptAsync: HTMLScriptElement = document.createElement('script');
545 |       scriptAsync.setAttribute('async', '');
546 |       scriptAsync.setAttribute(
547 |         'src',
548 |         `https://www.googletagmanager.com/gtag/js?id=${this.gaCode}`
549 |       );
550 |       scriptAsync.onload = () => {
551 |         const target: HTMLElement | null = document.getElementById(
552 |           this.scriptSyncId
553 |         );
554 |         if (target) {
555 |           target.remove();
556 |         }
557 |  
558 |         const scriptSync: HTMLScriptElement = document.createElement('script');
559 |  
560 |         scriptSync.setAttribute('id', this.scriptSyncId);
561 |  
562 |         let scriptHTML: string = `window.dataLayer = window.dataLayer || [];
563 |         function gtag(){dataLayer.push(arguments);};
564 |         gtag('js', new Date());
565 |         gtag('config', '${this.gaCode}', ${JSON.stringify(this.config)});`;
566 |  
567 |         if (this.additionalGaCode) {
568 |           this.additionalGaCode.forEach((code: string) => {
569 |             scriptHTML += `gtag('config', '${code}', ${JSON.stringify(
570 |               this.config
571 |             )});`;
572 |           });
573 |         }
574 |  
575 |         scriptSync.innerHTML = scriptHTML;
576 |  
577 |         head.appendChild(scriptSync);
578 |  
579 |         const resolved: GA4ReactResolveInterface = this.outputOnResolve();
580 |  
581 |         Iif (window.ga) {
582 |           Object.assign(resolved, { ga: this.ga });
583 |         }
584 |  
585 |         Object.assign(window, { [GA4ReactGlobalIndex]: resolved });
586 |  
587 |         resolve(resolved);
588 |       };
589 |  
590 |       scriptAsync.onerror = () => {
591 |         reject(new Error('GA4React initialization failed'));
592 |       };
593 |  
594 |       const onChangeReadyState = () => {
595 |         switch (document.readyState) {
596 |           case 'interactive':
597 |           case 'complete':
598 |             Eif (!GA4React.isInitialized()) {
599 |               head.appendChild(scriptAsync);
600 |               document.removeEventListener(
601 |                 'readystatechange',
602 |                 onChangeReadyState
603 |               );
604 |             }
605 |             break;
606 |         }
607 |       };
608 |  
609 |       document.addEventListener('readystatechange', onChangeReadyState);
610 |  
611 |       setTimeout(() => {
612 |         reject(new Error('GA4React Timeout'));
613 |       }, this.timeout);
614 |     });
615 |   }
616 |  
617 |   /**
618 |    * @desc send pageview event to gtag
619 |    * @param path
620 |    */
621 |   public pageview(
622 |     path: string | Location,
623 |     location?: string | Location,
624 |     title?: string
625 |   ): any {
626 |     return this.gtag('event', 'page_view', {
627 |       page_path: path,
628 |       page_location: location || window.location,
629 |       page_title: title || document.title,
630 |     });
631 |   }
632 |  
633 |   /**
634 |    * @desc set event and send to gtag
635 |    * @param action
636 |    * @param label
637 |    * @param category
638 |    * @param nonInteraction
639 |    */
640 |   public event(
641 |     action: gtagAction,
642 |     label: gtagLabel,
643 |     category: gtagCategory,
644 |     nonInteraction: boolean = false
645 |   ): any {
646 |     return this.gtag('event', action, {
647 |       event_label: label,
648 |       event_category: category,
649 |       non_interaction: nonInteraction,
650 |     });
651 |   }
652 |  
653 |   /**
654 |    * @desc direct access to ga
655 |    * @param args
656 |    */
657 |   public ga(...args: any): any {
658 |     //@ts-ignore
659 |     return window.ga(...args);
660 |   }
661 |  
662 |   /**
663 |    * @desc direct access to gtag
664 |    * @param args
665 |    */
666 |   public gtag(...args: any): any {
667 |     //@ts-ignore
668 |     return window.gtag(...args);
669 |   }
670 |  
671 |   /**
672 |    * @desc ga is initialized?
673 |    */
674 |   static isInitialized(): boolean {
675 |     switch (typeof window[GA4ReactGlobalIndex] !== 'undefined') {
676 |       case true:
677 |         return true;
678 |       default:
679 |         return false;
680 |     }
681 |   }
682 |  
683 |   /**
684 |    * @desc get ga4react from global
685 |    */
686 |   static getGA4React(): GA4ReactResolveInterface | void {
687 |     if (GA4React.isInitialized()) {
688 |       return window[GA4ReactGlobalIndex];
689 |     } else {
690 |       console.error(new Error('GA4React is not initialized'));
691 |     }
692 |   }
693 | }
694 |  
695 | export default GA4React;
696 |  
697 | 698 |
699 |
700 | 705 | 706 | 707 | 712 | 713 | 714 | 715 | 716 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/lib/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/lib 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src/lib

23 |
24 | 25 |
26 | 88.89% 27 | Statements 28 | 64/72 29 |
30 | 31 | 32 |
33 | 80.77% 34 | Branches 35 | 21/26 36 |
37 | 38 | 39 |
40 | 75% 41 | Functions 42 | 12/16 43 |
44 | 45 | 46 |
47 | 89.71% 48 | Lines 49 | 61/68 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 |
58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
FileStatementsBranchesFunctionsLines
ga4manager.tsx 78 |
79 |
88.89%64/7280.77%21/2675%12/1689.71%61/68
92 |
93 |
94 |
95 | 100 | 101 | 102 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /coverage/lcov.info: -------------------------------------------------------------------------------- 1 | TN: 2 | SF:src/index.tsx 3 | FNF:0 4 | FNH:0 5 | DA:1,7 6 | DA:3,7 7 | DA:5,7 8 | DA:7,7 9 | DA:9,7 10 | DA:11,7 11 | DA:13,7 12 | DA:15,7 13 | DA:17,7 14 | LF:9 15 | LH:9 16 | BRF:0 17 | BRH:0 18 | end_of_record 19 | TN: 20 | SF:src/components/GA4RComponents.tsx 21 | FN:18,(anonymous_0) 22 | FN:24,(anonymous_1) 23 | FN:43,(anonymous_2) 24 | FN:52,(anonymous_3) 25 | FN:61,(anonymous_4) 26 | FN:64,(anonymous_5) 27 | FNF:6 28 | FNH:6 29 | FNDA:3,(anonymous_0) 30 | FNDA:3,(anonymous_1) 31 | FNDA:8,(anonymous_2) 32 | FNDA:4,(anonymous_3) 33 | FNDA:1,(anonymous_4) 34 | FNDA:1,(anonymous_5) 35 | DA:1,7 36 | DA:2,7 37 | DA:18,7 38 | DA:23,3 39 | DA:25,3 40 | DA:26,1 41 | DA:30,2 42 | DA:31,1 43 | DA:37,1 44 | DA:43,7 45 | DA:44,8 46 | DA:45,7 47 | DA:46,7 48 | DA:47,7 49 | DA:48,7 50 | DA:50,7 51 | DA:52,7 52 | DA:53,4 53 | DA:54,2 54 | DA:60,2 55 | DA:62,1 56 | DA:65,1 57 | DA:69,2 58 | DA:70,2 59 | DA:71,2 60 | DA:76,7 61 | DA:79,7 62 | LF:27 63 | LH:27 64 | BRDA:25,0,0,1 65 | BRDA:25,0,1,2 66 | BRDA:30,1,0,1 67 | BRDA:30,1,1,1 68 | BRDA:30,2,0,2 69 | BRDA:30,2,1,2 70 | BRDA:53,3,0,2 71 | BRDA:53,3,1,2 72 | BRDA:70,4,0,2 73 | BRDA:70,4,1,0 74 | BRF:10 75 | BRH:9 76 | end_of_record 77 | TN: 78 | SF:src/components/withTracker.tsx 79 | FN:15,withTracker 80 | FN:18,(anonymous_1) 81 | FN:28,(anonymous_2) 82 | FN:45,(anonymous_3) 83 | FN:48,(anonymous_4) 84 | FNF:5 85 | FNH:4 86 | FNDA:2,withTracker 87 | FNDA:3,(anonymous_1) 88 | FNDA:3,(anonymous_2) 89 | FNDA:2,(anonymous_3) 90 | FNDA:0,(anonymous_4) 91 | DA:1,7 92 | DA:2,7 93 | DA:15,7 94 | DA:18,2 95 | DA:27,21 96 | DA:28,3 97 | DA:29,3 98 | DA:31,1 99 | DA:32,1 100 | DA:33,1 101 | DA:35,1 102 | DA:38,2 103 | DA:44,2 104 | DA:46,2 105 | DA:49,0 106 | DA:52,2 107 | DA:55,3 108 | DA:59,7 109 | LF:18 110 | LH:17 111 | BRDA:30,0,0,1 112 | BRDA:30,0,1,0 113 | BRDA:30,0,2,2 114 | BRDA:32,1,0,1 115 | BRDA:32,1,1,0 116 | BRF:5 117 | BRH:3 118 | end_of_record 119 | TN: 120 | SF:src/hooks/useGA4React.tsx 121 | FN:5,(anonymous_0) 122 | FN:12,(anonymous_1) 123 | FN:23,(anonymous_2) 124 | FN:26,(anonymous_3) 125 | FNF:4 126 | FNH:4 127 | FNDA:7,(anonymous_0) 128 | FNDA:4,(anonymous_1) 129 | FNDA:1,(anonymous_2) 130 | FNDA:1,(anonymous_3) 131 | DA:1,7 132 | DA:2,7 133 | DA:5,7 134 | DA:11,7 135 | DA:12,6 136 | DA:13,4 137 | DA:14,3 138 | DA:16,2 139 | DA:22,2 140 | DA:24,1 141 | DA:27,1 142 | DA:30,2 143 | DA:32,1 144 | DA:33,1 145 | DA:36,1 146 | DA:39,6 147 | LF:16 148 | LH:16 149 | BRDA:13,0,0,3 150 | BRDA:13,0,1,1 151 | BRDA:15,1,0,2 152 | BRDA:15,1,1,1 153 | BRF:4 154 | BRH:4 155 | end_of_record 156 | TN: 157 | SF:src/lib/ga4manager.tsx 158 | FN:25,(anonymous_0) 159 | FN:27,GA4React 160 | FN:42,(anonymous_2) 161 | FN:54,(anonymous_3) 162 | FN:55,(anonymous_4) 163 | FN:67,(anonymous_5) 164 | FN:85,(anonymous_6) 165 | FN:107,(anonymous_7) 166 | FN:111,(anonymous_8) 167 | FN:128,(anonymous_9) 168 | FN:138,(anonymous_10) 169 | FN:157,(anonymous_11) 170 | FN:174,(anonymous_12) 171 | FN:183,(anonymous_13) 172 | FN:191,(anonymous_14) 173 | FN:203,(anonymous_15) 174 | FNF:16 175 | FNH:12 176 | FNDA:7,(anonymous_0) 177 | FNDA:7,GA4React 178 | FNDA:11,(anonymous_2) 179 | FNDA:7,(anonymous_3) 180 | FNDA:7,(anonymous_4) 181 | FNDA:11,(anonymous_5) 182 | FNDA:0,(anonymous_6) 183 | FNDA:0,(anonymous_7) 184 | FNDA:7,(anonymous_8) 185 | FNDA:3,(anonymous_9) 186 | FNDA:3,(anonymous_10) 187 | FNDA:0,(anonymous_11) 188 | FNDA:0,(anonymous_12) 189 | FNDA:3,(anonymous_13) 190 | FNDA:29,(anonymous_14) 191 | FNDA:5,(anonymous_15) 192 | DA:11,7 193 | DA:25,7 194 | DA:26,7 195 | DA:28,7 196 | DA:29,7 197 | DA:30,7 198 | DA:31,7 199 | DA:33,7 200 | DA:34,7 201 | DA:35,7 202 | DA:36,7 203 | DA:42,7 204 | DA:43,11 205 | DA:54,7 206 | DA:55,7 207 | DA:56,7 208 | DA:57,0 209 | DA:60,7 210 | DA:61,7 211 | DA:62,7 212 | DA:63,7 213 | DA:67,7 214 | DA:68,11 215 | DA:71,11 216 | DA:72,4 217 | DA:75,11 218 | DA:77,11 219 | DA:79,11 220 | DA:84,11 221 | DA:85,1 222 | DA:86,0 223 | DA:92,11 224 | DA:94,11 225 | DA:96,11 226 | DA:98,11 227 | DA:99,0 228 | DA:102,11 229 | DA:104,11 230 | DA:107,7 231 | DA:108,0 232 | DA:111,7 233 | DA:112,7 234 | DA:115,7 235 | DA:116,7 236 | DA:117,7 237 | DA:122,7 238 | DA:126,7 239 | DA:128,7 240 | DA:129,3 241 | DA:138,7 242 | DA:143,3 243 | DA:157,7 244 | DA:161,0 245 | DA:163,0 246 | DA:174,7 247 | DA:176,0 248 | DA:183,15 249 | DA:185,3 250 | DA:191,7 251 | DA:192,29 252 | DA:194,8 253 | DA:196,21 254 | DA:203,7 255 | DA:204,5 256 | DA:205,4 257 | DA:207,1 258 | DA:210,7 259 | DA:212,7 260 | LF:68 261 | LH:61 262 | BRDA:33,0,0,7 263 | BRDA:33,0,1,6 264 | BRDA:35,1,0,7 265 | BRDA:35,1,1,5 266 | BRDA:56,2,0,0 267 | BRDA:56,2,1,7 268 | BRDA:71,3,0,4 269 | BRDA:71,3,1,7 270 | BRDA:84,4,0,1 271 | BRDA:84,4,1,10 272 | BRDA:98,5,0,0 273 | BRDA:98,5,1,11 274 | BRDA:113,6,0,7 275 | BRDA:113,6,1,7 276 | BRDA:115,7,0,7 277 | BRDA:115,7,1,0 278 | BRDA:145,8,0,3 279 | BRDA:145,8,1,3 280 | BRDA:146,9,0,3 281 | BRDA:146,9,1,3 282 | BRDA:161,10,0,0 283 | BRDA:161,10,1,0 284 | BRDA:193,11,0,8 285 | BRDA:193,11,1,21 286 | BRDA:204,12,0,4 287 | BRDA:204,12,1,1 288 | BRF:26 289 | BRH:21 290 | end_of_record 291 | -------------------------------------------------------------------------------- /example/GA4R-Component/GA4R-Component.tsx: -------------------------------------------------------------------------------- 1 | import GA4R from 'ga-4-react/dist/components/GA4RComponents'; 2 | import { GA4ReactResolveInterface } from 'ga-4-react/dist/lib/gtagModels'; 3 | import React, { useEffect } from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | export const ExampleChild: React.FC<{ 7 | ga4?: GA4ReactResolveInterface; 8 | children?: any; 9 | }> = ({ ga4, children }) => { 10 | useEffect(() => { 11 | console.log('ga4', ga4); 12 | }, []); 13 | 14 | return
{children}
; 15 | }; 16 | 17 | export const ExampleContainer: React.FC = () => { 18 | return ( 19 |
20 | 21 | Valid element to inject GA4 22 |
is not possibile to inject GA4 in html element
23 |
24 |
25 | ); 26 | }; 27 | 28 | ReactDOM.render( 29 | 30 | 31 | , 32 | document.getElementById('root') 33 | ); -------------------------------------------------------------------------------- /example/withTracker/React-Router-v4-withTracker.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | 3 | /** 4 | * Originally from ReactGA Community Wiki Page https://github.com/react-ga/react-ga/wiki/React-Router-v4-withTracker 5 | * (Implemented for ga-4-react by Cristian Custodio, review and integrated by Manuel Trebbi) 6 | */ 7 | 8 | import GA4React from 'ga-4-react'; 9 | import { GA4ReactResolveInterface } from 'ga-4-react/dist/lib/gtagModels'; 10 | 11 | import ReactDOM from 'react-dom'; 12 | 13 | export const Example: React.FC = withTracker((props: any) => { 14 | return <>Example component; 15 | }); 16 | 17 | export interface GA4WithTrackerComponentInterface { 18 | path: string | Location; 19 | location?: string | Location; 20 | title?: string; 21 | gaCode?: string; 22 | } 23 | 24 | export default function withTracker( 25 | MyComponent: React.FC 26 | ): React.FC { 27 | return (props: GA4WithTrackerComponentInterface & any) => { 28 | const { path, location, title, gaCode } = props; 29 | useEffect(() => { 30 | switch (GA4React.isInitialized()) { 31 | case true: 32 | const ga4 = GA4React.getGA4React(); 33 | if (ga4) { 34 | ga4.pageview(path, location, title); 35 | } 36 | break; 37 | default: 38 | case false: 39 | const ga4react = new GA4React(gaCode); 40 | ga4react.initialize().then( 41 | (ga4: GA4ReactResolveInterface) => { 42 | ga4.pageview(path, location, title); 43 | }, 44 | (err: Error) => { 45 | console.error(err); 46 | } 47 | ); 48 | break; 49 | } 50 | }); 51 | return ; 52 | }; 53 | } 54 | 55 | ReactDOM.render( 56 | 57 | 61 | , 62 | document.getElementById('root') 63 | ); 64 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "testTimeout": 10000, 3 | testEnvironment: 'jsdom', 4 | testMatch: ['/test/*(*.)@(test).[tj]s?(x)'], 5 | testPathIgnorePatterns: [ 6 | '/node_modules/', // default 7 | ], 8 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.281", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "engines": { 11 | "node": ">=10" 12 | }, 13 | "scripts": { 14 | "start": "tsdx watch", 15 | "build": "tsdx build", 16 | "test": "tsdx test", 17 | "lint": "tsdx lint", 18 | "prepare": "tsdx build", 19 | "size": "size-limit", 20 | "analyze": "size-limit --why", 21 | "tsc-check": "./node_modules/typescript/bin/tsc -p ./tsconfig.json" 22 | }, 23 | "peerDependencies": {}, 24 | "husky": { 25 | "hooks": { 26 | "pre-commit": "npm run lint && npm run tsc-check" 27 | } 28 | }, 29 | "prettier": { 30 | "printWidth": 80, 31 | "semi": true, 32 | "singleQuote": true, 33 | "trailingComma": "es5" 34 | }, 35 | "name": "ga-4-react", 36 | "author": "manueltrebbi", 37 | "repository": "https://github.com/unrealmanu/ga-4-react", 38 | "module": "dist/ga-4-react.esm.js", 39 | "size-limit": [ 40 | { 41 | "path": "dist/ga-4-react.cjs.production.min.js", 42 | "limit": "10 KB" 43 | }, 44 | { 45 | "path": "dist/ga-4-react.esm.js", 46 | "limit": "10 KB" 47 | } 48 | ], 49 | "devDependencies": { 50 | "@size-limit/preset-small-lib": "^4.9.0", 51 | "@testing-library/jest-dom": "^5.11.8", 52 | "@testing-library/react": "^11.2.2", 53 | "@testing-library/user-event": "^12.2.2", 54 | "@types/jest": "^26.0.15", 55 | "@types/jsdom": "^16.2.5", 56 | "@types/node": "^14.14.10", 57 | "@types/react": "^17.0.0", 58 | "@types/react-dom": "^17.0.0", 59 | "@types/react-test-renderer": "^17.0.0", 60 | "husky": "^4.3.0", 61 | "jsdom": "^16.4.0", 62 | "react": "^17.0.1", 63 | "react-dom": "^17.0.1", 64 | "react-test-renderer": "^16.8.6", 65 | "size-limit": "^4.9.0", 66 | "ts-jest": "^26.4.4", 67 | "tsdx": "^0.14.1", 68 | "tslib": "^2.0.3", 69 | "typescript": "^4.1.2" 70 | }, 71 | "keywords": [ 72 | "React", 73 | "Google", 74 | "Analytics", 75 | "GA", 76 | "Google Analytics" 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /src/components/GA4RComponents.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import GA4React from './../lib/ga4manager'; 3 | import { 4 | GA4Config, 5 | GA4ManagerOptionsInterface, 6 | GA4ReactResolveInterface, 7 | } from '../models/gtagModels'; 8 | 9 | export interface IGA4R { 10 | code: string; 11 | timeout?: number; 12 | config?: GA4Config; 13 | additionalCode?: Array; 14 | children?: any; 15 | options?: GA4ManagerOptionsInterface; 16 | } 17 | 18 | const outputGA4 = ( 19 | children: any, 20 | setComponents: Function, 21 | ga4: GA4ReactResolveInterface 22 | ) => { 23 | setComponents( 24 | React.Children.map(children, (child: React.ReactChildren, index) => { 25 | if (!React.isValidElement(child)) { 26 | return {child}; 27 | } 28 | 29 | //@ts-ignore 30 | if (child.type && typeof child.type.name !== 'undefined') { 31 | return React.cloneElement(child, { 32 | //@ts-ignore 33 | ga4: ga4, 34 | index, 35 | }); 36 | } else { 37 | return child; 38 | } 39 | }) 40 | ); 41 | }; 42 | 43 | export const GA4R: React.FC = ({ 44 | code, 45 | timeout, 46 | config, 47 | additionalCode, 48 | children, 49 | options, 50 | }) => { 51 | const [components, setComponents] = useState(null); 52 | 53 | useEffect(() => { 54 | if (!GA4React.isInitialized()) { 55 | const ga4manager = new GA4React( 56 | `${code}`, 57 | config, 58 | additionalCode, 59 | timeout, 60 | options 61 | ); 62 | ga4manager.initialize().then( 63 | (ga4: GA4ReactResolveInterface) => { 64 | outputGA4(children, setComponents, ga4); 65 | }, 66 | err => { 67 | console.error(err); 68 | } 69 | ); 70 | } else { 71 | const ga4 = GA4React.getGA4React(); 72 | if (ga4) { 73 | outputGA4(children, setComponents, ga4); 74 | } 75 | } 76 | }, []); 77 | 78 | return <>{components}; 79 | }; 80 | 81 | export default GA4R; 82 | -------------------------------------------------------------------------------- /src/components/withTracker.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import GA4React from '../lib/ga4manager'; 3 | 4 | import { 5 | GA4Config, 6 | GA4ManagerOptionsInterface, 7 | GA4ReactResolveInterface, 8 | } from '../models/gtagModels'; 9 | 10 | export interface GA4WithTrackerComponentInterface { 11 | path: string | Location; 12 | location?: string | Location; 13 | title?: string; 14 | gaCode?: string; 15 | gaConfig?: GA4Config | object; 16 | additionalCode?: Array; 17 | timeout?: number; 18 | options?: GA4ManagerOptionsInterface; 19 | } 20 | 21 | export function withTracker( 22 | MyComponent: React.FC 23 | ): React.FC { 24 | return (props: GA4WithTrackerComponentInterface & any) => { 25 | const { 26 | path, 27 | location, 28 | title, 29 | gaCode, 30 | gaTimeout, 31 | gaConfig, 32 | gaAdditionalCode, 33 | options, 34 | } = props; 35 | useEffect(() => { 36 | switch (GA4React.isInitialized()) { 37 | case true: 38 | const ga4 = GA4React.getGA4React(); 39 | if (ga4) { 40 | ga4.pageview(path, location, title); 41 | } 42 | break; 43 | default: 44 | case false: 45 | const ga4react = new GA4React( 46 | `${gaCode}`, 47 | gaConfig, 48 | gaAdditionalCode, 49 | gaTimeout, 50 | options 51 | ); 52 | ga4react.initialize().then( 53 | (ga4: GA4ReactResolveInterface) => { 54 | ga4.pageview(path, location, title); 55 | }, 56 | (err: Error) => { 57 | console.error(err); 58 | } 59 | ); 60 | break; 61 | } 62 | }); 63 | return ; 64 | }; 65 | } 66 | 67 | export default withTracker; 68 | -------------------------------------------------------------------------------- /src/hooks/useGA4React.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import GA4React from '../lib/ga4manager'; 3 | 4 | import { 5 | GA4Config, 6 | GA4ManagerOptionsInterface, 7 | GA4ReactResolveInterface, 8 | } from '../models/gtagModels'; 9 | 10 | export const useGA4React = ( 11 | gaCode?: string, 12 | gaConfig?: GA4Config | object, 13 | gaAdditionalCode?: Array, 14 | gaTimeout?: number, 15 | options?: GA4ManagerOptionsInterface 16 | ): GA4ReactResolveInterface | void => { 17 | const [ga4, setGA4] = useState(undefined); 18 | useEffect(() => { 19 | if (gaCode) { 20 | switch (GA4React.isInitialized()) { 21 | case false: 22 | const ga4react = new GA4React( 23 | `${gaCode}`, 24 | gaConfig, 25 | gaAdditionalCode, 26 | gaTimeout, 27 | options 28 | ); 29 | ga4react.initialize().then( 30 | (ga4: GA4ReactResolveInterface) => { 31 | setGA4(ga4); 32 | }, 33 | (err: Error) => { 34 | console.error(err); 35 | } 36 | ); 37 | break; 38 | case true: 39 | setGA4(GA4React.getGA4React()); 40 | break; 41 | } 42 | } else { 43 | setGA4(GA4React.getGA4React()); 44 | } 45 | }, [gaCode]); 46 | return ga4; 47 | }; 48 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { GA4React } from './lib/ga4manager'; 2 | 3 | import { GA4R } from './components/GA4RComponents'; 4 | 5 | import { useGA4React } from './hooks/useGA4React'; 6 | 7 | import { withTracker } from './components/withTracker'; 8 | 9 | export { GA4React, GA4R, useGA4React, withTracker }; 10 | 11 | export default GA4React; 12 | -------------------------------------------------------------------------------- /src/lib/ga4manager.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | GA4Config, 3 | GA4ManagerOptionsInterface, 4 | GA4ReactInterface, 5 | GA4ReactResolveInterface, 6 | gtagAction, 7 | gtagCategory, 8 | gtagFunction, 9 | gtagLabel, 10 | } from '../models/gtagModels'; 11 | 12 | export const GA4ReactGlobalIndex = '__ga4React__'; 13 | 14 | declare global { 15 | interface Window { 16 | gtag: gtagFunction | Function; 17 | __ga4React__: GA4ReactResolveInterface; 18 | } 19 | } 20 | 21 | /** 22 | * @desc class required to manage google analitycs 4 23 | * @class GA4React 24 | * */ 25 | export class GA4React implements GA4ReactInterface { 26 | private scriptSyncId: string = 'ga4ReactScriptSync'; 27 | private scriptAsyncId: string = 'ga4ReactScriptAsync'; 28 | private nonceAsync: string = ''; 29 | private nonceSync: string = ''; 30 | constructor( 31 | private gaCode: string, 32 | private gaConfig?: GA4Config, 33 | private additionalGaCode?: Array, 34 | private timeout?: number, 35 | private options?: GA4ManagerOptionsInterface 36 | ) { 37 | this.gaConfig = gaConfig ? gaConfig : {}; 38 | this.gaCode = gaCode; 39 | this.timeout = timeout || 5000; 40 | this.additionalGaCode = additionalGaCode; 41 | this.options = options; 42 | 43 | if (this.options) { 44 | const { nonce } = this.options; 45 | this.nonceAsync = nonce && nonce[0] ? nonce[0] : ''; 46 | this.nonceSync = nonce && nonce[1] ? nonce[1] : ''; 47 | } 48 | } 49 | 50 | /** 51 | * @desc output on resolve initialization 52 | */ 53 | private outputOnResolve(): GA4ReactResolveInterface { 54 | return { 55 | pageview: this.pageview, 56 | event: this.event, 57 | gtag: this.gtag, 58 | }; 59 | } 60 | 61 | /** 62 | * @desc Return main function for send ga4 events, pageview etc 63 | * @returns {Promise} 64 | */ 65 | public initialize(): Promise { 66 | return new Promise((resolve, reject) => { 67 | if (GA4React.isInitialized()) { 68 | reject(new Error('GA4React is being initialized')); 69 | } 70 | 71 | // in case of retry logics, remove previous scripts 72 | const previousScriptAsync = document.getElementById(this.scriptAsyncId); 73 | if (previousScriptAsync) { 74 | previousScriptAsync.remove(); 75 | } 76 | 77 | const head: HTMLHeadElement = document.getElementsByTagName('head')[0]; 78 | const scriptAsync: HTMLScriptElement = document.createElement('script'); 79 | scriptAsync.setAttribute('id', this.scriptAsyncId); 80 | scriptAsync.setAttribute('async', ''); 81 | 82 | if ( 83 | this.nonceAsync && 84 | typeof this.nonceAsync === 'string' && 85 | this.nonceAsync.length > 0 86 | ) { 87 | scriptAsync.setAttribute('nonce', this.nonceAsync); 88 | } 89 | 90 | scriptAsync.setAttribute( 91 | 'src', 92 | `https://www.googletagmanager.com/gtag/js?id=${this.gaCode}` 93 | ); 94 | scriptAsync.onload = () => { 95 | const target: HTMLElement | null = document.getElementById( 96 | this.scriptSyncId 97 | ); 98 | if (target) { 99 | target.remove(); 100 | } 101 | 102 | // in case of retry logics, remove previous script sync 103 | const previousScriptSync = document.getElementById(this.scriptSyncId); 104 | if (previousScriptSync) { 105 | previousScriptSync.remove(); 106 | } 107 | 108 | const scriptSync: HTMLScriptElement = document.createElement('script'); 109 | 110 | scriptSync.setAttribute('id', this.scriptSyncId); 111 | 112 | if ( 113 | this.nonceSync && 114 | typeof this.nonceSync === 'string' && 115 | this.nonceSync.length > 0 116 | ) { 117 | scriptSync.setAttribute('nonce', this.nonceSync); 118 | } 119 | 120 | let scriptHTML: string = `window.dataLayer = window.dataLayer || []; 121 | function gtag(){dataLayer.push(arguments);}; 122 | gtag('js', new Date()); 123 | gtag('config', '${this.gaCode}', ${JSON.stringify(this.gaConfig)});`; 124 | 125 | if (this.additionalGaCode) { 126 | this.additionalGaCode.forEach((code: string) => { 127 | scriptHTML += `\ngtag('config', '${code}', ${JSON.stringify( 128 | this.gaConfig 129 | )});`; 130 | }); 131 | } 132 | 133 | scriptSync.innerHTML = scriptHTML; 134 | 135 | head.appendChild(scriptSync); 136 | 137 | const resolved: GA4ReactResolveInterface = this.outputOnResolve(); 138 | 139 | Object.assign(window, { [GA4ReactGlobalIndex]: resolved }); 140 | 141 | resolve(resolved); 142 | }; 143 | 144 | scriptAsync.onerror = (event: Event | string): void => { 145 | if (typeof event === 'string') { 146 | reject(`GA4React intialization failed ${event}`); 147 | } else { 148 | const error = new Error(); 149 | error.name = 'GA4React intialization failed'; 150 | error.message = JSON.stringify(event, [ 151 | 'message', 152 | 'arguments', 153 | 'type', 154 | 'name', 155 | ]); 156 | reject(error); 157 | } 158 | }; 159 | 160 | const onChangeReadyState = () => { 161 | switch (document.readyState) { 162 | case 'interactive': 163 | case 'complete': 164 | if (!GA4React.isInitialized()) { 165 | head.appendChild(scriptAsync); 166 | document.removeEventListener( 167 | 'readystatechange', 168 | onChangeReadyState 169 | ); 170 | } 171 | break; 172 | } 173 | }; 174 | 175 | if (document.readyState !== 'complete') { 176 | document.addEventListener('readystatechange', onChangeReadyState); 177 | } else { 178 | onChangeReadyState(); 179 | } 180 | 181 | setTimeout(() => { 182 | reject(new Error('GA4React Timeout')); 183 | }, this.timeout); 184 | }); 185 | } 186 | 187 | /** 188 | * @desc send pageview event to gtag 189 | * @param path 190 | */ 191 | public pageview( 192 | path: string | Location, 193 | location?: string | Location, 194 | title?: string 195 | ): any { 196 | return this.gtag('event', 'page_view', { 197 | page_path: path, 198 | page_location: location || window.location, 199 | page_title: title || document.title, 200 | }); 201 | } 202 | 203 | /** 204 | * @desc set event and send to gtag 205 | * @param action 206 | * @param label 207 | * @param category 208 | * @param nonInteraction 209 | */ 210 | public event( 211 | action: gtagAction, 212 | label: gtagLabel, 213 | category: gtagCategory, 214 | nonInteraction: boolean = false 215 | ): any { 216 | return this.gtag('event', action, { 217 | event_label: label, 218 | event_category: category, 219 | non_interaction: nonInteraction, 220 | }); 221 | } 222 | 223 | /** 224 | * @desc direct access to gtag 225 | * @param args 226 | */ 227 | public gtag(...args: any): any { 228 | //@ts-ignore 229 | return window.gtag(...args); 230 | } 231 | 232 | /** 233 | * @desc ga is initialized? 234 | */ 235 | static isInitialized(): boolean { 236 | switch (typeof window[GA4ReactGlobalIndex] !== 'undefined') { 237 | case true: 238 | return true; 239 | default: 240 | return false; 241 | } 242 | } 243 | 244 | /** 245 | * @desc get ga4react from global 246 | */ 247 | static getGA4React(): GA4ReactResolveInterface | void { 248 | if (GA4React.isInitialized()) { 249 | return window[GA4ReactGlobalIndex]; 250 | } else { 251 | console.error(new Error('GA4React is not initialized')); 252 | } 253 | } 254 | } 255 | 256 | export default GA4React; 257 | -------------------------------------------------------------------------------- /src/models/gtagModels.tsx: -------------------------------------------------------------------------------- 1 | import GA4React from '../lib/ga4manager'; 2 | 3 | export type gtagEvent = 'event' | string; 4 | export type gtagAction = 'page_view' | string; 5 | export type gtagCategory = 'ecommerce' | 'engagement' | string; 6 | export type gtagLabel = 'method' | 'search_term' | 'content_type' | string; 7 | 8 | /** 9 | * @desc event accepted params 10 | * @interface gtagEventConfig 11 | */ 12 | export interface gtagEventConfig { 13 | event_label?: gtagLabel | string; 14 | event_category?: gtagCategory | string; 15 | non_interaction?: boolean; 16 | } 17 | 18 | /** 19 | * @desc pageview accepted params 20 | * @interface gtagPageView 21 | */ 22 | export interface gtagPageView { 23 | page_title?: string; 24 | page_location?: Location | string; 25 | page_path?: string; 26 | send_to?: string; 27 | } 28 | 29 | /** 30 | * @desc type of window.gtag 31 | * @type 32 | */ 33 | export type gtagFunction = ( 34 | event: gtagEvent, 35 | action: gtagAction, 36 | data: gtagEventConfig | gtagPageView | object, 37 | nonInteraction?: boolean 38 | ) => {}; 39 | 40 | /** 41 | * @desc GA4React interface of main class 42 | * @interface GA4ReactInterface 43 | */ 44 | export interface GA4ReactInterface extends GA4ReactResolveInterface { 45 | initialize(): Promise; 46 | } 47 | 48 | export interface GA4ReactStaticInterface { 49 | new (): GA4React; 50 | isInitialized(): boolean; 51 | getGA4React(): GA4ReactResolveInterface | void; 52 | } 53 | 54 | /** 55 | * @desc on resolve GA4React initial promises, return function in this interface 56 | * @interface GA4ReactResolveInterface 57 | */ 58 | export interface GA4ReactResolveInterface { 59 | pageview( 60 | path: string | Location, 61 | location?: string | Location, 62 | title?: string 63 | ): any; 64 | event( 65 | action: gtagAction, 66 | label: gtagLabel, 67 | data: gtagCategory, 68 | nonInteraction?: boolean 69 | ): any; 70 | gtag(...args: any): any; 71 | ga?(...args: any): any; 72 | } 73 | 74 | /** 75 | * @desc default accepted config 76 | * @interface GA4Config 77 | */ 78 | export interface GA4Config { 79 | debug_mode?: boolean; 80 | send_page_view?: boolean; 81 | groups?: string; 82 | } 83 | 84 | /** 85 | * @desc options for confgure Ga4Manager during initialization 86 | * @interface GA4ManagerOptionsInterface 87 | */ 88 | export interface GA4ManagerOptionsInterface { 89 | nonce?: Array; 90 | } 91 | -------------------------------------------------------------------------------- /test/GA4RComponents.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | import { GA4R } from './../src/index'; 5 | 6 | Object.defineProperty(global.document, 'readyState', { 7 | get() { 8 | return 'interactive'; 9 | }, 10 | }); 11 | 12 | beforeAll(() => { 13 | global.document.head.innerHTML = ''; 14 | }); 15 | 16 | describe('GA4R Components', () => { 17 | it('Rendering with Children', async done => { 18 | const Test: React.FC = ({ ga4 }) => { 19 | return <>{JSON.stringify(ga4)}; 20 | }; 21 | 22 | const { container } = render( 23 | 27 | 28 |
Try
29 |
30 | ); 31 | 32 | setTimeout(() => { 33 | global.document.dispatchEvent(new Event('readystatechange')); 34 | }, 100); 35 | 36 | setTimeout(() => { 37 | const LoadEvent = document.createEvent('HTMLEvents'); 38 | LoadEvent.initEvent('load', true, true); 39 | const target = global.document.head.querySelector('script'); 40 | if (target) { 41 | target.dispatchEvent(LoadEvent); 42 | } 43 | }, 1000); 44 | 45 | setTimeout(() => { 46 | expect(container.innerHTML).toMatchSnapshot(); 47 | expect(global.document.head).toMatchSnapshot(); 48 | done(); 49 | }, 2000); 50 | }); 51 | 52 | it('Rendering with invalid Children', async done => { 53 | const { container } = render( 54 | im not valid children element 55 | ); 56 | 57 | setTimeout(() => { 58 | global.document.dispatchEvent(new Event('readystatechange')); 59 | }, 100); 60 | 61 | setTimeout(() => { 62 | const LoadEvent = document.createEvent('HTMLEvents'); 63 | LoadEvent.initEvent('load', true, true); 64 | const target = global.document.head.querySelector('script'); 65 | if (target) { 66 | target.dispatchEvent(LoadEvent); 67 | } 68 | }, 1000); 69 | 70 | setTimeout(() => { 71 | expect(container.innerHTML).toMatchSnapshot(); 72 | expect(global.document.head).toMatchSnapshot(); 73 | done(); 74 | }, 2000); 75 | }); 76 | 77 | it('Rendering without Children', async done => { 78 | const { container } = render(); 79 | 80 | setTimeout(() => { 81 | global.document.dispatchEvent(new Event('readystatechange')); 82 | }, 100); 83 | 84 | setTimeout(() => { 85 | const LoadEvent = document.createEvent('HTMLEvents'); 86 | LoadEvent.initEvent('load', true, true); 87 | const target = global.document.head.querySelector('script'); 88 | if (target) { 89 | target.dispatchEvent(LoadEvent); 90 | } 91 | }, 1000); 92 | 93 | setTimeout(() => { 94 | expect(container.innerHTML).toMatchSnapshot(); 95 | expect(global.document.head).toMatchSnapshot(); 96 | done(); 97 | }, 2000); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/GA4RComponents.timeout.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | import { GA4R } from '../src/index'; 5 | 6 | Object.defineProperty(global.document, 'readyState', { 7 | get() { 8 | return 'interactive'; 9 | }, 10 | }); 11 | 12 | beforeAll(() => { 13 | global.document.head.innerHTML = ''; 14 | }); 15 | 16 | describe('GA4R Components', () => { 17 | it('Rendering without Children, and with very shortest timeout', async done => { 18 | const { container } = render(); 19 | 20 | setTimeout(() => { 21 | global.document.dispatchEvent(new Event('readystatechange')); 22 | }, 100); 23 | 24 | setTimeout(() => { 25 | const LoadEvent = document.createEvent('HTMLEvents'); 26 | LoadEvent.initEvent('load', true, true); 27 | const target = global.document.head.querySelector('script'); 28 | if (target) { 29 | target.dispatchEvent(LoadEvent); 30 | } 31 | }, 1000); 32 | 33 | setTimeout(() => { 34 | expect(container.innerHTML).toMatchSnapshot(); 35 | expect(global.document.head).toMatchSnapshot(); 36 | done(); 37 | }, 2000); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/__snapshots__/GA4RComponents.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GA4R Components Rendering with Children 1`] = `"{}
Try
"`; 4 | 5 | exports[`GA4R Components Rendering with Children 2`] = ` 6 | 7 | 22 | 23 | `; 24 | 25 | exports[`GA4R Components Rendering with invalid Children 1`] = `"im not valid children element"`; 26 | 27 | exports[`GA4R Components Rendering with invalid Children 2`] = ` 28 | 29 | 44 | 45 | `; 46 | 47 | exports[`GA4R Components Rendering without Children 1`] = `""`; 48 | 49 | exports[`GA4R Components Rendering without Children 2`] = ` 50 | 51 | 66 | 67 | `; 68 | -------------------------------------------------------------------------------- /test/__snapshots__/GA4RComponents.timeout.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GA4R Components Rendering without Children, and with very shortest timeout 1`] = `""`; 4 | 5 | exports[`GA4R Components Rendering without Children, and with very shortest timeout 2`] = ` 6 | 7 | 20 | 21 | `; 22 | -------------------------------------------------------------------------------- /test/__snapshots__/ga4react.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ga4react initialized 1`] = ` 4 | Object { 5 | "event": [Function], 6 | "gtag": [Function], 7 | "pageview": [Function], 8 | } 9 | `; 10 | -------------------------------------------------------------------------------- /test/__snapshots__/ga4react2.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ga4react initialized 1`] = ` 4 | Object { 5 | "event": [Function], 6 | "gtag": [Function], 7 | "pageview": [Function], 8 | } 9 | `; 10 | -------------------------------------------------------------------------------- /test/__snapshots__/useGA4React.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GA4R hook Rendering 1`] = `"{}"`; 4 | 5 | exports[`GA4R hook Rendering 2`] = ` 6 | 7 | 20 | 21 | `; 22 | 23 | exports[`GA4R hook Rendering, but ga4React is initialized 1`] = `"{}"`; 24 | 25 | exports[`GA4R hook Rendering, but ga4React is initialized 2`] = ` 26 | 27 | 40 | 41 | `; 42 | -------------------------------------------------------------------------------- /test/__snapshots__/useGA4React.timeout.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GA4R hook Rendering with very short timeout 1`] = `""`; 4 | 5 | exports[`GA4R hook Rendering with very short timeout 2`] = ` 6 | 7 | 20 | 21 | `; 22 | -------------------------------------------------------------------------------- /test/__snapshots__/useGA4React.withoutGaCode.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GA4R hook Rendering with useGa4React without gaCode 1`] = `""`; 4 | 5 | exports[`GA4R hook Rendering with useGa4React without gaCode 2`] = ` 6 | 7 | 20 | 21 | `; 22 | -------------------------------------------------------------------------------- /test/__snapshots__/withTracker.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GA4R withTracker Rendering 1`] = `"{\\"path\\":\\"myCustomPath\\",\\"gaCode\\":\\"GA-CODE\\"}"`; 4 | 5 | exports[`GA4R withTracker Rendering 2`] = ` 6 | 7 | 20 | 21 | `; 22 | 23 | exports[`GA4R withTracker Rendering, after GA4React initialization 1`] = `"{\\"path\\":\\"myCustomPath\\",\\"gaCode\\":\\"GA-CODE\\"}"`; 24 | 25 | exports[`GA4R withTracker Rendering, after GA4React initialization 2`] = ` 26 | 27 | 40 | 41 | `; 42 | -------------------------------------------------------------------------------- /test/__snapshots__/withTracker.timeout.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GA4R withTracker Rendering but timeout is very short 1`] = `"{\\"path\\":\\"myCustomPath\\",\\"gaCode\\":\\"GA-CODE\\",\\"timeout\\":10}"`; 4 | 5 | exports[`GA4R withTracker Rendering but timeout is very short 2`] = ` 6 | 7 | 20 | 21 | `; 22 | -------------------------------------------------------------------------------- /test/ga4react.test.ts: -------------------------------------------------------------------------------- 1 | import GA4React from '../dist/index'; 2 | import '@testing-library/jest-dom/extend-expect'; 3 | import { GA4ReactInterface } from '../dist/models/gtagModels'; 4 | 5 | describe('ga4react', () => { 6 | it('initialized', async done => { 7 | const ga4react: GA4ReactInterface = new GA4React('CODE'); 8 | const ga4promise = ga4react.initialize().then( 9 | ga4 => { 10 | expect(ga4promise).toBeInstanceOf(Promise); 11 | expect(ga4).toMatchSnapshot(); 12 | done(); 13 | }, 14 | err => { 15 | console.error(err); 16 | } 17 | ); 18 | 19 | setTimeout(() => { 20 | global.document.dispatchEvent(new Event('readystatechange')); 21 | }, 100); 22 | 23 | setTimeout(() => { 24 | const LoadEvent = document.createEvent('HTMLEvents'); 25 | LoadEvent.initEvent('load', true, true); 26 | const target = global.document.head.querySelector('script'); 27 | if (target) { 28 | target.dispatchEvent(LoadEvent); 29 | } 30 | }, 1000); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/ga4react.timeout.test.ts: -------------------------------------------------------------------------------- 1 | import { GA4React } from '../dist/index'; 2 | import '@testing-library/jest-dom/extend-expect'; 3 | import { GA4ReactInterface } from '../dist/models/gtagModels'; 4 | 5 | describe('ga4react', () => { 6 | it('initialized with very short timeout, and without body event dispatch', async done => { 7 | const ga4react: GA4ReactInterface = new GA4React('CODE', {}, [], 10); 8 | ga4react.initialize().then( 9 | () => {}, 10 | err => { 11 | expect(err).toBeInstanceOf(Error); 12 | done(); 13 | } 14 | ); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/ga4react2.test.ts: -------------------------------------------------------------------------------- 1 | import { GA4ReactInterface } from '../dist/models/gtagModels'; 2 | import GA4React from '../dist/index'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | 5 | describe('ga4react', () => { 6 | it('initialized', async done => { 7 | const ga4react: GA4ReactInterface = new GA4React( 8 | 'CODE', 9 | {}, 10 | undefined, 11 | 5000, 12 | { 13 | nonce: ['custom-nonce0', 'custom-nonce1'], 14 | } 15 | ); 16 | const ga4promise = ga4react.initialize().then( 17 | ga4 => { 18 | expect(ga4promise).toBeInstanceOf(Promise); 19 | expect(ga4).toMatchSnapshot(); 20 | done(); 21 | }, 22 | err => { 23 | console.error(err); 24 | } 25 | ); 26 | 27 | setTimeout(() => { 28 | global.document.dispatchEvent(new Event('readystatechange')); 29 | }, 100); 30 | 31 | setTimeout(() => { 32 | const LoadEvent = document.createEvent('HTMLEvents'); 33 | LoadEvent.initEvent('load', true, true); 34 | const target = global.document.head.querySelector('script'); 35 | if (target) { 36 | target.dispatchEvent(LoadEvent); 37 | } 38 | }, 1000); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/useGA4React.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | import { useGA4React } from '../src/index'; 5 | 6 | Object.defineProperty(global.document, 'readyState', { 7 | get() { 8 | return 'interactive'; 9 | }, 10 | }); 11 | 12 | beforeAll(() => { 13 | global.document.head.innerHTML = ''; 14 | }); 15 | 16 | const Example: React.FC = () => { 17 | const ga4React = useGA4React('GA-CODE'); 18 | return <>{JSON.stringify(ga4React)}; 19 | }; 20 | 21 | describe('GA4R hook', () => { 22 | it('Rendering', async done => { 23 | const { container } = render(); 24 | 25 | setTimeout(() => { 26 | global.document.dispatchEvent(new Event('readystatechange')); 27 | }, 100); 28 | 29 | setTimeout(() => { 30 | const LoadEvent = document.createEvent('HTMLEvents'); 31 | LoadEvent.initEvent('load', true, true); 32 | const target = global.document.head.querySelector('script'); 33 | if (target) { 34 | target.dispatchEvent(LoadEvent); 35 | } 36 | }, 1000); 37 | 38 | setTimeout(() => { 39 | expect(container.innerHTML).toMatchSnapshot(); 40 | expect(global.document.head).toMatchSnapshot(); 41 | done(); 42 | }, 2000); 43 | }); 44 | 45 | it('Rendering, but ga4React is initialized', async done => { 46 | const { container } = render(); 47 | 48 | setTimeout(() => { 49 | global.document.dispatchEvent(new Event('readystatechange')); 50 | }, 100); 51 | 52 | setTimeout(() => { 53 | const LoadEvent = document.createEvent('HTMLEvents'); 54 | LoadEvent.initEvent('load', true, true); 55 | const target = global.document.head.querySelector('script'); 56 | if (target) { 57 | target.dispatchEvent(LoadEvent); 58 | } 59 | }, 1000); 60 | 61 | setTimeout(() => { 62 | expect(container.innerHTML).toMatchSnapshot(); 63 | expect(global.document.head).toMatchSnapshot(); 64 | done(); 65 | }, 2000); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/useGA4React.timeout.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | import { useGA4React } from '../src/index'; 5 | 6 | Object.defineProperty(global.document, 'readyState', { 7 | get() { 8 | return 'interactive'; 9 | }, 10 | }); 11 | 12 | beforeAll(() => { 13 | global.document.head.innerHTML = ''; 14 | }); 15 | 16 | const Example = () => { 17 | const ga4React = useGA4React('GA-CODE', {}, [], 10); 18 | return <>{JSON.stringify(ga4React)}; 19 | }; 20 | 21 | describe('GA4R hook', () => { 22 | it('Rendering with very short timeout', async done => { 23 | const { container } = render(); 24 | 25 | setTimeout(() => { 26 | global.document.dispatchEvent(new Event('readystatechange')); 27 | }, 100); 28 | 29 | setTimeout(() => { 30 | const LoadEvent = document.createEvent('HTMLEvents'); 31 | LoadEvent.initEvent('load', true, true); 32 | const target = global.document.head.querySelector('script'); 33 | if (target) { 34 | target.dispatchEvent(LoadEvent); 35 | } 36 | }, 1000); 37 | 38 | setTimeout(() => { 39 | expect(container.innerHTML).toMatchSnapshot(); 40 | expect(global.document.head).toMatchSnapshot(); 41 | done(); 42 | }, 2000); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/useGA4React.withoutGaCode.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | import { GA4React, useGA4React } from '../src/index'; 5 | 6 | Object.defineProperty(global.document, 'readyState', { 7 | get() { 8 | return 'interactive'; 9 | }, 10 | }); 11 | 12 | beforeAll(() => { 13 | global.document.head.innerHTML = ''; 14 | const init = new GA4React('GA-CODE'); 15 | init.initialize(); 16 | }); 17 | 18 | const Example = () => { 19 | const ga4React = useGA4React(); 20 | return <>{JSON.stringify(ga4React)}; 21 | }; 22 | 23 | describe('GA4R hook', () => { 24 | it('Rendering with useGa4React without gaCode', async done => { 25 | const { container } = render(); 26 | 27 | setTimeout(() => { 28 | global.document.dispatchEvent(new Event('readystatechange')); 29 | }, 100); 30 | 31 | setTimeout(() => { 32 | const LoadEvent = document.createEvent('HTMLEvents'); 33 | LoadEvent.initEvent('load', true, true); 34 | const target = global.document.head.querySelector('script'); 35 | if (target) { 36 | target.dispatchEvent(LoadEvent); 37 | } 38 | }, 1000); 39 | 40 | setTimeout(() => { 41 | expect(container.innerHTML).toMatchSnapshot(); 42 | expect(global.document.head).toMatchSnapshot(); 43 | done(); 44 | }, 2000); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/withTracker.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | import { withTracker } from '../src/index'; 5 | 6 | Object.defineProperty(global.document, 'readyState', { 7 | get() { 8 | return 'interactive'; 9 | }, 10 | }); 11 | 12 | beforeAll(() => { 13 | global.document.head.innerHTML = ''; 14 | }); 15 | 16 | const Tracker = withTracker(props => <>{JSON.stringify(props)}); 17 | 18 | describe('GA4R withTracker', () => { 19 | it('Rendering', async done => { 20 | const { container } = render( 21 | 22 | ); 23 | 24 | setTimeout(() => { 25 | global.document.dispatchEvent(new Event('readystatechange')); 26 | }, 100); 27 | 28 | setTimeout(() => { 29 | const LoadEvent = document.createEvent('HTMLEvents'); 30 | LoadEvent.initEvent('load', true, true); 31 | const target = global.document.head.querySelector('script'); 32 | if (target) { 33 | target.dispatchEvent(LoadEvent); 34 | } 35 | }, 1000); 36 | 37 | setTimeout(() => { 38 | expect(container.innerHTML).toMatchSnapshot(); 39 | expect(global.document.head).toMatchSnapshot(); 40 | done(); 41 | }, 2000); 42 | }); 43 | 44 | it('Rendering, after GA4React initialization', async done => { 45 | const { container } = render( 46 | 47 | ); 48 | 49 | setTimeout(() => { 50 | global.document.dispatchEvent(new Event('readystatechange')); 51 | }, 100); 52 | 53 | setTimeout(() => { 54 | const LoadEvent = document.createEvent('HTMLEvents'); 55 | LoadEvent.initEvent('load', true, true); 56 | const target = global.document.head.querySelector('script'); 57 | if (target) { 58 | target.dispatchEvent(LoadEvent); 59 | } 60 | }, 1000); 61 | 62 | setTimeout(() => { 63 | expect(container.innerHTML).toMatchSnapshot(); 64 | expect(global.document.head).toMatchSnapshot(); 65 | done(); 66 | }, 2000); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/withTracker.timeout.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | import { withTracker } from '../src/index'; 5 | 6 | Object.defineProperty(global.document, 'readyState', { 7 | get() { 8 | return 'interactive'; 9 | }, 10 | }); 11 | 12 | beforeAll(() => { 13 | global.document.head.innerHTML = ''; 14 | }); 15 | 16 | const Tracker = withTracker(props => <>{JSON.stringify(props)}); 17 | 18 | describe('GA4R withTracker', () => { 19 | it('Rendering but timeout is very short', async done => { 20 | const { container } = render( 21 | 22 | ); 23 | 24 | setTimeout(() => { 25 | global.document.dispatchEvent(new Event('readystatechange')); 26 | }, 100); 27 | 28 | setTimeout(() => { 29 | const LoadEvent = document.createEvent('HTMLEvents'); 30 | LoadEvent.initEvent('load', true, true); 31 | const target = global.document.head.querySelector('script'); 32 | if (target) { 33 | target.dispatchEvent(LoadEvent); 34 | } 35 | }, 1000); 36 | 37 | setTimeout(() => { 38 | expect(container.innerHTML).toMatchSnapshot(); 39 | expect(global.document.head).toMatchSnapshot(); 40 | done(); 41 | }, 2000); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | } 35 | } 36 | --------------------------------------------------------------------------------