Skip to content

Commit

Permalink
feat: add redux-beacon middleware for Matomo Tag Manager
Browse files Browse the repository at this point in the history
  • Loading branch information
dbartholomae committed Jun 14, 2019
1 parent c7de7a8 commit 30472a2
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .lintstagedrc
@@ -1,3 +1,3 @@
{
"src/**/*.{js,jsx,ts,tsx}": ["prettier-standard", "git add"]
"src/**/*.{js,jsx,ts,tsx}": ["prettier-standard", "npm run lint", "git add"]
}
33 changes: 23 additions & 10 deletions README.md
Expand Up @@ -10,25 +10,38 @@ Download node at [nodejs.org](http://nodejs.org) and install it, if you haven't
npm install redux-beacon-matomo-tag-manager --save
```

## Documentation

There is [additional documentation](https://dbartholomae.github.com/redux-beacon-matomo-tag-manager).

## Usage
1. Sign up for or install [Matomo Tag Manager](https://matomo.org/docs/tag-manager/) and create a new web container.
2. Add the Matomo Tag Manager container snippet to your site.
3. Install [redux-beacon](https://rangle.gitbook.io/redux-beacon/) in your app
4. Use this library to connect redux-beacon to the tag manager

```typescript
import { applyMiddleware, createStore } from 'redux'
import { createMiddleware } from 'redux-beacon'
import MatomoTagManager from 'redux-beacon-matomo-tag-manager'

// Create or import an events map.
// See "getting started" pages for instructions.
const ACTION_TYPE = 'ACTION_TYPE'

const options = {}
// Set up which actions should trigger which events or variables
const eventsMap = {
[ACTION_TYPE]: () => ({
event: 'integrationTestEvent'
})
}

const mtm = MatomoTagManager(options);
// Create the middleware
const matomoTagManager = MatomoTagManager()
const matomoTagManagerMiddleware = createMiddleware(eventsMap, matomoTagManager)

const gtmMiddleware = createMiddleware(eventsMap, mtm);
const gtmMetaReducer = createMetaReducer(eventsMap, menubar);
const store = createStore((state = {}) => state, applyMiddleware(matomoTagManagerMiddleware))

// When you dispatch an action, the middleware will trigger the event
store.dispatch({
type: ACTION_TYPE
})
```

## Thanks

Thanks to [Matomo](https://matomo.org) for supporting this project by providing an analytics property for integration testing this library.
1 change: 1 addition & 0 deletions jest.unit.config.js
Expand Up @@ -13,5 +13,6 @@ module.exports = {
statements: 100
}
},
testEnvironment: 'node',
testRegex: '.*\\.(test|spec)\\.(t|j)sx?$'
}
19 changes: 9 additions & 10 deletions src/index.spec.ts
@@ -1,12 +1,11 @@
import debug from 'debug'
import Module from './index'
import MatomoTagManager, { IMatomoTagManagerOptions } from './'

jest.mock('debug')
const mockedDebug = (debug as any) as jest.Mock<typeof debug>

test('should send debug message on start', () => {
const mockLogger = jest.fn()
mockedDebug.mockReturnValue(mockLogger as any)
new Module().start()
expect(mockLogger).toBeCalled()
describe('index', () => {
it('exports the MatomoTagManager', () => {
expect(MatomoTagManager).toBeDefined()
})
it('exports the IMatomoTagManagerOptions', () => {
const options: IMatomoTagManagerOptions = {}
expect(options).toBeDefined()
})
})
19 changes: 3 additions & 16 deletions src/index.ts
@@ -1,17 +1,4 @@
import debugFactory, { IDebugger } from 'debug'
import MatomoTagManager from './matomo-tag-manager'
export default MatomoTagManager

/** A documented example module */
export default class Module {
/** The logger used in the module */
private readonly logger: IDebugger

/** Creates a new Module */
constructor () {
this.logger = debugFactory('module-ts-template')
}

/** Starts the module */
start () {
this.logger('Starting module')
}
}
export { IMatomoTagManagerOptions } from './matomo-tag-manager'
6 changes: 6 additions & 0 deletions src/matomo-tag-manager.int-spec.ts
Expand Up @@ -11,4 +11,10 @@ describe('matomo-tag-manager', () => {
it('activates the test trigger on initialization', async () => {
expect(consoleListener).toHaveBeenCalledWith('Tag manager loaded')
})

it('triggers the integration test event on initialization', async () => {
expect(consoleListener).toHaveBeenCalledWith(
'Integration test event triggered'
)
})
})
76 changes: 76 additions & 0 deletions src/matomo-tag-manager.spec.ts
@@ -0,0 +1,76 @@
import MatomoTagManager from './matomo-tag-manager'

describe('matomo-tag-manager', () => {
describe('happy path', () => {
it('pushes events to _mtm by default', () => {
const push = jest.fn()
// @ts-ignore
global.window = { _mtm: { push } }

const matomoTagManager = MatomoTagManager()
const events = [
{
event: 'testEvent'
}
]

matomoTagManager(events)

expect(push).toHaveBeenCalledWith(events[0])
})

it('pushes events to a custom set datalayer', () => {
const push = jest.fn()
// @ts-ignore
global.window = { custom: { push } }

const matomoTagManager = MatomoTagManager({ dataLayerName: 'custom' })
const events = [
{
event: 'testEvent'
}
]

matomoTagManager(events)

expect(push).toHaveBeenCalledWith(events[0])
})
})

describe('sad path', () => {
it('does not do anything if window is not defined', () => {
// @ts-ignore
global.window = undefined
const matomoTagManager = MatomoTagManager()
expect(matomoTagManager).not.toThrow()
})

it('throws an error if the dataLayer is not defined', () => {
// @ts-ignore
global.window = {}
const matomoTagManager = MatomoTagManager()
expect(matomoTagManager).toThrow()
})

it('throws an error if the dataLayer does not have a push method', () => {
// @ts-ignore
global.window = { _mtm: {} }
const matomoTagManager = MatomoTagManager()
expect(matomoTagManager).toThrow()
})

it('does not push events that are not objects', () => {
const push = jest.fn()
// @ts-ignore
global.window = { _mtm: { push } }

const matomoTagManager = MatomoTagManager()
// tslint:disable-next-line:no-empty
const events = [() => {}]

matomoTagManager(events)

expect(push).not.toHaveBeenCalled()
})
})
})
35 changes: 35 additions & 0 deletions src/matomo-tag-manager.ts
@@ -0,0 +1,35 @@
import { Target } from 'redux-beacon'

/** Options for the redux-beacon Matomo tag manager */
export interface IMatomoTagManagerOptions {
/** The dataLayer used for pushing events and variables. By default '_mtm' */
dataLayerName?: string
}

const MatomoTagManager = ({
dataLayerName = '_mtm'
}: IMatomoTagManagerOptions = {}): Target => (events: object[]) => {
// tslint:disable-next-line:strict-type-predicates
if (typeof window === 'undefined') {
return
}

if (
!(window as any)[dataLayerName] ||
typeof (window as any)[dataLayerName].push !== 'function'
) {
throw new Error(
`redux-beacon error: window.${dataLayerName} is not defined. Have you forgotten to include Matomo Tag Manager and dataLayer?`
)
}

events.forEach(event => {
// tslint:disable-next-line:strict-type-predicates
if (typeof event === 'object') {
// tslint:disable-next-line:semicolon
;(window as any)[dataLayerName].push(event)
}
})
}

export default MatomoTagManager

0 comments on commit 30472a2

Please sign in to comment.