Skip to content

Commit

Permalink
[Feature] Access to component state data (#45)
Browse files Browse the repository at this point in the history
* [Feature] Add component's state in tracking data

* [Tests] Add test for passing state in track function

* [Lint] Fix linter errors

* [Tests] Add test for trackingData function arguments

* [Tests] Add comments, fix lint

* [Lint] Fix errors

* [Docs] Add state use case
  • Loading branch information
mennenia authored and tizmagik committed Sep 26, 2017
1 parent aaf9ed0 commit f6783d1
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 3 deletions.
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Alternatively, if you just want to just silence proptype errors when using [esli
### Usage as a Decorator
`react-tracking` is best used as a `@decorator()` using the [babel decorators plugin](https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy).

The decorator can be used on React Classes and on methods within those classes.
The decorator can be used on React Classes and on methods within those classes. If you use it on methods within these classes, make sure to decorate the class as well.

```js
import React from 'react';
Expand Down Expand Up @@ -213,7 +213,7 @@ export default class FooButton extends React.Component {

// In this case the tracking data depends on
// some unknown (until runtime) value
@track((props, [event]) => ({
@track((props, state, [event]) => ({
action: 'click',
label: event.currentTarget.title || event.currentTarget.textContent
}))
Expand Down Expand Up @@ -247,6 +247,20 @@ NOTE: That the above code utilizes some of the newer ES6 syntax. This is what it
})
// ...
```
### Accessing data stored in the component's `props` and `state`
Further runtime data, such as the component's `props` and `state`, are available as follows:
```js
@track((props, state) => ({
action: state.following ? "unfollow clicked" : "follow clicked"
name: props.name
}))
handleFollow = () => {
this.setState({ following: !this.state.following })
}
}
```
#### Example `props.tracking.getTrackingData()` usage
Expand Down
30 changes: 30 additions & 0 deletions src/__tests__/e2e.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ jest.setMock('../dispatchTrackingEvent', dispatchTrackingEvent);
const testDataContext = { testDataContext: true };
const testData = { testData: true };
const dispatch = jest.fn();
const testState = { booleanState: true };

describe('e2e', () => {
// eslint-disable-next-line global-require
Expand Down Expand Up @@ -362,6 +363,35 @@ describe('e2e', () => {
expect(dispatch).toHaveBeenCalledTimes(2); // pageview event and simulated button click
});

it('dispatches state data when components contain state', () => {
@track(testDataContext, { dispatch })
class TestOptions extends React.Component {
constructor() {
super();
this.state = {
booleanState: true,
};
}

@track((props, state) => ({ booleanState: state.booleanState }))
exampleMethod = () => {}

render() {
this.exampleMethod();
return <div />;
}
}

mount(<TestOptions />);


expect(dispatchTrackingEvent).not.toHaveBeenCalled();
expect(dispatch).toHaveBeenCalledWith({
...testDataContext,
...testState,
});
});

it('logs a console error when there is already a process defined on context', () => {
global.console.error = jest.fn();
const process = () => {};
Expand Down
40 changes: 40 additions & 0 deletions src/__tests__/trackEventMethodDecorator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,44 @@ describe('trackEventMethodDecorator', () => {
expect(trackEvent).toHaveBeenCalledWith(dummyData);
expect(spyTestEvent).toHaveBeenCalledWith('x');
});

it('properly passes through the correct arguments when trackingData is a function', () => {
const dummyData = {};
const trackingData = jest.fn(() => dummyData);
const trackEvent = jest.fn();
const spyTestEvent = jest.fn();
const dummyArgument = 'x';

class TestClass {
constructor() {
this.props = {
tracking: {
trackEvent,
},
};
this.state = {
myState: 'someState',
};
}

@trackEventMethodDecorator(trackingData)
handleTestEvent = spyTestEvent
}

const myTC = new TestClass();
myTC.handleTestEvent(dummyArgument);

// Access the trackingData arguments
const trackingDataArguments = trackingData.mock.calls[0];

expect(trackingData).toHaveBeenCalledTimes(1);
expect(trackingDataArguments[0]).toEqual(myTC.props);
expect(trackingDataArguments[1]).toEqual(myTC.state);
// Here we have access to the raw `arguments` object, which is not an actual Array,
// so in order to compare, we convert the arguments to an array.
expect(Array.from(trackingDataArguments[2])).toEqual([dummyArgument]);

expect(trackEvent).toHaveBeenCalledWith(dummyData);
expect(spyTestEvent).toHaveBeenCalledWith(dummyArgument);
});
});
2 changes: 1 addition & 1 deletion src/trackEventMethodDecorator.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default function trackEventMethodDecorator(trackingData = {}) {
return makeClassMemberDecorator(decoratedFn => function decorateClassMember() {
if (this.props && this.props.tracking && typeof this.props.tracking.trackEvent === 'function') {
const thisTrackingData = typeof trackingData === 'function'
? trackingData(this.props, arguments)
? trackingData(this.props, this.state, arguments)
: trackingData;
this.props.tracking.trackEvent(thisTrackingData);
}
Expand Down

0 comments on commit f6783d1

Please sign in to comment.