diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index 25f684f..0000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,5 +0,0 @@ -version: "2" -checks: - method-lines: - config: - threshold: 40 diff --git a/.eslintrc b/.eslintrc index 8aad754..722d92b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -11,6 +11,7 @@ "env": { "browser": true, "node": true, - "es6": true + "es6": true, + "jest": true } } diff --git a/.gitignore b/.gitignore index ed93680..f085cf0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /*.js node_modules package-lock.json +coverage \ No newline at end of file diff --git a/.npmignore b/.npmignore index 2276f67..b01cf90 100644 --- a/.npmignore +++ b/.npmignore @@ -4,3 +4,5 @@ src .editorconfig .eslintrc .travis.yml +coverage +__tests__ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 83a38c6..b66c824 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,17 @@ language: node_js node_js: node +before_script: + - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + - chmod +x ./cc-test-reporter + - ./cc-test-reporter before-build + +script: + - npm test + - npm run build + +after_script: + - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT + notifications: - email: false + email: false diff --git a/README.md b/README.md index 7bb15fd..b114cc0 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![npm package](https://img.shields.io/npm/v/gatsby-plugin-matomo.svg)](https://www.npmjs.com/package/gatsby-plugin-matomo) [![Build Status](https://travis-ci.com/kremalicious/gatsby-plugin-matomo.svg?branch=master)](https://travis-ci.com/kremalicious/gatsby-plugin-matomo) [![Maintainability](https://api.codeclimate.com/v1/badges/067339a02f2058f5ba01/maintainability)](https://codeclimate.com/github/kremalicious/gatsby-plugin-matomo/maintainability) +[![Test Coverage](https://api.codeclimate.com/v1/badges/067339a02f2058f5ba01/test_coverage)](https://codeclimate.com/github/kremalicious/gatsby-plugin-matomo/test_coverage) [![Greenkeeper badge](https://badges.greenkeeper.io/kremalicious/gatsby-plugin-matomo.svg)](https://greenkeeper.io/) > 🥂 Gatsby plugin to add Matomo (formerly Piwik) onto a site. https://kremalicious.com/gatsby-plugin-matomo/ diff --git a/package.json b/package.json index 510c90c..efffb36 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "version": "0.7.2", "author": "Matthias Kretschmann ", "scripts": { - "build": "babel src --out-dir . --ignore __tests__", - "start": "babel -w src --out-dir . --ignore __tests__", - "test": "eslint ./src/**/*.js", + "build": "babel src --out-dir . --ignore **/__tests__", + "start": "babel -w src --out-dir . --ignore **/__tests__", + "test": "npm run lint && jest --coverage", + "lint": "eslint ./src/**/*.js", "format": "prettier --write 'src/**/*.js'", "release": "release-it --non-interactive", "changelog": "auto-changelog -p", @@ -16,13 +17,16 @@ "devDependencies": { "@babel/cli": "^7.8.4", "@babel/core": "^7.8.4", + "@babel/runtime": "^7.8.4", "auto-changelog": "^1.16.2", "babel-preset-gatsby-package": "^0.2.16", "cross-env": "^7.0.0", "eslint": "^6.8.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", + "jest": "^25.1.0", "prettier": "^1.19.1", + "react": "^16.12.0", "release-it": "^12.4.3" }, "homepage": "https://kremalicious.com/gatsby-plugin-matomo", @@ -30,6 +34,7 @@ "gatsby", "gatsby-plugin", "analytics", + "tracking", "matomo", "piwik" ], diff --git a/src/__tests__/gatsby-browser.js b/src/__tests__/gatsby-browser.js new file mode 100644 index 0000000..c1236e3 --- /dev/null +++ b/src/__tests__/gatsby-browser.js @@ -0,0 +1,66 @@ +import { onRouteUpdate } from '../gatsby-browser' + +describe('gatsby-plugin-matomo', () => { + describe('gatsby-browser', () => { + beforeEach(() => { + jest.useFakeTimers() + window._paq = { push: jest.fn() } + }) + + afterEach(() => { + jest.resetAllMocks() + }) + + describe('onRouteUpdate', () => { + describe('in non-production env', () => { + beforeAll(() => { + window._paq = { push: jest.fn() } + }) + + it('does not send page view', () => { + onRouteUpdate({}) + expect(window._paq.push).not.toHaveBeenCalled() + }) + + it('sends page view in dev mode', () => { + window.dev = true + onRouteUpdate({}) + expect(window._paq.push).toHaveBeenCalledTimes(1) + }) + }) + + describe('in production env', () => { + let env + + beforeAll(() => { + env = process.env.NODE_ENV + process.env.NODE_ENV = 'production' + }) + + afterAll(() => { + process.env.NODE_ENV = env + }) + + it('does not send page view when _paq is undefined', () => { + delete window._paq + onRouteUpdate({}) + jest.runAllTimers() + expect(setTimeout).not.toHaveBeenCalled() + }) + + it('sends page view', () => { + onRouteUpdate({}) + jest.runAllTimers() + expect(window._paq.push).toHaveBeenCalledTimes(5) + }) + + it('uses setTimeout with a minimum delay of 32ms', () => { + onRouteUpdate({}) + jest.runAllTimers() + expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 32) + expect(window._paq.push).toHaveBeenCalledTimes(5) + }) + }) + }) + }) +}) diff --git a/src/__tests__/gatsby-ssr.js b/src/__tests__/gatsby-ssr.js new file mode 100644 index 0000000..1e70786 --- /dev/null +++ b/src/__tests__/gatsby-ssr.js @@ -0,0 +1,74 @@ +import { onRenderBody } from '../gatsby-ssr' + +describe('gatsby-plugin-google-analytics', () => { + describe('gatsby-ssr', () => { + describe('onRenderBody', () => { + describe('in non-production env', () => { + it('does not set tracking script', () => { + const setHeadComponents = jest.fn() + onRenderBody({ setHeadComponents }) + expect(setHeadComponents).not.toHaveBeenCalled() + }) + }) + + describe('in production env', () => { + let env + + beforeAll(() => { + env = process.env.NODE_ENV + process.env.NODE_ENV = 'production' + }) + + afterAll(() => { + process.env.NODE_ENV = env + }) + + const setup = options => { + const setHeadComponents = jest.fn() + const setPostBodyComponents = jest.fn() + + options = Object.assign( + { + siteId: 'TEST_SITE_ID', + matomoUrl: 'TEST_MATOMO_URL', + siteUrl: 'TEST_SITE_URL' + }, + options + ) + + onRenderBody({ setHeadComponents, setPostBodyComponents }, options) + + return { + setHeadComponents, + setPostBodyComponents + } + } + + it('sets tracking script', () => { + const { setHeadComponents, setPostBodyComponents } = setup() + + expect(setHeadComponents).toHaveBeenCalledTimes(1) + expect(setPostBodyComponents).toHaveBeenCalledTimes(1) + }) + + it('sets siteId', () => { + const { setPostBodyComponents } = setup() + const result = JSON.stringify(setPostBodyComponents.mock.calls[0][0]) + expect(result).toMatch(/TEST_SITE_ID/) + }) + + it('sets matomoUrl', () => { + const { setPostBodyComponents } = setup() + const result = JSON.stringify(setPostBodyComponents.mock.calls[0][0]) + expect(result).toMatch(/TEST_MATOMO_URL/) + }) + + it('sets siteUrl', () => { + const { setPostBodyComponents } = setup() + const result = JSON.stringify(setPostBodyComponents.mock.calls[0][0]) + expect(result).toMatch(/TEST_SITE_URL/) + }) + }) + }) + }) +}) diff --git a/src/gatsby-browser.js b/src/gatsby-browser.js index 93f5b7f..7a76e81 100644 --- a/src/gatsby-browser.js +++ b/src/gatsby-browser.js @@ -13,14 +13,11 @@ function getDuration() { } export const onRouteUpdate = ({ location, prevLocation }) => { - if ( - (process.env.NODE_ENV === 'production' && typeof _paq !== 'undefined') || - window.dev === true - ) { - const _paq = window._paq || [] - const dev = window.dev || null + if (process.env.NODE_ENV === 'production' || window.dev === true) { + if (!window._paq) return - const url = location.pathname + location.search + location.hash + const { _paq, dev } = window + const url = location && location.pathname + location.search + location.hash const prevUrl = prevLocation && prevLocation.pathname + prevLocation.search + prevLocation.hash @@ -42,14 +39,9 @@ export const onRouteUpdate = ({ location, prevLocation }) => { } } - if ('requestAnimationFrame' in window) { - requestAnimationFrame(() => { - requestAnimationFrame(sendPageView) - }) - } else { - // simulate 2 rAF calls - setTimeout(sendPageView, 32) - } + // Minimum delay for reactHelmet's requestAnimationFrame + const delay = Math.max(32, 0) + setTimeout(sendPageView, delay) if (first) { first = false diff --git a/src/gatsby-ssr.js b/src/gatsby-ssr.js index dc48de1..1dfaa1f 100644 --- a/src/gatsby-ssr.js +++ b/src/gatsby-ssr.js @@ -71,19 +71,21 @@ export const onRenderBody = ( { setHeadComponents, setPostBodyComponents, pathname }, pluginOptions ) => { - const { exclude, dev } = pluginOptions const isProduction = process.env.NODE_ENV === 'production' let excludePaths = ['/offline-plugin-app-shell-fallback/'] - if (typeof exclude !== 'undefined') { - exclude.map(exclude => { + if (pluginOptions && typeof pluginOptions.exclude !== 'undefined') { + pluginOptions.exclude.map(exclude => { excludePaths.push(exclude) }) } const isPathExcluded = excludePaths.some(path => pathname === path) - if ((isProduction || dev === true) && !isPathExcluded) { + if ( + (isProduction || (pluginOptions && pluginOptions.dev === true)) && + !isPathExcluded + ) { setHeadComponents([buildHead(pluginOptions)]) setPostBodyComponents([ buildTrackingCode(pluginOptions),